Skip to content

Commit 37ff2b8

Browse files
authored
allow API calls without GAE context (#284)
The ApiHost used to require a security ticket for all API calls, so the client side code used to be able to assume that any request without one was invalid and reject it. The backend now is able to handle requests without security tickets in many cases, so the client side check is now just getting in the way. This change removes that check and lets the backend attempt to handle all requests. The way the client implementation happened to require a security ticket was actually by requiring a GAE context. This change removes that constraint as well and removes the now-unecessary BackgroundContext.
1 parent 1f27190 commit 37ff2b8

File tree

10 files changed

+192
-279
lines changed

10 files changed

+192
-279
lines changed

aetest/instance_vm.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"regexp"
1919
"time"
2020

21-
"golang.org/x/net/context"
2221
"google.golang.org/appengine/internal"
2322
)
2423

@@ -61,7 +60,6 @@ type instance struct {
6160
appDir string
6261
appID string
6362
startupTimeout time.Duration
64-
relFuncs []func() // funcs to release any associated contexts
6563
}
6664

6765
// NewRequest returns an *http.Request associated with this instance.
@@ -72,21 +70,11 @@ func (i *instance) NewRequest(method, urlStr string, body io.Reader) (*http.Requ
7270
}
7371

7472
// Associate this request.
75-
req, release := internal.RegisterTestRequest(req, i.apiURL, func(ctx context.Context) context.Context {
76-
ctx = internal.WithAppIDOverride(ctx, "dev~"+i.appID)
77-
return ctx
78-
})
79-
i.relFuncs = append(i.relFuncs, release)
80-
81-
return req, nil
73+
return internal.RegisterTestRequest(req, i.apiURL, "dev~"+i.appID), nil
8274
}
8375

8476
// Close kills the child api_server.py process, releasing its resources.
8577
func (i *instance) Close() (err error) {
86-
for _, rel := range i.relFuncs {
87-
rel()
88-
}
89-
i.relFuncs = nil
9078
child := i.child
9179
if child == nil {
9280
return nil

appengine_vm.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
package appengine
99

1010
import (
11-
"golang.org/x/net/context"
12-
13-
"google.golang.org/appengine/internal"
11+
"context"
1412
)
1513

1614
// BackgroundContext returns a context not associated with a request.
15+
//
16+
// Deprecated: App Engine no longer has a special background context.
17+
// Just use context.Background().
1718
func BackgroundContext() context.Context {
18-
return internal.BackgroundContext()
19+
return context.Background()
1920
}

internal/api.go

Lines changed: 39 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ import (
2424
"sync/atomic"
2525
"time"
2626

27+
netcontext "context"
28+
2729
"github.com/golang/protobuf/proto"
28-
netcontext "golang.org/x/net/context"
2930

3031
basepb "google.golang.org/appengine/internal/base"
3132
logpb "google.golang.org/appengine/internal/log"
3233
remotepb "google.golang.org/appengine/internal/remote_api"
3334
)
3435

3536
const (
36-
apiPath = "/rpc_http"
37-
defaultTicketSuffix = "/default.20150612t184001.0"
37+
apiPath = "/rpc_http"
3838
)
3939

4040
var (
@@ -66,21 +66,22 @@ var (
6666
IdleConnTimeout: 90 * time.Second,
6767
},
6868
}
69-
70-
defaultTicketOnce sync.Once
71-
defaultTicket string
72-
backgroundContextOnce sync.Once
73-
backgroundContext netcontext.Context
7469
)
7570

76-
func apiURL() *url.URL {
71+
func apiURL(ctx netcontext.Context) *url.URL {
7772
host, port := "appengine.googleapis.internal", "10001"
7873
if h := os.Getenv("API_HOST"); h != "" {
7974
host = h
8075
}
76+
if hostOverride := ctx.Value(apiHostOverrideKey); hostOverride != nil {
77+
host = hostOverride.(string)
78+
}
8179
if p := os.Getenv("API_PORT"); p != "" {
8280
port = p
8381
}
82+
if portOverride := ctx.Value(apiPortOverrideKey); portOverride != nil {
83+
port = portOverride.(string)
84+
}
8485
return &url.URL{
8586
Scheme: "http",
8687
Host: host + ":" + port,
@@ -98,7 +99,6 @@ func handleHTTPMiddleware(next http.Handler) http.Handler {
9899
c := &context{
99100
req: r,
100101
outHeader: w.Header(),
101-
apiURL: apiURL(),
102102
}
103103
r = r.WithContext(withContext(r.Context(), c))
104104
c.req = r
@@ -235,8 +235,6 @@ type context struct {
235235
lines []*logpb.UserAppLogLine
236236
flushes int
237237
}
238-
239-
apiURL *url.URL
240238
}
241239

242240
var contextKey = "holds a *context"
@@ -304,59 +302,19 @@ func WithContext(parent netcontext.Context, req *http.Request) netcontext.Contex
304302
}
305303
}
306304

307-
// DefaultTicket returns a ticket used for background context or dev_appserver.
308-
func DefaultTicket() string {
309-
defaultTicketOnce.Do(func() {
310-
if IsDevAppServer() {
311-
defaultTicket = "testapp" + defaultTicketSuffix
312-
return
313-
}
314-
appID := partitionlessAppID()
315-
escAppID := strings.Replace(strings.Replace(appID, ":", "_", -1), ".", "_", -1)
316-
majVersion := VersionID(nil)
317-
if i := strings.Index(majVersion, "."); i > 0 {
318-
majVersion = majVersion[:i]
319-
}
320-
defaultTicket = fmt.Sprintf("%s/%s.%s.%s", escAppID, ModuleName(nil), majVersion, InstanceID())
321-
})
322-
return defaultTicket
323-
}
324-
325-
func BackgroundContext() netcontext.Context {
326-
backgroundContextOnce.Do(func() {
327-
// Compute background security ticket.
328-
ticket := DefaultTicket()
329-
330-
c := &context{
331-
req: &http.Request{
332-
Header: http.Header{
333-
ticketHeader: []string{ticket},
334-
},
335-
},
336-
apiURL: apiURL(),
337-
}
338-
backgroundContext = toContext(c)
339-
340-
// TODO(dsymonds): Wire up the shutdown handler to do a final flush.
341-
go c.logFlusher(make(chan int))
342-
})
343-
344-
return backgroundContext
345-
}
346-
347305
// RegisterTestRequest registers the HTTP request req for testing, such that
348-
// any API calls are sent to the provided URL. It returns a closure to delete
349-
// the registration.
306+
// any API calls are sent to the provided URL.
350307
// It should only be used by aetest package.
351-
func RegisterTestRequest(req *http.Request, apiURL *url.URL, decorate func(netcontext.Context) netcontext.Context) (*http.Request, func()) {
352-
c := &context{
353-
req: req,
354-
apiURL: apiURL,
355-
}
356-
ctx := withContext(decorate(req.Context()), c)
357-
req = req.WithContext(ctx)
358-
c.req = req
359-
return req, func() {}
308+
func RegisterTestRequest(req *http.Request, apiURL *url.URL, appID string) *http.Request {
309+
ctx := req.Context()
310+
ctx = withAPIHostOverride(ctx, apiURL.Hostname())
311+
ctx = withAPIPortOverride(ctx, apiURL.Port())
312+
ctx = WithAppIDOverride(ctx, appID)
313+
314+
// use the unregistered request as a placeholder so that withContext can read the headers
315+
c := &context{req: req}
316+
c.req = req.WithContext(withContext(ctx, c))
317+
return c.req
360318
}
361319

362320
var errTimeout = &CallError{
@@ -401,10 +359,11 @@ func (c *context) WriteHeader(code int) {
401359
c.outCode = code
402360
}
403361

404-
func (c *context) post(body []byte, timeout time.Duration) (b []byte, err error) {
362+
func post(ctx netcontext.Context, body []byte, timeout time.Duration) (b []byte, err error) {
363+
apiURL := apiURL(ctx)
405364
hreq := &http.Request{
406365
Method: "POST",
407-
URL: c.apiURL,
366+
URL: apiURL,
408367
Header: http.Header{
409368
apiEndpointHeader: apiEndpointHeaderValue,
410369
apiMethodHeader: apiMethodHeaderValue,
@@ -413,13 +372,16 @@ func (c *context) post(body []byte, timeout time.Duration) (b []byte, err error)
413372
},
414373
Body: ioutil.NopCloser(bytes.NewReader(body)),
415374
ContentLength: int64(len(body)),
416-
Host: c.apiURL.Host,
417-
}
418-
if info := c.req.Header.Get(dapperHeader); info != "" {
419-
hreq.Header.Set(dapperHeader, info)
375+
Host: apiURL.Host,
420376
}
421-
if info := c.req.Header.Get(traceHeader); info != "" {
422-
hreq.Header.Set(traceHeader, info)
377+
c := fromContext(ctx)
378+
if c != nil {
379+
if info := c.req.Header.Get(dapperHeader); info != "" {
380+
hreq.Header.Set(dapperHeader, info)
381+
}
382+
if info := c.req.Header.Get(traceHeader); info != "" {
383+
hreq.Header.Set(traceHeader, info)
384+
}
423385
}
424386

425387
tr := apiHTTPClient.Transport.(*http.Transport)
@@ -480,10 +442,6 @@ func Call(ctx netcontext.Context, service, method string, in, out proto.Message)
480442
}
481443

482444
c := fromContext(ctx)
483-
if c == nil {
484-
// Give a good error message rather than a panic lower down.
485-
return errNotAppEngineContext
486-
}
487445

488446
// Apply transaction modifications if we're in a transaction.
489447
if t := transactionFromContext(ctx); t != nil {
@@ -504,20 +462,13 @@ func Call(ctx netcontext.Context, service, method string, in, out proto.Message)
504462
return err
505463
}
506464

507-
ticket := c.req.Header.Get(ticketHeader)
508-
// Use a test ticket under test environment.
509-
if ticket == "" {
510-
if appid := ctx.Value(&appIDOverrideKey); appid != nil {
511-
ticket = appid.(string) + defaultTicketSuffix
465+
ticket := ""
466+
if c != nil {
467+
ticket = c.req.Header.Get(ticketHeader)
468+
if dri := c.req.Header.Get(devRequestIdHeader); IsDevAppServer() && dri != "" {
469+
ticket = dri
512470
}
513471
}
514-
// Fall back to use background ticket when the request ticket is not available in Flex or dev_appserver.
515-
if ticket == "" {
516-
ticket = DefaultTicket()
517-
}
518-
if dri := c.req.Header.Get(devRequestIdHeader); IsDevAppServer() && dri != "" {
519-
ticket = dri
520-
}
521472
req := &remotepb.Request{
522473
ServiceName: &service,
523474
Method: &method,
@@ -529,7 +480,7 @@ func Call(ctx netcontext.Context, service, method string, in, out proto.Message)
529480
return err
530481
}
531482

532-
hrespBody, err := c.post(hreqBody, timeout)
483+
hrespBody, err := post(ctx, hreqBody, timeout)
533484
if err != nil {
534485
return err
535486
}

internal/api_common.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@
55
package internal
66

77
import (
8+
netcontext "context"
89
"errors"
910
"os"
1011

1112
"github.com/golang/protobuf/proto"
12-
netcontext "golang.org/x/net/context"
1313
)
1414

15+
type ctxKey string
16+
17+
func (c ctxKey) String() string {
18+
return "appengine context key: " + string(c)
19+
}
20+
1521
var errNotAppEngineContext = errors.New("not an App Engine context")
1622

1723
type CallOverrideFunc func(ctx netcontext.Context, service, method string, in, out proto.Message) error
@@ -55,6 +61,18 @@ func WithAppIDOverride(ctx netcontext.Context, appID string) netcontext.Context
5561
return netcontext.WithValue(ctx, &appIDOverrideKey, appID)
5662
}
5763

64+
var apiHostOverrideKey = ctxKey("holds a string, being the alternate API_HOST")
65+
66+
func withAPIHostOverride(ctx netcontext.Context, apiHost string) netcontext.Context {
67+
return netcontext.WithValue(ctx, apiHostOverrideKey, apiHost)
68+
}
69+
70+
var apiPortOverrideKey = ctxKey("holds a string, being the alternate API_PORT")
71+
72+
func withAPIPortOverride(ctx netcontext.Context, apiPort string) netcontext.Context {
73+
return netcontext.WithValue(ctx, apiPortOverrideKey, apiPort)
74+
}
75+
5876
var namespaceKey = "holds the namespace string"
5977

6078
func withNamespace(ctx netcontext.Context, ns string) netcontext.Context {

0 commit comments

Comments
 (0)