Skip to content

Commit

Permalink
More generic proxy with examples
Browse files Browse the repository at this point in the history
  • Loading branch information
albertolerda committed Sep 30, 2022
1 parent 48611cf commit 9f45293
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 44 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<p align="center">
<h1>Zenflows proxy</h1>

**HTTP proxy** for **services** commonly used by [interfacer-gui](https://github.com/dyne/interfacer-gui/)
</p>

# Currently known hosts
- [zenflows](https://fcos.interfacer.dyne.org/)
- [location autocomplete](https://autocomplete.search.hereapi.com/v1/autocomplete)
- [location lookup](https://lookup.search.hereapi.com/v1/lookup)

# Examples
See subdirectory `examples`
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# One line examples with curl
1 change: 1 addition & 0 deletions examples/run-location-autocomplete.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
curl -X GET http://localhost:8080/location-autocomplete/?q=Cuneo
1 change: 1 addition & 0 deletions examples/run-location-lookup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
curl -X GET http://localhost:8080/location-lookup/?id=here:cm:namedplace:20112331
1 change: 1 addition & 0 deletions examples/run-zenflows1.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
curl -X POST localhost:8080/zenflows/api -d "query{instanceVariables{specs{specCurrency{id}specProjectDesign{id}specProjectProduct{id}specProjectService{id}}units{unitOne{id}}}}"
1 change: 1 addition & 0 deletions examples/run-zenflows2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
curl -X POST localhost:8080/zenflows/api -d "query{instanceVariables{specs{specCurrency{id}specProjectDesign{id}specProjectProduct{id}specProjectService{id}}units{unitOne{id}}}}"
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/dyne/zenflows-proxy

go 1.18

require github.com/gorilla/mux v1.8.0 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
138 changes: 94 additions & 44 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,73 +1,123 @@
package main

import (
"strings"
"errors"
"fmt"
"io"
"net/http"
"os"
"time"
"io/ioutil"
"net/url"
)

func httpClient() *http.Client {
clientTimeout := 10 * time.Second
const clientTimeout = 10 * time.Second
// from https://pkg.go.dev/net/http#pkg-overview
// Clients and Transports are safe for concurrent use by multiple goroutines
// and for efficiency should only be created once and re-used.
// TODO: Look at https://mauricio.github.io/golang-proxies
var client = &http.Client{
Timeout: clientTimeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}

client := &http.Client{
Timeout: clientTimeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
// A ProxiedHost reppresent an host we will redirect to
// the `name` is just a string identifier (this will be the first name in the request path)
// `buildUrl` takes the input and create the url that will be used in the request of the proxy
// TODO: implement an authentication mechanism
type ProxiedHost struct {
name string
// authenticatd bool
buildUrl func(*url.URL) *url.URL
}

return client
// Currently know host we will proxy to
var proxiedHosts = []ProxiedHost {
ProxiedHost {
name: "zenflows",
buildUrl: func(u *url.URL) *url.URL {
paths := strings.SplitN(strings.TrimPrefix(u.Path, "/"), "/", 2)
currentUrl, _ := url.Parse("https://fcos.interfacer.dyne.org/api")
currentUrl.Path = paths[1]
return currentUrl
},
},
ProxiedHost {
name: "location-autocomplete",
buildUrl: func(u *url.URL) *url.URL {
values := u.Query()
values.Add("apiKey", os.Getenv("HERE_API"))
currentUrl, _ := url.Parse("https://autocomplete.search.hereapi.com/v1/autocomplete")
currentUrl.RawQuery = values.Encode()
return currentUrl
},
},
ProxiedHost {
name: "location-lookup",
buildUrl: func(u *url.URL) *url.URL {
values := u.Query()
values.Add("apiKey", os.Getenv("HERE_API"))
currentUrl, _ := url.Parse("https://lookup.search.hereapi.com/v1/lookup")
currentUrl.RawQuery = values.Encode()
return currentUrl
},
},
}

func getRoot(w http.ResponseWriter, r *http.Request) {
fmt.Printf("got / request\n")
io.WriteString(w, "This is my website!\n")
io.WriteString(w, "Here I'll put the list of proxied host\n")
}

// TODO: Look at https://mauricio.github.io/golang-proxies
func makeProxy(prefix string, proxiedHost string) func(http.ResponseWriter, *http.Request){
// For there is a client for each host I want to proxy
client := httpClient()

return func (w http.ResponseWriter, r *http.Request) {
url := fmt.Sprintf("%s/%s", proxiedHost, r.URL.Path[len(prefix)+1:])
func (p *ProxiedHost) proxyRequest (w http.ResponseWriter, r *http.Request) {
// The request has the path /destination_name/desired_path
if !strings.HasPrefix(r.URL.Path, "/" + p.name + "/") {
fmt.Printf("wrong destination")
os.Exit(1)
}

req, err := http.NewRequest(r.Method, url, r.Body)
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
os.Exit(1)
}
req.Header = r.Header
res, err := client.Do(req)
if res != nil && res.Body != nil {
defer func() {
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
}()
}
if err != nil {
fmt.Printf("client: error making http request: %s\n", err)
os.Exit(1)
}
// Read all the headers
for name, headers := range res.Header {
// Iterate all headers with one name (e.g. Content-Type)
for _, hdr := range headers {
w.Header().Add(name, hdr)
}
req, err := http.NewRequest(r.Method, p.buildUrl(r.URL).String(), r.Body)
if err != nil {
msg := fmt.Sprintf("client: could not create request: %s\n", err)
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte(msg))
return
}
req.Header = r.Header
res, err := client.Do(req)
if res != nil && res.Body != nil {
defer func() {
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
}()
}
if err != nil {
msg := fmt.Sprintf("client: error making http request: %s\n", err)
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte(msg))
return
}
// Read all the headers
for name, headers := range res.Header {
// Iterate all headers with one name (e.g. Content-Type)
for _, hdr := range headers {
w.Header().Add(name, hdr)
}
io.Copy(w, res.Body)
}
io.Copy(w, res.Body)
}

func (p *ProxiedHost) addHandle() (string, func(w http.ResponseWriter, r *http.Request)) {
return "/" + p.name + "/", p.proxyRequest
}

func main() {
//http.HandleFunc("/", getRoot)
http.HandleFunc("/zenflows/", makeProxy("/zenflows", "https://fcos.interfacer.dyne.org"))
http.HandleFunc("/", getRoot)
for i := 0; i<len(proxiedHosts); i++{
http.HandleFunc(proxiedHosts[i].addHandle())
}
err := http.ListenAndServe(":8080", nil)
if errors.Is(err, http.ErrServerClosed) {
fmt.Printf("server closed\n")
Expand Down

0 comments on commit 9f45293

Please sign in to comment.