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