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 |