commit a150a991d0c2ba66ca9e567bd088c603c9434775 Author: David Fifield david@bamsoftware.com Date: Sat Jul 15 11:48:28 2017 -0700
Copy appengine directory from meek commit 6057a9e9d6. --- appengine/README | 31 +++++++++++++ appengine/app.yaml | 10 ++++ appengine/reflect.go | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+)
diff --git a/appengine/README b/appengine/README new file mode 100644 index 0000000..d71277a --- /dev/null +++ b/appengine/README @@ -0,0 +1,31 @@ +This component runs on Google App Engine. It lies between meek-client +and meek-server. The App Engine component receives requests from the +client and forwards them to the server, then receives responses from the +server and forwards them to the client. + +You need the Go App Engine SDK in order to deploy the app. + https://cloud.google.com/sdk/docs/#linux +After unpacking, install the app-engine-go component: + google-cloud-sdk/bin/gcloud components install app-engine-go + +To test locally, run + google-cloud-sdk/bin/dev_appserver.py app.yaml +The app will be running at http://127.0.0.1:8080/. You can test broker +forwarding function by browsing to http://127.0.0.1:8000/ip. + +To deploy to App Engine, first create a new project and app. You have to +think of a unique name (marked as "<appname>" in the commands). You only +have to do the "create" step once; subsequent times you can go straight +to the "deploy" step. This command will open a browser window so you can +log in to a Google account. + google-cloud-sdk/bin/gcloud projects create <appname> + google-cloud-sdk/bin/gcloud app create --project=<appname> +Then to deploy the project, run: + google-cloud-sdk/bin/gcloud app deploy --project=<appname> + +To configure meek-client to talk to the App Engine app, provide +"https://<appname>.appspot.com/" as the url and "www.google.com" as the +front domain. + UseBridges 1 + Bridge meek 0.0.2.0:1 url=https://example.appspot.com/ front=www.google.com + ClientTransportPlugin meek exec ./meek-client --log meek-client.log diff --git a/appengine/app.yaml b/appengine/app.yaml new file mode 100644 index 0000000..ff6efc1 --- /dev/null +++ b/appengine/app.yaml @@ -0,0 +1,10 @@ +runtime: go +api_version: go1 +automatic_scaling: + max_idle_instances: 2 + min_pending_latency: 1000ms + +handlers: +- url: /.* + script: _go_app + secure: always diff --git a/appengine/reflect.go b/appengine/reflect.go new file mode 100644 index 0000000..ccf11f3 --- /dev/null +++ b/appengine/reflect.go @@ -0,0 +1,127 @@ +// A web app for Google App Engine that proxies HTTP requests and responses to a +// Tor relay running meek-server. +package reflect + +import ( + "io" + "net" + "net/http" + "net/url" + "time" + + "appengine" + "appengine/urlfetch" +) + +const ( + forwardURL = "https://meek.bamsoftware.com/" + // A timeout of 0 means to use the App Engine default (5 seconds). + urlFetchTimeout = 20 * time.Second +) + +var context appengine.Context + +// Join two URL paths. +func pathJoin(a, b string) string { + if len(a) > 0 && a[len(a)-1] == '/' { + a = a[:len(a)-1] + } + if len(b) == 0 || b[0] != '/' { + b = "/" + b + } + return a + b +} + +// We reflect only a whitelisted set of header fields. In requests, the full +// list includes things like User-Agent and X-Appengine-Country that the Tor +// bridge doesn't need to know. In responses, there may be things like +// Transfer-Encoding that interfere with App Engine's own hop-by-hop headers. +var reflectedHeaderFields = []string{ + "Content-Type", + "X-Session-Id", +} + +// Get the original client IP address as a string. When using the standard +// net/http server, Request.RemoteAddr is a "host:port" string; however App +// Engine seems to use just "host". We check for both to be safe. +func getClientAddr(r *http.Request) string { + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err == nil { + return host + } + return r.RemoteAddr +} + +// Make a copy of r, with the URL being changed to be relative to forwardURL, +// and including only the headers in reflectedHeaderFields. +func copyRequest(r *http.Request) (*http.Request, error) { + u, err := url.Parse(forwardURL) + if err != nil { + return nil, err + } + // Append the requested path to the path in forwardURL, so that + // forwardURL can be something like "https://example.com/reflect". + u.Path = pathJoin(u.Path, r.URL.Path) + c, err := http.NewRequest(r.Method, u.String(), r.Body) + if err != nil { + return nil, err + } + for _, key := range reflectedHeaderFields { + values, ok := r.Header[key] + if ok { + for _, value := range values { + c.Header.Add(key, value) + } + } + } + // Set the original client IP address in a Meek-IP header. We would use + // X-Forwarded-For, but App Engine prohibits setting that header: + // https://cloud.google.com/appengine/docs/standard/go/outbound-requests#reques... + // We could use Forwarded from RFC 7239, but other CDNs already use + // X-Forwarded-For and this way we only need one parser. + c.Header.Add("Meek-IP", getClientAddr(r)) + return c, nil +} + +func handler(w http.ResponseWriter, r *http.Request) { + context = appengine.NewContext(r) + fr, err := copyRequest(r) + if err != nil { + context.Errorf("copyRequest: %s", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + // Use urlfetch.Transport directly instead of urlfetch.Client because we + // want only a single HTTP transaction, not following redirects. + transport := urlfetch.Transport{ + Context: context, + // Despite the name, Transport.Deadline is really a timeout and + // not an absolute deadline as used in the net package. In + // other words it is a time.Duration, not a time.Time. + Deadline: urlFetchTimeout, + } + resp, err := transport.RoundTrip(fr) + if err != nil { + context.Errorf("RoundTrip: %s", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer resp.Body.Close() + for _, key := range reflectedHeaderFields { + values, ok := resp.Header[key] + if ok { + for _, value := range values { + w.Header().Add(key, value) + } + } + } + w.WriteHeader(resp.StatusCode) + n, err := io.Copy(w, resp.Body) + if err != nil { + context.Errorf("io.Copy after %d bytes: %s", n, err) + } +} + +func init() { + http.HandleFunc("/", handler) +}
tor-commits@lists.torproject.org