├── .gitignore ├── README.md └── proxy.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | flask-as-http-proxy-server 2 | ========================== 3 | 4 | Small HTTP Server used with Flask and werkzeug 5 | 6 | test 7 | ======= 8 | 9 | * works with python 2/3 10 | * the only needed library is flask :) 11 | 12 | License 13 | ======= 14 | BSD. use your own risk. 15 | -------------------------------------------------------------------------------- /proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | ############################################# 5 | # Flask & werkzeug HTTP Proxy Sample code. 6 | # - Code by Jioh L. Jung (ziozzang@gmail.com) 7 | ############################################# 8 | 9 | import sys 10 | if sys.version_info[0] < 3: 11 | import httplib 12 | import urlparse 13 | else: 14 | import http.client as httplib 15 | import urllib.parse as urlparse 16 | 17 | import re 18 | import urllib 19 | import json 20 | 21 | from flask import Flask, Blueprint, request, Response, url_for 22 | from werkzeug.datastructures import Headers 23 | from werkzeug.exceptions import NotFound 24 | 25 | app = Flask(__name__) 26 | 27 | # Default Configuration 28 | DEBUG_FLAG = True 29 | LISTEN_PORT = 7788 30 | 31 | proxy = Blueprint('proxy', __name__) 32 | 33 | # You can insert Authentication here. 34 | #proxy.before_request(check_login) 35 | 36 | # Filters. 37 | HTML_REGEX = re.compile(r'((?:src|action|href)=["\'])/') 38 | JQUERY_REGEX = re.compile(r'(\$\.(?:get|post)\(["\'])/') 39 | JS_LOCATION_REGEX = re.compile(r'((?:window|document)\.location.*=.*["\'])/') 40 | CSS_REGEX = re.compile(r'(url\(["\']?)/') 41 | 42 | REGEXES = [HTML_REGEX, JQUERY_REGEX, JS_LOCATION_REGEX, CSS_REGEX] 43 | 44 | 45 | def iterform(multidict): 46 | for key in multidict.keys(): 47 | for value in multidict.getlist(key): 48 | yield (key.encode("utf8"), value.encode("utf8")) 49 | 50 | def parse_host_port(h, proto): 51 | """Parses strings in the form host[:port]""" 52 | host_port = h.split(":", 1) 53 | if len(host_port) == 1: 54 | if proto.lower() == 'http': return (h, 80) 55 | if proto.lower() == 'https': return (h, 443) 56 | return (h, 443) # Default is HTTPS 57 | else: 58 | host_port[1] = int(host_port[1]) 59 | return host_port 60 | 61 | 62 | # For RESTful Service 63 | @proxy.route('/proxy///', methods=["GET", "POST", "PUT", "DELETE"]) 64 | @proxy.route('/proxy///', methods=["GET", "POST", "PUT", "DELETE"]) 65 | def proxy_request(proto, host, file=""): 66 | hostname, port = parse_host_port(host, proto) 67 | 68 | print ("H: '%s' P: %d" % (hostname, port)) 69 | print ("F: '%s'" % (file)) 70 | # Whitelist a few headers to pass on 71 | request_headers = {} 72 | for h in ["Cookie", "Referer", "X-Csrf-Token"]: 73 | if h in request.headers: 74 | request_headers[h] = request.headers[h] 75 | 76 | if request.query_string: 77 | path = "/%s?%s" % (file, request.query_string) 78 | else: 79 | path = "/" + file 80 | 81 | if request.method == "POST" or request.method == "PUT": 82 | form_data = list(iterform(request.form)) 83 | form_data = urllib.urlencode(form_data) 84 | request_headers["Content-Length"] = len(form_data) 85 | else: 86 | form_data = None 87 | 88 | 89 | if not ('host' in request_headers.keys()): 90 | request_headers['host'] = hostname 91 | 92 | # if target is for HTTP, use HTTPConnection method. 93 | request_method = httplib.HTTPSConnection 94 | if proto.lower() == 'http': request_method = httplib.HTTPConnection 95 | conn = request_method(hostname, port) 96 | conn.request(request.method, path, body=form_data, headers=request_headers) 97 | resp = conn.getresponse() 98 | 99 | # Clean up response headers for forwarding 100 | d = {} 101 | response_headers = Headers() 102 | for key, value in resp.getheaders(): 103 | print ("HEADER: '%s':'%s'" % (key, value)) 104 | d[key.lower()] = value 105 | if key in ["content-length", "connection", "content-type"]: 106 | continue 107 | 108 | if key == "set-cookie": 109 | cookies = value.split(",") 110 | [response_headers.add(key, c) for c in cookies] 111 | else: 112 | response_headers.add(key, value) 113 | 114 | # If this is a redirect, munge the Location URL 115 | if "location" in response_headers: 116 | redirect = response_headers["location"] 117 | parsed = urlparse.urlparse(request.url) 118 | redirect_parsed = urlparse.urlparse(redirect) 119 | 120 | redirect_host = redirect_parsed.netloc 121 | if not redirect_host: 122 | redirect_host = "%s:%d" % (hostname, port) 123 | 124 | redirect_path = redirect_parsed.path 125 | if redirect_parsed.query: 126 | redirect_path += "?" + redirect_parsed.query 127 | 128 | munged_path = url_for(".proxy_request", 129 | proto=proto, 130 | host=redirect_host, 131 | file=redirect_path[1:]) 132 | 133 | url = "%s://%s%s" % (parsed.scheme, parsed.netloc, munged_path) 134 | response_headers["location"] = url 135 | 136 | # Rewrite URLs in the content to point to our URL schemt.method == " instead. 137 | # Ugly, but seems to mostly work. 138 | root = url_for(".proxy_request", proto=proto, host=host) 139 | contents = resp.read() 140 | 141 | # Restructing Contents. 142 | if "content-type" in d.keys(): 143 | if d["content-type"].find("application/json") >= 0: 144 | # JSON format conentens will be modified here. 145 | jc = json.loads(contents) 146 | if jc.has_key("nodes"): 147 | del jc["nodes"] 148 | contents = json.dumps(jc) 149 | 150 | else: 151 | # Generic HTTP. 152 | pass 153 | 154 | # only valid for python2 / cuz, python3's string & byte handling is diffrent from python2 155 | #for regex in REGEXES: 156 | # contents = regex.sub(r'\1%s' % root, contents) 157 | else: 158 | # set default content-type, for error handling 159 | d['content-type'] = 'text/html; charset=utf-8' 160 | 161 | # Remove transfer-encoding: chunked header. cuz proxy does not use chunk trnasfer. 162 | if 'transfer-encoding' in d: 163 | if d['transfer-encoding'].lower() == 'chunked': 164 | del(d['transfer-encoding']) 165 | d['content-length'] = len(contents) 166 | 167 | flask_response = Response(response=contents, 168 | status=resp.status, 169 | headers=d) 170 | return flask_response 171 | 172 | 173 | app.register_blueprint(proxy) 174 | app.run(debug=DEBUG_FLAG, host='0.0.0.0', port=LISTEN_PORT) 175 | --------------------------------------------------------------------------------