github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/api/api.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); 2 // you may not use this file except in compliance with the License. 3 // You may obtain a copy of the License at 4 // 5 // https://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, 9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 // See the License for the specific language governing permissions and 11 // limitations under the License. 12 // 13 // Original source: github.com/micro/go-micro/v3/api/api.go 14 15 package api 16 17 import ( 18 "crypto/tls" 19 "encoding/json" 20 "errors" 21 "io" 22 "io/ioutil" 23 "net/http" 24 "regexp" 25 "strings" 26 27 jsonpatch "github.com/evanphx/json-patch/v5" 28 29 "github.com/tickoalcantara12/micro/v3/service/api/resolver" 30 "github.com/tickoalcantara12/micro/v3/service/context/metadata" 31 "github.com/tickoalcantara12/micro/v3/service/registry" 32 "github.com/tickoalcantara12/micro/v3/service/server" 33 "github.com/tickoalcantara12/micro/v3/util/acme" 34 "github.com/tickoalcantara12/micro/v3/util/codec" 35 "github.com/tickoalcantara12/micro/v3/util/codec/bytes" 36 "github.com/tickoalcantara12/micro/v3/util/codec/jsonrpc" 37 "github.com/tickoalcantara12/micro/v3/util/codec/protorpc" 38 "github.com/tickoalcantara12/micro/v3/util/qson" 39 "github.com/oxtoacart/bpool" 40 ) 41 42 var ( 43 bufferPool = bpool.NewSizedBufferPool(1024, 8) 44 ) 45 46 type buffer struct { 47 io.ReadCloser 48 } 49 50 func (b *buffer) Write(_ []byte) (int, error) { 51 return 0, nil 52 } 53 54 type API interface { 55 // Initialise options 56 Init(...Option) error 57 // Get the options 58 Options() Options 59 // Register a http handler 60 Register(*Endpoint) error 61 // Register a route 62 Deregister(*Endpoint) error 63 // Implementation of api 64 String() string 65 } 66 67 // Server serves api requests 68 type Server interface { 69 Address() string 70 Init(opts ...Option) error 71 Handle(path string, handler http.Handler) 72 Start() error 73 Stop() error 74 } 75 76 type Options struct { 77 EnableACME bool 78 EnableCORS bool 79 ACMEProvider acme.Provider 80 EnableTLS bool 81 ACMEHosts []string 82 TLSConfig *tls.Config 83 Resolver resolver.Resolver 84 Wrappers []Wrapper 85 } 86 87 type Option func(*Options) 88 89 type Wrapper func(h http.Handler) http.Handler 90 91 func WrapHandler(w ...Wrapper) Option { 92 return func(o *Options) { 93 o.Wrappers = append(o.Wrappers, w...) 94 } 95 } 96 97 func EnableCORS(b bool) Option { 98 return func(o *Options) { 99 o.EnableCORS = b 100 } 101 } 102 103 func EnableACME(b bool) Option { 104 return func(o *Options) { 105 o.EnableACME = b 106 } 107 } 108 109 func ACMEHosts(hosts ...string) Option { 110 return func(o *Options) { 111 o.ACMEHosts = hosts 112 } 113 } 114 115 func ACMEProvider(p acme.Provider) Option { 116 return func(o *Options) { 117 o.ACMEProvider = p 118 } 119 } 120 121 func EnableTLS(b bool) Option { 122 return func(o *Options) { 123 o.EnableTLS = b 124 } 125 } 126 127 func TLSConfig(t *tls.Config) Option { 128 return func(o *Options) { 129 o.TLSConfig = t 130 } 131 } 132 133 func Resolver(r resolver.Resolver) Option { 134 return func(o *Options) { 135 o.Resolver = r 136 } 137 } 138 139 // Endpoint is a mapping between an RPC method and HTTP endpoint 140 type Endpoint struct { 141 // RPC Method e.g. Greeter.Hello 142 Name string 143 // Description e.g what's this endpoint for 144 Description string 145 // API Handler e.g rpc, proxy 146 Handler string 147 // HTTP Host e.g example.com 148 Host []string 149 // HTTP Methods e.g GET, POST 150 Method []string 151 // HTTP Path e.g /greeter. Expect POSIX regex 152 Path []string 153 // Body destination 154 // "*" or "" - top level message value 155 // "string" - inner message value 156 Body string 157 // Stream flag 158 Stream bool 159 } 160 161 // Service represents an API service 162 type Service struct { 163 // Name of service 164 Name string 165 // The endpoint for this service 166 Endpoint *Endpoint 167 // Versions of this service 168 Services []*registry.Service 169 } 170 171 // Encode encodes an endpoint to endpoint metadata 172 func Encode(e *Endpoint) map[string]string { 173 if e == nil { 174 return nil 175 } 176 177 // endpoint map 178 ep := make(map[string]string) 179 180 // set vals only if they exist 181 set := func(k, v string) { 182 if len(v) == 0 { 183 return 184 } 185 ep[k] = v 186 } 187 188 set("endpoint", e.Name) 189 set("description", e.Description) 190 set("handler", e.Handler) 191 set("method", strings.Join(e.Method, ",")) 192 set("path", strings.Join(e.Path, ",")) 193 set("host", strings.Join(e.Host, ",")) 194 195 return ep 196 } 197 198 // Decode decodes endpoint metadata into an endpoint 199 func Decode(e map[string]string) *Endpoint { 200 if e == nil { 201 return nil 202 } 203 204 return &Endpoint{ 205 Name: e["endpoint"], 206 Description: e["description"], 207 Method: slice(e["method"]), 208 Path: slice(e["path"]), 209 Host: slice(e["host"]), 210 Handler: e["handler"], 211 } 212 } 213 214 // Validate validates an endpoint to guarantee it won't blow up when being served 215 func Validate(e *Endpoint) error { 216 if e == nil { 217 return errors.New("endpoint is nil") 218 } 219 220 if len(e.Name) == 0 { 221 return errors.New("name required") 222 } 223 224 for _, p := range e.Path { 225 ps := p[0] 226 pe := p[len(p)-1] 227 228 if ps == '^' && pe == '$' { 229 _, err := regexp.CompilePOSIX(p) 230 if err != nil { 231 return err 232 } 233 } else if ps == '^' && pe != '$' { 234 return errors.New("invalid path") 235 } else if ps != '^' && pe == '$' { 236 return errors.New("invalid path") 237 } 238 } 239 240 if len(e.Handler) == 0 { 241 return errors.New("invalid handler") 242 } 243 244 return nil 245 } 246 247 func WithEndpoint(e *Endpoint) server.HandlerOption { 248 return server.EndpointMetadata(e.Name, Encode(e)) 249 } 250 251 func slice(s string) []string { 252 var sl []string 253 254 for _, p := range strings.Split(s, ",") { 255 if str := strings.TrimSpace(p); len(str) > 0 { 256 sl = append(sl, strings.TrimSpace(p)) 257 } 258 } 259 260 return sl 261 } 262 263 // RequestPayload takes a *http.Request and returns the payload 264 // If the request is a GET the query string parameters are extracted and marshaled to JSON and the raw bytes are returned. 265 // If the request method is a POST the request body is read and returned 266 func RequestPayload(r *http.Request) ([]byte, error) { 267 var err error 268 269 // we have to decode json-rpc and proto-rpc because we suck 270 // well actually because there's no proxy codec right now 271 272 ct := r.Header.Get("Content-Type") 273 switch { 274 case strings.Contains(ct, "application/json-rpc"): 275 msg := codec.Message{ 276 Type: codec.Request, 277 Header: make(map[string]string), 278 } 279 c := jsonrpc.NewCodec(&buffer{r.Body}) 280 if err = c.ReadHeader(&msg, codec.Request); err != nil { 281 return nil, err 282 } 283 var raw json.RawMessage 284 if err = c.ReadBody(&raw); err != nil { 285 return nil, err 286 } 287 return ([]byte)(raw), nil 288 case strings.Contains(ct, "application/proto-rpc"), strings.Contains(ct, "application/octet-stream"): 289 msg := codec.Message{ 290 Type: codec.Request, 291 Header: make(map[string]string), 292 } 293 c := protorpc.NewCodec(&buffer{r.Body}) 294 if err = c.ReadHeader(&msg, codec.Request); err != nil { 295 return nil, err 296 } 297 var raw *bytes.Frame 298 if err = c.ReadBody(&raw); err != nil { 299 return nil, err 300 } 301 return raw.Data, nil 302 case strings.Contains(ct, "application/x-www-form-urlencoded"): 303 r.ParseForm() 304 305 // generate a new set of values from the form 306 vals := make(map[string]string) 307 for k, v := range r.Form { 308 vals[k] = strings.Join(v, ",") 309 } 310 311 // marshal 312 return json.Marshal(vals) 313 case strings.Contains(ct, "multipart/form-data"): 314 // 10MB buffer 315 if err := r.ParseMultipartForm(int64(10 << 20)); err != nil { 316 return nil, err 317 } 318 vals := make(map[string]interface{}) 319 for k, v := range r.MultipartForm.Value { 320 vals[k] = strings.Join(v, ",") 321 } 322 for k, _ := range r.MultipartForm.File { 323 f, _, err := r.FormFile(k) 324 if err != nil { 325 return nil, err 326 } 327 b, err := ioutil.ReadAll(f) 328 if err != nil { 329 return nil, err 330 } 331 vals[k] = b 332 } 333 return json.Marshal(vals) 334 // TODO: application/grpc 335 } 336 337 // otherwise as per usual 338 ctx := r.Context() 339 // dont user metadata.FromContext as it mangles names 340 md, ok := metadata.FromContext(ctx) 341 if !ok { 342 md = make(map[string]string) 343 } 344 345 // allocate maximum 346 matches := make(map[string]interface{}, len(md)) 347 bodydst := "" 348 349 // get fields from url path 350 for k, v := range md { 351 k = strings.ToLower(k) 352 // filter own keys 353 if strings.HasPrefix(k, "x-api-field-") { 354 matches[strings.TrimPrefix(k, "x-api-field-")] = v 355 delete(md, k) 356 } else if k == "x-api-body" { 357 bodydst = v 358 delete(md, k) 359 } 360 } 361 362 // map of all fields 363 req := make(map[string]interface{}, len(md)) 364 365 // get fields from url values 366 if len(r.URL.RawQuery) > 0 { 367 umd := make(map[string]interface{}) 368 err = qson.Unmarshal(&umd, r.URL.RawQuery) 369 if err != nil { 370 return nil, err 371 } 372 for k, v := range umd { 373 matches[k] = v 374 } 375 } 376 377 // restore context without fields 378 *r = *r.Clone(metadata.NewContext(ctx, md)) 379 380 for k, v := range matches { 381 ps := strings.Split(k, ".") 382 if len(ps) == 1 { 383 req[k] = v 384 continue 385 } 386 em := make(map[string]interface{}) 387 em[ps[len(ps)-1]] = v 388 for i := len(ps) - 2; i > 0; i-- { 389 nm := make(map[string]interface{}) 390 nm[ps[i]] = em 391 em = nm 392 } 393 if vm, ok := req[ps[0]]; ok { 394 // nested map 395 nm := vm.(map[string]interface{}) 396 for vk, vv := range em { 397 nm[vk] = vv 398 } 399 req[ps[0]] = nm 400 } else { 401 req[ps[0]] = em 402 } 403 } 404 pathbuf := []byte("{}") 405 if len(req) > 0 { 406 pathbuf, err = json.Marshal(req) 407 if err != nil { 408 return nil, err 409 } 410 } 411 412 urlbuf := []byte("{}") 413 out, err := jsonpatch.MergeMergePatches(urlbuf, pathbuf) 414 if err != nil { 415 return nil, err 416 } 417 418 switch r.Method { 419 case "GET": 420 // empty response 421 if strings.Contains(ct, "application/json") && string(out) == "{}" { 422 return out, nil 423 } else if string(out) == "{}" && !strings.Contains(ct, "application/json") { 424 return []byte{}, nil 425 } 426 return out, nil 427 case "PATCH", "POST", "PUT", "DELETE": 428 bodybuf := []byte("{}") 429 buf := bufferPool.Get() 430 defer bufferPool.Put(buf) 431 if _, err := buf.ReadFrom(r.Body); err != nil { 432 return nil, err 433 } 434 if b := buf.Bytes(); len(b) > 0 { 435 bodybuf = b 436 } 437 if bodydst == "" || bodydst == "*" { 438 // jsonpatch resequences the json object so we avoid it if possible (some usecases such as 439 // validating signatures require the request body to be unchangedd). We're keeping support 440 // for the custom paramaters for backwards compatability reasons. 441 if string(out) == "{}" { 442 return bodybuf, nil 443 } 444 445 if out, err = jsonpatch.MergeMergePatches(out, bodybuf); err == nil { 446 return out, nil 447 } 448 } 449 var jsonbody map[string]interface{} 450 if json.Valid(bodybuf) { 451 if err = json.Unmarshal(bodybuf, &jsonbody); err != nil { 452 return nil, err 453 } 454 } 455 dstmap := make(map[string]interface{}) 456 ps := strings.Split(bodydst, ".") 457 if len(ps) == 1 { 458 if jsonbody != nil { 459 dstmap[ps[0]] = jsonbody 460 } else { 461 // old unexpected behaviour 462 dstmap[ps[0]] = bodybuf 463 } 464 } else { 465 em := make(map[string]interface{}) 466 if jsonbody != nil { 467 em[ps[len(ps)-1]] = jsonbody 468 } else { 469 // old unexpected behaviour 470 em[ps[len(ps)-1]] = bodybuf 471 } 472 for i := len(ps) - 2; i > 0; i-- { 473 nm := make(map[string]interface{}) 474 nm[ps[i]] = em 475 em = nm 476 } 477 dstmap[ps[0]] = em 478 } 479 480 bodyout, err := json.Marshal(dstmap) 481 if err != nil { 482 return nil, err 483 } 484 485 if out, err = jsonpatch.MergeMergePatches(out, bodyout); err == nil { 486 return out, nil 487 } 488 489 //fallback to previous unknown behaviour 490 return bodybuf, nil 491 } 492 493 return []byte{}, nil 494 }