...

Source file src/github.com/go-kit/kit/transport/http/jsonrpc/server.go

Documentation: github.com/go-kit/kit/transport/http/jsonrpc

     1  package jsonrpc
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  
    10  	httptransport "github.com/go-kit/kit/transport/http"
    11  	"github.com/go-kit/log"
    12  )
    13  
    14  type requestIDKeyType struct{}
    15  
    16  var requestIDKey requestIDKeyType
    17  
    18  // Server wraps an endpoint and implements http.Handler.
    19  type Server struct {
    20  	ecm          EndpointCodecMap
    21  	before       []httptransport.RequestFunc
    22  	beforeCodec  []RequestFunc
    23  	after        []httptransport.ServerResponseFunc
    24  	errorEncoder httptransport.ErrorEncoder
    25  	finalizer    httptransport.ServerFinalizerFunc
    26  	logger       log.Logger
    27  }
    28  
    29  // NewServer constructs a new server, which implements http.Server.
    30  func NewServer(
    31  	ecm EndpointCodecMap,
    32  	options ...ServerOption,
    33  ) *Server {
    34  	s := &Server{
    35  		ecm:          ecm,
    36  		errorEncoder: DefaultErrorEncoder,
    37  		logger:       log.NewNopLogger(),
    38  	}
    39  	for _, option := range options {
    40  		option(s)
    41  	}
    42  	return s
    43  }
    44  
    45  // ServerOption sets an optional parameter for servers.
    46  type ServerOption func(*Server)
    47  
    48  // ServerBefore functions are executed on the HTTP request object before the
    49  // request is decoded.
    50  func ServerBefore(before ...httptransport.RequestFunc) ServerOption {
    51  	return func(s *Server) { s.before = append(s.before, before...) }
    52  }
    53  
    54  // ServerBeforeCodec functions are executed after the JSON request body has been
    55  // decoded, but before the method's decoder is called. This provides an opportunity
    56  // for middleware to inspect the contents of the rpc request before being passed
    57  // to the codec.
    58  func ServerBeforeCodec(beforeCodec ...RequestFunc) ServerOption {
    59  	return func(s *Server) { s.beforeCodec = append(s.beforeCodec, beforeCodec...) }
    60  }
    61  
    62  // ServerAfter functions are executed on the HTTP response writer after the
    63  // endpoint is invoked, but before anything is written to the client.
    64  func ServerAfter(after ...httptransport.ServerResponseFunc) ServerOption {
    65  	return func(s *Server) { s.after = append(s.after, after...) }
    66  }
    67  
    68  // ServerErrorEncoder is used to encode errors to the http.ResponseWriter
    69  // whenever they're encountered in the processing of a request. Clients can
    70  // use this to provide custom error formatting and response codes. By default,
    71  // errors will be written with the DefaultErrorEncoder.
    72  func ServerErrorEncoder(ee httptransport.ErrorEncoder) ServerOption {
    73  	return func(s *Server) { s.errorEncoder = ee }
    74  }
    75  
    76  // ServerErrorLogger is used to log non-terminal errors. By default, no errors
    77  // are logged. This is intended as a diagnostic measure. Finer-grained control
    78  // of error handling, including logging in more detail, should be performed in a
    79  // custom ServerErrorEncoder or ServerFinalizer, both of which have access to
    80  // the context.
    81  func ServerErrorLogger(logger log.Logger) ServerOption {
    82  	return func(s *Server) { s.logger = logger }
    83  }
    84  
    85  // ServerFinalizer is executed at the end of every HTTP request.
    86  // By default, no finalizer is registered.
    87  func ServerFinalizer(f httptransport.ServerFinalizerFunc) ServerOption {
    88  	return func(s *Server) { s.finalizer = f }
    89  }
    90  
    91  // ServeHTTP implements http.Handler.
    92  func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    93  	if r.Method != http.MethodPost {
    94  		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    95  		w.WriteHeader(http.StatusMethodNotAllowed)
    96  		_, _ = io.WriteString(w, "405 must POST\n")
    97  		return
    98  	}
    99  	ctx := r.Context()
   100  
   101  	if s.finalizer != nil {
   102  		iw := &interceptingWriter{w, http.StatusOK}
   103  		defer func() { s.finalizer(ctx, iw.code, r) }()
   104  		w = iw
   105  	}
   106  
   107  	for _, f := range s.before {
   108  		ctx = f(ctx, r)
   109  	}
   110  
   111  	// Decode the body into an  object
   112  	var req Request
   113  	err := json.NewDecoder(r.Body).Decode(&req)
   114  	if err != nil {
   115  		rpcerr := parseError("JSON could not be decoded: " + err.Error())
   116  		s.logger.Log("err", rpcerr)
   117  		s.errorEncoder(ctx, rpcerr, w)
   118  		return
   119  	}
   120  
   121  	ctx = context.WithValue(ctx, requestIDKey, req.ID)
   122  	ctx = context.WithValue(ctx, ContextKeyRequestMethod, req.Method)
   123  
   124  	for _, f := range s.beforeCodec {
   125  		ctx = f(ctx, r, req)
   126  	}
   127  
   128  	// Get the endpoint and codecs from the map using the method
   129  	// defined in the JSON  object
   130  	ecm, ok := s.ecm[req.Method]
   131  	if !ok {
   132  		err := methodNotFoundError(fmt.Sprintf("Method %s was not found.", req.Method))
   133  		s.logger.Log("err", err)
   134  		s.errorEncoder(ctx, err, w)
   135  		return
   136  	}
   137  
   138  	// Decode the JSON "params"
   139  	reqParams, err := ecm.Decode(ctx, req.Params)
   140  	if err != nil {
   141  		s.logger.Log("err", err)
   142  		s.errorEncoder(ctx, err, w)
   143  		return
   144  	}
   145  
   146  	// Call the Endpoint with the params
   147  	response, err := ecm.Endpoint(ctx, reqParams)
   148  	if err != nil {
   149  		s.logger.Log("err", err)
   150  		s.errorEncoder(ctx, err, w)
   151  		return
   152  	}
   153  
   154  	for _, f := range s.after {
   155  		ctx = f(ctx, w)
   156  	}
   157  
   158  	res := Response{
   159  		ID:      req.ID,
   160  		JSONRPC: Version,
   161  	}
   162  
   163  	// Encode the response from the Endpoint
   164  	resParams, err := ecm.Encode(ctx, response)
   165  	if err != nil {
   166  		s.logger.Log("err", err)
   167  		s.errorEncoder(ctx, err, w)
   168  		return
   169  	}
   170  
   171  	res.Result = resParams
   172  
   173  	w.Header().Set("Content-Type", ContentType)
   174  	_ = json.NewEncoder(w).Encode(res)
   175  }
   176  
   177  // DefaultErrorEncoder writes the error to the ResponseWriter,
   178  // as a json-rpc error response, with an InternalError status code.
   179  // The Error() string of the error will be used as the response error message.
   180  // If the error implements ErrorCoder, the provided code will be set on the
   181  // response error.
   182  // If the error implements Headerer, the given headers will be set.
   183  func DefaultErrorEncoder(ctx context.Context, err error, w http.ResponseWriter) {
   184  	w.Header().Set("Content-Type", ContentType)
   185  	if headerer, ok := err.(httptransport.Headerer); ok {
   186  		for k := range headerer.Headers() {
   187  			w.Header().Set(k, headerer.Headers().Get(k))
   188  		}
   189  	}
   190  
   191  	e := Error{
   192  		Code:    InternalError,
   193  		Message: err.Error(),
   194  	}
   195  	if sc, ok := err.(ErrorCoder); ok {
   196  		e.Code = sc.ErrorCode()
   197  	}
   198  
   199  	w.WriteHeader(http.StatusOK)
   200  
   201  	var requestID *RequestID
   202  	if v := ctx.Value(requestIDKey); v != nil {
   203  		requestID = v.(*RequestID)
   204  	}
   205  	_ = json.NewEncoder(w).Encode(Response{
   206  		ID:      requestID,
   207  		JSONRPC: Version,
   208  		Error:   &e,
   209  	})
   210  }
   211  
   212  // ErrorCoder is checked by DefaultErrorEncoder. If an error value implements
   213  // ErrorCoder, the integer result of ErrorCode() will be used as the JSONRPC
   214  // error code when encoding the error.
   215  //
   216  // By default, InternalError (-32603) is used.
   217  type ErrorCoder interface {
   218  	ErrorCode() int
   219  }
   220  
   221  // interceptingWriter intercepts calls to WriteHeader, so that a finalizer
   222  // can be given the correct status code.
   223  type interceptingWriter struct {
   224  	http.ResponseWriter
   225  	code int
   226  }
   227  
   228  // WriteHeader may not be explicitly called, so care must be taken to
   229  // initialize w.code to its default value of http.StatusOK.
   230  func (w *interceptingWriter) WriteHeader(code int) {
   231  	w.code = code
   232  	w.ResponseWriter.WriteHeader(code)
   233  }
   234  

View as plain text