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

写了一个Web Gateway做Proxy》有31个想法

    1. Xiaoxia 文章作者

      国内的设备和服务都不行啊,直接从国外交涉然后获取,服务还好一些,代理那些风险太大

      回复
  1. 元谷

    虾哥!能不能把我的在线QQtea加密解密的也放到你的实验室里啊!方便分析linuxQQ或者macqq协议啊

    回复
  2. Divinitus

    你有没有什么较好的关于json和python的教程,或者python的dict直接就是json格式的数据。如果不麻烦的话你大概给你讲一下吧,谢谢!

    回复
    1. Xiaoxia 文章作者

      我觉得学习配置nginx的时间会比我用python写这么一个小程序的时间要长哦!!!起码要一个晚上才能摸索会nginx,但我写个转发小程序不到1小时就好了。

      回复
  3. Pingback引用通告: Python写简单的网络服务器示例 « Xiaoxia[PG]

  4. Pingback引用通告: Thought this was cool: Python写简单的网络服务器示例 « CWYAlpha

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据