Last active
May 3, 2020 17:00
-
-
Save mishudark/77517a534369e624a5509bd6137944ef to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package httputil | |
import ( | |
"context" | |
"net/http" | |
"encoding/json" | |
) | |
// RequestFunc may take information from an HTTP request and put it into a | |
// request context. In Servers, RequestFuncs are executed prior to invoking the | |
// endpoint. In Clients, RequestFuncs are executed after creating the request | |
// but prior to invoking the HTTP client. | |
type RequestFunc func(context.Context, *http.Request) context.Context | |
// 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) | |
// DecodeRequestFunc extracts a user-domain request object from an HTTP | |
// request object. It's designed to be used in HTTP servers, for server-side | |
// endpoints. One straightforward DecodeRequestFunc could be something that | |
// JSON decodes from the request body to the concrete request type. | |
type DecodeRequestFunc(type I) func(context.Context, *http.Request) (req I, err error) | |
// EncodeResponseFunc encodes the passed response object to the HTTP response | |
// writer. It's designed to be used in HTTP servers, for server-side | |
// endpoints. One straightforward EncodeResponseFunc could be something that | |
// JSON encodes the object directly to the response body. | |
type EncodeReponseFunc(type O) func(context.Context, http.ResponseWriter, O) error | |
// HandlerFunc performs the business logic for the service. | |
type HandlerFunc(type I, O) func(context.Context, I) (resp O, err error) | |
// 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, values := range headerer.Headers() { | |
for _, v := range values { | |
w.Header().Add(k, v) | |
} | |
} | |
} | |
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 | |
} | |
// 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(type O) (_ context.Context, w http.ResponseWriter, response O) error { | |
// convert response to an interface to check if it implements StatusCoder or Headerer | |
var r interface{} = response | |
w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
if headerer, ok := r.(Headerer); ok { | |
for k, values := range headerer.Headers() { | |
for _, v := range values { | |
w.Header().Add(k, v) | |
} | |
} | |
} | |
code := http.StatusOK | |
if sc, ok := r.(StatusCoder); ok { | |
code = sc.StatusCode() | |
} | |
w.WriteHeader(code) | |
if code == http.StatusNoContent { | |
return nil | |
} | |
return json.NewEncoder(w).Encode(response) | |
} | |
// Server wraps an business logic service and implements http.Handler. | |
type Server(type I, O) struct { | |
h HandlerFunc(I, O) | |
dec DecodeRequestFunc(I) | |
enc EncodeReponseFunc(O) | |
before []RequestFunc | |
errorEncoder ErrorEncoder | |
} | |
// ServeHTTP implements http.Handler. | |
func (s Server(I, O)) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
ctx := r.Context() | |
for _, f := range s.before { | |
ctx = f(ctx, r) | |
} | |
req, err := s.dec(ctx, r) | |
if err != nil { | |
s.errorEncoder(ctx, err, w) | |
return | |
} | |
resp, err := s.h(ctx, req) | |
if err != nil { | |
s.errorEncoder(ctx, err, w) | |
return | |
} | |
err = s.enc(ctx, w, resp) | |
if err != nil { | |
s.errorEncoder(ctx, err, w) | |
} | |
} | |
// ServerOption sets an optional parameter for servers. | |
type ServerOption(type I, O) func(*Server(I, O)) | |
// ServerBefore functions are executed on the HTTP request object before the | |
// request is decoded. | |
func ServerBefore(type I, O) (before ...RequestFunc) ServerOption(I, O) { | |
return func(s *Server(I, O)) { | |
s.before = append(s.before, before...) | |
} | |
} | |
// NewServer constructs a new server, which implements http.Handler. | |
func NewServer(type I, O)( | |
h HandlerFunc(I, O), | |
dec DecodeRequestFunc(I), | |
enc EncodeReponseFunc(O), | |
options ...ServerOption(I, O), | |
) *Server(I, O) { | |
s := &Server(I, O){ | |
h: h, | |
dec: dec, | |
enc: enc, | |
errorEncoder: DefaultErrorEncoder, | |
} | |
for _, option := range options { | |
option(s) | |
} | |
return s | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment