Merge branch 'master' into merge-qa
diff --git a/aetest/instance_vm.go b/aetest/instance_vm.go
index 89ff8b1..d2e5d29 100644
--- a/aetest/instance_vm.go
+++ b/aetest/instance_vm.go
@@ -192,6 +192,11 @@
return err
}
+ datastorePath := os.Getenv("APPENGINE_DEV_APPSERVER_DATASTORE_PATH")
+ if len(datastorePath) == 0 {
+ datastorePath = filepath.Join(i.appDir, "datastore")
+ }
+
appserverArgs = append(appserverArgs,
"--port=0",
"--api_port=0",
@@ -200,7 +205,7 @@
"--skip_sdk_update_check=true",
"--clear_datastore=true",
"--clear_search_indexes=true",
- "--datastore_path", filepath.Join(i.appDir, "datastore"),
+ "--datastore_path", datastorePath,
)
if i.opts != nil && i.opts.StronglyConsistentDatastore {
appserverArgs = append(appserverArgs, "--datastore_consistency_policy=consistent")
diff --git a/appengine.go b/appengine.go
index 8c96976..f65f94a 100644
--- a/appengine.go
+++ b/appengine.go
@@ -54,6 +54,9 @@
internal.Main()
}
+// Middleware wraps an http handler so that it can make GAE API calls
+var Middleware func(http.Handler) http.Handler = internal.Middleware
+
// IsDevAppServer reports whether the App Engine app is running in the
// development App Server.
func IsDevAppServer() bool {
diff --git a/appengine_vm.go b/appengine_vm.go
index f4b645a..4893e52 100644
--- a/appengine_vm.go
+++ b/appengine_vm.go
@@ -13,8 +13,6 @@
)
// BackgroundContext returns a context not associated with a request.
-// This should only be used when not servicing a request.
-// This only works in App Engine "flexible environment".
func BackgroundContext() context.Context {
return internal.BackgroundContext()
}
diff --git a/internal/api.go b/internal/api.go
index 2748318..7be64e2 100644
--- a/internal/api.go
+++ b/internal/api.go
@@ -87,88 +87,98 @@
}
}
-func handleHTTP(w http.ResponseWriter, r *http.Request) {
- c := &context{
- req: r,
- outHeader: w.Header(),
- apiURL: apiURL(),
- }
- r = r.WithContext(withContext(r.Context(), c))
- c.req = r
-
- stopFlushing := make(chan int)
-
- // Patch up RemoteAddr so it looks reasonable.
- if addr := r.Header.Get(userIPHeader); addr != "" {
- r.RemoteAddr = addr
- } else if addr = r.Header.Get(remoteAddrHeader); addr != "" {
- r.RemoteAddr = addr
- } else {
- // Should not normally reach here, but pick a sensible default anyway.
- r.RemoteAddr = "127.0.0.1"
- }
- // The address in the headers will most likely be of these forms:
- // 123.123.123.123
- // 2001:db8::1
- // net/http.Request.RemoteAddr is specified to be in "IP:port" form.
- if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
- // Assume the remote address is only a host; add a default port.
- r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80")
- }
-
- if logToLogservice() {
- // Start goroutine responsible for flushing app logs.
- // This is done after adding c to ctx.m (and stopped before removing it)
- // because flushing logs requires making an API call.
- go c.logFlusher(stopFlushing)
- }
-
- executeRequestSafely(c, r)
- c.outHeader = nil // make sure header changes aren't respected any more
-
- flushed := make(chan struct{})
- if logToLogservice() {
- stopFlushing <- 1 // any logging beyond this point will be dropped
-
- // Flush any pending logs asynchronously.
- c.pendingLogs.Lock()
- flushes := c.pendingLogs.flushes
- if len(c.pendingLogs.lines) > 0 {
- flushes++
- }
- c.pendingLogs.Unlock()
- go func() {
- defer close(flushed)
- // Force a log flush, because with very short requests we
- // may not ever flush logs.
- c.flushLog(true)
- }()
- w.Header().Set(logFlushHeader, strconv.Itoa(flushes))
- }
-
- // Avoid nil Write call if c.Write is never called.
- if c.outCode != 0 {
- w.WriteHeader(c.outCode)
- }
- if c.outBody != nil {
- w.Write(c.outBody)
- }
- if logToLogservice() {
- // Wait for the last flush to complete before returning,
- // otherwise the security ticket will not be valid.
- <-flushed
- }
+// Middleware wraps an http handler so that it can make GAE API calls
+func Middleware(next http.Handler) http.Handler {
+ return handleHTTPMiddleware(executeRequestSafelyMiddleware(next))
}
-func executeRequestSafely(c *context, r *http.Request) {
- defer func() {
- if x := recover(); x != nil {
- logf(c, 4, "%s", renderPanic(x)) // 4 == critical
- c.outCode = 500
+func handleHTTPMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ c := &context{
+ req: r,
+ outHeader: w.Header(),
+ apiURL: apiURL(),
}
- }()
+ r = r.WithContext(withContext(r.Context(), c))
+ c.req = r
- http.DefaultServeMux.ServeHTTP(c, r)
+ stopFlushing := make(chan int)
+
+ // Patch up RemoteAddr so it looks reasonable.
+ if addr := r.Header.Get(userIPHeader); addr != "" {
+ r.RemoteAddr = addr
+ } else if addr = r.Header.Get(remoteAddrHeader); addr != "" {
+ r.RemoteAddr = addr
+ } else {
+ // Should not normally reach here, but pick a sensible default anyway.
+ r.RemoteAddr = "127.0.0.1"
+ }
+ // The address in the headers will most likely be of these forms:
+ // 123.123.123.123
+ // 2001:db8::1
+ // net/http.Request.RemoteAddr is specified to be in "IP:port" form.
+ if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
+ // Assume the remote address is only a host; add a default port.
+ r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80")
+ }
+
+ if logToLogservice() {
+ // Start goroutine responsible for flushing app logs.
+ // This is done after adding c to ctx.m (and stopped before removing it)
+ // because flushing logs requires making an API call.
+ go c.logFlusher(stopFlushing)
+ }
+
+ next.ServeHTTP(c, r)
+ c.outHeader = nil // make sure header changes aren't respected any more
+
+ flushed := make(chan struct{})
+ if logToLogservice() {
+ stopFlushing <- 1 // any logging beyond this point will be dropped
+
+ // Flush any pending logs asynchronously.
+ c.pendingLogs.Lock()
+ flushes := c.pendingLogs.flushes
+ if len(c.pendingLogs.lines) > 0 {
+ flushes++
+ }
+ c.pendingLogs.Unlock()
+ go func() {
+ defer close(flushed)
+ // Force a log flush, because with very short requests we
+ // may not ever flush logs.
+ c.flushLog(true)
+ }()
+ w.Header().Set(logFlushHeader, strconv.Itoa(flushes))
+ }
+
+ // Avoid nil Write call if c.Write is never called.
+ if c.outCode != 0 {
+ w.WriteHeader(c.outCode)
+ }
+ if c.outBody != nil {
+ w.Write(c.outBody)
+ }
+ if logToLogservice() {
+ // Wait for the last flush to complete before returning,
+ // otherwise the security ticket will not be valid.
+ <-flushed
+ }
+ })
+}
+
+func executeRequestSafelyMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ defer func() {
+ if x := recover(); x != nil {
+ c := w.(*context)
+ logf(c, 4, "%s", renderPanic(x)) // 4 == critical
+ c.outCode = 500
+ }
+ }()
+
+ next.ServeHTTP(w, r)
+ })
}
func renderPanic(x interface{}) string {
diff --git a/internal/api_classic.go b/internal/api_classic.go
index f0f40b2..a9beece 100644
--- a/internal/api_classic.go
+++ b/internal/api_classic.go
@@ -144,8 +144,8 @@
return err
}
-func handleHTTP(w http.ResponseWriter, r *http.Request) {
- panic("handleHTTP called; this should be impossible")
+func Middleware(next http.Handler) http.Handler {
+ panic("Middleware called; this should be impossible")
}
func logf(c appengine.Context, level int64, format string, args ...interface{}) {
diff --git a/internal/api_test.go b/internal/api_test.go
index 5b025cf..d4af31b 100644
--- a/internal/api_test.go
+++ b/internal/api_test.go
@@ -302,7 +302,7 @@
handled := make(chan struct{})
go func() {
defer close(handled)
- handleHTTP(w, r)
+ Middleware(http.DefaultServeMux).ServeHTTP(w, r)
}()
// Check that the log flush eventually comes in.
time.Sleep(1200 * time.Millisecond)
@@ -360,7 +360,7 @@
}
w := httptest.NewRecorder()
- handleHTTP(w, r)
+ Middleware(http.DefaultServeMux).ServeHTTP(w, r)
const hdr = "X-AppEngine-Log-Flush-Count"
if got := w.HeaderMap.Get(hdr); got != tc.wantHeader {
t.Errorf("%s header = %q, want %q", hdr, got, tc.wantHeader)
@@ -403,7 +403,7 @@
Header: tc.headers,
Body: ioutil.NopCloser(bytes.NewReader(nil)),
}
- handleHTTP(httptest.NewRecorder(), r)
+ Middleware(http.DefaultServeMux).ServeHTTP(httptest.NewRecorder(), r)
if addr != tc.addr {
t.Errorf("Header %v, got %q, want %q", tc.headers, addr, tc.addr)
}
@@ -420,7 +420,7 @@
Body: ioutil.NopCloser(bytes.NewReader(nil)),
}
rec := httptest.NewRecorder()
- handleHTTP(rec, r)
+ Middleware(http.DefaultServeMux).ServeHTTP(rec, r)
if rec.Code != 500 {
t.Errorf("Panicking handler returned HTTP %d, want HTTP %d", rec.Code, 500)
}
diff --git a/internal/main_vm.go b/internal/main_vm.go
index ddb79a3..2c53daf 100644
--- a/internal/main_vm.go
+++ b/internal/main_vm.go
@@ -29,7 +29,7 @@
if IsDevAppServer() {
host = "127.0.0.1"
}
- if err := http.ListenAndServe(host+":"+port, http.HandlerFunc(handleHTTP)); err != nil {
+ if err := http.ListenAndServe(host+":"+port, Middleware(http.DefaultServeMux)); err != nil {
log.Fatalf("http.ListenAndServe: %v", err)
}
}
diff --git a/urlfetch/urlfetch.go b/urlfetch/urlfetch.go
index 6ffe1e6..8d44bfe 100644
--- a/urlfetch/urlfetch.go
+++ b/urlfetch/urlfetch.go
@@ -44,11 +44,10 @@
var _ http.RoundTripper = (*Transport)(nil)
// Client returns an *http.Client using a default urlfetch Transport. This
-// client will have the default deadline of 5 seconds, and will check the
-// validity of SSL certificates.
+// client will check the validity of SSL certificates.
//
-// Any deadline of the provided context will be used for requests through this client;
-// if the client does not have a deadline then a 5 second default is used.
+// Any deadline of the provided context will be used for requests through this client.
+// If the client does not have a deadline, then an App Engine default of 60 second is used.
func Client(ctx context.Context) *http.Client {
return &http.Client{
Transport: &Transport{
diff --git a/v2/appengine.go b/v2/appengine.go
index 58a52c1..b2ab1aa 100644
--- a/v2/appengine.go
+++ b/v2/appengine.go
@@ -135,8 +135,6 @@
}
// BackgroundContext returns a context not associated with a request.
-// This should only be used when not servicing a request.
-// This only works in App Engine "flexible environment".
func BackgroundContext() context.Context {
return internal.BackgroundContext()
}