写了一个Web Gateway做Proxy « Xiaoxia[PG]
写了一个Web Gateway做Proxy
因为EMSVPS的服务器实在有太多的问题,故现在改回比较稳定的burst.net机器。现在Paypal支持Unionpay的卡,5.95USD/mo大概人民币38元。burst.net的机器提供512M内存和2个IP地址,内存充足的时候跑起Wordpress的确轻松很多了。现在一个IP用作博客服务,另外一个IP用作提供一些Web服务。
因为不同的Web服务都需要监听一个服务端口,为了能让他们共用一个IP上的80端口,需要一个代理分发请求的程序。例如
访问http://lab.xiaoxia.org/server/*的请求分发到localhost:10000的服务,
访问http://lab.xiaoxia.org/whois/*的请求分发到localhost:10001的服务,
而访问http://lab.xiaoxia.org/*的请求,直接获取www目录下的资源文件,例如index.html。因为使用的都是同一个域名,不同的只是路径,要根据不同的路径选择不同的服务端口,我使用了正则表达式来解决这个问题。
效果见 http://lab.xiaoxia.org/
我现在的分发规则如下:# Host Request Path Handle Method Forward Host or Root Forward Path lab.xiaoxia.org /server/(.*) proxy localhost:10000 /$1 lab.xiaoxia.org /mail/(.*).action proxy localhost:10005 /$1.action lab.xiaoxia.org /mail/(.*) resource /var/mail /mail/$1 lab.xiaoxia.org /hashlib/(.*).action proxy localhost:10002 / lab.xiaoxia.org /whois/request/(.*) proxy localhost:10003 /$1 lab.xiaoxia.org /iplookup/request/(.*) proxy localhost:10004 /$1 lab.xiaoxia.org /(.*) resource www /$1今晚写的WebGateway.py的代码如下。可以优化效率的地方很多,但对目前来说,已经足够。本来写了一个epoll版本的,但是代码太复杂太多了,就抛弃了,不利于阅读和维护。对于Python代码来说,应该坚持KISS (Keep It Simple & Stupid) 原则。
规则文件可以经常修改,而不需要重启WebGateway。
- #!/usr/bin/python
- from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
- from httplib import HTTPResponse
- from SocketServer import ThreadingMixIn
- import socket, threading
- import posixpath, shutil, mimetypes, urlparse
- import time, sys, re, traceback, os
- threading.stack_size(128*1024)
- ConnectionTimeout = 30.0
- ServiceConfigFile = "services.list"
- class Handler(BaseHTTPRequestHandler):
- def proxy(self, proxy_host, proxy_port, url):
- try:
- self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.s.settimeout(ConnectionTimeout)
- self.s.connect((proxy_host, proxy_port))
- header = " ".join((self.command, url, "HTTP/1.1")) + "\r\n"
- header += str(self.headers) + "\r\n"
- self.s.send(header)
- # Send Post data
- if(self.command=='POST'):
- self.s.send(self.rfile.read(int(self.headers['Content-Length'])))
- response = HTTPResponse(self.s, method=self.command)
- response.begin()
- # Reply to the browser
- status = "HTTP/1.1 " + str(response.status) + " " + response.reason
- header = status + '\r\n'
- for hh, vv in response.getheaders():
- if hh.upper()!='TRANSFER-ENCODING':
- header += hh + ': ' + vv + '\r\n'
- self.wfile.write(header + '\r\n')
- while True:
- response_data = response.read(8192)
- if not response_data: break
- self.wfile.write(response_data)
- self.s.close()
- except:
- print('error in ' + self.requestline + '\n' + traceback.format_exc())
- def getResource(self, www_root, path):
- path = path.split("?")[0].split("#")[0]
- path = posixpath.normpath(path).strip("/")
- fullpath = os.path.join(www_root, path)
- # Default page
- if os.path.isdir(fullpath):
- fullpath = os.path.join(fullpath, "index.html")
- mtype = mimetypes.guess_type(fullpath)[0]
- if mtype is None: mtype = "text/plain"
- if os.path.isfile(fullpath):
- f = open(fullpath, "rb")
- self.send_response(200)
- self.send_header("Content-Type", mtype + "; charset=\"utf-8\"")
- fs = os.fstat(f.fileno())
- self.send_header("Content-Length", str(fs[6]))
- self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
- self.end_headers()
- shutil.copyfileobj(f, self.wfile)
- else:
- self.send_error(404, "File not found %s" % path)
- def getService(self):
- hostname = self.headers["host"].split(":")[0].lower()
- self.headers["X-Forwarded-For"] = self.connection.getpeername()[0]
- for line in file(ServiceConfigFile).readlines():
- var = line.split()
- if var[0].lower() == hostname:
- r = re.match(var[1], self.path)
- if r:
- i = 1
- for k in r.groups():
- var[4] = var[4].replace("$" + str(i), k)
- i += 1
- if var[2] == "proxy":
- ip, port = var[3].split(":")
- self.proxy(ip, 80 if port == "" else int(port), var[4])
- elif var[2] == "resource":
- self.getResource(var[3], var[4])
- else:
- self.send_error(500, "Unknown method")
- return
- self.send_error(400, "Bad Request")
- do_POST = getService
- do_GET = getService
- class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): pass
- try:
- server = ThreadingHTTPServer(("", 8000), Handler)
- server.serve_forever()
- except KeyboardInterrupt:
- exit()