写了一个Web Gateway做Proxy

写了一个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。

  1. #!/usr/bin/python  
  2.   
  3. from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer  
  4. from httplib import HTTPResponse  
  5. from SocketServer import ThreadingMixIn  
  6. import socket, threading  
  7. import posixpath, shutil, mimetypes, urlparse  
  8. import time, sys, re, traceback, os  
  9.   
  10. threading.stack_size(128*1024)  
  11.   
  12. ConnectionTimeout = 30.0  
  13. ServiceConfigFile = "services.list"  
  14.   
  15. class Handler(BaseHTTPRequestHandler):  
  16.     def proxy(self, proxy_host, proxy_port, url):  
  17.         try:  
  18.             self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  19.             self.s.settimeout(ConnectionTimeout)  
  20.             self.s.connect((proxy_host, proxy_port))  
  21.             header = " ".join((self.command, url, "HTTP/1.1")) + "\r\n"  
  22.             header += str(self.headers) + "\r\n"  
  23.             self.s.send(header)  
  24.             # Send Post data  
  25.             if(self.command=='POST'):  
  26.                 self.s.send(self.rfile.read(int(self.headers['Content-Length'])))  
  27.             response = HTTPResponse(self.s, method=self.command)  
  28.             response.begin()  
  29.   
  30.             # Reply to the browser  
  31.             status = "HTTP/1.1 " + str(response.status) + " " + response.reason  
  32.             header = status + '\r\n'  
  33.             for hh, vv in response.getheaders():  
  34.                 if hh.upper()!='TRANSFER-ENCODING':  
  35.                     header += hh + ': ' + vv + '\r\n'  
  36.             self.wfile.write(header + '\r\n')  
  37.             while True:  
  38.                 response_data = response.read(8192)  
  39.                 if not response_data: break  
  40.                 self.wfile.write(response_data)  
  41.             self.s.close()  
  42.         except:  
  43.             print('error in ' + self.requestline + '\n' + traceback.format_exc())  
  44.   
  45.     def getResource(self, www_root, path):  
  46.         path = path.split("?")[0].split("#")[0]  
  47.         path = posixpath.normpath(path).strip("/")  
  48.         fullpath = os.path.join(www_root, path)  
  49.         # Default page  
  50.         if os.path.isdir(fullpath):  
  51.             fullpath = os.path.join(fullpath, "index.html")  
  52.         mtype = mimetypes.guess_type(fullpath)[0]  
  53.         if mtype is None: mtype = "text/plain"  
  54.         if os.path.isfile(fullpath):  
  55.             f = open(fullpath, "rb")  
  56.             self.send_response(200)  
  57.             self.send_header("Content-Type", mtype + "; charset=\"utf-8\"")  
  58.             fs = os.fstat(f.fileno())  
  59.             self.send_header("Content-Length", str(fs[6]))  
  60.             self.send_header("Last-Modified"self.date_time_string(fs.st_mtime))  
  61.             self.end_headers()  
  62.             shutil.copyfileobj(f, self.wfile)  
  63.         else:  
  64.             self.send_error(404"File not found %s" % path)  
  65.   
  66.     def getService(self):  
  67.         hostname = self.headers["host"].split(":")[0].lower()  
  68.         self.headers["X-Forwarded-For"] = self.connection.getpeername()[0]  
  69.         for line in file(ServiceConfigFile).readlines():  
  70.             var = line.split()  
  71.             if var[0].lower() == hostname:  
  72.                 r = re.match(var[1], self.path)  
  73.                 if r:  
  74.                     i = 1  
  75.                     for k in r.groups():  
  76.                         var[4] = var[4].replace("$" + str(i), k)  
  77.                         i += 1  
  78.                     if var[2] == "proxy":  
  79.                         ip, port = var[3].split(":")  
  80.                         self.proxy(ip, 80 if port == "" else int(port), var[4])  
  81.                     elif var[2] == "resource":  
  82.                         self.getResource(var[3], var[4])  
  83.                     else:  
  84.                         self.send_error(500"Unknown method")  
  85.                     return  
  86.         self.send_error(400"Bad Request")  
  87.   
  88.     do_POST = getService  
  89.     do_GET = getService  
  90.   
  91. class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): pass  
  92.   
  93. try:  
  94.     server = ThreadingHTTPServer(("", 8000), Handler)  
  95.     server.serve_forever()  
  96. except KeyboardInterrupt:  
  97.     exit()  

你可能感兴趣的:(proxy)