Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2898)

Unified Diff: appengine/ephelper/epfrontend/service.go

Issue 1750143003: Remove ephelper and other endpoints code. (Closed) Base URL: https://github.com/luci/luci-go@master
Patch Set: Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « appengine/ephelper/epfrontend/json.go ('k') | appengine/ephelper/epfrontend/service_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/ephelper/epfrontend/service.go
diff --git a/appengine/ephelper/epfrontend/service.go b/appengine/ephelper/epfrontend/service.go
deleted file mode 100644
index dc42dcd308bb3bd89deab261dde56354a6a15c65..0000000000000000000000000000000000000000
--- a/appengine/ephelper/epfrontend/service.go
+++ /dev/null
@@ -1,490 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package epfrontend
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "strconv"
- "strings"
-
- "github.com/GoogleCloudPlatform/go-endpoints/endpoints"
- "github.com/luci/luci-go/common/logging"
- "github.com/luci/luci-go/common/paniccatcher"
-)
-
-const (
- // DefaultServerRoot is the default Server root value.
- DefaultServerRoot = "/_ah/api/"
-)
-
-var (
- errMethodNotAllowed = endpoints.NewAPIError(http.StatusText(http.StatusMethodNotAllowed),
- "", http.StatusMethodNotAllowed)
-)
-
-type handlerFuncWithError func(http.ResponseWriter, *http.Request) error
-
-// endpoint is a resolved backend endpoint.
-type endpoint struct {
- // desc is the resolved service descriptor.
- desc *endpoints.APIDescriptor
- // method is the resolved method within the service.
- method *endpoints.APIMethod
- // backendPath is the backend path of this method.
- backendPath string
-}
-
-type endpointNode struct {
- // parent points to this endpointNode's parent node. If nil, this is the
- // root endpoint node.
- parent *endpointNode
-
- // component is the path component's value.
- //
- // If this is an empty string, this is a query parameter component.
- component string
- // paramName is the query parameter name for this node. It is valid if
- // component is "".
- paramName string
- // ep is the endpoint attached to this node. It can be nil if no endpoint is
- // attached.
- ep *endpoint
-
- children map[string]*endpointNode
-}
-
-func (n *endpointNode) addChild(components []string, ep *endpoint) error {
- comp := components[0]
- paramName := ""
-
- if strings.HasPrefix(comp, "{") && strings.HasSuffix(comp, "}") {
- // Replace query parameter with empty string.
- paramName = comp[1 : len(comp)-1]
- comp = ""
- }
-
- child := n.children[comp]
- if child == nil {
- if n.children == nil {
- n.children = map[string]*endpointNode{}
- }
-
- child = &endpointNode{
- parent: n,
- component: comp,
- paramName: paramName,
- }
- n.children[child.component] = child
- }
-
- if len(components) > 1 {
- return child.addChild(components[1:], ep)
- }
-
- child.ep = ep
- return nil
-}
-
-func (n *endpointNode) lookup(components ...string) *endpointNode {
- if len(components) == 0 {
- if n.ep == nil {
- // Not a leaf node!
- return nil
- }
- return n
- }
-
- // Is this node parent to a query parameter?
- if child := n.children[""]; child != nil {
- // Assume that this component is a query parameter. Can we resolve against
- // the remainder of the tree?
- if cn := child.lookup(components[1:]...); cn != nil {
- return cn
- }
- }
-
- // Use this node as a direct value.
- if child := n.children[components[0]]; child != nil {
- return child.lookup(components[1:]...)
- }
- return nil
-}
-
-// serviceCall is a stateful structure that will be populated with the service
-// call's parameters as they are resolved.
-type serviceCall struct {
- // path is the initial service call path.
- path string
-
- // ep is the resolved endpoint description for this call.
- ep *endpoint
- // params is the map of key/value query parameters populated during
- // resolution.
- params map[string]interface{}
-}
-
-func (c *serviceCall) addParam(k string, v interface{}) {
- if c.params == nil {
- c.params = map[string]interface{}{}
- }
- c.params[k] = v
-}
-
-// Server is an HTTP handler
-type Server struct {
- // Logger is a function that is called to obtain a logger for the current
- // http.Request. Logger will be called with a nil http.Request when a logger
- // is needed outside of the scope of request handling.
- //
- // If Logger is nil, or if Logger returns nil, a logging.Null logger will be
- // used.
- Logger func(*http.Request) logging.Logger
-
- // root is the root path of this Server.
- root string
- // backend is the endpoints Server instance to bind to.
- backend http.Handler
- // logger is the resolved no-context logger.
- logger logging.Logger
- // endpoints is a map of method to endpoint path component tree.
- endpoints map[string]*endpointNode
- // services is a map of registered services.
- services map[string]*endpoints.APIDescriptor
- // paths is a map of direct paths and their handler functions.
- paths map[string]handlerFuncWithError
-}
-
-// New instantiates a new Server instance.
-//
-// If root is the empty string, DefaultServerRoot will be used.
-// If backend is nil, endpoints.DefaultServer will be used.
-func New(root string, backend http.Handler) *Server {
- if root == "" {
- root = DefaultServerRoot
- }
- if backend == nil {
- backend = endpoints.DefaultServer
- }
-
- s := &Server{
- root: root,
- backend: backend,
- }
- s.logger = s.getLogger(nil)
-
- s.registerPath(safeURLPathJoin("discovery", "v1", "apis"), s.serveGetJSON(s.handleDirectoryList))
- s.registerPath(safeURLPathJoin("explorer"), func(w http.ResponseWriter, r *http.Request) error {
- http.Redirect(w, r, fmt.Sprintf("http://apis-explorer.appspot.com/apis-explorer/?base=%s",
- safeURLPathJoin(getHostURL(r).String(), s.root)), http.StatusFound)
- return nil
- })
- return s
-}
-
-// RegisterService registers an endpoints service's descriptor with the Server.
-func (s *Server) RegisterService(svc *endpoints.RPCService) error {
- desc := endpoints.APIDescriptor{}
- if err := svc.APIDescriptor(&desc, "epfrontend"); err != nil {
- return err
- }
- if _, ok := s.services[desc.Name]; ok {
- return fmt.Errorf("service [%s] already registered", desc.Name)
- }
- if s.services == nil {
- s.services = map[string]*endpoints.APIDescriptor{}
- }
- s.services[desc.Name] = &desc
-
- // Traverse the method map.
- apibase := []string{desc.Name, desc.Version}
- for _, m := range desc.Methods {
- // Build our API path: service/version / method/path/components
- parts := strings.Split(m.Path, "/")
- apipath := make([]string, 0, len(apibase)+len(parts))
- apipath = append(apipath, apibase...)
- apipath = append(apipath, parts...)
-
- ep := endpoint{
- desc: &desc,
- method: m,
- backendPath: m.RosyMethod,
- }
- if err := s.registerEndpoint(m.HTTPMethod, apipath, &ep); err != nil {
- return err
- }
- }
-
- s.registerPath(safeURLPathJoin("discovery", "v1", "apis", desc.Name, desc.Version, "rest"),
- s.serveGetJSON(func(r *http.Request) (interface{}, error) {
- return s.handleRestDescription(r, &desc)
- }))
- return nil
-}
-
-func (s *Server) registerEndpoint(method string, apipath []string, ep *endpoint) error {
- s.logger.Debugf("Registering %s %s => %#v", method, apipath, ep)
- if s.endpoints == nil {
- s.endpoints = map[string]*endpointNode{}
- }
-
- methodNode := s.endpoints[method]
- if methodNode == nil {
- methodNode = &endpointNode{}
- s.endpoints[method] = methodNode
- }
-
- if n := methodNode.lookup(apipath...); n != nil {
- return fmt.Errorf("%s method [%s] is already registered", method, apipath)
- }
- return methodNode.addChild(apipath, ep)
-}
-
-func (s *Server) registerPath(path string, handler handlerFuncWithError) {
- if s.paths == nil {
- s.paths = map[string]handlerFuncWithError{}
- }
- s.paths[path] = handler
-}
-
-// HandleHTTP adds Server s to specified http.ServeMux.
-// If no mux is provided, http.DefaultServeMux will be used.
-func (s *Server) HandleHTTP(mux *http.ServeMux) {
- if mux == nil {
- mux = http.DefaultServeMux
- }
- mux.Handle(s.root, s)
-}
-
-// ServeHTTP is the Server's implementation of the http.Handler interface.
-func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- h := requestHandler{
- Server: s,
- logger: s.getLogger(r),
- }
- h.handle(w, r)
-}
-
-// serveGetJSON returns an HTTP handler that serves the marshalled JSON form of
-// the specified object.
-func (s *Server) serveGetJSON(f func(*http.Request) (interface{}, error)) handlerFuncWithError {
- return func(w http.ResponseWriter, req *http.Request) error {
- if req.Method != "GET" {
- return errMethodNotAllowed
- }
-
- i, err := f(req)
- if err != nil {
- return err
- }
-
- data, err := json.MarshalIndent(i, "", " ")
- if err != nil {
- return err
- }
-
- w.Header().Add("Content-Type", "application/json")
- _, err = w.Write(data)
- return err
- }
-}
-
-func (s *Server) getLogger(r *http.Request) (l logging.Logger) {
- if s.Logger != nil {
- l = s.Logger(r)
- }
- if l == nil {
- l = logging.Null()
- }
- return
-}
-
-type requestHandler struct {
- *Server
-
- // logger shadows Server.logger and is bound to an http.Request.
- logger logging.Logger
-}
-
-func (h *requestHandler) handle(w http.ResponseWriter, r *http.Request) {
- var err error
- iw := &errorResponseWriter{ResponseWriter: w}
- defer func() {
- if err != nil {
- iw.setError(err)
- }
- iw.forwardError()
- }()
-
- paniccatcher.Do(func() {
- err = h.handleImpl(iw, r)
- }, func(p *paniccatcher.Panic) {
- h.logger.Errorf("Panic during endpoint handling: %s\n%s", p.Reason, p.Stack)
- err = endpoints.InternalServerError
- })
-}
-
-func (h *requestHandler) handleImpl(w http.ResponseWriter, r *http.Request) error {
- if r.Body != nil {
- defer r.Body.Close()
- }
-
- // Strip our root from the request path, leaving the endpoint path.
- if !strings.HasPrefix(r.URL.Path, h.root) {
- h.logger.Debugf("Path (%s) does not begin with root (%s)", r.URL.Path, h.root)
- return endpoints.NotFoundError
- }
- path := strings.TrimPrefix(r.URL.Path, h.root)
- h.logger.Debugf("Received HTTP %s request [%s]: [%s]", r.Method, r.URL.Path, path)
-
- // If we have an explicit path handler set up for this path, use it.
- if ph := h.paths[path]; ph != nil {
- return ph(w, r)
- }
-
- // Resolve path to a service call.
- call, err := h.resolveRequest(r.Method, path)
- if err != nil {
- h.logger.Warningf("Could not resolve %s request [%s] to a backend endpoint: %v",
- r.Method, path, err)
- return err
- }
- // Add query strings to call parameters.
- for k, vs := range r.URL.Query() {
- // If there are multiple values, add the last.
- if len(vs) > 0 {
- param := vs[len(vs)-1]
- v, err := convertQueryParam(k, param, call.ep.method)
- if err != nil {
- h.logger.Errorf("Could not convert query param %q (%q): %v", k, param, err)
- return endpoints.BadRequestError
- }
- call.addParam(k, v)
- }
- }
- h.logger.Debugf("Resolved %s request [%s] to endpoint [%s].", r.Method, path, call.ep.backendPath)
-
- // Read the body for forwarding.
- buf := bytes.Buffer{}
- if _, err := buf.ReadFrom(r.Body); err != nil {
- h.logger.Errorf("failed to read request body: %v", err)
- return endpoints.InternalServerError
- }
-
- // Replace the body with a JSON object, if necessary.
- if buf.Len() == 0 || len(call.params) > 0 {
- data := map[string]*jsonRWRawMessage{}
- if buf.Len() > 0 {
- if err := json.Unmarshal(buf.Bytes(), &data); err != nil {
- h.logger.Errorf("Failed to unmarshal request JSON: %v", err)
- return endpoints.BadRequestError
- }
- }
-
- for k, v := range call.params {
- data[k] = &jsonRWRawMessage{
- v: v,
- }
- }
-
- buf.Reset()
- m := json.NewEncoder(&buf)
- if err := m.Encode(data); err != nil {
- h.logger.Errorf("Failed to re-marshal request JSON: %v", err)
- return endpoints.InternalServerError
- }
- }
- r.Body = ioutil.NopCloser(&buf)
-
- // Mutate our request to pretend it's a frontend-to-backend request, then
- // forward it to the backend for actual API processing!
- //
- // The actual Path prefix doesn't matter, as the service only looks at the
- // last component to derive the service call.
- r.Method = "POST"
- r.RequestURI = "/_ah/spi/" + call.ep.backendPath // Strips query parameters.
- r.URL.Path = r.RequestURI
- h.backend.ServeHTTP(w, r)
- return nil
-}
-
-func (h *requestHandler) resolveRequest(method, path string) (*serviceCall, error) {
- call := serviceCall{
- path: path,
- }
-
- parts := strings.Split(path, "/")
-
- h.logger.Debugf("Resolving %s request %s...", method, parts)
- n := h.endpoints[method]
- if n != nil {
- n = n.lookup(parts...)
- }
- if n == nil {
- return nil, endpoints.NotFoundError
- }
- ep := n.ep
- call.ep = ep
-
- // Build our query parameters by reverse-traversing the tree towards its root.
- //
- // The endpoints node path will have the same size as parts, since it matched.
- for len(parts) > 0 {
- if n.paramName != "" {
- param := parts[len(parts)-1]
- v, err := convertQueryParam(n.paramName, param, ep.method)
- if err != nil {
- h.logger.Errorf("Could not convert path param %q (%q): %v", n.paramName, param, err)
- return nil, endpoints.BadRequestError
- }
- call.addParam(n.paramName, v)
- }
-
- parts = parts[:len(parts)-1]
- n = n.parent
- }
-
- return &call, nil
-}
-
-func convertQueryParam(k, v string, m *endpoints.APIMethod) (interface{}, error) {
- // Find the parameter for "k".
- p := m.Request.Params[k]
- if p == nil {
- return v, nil
- }
-
- switch p.Type {
- case "int64", "uint64":
- // Assume all int64 are "string"-quoted integers. If there are quotes, strip
- // them. Return the result as a string.
- if len(v) >= 2 && strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) {
- v = v[1 : len(v)-1]
- }
- if _, err := strconv.ParseInt(v, 10, 64); err != nil {
- return nil, err
- }
- return v, nil
- case "int32", "uint32":
- return strconv.ParseInt(v, 10, 32)
- case "float", "double":
- return strconv.ParseFloat(v, 64)
- case "boolean":
- switch v {
- case "", "false":
- return false, nil
- default:
- return true, nil
- }
- case "string", "bytes":
- return v, nil
-
- default:
- return nil, fmt.Errorf("unknown type %q", p.Type)
- }
-}
« no previous file with comments | « appengine/ephelper/epfrontend/json.go ('k') | appengine/ephelper/epfrontend/service_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698