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
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}
170