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()     {}