]>
Commit | Line | Data |
---|---|---|
107c1cdb ND |
1 | // 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 | ||
7 | package internal | |
8 | ||
9 | import ( | |
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 | ||
23 | var 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. | |
27 | func 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. | |
33 | func 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 | ||
41 | func 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 | ||
53 | func 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 | ||
62 | func ReqContext(req *http.Request) netcontext.Context { | |
63 | return WithContext(netcontext.Background(), req) | |
64 | } | |
65 | ||
66 | func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context { | |
67 | c := appengine.NewContext(req) | |
68 | return withContext(parent, c) | |
69 | } | |
70 | ||
71 | type testingContext struct { | |
72 | appengine.Context | |
73 | ||
74 | req *http.Request | |
75 | } | |
76 | ||
77 | func (t *testingContext) FullyQualifiedAppID() string { return "dev~testcontext" } | |
78 | func (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 | } | |
84 | func (t *testingContext) Request() interface{} { return t.req } | |
85 | ||
86 | func ContextForTesting(req *http.Request) netcontext.Context { | |
87 | return withContext(netcontext.Background(), &testingContext{req: req}) | |
88 | } | |
89 | ||
90 | func 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 | ||
147 | func handleHTTP(w http.ResponseWriter, r *http.Request) { | |
148 | panic("handleHTTP called; this should be impossible") | |
149 | } | |
150 | ||
151 | func 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 | } |