// 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...) }