+++ /dev/null
-package http
-
-import (
- "context"
- "encoding/json"
- "net/http"
-
- "github.com/go-kit/kit/endpoint"
- "github.com/go-kit/kit/log"
-)
-
-// Server wraps an endpoint and implements http.Handler.
-type Server struct {
- e endpoint.Endpoint
- dec DecodeRequestFunc
- enc EncodeResponseFunc
- before []RequestFunc
- after []ServerResponseFunc
- errorEncoder ErrorEncoder
- finalizer ServerFinalizerFunc
- logger log.Logger
-}
-
-// NewServer constructs a new server, which implements http.Handler and wraps
-// the provided endpoint.
-func NewServer(
- e endpoint.Endpoint,
- dec DecodeRequestFunc,
- enc EncodeResponseFunc,
- options ...ServerOption,
-) *Server {
- s := &Server{
- e: e,
- dec: dec,
- enc: enc,
- errorEncoder: DefaultErrorEncoder,
- logger: log.NewNopLogger(),
- }
- for _, option := range options {
- option(s)
- }
- return s
-}
-
-// ServerOption sets an optional parameter for servers.
-type ServerOption func(*Server)
-
-// ServerBefore functions are executed on the HTTP request object before the
-// request is decoded.
-func ServerBefore(before ...RequestFunc) ServerOption {
- return func(s *Server) { s.before = append(s.before, before...) }
-}
-
-// ServerAfter functions are executed on the HTTP response writer after the
-// endpoint is invoked, but before anything is written to the client.
-func ServerAfter(after ...ServerResponseFunc) ServerOption {
- return func(s *Server) { s.after = append(s.after, after...) }
-}
-
-// ServerErrorEncoder is used to encode errors to the http.ResponseWriter
-// whenever they're encountered in the processing of a request. Clients can
-// use this to provide custom error formatting and response codes. By default,
-// errors will be written with the DefaultErrorEncoder.
-func ServerErrorEncoder(ee ErrorEncoder) ServerOption {
- return func(s *Server) { s.errorEncoder = ee }
-}
-
-// ServerErrorLogger is used to log non-terminal errors. By default, no errors
-// are logged. This is intended as a diagnostic measure. Finer-grained control
-// of error handling, including logging in more detail, should be performed in a
-// custom ServerErrorEncoder or ServerFinalizer, both of which have access to
-// the context.
-func ServerErrorLogger(logger log.Logger) ServerOption {
- return func(s *Server) { s.logger = logger }
-}
-
-// ServerFinalizer is executed at the end of every HTTP request.
-// By default, no finalizer is registered.
-func ServerFinalizer(f ServerFinalizerFunc) ServerOption {
- return func(s *Server) { s.finalizer = f }
-}
-
-// ServeHTTP implements http.Handler.
-func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
-
- if s.finalizer != nil {
- iw := &interceptingWriter{w, http.StatusOK, 0}
- defer func() {
- ctx = context.WithValue(ctx, ContextKeyResponseHeaders, iw.Header())
- ctx = context.WithValue(ctx, ContextKeyResponseSize, iw.written)
- s.finalizer(ctx, iw.code, r)
- }()
- w = iw
- }
-
- for _, f := range s.before {
- ctx = f(ctx, r)
- }
-
- request, err := s.dec(ctx, r)
- if err != nil {
- s.logger.Log("err", err)
- s.errorEncoder(ctx, err, w)
- return
- }
-
- response, err := s.e(ctx, request)
- if err != nil {
- s.logger.Log("err", err)
- s.errorEncoder(ctx, err, w)
- return
- }
-
- for _, f := range s.after {
- ctx = f(ctx, w)
- }
-
- if err := s.enc(ctx, w, response); err != nil {
- s.logger.Log("err", err)
- s.errorEncoder(ctx, err, w)
- return
- }
-}
-
-// ErrorEncoder is responsible for encoding an error to the ResponseWriter.
-// Users are encouraged to use custom ErrorEncoders to encode HTTP errors to
-// their clients, and will likely want to pass and check for their own error
-// types. See the example shipping/handling service.
-type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter)
-
-// ServerFinalizerFunc can be used to perform work at the end of an HTTP
-// request, after the response has been written to the client. The principal
-// intended use is for request logging. In addition to the response code
-// provided in the function signature, additional response parameters are
-// provided in the context under keys with the ContextKeyResponse prefix.
-type ServerFinalizerFunc func(ctx context.Context, code int, r *http.Request)
-
-// EncodeJSONResponse is a EncodeResponseFunc that serializes the response as a
-// JSON object to the ResponseWriter. Many JSON-over-HTTP services can use it as
-// a sensible default. If the response implements Headerer, the provided headers
-// will be applied to the response. If the response implements StatusCoder, the
-// provided StatusCode will be used instead of 200.
-func EncodeJSONResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
- w.Header().Set("Content-Type", "application/json; charset=utf-8")
- if headerer, ok := response.(Headerer); ok {
- for k := range headerer.Headers() {
- w.Header().Set(k, headerer.Headers().Get(k))
- }
- }
- code := http.StatusOK
- if sc, ok := response.(StatusCoder); ok {
- code = sc.StatusCode()
- }
- w.WriteHeader(code)
- if code == http.StatusNoContent {
- return nil
- }
- return json.NewEncoder(w).Encode(response)
-}
-
-// DefaultErrorEncoder writes the error to the ResponseWriter, by default a
-// content type of text/plain, a body of the plain text of the error, and a
-// status code of 500. If the error implements Headerer, the provided headers
-// will be applied to the response. If the error implements json.Marshaler, and
-// the marshaling succeeds, a content type of application/json and the JSON
-// encoded form of the error will be used. If the error implements StatusCoder,
-// the provided StatusCode will be used instead of 500.
-func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) {
- contentType, body := "text/plain; charset=utf-8", []byte(err.Error())
- if marshaler, ok := err.(json.Marshaler); ok {
- if jsonBody, marshalErr := marshaler.MarshalJSON(); marshalErr == nil {
- contentType, body = "application/json; charset=utf-8", jsonBody
- }
- }
- w.Header().Set("Content-Type", contentType)
- if headerer, ok := err.(Headerer); ok {
- for k := range headerer.Headers() {
- w.Header().Set(k, headerer.Headers().Get(k))
- }
- }
- code := http.StatusInternalServerError
- if sc, ok := err.(StatusCoder); ok {
- code = sc.StatusCode()
- }
- w.WriteHeader(code)
- w.Write(body)
-}
-
-// StatusCoder is checked by DefaultErrorEncoder. If an error value implements
-// StatusCoder, the StatusCode will be used when encoding the error. By default,
-// StatusInternalServerError (500) is used.
-type StatusCoder interface {
- StatusCode() int
-}
-
-// Headerer is checked by DefaultErrorEncoder. If an error value implements
-// Headerer, the provided headers will be applied to the response writer, after
-// the Content-Type is set.
-type Headerer interface {
- Headers() http.Header
-}
-
-type interceptingWriter struct {
- http.ResponseWriter
- code int
- written int64
-}
-
-// WriteHeader may not be explicitly called, so care must be taken to
-// initialize w.code to its default value of http.StatusOK.
-func (w *interceptingWriter) WriteHeader(code int) {
- w.code = code
- w.ResponseWriter.WriteHeader(code)
-}
-
-func (w *interceptingWriter) Write(p []byte) (int, error) {
- n, err := w.ResponseWriter.Write(p)
- w.written += int64(n)
- return n, err
-}