blob: f0f40b2e35c2c35904b1c079eb0e5e54027ed0de [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001// Copyright 2015 Google Inc. All rights reserved.
2// Use of this source code is governed by the Apache 2.0
3// license that can be found in the LICENSE file.
4
5// +build appengine
6
7package internal
8
9import (
10 "errors"
11 "fmt"
12 "net/http"
13 "time"
14
15 "appengine"
16 "appengine_internal"
17 basepb "appengine_internal/base"
18
19 "github.com/golang/protobuf/proto"
20 netcontext "golang.org/x/net/context"
21)
22
23var contextKey = "holds an appengine.Context"
24
25// fromContext returns the App Engine context or nil if ctx is not
26// derived from an App Engine context.
27func fromContext(ctx netcontext.Context) appengine.Context {
28 c, _ := ctx.Value(&contextKey).(appengine.Context)
29 return c
30}
31
32// This is only for classic App Engine adapters.
33func ClassicContextFromContext(ctx netcontext.Context) (appengine.Context, error) {
34 c := fromContext(ctx)
35 if c == nil {
36 return nil, errNotAppEngineContext
37 }
38 return c, nil
39}
40
41func withContext(parent netcontext.Context, c appengine.Context) netcontext.Context {
42 ctx := netcontext.WithValue(parent, &contextKey, c)
43
44 s := &basepb.StringProto{}
45 c.Call("__go__", "GetNamespace", &basepb.VoidProto{}, s, nil)
46 if ns := s.GetValue(); ns != "" {
47 ctx = NamespacedContext(ctx, ns)
48 }
49
50 return ctx
51}
52
53func IncomingHeaders(ctx netcontext.Context) http.Header {
54 if c := fromContext(ctx); c != nil {
55 if req, ok := c.Request().(*http.Request); ok {
56 return req.Header
57 }
58 }
59 return nil
60}
61
62func ReqContext(req *http.Request) netcontext.Context {
63 return WithContext(netcontext.Background(), req)
64}
65
66func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context {
67 c := appengine.NewContext(req)
68 return withContext(parent, c)
69}
70
71type testingContext struct {
72 appengine.Context
73
74 req *http.Request
75}
76
77func (t *testingContext) FullyQualifiedAppID() string { return "dev~testcontext" }
78func (t *testingContext) Call(service, method string, _, _ appengine_internal.ProtoMessage, _ *appengine_internal.CallOptions) error {
79 if service == "__go__" && method == "GetNamespace" {
80 return nil
81 }
82 return fmt.Errorf("testingContext: unsupported Call")
83}
84func (t *testingContext) Request() interface{} { return t.req }
85
86func ContextForTesting(req *http.Request) netcontext.Context {
87 return withContext(netcontext.Background(), &testingContext{req: req})
88}
89
90func Call(ctx netcontext.Context, service, method string, in, out proto.Message) error {
91 if ns := NamespaceFromContext(ctx); ns != "" {
92 if fn, ok := NamespaceMods[service]; ok {
93 fn(in, ns)
94 }
95 }
96
97 if f, ctx, ok := callOverrideFromContext(ctx); ok {
98 return f(ctx, service, method, in, out)
99 }
100
101 // Handle already-done contexts quickly.
102 select {
103 case <-ctx.Done():
104 return ctx.Err()
105 default:
106 }
107
108 c := fromContext(ctx)
109 if c == nil {
110 // Give a good error message rather than a panic lower down.
111 return errNotAppEngineContext
112 }
113
114 // Apply transaction modifications if we're in a transaction.
115 if t := transactionFromContext(ctx); t != nil {
116 if t.finished {
117 return errors.New("transaction context has expired")
118 }
119 applyTransaction(in, &t.transaction)
120 }
121
122 var opts *appengine_internal.CallOptions
123 if d, ok := ctx.Deadline(); ok {
124 opts = &appengine_internal.CallOptions{
125 Timeout: d.Sub(time.Now()),
126 }
127 }
128
129 err := c.Call(service, method, in, out, opts)
130 switch v := err.(type) {
131 case *appengine_internal.APIError:
132 return &APIError{
133 Service: v.Service,
134 Detail: v.Detail,
135 Code: v.Code,
136 }
137 case *appengine_internal.CallError:
138 return &CallError{
139 Detail: v.Detail,
140 Code: v.Code,
141 Timeout: v.Timeout,
142 }
143 }
144 return err
145}
146
147func handleHTTP(w http.ResponseWriter, r *http.Request) {
148 panic("handleHTTP called; this should be impossible")
149}
150
151func logf(c appengine.Context, level int64, format string, args ...interface{}) {
152 var fn func(format string, args ...interface{})
153 switch level {
154 case 0:
155 fn = c.Debugf
156 case 1:
157 fn = c.Infof
158 case 2:
159 fn = c.Warningf
160 case 3:
161 fn = c.Errorf
162 case 4:
163 fn = c.Criticalf
164 default:
165 // This shouldn't happen.
166 fn = c.Criticalf
167 }
168 fn(format, args...)
169}