storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/rpc/json2/server.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Copyright 2012 The Gorilla Authors. All rights reserved.
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  // Copyright 2020 MinIO, Inc. All rights reserved.
     7  // forked from https://github.com/gorilla/rpc/v2
     8  // modified to be used with MinIO under Apache
     9  // 2.0 license that can be found in the LICENSE file.
    10  
    11  package json2
    12  
    13  import (
    14  	"net/http"
    15  
    16  	jsoniter "github.com/json-iterator/go"
    17  
    18  	"storj.io/minio/pkg/rpc"
    19  )
    20  
    21  var null = jsoniter.RawMessage([]byte("null"))
    22  var Version = "2.0"
    23  
    24  // ----------------------------------------------------------------------------
    25  // Request and Response
    26  // ----------------------------------------------------------------------------
    27  
    28  // serverRequest represents a JSON-RPC request received by the server.
    29  type serverRequest struct {
    30  	// JSON-RPC protocol.
    31  	Version string `json:"jsonrpc"`
    32  
    33  	// A String containing the name of the method to be invoked.
    34  	Method string `json:"method"`
    35  
    36  	// A Structured value to pass as arguments to the method.
    37  	Params *jsoniter.RawMessage `json:"params"`
    38  
    39  	// The request id. MUST be a string, number or null.
    40  	// Our implementation will not do type checking for id.
    41  	// It will be copied as it is.
    42  	ID *jsoniter.RawMessage `json:"id"`
    43  }
    44  
    45  // serverResponse represents a JSON-RPC response returned by the server.
    46  type serverResponse struct {
    47  	// JSON-RPC protocol.
    48  	Version string `json:"jsonrpc"`
    49  
    50  	// The Object that was returned by the invoked method. This must be null
    51  	// in case there was an error invoking the method.
    52  	// As per spec the member will be omitted if there was an error.
    53  	Result interface{} `json:"result,omitempty"`
    54  
    55  	// An Error object if there was an error invoking the method. It must be
    56  	// null if there was no error.
    57  	// As per spec the member will be omitted if there was no error.
    58  	Error *Error `json:"error,omitempty"`
    59  
    60  	// This must be the same id as the request it is responding to.
    61  	ID *jsoniter.RawMessage `json:"id"`
    62  }
    63  
    64  // ----------------------------------------------------------------------------
    65  // Codec
    66  // ----------------------------------------------------------------------------
    67  
    68  // NewCustomCodec returns a new JSON Codec based on passed encoder selector.
    69  func NewCustomCodec(encSel rpc.EncoderSelector) *Codec {
    70  	return &Codec{encSel: encSel}
    71  }
    72  
    73  // NewCustomCodecWithErrorMapper returns a new JSON Codec based on the passed encoder selector
    74  // and also accepts an errorMapper function.
    75  // The errorMapper function will be called if the Service implementation returns an error, with that
    76  // error as a param, replacing it by the value returned by this function. This function is intended
    77  // to decouple your service implementation from the codec itself, making possible to return abstract
    78  // errors in your service, and then mapping them here to the JSON-RPC error codes.
    79  func NewCustomCodecWithErrorMapper(encSel rpc.EncoderSelector, errorMapper func(error) error) *Codec {
    80  	return &Codec{
    81  		encSel:      encSel,
    82  		errorMapper: errorMapper,
    83  	}
    84  }
    85  
    86  // NewCodec returns a new JSON Codec.
    87  func NewCodec() *Codec {
    88  	return NewCustomCodec(rpc.DefaultEncoderSelector)
    89  }
    90  
    91  // Codec creates a CodecRequest to process each request.
    92  type Codec struct {
    93  	encSel      rpc.EncoderSelector
    94  	errorMapper func(error) error
    95  }
    96  
    97  // NewRequest returns a CodecRequest.
    98  func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
    99  	return newCodecRequest(r, c.encSel.Select(r), c.errorMapper)
   100  }
   101  
   102  // ----------------------------------------------------------------------------
   103  // CodecRequest
   104  // ----------------------------------------------------------------------------
   105  
   106  // newCodecRequest returns a new CodecRequest.
   107  func newCodecRequest(r *http.Request, encoder rpc.Encoder, errorMapper func(error) error) rpc.CodecRequest {
   108  	// Decode the request body and check if RPC method is valid.
   109  	req := new(serverRequest)
   110  	var json = jsoniter.ConfigCompatibleWithStandardLibrary
   111  	err := json.NewDecoder(r.Body).Decode(req)
   112  
   113  	if err != nil {
   114  		err = &Error{
   115  			Code:    E_PARSE,
   116  			Message: err.Error(),
   117  			Data:    req,
   118  		}
   119  	} else if req.Version != Version {
   120  		err = &Error{
   121  			Code:    E_INVALID_REQ,
   122  			Message: "jsonrpc must be " + Version,
   123  			Data:    req,
   124  		}
   125  	}
   126  
   127  	r.Body.Close()
   128  	return &CodecRequest{request: req, err: err, encoder: encoder, errorMapper: errorMapper}
   129  }
   130  
   131  // CodecRequest decodes and encodes a single request.
   132  type CodecRequest struct {
   133  	request     *serverRequest
   134  	err         error
   135  	encoder     rpc.Encoder
   136  	errorMapper func(error) error
   137  }
   138  
   139  // Method returns the RPC method for the current request.
   140  //
   141  // The method uses a dotted notation as in "Service.Method".
   142  func (c *CodecRequest) Method() (string, error) {
   143  	if c.err == nil {
   144  		return c.request.Method, nil
   145  	}
   146  	return "", c.err
   147  }
   148  
   149  // ReadRequest fills the request object for the RPC method.
   150  //
   151  // ReadRequest parses request parameters in two supported forms in
   152  // accordance with http://www.jsonrpc.org/specification#parameter_structures
   153  //
   154  // by-position: params MUST be an Array, containing the
   155  // values in the Server expected order.
   156  //
   157  // by-name: params MUST be an Object, with member names
   158  // that match the Server expected parameter names. The
   159  // absence of expected names MAY result in an error being
   160  // generated. The names MUST match exactly, including
   161  // case, to the method's expected parameters.
   162  func (c *CodecRequest) ReadRequest(args interface{}) error {
   163  	if c.err == nil && c.request.Params != nil {
   164  		// Note: if c.request.Params is nil it's not an error, it's an optional member.
   165  		// JSON params structured object. Unmarshal to the args object.
   166  		var json = jsoniter.ConfigCompatibleWithStandardLibrary
   167  		if err := json.Unmarshal(*c.request.Params, args); err != nil {
   168  			// Clearly JSON params is not a structured object,
   169  			// fallback and attempt an unmarshal with JSON params as
   170  			// array value and RPC params is struct. Unmarshal into
   171  			// array containing the request struct.
   172  			params := [1]interface{}{args}
   173  			if err = json.Unmarshal(*c.request.Params, &params); err != nil {
   174  				c.err = &Error{
   175  					Code:    E_INVALID_REQ,
   176  					Message: err.Error(),
   177  					Data:    c.request.Params,
   178  				}
   179  			}
   180  		}
   181  	}
   182  	return c.err
   183  }
   184  
   185  // WriteResponse encodes the response and writes it to the ResponseWriter.
   186  func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
   187  	res := &serverResponse{
   188  		Version: Version,
   189  		Result:  reply,
   190  		ID:      c.request.ID,
   191  	}
   192  	c.writeServerResponse(w, res)
   193  }
   194  
   195  func (c *CodecRequest) WriteError(w http.ResponseWriter, status int, err error) {
   196  	err = c.tryToMapIfNotAnErrorAlready(err)
   197  	jsonErr, ok := err.(*Error)
   198  	if !ok {
   199  		jsonErr = &Error{
   200  			Code:    E_SERVER,
   201  			Message: err.Error(),
   202  		}
   203  	}
   204  	res := &serverResponse{
   205  		Version: Version,
   206  		Error:   jsonErr,
   207  		ID:      c.request.ID,
   208  	}
   209  	c.writeServerResponse(w, res)
   210  }
   211  
   212  func (c CodecRequest) tryToMapIfNotAnErrorAlready(err error) error {
   213  	if _, ok := err.(*Error); ok || c.errorMapper == nil {
   214  		return err
   215  	}
   216  	return c.errorMapper(err)
   217  }
   218  
   219  func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, res *serverResponse) {
   220  	// ID is null for notifications and they don't have a response, unless we couldn't even parse the JSON, in that
   221  	// case we can't know whether it was intended to be a notification
   222  	if c.request.ID != nil || isParseErrorResponse(res) {
   223  		w.Header().Set("Content-Type", "application/json; charset=utf-8")
   224  		var json = jsoniter.ConfigCompatibleWithStandardLibrary
   225  		encoder := json.NewEncoder(c.encoder.Encode(w))
   226  		err := encoder.Encode(res)
   227  
   228  		// Not sure in which case will this happen. But seems harmless.
   229  		if err != nil {
   230  			rpc.WriteError(w, http.StatusInternalServerError, err.Error())
   231  		}
   232  	}
   233  }
   234  
   235  func isParseErrorResponse(res *serverResponse) bool {
   236  	return res != nil && res.Error != nil && res.Error.Code == E_PARSE
   237  }
   238  
   239  type EmptyResponse struct {
   240  }