| // Copyright 2015 Google Inc. All rights reserved. |
| // Use of this source code is governed by the Apache 2.0 |
| // license that can be found in the LICENSE file. |
| |
| // +build appengine |
| |
| package internal |
| |
| import ( |
| "errors" |
| "fmt" |
| "net/http" |
| "time" |
| |
| "appengine" |
| "appengine_internal" |
| basepb "appengine_internal/base" |
| |
| "github.com/golang/protobuf/proto" |
| netcontext "golang.org/x/net/context" |
| ) |
| |
| var contextKey = "holds an appengine.Context" |
| |
| // fromContext returns the App Engine context or nil if ctx is not |
| // derived from an App Engine context. |
| func fromContext(ctx netcontext.Context) appengine.Context { |
| c, _ := ctx.Value(&contextKey).(appengine.Context) |
| return c |
| } |
| |
| // This is only for classic App Engine adapters. |
| func ClassicContextFromContext(ctx netcontext.Context) (appengine.Context, error) { |
| c := fromContext(ctx) |
| if c == nil { |
| return nil, errNotAppEngineContext |
| } |
| return c, nil |
| } |
| |
| func withContext(parent netcontext.Context, c appengine.Context) netcontext.Context { |
| ctx := netcontext.WithValue(parent, &contextKey, c) |
| |
| s := &basepb.StringProto{} |
| c.Call("__go__", "GetNamespace", &basepb.VoidProto{}, s, nil) |
| if ns := s.GetValue(); ns != "" { |
| ctx = NamespacedContext(ctx, ns) |
| } |
| |
| return ctx |
| } |
| |
| func IncomingHeaders(ctx netcontext.Context) http.Header { |
| if c := fromContext(ctx); c != nil { |
| if req, ok := c.Request().(*http.Request); ok { |
| return req.Header |
| } |
| } |
| return nil |
| } |
| |
| func ReqContext(req *http.Request) netcontext.Context { |
| return WithContext(netcontext.Background(), req) |
| } |
| |
| func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context { |
| c := appengine.NewContext(req) |
| return withContext(parent, c) |
| } |
| |
| type testingContext struct { |
| appengine.Context |
| |
| req *http.Request |
| } |
| |
| func (t *testingContext) FullyQualifiedAppID() string { return "dev~testcontext" } |
| func (t *testingContext) Call(service, method string, _, _ appengine_internal.ProtoMessage, _ *appengine_internal.CallOptions) error { |
| if service == "__go__" && method == "GetNamespace" { |
| return nil |
| } |
| return fmt.Errorf("testingContext: unsupported Call") |
| } |
| func (t *testingContext) Request() interface{} { return t.req } |
| |
| func ContextForTesting(req *http.Request) netcontext.Context { |
| return withContext(netcontext.Background(), &testingContext{req: req}) |
| } |
| |
| func Call(ctx netcontext.Context, service, method string, in, out proto.Message) error { |
| if ns := NamespaceFromContext(ctx); ns != "" { |
| if fn, ok := NamespaceMods[service]; ok { |
| fn(in, ns) |
| } |
| } |
| |
| if f, ctx, ok := callOverrideFromContext(ctx); ok { |
| return f(ctx, service, method, in, out) |
| } |
| |
| // Handle already-done contexts quickly. |
| select { |
| case <-ctx.Done(): |
| return ctx.Err() |
| default: |
| } |
| |
| c := fromContext(ctx) |
| if c == nil { |
| // Give a good error message rather than a panic lower down. |
| return errNotAppEngineContext |
| } |
| |
| // Apply transaction modifications if we're in a transaction. |
| if t := transactionFromContext(ctx); t != nil { |
| if t.finished { |
| return errors.New("transaction context has expired") |
| } |
| applyTransaction(in, &t.transaction) |
| } |
| |
| var opts *appengine_internal.CallOptions |
| if d, ok := ctx.Deadline(); ok { |
| opts = &appengine_internal.CallOptions{ |
| Timeout: d.Sub(time.Now()), |
| } |
| } |
| |
| err := c.Call(service, method, in, out, opts) |
| switch v := err.(type) { |
| case *appengine_internal.APIError: |
| return &APIError{ |
| Service: v.Service, |
| Detail: v.Detail, |
| Code: v.Code, |
| } |
| case *appengine_internal.CallError: |
| return &CallError{ |
| Detail: v.Detail, |
| Code: v.Code, |
| Timeout: v.Timeout, |
| } |
| } |
| return err |
| } |
| |
| func handleHTTP(w http.ResponseWriter, r *http.Request) { |
| panic("handleHTTP called; this should be impossible") |
| } |
| |
| func logf(c appengine.Context, level int64, format string, args ...interface{}) { |
| var fn func(format string, args ...interface{}) |
| switch level { |
| case 0: |
| fn = c.Debugf |
| case 1: |
| fn = c.Infof |
| case 2: |
| fn = c.Warningf |
| case 3: |
| fn = c.Errorf |
| case 4: |
| fn = c.Criticalf |
| default: |
| // This shouldn't happen. |
| fn = c.Criticalf |
| } |
| fn(format, args...) |
| } |