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 }