OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package epfrontend | |
6 | |
7 import ( | |
8 "bytes" | |
9 "encoding/json" | |
10 "fmt" | |
11 "io/ioutil" | |
12 "net/http" | |
13 "strconv" | |
14 "strings" | |
15 | |
16 "github.com/GoogleCloudPlatform/go-endpoints/endpoints" | |
17 "github.com/luci/luci-go/common/logging" | |
18 "github.com/luci/luci-go/common/paniccatcher" | |
19 ) | |
20 | |
21 const ( | |
22 // DefaultServerRoot is the default Server root value. | |
23 DefaultServerRoot = "/_ah/api/" | |
24 ) | |
25 | |
26 var ( | |
27 errMethodNotAllowed = endpoints.NewAPIError(http.StatusText(http.StatusM
ethodNotAllowed), | |
28 "", http.StatusMethodNotAllowed) | |
29 ) | |
30 | |
31 type handlerFuncWithError func(http.ResponseWriter, *http.Request) error | |
32 | |
33 // endpoint is a resolved backend endpoint. | |
34 type endpoint struct { | |
35 // desc is the resolved service descriptor. | |
36 desc *endpoints.APIDescriptor | |
37 // method is the resolved method within the service. | |
38 method *endpoints.APIMethod | |
39 // backendPath is the backend path of this method. | |
40 backendPath string | |
41 } | |
42 | |
43 type endpointNode struct { | |
44 // parent points to this endpointNode's parent node. If nil, this is the | |
45 // root endpoint node. | |
46 parent *endpointNode | |
47 | |
48 // component is the path component's value. | |
49 // | |
50 // If this is an empty string, this is a query parameter component. | |
51 component string | |
52 // paramName is the query parameter name for this node. It is valid if | |
53 // component is "". | |
54 paramName string | |
55 // ep is the endpoint attached to this node. It can be nil if no endpoin
t is | |
56 // attached. | |
57 ep *endpoint | |
58 | |
59 children map[string]*endpointNode | |
60 } | |
61 | |
62 func (n *endpointNode) addChild(components []string, ep *endpoint) error { | |
63 comp := components[0] | |
64 paramName := "" | |
65 | |
66 if strings.HasPrefix(comp, "{") && strings.HasSuffix(comp, "}") { | |
67 // Replace query parameter with empty string. | |
68 paramName = comp[1 : len(comp)-1] | |
69 comp = "" | |
70 } | |
71 | |
72 child := n.children[comp] | |
73 if child == nil { | |
74 if n.children == nil { | |
75 n.children = map[string]*endpointNode{} | |
76 } | |
77 | |
78 child = &endpointNode{ | |
79 parent: n, | |
80 component: comp, | |
81 paramName: paramName, | |
82 } | |
83 n.children[child.component] = child | |
84 } | |
85 | |
86 if len(components) > 1 { | |
87 return child.addChild(components[1:], ep) | |
88 } | |
89 | |
90 child.ep = ep | |
91 return nil | |
92 } | |
93 | |
94 func (n *endpointNode) lookup(components ...string) *endpointNode { | |
95 if len(components) == 0 { | |
96 if n.ep == nil { | |
97 // Not a leaf node! | |
98 return nil | |
99 } | |
100 return n | |
101 } | |
102 | |
103 // Is this node parent to a query parameter? | |
104 if child := n.children[""]; child != nil { | |
105 // Assume that this component is a query parameter. Can we resol
ve against | |
106 // the remainder of the tree? | |
107 if cn := child.lookup(components[1:]...); cn != nil { | |
108 return cn | |
109 } | |
110 } | |
111 | |
112 // Use this node as a direct value. | |
113 if child := n.children[components[0]]; child != nil { | |
114 return child.lookup(components[1:]...) | |
115 } | |
116 return nil | |
117 } | |
118 | |
119 // serviceCall is a stateful structure that will be populated with the service | |
120 // call's parameters as they are resolved. | |
121 type serviceCall struct { | |
122 // path is the initial service call path. | |
123 path string | |
124 | |
125 // ep is the resolved endpoint description for this call. | |
126 ep *endpoint | |
127 // params is the map of key/value query parameters populated during | |
128 // resolution. | |
129 params map[string]interface{} | |
130 } | |
131 | |
132 func (c *serviceCall) addParam(k string, v interface{}) { | |
133 if c.params == nil { | |
134 c.params = map[string]interface{}{} | |
135 } | |
136 c.params[k] = v | |
137 } | |
138 | |
139 // Server is an HTTP handler | |
140 type Server struct { | |
141 // Logger is a function that is called to obtain a logger for the curren
t | |
142 // http.Request. Logger will be called with a nil http.Request when a lo
gger | |
143 // is needed outside of the scope of request handling. | |
144 // | |
145 // If Logger is nil, or if Logger returns nil, a logging.Null logger wil
l be | |
146 // used. | |
147 Logger func(*http.Request) logging.Logger | |
148 | |
149 // root is the root path of this Server. | |
150 root string | |
151 // backend is the endpoints Server instance to bind to. | |
152 backend http.Handler | |
153 // logger is the resolved no-context logger. | |
154 logger logging.Logger | |
155 // endpoints is a map of method to endpoint path component tree. | |
156 endpoints map[string]*endpointNode | |
157 // services is a map of registered services. | |
158 services map[string]*endpoints.APIDescriptor | |
159 // paths is a map of direct paths and their handler functions. | |
160 paths map[string]handlerFuncWithError | |
161 } | |
162 | |
163 // New instantiates a new Server instance. | |
164 // | |
165 // If root is the empty string, DefaultServerRoot will be used. | |
166 // If backend is nil, endpoints.DefaultServer will be used. | |
167 func New(root string, backend http.Handler) *Server { | |
168 if root == "" { | |
169 root = DefaultServerRoot | |
170 } | |
171 if backend == nil { | |
172 backend = endpoints.DefaultServer | |
173 } | |
174 | |
175 s := &Server{ | |
176 root: root, | |
177 backend: backend, | |
178 } | |
179 s.logger = s.getLogger(nil) | |
180 | |
181 s.registerPath(safeURLPathJoin("discovery", "v1", "apis"), s.serveGetJSO
N(s.handleDirectoryList)) | |
182 s.registerPath(safeURLPathJoin("explorer"), func(w http.ResponseWriter,
r *http.Request) error { | |
183 http.Redirect(w, r, fmt.Sprintf("http://apis-explorer.appspot.co
m/apis-explorer/?base=%s", | |
184 safeURLPathJoin(getHostURL(r).String(), s.root)), http.S
tatusFound) | |
185 return nil | |
186 }) | |
187 return s | |
188 } | |
189 | |
190 // RegisterService registers an endpoints service's descriptor with the Server. | |
191 func (s *Server) RegisterService(svc *endpoints.RPCService) error { | |
192 desc := endpoints.APIDescriptor{} | |
193 if err := svc.APIDescriptor(&desc, "epfrontend"); err != nil { | |
194 return err | |
195 } | |
196 if _, ok := s.services[desc.Name]; ok { | |
197 return fmt.Errorf("service [%s] already registered", desc.Name) | |
198 } | |
199 if s.services == nil { | |
200 s.services = map[string]*endpoints.APIDescriptor{} | |
201 } | |
202 s.services[desc.Name] = &desc | |
203 | |
204 // Traverse the method map. | |
205 apibase := []string{desc.Name, desc.Version} | |
206 for _, m := range desc.Methods { | |
207 // Build our API path: service/version / method/path/components | |
208 parts := strings.Split(m.Path, "/") | |
209 apipath := make([]string, 0, len(apibase)+len(parts)) | |
210 apipath = append(apipath, apibase...) | |
211 apipath = append(apipath, parts...) | |
212 | |
213 ep := endpoint{ | |
214 desc: &desc, | |
215 method: m, | |
216 backendPath: m.RosyMethod, | |
217 } | |
218 if err := s.registerEndpoint(m.HTTPMethod, apipath, &ep); err !=
nil { | |
219 return err | |
220 } | |
221 } | |
222 | |
223 s.registerPath(safeURLPathJoin("discovery", "v1", "apis", desc.Name, des
c.Version, "rest"), | |
224 s.serveGetJSON(func(r *http.Request) (interface{}, error) { | |
225 return s.handleRestDescription(r, &desc) | |
226 })) | |
227 return nil | |
228 } | |
229 | |
230 func (s *Server) registerEndpoint(method string, apipath []string, ep *endpoint)
error { | |
231 s.logger.Debugf("Registering %s %s => %#v", method, apipath, ep) | |
232 if s.endpoints == nil { | |
233 s.endpoints = map[string]*endpointNode{} | |
234 } | |
235 | |
236 methodNode := s.endpoints[method] | |
237 if methodNode == nil { | |
238 methodNode = &endpointNode{} | |
239 s.endpoints[method] = methodNode | |
240 } | |
241 | |
242 if n := methodNode.lookup(apipath...); n != nil { | |
243 return fmt.Errorf("%s method [%s] is already registered", method
, apipath) | |
244 } | |
245 return methodNode.addChild(apipath, ep) | |
246 } | |
247 | |
248 func (s *Server) registerPath(path string, handler handlerFuncWithError) { | |
249 if s.paths == nil { | |
250 s.paths = map[string]handlerFuncWithError{} | |
251 } | |
252 s.paths[path] = handler | |
253 } | |
254 | |
255 // HandleHTTP adds Server s to specified http.ServeMux. | |
256 // If no mux is provided, http.DefaultServeMux will be used. | |
257 func (s *Server) HandleHTTP(mux *http.ServeMux) { | |
258 if mux == nil { | |
259 mux = http.DefaultServeMux | |
260 } | |
261 mux.Handle(s.root, s) | |
262 } | |
263 | |
264 // ServeHTTP is the Server's implementation of the http.Handler interface. | |
265 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
266 h := requestHandler{ | |
267 Server: s, | |
268 logger: s.getLogger(r), | |
269 } | |
270 h.handle(w, r) | |
271 } | |
272 | |
273 // serveGetJSON returns an HTTP handler that serves the marshalled JSON form of | |
274 // the specified object. | |
275 func (s *Server) serveGetJSON(f func(*http.Request) (interface{}, error)) handle
rFuncWithError { | |
276 return func(w http.ResponseWriter, req *http.Request) error { | |
277 if req.Method != "GET" { | |
278 return errMethodNotAllowed | |
279 } | |
280 | |
281 i, err := f(req) | |
282 if err != nil { | |
283 return err | |
284 } | |
285 | |
286 data, err := json.MarshalIndent(i, "", " ") | |
287 if err != nil { | |
288 return err | |
289 } | |
290 | |
291 w.Header().Add("Content-Type", "application/json") | |
292 _, err = w.Write(data) | |
293 return err | |
294 } | |
295 } | |
296 | |
297 func (s *Server) getLogger(r *http.Request) (l logging.Logger) { | |
298 if s.Logger != nil { | |
299 l = s.Logger(r) | |
300 } | |
301 if l == nil { | |
302 l = logging.Null() | |
303 } | |
304 return | |
305 } | |
306 | |
307 type requestHandler struct { | |
308 *Server | |
309 | |
310 // logger shadows Server.logger and is bound to an http.Request. | |
311 logger logging.Logger | |
312 } | |
313 | |
314 func (h *requestHandler) handle(w http.ResponseWriter, r *http.Request) { | |
315 var err error | |
316 iw := &errorResponseWriter{ResponseWriter: w} | |
317 defer func() { | |
318 if err != nil { | |
319 iw.setError(err) | |
320 } | |
321 iw.forwardError() | |
322 }() | |
323 | |
324 paniccatcher.Do(func() { | |
325 err = h.handleImpl(iw, r) | |
326 }, func(p *paniccatcher.Panic) { | |
327 h.logger.Errorf("Panic during endpoint handling: %s\n%s", p.Reas
on, p.Stack) | |
328 err = endpoints.InternalServerError | |
329 }) | |
330 } | |
331 | |
332 func (h *requestHandler) handleImpl(w http.ResponseWriter, r *http.Request) erro
r { | |
333 if r.Body != nil { | |
334 defer r.Body.Close() | |
335 } | |
336 | |
337 // Strip our root from the request path, leaving the endpoint path. | |
338 if !strings.HasPrefix(r.URL.Path, h.root) { | |
339 h.logger.Debugf("Path (%s) does not begin with root (%s)", r.URL
.Path, h.root) | |
340 return endpoints.NotFoundError | |
341 } | |
342 path := strings.TrimPrefix(r.URL.Path, h.root) | |
343 h.logger.Debugf("Received HTTP %s request [%s]: [%s]", r.Method, r.URL.P
ath, path) | |
344 | |
345 // If we have an explicit path handler set up for this path, use it. | |
346 if ph := h.paths[path]; ph != nil { | |
347 return ph(w, r) | |
348 } | |
349 | |
350 // Resolve path to a service call. | |
351 call, err := h.resolveRequest(r.Method, path) | |
352 if err != nil { | |
353 h.logger.Warningf("Could not resolve %s request [%s] to a backen
d endpoint: %v", | |
354 r.Method, path, err) | |
355 return err | |
356 } | |
357 // Add query strings to call parameters. | |
358 for k, vs := range r.URL.Query() { | |
359 // If there are multiple values, add the last. | |
360 if len(vs) > 0 { | |
361 param := vs[len(vs)-1] | |
362 v, err := convertQueryParam(k, param, call.ep.method) | |
363 if err != nil { | |
364 h.logger.Errorf("Could not convert query param %
q (%q): %v", k, param, err) | |
365 return endpoints.BadRequestError | |
366 } | |
367 call.addParam(k, v) | |
368 } | |
369 } | |
370 h.logger.Debugf("Resolved %s request [%s] to endpoint [%s].", r.Method,
path, call.ep.backendPath) | |
371 | |
372 // Read the body for forwarding. | |
373 buf := bytes.Buffer{} | |
374 if _, err := buf.ReadFrom(r.Body); err != nil { | |
375 h.logger.Errorf("failed to read request body: %v", err) | |
376 return endpoints.InternalServerError | |
377 } | |
378 | |
379 // Replace the body with a JSON object, if necessary. | |
380 if buf.Len() == 0 || len(call.params) > 0 { | |
381 data := map[string]*jsonRWRawMessage{} | |
382 if buf.Len() > 0 { | |
383 if err := json.Unmarshal(buf.Bytes(), &data); err != nil
{ | |
384 h.logger.Errorf("Failed to unmarshal request JSO
N: %v", err) | |
385 return endpoints.BadRequestError | |
386 } | |
387 } | |
388 | |
389 for k, v := range call.params { | |
390 data[k] = &jsonRWRawMessage{ | |
391 v: v, | |
392 } | |
393 } | |
394 | |
395 buf.Reset() | |
396 m := json.NewEncoder(&buf) | |
397 if err := m.Encode(data); err != nil { | |
398 h.logger.Errorf("Failed to re-marshal request JSON: %v",
err) | |
399 return endpoints.InternalServerError | |
400 } | |
401 } | |
402 r.Body = ioutil.NopCloser(&buf) | |
403 | |
404 // Mutate our request to pretend it's a frontend-to-backend request, the
n | |
405 // forward it to the backend for actual API processing! | |
406 // | |
407 // The actual Path prefix doesn't matter, as the service only looks at t
he | |
408 // last component to derive the service call. | |
409 r.Method = "POST" | |
410 r.RequestURI = "/_ah/spi/" + call.ep.backendPath // Strips query paramet
ers. | |
411 r.URL.Path = r.RequestURI | |
412 h.backend.ServeHTTP(w, r) | |
413 return nil | |
414 } | |
415 | |
416 func (h *requestHandler) resolveRequest(method, path string) (*serviceCall, erro
r) { | |
417 call := serviceCall{ | |
418 path: path, | |
419 } | |
420 | |
421 parts := strings.Split(path, "/") | |
422 | |
423 h.logger.Debugf("Resolving %s request %s...", method, parts) | |
424 n := h.endpoints[method] | |
425 if n != nil { | |
426 n = n.lookup(parts...) | |
427 } | |
428 if n == nil { | |
429 return nil, endpoints.NotFoundError | |
430 } | |
431 ep := n.ep | |
432 call.ep = ep | |
433 | |
434 // Build our query parameters by reverse-traversing the tree towards its
root. | |
435 // | |
436 // The endpoints node path will have the same size as parts, since it ma
tched. | |
437 for len(parts) > 0 { | |
438 if n.paramName != "" { | |
439 param := parts[len(parts)-1] | |
440 v, err := convertQueryParam(n.paramName, param, ep.metho
d) | |
441 if err != nil { | |
442 h.logger.Errorf("Could not convert path param %q
(%q): %v", n.paramName, param, err) | |
443 return nil, endpoints.BadRequestError | |
444 } | |
445 call.addParam(n.paramName, v) | |
446 } | |
447 | |
448 parts = parts[:len(parts)-1] | |
449 n = n.parent | |
450 } | |
451 | |
452 return &call, nil | |
453 } | |
454 | |
455 func convertQueryParam(k, v string, m *endpoints.APIMethod) (interface{}, error)
{ | |
456 // Find the parameter for "k". | |
457 p := m.Request.Params[k] | |
458 if p == nil { | |
459 return v, nil | |
460 } | |
461 | |
462 switch p.Type { | |
463 case "int64", "uint64": | |
464 // Assume all int64 are "string"-quoted integers. If there are q
uotes, strip | |
465 // them. Return the result as a string. | |
466 if len(v) >= 2 && strings.HasPrefix(v, `"`) && strings.HasSuffix
(v, `"`) { | |
467 v = v[1 : len(v)-1] | |
468 } | |
469 if _, err := strconv.ParseInt(v, 10, 64); err != nil { | |
470 return nil, err | |
471 } | |
472 return v, nil | |
473 case "int32", "uint32": | |
474 return strconv.ParseInt(v, 10, 32) | |
475 case "float", "double": | |
476 return strconv.ParseFloat(v, 64) | |
477 case "boolean": | |
478 switch v { | |
479 case "", "false": | |
480 return false, nil | |
481 default: | |
482 return true, nil | |
483 } | |
484 case "string", "bytes": | |
485 return v, nil | |
486 | |
487 default: | |
488 return nil, fmt.Errorf("unknown type %q", p.Type) | |
489 } | |
490 } | |
OLD | NEW |