| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package prod | 5 package prod |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "fmt" |
| 8 "net/http" | 9 "net/http" |
| 10 "net/http/cookiejar" |
| 11 "net/url" |
| 12 "strings" |
| 9 | 13 |
| 10 "github.com/luci/gae/service/info" | 14 "github.com/luci/gae/service/info" |
| 15 "github.com/luci/gae/service/urlfetch" |
| 11 "golang.org/x/net/context" | 16 "golang.org/x/net/context" |
| 12 gOAuth "golang.org/x/oauth2/google" | 17 gOAuth "golang.org/x/oauth2/google" |
| 13 "google.golang.org/appengine" | 18 "google.golang.org/appengine" |
| 14 "google.golang.org/appengine/remote_api" | 19 "google.golang.org/appengine/remote_api" |
| 15 ) | 20 ) |
| 16 | 21 |
| 17 type key int | 22 type key int |
| 18 | 23 |
| 19 var ( | 24 var ( |
| 20 prodContextKey key | 25 prodContextKey key |
| 21 prodContextNoTxnKey key = 1 | 26 prodContextNoTxnKey key = 1 |
| 22 probeCacheKey key = 2 | 27 probeCacheKey key = 2 |
| 23 ) | 28 ) |
| 24 | 29 |
| 25 // AEContext retrieves the raw "google.golang.org/appengine" compatible Context. | 30 // AEContext retrieves the raw "google.golang.org/appengine" compatible Context. |
| 26 // | 31 // |
| 27 // It also transfers deadline of `c` to AE context, since deadline is used for | 32 // It also transfers deadline of `c` to AE context, since deadline is used for |
| 28 // RPCs. Doesn't transfer cancelation ability though (since it's ignored by GAE | 33 // RPCs. Doesn't transfer cancelation ability though (since it's ignored by GAE |
| 29 // anyway). | 34 // anyway). |
| 30 func AEContext(c context.Context) context.Context { | 35 func AEContext(c context.Context) context.Context { |
| 31 aeCtx, _ := c.Value(prodContextKey).(context.Context) | 36 aeCtx, _ := c.Value(prodContextKey).(context.Context) |
| 37 if aeCtx == nil { |
| 38 return nil |
| 39 } |
| 32 if deadline, ok := c.Deadline(); ok { | 40 if deadline, ok := c.Deadline(); ok { |
| 33 aeCtx, _ = context.WithDeadline(aeCtx, deadline) | 41 aeCtx, _ = context.WithDeadline(aeCtx, deadline) |
| 34 } | 42 } |
| 35 return aeCtx | 43 return aeCtx |
| 36 } | 44 } |
| 37 | 45 |
| 38 // AEContextNoTxn retrieves the raw "google.golang.org/appengine" compatible | 46 // AEContextNoTxn retrieves the raw "google.golang.org/appengine" compatible |
| 39 // Context that's not part of a transaction. | 47 // Context that's not part of a transaction. |
| 40 func AEContextNoTxn(c context.Context) context.Context { | 48 func AEContextNoTxn(c context.Context) context.Context { |
| 41 aeCtx, _ := c.Value(prodContextNoTxnKey).(context.Context) | 49 aeCtx, _ := c.Value(prodContextNoTxnKey).(context.Context) |
| 50 if aeCtx == nil { |
| 51 return nil |
| 52 } |
| 42 aeCtx, err := appengine.Namespace(aeCtx, info.Get(c).GetNamespace()) | 53 aeCtx, err := appengine.Namespace(aeCtx, info.Get(c).GetNamespace()) |
| 43 if err != nil { | 54 if err != nil { |
| 44 panic(err) | 55 panic(err) |
| 45 } | 56 } |
| 46 if deadline, ok := c.Deadline(); ok { | 57 if deadline, ok := c.Deadline(); ok { |
| 47 aeCtx, _ = context.WithDeadline(aeCtx, deadline) | 58 aeCtx, _ = context.WithDeadline(aeCtx, deadline) |
| 48 } | 59 } |
| 49 return aeCtx | 60 return aeCtx |
| 50 } | 61 } |
| 51 | 62 |
| (...skipping 22 matching lines...) Expand all Loading... |
| 74 func Use(c context.Context, r *http.Request) context.Context { | 85 func Use(c context.Context, r *http.Request) context.Context { |
| 75 return setupAECtx(c, appengine.NewContext(r)) | 86 return setupAECtx(c, appengine.NewContext(r)) |
| 76 } | 87 } |
| 77 | 88 |
| 78 // UseRemote is the same as Use, except that it lets you attach a context to | 89 // UseRemote is the same as Use, except that it lets you attach a context to |
| 79 // a remote host using the Remote API feature. See the docs for the | 90 // a remote host using the Remote API feature. See the docs for the |
| 80 // prerequisites. | 91 // prerequisites. |
| 81 // | 92 // |
| 82 // docs: https://cloud.google.com/appengine/docs/go/tools/remoteapi | 93 // docs: https://cloud.google.com/appengine/docs/go/tools/remoteapi |
| 83 // | 94 // |
| 84 // If client is nil, this will use a default Google OAuth2 client. Otherwise the | 95 // inOutCtx will be replaced with the new, derived context, if err is nil, |
| 85 // client must be configured to have the following OAuth2 scopes: | 96 // otherwise it's unchanged and continues to be safe-to-use. |
| 86 //» » "https://www.googleapis.com/auth/appengine.apis" | 97 // |
| 87 //» » "https://www.googleapis.com/auth/userinfo.email" | 98 // If client is nil, this will use create a new client, and will try to be |
| 88 //» » "https://www.googleapis.com/auth/cloud.platform" | 99 // clever about it: |
| 89 func UseRemote(c context.Context, host string, client *http.Client) (context.Con
text, error) { | 100 // * If you're creating a remote context FROM AppEngine, this will use |
| 90 » err := error(nil) | 101 // urlfetch.Transport. This can be used to allow app-to-app remote_api |
| 102 // control. |
| 103 // |
| 104 // * If host starts with "localhost", this will create a regular http.Client |
| 105 // with a cookiejar, and call the _ah/login API to log in as an admin with |
| 106 // the user "admin@example.com". |
| 107 // |
| 108 // * Otherwise, it will create a Google OAuth2 client with the following scope
s: |
| 109 // - "https://www.googleapis.com/auth/appengine.apis" |
| 110 // - "https://www.googleapis.com/auth/userinfo.email" |
| 111 // - "https://www.googleapis.com/auth/cloud.platform" |
| 112 func UseRemote(inOutCtx *context.Context, host string, client *http.Client) (err
error) { |
| 91 if client == nil { | 113 if client == nil { |
| 92 » » client, err = gOAuth.DefaultClient(context.Background(), | 114 » » if strings.HasPrefix(host, "localhost") { |
| 93 » » » "https://www.googleapis.com/auth/appengine.apis", | 115 » » » transp := http.DefaultTransport |
| 94 » » » "https://www.googleapis.com/auth/userinfo.email", | 116 » » » if aeCtx := AEContextNoTxn(*inOutCtx); aeCtx != nil { |
| 95 » » » "https://www.googleapis.com/auth/cloud.platform", | 117 » » » » transp = urlfetch.Get(aeCtx) |
| 96 » » ) | 118 » » » } |
| 97 » » if err != nil { | 119 |
| 98 » » » return nil, err | 120 » » » client = &http.Client{Transport: transp} |
| 121 » » » client.Jar, err = cookiejar.New(nil) |
| 122 » » » if err != nil { |
| 123 » » » » return |
| 124 » » » } |
| 125 » » » u := fmt.Sprintf("http://%s/_ah/login?%s", host, url.Val
ues{ |
| 126 » » » » "email": {"admin@example.com"}, |
| 127 » » » » "admin": {"True"}, |
| 128 » » » » "action": {"Login"}, |
| 129 » » » }.Encode()) |
| 130 |
| 131 » » » var rsp *http.Response |
| 132 » » » rsp, err = client.Get(u) |
| 133 » » » if err != nil { |
| 134 » » » » return |
| 135 » » » } |
| 136 » » » defer rsp.Body.Close() |
| 137 » » } else { |
| 138 » » » aeCtx := AEContextNoTxn(*inOutCtx) |
| 139 » » » if aeCtx == nil { |
| 140 » » » » aeCtx = context.Background() |
| 141 » » » } |
| 142 » » » client, err = gOAuth.DefaultClient(aeCtx, |
| 143 » » » » "https://www.googleapis.com/auth/appengine.apis"
, |
| 144 » » » » "https://www.googleapis.com/auth/userinfo.email"
, |
| 145 » » » » "https://www.googleapis.com/auth/cloud.platform"
, |
| 146 » » » ) |
| 147 » » » if err != nil { |
| 148 » » » » return |
| 149 » » » } |
| 99 } | 150 } |
| 100 } | 151 } |
| 101 | 152 |
| 102 aeCtx, err := remote_api.NewRemoteContext(host, client) | 153 aeCtx, err := remote_api.NewRemoteContext(host, client) |
| 103 if err != nil { | 154 if err != nil { |
| 104 » » return nil, err | 155 » » return |
| 105 } | 156 } |
| 106 » return setupAECtx(c, aeCtx), nil | 157 » *inOutCtx = setupAECtx(*inOutCtx, aeCtx) |
| 158 » return nil |
| 107 } | 159 } |
| OLD | NEW |