func TestFilter(t *testing.T) { f := NewFilter() if reqmod := f.RequestModifier("id"); reqmod != nil { t.Fatalf("f.RequestModifier(%q): got reqmod, want no reqmod", "id") } if resmod := f.ResponseModifier("id"); resmod != nil { t.Fatalf("f.ResponseModifier(%q): got resmod, want no resmod", "id") } f.SetRequestModifier("id", martian.RequestModifierFunc( func(*martian.Context, *http.Request) error { return nil })) f.SetResponseModifier("id", martian.ResponseModifierFunc( func(*martian.Context, *http.Response) error { return nil })) if reqmod := f.RequestModifier("id"); reqmod == nil { t.Errorf("f.RequestModifier(%q): got no reqmod, want reqmod", "id") } if resmod := f.ResponseModifier("id"); resmod == nil { t.Errorf("f.ResponseModifier(%q): got no resmod, want resmod", "id") } f.SetRequestModifier("id", nil) f.SetResponseModifier("id", nil) if reqmod := f.RequestModifier("id"); reqmod != nil { t.Fatalf("f.RequestModifier(%q): got reqmod, want no reqmod", "id") } if resmod := f.ResponseModifier("id"); resmod != nil { t.Fatalf("f.ResponseModifier(%q): got resmod, want no resmod", "id") } }
func TestModifyRequest(t *testing.T) { m := NewModifier() ctx := martian.NewContext() req, err := http.NewRequest("GET", "http://example.com", nil) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } req.Header.Set("Proxy-Authorization", "Basic "+encode("user:pass")) if err := m.ModifyRequest(ctx, req); err != nil { t.Fatalf("ModifyRequest(): got %v, want no error", err) } if got, want := ctx.Auth.ID, "user:pass"; got != want { t.Fatalf("ctx.Auth.ID: got %q, want %q", got, want) } modifierRun := false m.SetRequestModifier(martian.RequestModifierFunc( func(*martian.Context, *http.Request) error { modifierRun = true return nil })) if err := m.ModifyRequest(ctx, req); err != nil { t.Fatalf("ModifyRequest(): got %v, want no error", err) } if !modifierRun { t.Error("modifierRun: got false, want true") } }
func TestModifyRequest(t *testing.T) { f := NewFilter() modifierRun := false f.SetRequestModifier("id", martian.RequestModifierFunc( func(*martian.Context, *http.Request) error { modifierRun = true return nil })) req, err := http.NewRequest("GET", "/", nil) if err != nil { t.Fatalf("NewRequest(): got %v, want no error", err) } ctx := martian.NewContext() // No ID, auth required. f.SetAuthRequired(true) if err := f.ModifyRequest(ctx, req); err != nil { t.Fatalf("ModifyRequest(): got %v, want no error", err) } if ctx.Auth.Error == nil { t.Error("ctx.Auth.Error: got nil, want error") } if modifierRun { t.Error("modifierRun: got true, want false") } // No ID, auth not required. f.SetAuthRequired(false) ctx.Auth.Error = nil if err := f.ModifyRequest(ctx, req); err != nil { t.Fatalf("ModifyRequest(): got %v, want no error", err) } if ctx.Auth.Error != nil { t.Errorf("ctx.Auth.Error: got %v, want no error", err) } if modifierRun { t.Error("modifierRun: got true, want false") } // Valid ID. ctx.Auth.ID = "id" ctx.Auth.Error = nil if err := f.ModifyRequest(ctx, req); err != nil { t.Fatalf("ModifyRequest(): got %v, want no error", err) } if ctx.Auth.Error != nil { t.Errorf("ctx.Auth.Error: got %v, want no error", ctx.Auth.Error) } if !modifierRun { t.Error("modifierRun: got false, want true") } }
func TestPriorityGroupModifyRequest(t *testing.T) { var priorities []int64 pg := NewGroup() f := func(*martian.Context, *http.Request) error { priorities = append(priorities, 50) return nil } pg.AddRequestModifier(martian.RequestModifierFunc(f), 50) f = func(*martian.Context, *http.Request) error { priorities = append(priorities, 100) return nil } pg.AddRequestModifier(martian.RequestModifierFunc(f), 100) f = func(*martian.Context, *http.Request) error { priorities = append(priorities, 75) return nil } // Functions are not directly comparable, so we must wrap in a // type that is. m := &struct{ martian.RequestModifier }{martian.RequestModifierFunc(f)} if err := pg.RemoveRequestModifier(m); err != ErrModifierNotFound { t.Fatalf("RemoveRequestModifier(): got %v, want ErrModifierNotFound", err) } pg.AddRequestModifier(m, 75) if err := pg.RemoveRequestModifier(m); err != nil { t.Fatalf("RemoveRequestModifier(): got %v, want no error", err) } req, err := http.NewRequest("GET", "/", nil) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } if err := pg.ModifyRequest(martian.NewContext(), req); err != nil { t.Fatalf("ModifyRequest(): got %v, want no error", err) } if got, want := priorities, []int64{100, 50}; !reflect.DeepEqual(got, want) { t.Fatalf("reflect.DeepEqual(%v, %v): got false, want true", got, want) } }
func TestModifyResponseResetAuth(t *testing.T) { auth := NewModifier() auth.SetRequestModifier(martian.RequestModifierFunc( func(ctx *martian.Context, req *http.Request) error { if ctx.Auth.ID != "secret:pass" { ctx.Auth.Error = errors.New("invalid auth") } return nil })) req, err := http.NewRequest("GET", "http://example.com", nil) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } req.Header.Set("Proxy-Authorization", "Basic "+encode("wrong:pass")) ctx := martian.NewContext() // This will set ctx.Auth.Error since the ID isn't "secret:pass". if err := auth.ModifyRequest(ctx, req); err != nil { t.Fatalf("ModifyRequest(): got %v, want no error", err) } res := proxyutil.NewResponse(200, nil, req) if err := auth.ModifyResponse(ctx, res); err != nil { t.Fatalf("ModifyResponse(): got %v, want no error", err) } if got, want := res.StatusCode, 407; got != want { t.Errorf("res.StatusCode: got %d, want %d", got, want) } if got, want := ctx.Auth.ID, ""; got != want { t.Errorf("ctx.Auth.ID: got %q, want %q", got, want) } if err := ctx.Auth.Error; err != nil { t.Errorf("ctx.Auth.Error: got %v, want no error", err) } // This will be successful because the ID is "secret:pass". req.Header.Set("Proxy-Authorization", "Basic "+encode("secret:pass")) if err := auth.ModifyRequest(ctx, req); err != nil { t.Fatalf("ModifyRequest(): got %v, want no error", err) } // Reset the response. res = proxyutil.NewResponse(200, nil, req) if err := auth.ModifyResponse(ctx, res); err != nil { t.Fatalf("ModifyResponse(): got %v, want no error", err) } if got, want := res.StatusCode, 200; got != want { t.Errorf("res.StatusCode: got %d, want %d", got, want) } }
// NewViaModifier sets the Via header. // // If Via is already present, via is appended to the existing value. // // http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-9.9 func NewViaModifier(via string) martian.RequestModifier { return martian.RequestModifierFunc( func(ctx *martian.Context, req *http.Request) error { if v := req.Header.Get("Via"); v != "" { via = v + ", " + via } req.Header.Set("Via", via) return nil }) }
func TestFilterWithQueryStringNameAndValue(t *testing.T) { name, value := "name", "value" nameMatcher, err := regexp.Compile(name) if err != nil { t.Fatalf("regexp.Compile(%q): got %v, want no error", name, err) } valueMatcher, err := regexp.Compile(value) if err != nil { t.Fatalf("regexp.Compile(%q): got %v, want no error", value, err) } modifierRun := false f := func(*martian.Context, *http.Request) error { modifierRun = true return nil } filter, err := NewFilter(nameMatcher, valueMatcher) if err != nil { t.Fatalf("NewFilter(): got %v, want no error", err) } filter.SetRequestModifier(martian.RequestModifierFunc(f)) v := url.Values{} v.Add("nomatch", "value") req, err := http.NewRequest("POST", "http://martian.local?name=value", strings.NewReader(v.Encode())) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err := filter.ModifyRequest(martian.NewContext(), req); err != nil { t.Errorf("ModifyRequest(): got %v, want no error", err) } if !modifierRun { t.Error("modifierRun: got false, want true") } v = url.Values{} req, err = http.NewRequest("POST", "http://martian.local", strings.NewReader(v.Encode())) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } modifierRun = false if err := filter.ModifyRequest(martian.NewContext(), req); err != nil { t.Errorf("ModifyRequest(): got %v, want no error", err) } if modifierRun { t.Error("modifierRun: got true, want false") } }
func TestModifyRequestHaltsOnError(t *testing.T) { mg := NewGroup() errHalt := errors.New("halt modifier chain") f := func(*martian.Context, *http.Request) error { return errHalt } mg.AddRequestModifier(martian.RequestModifierFunc(f)) f = func(*martian.Context, *http.Request) error { t.Fatal("ModifyRequest(): got called, want skipped") return nil } mg.AddRequestModifier(martian.RequestModifierFunc(f)) req, err := http.NewRequest("GET", "/", nil) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } if err := mg.ModifyRequest(martian.NewContext(), req); err != errHalt { t.Fatalf("mg.ModifyRequest(): got %v, want %v", err, errHalt) } }
func TestFromJSON(t *testing.T) { wasRun := false Register("parse.Key", func(b []byte) (*Result, error) { m := martian.RequestModifierFunc( func(*martian.Context, *http.Request) error { wasRun = true return nil }) msg := &struct { Scope []ModifierType `json:"scope"` }{} if err := json.Unmarshal(b, msg); err != nil { return nil, err } return NewResult(m, msg.Scope) }) rawMsg := []byte(`{ "parse.Key": { "scope":["request"] } }`) r, err := FromJSON(rawMsg) if err != nil { t.Fatalf("FromJSON(): got %v, want no error", err) } reqmod := r.RequestModifier() if reqmod == nil { t.Fatal("FromJSON(): got nil, want not nil") } resmod := r.ResponseModifier() if resmod != nil { t.Fatal("FromJSON(): got nil, want not nil") } err = reqmod.ModifyRequest(nil, nil) if err != nil { t.Fatalf("reqmod.ModifyRequest(): got %v, want no error", err) } if !wasRun { t.Error("FromJSON(): got false, want true") } }
func TestPriorityGroupModifyRequestHaltsOnError(t *testing.T) { pg := NewGroup() errHalt := errors.New("modifier chain halted") f := func(*martian.Context, *http.Request) error { return errHalt } pg.AddRequestModifier(martian.RequestModifierFunc(f), 100) f = func(*martian.Context, *http.Request) error { t.Fatal("ModifyRequest(): got called, want skipped") return nil } pg.AddRequestModifier(martian.RequestModifierFunc(f), 75) req, err := http.NewRequest("GET", "/", nil) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } if err := pg.ModifyRequest(martian.NewContext(), req); err != errHalt { t.Fatalf("ModifyRequest(): got %v, want errHalt", err) } }
// NewBadFramingModifier makes a best effort to fix inconsistencies in the // request such as multiple Content-Lengths or the lack of Content-Length and // improper Transfer-Encoding. If it is unable to determine a proper resolution // it returns an error. // // http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-3.3 func NewBadFramingModifier() martian.RequestModifier { return martian.RequestModifierFunc( func(ctx *martian.Context, req *http.Request) error { cls := req.Header["Content-Length"] if len(cls) > 0 { var length string // Iterate over all Content-Length headers, splitting any we find with // commas, and check that all Content-Lengths are equal. for _, ls := range cls { for _, l := range strings.Split(ls, ",") { // First length, set it as the canonical Content-Length. if length == "" { length = strings.TrimSpace(l) continue } // Mismatched Content-Lengths. if length != strings.TrimSpace(l) { return fmt.Errorf(`bad request framing: multiple mismatched "Content-Length" headers: %v`, cls) } } } // All Content-Lengths are equal, remove extras and set it to the // canonical value. req.Header.Set("Content-Length", length) } tes := req.Header["Transfer-Encoding"] if len(tes) > 0 { // Extract the last Transfer-Encoding value, and split on commas. last := strings.Split(tes[len(tes)-1], ",") // Check that the last, potentially comma-delimited, value is // "chunked", else we have no way to determine when the request is // finished. if strings.TrimSpace(last[len(last)-1]) != "chunked" { return fmt.Errorf(`bad request framing: "Transfer-Encoding" header is present, but does not end in "chunked"`) } // Transfer-Encoding "chunked" takes precedence over // Content-Length. req.Header.Del("Content-Length") } return nil }) }
func TestFilterWithQueryParamNameAndNoValue(t *testing.T) { name := "name" nameMatcher, err := regexp.Compile(name) if err != nil { t.Fatalf("regexp.Compile(%q): got %v, want no error", name, err) } modifierRun := false f := func(*martian.Context, *http.Request) error { modifierRun = true return nil } filter, err := NewFilter(nameMatcher, nil) if err != nil { t.Fatalf("NewFilter(): got %v, want no error", err) } filter.SetRequestModifier(martian.RequestModifierFunc(f)) req, err := http.NewRequest("GET", "http://martian.local?name", nil) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } if err := filter.ModifyRequest(martian.NewContext(), req); err != nil { t.Errorf("ModifyRequest(): got %v, want no error", err) } if !modifierRun { t.Error("modifierRun: got false, want true") } req, err = http.NewRequest("GET", "http://martian.local?test", nil) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } modifierRun = false if err := filter.ModifyRequest(martian.NewContext(), req); err != nil { t.Errorf("ModifyRequest(): got %v, want no error", err) } if modifierRun { t.Error("modifierRun: got true, want false") } }
func TestModifyRequest(t *testing.T) { tt := []struct { name string values []string want bool }{ { name: "Martian-Production", values: []string{"true"}, want: false, }, { name: "Martian-Testing", values: []string{"see-next-value", "true"}, want: true, }, } for i, tc := range tt { var got bool f := NewFilter("mARTian-teSTInG", "true") f.SetRequestModifier(martian.RequestModifierFunc( func(*martian.Context, *http.Request) error { got = true return nil })) req, err := http.NewRequest("GET", "http://example.com", nil) if err != nil { t.Fatalf("%d. http.NewRequest(): got %v, want no error", i, err) } req.Header[tc.name] = tc.values if err := f.ModifyRequest(martian.NewContext(), req); err != nil { t.Fatalf("%d. ModifyRequest(): got %v, want no error", i, err) } if got != tc.want { t.Errorf("%d. modifier run: got %t, want %t", i, got, tc.want) } } }
// NewForwardedModifier sets the X-Forwarded-For and X-Forwarded-Proto headers. // // If X-Forwarded-For is already present, the client IP is appended to // the existing value. // // TODO: Support "Forwarded" header. // see: http://tools.ietf.org/html/rfc7239 func NewForwardedModifier() martian.RequestModifier { return martian.RequestModifierFunc( func(req *http.Request) error { req.Header.Set("X-Forwarded-Proto", req.URL.Scheme) xff, _, err := net.SplitHostPort(req.RemoteAddr) if err != nil { xff = req.RemoteAddr } if v := req.Header.Get("X-Forwarded-For"); v != "" { xff = v + ", " + xff } req.Header.Set("X-Forwarded-For", xff) return nil }) }
func TestResultRequestResponseModifierCorrectScope(t *testing.T) { mod := struct { martian.RequestModifier martian.ResponseModifier }{ RequestModifier: martian.RequestModifierFunc( func(*martian.Context, *http.Request) error { return nil }), ResponseModifier: martian.ResponseModifierFunc( func(*martian.Context, *http.Response) error { return nil }), } result := &Result{ reqmod: mod, resmod: nil, } reqmod := result.RequestModifier() if reqmod == nil { t.Error("result.RequestModifier: got nil, want not nil") } resmod := result.ResponseModifier() if resmod != nil { t.Errorf("result.ResponseModifier: got %v, want nil", resmod) } result = &Result{ reqmod: nil, resmod: mod, } reqmod = result.RequestModifier() if reqmod != nil { t.Errorf("result.RequestModifier: got %v, want nil", reqmod) } resmod = result.ResponseModifier() if resmod == nil { t.Error("result.ResponseModifier: got nil, want not nil") } }
func TestModifyRequest(t *testing.T) { mg := NewGroup() modifierRun := false f := func(*martian.Context, *http.Request) error { modifierRun = true return nil } mg.AddRequestModifier(martian.RequestModifierFunc(f)) req, err := http.NewRequest("GET", "/", nil) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } if err := mg.ModifyRequest(martian.NewContext(), req); err != nil { t.Fatalf("mg.ModifyRequest(): got %v, want no error", err) } if !modifierRun { t.Error("modifierRun: got false, want true") } }
func TestNewResultMismatchedScopes(t *testing.T) { reqmod := martian.RequestModifierFunc( func(*http.Request) error { return nil }) resmod := martian.ResponseModifierFunc( func(*http.Response) error { return nil }) if _, err := NewResult(reqmod, []ModifierType{Response}); err == nil { t.Error("NewResult(reqmod, RESPONSE): got nil, want error") } if _, err := NewResult(resmod, []ModifierType{Request}); err == nil { t.Error("NewResult(resmod, REQUEST): got nil, want error") } if _, err := NewResult(reqmod, []ModifierType{ModifierType("unknown")}); err == nil { t.Error("NewResult(resmod, REQUEST): got nil, want error") } }
func TestModifyRequest(t *testing.T) { m := NewModifier() var modRun bool m.reqmod = martian.RequestModifierFunc( func(*martian.Context, *http.Request) error { modRun = true return nil }) req, err := http.NewRequest("GET", "http://example.com", nil) if err != nil { t.Fatalf("http.NewRequest(): got %v, want no error", err) } if err := m.ModifyRequest(martian.NewContext(), req); err != nil { t.Fatalf("ModifyRequest(): got %v, want no error", err) } if !modRun { t.Error("modRun: got false, want true") } }