M .gcloudignore => .gcloudignore +0 -1
@@ 17,4 17,3 @@
__pycache__/
# Ignored by the build system
/setup.cfg
-/send.py
M README.md => README.md +3 -4
@@ 7,8 7,7 @@ while plain HTTP is easy, HTTPS requires actual tcp tunnelling which I'm not
really up for at the moment (is that even possible on GAE standard
evironment?).
-So I devised my own scheme where every param is defined in a POST json body.
-Dumbest thing that works right?
+So I devised my own "scheme". Dumbest thing that works right?
# Server
@@ 16,11 15,11 @@ Create an `envars.yaml` file to store secret password:
```yaml
env_variables:
- GAEPROXY_PASSWORD: "long long string"
+ GAEPROXY_KEY: "long long string"
```
Then just `gcloud app deploy`.
# Use
-See **send.py**. It's pretty straightforward.
+See comments in **app.py**.
A app.py => app.py +75 -0
@@ 0,0 1,75 @@
+"""
+Dumb (bespoke "protocol" & grossly inefficient) http(s) proxy on Google Cloud Functions.
+It also attempts to solve CloudFlare's javascript challenge where necessary.
+
+To use, simply send request to proxy just as you would to the target, but provide some
+extra http headers:
+
+- X-Proxy-Key: For authentication.
+- X-Proxy-Target-Host: So the proxy knows what hostname to proxy to.
+- X-Proxy-Target-Scheme: Optional. Defaults to https.
+
+To deploy:
+
+ $ export GAEPROXY_KEY='my-secret-proxy-key'
+ $ gcloud app deploy
+"""
+import os
+
+import cloudscraper
+from flask import Flask, Response, request
+
+http = cloudscraper.create_scraper(
+ browser={"browser": "firefox", "platform": "windows", "mobile": False}
+)
+
+PROXY_KEY = os.environ["GAEPROXY_KEY"]
+
+
+app = Flask(__name__)
+
+
+@app.route("/", defaults={"path": ""}, methods=["GET", "POST", "PUT", "DELETE"])
+@app.route("/<path:path>", methods=["GET", "POST", "PUT", "DELETE"])
+def hello_world(path):
+ auth_key = request.headers.get("X-Proxy-Key")
+ if auth_key != PROXY_KEY:
+ return "Go away", 403
+
+ target_headers = {
+ key: val
+ for key, val in dict(request.headers).items()
+ if not (
+ key.startswith("X-Appengine-")
+ or key
+ in (
+ "Accept-Encoding",
+ "Content-Length",
+ "Transfer-Encoding",
+ "X-Amzn-Trace-Id",
+ "X-Cloud-Trace-Context",
+ "X-Forwarded-For",
+ "X-Forwarded-Proto",
+ )
+ )
+ }
+ target_headers.pop("X-Proxy-Key")
+ target_headers.pop("Host")
+
+ target_host = target_headers.pop("X-Proxy-Target-Host")
+ target_scheme = target_headers.pop("X-Proxy-Target-Scheme", "https")
+ target_path = "/".join(request.full_path.split("/")[1:])
+ target_url = f"{target_scheme}://{target_host}/{target_path}"
+
+ send = getattr(http, request.method.lower())
+ target_resp = send(target_url, headers=target_headers)
+
+ resp_headers = dict(target_resp.headers)
+ resp_headers.pop("Content-Encoding", None)
+ resp_headers.pop("Transfer-Encoding", None)
+
+ return Response(
+ response=target_resp.content,
+ status=target_resp.status_code,
+ headers=resp_headers,
+ )
M app.yaml => app.yaml +2 -2
@@ 1,5 1,5 @@
-runtime: python37
-entrypoint: gunicorn -b :$PORT --workers=5 main:app
+runtime: python39
+entrypoint: gunicorn -b :$PORT --workers=5 app:app
handlers:
- url: /.*
D main.py => main.py +0 -47
@@ 1,47 0,0 @@
-import os
-
-import requests
-from bottle import HTTPResponse, default_app, request, route, run
-
-# GAE recommended
-PORT = os.environ.get("PORT", 8080)
-PASSWORD = os.environ.get("GAEPROXY_PASSWORD", "")
-
-proxiable_methods = {
- "get": requests.get,
- "post": requests.post,
-}
-
-
-@route("/proxy", method="POST")
-def proxy():
- req = request.json
-
- fields = ("method", "password", "url", "body")
- missing_fields = [f for f in fields if f not in req]
- if missing_fields:
- return HTTPResponse(status=400, body=f"Missing fields: {missing_fields}")
-
- if req["method"] not in proxiable_methods:
- return HTTPResponse(status=400, body="We serve get/post only!")
-
- if req.get("password") != PASSWORD:
- return HTTPResponse(status=400, body="Get off my lawn!")
-
- requests_func = proxiable_methods[req["method"]]
- requests_kwargs = {"url": req["url"], "headers": req["headers"]}
- if req["method"] == "post":
- requests_kwargs["body"] = req["body"]
-
- try:
- resp = requests_func(**requests_kwargs, timeout=10)
- except Exception as e:
- return HTTPResponse(status=500, body=f"Unexpected error:\n{e}")
-
- return HTTPResponse(body=resp.text, status=resp.status_code)
-
-
-app = default_app()
-
-if __name__ == "__main__":
- run(host="localhost", port=PORT)
M requirements.txt => requirements.txt +2 -2
@@ 1,3 1,3 @@
-requests
-bottle
+cloudscraper
+flask
gunicorn
D send.py => send.py +0 -32
@@ 1,32 0,0 @@
-import os
-from datetime import datetime as dt
-
-import requests
-
-"""
-Lazy quick test script
-"""
-
-PASSWORD = os.environ["GAEPROXY_PASSWORD"]
-
-while True:
- start = dt.now()
- resp = requests.post(
- "https://nhansproxy.df.r.appspot.com/proxy",
- json={
- "url": "https://httpbin.org/ip",
- "method": "get",
- "body": None,
- "headers": {"Foo-Bar": "ehhhh"},
- "password": PASSWORD,
- },
- )
-
- """
- print(resp.status_code)
- for hkey, hval in resp.headers.items():
- print(hkey, ":", hval)
- """
- end = dt.now()
- print(resp.status_code)
- print((end - start).total_seconds())