gitee.com/liuxuezhan/go-micro-v1.18.0@v1.0.0/api/handler/rpc/rpc.go (about)

     1  // Package rpc is a go-micro rpc handler.
     2  package rpc
     3  
     4  import (
     5  	"encoding/json"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/joncalhoun/qson"
    13  	"gitee.com/liuxuezhan/go-micro-v1.18.0/api"
    14  	"gitee.com/liuxuezhan/go-micro-v1.18.0/api/handler"
    15  	proto "gitee.com/liuxuezhan/go-micro-v1.18.0/api/internal/proto"
    16  	"gitee.com/liuxuezhan/go-micro-v1.18.0/client"
    17  	"gitee.com/liuxuezhan/go-micro-v1.18.0/client/selector"
    18  	"gitee.com/liuxuezhan/go-micro-v1.18.0/codec"
    19  	"gitee.com/liuxuezhan/go-micro-v1.18.0/codec/jsonrpc"
    20  	"gitee.com/liuxuezhan/go-micro-v1.18.0/codec/protorpc"
    21  	"gitee.com/liuxuezhan/go-micro-v1.18.0/errors"
    22  	"gitee.com/liuxuezhan/go-micro-v1.18.0/registry"
    23  	"gitee.com/liuxuezhan/go-micro-v1.18.0/util/ctx"
    24  )
    25  
    26  const (
    27  	Handler = "rpc"
    28  )
    29  
    30  var (
    31  	// supported json codecs
    32  	jsonCodecs = []string{
    33  		"application/grpc+json",
    34  		"application/json",
    35  		"application/json-rpc",
    36  	}
    37  
    38  	// support proto codecs
    39  	protoCodecs = []string{
    40  		"application/grpc",
    41  		"application/grpc+proto",
    42  		"application/proto",
    43  		"application/protobuf",
    44  		"application/proto-rpc",
    45  		"application/octet-stream",
    46  	}
    47  )
    48  
    49  type rpcHandler struct {
    50  	opts handler.Options
    51  	s    *api.Service
    52  }
    53  
    54  type buffer struct {
    55  	io.ReadCloser
    56  }
    57  
    58  func (b *buffer) Write(_ []byte) (int, error) {
    59  	return 0, nil
    60  }
    61  
    62  // strategy is a hack for selection
    63  func strategy(services []*registry.Service) selector.Strategy {
    64  	return func(_ []*registry.Service) selector.Next {
    65  		// ignore input to this function, use services above
    66  		return selector.Random(services)
    67  	}
    68  }
    69  
    70  func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    71  	defer r.Body.Close()
    72  	var service *api.Service
    73  
    74  	if h.s != nil {
    75  		// we were given the service
    76  		service = h.s
    77  	} else if h.opts.Router != nil {
    78  		// try get service from router
    79  		s, err := h.opts.Router.Route(r)
    80  		if err != nil {
    81  			writeError(w, r, errors.InternalServerError("go.micro.api", err.Error()))
    82  			return
    83  		}
    84  		service = s
    85  	} else {
    86  		// we have no way of routing the request
    87  		writeError(w, r, errors.InternalServerError("go.micro.api", "no route found"))
    88  		return
    89  	}
    90  
    91  	// only allow post when we have the router
    92  	if r.Method != "GET" && (h.opts.Router != nil && r.Method != "POST") {
    93  		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    94  		return
    95  	}
    96  
    97  	ct := r.Header.Get("Content-Type")
    98  
    99  	// Strip charset from Content-Type (like `application/json; charset=UTF-8`)
   100  	if idx := strings.IndexRune(ct, ';'); idx >= 0 {
   101  		ct = ct[:idx]
   102  	}
   103  
   104  	// micro client
   105  	c := h.opts.Service.Client()
   106  
   107  	// create strategy
   108  	so := selector.WithStrategy(strategy(service.Services))
   109  
   110  	// get payload
   111  	br, err := requestPayload(r)
   112  	if err != nil {
   113  		writeError(w, r, err)
   114  		return
   115  	}
   116  
   117  	// create context
   118  	cx := ctx.FromRequest(r)
   119  
   120  	var rsp []byte
   121  
   122  	switch {
   123  	// proto codecs
   124  	case hasCodec(ct, protoCodecs):
   125  		request := &proto.Message{}
   126  		// if the extracted payload isn't empty lets use it
   127  		if len(br) > 0 {
   128  			request = proto.NewMessage(br)
   129  		}
   130  
   131  		// create request/response
   132  		response := &proto.Message{}
   133  
   134  		req := c.NewRequest(
   135  			service.Name,
   136  			service.Endpoint.Name,
   137  			request,
   138  			client.WithContentType(ct),
   139  		)
   140  
   141  		// make the call
   142  		if err := c.Call(cx, req, response, client.WithSelectOption(so)); err != nil {
   143  			writeError(w, r, err)
   144  			return
   145  		}
   146  
   147  		// marshall response
   148  		rsp, _ = response.Marshal()
   149  	default:
   150  		// if json codec is not present set to json
   151  		if !hasCodec(ct, jsonCodecs) {
   152  			ct = "application/json"
   153  		}
   154  
   155  		// default to trying json
   156  		var request json.RawMessage
   157  		// if the extracted payload isn't empty lets use it
   158  		if len(br) > 0 {
   159  			request = json.RawMessage(br)
   160  		}
   161  
   162  		// create request/response
   163  		var response json.RawMessage
   164  
   165  		req := c.NewRequest(
   166  			service.Name,
   167  			service.Endpoint.Name,
   168  			&request,
   169  			client.WithContentType(ct),
   170  		)
   171  
   172  		// make the call
   173  		if err := c.Call(cx, req, &response, client.WithSelectOption(so)); err != nil {
   174  			writeError(w, r, err)
   175  			return
   176  		}
   177  
   178  		// marshall response
   179  		rsp, _ = response.MarshalJSON()
   180  	}
   181  
   182  	// write the response
   183  	writeResponse(w, r, rsp)
   184  }
   185  
   186  func (rh *rpcHandler) String() string {
   187  	return "rpc"
   188  }
   189  
   190  func hasCodec(ct string, codecs []string) bool {
   191  	for _, codec := range codecs {
   192  		if ct == codec {
   193  			return true
   194  		}
   195  	}
   196  	return false
   197  }
   198  
   199  // requestPayload takes a *http.Request.
   200  // If the request is a GET the query string parameters are extracted and marshaled to JSON and the raw bytes are returned.
   201  // If the request method is a POST the request body is read and returned
   202  func requestPayload(r *http.Request) ([]byte, error) {
   203  	// we have to decode json-rpc and proto-rpc because we suck
   204  	// well actually because there's no proxy codec right now
   205  	switch r.Header.Get("Content-Type") {
   206  	case "application/json-rpc":
   207  		msg := codec.Message{
   208  			Type:   codec.Request,
   209  			Header: make(map[string]string),
   210  		}
   211  		c := jsonrpc.NewCodec(&buffer{r.Body})
   212  		if err := c.ReadHeader(&msg, codec.Request); err != nil {
   213  			return nil, err
   214  		}
   215  		var raw json.RawMessage
   216  		if err := c.ReadBody(&raw); err != nil {
   217  			return nil, err
   218  		}
   219  		return ([]byte)(raw), nil
   220  	case "application/proto-rpc", "application/octet-stream":
   221  		msg := codec.Message{
   222  			Type:   codec.Request,
   223  			Header: make(map[string]string),
   224  		}
   225  		c := protorpc.NewCodec(&buffer{r.Body})
   226  		if err := c.ReadHeader(&msg, codec.Request); err != nil {
   227  			return nil, err
   228  		}
   229  		var raw proto.Message
   230  		if err := c.ReadBody(&raw); err != nil {
   231  			return nil, err
   232  		}
   233  		b, _ := raw.Marshal()
   234  		return b, nil
   235  	}
   236  
   237  	// otherwise as per usual
   238  
   239  	switch r.Method {
   240  	case "GET":
   241  		if len(r.URL.RawQuery) > 0 {
   242  			return qson.ToJSON(r.URL.RawQuery)
   243  		}
   244  	case "PATCH", "POST":
   245  		return ioutil.ReadAll(r.Body)
   246  	}
   247  
   248  	return []byte{}, nil
   249  }
   250  
   251  func writeError(w http.ResponseWriter, r *http.Request, err error) {
   252  	ce := errors.Parse(err.Error())
   253  
   254  	switch ce.Code {
   255  	case 0:
   256  		// assuming it's totally screwed
   257  		ce.Code = 500
   258  		ce.Id = "go.micro.api"
   259  		ce.Status = http.StatusText(500)
   260  		ce.Detail = "error during request: " + ce.Detail
   261  		w.WriteHeader(500)
   262  	default:
   263  		w.WriteHeader(int(ce.Code))
   264  	}
   265  
   266  	// response content type
   267  	w.Header().Set("Content-Type", "application/json")
   268  
   269  	// Set trailers
   270  	if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
   271  		w.Header().Set("Trailer", "grpc-status")
   272  		w.Header().Set("Trailer", "grpc-message")
   273  		w.Header().Set("grpc-status", "13")
   274  		w.Header().Set("grpc-message", ce.Detail)
   275  	}
   276  
   277  	w.Write([]byte(ce.Error()))
   278  }
   279  
   280  func writeResponse(w http.ResponseWriter, r *http.Request, rsp []byte) {
   281  	w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
   282  	w.Header().Set("Content-Length", strconv.Itoa(len(rsp)))
   283  
   284  	// Set trailers
   285  	if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
   286  		w.Header().Set("Trailer", "grpc-status")
   287  		w.Header().Set("Trailer", "grpc-message")
   288  		w.Header().Set("grpc-status", "0")
   289  		w.Header().Set("grpc-message", "")
   290  	}
   291  
   292  	// write response
   293  	w.Write(rsp)
   294  }
   295  
   296  func NewHandler(opts ...handler.Option) handler.Handler {
   297  	options := handler.NewOptions(opts...)
   298  	return &rpcHandler{
   299  		opts: options,
   300  	}
   301  }
   302  
   303  func WithService(s *api.Service, opts ...handler.Option) handler.Handler {
   304  	options := handler.NewOptions(opts...)
   305  	return &rpcHandler{
   306  		opts: options,
   307  		s:    s,
   308  	}
   309  }