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 }