func TestOriginalRequestResponse(t *testing.T) { s := startTestServer(nil, 0, func(r *http.Request) { if th, ok := r.Header["X-Test-Header-Preserved"]; !ok || th[0] != "test value" { t.Error("wrong request header") } }) defer s.Close() u, _ := url.ParseRequestURI("https://www.example.org/hello") r := &http.Request{ URL: u, Method: "GET", Header: http.Header{"X-Test-Header": []string{"test value"}}} w := httptest.NewRecorder() fr := builtin.MakeRegistry() fr.Register(&preserveOriginalSpec{}) doc := fmt.Sprintf(`hello: Path("/hello") -> preserveOriginal() -> "%s"`, s.URL) tp, err := newTestProxyWithFilters(fr, doc, PreserveOriginal) if err != nil { t.Error(err) return } defer tp.close() tp.proxy.ServeHTTP(w, r) if th, ok := w.Header()["X-Test-Response-Header-Preserved"]; !ok || th[0] != "response header value" { t.Error("wrong response header", ok) } }
func Example() { // create registry registry := builtin.MakeRegistry() // create and register the filter specification spec := &customSpec{name: "customFilter"} registry.Register(spec) // create simple data client, with route entries referencing 'customFilter', // and clipping part of the request path: dataClient, err := testdataclient.NewDoc(` ui: Path("/ui/*page") -> customFilter("ui request") -> modPath("^/[^/]*", "") -> "https://ui.example.org"; api: Path("/api/*resource") -> customFilter("api request") -> modPath("^/[^/]*", "") -> "https://api.example.org"`) if err != nil { log.Fatal(err) } // create http.Handler: proxy.New( routing.New(routing.Options{ FilterRegistry: registry, DataClients: []routing.DataClient{dataClient}}), proxy.OptionsNone) }
// Run skipper. func Run(o Options) error { // create authentication for Innkeeper auth := createInnkeeperAuthentication(o) // create data client dataClients, err := createDataClients(o, auth) if err != nil { return err } if len(dataClients) == 0 { log.Println("warning: no route source specified") } // create a filter registry with the available filter specs registered, // and register the custom filters registry := builtin.MakeRegistry() for _, f := range o.CustomFilters { registry.Register(f) } // create routing // create the proxy instance var mo routing.MatchingOptions if o.IgnoreTrailingSlash { mo = routing.IgnoreTrailingSlash } // ensure a non-zero poll timeout if o.SourcePollTimeout <= 0 { o.SourcePollTimeout = defaultSourcePollTimeout } // check for dev mode, and set update buffer of the routes updateBuffer := defaultRoutingUpdateBuffer if o.DevMode { updateBuffer = 0 } // create a routing engine routing := routing.New(routing.Options{ registry, mo, o.SourcePollTimeout, dataClients, updateBuffer}) // create the proxy proxy := proxy.New(routing, o.ProxyOptions, o.PriorityRoutes...) // start the http server log.Printf("listening on %v\n", o.Address) return http.ListenAndServe(o.Address, proxy) }
func Example() { // create etcd data client: dataClient := etcd.New([]string{"https://etcd.example.org"}, "/skipper") // create http.Handler: proxy.New( routing.New(routing.Options{ FilterRegistry: builtin.MakeRegistry(), DataClients: []routing.DataClient{dataClient}}), proxy.OptionsNone) }
func Example() { // create etcd data client: dataClient, err := etcd.New(etcd.Options{[]string{"https://etcd.example.org"}, "/skipper", 0}) if err != nil { log.Fatal(err) } // create http.Handler: proxy.New( routing.New(routing.Options{ FilterRegistry: builtin.MakeRegistry(), DataClients: []routing.DataClient{dataClient}}), proxy.OptionsNone) }
func DisabledExample() { // create a target backend server. It will return the value of the 'X-Echo' request header // as the response body: targetServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(r.Header.Get("X-Echo"))) })) defer targetServer.Close() // create a filter registry, and register the custom filter: filterRegistry := builtin.MakeRegistry() filterRegistry.Register(&setEchoHeader{}) // create a data client with a predefined route, referencing the filter and a path condition // containing a wildcard called 'echo': routeDoc := fmt.Sprintf(`Path("/return/:echo") -> setEchoHeader() -> "%s"`, targetServer.URL) dataClient, err := testdataclient.NewDoc(routeDoc) if err != nil { log.Fatal(err) } // create routing object: rt := routing.New(routing.Options{ FilterRegistry: filterRegistry, DataClients: []routing.DataClient{dataClient}}) defer rt.Close() // create a proxy instance, and start an http server: proxy := proxy.New(rt, proxy.OptionsNone) defer proxy.Close() router := httptest.NewServer(proxy) defer router.Close() // make a request to the proxy: rsp, err := http.Get(fmt.Sprintf("%s/return/Hello,+world!", router.URL)) if err != nil { log.Fatal(err) } defer rsp.Body.Close() // print out the response: if _, err := io.Copy(os.Stdout, rsp.Body); err != nil { log.Fatal(err) } // Output: // Hello, world! }
func Example() { // create etcd data client: dataClient, err := etcd.New(etcd.Options{[]string{"https://etcd.example.org"}, "/skipper", 0, false}) if err != nil { log.Fatal(err) } // create routing object: rt := routing.New(routing.Options{ FilterRegistry: builtin.MakeRegistry(), DataClients: []routing.DataClient{dataClient}}) defer rt.Close() // create http.Handler: p := proxy.New(rt, proxy.OptionsNone) defer p.Close() }
func ExampleFilter() { // create a test filter and add to the registry: fr := builtin.MakeRegistry() fr.Register(&filtertest.Filter{FilterName: "testFilter"}) // create a data client, with a predefined route referencing the filter: dc, err := testdataclient.NewDoc(`Path("/some/path/:param") -> testFilter(3.14, "Hello, world!") -> "https://www.example.org"`) if err != nil { log.Fatal(err) } // create an http.Handler: proxy.New( routing.New(routing.Options{ DataClients: []routing.DataClient{dc}, FilterRegistry: fr}), proxy.OptionsNone) }
func ExamplePriorityRoute() { // create a routing doc forwarding all requests, // and load it in a data client: routeDoc := `* -> "https://www.example.org"` dataClient, err := testdataclient.NewDoc(routeDoc) if err != nil { log.Fatal(err) } // create a priority route making exceptions: pr := &priorityRoute{} // create an http.Handler: proxy.New( routing.New(routing.Options{ FilterRegistry: builtin.MakeRegistry(), DataClients: []routing.DataClient{dataClient}}), proxy.OptionsNone, pr) }
// to run this test, set `-args listener` for the test command func TestHTTPSServer(t *testing.T) { // TODO: figure why sometimes cannot connect if !testListener() { t.Skip() } a, err := findAddress() if err != nil { t.Fatal(err) } o := Options{ Address: a, CertPathTLS: "fixtures/test.crt", KeyPathTLS: "fixtures/test.key", } rt := routing.New(routing.Options{ FilterRegistry: builtin.MakeRegistry(), DataClients: []routing.DataClient{}}) defer rt.Close() proxy := proxy.New(rt, proxy.OptionsNone) defer proxy.Close() go listenAndServe(proxy, &o) r, err := waitConnGet("https://" + o.Address) if r != nil { defer r.Body.Close() } if err != nil { t.Fatalf("Cannot connect to the local server for testing: %s ", err.Error()) } if r.StatusCode != 404 { t.Fatalf("Status code should be 404, instead got: %d\n", r.StatusCode) } _, err = ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to stream response body: %v", err) } }
func TestOriginalRequestResponse(t *testing.T) { s := startTestServer(nil, 0, func(r *http.Request) { if th, ok := r.Header["X-Test-Header-Preserved"]; !ok || th[0] != "test value" { t.Error("wrong request header") } }) defer s.Close() u, _ := url.ParseRequestURI("https://www.example.org/hello") r := &http.Request{ URL: u, Method: "GET", Header: http.Header{"X-Test-Header": []string{"test value"}}} w := httptest.NewRecorder() doc := fmt.Sprintf(`hello: Path("/hello") -> preserveOriginal() -> "%s"`, s.URL) dc, err := testdataclient.NewDoc(doc) if err != nil { t.Error(err) } fr := builtin.MakeRegistry() fr.Register(&preserveOriginalSpec{}) p := New(routing.New(routing.Options{ fr, routing.MatchingOptionsNone, sourcePollTimeout, []routing.DataClient{dc}, nil, 0}), OptionsPreserveOriginal) delay() p.ServeHTTP(w, r) if th, ok := w.Header()["X-Test-Response-Header-Preserved"]; !ok || th[0] != "response header value" { t.Error("wrong response header", ok) } }
func TestWithWrongKeyPathFails(t *testing.T) { a, err := findAddress() if err != nil { t.Fatal(err) } o := Options{Address: a, CertPathTLS: "fixtures/test.crt", KeyPathTLS: "fixtures/notFound.key", } rt := routing.New(routing.Options{ FilterRegistry: builtin.MakeRegistry(), DataClients: []routing.DataClient{}}) defer rt.Close() proxy := proxy.New(rt, proxy.OptionsNone) defer proxy.Close() err = listenAndServe(proxy, &o) if err == nil { t.Fatal(err) } }
func Example() { // create a data client with a predefined route: dataClient, err := testdataclient.NewDoc( `Path("/some/path/to/:id") -> requestHeader("X-From", "skipper") -> "https://www.example.org"`) if err != nil { log.Fatal(err) } // create a router: r := routing.New(routing.Options{ FilterRegistry: builtin.MakeRegistry(), MatchingOptions: routing.IgnoreTrailingSlash, DataClients: []routing.DataClient{dataClient}}) // let the route data get propagated in the background: time.Sleep(36 * time.Millisecond) // create a request: req, err := http.NewRequest("GET", "https://www.example.com/some/path/to/Hello,+world!", nil) if err != nil { log.Fatal(err) } // match the request with the router: route, params := r.Route(req) if route == nil { log.Fatal("failed to route") } // verify the matched route and the path params: fmt.Println(route.Backend) fmt.Println(params["id"]) // Output: // https://www.example.org // Hello, world! }
func newTestRoutingWithPredicates(cps []routing.PredicateSpec, dc ...routing.DataClient) (*testRouting, error) { return newTestRoutingWithFiltersPredicates(builtin.MakeRegistry(), cps, dc...) }
func newTestProxy(doc string, flags Flags, pr ...PriorityRoute) (*testProxy, error) { return newTestProxyWithFilters(builtin.MakeRegistry(), doc, flags, pr...) }
// Run skipper. func Run(o Options) error { // init log err := initLog(o) if err != nil { return err } // init metrics metrics.Init(metrics.Options{ Listener: o.MetricsListener, Prefix: o.MetricsPrefix, EnableDebugGcMetrics: o.EnableDebugGcMetrics, EnableRuntimeMetrics: o.EnableRuntimeMetrics, }) // create authentication for Innkeeper auth := innkeeper.CreateInnkeeperAuthentication(innkeeper.AuthOptions{ InnkeeperAuthToken: o.InnkeeperAuthToken, OAuthCredentialsDir: o.OAuthCredentialsDir, OAuthUrl: o.OAuthUrl, OAuthScope: o.OAuthScope}) // create data client dataClients, err := createDataClients(o, auth) if err != nil { return err } if len(dataClients) == 0 { log.Warning("no route source specified") } // create a filter registry with the available filter specs registered, // and register the custom filters registry := builtin.MakeRegistry() for _, f := range o.CustomFilters { registry.Register(f) } // create routing // create the proxy instance var mo routing.MatchingOptions if o.IgnoreTrailingSlash { mo = routing.IgnoreTrailingSlash } // ensure a non-zero poll timeout if o.SourcePollTimeout <= 0 { o.SourcePollTimeout = defaultSourcePollTimeout } // check for dev mode, and set update buffer of the routes updateBuffer := defaultRoutingUpdateBuffer if o.DevMode { updateBuffer = 0 } // create a routing engine routing := routing.New(routing.Options{ registry, mo, o.SourcePollTimeout, dataClients, o.CustomPredicates, updateBuffer}) // create the proxy proxy := proxy.New(routing, o.ProxyOptions, o.PriorityRoutes...) // create the access log handler loggingHandler := logging.NewHandler(proxy) // start the http server log.Infof("proxy listener on %v", o.Address) return http.ListenAndServe(o.Address, loggingHandler) }
func newTestRouting(dc ...routing.DataClient) (*testRouting, error) { return newTestRoutingWithFiltersPredicates(builtin.MakeRegistry(), nil, dc...) }
// Run skipper. func Run(o Options) error { // init log err := initLog(o) if err != nil { return err } // init metrics metrics.Init(metrics.Options{ Listener: o.MetricsListener, Prefix: o.MetricsPrefix, EnableDebugGcMetrics: o.EnableDebugGcMetrics, EnableRuntimeMetrics: o.EnableRuntimeMetrics, EnableServeRouteMetrics: o.EnableServeRouteMetrics, EnableServeHostMetrics: o.EnableServeHostMetrics, }) // create authentication for Innkeeper auth := innkeeper.CreateInnkeeperAuthentication(innkeeper.AuthOptions{ InnkeeperAuthToken: o.InnkeeperAuthToken, OAuthCredentialsDir: o.OAuthCredentialsDir, OAuthUrl: o.OAuthUrl, OAuthScope: o.OAuthScope}) // create data clients dataClients, err := createDataClients(o, auth) if err != nil { return err } // append custom data clients dataClients = append(dataClients, o.CustomDataClients...) if len(dataClients) == 0 { log.Warning("no route source specified") } // create a filter registry with the available filter specs registered, // and register the custom filters registry := builtin.MakeRegistry() for _, f := range o.CustomFilters { registry.Register(f) } // create routing // create the proxy instance var mo routing.MatchingOptions if o.IgnoreTrailingSlash { mo = routing.IgnoreTrailingSlash } // ensure a non-zero poll timeout if o.SourcePollTimeout <= 0 { o.SourcePollTimeout = defaultSourcePollTimeout } // check for dev mode, and set update buffer of the routes updateBuffer := defaultRoutingUpdateBuffer if o.DevMode { updateBuffer = 0 } // include bundeled custom predicates o.CustomPredicates = append(o.CustomPredicates, source.New(), interval.NewBetween(), interval.NewBefore(), interval.NewAfter(), cookie.New(), query.New()) // create a routing engine routing := routing.New(routing.Options{ FilterRegistry: registry, MatchingOptions: mo, PollTimeout: o.SourcePollTimeout, DataClients: dataClients, Predicates: o.CustomPredicates, UpdateBuffer: updateBuffer}) defer routing.Close() proxyFlags := proxy.Flags(o.ProxyOptions) | o.ProxyFlags proxyParams := proxy.Params{ Routing: routing, Flags: proxyFlags, PriorityRoutes: o.PriorityRoutes, IdleConnectionsPerHost: o.IdleConnectionsPerHost, CloseIdleConnsPeriod: o.CloseIdleConnsPeriod, FlushInterval: o.BackendFlushInterval, ExperimentalUpgrade: o.ExperimentalUpgrade} if o.DebugListener != "" { do := proxyParams do.Flags |= proxy.Debug dbg := proxy.WithParams(do) log.Infof("debug listener on %v", o.DebugListener) go func() { http.ListenAndServe(o.DebugListener, dbg) }() } // create the proxy proxy := proxy.WithParams(proxyParams) defer proxy.Close() return listenAndServe(proxy, &o) }
func TestHostHeader(t *testing.T) { // start a test backend that returns the received host header backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Received-Host", r.Host) })) // take the generated host part of the backend bu, err := url.Parse(backend.URL) if err != nil { t.Error("failed to parse test backend url") return } backendHost := bu.Host for _, ti := range []struct { msg string options Options routeFmt string incomingHost string expectedHost string }{{ "no proxy preserve", OptionsNone, `route: Any() -> "%s"`, "www.example.org", backendHost, }, { "no proxy preserve, route preserve not", OptionsNone, `route: Any() -> preserveHost("false") -> "%s"`, "www.example.org", backendHost, }, { "no proxy preserve, route preserve", OptionsNone, `route: Any() -> preserveHost("true") -> "%s"`, "www.example.org", "www.example.org", }, { "no proxy preserve, route preserve not, explicit host last", OptionsNone, `route: Any() -> preserveHost("false") -> requestHeader("Host", "custom.example.org") -> "%s"`, "www.example.org", "custom.example.org", }, { "no proxy preserve, route preserve, explicit host last", OptionsNone, `route: Any() -> preserveHost("true") -> requestHeader("Host", "custom.example.org") -> "%s"`, "www.example.org", "custom.example.org", }, { "no proxy preserve, route preserve not, explicit host first", OptionsNone, `route: Any() -> requestHeader("Host", "custom.example.org") -> preserveHost("false") -> "%s"`, "www.example.org", "custom.example.org", }, { "no proxy preserve, route preserve, explicit host last", OptionsNone, `route: Any() -> requestHeader("Host", "custom.example.org") -> preserveHost("true") -> "%s"`, "www.example.org", "custom.example.org", }, { "proxy preserve", OptionsProxyPreserveHost, `route: Any() -> "%s"`, "www.example.org", "www.example.org", }, { "proxy preserve, route preserve not", OptionsProxyPreserveHost, `route: Any() -> preserveHost("false") -> "%s"`, "www.example.org", backendHost, }, { "proxy preserve, route preserve", OptionsProxyPreserveHost, `route: Any() -> preserveHost("true") -> "%s"`, "www.example.org", "www.example.org", }, { "proxy preserve, route preserve not, explicit host last", OptionsProxyPreserveHost, `route: Any() -> preserveHost("false") -> requestHeader("Host", "custom.example.org") -> "%s"`, "www.example.org", "custom.example.org", }, { "proxy preserve, route preserve, explicit host last", OptionsProxyPreserveHost, `route: Any() -> preserveHost("true") -> requestHeader("Host", "custom.example.org") -> "%s"`, "www.example.org", "custom.example.org", }, { "proxy preserve, route preserve not, explicit host first", OptionsProxyPreserveHost, `route: Any() -> requestHeader("Host", "custom.example.org") -> preserveHost("false") -> "%s"`, "www.example.org", "custom.example.org", }, { "proxy preserve, route preserve, explicit host last", OptionsProxyPreserveHost, `route: Any() -> requestHeader("Host", "custom.example.org") -> preserveHost("true") -> "%s"`, "www.example.org", "custom.example.org", }} { // replace the host in the route format f := ti.routeFmt + `;healthcheck: Path("/healthcheck") -> "%s"` route := fmt.Sprintf(f, backend.URL, backend.URL) // create a dataclient with the route dc, err := testdataclient.NewDoc(route) if err != nil { t.Error(ti.msg, "failed to parse route") continue } // start a proxy server r := routing.New(routing.Options{ FilterRegistry: builtin.MakeRegistry(), MatchingOptions: routing.MatchingOptionsNone, PollTimeout: 42 * time.Microsecond, DataClients: []routing.DataClient{dc}}) ps := httptest.NewServer(New(r, ti.options)) // wait for the routing table was activated healthcheckDone := make(chan struct{}) go func() { for { rs, _ := http.Get(ps.URL + "/healthcheck") if rs != nil && rs.StatusCode >= http.StatusOK && rs.StatusCode < http.StatusMultipleChoices { healthcheckDone <- struct{}{} return } } }() timeouted := false select { case <-time.After(999 * time.Millisecond): timeouted = true case <-healthcheckDone: } if timeouted { t.Error(ti.msg, "startup timeout") ps.Close() continue } req, err := http.NewRequest("GET", ps.URL, nil) if err != nil { t.Error(ti.msg, err) ps.Close() continue } req.Host = ti.incomingHost rsp, err := (&http.Client{}).Do(req) if err != nil { t.Error(ti.msg, "failed to make request") ps.Close() continue } if rsp.Header.Get("X-Received-Host") != ti.expectedHost { t.Error(ti.msg, "wrong host", rsp.Header.Get("X-Received-Host"), ti.expectedHost) } ps.Close() } }