def run(self): # Check certificate, the certificate will be used to warp https connections CertUtil.check_ca() # Start a stream server, wait for incoming http requests server = gevent.server.StreamServer((self.listen_ip, self.listen_port), self.paasproxy_handler) self.logger.info("proxy_client listen on: %s:%d" % (self.listen_ip, self.listen_port) ) server.serve_forever() return
def paasproxy_handler(self, sock, address): http = self.http_handler # Get user request url and parse it rfile = sock.makefile('rb', __bufsize__) try: method, path, version, headers = http.parse_request(rfile) except (EOFError, socket.error) as e: if e[0] in ('empty line', 10053, errno.EPIPE): return rfile.close() raise # Change user-agent in header, if you need to fake browser #headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.99 Safari/537.36' remote_addr, remote_port = address # Socket and rfilt for CONNECT request __realsock = None __realrfile = None # For CONNECT request, establish the connection. if method == 'CONNECT': host, _, port = path.rpartition(':') port = int(port) logging.info('%s:%s "CONNECT %s:%d HTTP/1.1" - -' % (address[0], address[1], host, port)) # Get certificate from target host keyfile, certfile = CertUtil.get_cert(host) # Tell the user browser, the connection is established. sock.sendall('HTTP/1.1 200 OK\r\n\r\n') # The response will be send by the socket that sent the CONNECT connect __realsock = sock __realrfile = rfile try: # Wrap the connection with target host certificate sock = ssl.wrap_socket(__realsock, certfile=certfile, keyfile=keyfile, server_side=True) except Exception as e: logging.exception('ssl.wrap_socket(__realsock=%r) failed: %s', __realsock, e) __realrfile.close() __realsock.close() return # Get user browser's next request rfile = sock.makefile('rb', __bufsize__) try: method, path, version, headers = http.parse_request(rfile) except (EOFError, socket.error) as e: if e[0] in ('empty line', 10053, errno.EPIPE): return rfile.close() raise # If the request uses CONNECT, it is a https request proto = "https" else: proto = "http" host = headers.get('Host', '') # If path is not complete, compose one. if path[0] == '/' and host: path = proto + '://%s%s' % (host, path) try: try: # Wrap the user browser's request into a POST request, send the POST request to proxy_server # Get the proxy_server's response, unwarp the content. content_length = int(headers.get('Content-Length', 0)) payload = rfile.read(content_length) if content_length else '' response = self.paas_urlfetch(method, path, headers, payload, cfg.PROXY_SERVER) logging.info('%s:%s "%s %s HTTP/1.1" %s -', remote_addr, remote_port, method, path, response.status) #except socket.error as e: # TODO: why ignore some exception? These exception will send the response back to user browser #if e.reason[0] not in (11004, 10051, 10060, 'timed out', 10054): #raise except Exception as e: logging.exception('socket error: %s', e) raise # Bad request, stop doing crlf injection if response.app_status in (400, 405): http.crlf = 0 # Send the result back to user browser wfile = sock.makefile('wb', 0) # Format Set-Cookie field, TODO: why doing this? if 'Set-Cookie' in response.msg: response.msg['Set-Cookie'] = re.sub(', ([^ =]+(?:=|$))', '\\r\\nSet-Cookie: \\1', response.msg['Set-Cookie']) # Write the response head to socket. Do not set transfer-encoding, TODO: why doing this? wfile.write('HTTP/1.1 %s\r\n%s\r\n' % (response.status, ''.join('%s: %s\r\n' % (k.title(), v) for k, v in response.getheaders() if k != 'transfer-encoding'))) # Write the response payload to socket while 1: data = response.read(8192) if not data: break wfile.write(data) response.close() except socket.error as e: # Connection closed before proxy return if e[0] not in (10053, errno.EPIPE): raise finally: rfile.close() sock.close() if __realrfile: __realrfile.close() if __realsock: __realsock.close() return