From 0824f4bcd9a31add8e7cfa1662d2cb40900de073 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Sun, 27 Mar 2016 20:44:30 +0200 Subject: Encapsulate in package --- Dockerfile | 2 +- cmd/goxy/main.go | 28 +++++++++++++++++ cmd/goxyctl/main.go | 73 ++++++++++++++++++++++++++++++++++++++++++++ data.go | 26 ---------------- goxyctl/main.go | 87 ----------------------------------------------------- main.go | 34 --------------------- route.go | 42 +++++++++++++------------- rpc.go | 33 ++++++++++---------- server.go | 52 ++++++++++++++++++++++++++++++++ ws.go | 2 +- 10 files changed, 192 insertions(+), 187 deletions(-) create mode 100644 cmd/goxy/main.go create mode 100644 cmd/goxyctl/main.go delete mode 100644 data.go delete mode 100644 goxyctl/main.go delete mode 100644 main.go create mode 100644 server.go diff --git a/Dockerfile b/Dockerfile index ae1638b..343a2e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM golang ADD . /go/src/dim13.org/goxy -RUN go install dim13.org/goxy +RUN go install dim13.org/goxy/cmd/goxy VOLUME /go/src/dim13.org/goxy/data WORKDIR /go/src/dim13.org/goxy ENTRYPOINT /go/bin/goxy diff --git a/cmd/goxy/main.go b/cmd/goxy/main.go new file mode 100644 index 0000000..fc122e0 --- /dev/null +++ b/cmd/goxy/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "flag" + "log" + "net/http" + + "dim13.org/goxy" + + _ "net/http/pprof" +) + +var data = flag.String("data", "data/goxy.gob", "persistent storage file") + +func main() { + flag.Parse() + + server, err := goxy.NewServer(*data) + if err != nil { + log.Fatal(err) + } + + errc := make(chan error, 3) + go func() { errc <- server.ListenAndServe() }() + go func() { errc <- server.ListenAndServeTLS("", "") }() + go func() { errc <- http.ListenAndServe(":http-alt", nil) }() + log.Fatal(<-errc) +} diff --git a/cmd/goxyctl/main.go b/cmd/goxyctl/main.go new file mode 100644 index 0000000..1057fd4 --- /dev/null +++ b/cmd/goxyctl/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "flag" + "io/ioutil" + "log" + "net/rpc" + + "dim13.org/goxy" +) + +var ( + rpcserver = flag.String("server", ":http-alt", "RPC Server port") + servername = flag.String("host", "", "ServerName") + upstream = flag.String("upstream", "", "Upstream URL") + keyfile = flag.String("key", "", "TLS Key file") + certfile = flag.String("cert", "", "TLS Cert file") + remove = flag.Bool("remove", false, "Remove route") +) + +func loadCert(certFile, keyFile string) ([]byte, []byte) { + if certFile == "" || keyFile == "" { + return nil, nil + } + cert, err := ioutil.ReadFile(certFile) + if err != nil { + log.Fatal(err) + } + key, err := ioutil.ReadFile(keyFile) + if err != nil { + log.Fatal(err) + } + return cert, key +} + +func send(server string, e goxy.Entry, del bool) error { + client, err := rpc.DialHTTP("tcp", server) + if err != nil { + return err + } + defer client.Close() + + switch { + case e.ServerName != "" && e.Upstream != "": + log.Println("Add", e) + return client.Call("GoXY.Add", e, nil) + case e.ServerName != "" && del: + log.Println("Del", e) + return client.Call("GoXY.Del", e, nil) + default: + var r goxy.Route + err = client.Call("GoXY.List", struct{}{}, &r) + if err != nil { + return err + } + for k, v := range r { + log.Println(k, v) + } + } + return nil +} + +func main() { + var e goxy.Entry + flag.Parse() + + e.ServerName, e.Upstream = *servername, *upstream + e.Cert, e.Key = loadCert(*certfile, *keyfile) + + if err := send(*rpcserver, e, *remove); err != nil { + log.Fatal(err) + } +} diff --git a/data.go b/data.go deleted file mode 100644 index 474afae..0000000 --- a/data.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "encoding/gob" - "os" -) - -// Save routes to persistent file -func (r Route) Save(fname string) error { - fd, err := os.Create(fname) - if err != nil { - return err - } - defer fd.Close() - return gob.NewEncoder(fd).Encode(r) -} - -// Load routes from persistent file -func (r *Route) Load(fname string) error { - fd, err := os.Open(fname) - if err != nil { - return err - } - defer fd.Close() - return gob.NewDecoder(fd).Decode(r) -} diff --git a/goxyctl/main.go b/goxyctl/main.go deleted file mode 100644 index 5fbc1e8..0000000 --- a/goxyctl/main.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "flag" - "io/ioutil" - "log" - "net/rpc" -) - -var ( - rpcserver = flag.String("server", ":http-alt", "RPC Server port") - servername = flag.String("host", "", "ServerName") - upstream = flag.String("upstream", "", "Upstream URL") - keyfile = flag.String("key", "", "TLS Key file") - certfile = flag.String("cert", "", "TLS Cert file") - remove = flag.Bool("remove", false, "Remove route") -) - -// Entry contains routing settings -type Entry struct { - ServerName string - Upstream string - Cert []byte - Key []byte -} - -func (e Entry) String() string { - ret := e.ServerName + " → " + e.Upstream - if e.Cert != nil && e.Key != nil { - ret += " with TLS" - } - return ret -} - -func loadCert(certFile, keyFile string) ([]byte, []byte) { - if certFile == "" || keyFile == "" { - return nil, nil - } - cert, err := ioutil.ReadFile(certFile) - if err != nil { - log.Fatal(err) - } - key, err := ioutil.ReadFile(keyFile) - if err != nil { - log.Fatal(err) - } - return cert, key -} - -func send(server string, e Entry, del bool) error { - client, err := rpc.DialHTTP("tcp", server) - if err != nil { - return err - } - defer client.Close() - - switch { - case e.ServerName != "" && e.Upstream != "": - log.Println("Add", e) - return client.Call("GoXY.Add", e, nil) - case e.ServerName != "" && del: - log.Println("Del", e) - return client.Call("GoXY.Del", e, nil) - default: - var r []Entry - err = client.Call("GoXY.List", struct{}{}, &r) - if err != nil { - return err - } - for _, e := range r { - log.Println(e) - } - } - return nil -} - -func main() { - var e Entry - flag.Parse() - - e.ServerName, e.Upstream = *servername, *upstream - e.Cert, e.Key = loadCert(*certfile, *keyfile) - - if err := send(*rpcserver, e, *remove); err != nil { - log.Fatal(err) - } -} diff --git a/main.go b/main.go deleted file mode 100644 index 21e68da..0000000 --- a/main.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "crypto/tls" - "flag" - "log" - "net/http" - - _ "net/http/pprof" -) - -var ( - data = flag.String("data", "data/goxy.gob", "persistent storage file") - route = make(Route) - server = http.Server{ - Handler: http.NewServeMux(), - TLSConfig: &tls.Config{GetCertificate: route.GetCertificate}, - } -) - -func main() { - flag.Parse() - if err := route.Load(*data); err != nil { - log.Println(err) - } - if err := route.Restore(); err != nil { - log.Fatal(err) - } - errc := make(chan error) - go func() { errc <- server.ListenAndServe() }() - go func() { errc <- server.ListenAndServeTLS("", "") }() - go func() { errc <- http.ListenAndServe(":http-alt", nil) }() - log.Fatal(<-errc) -} diff --git a/route.go b/route.go index a695355..2fc38b3 100644 --- a/route.go +++ b/route.go @@ -1,4 +1,4 @@ -package main +package goxy import ( "crypto/tls" @@ -12,21 +12,29 @@ import ( // Route defines a set of routes including correspondent TLS certificates type Route map[string]Entry +// GetCertificate returns certificate for SNI negotiation +func (r Route) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) { + if e, ok := r[h.ServerName]; ok && e.cert != nil { + return e.cert, nil + } + return nil, errors.New("no cert for " + h.ServerName) +} + // Entry holds routing settings type Entry struct { ServerName string Upstream string - Cert []byte - Key []byte - cert *tls.Certificate + Cert []byte // PEM + Key []byte // PEM + cert *tls.Certificate // Parsed } -// GetCertificate returns certificate for SNI negotiation -func (r Route) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) { - if e, ok := r[h.ServerName]; ok && e.cert != nil { - return e.cert, nil +func (e Entry) String() string { + ret := e.ServerName + " → " + e.Upstream + if e.cert != nil { + ret += " with TLS" } - return nil, errors.New("no cert for " + h.ServerName) + return ret } func NewReverseProxy(target *url.URL) *httputil.ReverseProxy { @@ -39,16 +47,16 @@ func NewReverseProxy(target *url.URL) *httputil.ReverseProxy { } // Restore and update routes from in-memory state -func (r Route) Restore() error { +func (s *Server) Restore() error { mux := http.NewServeMux() - for k, v := range route { + for k, v := range s.Route { if v.Cert != nil && v.Key != nil { cert, err := tls.X509KeyPair(v.Cert, v.Key) if err != nil { return err } v.cert = &cert - r[k] = v + s.Route[k] = v } up, err := url.Parse(v.Upstream) if err != nil { @@ -65,14 +73,6 @@ func (r Route) Restore() error { mux.Handle(v.ServerName, NewReverseProxy(up)) } } - server.Handler = mux + s.Server.Handler = mux return nil } - -func (e Entry) String() string { - ret := e.ServerName + " → " + e.Upstream - if e.cert != nil { - ret += " with TLS" - } - return ret -} diff --git a/rpc.go b/rpc.go index 62a6a3f..bf7b36d 100644 --- a/rpc.go +++ b/rpc.go @@ -1,38 +1,37 @@ -package main +package goxy import ( "log" "net/rpc" ) -// GoXY defines RPC interface -type GoXY struct{} +type GoXY struct { + Server *Server +} -func init() { - rpc.Register(GoXY{}) +func Register(s *Server) { + rpc.Register(&GoXY{s}) rpc.HandleHTTP() } // Add adds a new route -func (GoXY) Add(e Entry, _ *struct{}) error { +func (s *GoXY) Add(e Entry, _ *struct{}) error { log.Println("Add route", e) - defer route.Save(*data) - route[e.ServerName] = e - return route.Restore() + defer s.Server.Save() + s.Server.Route[e.ServerName] = e + return s.Server.Restore() } // Del removes a route -func (GoXY) Del(e Entry, _ *struct{}) error { +func (s *GoXY) Del(e Entry, _ *struct{}) error { log.Println("Del route", e) - defer route.Save(*data) - delete(route, e.ServerName) - return route.Restore() + defer s.Server.Save() + delete(s.Server.Route, e.ServerName) + return s.Server.Restore() } // List routes -func (GoXY) List(_ struct{}, r *[]Entry) error { - for _, v := range route { - *r = append(*r, v) - } +func (s GoXY) List(_ struct{}, r *Route) error { + *r = s.Server.Route return nil } diff --git a/server.go b/server.go new file mode 100644 index 0000000..3d6651f --- /dev/null +++ b/server.go @@ -0,0 +1,52 @@ +package goxy + +import ( + "crypto/tls" + "encoding/gob" + "log" + "net/http" + "os" +) + +type Server struct { + Route + http.Server + Data string +} + +func NewServer(fname string) (*Server, error) { + r := make(Route) + s := http.Server{ + Handler: http.NewServeMux(), + TLSConfig: &tls.Config{GetCertificate: r.GetCertificate}, + } + server := &Server{Route: r, Server: s, Data: fname} + if err := server.Load(); err != nil { + log.Println(err) + } + if err := server.Restore(); err != nil { + return nil, err + } + Register(server) + return server, nil +} + +// Save routes to persistent file +func (s Server) Save() error { + fd, err := os.Create(s.Data) + if err != nil { + return err + } + defer fd.Close() + return gob.NewEncoder(fd).Encode(s.Route) +} + +// Load routes from persistent file +func (s *Server) Load() error { + fd, err := os.Open(s.Data) + if err != nil { + return err + } + defer fd.Close() + return gob.NewDecoder(fd).Decode(&s.Route) +} diff --git a/ws.go b/ws.go index a5c5d2f..b0199e8 100644 --- a/ws.go +++ b/ws.go @@ -1,4 +1,4 @@ -package main +package goxy import ( "io" -- cgit v1.2.3 From d21557ca8920525494670ab59474dd0c3a3517ad Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Sun, 27 Mar 2016 21:23:53 +0200 Subject: Add testing --- cmd/goxy/main.go | 7 +------ server.go | 8 ++++++++ server_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 server_test.go diff --git a/cmd/goxy/main.go b/cmd/goxy/main.go index fc122e0..7932bfa 100644 --- a/cmd/goxy/main.go +++ b/cmd/goxy/main.go @@ -3,7 +3,6 @@ package main import ( "flag" "log" - "net/http" "dim13.org/goxy" @@ -20,9 +19,5 @@ func main() { log.Fatal(err) } - errc := make(chan error, 3) - go func() { errc <- server.ListenAndServe() }() - go func() { errc <- server.ListenAndServeTLS("", "") }() - go func() { errc <- http.ListenAndServe(":http-alt", nil) }() - log.Fatal(<-errc) + log.Fatal(server.Start()) } diff --git a/server.go b/server.go index 3d6651f..6f66942 100644 --- a/server.go +++ b/server.go @@ -50,3 +50,11 @@ func (s *Server) Load() error { defer fd.Close() return gob.NewDecoder(fd).Decode(&s.Route) } + +func (s *Server) Start() error { + errc := make(chan error, 3) + go func() { errc <- s.ListenAndServe() }() + go func() { errc <- s.ListenAndServeTLS("", "") }() + go func() { errc <- http.ListenAndServe(":http-alt", nil) }() + return <-errc +} diff --git a/server_test.go b/server_test.go new file mode 100644 index 0000000..f3ad3d8 --- /dev/null +++ b/server_test.go @@ -0,0 +1,60 @@ +package goxy + +import ( + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/rpc" + "testing" +) + +const cannary = "hello from backend" + +func TestNewServer(t *testing.T) { + backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, cannary) + })) + defer backendServer.Close() + + server, err := NewServer("data.gob") + if err != nil { + t.Error(err) + } + + errc := make(chan error, 2) + go func() { errc <- server.ListenAndServe() }() + go func() { errc <- http.ListenAndServe(":http-alt", nil) }() + select { + case err := <-errc: + t.Error(err) + default: + } + + client, err := rpc.DialHTTP("tcp", ":http-alt") + if err != nil { + t.Error(err) + } + defer client.Close() + + e := Entry{ + ServerName: "localhost", + Upstream: backendServer.URL, + } + client.Call("GoXY.Add", e, nil) + + resp, err := http.Get("http://localhost") + if err != nil { + t.Error(err) + } + defer resp.Body.Close() + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Error(err) + } + + if string(b) != cannary { + t.Error("got", string(b), "expected", cannary) + } +} -- cgit v1.2.3 From 13ce0d0853fc888cf465742ed2e134e9bc5be77a Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Sun, 27 Mar 2016 22:15:22 +0200 Subject: Clenup RPC --- cmd/goxyctl/main.go | 16 ++++------------ rpc.go | 43 ++++++++++++++++++++++++++++++++++++++----- server.go | 2 +- server_test.go | 8 ++------ 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/cmd/goxyctl/main.go b/cmd/goxyctl/main.go index 1057fd4..d68eafa 100644 --- a/cmd/goxyctl/main.go +++ b/cmd/goxyctl/main.go @@ -4,7 +4,6 @@ import ( "flag" "io/ioutil" "log" - "net/rpc" "dim13.org/goxy" ) @@ -34,22 +33,15 @@ func loadCert(certFile, keyFile string) ([]byte, []byte) { } func send(server string, e goxy.Entry, del bool) error { - client, err := rpc.DialHTTP("tcp", server) - if err != nil { - return err - } - defer client.Close() - switch { case e.ServerName != "" && e.Upstream != "": log.Println("Add", e) - return client.Call("GoXY.Add", e, nil) + return goxy.Add(e, server) case e.ServerName != "" && del: - log.Println("Del", e) - return client.Call("GoXY.Del", e, nil) + log.Println("Del", e.ServerName) + return goxy.Del(e.ServerName, server) default: - var r goxy.Route - err = client.Call("GoXY.List", struct{}{}, &r) + r, err := goxy.List(server) if err != nil { return err } diff --git a/rpc.go b/rpc.go index bf7b36d..e7a6a20 100644 --- a/rpc.go +++ b/rpc.go @@ -14,7 +14,7 @@ func Register(s *Server) { rpc.HandleHTTP() } -// Add adds a new route +// Add adds a new route (server) func (s *GoXY) Add(e Entry, _ *struct{}) error { log.Println("Add route", e) defer s.Server.Save() @@ -22,16 +22,49 @@ func (s *GoXY) Add(e Entry, _ *struct{}) error { return s.Server.Restore() } -// Del removes a route -func (s *GoXY) Del(e Entry, _ *struct{}) error { - log.Println("Del route", e) +// Add new entry (client) +func Add(e Entry, server string) error { + client, err := rpc.DialHTTP("tcp", server) + if err != nil { + return err + } + defer client.Close() + return client.Call("GoXY.Add", e, nil) +} + +// Del removes a route (server) +func (s *GoXY) Del(serverName string, _ *struct{}) error { + log.Println("Del route", serverName) defer s.Server.Save() - delete(s.Server.Route, e.ServerName) + delete(s.Server.Route, serverName) return s.Server.Restore() } +// Del removes a route (client) +func Del(serverName string, server string) error { + client, err := rpc.DialHTTP("tcp", server) + if err != nil { + return err + } + defer client.Close() + return client.Call("GoXY.Del", serverName, nil) +} + // List routes func (s GoXY) List(_ struct{}, r *Route) error { *r = s.Server.Route return nil } + +func List(server string) (Route, error) { + client, err := rpc.DialHTTP("tcp", server) + if err != nil { + return nil, err + } + defer client.Close() + var r Route + if err := client.Call("GoXY.List", struct{}{}, &r); err != nil { + return nil, err + } + return r, nil +} diff --git a/server.go b/server.go index 6f66942..5225ffc 100644 --- a/server.go +++ b/server.go @@ -9,9 +9,9 @@ import ( ) type Server struct { + Data string Route http.Server - Data string } func NewServer(fname string) (*Server, error) { diff --git a/server_test.go b/server_test.go index f3ad3d8..8ef9a5a 100644 --- a/server_test.go +++ b/server_test.go @@ -22,13 +22,9 @@ func TestNewServer(t *testing.T) { t.Error(err) } - errc := make(chan error, 2) - go func() { errc <- server.ListenAndServe() }() - go func() { errc <- http.ListenAndServe(":http-alt", nil) }() - select { - case err := <-errc: + err = server.Start() + if err != nil { t.Error(err) - default: } client, err := rpc.DialHTTP("tcp", ":http-alt") -- cgit v1.2.3 From ad1d07b46509adc15670678480479edcf78370b2 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Sun, 27 Mar 2016 23:52:21 +0200 Subject: Tweak rpc --- rpc.go | 9 +++++++++ server.go | 4 ++-- server_test.go | 17 ++++++----------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/rpc.go b/rpc.go index e7a6a20..574993d 100644 --- a/rpc.go +++ b/rpc.go @@ -24,6 +24,9 @@ func (s *GoXY) Add(e Entry, _ *struct{}) error { // Add new entry (client) func Add(e Entry, server string) error { + if server == "" { + server = ":http-alt" + } client, err := rpc.DialHTTP("tcp", server) if err != nil { return err @@ -42,6 +45,9 @@ func (s *GoXY) Del(serverName string, _ *struct{}) error { // Del removes a route (client) func Del(serverName string, server string) error { + if server == "" { + server = ":http-alt" + } client, err := rpc.DialHTTP("tcp", server) if err != nil { return err @@ -57,6 +63,9 @@ func (s GoXY) List(_ struct{}, r *Route) error { } func List(server string) (Route, error) { + if server == "" { + server = ":http-alt" + } client, err := rpc.DialHTTP("tcp", server) if err != nil { return nil, err diff --git a/server.go b/server.go index 5225ffc..4cb40e0 100644 --- a/server.go +++ b/server.go @@ -51,10 +51,10 @@ func (s *Server) Load() error { return gob.NewDecoder(fd).Decode(&s.Route) } -func (s *Server) Start() error { +func (s *Server) Start() <-chan error { errc := make(chan error, 3) go func() { errc <- s.ListenAndServe() }() go func() { errc <- s.ListenAndServeTLS("", "") }() go func() { errc <- http.ListenAndServe(":http-alt", nil) }() - return <-errc + return errc } diff --git a/server_test.go b/server_test.go index 8ef9a5a..9dd10c9 100644 --- a/server_test.go +++ b/server_test.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "net/http" "net/http/httptest" - "net/rpc" "testing" ) @@ -22,22 +21,18 @@ func TestNewServer(t *testing.T) { t.Error(err) } - err = server.Start() - if err != nil { - t.Error(err) - } - - client, err := rpc.DialHTTP("tcp", ":http-alt") - if err != nil { + select { + case err := <-server.Start(): t.Error(err) + default: } - defer client.Close() - e := Entry{ + if err = Add(Entry{ ServerName: "localhost", Upstream: backendServer.URL, + }, ""); err != nil { + t.Error(err) } - client.Call("GoXY.Add", e, nil) resp, err := http.Get("http://localhost") if err != nil { -- cgit v1.2.3 From 6f2b0345b2593c3896448d22afe1539be4cc1be2 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Mon, 28 Mar 2016 00:40:51 +0200 Subject: Add Get call --- rpc.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/rpc.go b/rpc.go index 574993d..57bfa73 100644 --- a/rpc.go +++ b/rpc.go @@ -56,6 +56,25 @@ func Del(serverName string, server string) error { return client.Call("GoXY.Del", serverName, nil) } +func (s *GoXY) Get(serverName string, e *Entry) error { + *e = s.Server.Route[serverName] + return nil +} + +func Get(serverName string, server string) (Entry, error) { + if server == "" { + server = ":http-alt" + } + client, err := rpc.DialHTTP("tcp", server) + if err != nil { + return Entry{}, err + } + defer client.Close() + var e Entry + err = client.Call("GoXY.Get", serverName, &e) + return e, err +} + // List routes func (s GoXY) List(_ struct{}, r *Route) error { *r = s.Server.Route -- cgit v1.2.3 From a3633a98e48bd76b74016d171a7d32f86092cd47 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Mon, 28 Mar 2016 00:51:58 +0200 Subject: Unify --- rpc.go | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/rpc.go b/rpc.go index 57bfa73..f33456f 100644 --- a/rpc.go +++ b/rpc.go @@ -14,6 +14,13 @@ func Register(s *Server) { rpc.HandleHTTP() } +func DialRPC(server string) (*rpc.Client, error) { + if server == "" { + server = ":http-alt" + } + return rpc.DialHTTP("tcp", server) +} + // Add adds a new route (server) func (s *GoXY) Add(e Entry, _ *struct{}) error { log.Println("Add route", e) @@ -24,10 +31,7 @@ func (s *GoXY) Add(e Entry, _ *struct{}) error { // Add new entry (client) func Add(e Entry, server string) error { - if server == "" { - server = ":http-alt" - } - client, err := rpc.DialHTTP("tcp", server) + client, err := DialRPC(server) if err != nil { return err } @@ -45,10 +49,7 @@ func (s *GoXY) Del(serverName string, _ *struct{}) error { // Del removes a route (client) func Del(serverName string, server string) error { - if server == "" { - server = ":http-alt" - } - client, err := rpc.DialHTTP("tcp", server) + client, err := DialRPC(server) if err != nil { return err } @@ -62,10 +63,7 @@ func (s *GoXY) Get(serverName string, e *Entry) error { } func Get(serverName string, server string) (Entry, error) { - if server == "" { - server = ":http-alt" - } - client, err := rpc.DialHTTP("tcp", server) + client, err := DialRPC(server) if err != nil { return Entry{}, err } @@ -82,10 +80,7 @@ func (s GoXY) List(_ struct{}, r *Route) error { } func List(server string) (Route, error) { - if server == "" { - server = ":http-alt" - } - client, err := rpc.DialHTTP("tcp", server) + client, err := DialRPC(server) if err != nil { return nil, err } -- cgit v1.2.3 From 5215daf6ebf0316a57b09029ffe7b71608baafa1 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Mon, 28 Mar 2016 04:43:07 +0200 Subject: Not ready jet --- rpc.go | 7 +------ server.go | 5 +---- server_test.go | 28 ++++++++++++++++++++-------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/rpc.go b/rpc.go index f33456f..032c3f4 100644 --- a/rpc.go +++ b/rpc.go @@ -1,9 +1,6 @@ package goxy -import ( - "log" - "net/rpc" -) +import "net/rpc" type GoXY struct { Server *Server @@ -23,7 +20,6 @@ func DialRPC(server string) (*rpc.Client, error) { // Add adds a new route (server) func (s *GoXY) Add(e Entry, _ *struct{}) error { - log.Println("Add route", e) defer s.Server.Save() s.Server.Route[e.ServerName] = e return s.Server.Restore() @@ -41,7 +37,6 @@ func Add(e Entry, server string) error { // Del removes a route (server) func (s *GoXY) Del(serverName string, _ *struct{}) error { - log.Println("Del route", serverName) defer s.Server.Save() delete(s.Server.Route, serverName) return s.Server.Restore() diff --git a/server.go b/server.go index 4cb40e0..110d81b 100644 --- a/server.go +++ b/server.go @@ -16,10 +16,7 @@ type Server struct { func NewServer(fname string) (*Server, error) { r := make(Route) - s := http.Server{ - Handler: http.NewServeMux(), - TLSConfig: &tls.Config{GetCertificate: r.GetCertificate}, - } + s := http.Server{TLSConfig: &tls.Config{GetCertificate: r.GetCertificate}} server := &Server{Route: r, Server: s, Data: fname} if err := server.Load(); err != nil { log.Println(err) diff --git a/server_test.go b/server_test.go index 9dd10c9..9a8e651 100644 --- a/server_test.go +++ b/server_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "net/url" "testing" ) @@ -21,20 +22,31 @@ func TestNewServer(t *testing.T) { t.Error(err) } - select { - case err := <-server.Start(): + rpcServer := httptest.NewServer(nil) + defer rpcServer.Close() + + rpcURL, err := url.Parse(rpcServer.URL) + if err != nil { + t.Error(err) + } + + err = Add(Entry{ServerName: "whatever", Upstream: backendServer.URL}, rpcURL.Host) + if err != nil { t.Error(err) - default: } - if err = Add(Entry{ - ServerName: "localhost", - Upstream: backendServer.URL, - }, ""); err != nil { + frontendServer := httptest.NewServer(server.Handler) + if err != nil { t.Error(err) } - resp, err := http.Get("http://localhost") + client := &http.Client{} + req, err := http.NewRequest("GET", frontendServer.URL, nil) + if err != nil { + t.Error(err) + } + req.Header.Set("Host", "whatever") + resp, err := client.Do(req) if err != nil { t.Error(err) } -- cgit v1.2.3 From d97fab561070205d57f756015cf9e85a559a573e Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Mon, 28 Mar 2016 14:58:50 +0200 Subject: Gurad save and load --- server.go | 26 ++++++++++++++++---------- server_test.go | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/server.go b/server.go index 110d81b..0f0bdad 100644 --- a/server.go +++ b/server.go @@ -30,22 +30,28 @@ func NewServer(fname string) (*Server, error) { // Save routes to persistent file func (s Server) Save() error { - fd, err := os.Create(s.Data) - if err != nil { - return err + if s.Data != "" { + fd, err := os.Create(s.Data) + if err != nil { + return err + } + defer fd.Close() + return gob.NewEncoder(fd).Encode(s.Route) } - defer fd.Close() - return gob.NewEncoder(fd).Encode(s.Route) + return nil } // Load routes from persistent file func (s *Server) Load() error { - fd, err := os.Open(s.Data) - if err != nil { - return err + if s.Data != "" { + fd, err := os.Open(s.Data) + if err != nil { + return err + } + defer fd.Close() + return gob.NewDecoder(fd).Decode(&s.Route) } - defer fd.Close() - return gob.NewDecoder(fd).Decode(&s.Route) + return nil } func (s *Server) Start() <-chan error { diff --git a/server_test.go b/server_test.go index 9a8e651..d0920c5 100644 --- a/server_test.go +++ b/server_test.go @@ -17,7 +17,7 @@ func TestNewServer(t *testing.T) { })) defer backendServer.Close() - server, err := NewServer("data.gob") + server, err := NewServer("") if err != nil { t.Error(err) } -- cgit v1.2.3 From 9a4e0f70c967c3905e4430824f420ce2dd68a92f Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Mon, 28 Mar 2016 15:51:40 +0200 Subject: Pass test --- cmd/goxyctl/main.go | 13 ++++++++++--- route.go | 6 ++++++ rpc.go | 49 +++---------------------------------------------- server_test.go | 25 ++++++++++++++++--------- 4 files changed, 35 insertions(+), 58 deletions(-) diff --git a/cmd/goxyctl/main.go b/cmd/goxyctl/main.go index d68eafa..897dc0b 100644 --- a/cmd/goxyctl/main.go +++ b/cmd/goxyctl/main.go @@ -33,15 +33,22 @@ func loadCert(certFile, keyFile string) ([]byte, []byte) { } func send(server string, e goxy.Entry, del bool) error { + client, err := goxy.DialRPC(server) + if err != nil { + return err + } + defer client.Close() + switch { case e.ServerName != "" && e.Upstream != "": log.Println("Add", e) - return goxy.Add(e, server) + return client.Call("GoXY.Add", e, nil) case e.ServerName != "" && del: log.Println("Del", e.ServerName) - return goxy.Del(e.ServerName, server) + return client.Call("GoXY.Del", e.ServerName, nil) default: - r, err := goxy.List(server) + var r goxy.Route + err := client.Call("GoXY.List", struct{}{}, &r) if err != nil { return err } diff --git a/route.go b/route.go index 2fc38b3..8f77120 100644 --- a/route.go +++ b/route.go @@ -68,10 +68,16 @@ func (s *Server) Restore() error { //mux.Handle(v.ServerName, httputil.NewSingleHostReverseProxy(up)) switch up.Scheme { case "ws": + //log.Println("ws", v.ServerName, up) mux.Handle(v.ServerName, NewWebSocketProxy(up)) default: + //log.Println("http", v.ServerName, up) mux.Handle(v.ServerName, NewReverseProxy(up)) } + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + //log.Printf("%+v\n", r) + http.Error(w, "gone", http.StatusGone) + }) } s.Server.Handler = mux return nil diff --git a/rpc.go b/rpc.go index 032c3f4..b514c64 100644 --- a/rpc.go +++ b/rpc.go @@ -18,71 +18,28 @@ func DialRPC(server string) (*rpc.Client, error) { return rpc.DialHTTP("tcp", server) } -// Add adds a new route (server) +// Add adds a new route func (s *GoXY) Add(e Entry, _ *struct{}) error { defer s.Server.Save() s.Server.Route[e.ServerName] = e return s.Server.Restore() } -// Add new entry (client) -func Add(e Entry, server string) error { - client, err := DialRPC(server) - if err != nil { - return err - } - defer client.Close() - return client.Call("GoXY.Add", e, nil) -} - -// Del removes a route (server) +// Del removes a route func (s *GoXY) Del(serverName string, _ *struct{}) error { defer s.Server.Save() delete(s.Server.Route, serverName) return s.Server.Restore() } -// Del removes a route (client) -func Del(serverName string, server string) error { - client, err := DialRPC(server) - if err != nil { - return err - } - defer client.Close() - return client.Call("GoXY.Del", serverName, nil) -} - +// Get returns Entry func (s *GoXY) Get(serverName string, e *Entry) error { *e = s.Server.Route[serverName] return nil } -func Get(serverName string, server string) (Entry, error) { - client, err := DialRPC(server) - if err != nil { - return Entry{}, err - } - defer client.Close() - var e Entry - err = client.Call("GoXY.Get", serverName, &e) - return e, err -} - // List routes func (s GoXY) List(_ struct{}, r *Route) error { *r = s.Server.Route return nil } - -func List(server string) (Route, error) { - client, err := DialRPC(server) - if err != nil { - return nil, err - } - defer client.Close() - var r Route - if err := client.Call("GoXY.List", struct{}{}, &r); err != nil { - return nil, err - } - return r, nil -} diff --git a/server_test.go b/server_test.go index d0920c5..b83edcd 100644 --- a/server_test.go +++ b/server_test.go @@ -12,10 +12,10 @@ import ( const cannary = "hello from backend" func TestNewServer(t *testing.T) { - backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + backServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, cannary) })) - defer backendServer.Close() + defer backServer.Close() server, err := NewServer("") if err != nil { @@ -30,23 +30,30 @@ func TestNewServer(t *testing.T) { t.Error(err) } - err = Add(Entry{ServerName: "whatever", Upstream: backendServer.URL}, rpcURL.Host) + frontServer := httptest.NewServer(nil) if err != nil { t.Error(err) } - - frontendServer := httptest.NewServer(server.Handler) + frontURL, err := url.Parse(frontServer.URL) if err != nil { t.Error(err) } - client := &http.Client{} - req, err := http.NewRequest("GET", frontendServer.URL, nil) + rpcClient, err := DialRPC(rpcURL.Host) + if err != nil { + t.Error(err) + } + err = rpcClient.Call("GoXY.Add", Entry{ + ServerName: frontURL.Host, + Upstream: backServer.URL, + }, nil) if err != nil { t.Error(err) } - req.Header.Set("Host", "whatever") - resp, err := client.Do(req) + + frontServer.Config.Handler = server.Handler + + resp, err := http.Get(frontServer.URL) if err != nil { t.Error(err) } -- cgit v1.2.3 From dbb5e2ce7f1ff71e29d4065957d0ab603d2157c0 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Mon, 28 Mar 2016 17:27:56 +0200 Subject: Add WS test --- rpc.go | 9 +++++++-- server_test.go | 50 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/rpc.go b/rpc.go index b514c64..194bbf0 100644 --- a/rpc.go +++ b/rpc.go @@ -1,6 +1,11 @@ package goxy -import "net/rpc" +import ( + "net/rpc" + "sync" +) + +var once sync.Once type GoXY struct { Server *Server @@ -8,7 +13,7 @@ type GoXY struct { func Register(s *Server) { rpc.Register(&GoXY{s}) - rpc.HandleHTTP() + once.Do(rpc.HandleHTTP) } func DialRPC(server string) (*rpc.Client, error) { diff --git a/server_test.go b/server_test.go index b83edcd..a514b3c 100644 --- a/server_test.go +++ b/server_test.go @@ -1,17 +1,20 @@ package goxy import ( + "bytes" "io" "io/ioutil" "net/http" "net/http/httptest" "net/url" "testing" + + "golang.org/x/net/websocket" ) const cannary = "hello from backend" -func TestNewServer(t *testing.T) { +func TestReverseProxy(t *testing.T) { backServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, cannary) })) @@ -24,16 +27,13 @@ func TestNewServer(t *testing.T) { rpcServer := httptest.NewServer(nil) defer rpcServer.Close() - rpcURL, err := url.Parse(rpcServer.URL) if err != nil { t.Error(err) } frontServer := httptest.NewServer(nil) - if err != nil { - t.Error(err) - } + defer frontServer.Close() frontURL, err := url.Parse(frontServer.URL) if err != nil { t.Error(err) @@ -43,13 +43,11 @@ func TestNewServer(t *testing.T) { if err != nil { t.Error(err) } - err = rpcClient.Call("GoXY.Add", Entry{ - ServerName: frontURL.Host, - Upstream: backServer.URL, - }, nil) + err = rpcClient.Call("GoXY.Add", Entry{ServerName: frontURL.Host, Upstream: backServer.URL}, nil) if err != nil { t.Error(err) } + defer rpcClient.Call("GoXY.Del", frontURL.Host, nil) frontServer.Config.Handler = server.Handler @@ -67,4 +65,38 @@ func TestNewServer(t *testing.T) { if string(b) != cannary { t.Error("got", string(b), "expected", cannary) } + + // XXX WebSocket + + wsServer := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { + io.Copy(ws, ws) + })) + defer wsServer.Close() + wsURL, err := url.Parse(wsServer.URL) + if err != nil { + t.Error(err) + } + + err = rpcClient.Call("GoXY.Add", Entry{ServerName: frontURL.Host, Upstream: "ws://" + wsURL.Host}, nil) + if err != nil { + t.Error(err) + } + defer rpcClient.Call("GoXY.Del", frontURL.Host, nil) + + frontServer.Config.Handler = server.Handler + + ws, err := websocket.Dial("ws://"+frontURL.Host, "", "http://localhost") + if err != nil { + t.Error(err) + } + if _, err := ws.Write([]byte(cannary)); err != nil { + t.Error(err) + } + msg := make([]byte, len(cannary)) + if _, err := ws.Read(msg); err != nil { + t.Error(err) + } + if !bytes.Equal(msg, []byte(cannary)) { + t.Error("got", string(msg), "expected", cannary) + } } -- cgit v1.2.3 From 1c6056b6fcc22e4335683f6e0f28b7d73b2013e9 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Mon, 28 Mar 2016 19:49:29 +0200 Subject: Cleanup --- route.go | 50 -------------------------------------------------- server.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 50 deletions(-) diff --git a/route.go b/route.go index 8f77120..7596023 100644 --- a/route.go +++ b/route.go @@ -3,10 +3,6 @@ package goxy import ( "crypto/tls" "errors" - "net/http" - "net/http/httputil" - "net/url" - "strings" ) // Route defines a set of routes including correspondent TLS certificates @@ -36,49 +32,3 @@ func (e Entry) String() string { } return ret } - -func NewReverseProxy(target *url.URL) *httputil.ReverseProxy { - director := func(req *http.Request) { - //log.Println("director", req) - req.URL.Scheme = target.Scheme - req.URL.Host = target.Host - } - return &httputil.ReverseProxy{Director: director} -} - -// Restore and update routes from in-memory state -func (s *Server) Restore() error { - mux := http.NewServeMux() - for k, v := range s.Route { - if v.Cert != nil && v.Key != nil { - cert, err := tls.X509KeyPair(v.Cert, v.Key) - if err != nil { - return err - } - v.cert = &cert - s.Route[k] = v - } - up, err := url.Parse(v.Upstream) - if err != nil { - return err - } - if !strings.Contains(v.ServerName, "/") { - v.ServerName += "/" - } - //mux.Handle(v.ServerName, httputil.NewSingleHostReverseProxy(up)) - switch up.Scheme { - case "ws": - //log.Println("ws", v.ServerName, up) - mux.Handle(v.ServerName, NewWebSocketProxy(up)) - default: - //log.Println("http", v.ServerName, up) - mux.Handle(v.ServerName, NewReverseProxy(up)) - } - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - //log.Printf("%+v\n", r) - http.Error(w, "gone", http.StatusGone) - }) - } - s.Server.Handler = mux - return nil -} diff --git a/server.go b/server.go index 0f0bdad..437df4d 100644 --- a/server.go +++ b/server.go @@ -5,7 +5,10 @@ import ( "encoding/gob" "log" "net/http" + "net/http/httputil" + "net/url" "os" + "strings" ) type Server struct { @@ -54,6 +57,36 @@ func (s *Server) Load() error { return nil } +// Restore and update routes from in-memory state +func (s *Server) Restore() error { + mux := http.NewServeMux() + for k, v := range s.Route { + if v.Cert != nil && v.Key != nil { + cert, err := tls.X509KeyPair(v.Cert, v.Key) + if err != nil { + return err + } + v.cert = &cert + s.Route[k] = v + } + up, err := url.Parse(v.Upstream) + if err != nil { + return err + } + if !strings.Contains(v.ServerName, "/") { + v.ServerName += "/" + } + switch up.Scheme { + case "ws": + mux.Handle(v.ServerName, httputil.NewSingleHostReverseProxy(up)) + default: + mux.Handle(v.ServerName, NewReverseProxy(up)) + } + } + s.Server.Handler = mux + return nil +} + func (s *Server) Start() <-chan error { errc := make(chan error, 3) go func() { errc <- s.ListenAndServe() }() -- cgit v1.2.3 From 23d9978bd57a39371fdaa224b9158f720716b053 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Mon, 28 Mar 2016 19:50:51 +0200 Subject: Fix misplaced handler --- server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 437df4d..c0449ee 100644 --- a/server.go +++ b/server.go @@ -78,9 +78,9 @@ func (s *Server) Restore() error { } switch up.Scheme { case "ws": - mux.Handle(v.ServerName, httputil.NewSingleHostReverseProxy(up)) + mux.Handle(v.ServerName, NewWebSocketProxy(up)) default: - mux.Handle(v.ServerName, NewReverseProxy(up)) + mux.Handle(v.ServerName, httputil.NewSingleHostReverseProxy(up)) } } s.Server.Handler = mux -- cgit v1.2.3 From 7a450dc19250ac3baa160db0f371aaec2f984921 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Mon, 28 Mar 2016 19:51:22 +0200 Subject: Remove dead code --- ws.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ws.go b/ws.go index b0199e8..35760e1 100644 --- a/ws.go +++ b/ws.go @@ -5,7 +5,6 @@ import ( "net" "net/http" "net/url" - "strings" ) type WebSocketProxy struct { @@ -68,12 +67,3 @@ func NewWebSocketProxy(target *url.URL) *WebSocketProxy { } return &WebSocketProxy{Director: director} } - -func isWebsocket(req *http.Request) bool { - conn := req.Header.Get("Connection") - if strings.ToLower(conn) == "upgrade" { - upgrade := req.Header.Get("Upgrade") - return strings.ToLower(upgrade) == "websocket" - } - return false -} -- cgit v1.2.3 From 20ac254c678d769dff0bdfac7276e60b13a1ee80 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 12:23:34 +0200 Subject: kiss --- route.go | 4 ++-- rpc.go | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/route.go b/route.go index 7596023..c8c9191 100644 --- a/route.go +++ b/route.go @@ -18,8 +18,8 @@ func (r Route) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) // Entry holds routing settings type Entry struct { - ServerName string - Upstream string + ServerName string // HostName + Upstream string // URL Cert []byte // PEM Key []byte // PEM cert *tls.Certificate // Parsed diff --git a/rpc.go b/rpc.go index 194bbf0..2d843b3 100644 --- a/rpc.go +++ b/rpc.go @@ -1,19 +1,17 @@ package goxy -import ( - "net/rpc" - "sync" -) - -var once sync.Once +import "net/rpc" type GoXY struct { Server *Server } +func init() { + rpc.HandleHTTP() +} + func Register(s *Server) { rpc.Register(&GoXY{s}) - once.Do(rpc.HandleHTTP) } func DialRPC(server string) (*rpc.Client, error) { -- cgit v1.2.3 From 6d55850fdff1faff388fb2a0649739ea857bf52f Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 13:07:43 +0200 Subject: Cleanup a bit --- server.go | 22 ++++++++-------------- server_test.go | 9 +++++++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/server.go b/server.go index c0449ee..6f0fe48 100644 --- a/server.go +++ b/server.go @@ -3,7 +3,6 @@ package goxy import ( "crypto/tls" "encoding/gob" - "log" "net/http" "net/http/httputil" "net/url" @@ -12,7 +11,7 @@ import ( ) type Server struct { - Data string + DataFile string Route http.Server } @@ -20,21 +19,16 @@ type Server struct { func NewServer(fname string) (*Server, error) { r := make(Route) s := http.Server{TLSConfig: &tls.Config{GetCertificate: r.GetCertificate}} - server := &Server{Route: r, Server: s, Data: fname} - if err := server.Load(); err != nil { - log.Println(err) - } - if err := server.Restore(); err != nil { - return nil, err - } + server := &Server{Route: r, Server: s, DataFile: fname} + server.Load() Register(server) - return server, nil + return server, server.Restore() } // Save routes to persistent file func (s Server) Save() error { - if s.Data != "" { - fd, err := os.Create(s.Data) + if s.DataFile != "" { + fd, err := os.Create(s.DataFile) if err != nil { return err } @@ -46,8 +40,8 @@ func (s Server) Save() error { // Load routes from persistent file func (s *Server) Load() error { - if s.Data != "" { - fd, err := os.Open(s.Data) + if s.DataFile != "" { + fd, err := os.Open(s.DataFile) if err != nil { return err } diff --git a/server_test.go b/server_test.go index a514b3c..89f3baf 100644 --- a/server_test.go +++ b/server_test.go @@ -7,12 +7,16 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" "testing" "golang.org/x/net/websocket" ) -const cannary = "hello from backend" +const ( + cannary = "hello from backend" + dataFile = "test.gob" +) func TestReverseProxy(t *testing.T) { backServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -20,10 +24,11 @@ func TestReverseProxy(t *testing.T) { })) defer backServer.Close() - server, err := NewServer("") + server, err := NewServer(dataFile) if err != nil { t.Error(err) } + defer os.Remove(dataFile) rpcServer := httptest.NewServer(nil) defer rpcServer.Close() -- cgit v1.2.3 From 3fcb64b17e34832df64e5dbbd268e7dad4b2c240 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 13:22:56 +0200 Subject: Resort tests --- server_test.go | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/server_test.go b/server_test.go index 89f3baf..881a11a 100644 --- a/server_test.go +++ b/server_test.go @@ -19,17 +19,23 @@ const ( ) func TestReverseProxy(t *testing.T) { + // Backend server backServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, cannary) })) defer backServer.Close() - server, err := NewServer(dataFile) + // Websocket echo server + wsServer := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { + io.Copy(ws, ws) + })) + defer wsServer.Close() + wsURL, err := url.Parse(wsServer.URL) if err != nil { t.Error(err) } - defer os.Remove(dataFile) + // RPC Server rpcServer := httptest.NewServer(nil) defer rpcServer.Close() rpcURL, err := url.Parse(rpcServer.URL) @@ -37,6 +43,7 @@ func TestReverseProxy(t *testing.T) { t.Error(err) } + // Frontend server frontServer := httptest.NewServer(nil) defer frontServer.Close() frontURL, err := url.Parse(frontServer.URL) @@ -44,15 +51,27 @@ func TestReverseProxy(t *testing.T) { t.Error(err) } - rpcClient, err := DialRPC(rpcURL.Host) + // Initialize proxy server + server, err := NewServer(dataFile) if err != nil { t.Error(err) } - err = rpcClient.Call("GoXY.Add", Entry{ServerName: frontURL.Host, Upstream: backServer.URL}, nil) + defer os.Remove(dataFile) + + // Add routing entries + rpcClient, err := DialRPC(rpcURL.Host) if err != nil { t.Error(err) } - defer rpcClient.Call("GoXY.Del", frontURL.Host, nil) + + // Test HTTP proxy + e := Entry{ + ServerName: frontURL.Host, + Upstream: backServer.URL, + } + if err := rpcClient.Call("GoXY.Add", e, nil); err != nil { + t.Error(err) + } frontServer.Config.Handler = server.Handler @@ -71,22 +90,16 @@ func TestReverseProxy(t *testing.T) { t.Error("got", string(b), "expected", cannary) } - // XXX WebSocket + rpcClient.Call("GoXY.Del", frontURL.Host, nil) - wsServer := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { - io.Copy(ws, ws) - })) - defer wsServer.Close() - wsURL, err := url.Parse(wsServer.URL) - if err != nil { - t.Error(err) + // Test WebSocket proxy + e = Entry{ + ServerName: frontURL.Host, + Upstream: "ws://" + wsURL.Host, } - - err = rpcClient.Call("GoXY.Add", Entry{ServerName: frontURL.Host, Upstream: "ws://" + wsURL.Host}, nil) - if err != nil { + if err := rpcClient.Call("GoXY.Add", e, nil); err != nil { t.Error(err) } - defer rpcClient.Call("GoXY.Del", frontURL.Host, nil) frontServer.Config.Handler = server.Handler @@ -94,6 +107,7 @@ func TestReverseProxy(t *testing.T) { if err != nil { t.Error(err) } + if _, err := ws.Write([]byte(cannary)); err != nil { t.Error(err) } @@ -104,4 +118,6 @@ func TestReverseProxy(t *testing.T) { if !bytes.Equal(msg, []byte(cannary)) { t.Error("got", string(msg), "expected", cannary) } + + rpcClient.Call("GoXY.Del", frontURL.Host, nil) } -- cgit v1.2.3 From fa2bf59b65accbfd0ac2491d6c263ff7e7ad8f58 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 16:14:13 +0200 Subject: Switch to JSON for persistent storage --- cmd/goxy/main.go | 2 +- server.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/goxy/main.go b/cmd/goxy/main.go index 7932bfa..f4ce583 100644 --- a/cmd/goxy/main.go +++ b/cmd/goxy/main.go @@ -9,7 +9,7 @@ import ( _ "net/http/pprof" ) -var data = flag.String("data", "data/goxy.gob", "persistent storage file") +var data = flag.String("data", "data/goxy.json", "persistent storage file") func main() { flag.Parse() diff --git a/server.go b/server.go index 6f0fe48..159362a 100644 --- a/server.go +++ b/server.go @@ -2,7 +2,7 @@ package goxy import ( "crypto/tls" - "encoding/gob" + "encoding/json" "net/http" "net/http/httputil" "net/url" @@ -33,7 +33,7 @@ func (s Server) Save() error { return err } defer fd.Close() - return gob.NewEncoder(fd).Encode(s.Route) + return json.NewEncoder(fd).Encode(s.Route) } return nil } @@ -46,7 +46,7 @@ func (s *Server) Load() error { return err } defer fd.Close() - return gob.NewDecoder(fd).Decode(&s.Route) + return json.NewDecoder(fd).Decode(&s.Route) } return nil } @@ -81,10 +81,10 @@ func (s *Server) Restore() error { return nil } -func (s *Server) Start() <-chan error { - errc := make(chan error, 3) +func (s *Server) Start() error { + errc := make(chan error) go func() { errc <- s.ListenAndServe() }() go func() { errc <- s.ListenAndServeTLS("", "") }() go func() { errc <- http.ListenAndServe(":http-alt", nil) }() - return errc + return <-errc } -- cgit v1.2.3 From f7bc45795104b666c61473e52f0d9bda865744af Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 16:20:18 +0200 Subject: Wording --- cmd/goxyctl/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/goxyctl/main.go b/cmd/goxyctl/main.go index 897dc0b..78d65aa 100644 --- a/cmd/goxyctl/main.go +++ b/cmd/goxyctl/main.go @@ -9,12 +9,12 @@ import ( ) var ( - rpcserver = flag.String("server", ":http-alt", "RPC Server port") - servername = flag.String("host", "", "ServerName") + rpcserver = flag.String("server", ":http-alt", "RPC Server") + servername = flag.String("host", "", "Host URL") upstream = flag.String("upstream", "", "Upstream URL") keyfile = flag.String("key", "", "TLS Key file") certfile = flag.String("cert", "", "TLS Cert file") - remove = flag.Bool("remove", false, "Remove route") + remove = flag.Bool("remove", false, "Remove host") ) func loadCert(certFile, keyFile string) ([]byte, []byte) { -- cgit v1.2.3 From 0d1620f5deaa4df8cee1870d6dce707b38aa8db9 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 16:26:30 +0200 Subject: Rename --- cmd/goxyctl/main.go | 22 +++++++++++----------- route.go | 12 ++++++------ rpc.go | 2 +- server.go | 8 ++++---- server_test.go | 8 ++++---- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cmd/goxyctl/main.go b/cmd/goxyctl/main.go index 78d65aa..c7a0d07 100644 --- a/cmd/goxyctl/main.go +++ b/cmd/goxyctl/main.go @@ -9,12 +9,12 @@ import ( ) var ( - rpcserver = flag.String("server", ":http-alt", "RPC Server") - servername = flag.String("host", "", "Host URL") - upstream = flag.String("upstream", "", "Upstream URL") - keyfile = flag.String("key", "", "TLS Key file") - certfile = flag.String("cert", "", "TLS Cert file") - remove = flag.Bool("remove", false, "Remove host") + rpcserver = flag.String("server", ":http-alt", "RPC Server") + host = flag.String("host", "", "Host URL") + upstream = flag.String("upstream", "", "Upstream URL") + keyfile = flag.String("key", "", "TLS Key file") + certfile = flag.String("cert", "", "TLS Cert file") + remove = flag.Bool("remove", false, "Remove host") ) func loadCert(certFile, keyFile string) ([]byte, []byte) { @@ -40,12 +40,12 @@ func send(server string, e goxy.Entry, del bool) error { defer client.Close() switch { - case e.ServerName != "" && e.Upstream != "": + case e.Host != "" && e.Upstream != "": log.Println("Add", e) return client.Call("GoXY.Add", e, nil) - case e.ServerName != "" && del: - log.Println("Del", e.ServerName) - return client.Call("GoXY.Del", e.ServerName, nil) + case e.Host != "" && del: + log.Println("Del", e.Host) + return client.Call("GoXY.Del", e.Host, nil) default: var r goxy.Route err := client.Call("GoXY.List", struct{}{}, &r) @@ -63,7 +63,7 @@ func main() { var e goxy.Entry flag.Parse() - e.ServerName, e.Upstream = *servername, *upstream + e.Host, e.Upstream = *host, *upstream e.Cert, e.Key = loadCert(*certfile, *keyfile) if err := send(*rpcserver, e, *remove); err != nil { diff --git a/route.go b/route.go index c8c9191..3c83bef 100644 --- a/route.go +++ b/route.go @@ -18,15 +18,15 @@ func (r Route) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) // Entry holds routing settings type Entry struct { - ServerName string // HostName - Upstream string // URL - Cert []byte // PEM - Key []byte // PEM - cert *tls.Certificate // Parsed + Host string // HostName + Upstream string // URL + Cert []byte // PEM + Key []byte // PEM + cert *tls.Certificate // Parsed } func (e Entry) String() string { - ret := e.ServerName + " → " + e.Upstream + ret := e.Host + " → " + e.Upstream if e.cert != nil { ret += " with TLS" } diff --git a/rpc.go b/rpc.go index 2d843b3..1bff95f 100644 --- a/rpc.go +++ b/rpc.go @@ -24,7 +24,7 @@ func DialRPC(server string) (*rpc.Client, error) { // Add adds a new route func (s *GoXY) Add(e Entry, _ *struct{}) error { defer s.Server.Save() - s.Server.Route[e.ServerName] = e + s.Server.Route[e.Host] = e return s.Server.Restore() } diff --git a/server.go b/server.go index 159362a..c19f3dd 100644 --- a/server.go +++ b/server.go @@ -67,14 +67,14 @@ func (s *Server) Restore() error { if err != nil { return err } - if !strings.Contains(v.ServerName, "/") { - v.ServerName += "/" + if !strings.Contains(v.Host, "/") { + v.Host += "/" } switch up.Scheme { case "ws": - mux.Handle(v.ServerName, NewWebSocketProxy(up)) + mux.Handle(v.Host, NewWebSocketProxy(up)) default: - mux.Handle(v.ServerName, httputil.NewSingleHostReverseProxy(up)) + mux.Handle(v.Host, httputil.NewSingleHostReverseProxy(up)) } } s.Server.Handler = mux diff --git a/server_test.go b/server_test.go index 881a11a..12e6a53 100644 --- a/server_test.go +++ b/server_test.go @@ -66,8 +66,8 @@ func TestReverseProxy(t *testing.T) { // Test HTTP proxy e := Entry{ - ServerName: frontURL.Host, - Upstream: backServer.URL, + Host: frontURL.Host, + Upstream: backServer.URL, } if err := rpcClient.Call("GoXY.Add", e, nil); err != nil { t.Error(err) @@ -94,8 +94,8 @@ func TestReverseProxy(t *testing.T) { // Test WebSocket proxy e = Entry{ - ServerName: frontURL.Host, - Upstream: "ws://" + wsURL.Host, + Host: frontURL.Host, + Upstream: "ws://" + wsURL.Host, } if err := rpcClient.Call("GoXY.Add", e, nil); err != nil { t.Error(err) -- cgit v1.2.3 From 19934249bca032ab39bcc1d3ab9eee500d3b1468 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 16:29:55 +0200 Subject: Shroten flag name --- cmd/goxyctl/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/goxyctl/main.go b/cmd/goxyctl/main.go index c7a0d07..e323a1b 100644 --- a/cmd/goxyctl/main.go +++ b/cmd/goxyctl/main.go @@ -11,7 +11,7 @@ import ( var ( rpcserver = flag.String("server", ":http-alt", "RPC Server") host = flag.String("host", "", "Host URL") - upstream = flag.String("upstream", "", "Upstream URL") + upstream = flag.String("up", "", "Upstream URL") keyfile = flag.String("key", "", "TLS Key file") certfile = flag.String("cert", "", "TLS Cert file") remove = flag.Bool("remove", false, "Remove host") -- cgit v1.2.3 From bed5281851cf8dbbf512c88c71d6686c481e8119 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 16:40:55 +0200 Subject: Consistent naming --- rpc.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rpc.go b/rpc.go index 1bff95f..3dc113c 100644 --- a/rpc.go +++ b/rpc.go @@ -29,15 +29,15 @@ func (s *GoXY) Add(e Entry, _ *struct{}) error { } // Del removes a route -func (s *GoXY) Del(serverName string, _ *struct{}) error { +func (s *GoXY) Del(host string, _ *struct{}) error { defer s.Server.Save() - delete(s.Server.Route, serverName) + delete(s.Server.Route, host) return s.Server.Restore() } // Get returns Entry -func (s *GoXY) Get(serverName string, e *Entry) error { - *e = s.Server.Route[serverName] +func (s *GoXY) Get(host string, e *Entry) error { + *e = s.Server.Route[host] return nil } -- cgit v1.2.3 From c1057f8fbf703b8fb18bb779d94771d2738ec073 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 17:04:01 +0200 Subject: Simplify --- cmd/goxyctl/main.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/cmd/goxyctl/main.go b/cmd/goxyctl/main.go index e323a1b..dd7c86d 100644 --- a/cmd/goxyctl/main.go +++ b/cmd/goxyctl/main.go @@ -17,19 +17,20 @@ var ( remove = flag.Bool("remove", false, "Remove host") ) -func loadCert(certFile, keyFile string) ([]byte, []byte) { - if certFile == "" || keyFile == "" { - return nil, nil - } - cert, err := ioutil.ReadFile(certFile) - if err != nil { - log.Fatal(err) - } - key, err := ioutil.ReadFile(keyFile) - if err != nil { - log.Fatal(err) +func getEntry() (e goxy.Entry, err error) { + e.Host = *host + e.Upstream = *upstream + if *certfile != "" && *keyfile != "" { + e.Cert, err = ioutil.ReadFile(*certfile) + if err != nil { + return + } + e.Key, err = ioutil.ReadFile(*keyfile) + if err != nil { + return + } } - return cert, key + return } func send(server string, e goxy.Entry, del bool) error { @@ -60,11 +61,12 @@ func send(server string, e goxy.Entry, del bool) error { } func main() { - var e goxy.Entry flag.Parse() - e.Host, e.Upstream = *host, *upstream - e.Cert, e.Key = loadCert(*certfile, *keyfile) + e, err := getEntry() + if err != nil { + log.Fatal(err) + } if err := send(*rpcserver, e, *remove); err != nil { log.Fatal(err) -- cgit v1.2.3 From 17e9094ebc0ca4a7d5c20dccf39c48bfa08449fd Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 20:06:42 +0200 Subject: Unexport server --- rpc.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rpc.go b/rpc.go index 3dc113c..49b9d21 100644 --- a/rpc.go +++ b/rpc.go @@ -3,7 +3,7 @@ package goxy import "net/rpc" type GoXY struct { - Server *Server + server *Server } func init() { @@ -23,26 +23,26 @@ func DialRPC(server string) (*rpc.Client, error) { // Add adds a new route func (s *GoXY) Add(e Entry, _ *struct{}) error { - defer s.Server.Save() - s.Server.Route[e.Host] = e - return s.Server.Restore() + defer s.server.Save() + s.server.Route[e.Host] = e + return s.server.Restore() } // Del removes a route func (s *GoXY) Del(host string, _ *struct{}) error { - defer s.Server.Save() - delete(s.Server.Route, host) - return s.Server.Restore() + defer s.server.Save() + delete(s.server.Route, host) + return s.server.Restore() } // Get returns Entry func (s *GoXY) Get(host string, e *Entry) error { - *e = s.Server.Route[host] + *e = s.server.Route[host] return nil } // List routes func (s GoXY) List(_ struct{}, r *Route) error { - *r = s.Server.Route + *r = s.server.Route return nil } -- cgit v1.2.3 From c1cbde5c9670f8cd08894a3d46b3ba114a314657 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 20:06:58 +0200 Subject: Add mapping notes --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 8d9a1d8..14e7def 100644 --- a/README.md +++ b/README.md @@ -60,3 +60,16 @@ Total: 1 100 223.2 37 1424 docker build -t goxy . docker run -d --name goxy -p 192.168.243.5:80:80 -p 192.168.243.5:443:443 --net testnet goxy + +## Mapping + +### Host scheme + http://host/path -> http only + https://host/path -> http redirect to https, cert required + ws, wss -- ? + +### Upstream scheme + http://backend/path + https://backend/path + ws://backend/path + wss://backend/path -- cgit v1.2.3 From b557529d33e781b677eb2edc7b6f54307695e419 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 20:21:30 +0200 Subject: Abort early --- server.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/server.go b/server.go index c19f3dd..e7ba6cd 100644 --- a/server.go +++ b/server.go @@ -27,28 +27,28 @@ func NewServer(fname string) (*Server, error) { // Save routes to persistent file func (s Server) Save() error { - if s.DataFile != "" { - fd, err := os.Create(s.DataFile) - if err != nil { - return err - } - defer fd.Close() - return json.NewEncoder(fd).Encode(s.Route) + if s.DataFile == "" { + return nil } - return nil + fd, err := os.Create(s.DataFile) + if err != nil { + return err + } + defer fd.Close() + return json.NewEncoder(fd).Encode(s.Route) } // Load routes from persistent file func (s *Server) Load() error { - if s.DataFile != "" { - fd, err := os.Open(s.DataFile) - if err != nil { - return err - } - defer fd.Close() - return json.NewDecoder(fd).Decode(&s.Route) + if s.DataFile == "" { + return nil } - return nil + fd, err := os.Open(s.DataFile) + if err != nil { + return err + } + defer fd.Close() + return json.NewDecoder(fd).Decode(&s.Route) } // Restore and update routes from in-memory state -- cgit v1.2.3 From b0eac3a85d7b2ddeb82214d4d4527d0dd73f5523 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 29 Mar 2016 21:16:51 +0200 Subject: Add TODO --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 14e7def..2d6ddc5 100644 --- a/README.md +++ b/README.md @@ -73,3 +73,7 @@ Total: 1 100 223.2 37 1424 https://backend/path ws://backend/path wss://backend/path + +## TODO +- rewrite for 2 (3?) independend mappings (http, https, ws, wss) +- improve testing -- cgit v1.2.3 From 3071e59632c668696f8bf1c6d9e3bc07b61f6391 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Wed, 30 Mar 2016 11:17:28 +0200 Subject: Cleanup --- route.go | 22 ++++++++++++++++++++++ rpc.go | 15 ++++++--------- server.go | 42 ++++++++---------------------------------- server_test.go | 2 +- 4 files changed, 37 insertions(+), 44 deletions(-) diff --git a/route.go b/route.go index 3c83bef..93a18d3 100644 --- a/route.go +++ b/route.go @@ -2,7 +2,9 @@ package goxy import ( "crypto/tls" + "encoding/json" "errors" + "os" ) // Route defines a set of routes including correspondent TLS certificates @@ -16,6 +18,26 @@ func (r Route) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) return nil, errors.New("no cert for " + h.ServerName) } +// Save routes to persistent file +func (r Route) Save(fname string) error { + fd, err := os.Create(fname) + if err != nil { + return err + } + defer fd.Close() + return json.NewEncoder(fd).Encode(r) +} + +// Load routes from persistent file +func (r *Route) Load(fname string) error { + fd, err := os.Open(fname) + if err != nil { + return err + } + defer fd.Close() + return json.NewDecoder(fd).Decode(r) +} + // Entry holds routing settings type Entry struct { Host string // HostName diff --git a/rpc.go b/rpc.go index 49b9d21..e6a674d 100644 --- a/rpc.go +++ b/rpc.go @@ -10,29 +10,26 @@ func init() { rpc.HandleHTTP() } -func Register(s *Server) { - rpc.Register(&GoXY{s}) +func RegisterRPC(s *Server) error { + return rpc.Register(&GoXY{s}) } func DialRPC(server string) (*rpc.Client, error) { - if server == "" { - server = ":http-alt" - } return rpc.DialHTTP("tcp", server) } // Add adds a new route func (s *GoXY) Add(e Entry, _ *struct{}) error { - defer s.server.Save() + defer s.server.Save(s.server.DataFile) s.server.Route[e.Host] = e - return s.server.Restore() + return s.server.Update() } // Del removes a route func (s *GoXY) Del(host string, _ *struct{}) error { - defer s.server.Save() + defer s.server.Save(s.server.DataFile) delete(s.server.Route, host) - return s.server.Restore() + return s.server.Update() } // Get returns Entry diff --git a/server.go b/server.go index e7ba6cd..9300497 100644 --- a/server.go +++ b/server.go @@ -2,11 +2,9 @@ package goxy import ( "crypto/tls" - "encoding/json" "net/http" "net/http/httputil" "net/url" - "os" "strings" ) @@ -16,43 +14,19 @@ type Server struct { http.Server } -func NewServer(fname string) (*Server, error) { +func NewServer(dataFile string) (*Server, error) { r := make(Route) s := http.Server{TLSConfig: &tls.Config{GetCertificate: r.GetCertificate}} - server := &Server{Route: r, Server: s, DataFile: fname} - server.Load() - Register(server) - return server, server.Restore() -} - -// Save routes to persistent file -func (s Server) Save() error { - if s.DataFile == "" { - return nil - } - fd, err := os.Create(s.DataFile) - if err != nil { - return err - } - defer fd.Close() - return json.NewEncoder(fd).Encode(s.Route) -} - -// Load routes from persistent file -func (s *Server) Load() error { - if s.DataFile == "" { - return nil - } - fd, err := os.Open(s.DataFile) - if err != nil { - return err + server := &Server{Route: r, Server: s, DataFile: dataFile} + if dataFile != "" { + server.Load(dataFile) } - defer fd.Close() - return json.NewDecoder(fd).Decode(&s.Route) + RegisterRPC(server) + return server, server.Update() } -// Restore and update routes from in-memory state -func (s *Server) Restore() error { +// Update routes from in-memory state +func (s *Server) Update() error { mux := http.NewServeMux() for k, v := range s.Route { if v.Cert != nil && v.Key != nil { diff --git a/server_test.go b/server_test.go index 12e6a53..ee72cb0 100644 --- a/server_test.go +++ b/server_test.go @@ -15,7 +15,7 @@ import ( const ( cannary = "hello from backend" - dataFile = "test.gob" + dataFile = "test.json" ) func TestReverseProxy(t *testing.T) { -- cgit v1.2.3