proxy: introduce director

The director class drives an httputil.ReverseProxy. This is used when
etcd is deployed in proxy mode.
This commit is contained in:
Brian Waldon 2014-09-10 15:23:35 -07:00
parent a3334eed23
commit e5a482266f
3 changed files with 136 additions and 1 deletions

64
proxy/proxy.go Normal file
View File

@ -0,0 +1,64 @@
package proxy
import (
"errors"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
)
func NewHandler(endpoints []string) (*httputil.ReverseProxy, error) {
d, err := newDirector(endpoints)
if err != nil {
return nil, err
}
proxy := httputil.ReverseProxy{
Director: d.direct,
Transport: &http.Transport{},
FlushInterval: 0,
}
return &proxy, nil
}
func newDirector(endpoints []string) (*director, error) {
if len(endpoints) == 0 {
return nil, errors.New("one or more endpoints required")
}
urls := make([]url.URL, len(endpoints))
for i, e := range endpoints {
u, err := url.Parse(e)
if err != nil {
return nil, fmt.Errorf("invalid endpoint %q: %v", e, err)
}
if u.Scheme == "" {
return nil, fmt.Errorf("invalid endpoint %q: scheme required", e)
}
if u.Host == "" {
return nil, fmt.Errorf("invalid endpoint %q: host empty", e)
}
urls[i] = *u
}
d := director{
endpoints: urls,
}
return &d, nil
}
type director struct {
endpoints []url.URL
}
func (d *director) direct(req *http.Request) {
choice := d.endpoints[0]
req.URL.Scheme = choice.Scheme
req.URL.Host = choice.Host
}

71
proxy/proxy_test.go Normal file
View File

@ -0,0 +1,71 @@
package proxy
import (
"net/http"
"net/url"
"reflect"
"testing"
)
func TestNewDirector(t *testing.T) {
tests := []struct {
good bool
endpoints []string
}{
{true, []string{"http://192.0.2.8"}},
{true, []string{"http://192.0.2.8:8001"}},
{true, []string{"http://example.com"}},
{true, []string{"http://example.com:8001"}},
{true, []string{"http://192.0.2.8:8001", "http://example.com:8002"}},
{false, []string{"192.0.2.8"}},
{false, []string{"192.0.2.8:8001"}},
{false, []string{""}},
}
for i, tt := range tests {
_, err := newDirector(tt.endpoints)
if tt.good != (err == nil) {
t.Errorf("#%d: expected success = %t, got err = %v", i, tt.good, err)
}
}
}
func TestDirectorDirect(t *testing.T) {
d := &director{
endpoints: []url.URL{
url.URL{
Scheme: "http",
Host: "bar.example.com",
},
},
}
req := &http.Request{
Method: "GET",
Host: "foo.example.com",
URL: &url.URL{
Host: "foo.example.com",
Path: "/v2/keys/baz",
},
}
d.direct(req)
want := &http.Request{
Method: "GET",
// this field must not change
Host: "foo.example.com",
URL: &url.URL{
// the Scheme field is updated per the director's first endpoint
Scheme: "http",
// the Host field is updated per the director's first endpoint
Host: "bar.example.com",
Path: "/v2/keys/baz",
},
}
if !reflect.DeepEqual(want, req) {
t.Fatalf("HTTP request does not match expected criteria: want=%#v got=%#v", want, req)
}
}

2
test
View File

@ -14,7 +14,7 @@ COVER=${COVER:-"-cover"}
source ./build
TESTABLE="wal snap etcdserver etcdserver/etcdhttp etcdserver/etcdserverpb functional raft store"
TESTABLE="wal snap etcdserver etcdserver/etcdhttp etcdserver/etcdserverpb functional proxy raft store"
FORMATTABLE="$TESTABLE cors.go main.go"
# user has not provided PKG override