Beispiel #1
1
// LogRequests logs the requests to the embedded logger.
func LogRequests(h httpx.Handler) httpx.Handler {
	return httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		logger.Info(ctx, "request.start",
			"method", r.Method,
			"path", r.URL.Path,
		)

		err := h.ServeHTTPContext(ctx, w, r)

		logger.Info(ctx, "request.complete")

		return err
	})
}
func TestTracing(t *testing.T) {
	tracerTests := []tracerTest{
		// simple path
		{
			routes: func(r *httpx.Router) {
				r.Handle("/path", httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
					return nil
				})).Methods("GET")
			},
			req: newRequest("GET", "/path"),
			expectedTransactionName: "GET /path",
			expectedUrl:             "/path",
		},
		// path with variables
		{
			routes: func(r *httpx.Router) {
				r.Handle("/users/{user_id}", httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
					return nil
				})).Methods("DELETE")
			},
			req: newRequest("DELETE", "/users/23"),
			expectedTransactionName: "DELETE /users/{user_id}",
			expectedUrl:             "/users/23",
		},
		// path with regexp variables
		{
			routes: func(r *httpx.Router) {
				r.Handle("/articles/{category}/{id:[0-9]+}", httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
					return nil
				})).Methods("PUT")
			},
			req: newRequest("PUT", "/articles/tech/123"),
			expectedTransactionName: "PUT /articles/{category}/{id:[0-9]+}",
			expectedUrl:             "/articles/tech/123",
		},
		// using Path().Handler() style
		{
			routes: func(r *httpx.Router) {
				r.Path("/articles/{category}/{id}").HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
					return nil
				}).Methods("GET")
			},
			req: newRequest("GET", "/articles/tech/456"),
			expectedTransactionName: "GET /articles/{category}/{id}",
			expectedUrl:             "/articles/tech/456",
		},
		// no route
		{
			routes: func(r *httpx.Router) {
			},
			req: newRequest("GET", "/non_existent"),
			expectedTransactionName: "GET /non_existent",
			expectedUrl:             "/non_existent",
		},
	}

	for _, tt := range tracerTests {
		traceTest(t, &tt)
	}
}
func TestAuthentication(t *testing.T) {
	m := &Authentication{
		findAccessToken: func(token string) (*empire.AccessToken, error) {
			return &empire.AccessToken{
				User: &empire.User{
					Name: "ehjolmes",
				},
			}, nil
		},
		handler: httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			_, ok := empire.UserFromContext(ctx)
			if !ok {
				t.Fatal("Expected a user to be present in the context")
			}

			return nil
		}),
	}

	ctx := context.Background()
	resp := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/apps", nil)
	req.SetBasicAuth("", "token")

	if err := m.ServeHTTPContext(ctx, resp, req); err != nil {
		t.Fatal(err)
	}
}
Beispiel #4
0
func TestError(t *testing.T) {
	h := &Error{
		handler: httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			return errors.New("boom")
		}),
	}

	ctx := context.Background()
	req, _ := http.NewRequest("GET", "/path", nil)
	resp := httptest.NewRecorder()

	err := h.ServeHTTPContext(ctx, resp, req)

	if err != nil {
		t.Fatal("Expected no error to be returned because it was handled")
	}

	if got, want := resp.Body.String(), "boom\n"; got != want {
		t.Fatalf("Body => %s; want %s", got, want)
	}

	if got, want := resp.Code, 500; got != want {
		t.Fatalf("Status => %v; want %v", got, want)
	}
}
Beispiel #5
0
// InsertLogger returns an httpx.Handler middleware that will call f to generate
// a logger, then insert it into the context.
func InsertLogger(h httpx.Handler, f func(context.Context, *http.Request) logger.Logger) httpx.Handler {
	return httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		l := f(ctx, r)
		ctx = logger.WithLogger(ctx, l)
		return h.ServeHTTPContext(ctx, w, r)
	})
}
Beispiel #6
0
func TestRecoveryPanicString(t *testing.T) {
	h := &Recovery{
		Reporter: reporter.ReporterFunc(func(ctx context.Context, err error) error {
			return nil
		}),
		handler: httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			panic("boom")
		}),
	}

	ctx := context.Background()
	req, _ := http.NewRequest("GET", "/", nil)
	resp := httptest.NewRecorder()

	defer func() {
		if err := recover(); err != nil {
			t.Fatal("Expected the panic to be handled.")
		}
	}()

	err := h.ServeHTTPContext(ctx, resp, req)

	if got, want := err.Error(), "boom"; got != want {
		t.Fatalf("err => %v; want %v", got, want)
	}
}
Beispiel #7
0
// PrefixRequestID adds the request as a prefix to the log15.Logger.
func PrefixRequestID(h httpx.Handler) httpx.Handler {
	return httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		if l, ok := logger.FromContext(ctx); ok {
			if l, ok := l.(log15.Logger); ok {
				ctx = logger.WithLogger(ctx, l.New("request_id", httpx.RequestID(ctx)))
			}
		}

		return h.ServeHTTPContext(ctx, w, r)
	})
}
Beispiel #8
0
// WithRequest adds information about the http.Request to reported errors.
func WithRequest(h httpx.Handler) httpx.Handler {
	return httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		ctx = httpx.WithRequest(ctx, r)

		// Add the request to the context.
		reporter.AddRequest(ctx, r)

		// Add the request id
		reporter.AddContext(ctx, "request_id", httpx.RequestID(ctx))

		return h.ServeHTTPContext(ctx, w, r)
	})
}
Beispiel #9
0
func withMetrics(handlerName string, h httpx.HandlerFunc) httpx.Handler {
	return httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		tags := []string{
			fmt.Sprintf("handler:%s", handlerName),
			fmt.Sprintf("user:%s", auth.UserFromContext(ctx).Name),
		}
		start := time.Now()
		err := h(ctx, w, r)
		d := time.Since(start)
		stats.Timing(ctx, fmt.Sprintf("heroku.request"), d, 1.0, tags)
		stats.Timing(ctx, fmt.Sprintf("heroku.request.%s", handlerName), d, 1.0, tags)
		return err
	})
}
Beispiel #10
0
func TestRecovery(t *testing.T) {
	var (
		called  bool
		errBoom = errors.New("boom")
	)

	h := &Recovery{
		Reporter: reporter.ReporterFunc(func(ctx context.Context, err error) error {
			called = true

			e := err.(*reporter.Error)

			if e.Err != errBoom {
				t.Fatalf("err => %v; want %v", err, errBoom)
			}

			if got, want := e.Context["request_id"], "1234"; got != want {
				t.Fatalf("RequestID => %s; want %s", got, want)
			}

			return nil
		}),
		handler: httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			panic(errBoom)
		}),
	}

	ctx := context.Background()
	req, _ := http.NewRequest("GET", "/", nil)
	req.Header.Set("X-Request-ID", "1234")
	resp := httptest.NewRecorder()

	ctx = httpx.WithRequest(ctx, req)

	defer func() {
		if err := recover(); err != nil {
			t.Fatal("Expected the panic to be handled.")
		}
	}()

	err := h.ServeHTTPContext(ctx, resp, req)

	if err != errBoom {
		t.Fatalf("err => %v; want %v", err, errBoom)
	}
}
Beispiel #11
0
// handle adds a new handler to the router, which also increments a counter.
func (s *Server) handle(method, path string, h httpx.HandlerFunc) {
	name := handlerName(h)

	fn := httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		tags := []string{
			fmt.Sprintf("handler:%s", name),
			fmt.Sprintf("user:%s", UserFromContext(ctx).Name),
		}
		start := time.Now()
		err := h(ctx, w, r)
		d := time.Since(start)
		stats.Timing(ctx, fmt.Sprintf("heroku.request"), d, 1.0, tags)
		stats.Timing(ctx, fmt.Sprintf("heroku.request.%s", name), d, 1.0, tags)
		return err
	})

	s.mux.HandleFunc(path, fn).Methods(method)
}
Beispiel #12
0
func TestLogger(t *testing.T) {
	b := new(bytes.Buffer)

	h := LogTo(httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		w.WriteHeader(201)
		return nil
	}), stdLogger(b))

	ctx := context.Background()
	resp := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/", nil)

	if err := h.ServeHTTPContext(ctx, resp, req); err != nil {
		t.Fatal(err)
	}

	if got, want := b.String(), "request_id= request.start method=GET path=/\nrequest_id= request.complete status=201\n"; got != want {
		t.Fatalf("%s; want %s", got, want)
	}
}
Beispiel #13
0
func TestBackground(t *testing.T) {
	m := &Background{
		handler: httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			if ctx == nil {
				t.Fatal("Expected a context to be generated")
			}

			io.WriteString(w, `Ok`)
			return nil
		}),
	}

	req, _ := http.NewRequest("GET", "/", nil)
	resp := httptest.NewRecorder()

	m.ServeHTTP(resp, req)

	if got, want := resp.Body.String(), `Ok`; got != want {
		t.Fatalf("Body => %s; want %s", got, want)
	}
}
Beispiel #14
0
func TestErrorWithHandler(t *testing.T) {
	var called bool

	h := &Error{
		ErrorHandler: func(err error, w http.ResponseWriter, r *http.Request) {
			called = true
		},
		handler: httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			return errors.New("boom")
		}),
	}

	ctx := context.Background()
	req, _ := http.NewRequest("GET", "/path", nil)
	resp := httptest.NewRecorder()

	h.ServeHTTPContext(ctx, resp, req)

	if !called {
		t.Fatal("Expected the error handler to be called")
	}
}
Beispiel #15
0
func TestRequestID(t *testing.T) {
	m := &RequestID{
		handler: httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
			requestID := httpx.RequestID(ctx)

			if got, want := requestID, "1234"; got != want {
				t.Fatalf("RequestID => %s; want %s", got, want)
			}

			return nil
		}),
	}

	ctx := context.Background()
	resp := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/", nil)
	req.Header.Set("X-Request-ID", "1234")

	if err := m.ServeHTTPContext(ctx, resp, req); err != nil {
		t.Fatal(err)
	}
}
Beispiel #16
0
// ensureUserInContext returns and httpx.Handler that raises an error if the
// user isn't set in the context.
func ensureUserInContext(t testing.TB) httpx.Handler {
	return httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		auth.UserFromContext(ctx) // Panics if user is not set.
		return nil
	})
}
Beispiel #17
0
// errHandler returns an httpx.Handler that responds with the given error.
func errHandler(err error) httpx.Handler {
	return httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		return err
	})
}
Beispiel #18
0
// New creates the API routes and returns a new http.Handler to serve them.
func New(e *empire.Empire, authenticator auth.Authenticator) httpx.Handler {
	r := httpx.NewRouter()

	// Apps
	r.Handle("/apps", &GetApps{e}).Methods("GET")                  // hk apps
	r.Handle("/apps/{app}", &GetAppInfo{e}).Methods("GET")         // hk info
	r.Handle("/apps/{app}", &DeleteApp{e}).Methods("DELETE")       // hk destroy
	r.Handle("/apps/{app}", &PatchApp{e}).Methods("PATCH")         // hk destroy
	r.Handle("/apps/{app}/deploys", &DeployApp{e}).Methods("POST") // Deploy an image to an app
	r.Handle("/apps", &PostApps{e}).Methods("POST")                // hk create
	r.Handle("/organizations/apps", &PostApps{e}).Methods("POST")  // hk create

	// Domains
	r.Handle("/apps/{app}/domains", &GetDomains{e}).Methods("GET")                 // hk domains
	r.Handle("/apps/{app}/domains", &PostDomains{e}).Methods("POST")               // hk domain-add
	r.Handle("/apps/{app}/domains/{hostname}", &DeleteDomain{e}).Methods("DELETE") // hk domain-remove

	// Deploys
	r.Handle("/deploys", &PostDeploys{e}).Methods("POST") // Deploy an app

	// Releases
	r.Handle("/apps/{app}/releases", &GetReleases{e}).Methods("GET")          // hk releases
	r.Handle("/apps/{app}/releases/{version}", &GetRelease{e}).Methods("GET") // hk release-info
	r.Handle("/apps/{app}/releases", &PostReleases{e}).Methods("POST")        // hk rollback

	// Configs
	r.Handle("/apps/{app}/config-vars", &GetConfigs{e}).Methods("GET")     // hk env, hk get
	r.Handle("/apps/{app}/config-vars", &PatchConfigs{e}).Methods("PATCH") // hk set, hk unset

	// Processes
	r.Handle("/apps/{app}/dynos", &GetProcesses{e}).Methods("GET")                     // hk dynos
	r.Handle("/apps/{app}/dynos", &PostProcess{e}).Methods("POST")                     // hk run
	r.Handle("/apps/{app}/dynos", &DeleteProcesses{e}).Methods("DELETE")               // hk restart
	r.Handle("/apps/{app}/dynos/{ptype}.{pid}", &DeleteProcesses{e}).Methods("DELETE") // hk restart web.1
	r.Handle("/apps/{app}/dynos/{pid}", &DeleteProcesses{e}).Methods("DELETE")         // hk restart web

	// Formations
	r.Handle("/apps/{app}/formation", &GetFormation{e}).Methods("GET")     // hk scale -l
	r.Handle("/apps/{app}/formation", &PatchFormation{e}).Methods("PATCH") // hk scale

	// OAuth
	r.Handle("/oauth/authorizations", &PostAuthorizations{e}).Methods("POST")

	// SSL
	sslRemoved := errHandler(ErrSSLRemoved)
	r.Handle("/apps/{app}/ssl-endpoints", sslRemoved).Methods("GET")           // hk ssl
	r.Handle("/apps/{app}/ssl-endpoints", sslRemoved).Methods("POST")          // hk ssl-cert-add
	r.Handle("/apps/{app}/ssl-endpoints/{cert}", sslRemoved).Methods("PATCH")  // hk ssl-cert-add, hk ssl-cert-rollback
	r.Handle("/apps/{app}/ssl-endpoints/{cert}", sslRemoved).Methods("DELETE") // hk ssl-destroy

	// Logs
	r.Handle("/apps/{app}/log-sessions", &PostLogs{e}).Methods("POST") // hk log

	api := Authenticate(r, authenticator)

	return httpx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		err := api.ServeHTTPContext(ctx, w, r)
		if err != nil {
			Error(w, err, http.StatusInternalServerError)
			reporter.Report(ctx, err)
		}
		return nil
	})
}