github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/google.golang.org/appengine/remote_api/client.go (about)

     1  // Copyright 2013 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
     6  
     7  // This file provides the client for connecting remotely to a user's production
     8  // application.
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"log"
    15  	"math/rand"
    16  	"net/http"
    17  	"net/url"
    18  	"regexp"
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/golang/protobuf/proto"
    24  	"golang.org/x/net/context"
    25  
    26  	"google.golang.org/appengine/internal"
    27  	pb "google.golang.org/appengine/internal/remote_api"
    28  )
    29  
    30  // NewRemoteContext returns a context that gives access to the production
    31  // APIs for the application at the given host. All communication will be
    32  // performed over SSL unless the host is localhost.
    33  func NewRemoteContext(host string, client *http.Client) (context.Context, error) {
    34  	// Add an appcfg header to outgoing requests.
    35  	t := client.Transport
    36  	if t == nil {
    37  		t = http.DefaultTransport
    38  	}
    39  	client.Transport = &headerAddingRoundTripper{t}
    40  
    41  	url := url.URL{
    42  		Scheme: "https",
    43  		Host:   host,
    44  		Path:   "/_ah/remote_api",
    45  	}
    46  	if host == "localhost" || strings.HasPrefix(host, "localhost:") {
    47  		url.Scheme = "http"
    48  	}
    49  	u := url.String()
    50  	appID, err := getAppID(client, u)
    51  	if err != nil {
    52  		return nil, fmt.Errorf("unable to contact server: %v", err)
    53  	}
    54  	rc := &remoteContext{
    55  		client: client,
    56  		url:    u,
    57  	}
    58  	ctx := internal.WithCallOverride(context.Background(), rc.call)
    59  	ctx = internal.WithLogOverride(ctx, rc.logf)
    60  	ctx = internal.WithAppIDOverride(ctx, appID)
    61  	return ctx, nil
    62  }
    63  
    64  type remoteContext struct {
    65  	client *http.Client
    66  	url    string
    67  }
    68  
    69  var logLevels = map[int64]string{
    70  	0: "DEBUG",
    71  	1: "INFO",
    72  	2: "WARNING",
    73  	3: "ERROR",
    74  	4: "CRITICAL",
    75  }
    76  
    77  func (c *remoteContext) logf(level int64, format string, args ...interface{}) {
    78  	log.Printf(logLevels[level]+": "+format, args...)
    79  }
    80  
    81  func (c *remoteContext) call(ctx context.Context, service, method string, in, out proto.Message) error {
    82  	req, err := proto.Marshal(in)
    83  	if err != nil {
    84  		return fmt.Errorf("error marshalling request: %v", err)
    85  	}
    86  
    87  	remReq := &pb.Request{
    88  		ServiceName: proto.String(service),
    89  		Method:      proto.String(method),
    90  		Request:     req,
    91  		// NOTE(djd): RequestId is unused in the server.
    92  	}
    93  
    94  	req, err = proto.Marshal(remReq)
    95  	if err != nil {
    96  		return fmt.Errorf("proto.Marshal: %v", err)
    97  	}
    98  
    99  	// TODO(djd): Respect ctx.Deadline()?
   100  	resp, err := c.client.Post(c.url, "application/octet-stream", bytes.NewReader(req))
   101  	if err != nil {
   102  		return fmt.Errorf("error sending request: %v", err)
   103  	}
   104  	defer resp.Body.Close()
   105  
   106  	body, err := ioutil.ReadAll(resp.Body)
   107  	if resp.StatusCode != http.StatusOK {
   108  		return fmt.Errorf("bad response %d; body: %q", resp.StatusCode, body)
   109  	}
   110  	if err != nil {
   111  		return fmt.Errorf("failed reading response: %v", err)
   112  	}
   113  	remResp := &pb.Response{}
   114  	if err := proto.Unmarshal(body, remResp); err != nil {
   115  		return fmt.Errorf("error unmarshalling response: %v", err)
   116  	}
   117  
   118  	if ae := remResp.GetApplicationError(); ae != nil {
   119  		return &internal.APIError{
   120  			Code:    ae.GetCode(),
   121  			Detail:  ae.GetDetail(),
   122  			Service: service,
   123  		}
   124  	}
   125  
   126  	if remResp.Response == nil {
   127  		return fmt.Errorf("unexpected response: %s", proto.MarshalTextString(remResp))
   128  	}
   129  
   130  	return proto.Unmarshal(remResp.Response, out)
   131  }
   132  
   133  // This is a forgiving regexp designed to parse the app ID from YAML.
   134  var appIDRE = regexp.MustCompile(`app_id["']?\s*:\s*['"]?([-a-z0-9.:~]+)`)
   135  
   136  func getAppID(client *http.Client, url string) (string, error) {
   137  	// Generate a pseudo-random token for handshaking.
   138  	token := strconv.Itoa(rand.New(rand.NewSource(time.Now().UnixNano())).Int())
   139  
   140  	resp, err := client.Get(fmt.Sprintf("%s?rtok=%s", url, token))
   141  	if err != nil {
   142  		return "", err
   143  	}
   144  	defer resp.Body.Close()
   145  
   146  	body, err := ioutil.ReadAll(resp.Body)
   147  	if resp.StatusCode != http.StatusOK {
   148  		return "", fmt.Errorf("bad response %d; body: %q", resp.StatusCode, body)
   149  	}
   150  	if err != nil {
   151  		return "", fmt.Errorf("failed reading response: %v", err)
   152  	}
   153  
   154  	// Check the token is present in response.
   155  	if !bytes.Contains(body, []byte(token)) {
   156  		return "", fmt.Errorf("token not found: want %q; body %q", token, body)
   157  	}
   158  
   159  	match := appIDRE.FindSubmatch(body)
   160  	if match == nil {
   161  		return "", fmt.Errorf("app ID not found: body %q", body)
   162  	}
   163  
   164  	return string(match[1]), nil
   165  }
   166  
   167  type headerAddingRoundTripper struct {
   168  	Wrapped http.RoundTripper
   169  }
   170  
   171  func (t *headerAddingRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
   172  	r.Header.Set("X-Appcfg-Api-Version", "1")
   173  	return t.Wrapped.RoundTrip(r)
   174  }