remote_api.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. // Copyright 2012 Google Inc. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. /*
  5. Package remote_api implements the /_ah/remote_api endpoint.
  6. This endpoint is used by offline tools such as the bulk loader.
  7. */
  8. package remote_api // import "google.golang.org/appengine/remote_api"
  9. import (
  10. "fmt"
  11. "io"
  12. "io/ioutil"
  13. "net/http"
  14. "strconv"
  15. "github.com/golang/protobuf/proto"
  16. "google.golang.org/appengine"
  17. "google.golang.org/appengine/internal"
  18. pb "google.golang.org/appengine/internal/remote_api"
  19. "google.golang.org/appengine/log"
  20. "google.golang.org/appengine/user"
  21. )
  22. func init() {
  23. http.HandleFunc("/_ah/remote_api", handle)
  24. }
  25. func handle(w http.ResponseWriter, req *http.Request) {
  26. c := appengine.NewContext(req)
  27. u := user.Current(c)
  28. if u == nil {
  29. u, _ = user.CurrentOAuth(c,
  30. "https://www.googleapis.com/auth/cloud-platform",
  31. "https://www.googleapis.com/auth/appengine.apis",
  32. )
  33. }
  34. if u == nil || !u.Admin {
  35. w.Header().Set("Content-Type", "text/plain; charset=utf-8")
  36. w.WriteHeader(http.StatusUnauthorized)
  37. io.WriteString(w, "You must be logged in as an administrator to access this.\n")
  38. return
  39. }
  40. if req.Header.Get("X-Appcfg-Api-Version") == "" {
  41. w.Header().Set("Content-Type", "text/plain; charset=utf-8")
  42. w.WriteHeader(http.StatusForbidden)
  43. io.WriteString(w, "This request did not contain a necessary header.\n")
  44. return
  45. }
  46. if req.Method != "POST" {
  47. // Response must be YAML.
  48. rtok := req.FormValue("rtok")
  49. if rtok == "" {
  50. rtok = "0"
  51. }
  52. w.Header().Set("Content-Type", "text/yaml; charset=utf-8")
  53. fmt.Fprintf(w, `{app_id: %q, rtok: %q}`, internal.FullyQualifiedAppID(c), rtok)
  54. return
  55. }
  56. defer req.Body.Close()
  57. body, err := ioutil.ReadAll(req.Body)
  58. if err != nil {
  59. w.WriteHeader(http.StatusBadRequest)
  60. log.Errorf(c, "Failed reading body: %v", err)
  61. return
  62. }
  63. remReq := &pb.Request{}
  64. if err := proto.Unmarshal(body, remReq); err != nil {
  65. w.WriteHeader(http.StatusBadRequest)
  66. log.Errorf(c, "Bad body: %v", err)
  67. return
  68. }
  69. service, method := *remReq.ServiceName, *remReq.Method
  70. if !requestSupported(service, method) {
  71. w.WriteHeader(http.StatusBadRequest)
  72. log.Errorf(c, "Unsupported RPC /%s.%s", service, method)
  73. return
  74. }
  75. rawReq := &rawMessage{remReq.Request}
  76. rawRes := &rawMessage{}
  77. err = internal.Call(c, service, method, rawReq, rawRes)
  78. remRes := &pb.Response{}
  79. if err == nil {
  80. remRes.Response = rawRes.buf
  81. } else if ae, ok := err.(*internal.APIError); ok {
  82. remRes.ApplicationError = &pb.ApplicationError{
  83. Code: &ae.Code,
  84. Detail: &ae.Detail,
  85. }
  86. } else {
  87. // This shouldn't normally happen.
  88. log.Errorf(c, "appengine/remote_api: Unexpected error of type %T: %v", err, err)
  89. remRes.ApplicationError = &pb.ApplicationError{
  90. Code: proto.Int32(0),
  91. Detail: proto.String(err.Error()),
  92. }
  93. }
  94. out, err := proto.Marshal(remRes)
  95. if err != nil {
  96. // This should not be possible.
  97. w.WriteHeader(500)
  98. log.Errorf(c, "proto.Marshal: %v", err)
  99. return
  100. }
  101. log.Infof(c, "Spooling %d bytes of response to /%s.%s", len(out), service, method)
  102. w.Header().Set("Content-Type", "application/octet-stream")
  103. w.Header().Set("Content-Length", strconv.Itoa(len(out)))
  104. w.Write(out)
  105. }
  106. // rawMessage is a protocol buffer type that is already serialised.
  107. // This allows the remote_api code here to handle messages
  108. // without having to know the real type.
  109. type rawMessage struct {
  110. buf []byte
  111. }
  112. func (rm *rawMessage) Marshal() ([]byte, error) {
  113. return rm.buf, nil
  114. }
  115. func (rm *rawMessage) Unmarshal(buf []byte) error {
  116. rm.buf = make([]byte, len(buf))
  117. copy(rm.buf, buf)
  118. return nil
  119. }
  120. func requestSupported(service, method string) bool {
  121. // This list of supported services is taken from SERVICE_PB_MAP in remote_api_services.py
  122. switch service {
  123. case "app_identity_service", "blobstore", "capability_service", "channel", "datastore_v3",
  124. "datastore_v4", "file", "images", "logservice", "mail", "matcher", "memcache", "remote_datastore",
  125. "remote_socket", "search", "modules", "system", "taskqueue", "urlfetch", "user", "xmpp":
  126. return true
  127. }
  128. return false
  129. }
  130. // Methods to satisfy proto.Message.
  131. func (rm *rawMessage) Reset() { rm.buf = nil }
  132. func (rm *rawMessage) String() string { return strconv.Quote(string(rm.buf)) }
  133. func (*rawMessage) ProtoMessage() {}