github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/api/handler/rpc/rpc.go (about) 1 // Copyright 2020 Asim Aslam 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // Original source: github.com/micro/go-micro/v3/api/handler/rpc/rpc.go 16 17 // Package rpc is a go-micro rpc handler. 18 package rpc 19 20 import ( 21 bts "bytes" 22 "encoding/json" 23 "io" 24 "net/http" 25 "strconv" 26 "strings" 27 28 "github.com/tickoalcantara12/micro/v3/service/api" 29 "github.com/tickoalcantara12/micro/v3/service/api/handler" 30 "github.com/tickoalcantara12/micro/v3/service/client" 31 "github.com/tickoalcantara12/micro/v3/service/errors" 32 "github.com/tickoalcantara12/micro/v3/service/logger" 33 "github.com/tickoalcantara12/micro/v3/util/codec/bytes" 34 "github.com/tickoalcantara12/micro/v3/util/ctx" 35 ) 36 37 const ( 38 Handler = "rpc" 39 ) 40 41 var ( 42 // supported json codecs 43 jsonCodecs = []string{ 44 "application/grpc+json", 45 "application/json", 46 "application/json-rpc", 47 } 48 49 // support proto codecs 50 protoCodecs = []string{ 51 "application/grpc", 52 "application/grpc+proto", 53 "application/proto", 54 "application/protobuf", 55 "application/proto-rpc", 56 "application/octet-stream", 57 } 58 ) 59 60 type rpcHandler struct { 61 opts handler.Options 62 s *api.Service 63 } 64 65 type buffer struct { 66 io.ReadCloser 67 } 68 69 func (b *buffer) Write(_ []byte) (int, error) { 70 return 0, nil 71 } 72 73 // see https://stackoverflow.com/questions/28595664/how-to-stop-json-marshal-from-escaping-and/28596225 74 func jsonMarshal(t interface{}) ([]byte, error) { 75 buffer := &bts.Buffer{} 76 encoder := json.NewEncoder(buffer) 77 encoder.SetEscapeHTML(false) 78 err := encoder.Encode(t) 79 return bts.TrimRight(buffer.Bytes(), "\n"), err 80 } 81 82 func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 83 bsize := handler.DefaultMaxRecvSize 84 if h.opts.MaxRecvSize > 0 { 85 bsize = h.opts.MaxRecvSize 86 } 87 88 r.Body = http.MaxBytesReader(w, r.Body, bsize) 89 90 defer r.Body.Close() 91 var service *api.Service 92 var c client.Client 93 94 if v, ok := r.Context().(handler.Context); ok { 95 // we were given the service 96 service = v.Service() 97 c = v.Client() 98 } else if h.opts.Router != nil { 99 // try get service from router 100 s, err := h.opts.Router.Route(r) 101 if err != nil { 102 writeError(w, r, errors.InternalServerError("go.micro.api", err.Error())) 103 return 104 } 105 service = s 106 c = h.opts.Client 107 } else { 108 // we have no way of routing the request 109 writeError(w, r, errors.InternalServerError("go.micro.api", "no route found")) 110 return 111 } 112 113 ct := r.Header.Get("Content-Type") 114 115 // Strip charset from Content-Type (like `application/json; charset=UTF-8`) 116 if idx := strings.IndexRune(ct, ';'); idx >= 0 { 117 ct = ct[:idx] 118 } 119 120 // create context 121 cx := ctx.FromRequest(r) 122 123 // set merged context to request 124 *r = *r.Clone(cx) 125 // if stream we currently only support json 126 if isStream(r, service) { 127 serveStream(cx, w, r, service, c) 128 return 129 } 130 131 // create custom router 132 var nodes []string 133 for _, service := range service.Services { 134 for _, node := range service.Nodes { 135 nodes = append(nodes, node.Address) 136 } 137 } 138 callOpt := client.WithAddress(nodes...) 139 140 // walk the standard call path 141 // get payload 142 br, err := api.RequestPayload(r) 143 if err != nil { 144 writeError(w, r, err) 145 return 146 } 147 148 var rsp []byte 149 150 switch { 151 // proto codecs 152 case hasCodec(ct, protoCodecs): 153 var request *bytes.Frame 154 // if the extracted payload isn't empty lets use it 155 if len(br) > 0 { 156 request = &bytes.Frame{Data: br} 157 } 158 159 // create the request 160 req := c.NewRequest( 161 service.Name, 162 service.Endpoint.Name, 163 request, 164 client.WithContentType(ct), 165 ) 166 167 // make the call 168 var response *bytes.Frame 169 if err := c.Call(cx, req, response, callOpt); err != nil { 170 writeError(w, r, err) 171 return 172 } 173 rsp = response.Data 174 default: 175 // if json codec is not present set to json 176 if !hasCodec(ct, jsonCodecs) { 177 ct = "application/json" 178 } 179 180 // default to trying json 181 var request json.RawMessage 182 // if the extracted payload isn't empty lets use it 183 if len(br) > 0 { 184 request = json.RawMessage(br) 185 } 186 187 // create request/response 188 var response interface{} 189 190 req := c.NewRequest( 191 service.Name, 192 service.Endpoint.Name, 193 &request, 194 client.WithContentType(ct), 195 ) 196 // make the call 197 if err := c.Call(cx, req, &response, callOpt); err != nil { 198 writeError(w, r, err) 199 return 200 } 201 202 // marshall response 203 // see https://play.golang.org/p/oBNxUjVTzus 204 rsp, err = jsonMarshal(response) 205 if err != nil { 206 writeError(w, r, err) 207 return 208 } 209 } 210 211 // write the response 212 writeResponse(w, r, rsp, ct) 213 } 214 215 func (rh *rpcHandler) String() string { 216 return "rpc" 217 } 218 219 func hasCodec(ct string, codecs []string) bool { 220 for _, codec := range codecs { 221 if ct == codec { 222 return true 223 } 224 } 225 return false 226 } 227 228 func writeError(w http.ResponseWriter, r *http.Request, err error) { 229 // response content type 230 w.Header().Set("Content-Type", "application/json") 231 232 // parse out the error code 233 ce := errors.Parse(err.Error()) 234 235 switch ce.Code { 236 case 0: 237 // assuming it's totally screwed 238 ce.Code = 500 239 ce.Id = "go.micro.api" 240 ce.Status = http.StatusText(500) 241 ce.Detail = "error during request: " + ce.Detail 242 w.WriteHeader(500) 243 default: 244 w.WriteHeader(int(ce.Code)) 245 } 246 247 // Set trailers 248 if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { 249 w.Header().Set("Trailer", "grpc-status") 250 w.Header().Set("Trailer", "grpc-message") 251 w.Header().Set("grpc-status", "13") 252 w.Header().Set("grpc-message", ce.Detail) 253 } 254 255 _, werr := w.Write([]byte(ce.Error())) 256 if werr != nil { 257 if logger.V(logger.ErrorLevel, logger.DefaultLogger) { 258 logger.Error(werr) 259 } 260 } 261 } 262 263 func writeResponse(w http.ResponseWriter, r *http.Request, rsp []byte, ct string) { 264 w.Header().Set("Content-Type", ct) 265 w.Header().Set("Content-Length", strconv.Itoa(len(rsp))) 266 267 // Set trailers 268 if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { 269 w.Header().Set("Trailer", "grpc-status") 270 w.Header().Set("Trailer", "grpc-message") 271 w.Header().Set("grpc-status", "0") 272 w.Header().Set("grpc-message", "") 273 } 274 275 // write 204 status if rsp is nil 276 if len(rsp) == 0 { 277 w.WriteHeader(http.StatusNoContent) 278 } 279 280 // write response 281 _, err := w.Write(rsp) 282 if err != nil { 283 if logger.V(logger.ErrorLevel, logger.DefaultLogger) { 284 logger.Error(err) 285 } 286 } 287 288 } 289 290 func NewHandler(opts ...handler.Option) handler.Handler { 291 options := handler.NewOptions(opts...) 292 return &rpcHandler{ 293 opts: options, 294 } 295 } 296 297 func WithService(s *api.Service, opts ...handler.Option) handler.Handler { 298 options := handler.NewOptions(opts...) 299 return &rpcHandler{ 300 opts: options, 301 s: s, 302 } 303 }