Total Pageviews

Wednesday, 22 January 2020

http proxy server程序tinyhttpproxy.py

tinyhttpproxy.py使用方法:
1)很好用,下载然后在后台运行。只依赖于基本的python modules,运行的时候不需要root权限。
  1. proxy [-p port] [-l logfile] [-dh] [allowed_client_name ...]]
  2.  
  3. -p - Port to bind to
  4. -l - Path to logfile. If not specified, STDOUT is used
  5. -d - Run in the background

TinyHTTPProxy.py的源代码:

  1. #!/usr/bin/python
  2.  
  3. __doc__ = """Tiny HTTP Proxy.
  4.  
  5. This module implements GET, HEAD, POST, PUT and DELETE methods
  6. on BaseHTTPServer, and behaves as an HTTP proxy. The CONNECT
  7. method is also implemented experimentally, but has not been
  8. tested yet.
  9.  
  10. Any help will be greatly appreciated. SUZUKI Hisao
  11.  
  12. 2009/11/23 - Modified by Mitko Haralanov
  13. * Added very simple FTP file retrieval
  14. * Added custom logging methods
  15. * Added code to make this a standalone application
  16. """
  17.  
  18. __version__ = "0.3.1"
  19.  
  20. import BaseHTTPServer, select, socket, SocketServer, urlparse
  21. import logging
  22. import logging.handlers
  23. import getopt
  24. import sys
  25. import os
  26. import signal
  27. import threading
  28. from types import FrameType, CodeType
  29. from time import sleep
  30. import ftplib
  31.  
  32. DEFAULT_LOG_FILENAME = "proxy.log"
  33.  
  34. class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler):
  35. __base = BaseHTTPServer.BaseHTTPRequestHandler
  36. __base_handle = __base.handle
  37.  
  38. server_version = "TinyHTTPProxy/" + __version__
  39. rbufsize = 0 # self.rfile Be unbuffered
  40.  
  41. def handle(self):
  42. (ip, port) = self.client_address
  43. self.server.logger.log (logging.INFO, "Request from '%s'", ip)
  44. if hasattr(self, 'allowed_clients') and ip not in self.allowed_clients:
  45. self.raw_requestline = self.rfile.readline()
  46. if self.parse_request(): self.send_error(403)
  47. else:
  48. self.__base_handle()
  49.  
  50. def _connect_to(self, netloc, soc):
  51. i = netloc.find(':')
  52. if i >= 0:
  53. host_port = netloc[:i], int(netloc[i+1:])
  54. else:
  55. host_port = netloc, 80
  56. self.server.logger.log (logging.INFO, "connect to %s:%d", host_port[0], host_port[1])
  57. try: soc.connect(host_port)
  58. except socket.error, arg:
  59. try: msg = arg[1]
  60. except: msg = arg
  61. self.send_error(404, msg)
  62. return 0
  63. return 1
  64.  
  65. def do_CONNECT(self):
  66. soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  67. try:
  68. if self._connect_to(self.path, soc):
  69. self.log_request(200)
  70. self.wfile.write(self.protocol_version +
  71. " 200 Connection established\r\n")
  72. self.wfile.write("Proxy-agent: %s\r\n" % self.version_string())
  73. self.wfile.write("\r\n")
  74. self._read_write(soc, 300)
  75. finally:
  76. soc.close()
  77. self.connection.close()
  78.  
  79. def do_GET(self):
  80. (scm, netloc, path, params, query, fragment) = urlparse.urlparse(
  81. self.path, 'http')
  82. if scm not in ('http', 'ftp') or fragment or not netloc:
  83. self.send_error(400, "bad url %s" % self.path)
  84. return
  85. soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  86. try:
  87. if scm == 'http':
  88. if self._connect_to(netloc, soc):
  89. self.log_request()
  90. soc.send("%s %s %s\r\n" % (self.command,
  91. urlparse.urlunparse(('', '', path,
  92. params, query,
  93. '')),
  94. self.request_version))
  95. self.headers['Connection'] = 'close'
  96. del self.headers['Proxy-Connection']
  97. for key_val in self.headers.items():
  98. soc.send("%s: %s\r\n" % key_val)
  99. soc.send("\r\n")
  100. self._read_write(soc)
  101. elif scm == 'ftp':
  102. # fish out user and password information
  103. i = netloc.find ('@')
  104. if i >= 0:
  105. login_info, netloc = netloc[:i], netloc[i+1:]
  106. try: user, passwd = login_info.split (':', 1)
  107. except ValueError: user, passwd = "anonymous", None
  108. else: user, passwd ="anonymous", None
  109. self.log_request ()
  110. try:
  111. ftp = ftplib.FTP (netloc)
  112. ftp.login (user, passwd)
  113. if self.command == "GET":
  114. ftp.retrbinary ("RETR %s"%path, self.connection.send)
  115. ftp.quit ()
  116. except Exception, e:
  117. self.server.logger.log (logging.WARNING, "FTP Exception: %s",
  118. e)
  119. finally:
  120. soc.close()
  121. self.connection.close()
  122.  
  123. def _read_write(self, soc, max_idling=20, local=False):
  124. iw = [self.connection, soc]
  125. local_data = ""
  126. ow = []
  127. count = 0
  128. while 1:
  129. count += 1
  130. (ins, _, exs) = select.select(iw, ow, iw, 1)
  131. if exs: break
  132. if ins:
  133. for i in ins:
  134. if i is soc: out = self.connection
  135. else: out = soc
  136. data = i.recv(8192)
  137. if data:
  138. if local: local_data += data
  139. else: out.send(data)
  140. count = 0
  141. if count == max_idling: break
  142. if local: return local_data
  143. return None
  144.  
  145. do_HEAD = do_GET
  146. do_POST = do_GET
  147. do_PUT = do_GET
  148. do_DELETE=do_GET
  149.  
  150. def log_message (self, format, *args):
  151. self.server.logger.log (logging.INFO, "%s %s", self.address_string (),
  152. format % args)
  153.  
  154. def log_error (self, format, *args):
  155. self.server.logger.log (logging.ERROR, "%s %s", self.address_string (),
  156. format % args)
  157.  
  158. class ThreadingHTTPServer (SocketServer.ThreadingMixIn,
  159. BaseHTTPServer.HTTPServer):
  160. def __init__ (self, server_address, RequestHandlerClass, logger=None):
  161. BaseHTTPServer.HTTPServer.__init__ (self, server_address,
  162. RequestHandlerClass)
  163. self.logger = logger
  164.  
  165. def logSetup (filename, log_size, daemon):
  166. logger = logging.getLogger ("TinyHTTPProxy")
  167. logger.setLevel (logging.INFO)
  168. if not filename:
  169. if not daemon:
  170. # display to the screen
  171. handler = logging.StreamHandler ()
  172. else:
  173. handler = logging.handlers.RotatingFileHandler (DEFAULT_LOG_FILENAME,
  174. maxBytes=(log_size*(1<<20)),
  175. backupCount=5)
  176. else:
  177. handler = logging.handlers.RotatingFileHandler (filename,
  178. maxBytes=(log_size*(1<<20)),
  179. backupCount=5)
  180. fmt = logging.Formatter ("[%(asctime)-12s.%(msecs)03d] "
  181. "%(levelname)-8s {%(name)s %(threadName)s}"
  182. " %(message)s",
  183. "%Y-%m-%d %H:%M:%S")
  184. handler.setFormatter (fmt)
  185.  
  186. logger.addHandler (handler)
  187. return logger
  188.  
  189. def usage (msg=None):
  190. if msg: print msg
  191. print sys.argv[0], "[-p port] [-l logfile] [-dh] [allowed_client_name ...]]"
  192. print
  193. print " -p - Port to bind to"
  194. print " -l - Path to logfile. If not specified, STDOUT is used"
  195. print " -d - Run in the background"
  196. print
  197.  
  198. def handler (signo, frame):
  199. while frame and isinstance (frame, FrameType):
  200. if frame.f_code and isinstance (frame.f_code, CodeType):
  201. if "run_event" in frame.f_code.co_varnames:
  202. frame.f_locals["run_event"].set ()
  203. return
  204. frame = frame.f_back
  205.  
  206. def daemonize (logger):
  207. class DevNull (object):
  208. def __init__ (self): self.fd = os.open ("/dev/null", os.O_WRONLY)
  209. def write (self, *args, **kwargs): return 0
  210. def read (self, *args, **kwargs): return 0
  211. def fileno (self): return self.fd
  212. def close (self): os.close (self.fd)
  213. class ErrorLog:
  214. def __init__ (self, obj): self.obj = obj
  215. def write (self, string): self.obj.log (logging.ERROR, string)
  216. def read (self, *args, **kwargs): return 0
  217. def close (self): pass
  218.  
  219. if os.fork () != 0:
  220. ## allow the child pid to instanciate the server
  221. ## class
  222. sleep (1)
  223. sys.exit (0)
  224. os.setsid ()
  225. fd = os.open ('/dev/null', os.O_RDONLY)
  226. if fd != 0:
  227. os.dup2 (fd, 0)
  228. os.close (fd)
  229. null = DevNull ()
  230. log = ErrorLog (logger)
  231. sys.stdout = null
  232. sys.stderr = log
  233. sys.stdin = null
  234. fd = os.open ('/dev/null', os.O_WRONLY)
  235. #if fd != 1: os.dup2 (fd, 1)
  236. os.dup2 (sys.stdout.fileno (), 1)
  237. if fd != 2: os.dup2 (fd, 2)
  238. if fd not in (1, 2): os.close (fd)
  239.  
  240. def main ():
  241. logfile = None
  242. daemon = False
  243. max_log_size = 20
  244. port = 8000
  245. allowed = []
  246. run_event = threading.Event ()
  247. local_hostname = socket.gethostname ()
  248.  
  249. try: opts, args = getopt.getopt (sys.argv[1:], "l:dhp:", [])
  250. except getopt.GetoptError, e:
  251. usage (str (e))
  252. return 1
  253.  
  254. for opt, value in opts:
  255. if opt == "-p": port = int (value)
  256. if opt == "-l": logfile = value
  257. if opt == "-d": daemon = not daemon
  258. if opt == "-h":
  259. usage ()
  260. return 0
  261.  
  262. # setup the log file
  263. logger = logSetup (logfile, max_log_size, daemon)
  264.  
  265. if daemon:
  266. daemonize (logger)
  267. signal.signal (signal.SIGINT, handler)
  268.  
  269. if args:
  270. allowed = []
  271. for name in args:
  272. client = socket.gethostbyname(name)
  273. allowed.append(client)
  274. logger.log (logging.INFO, "Accept: %s (%s)" % (client, name))
  275. ProxyHandler.allowed_clients = allowed
  276. else:
  277. logger.log (logging.INFO, "Any clients will be served...")
  278.  
  279. server_address = (socket.gethostbyname (local_hostname), port)
  280. ProxyHandler.protocol = "HTTP/1.0"
  281. httpd = ThreadingHTTPServer (server_address, ProxyHandler, logger)
  282. sa = httpd.socket.getsockname ()
  283. print "Servering HTTP on", sa[0], "port", sa[1]
  284. req_count = 0
  285. while not run_event.isSet ():
  286. try:
  287. httpd.handle_request ()
  288. req_count += 1
  289. if req_count == 1000:
  290. logger.log (logging.INFO, "Number of active threads: %s",
  291. threading.activeCount ())
  292. req_count = 0
  293. except select.error, e:
  294. if e[0] == 4 and run_event.isSet (): pass
  295. else:
  296. logger.log (logging.CRITICAL, "Errno: %d - %s", e[0], e[1])
  297. logger.log (logging.INFO, "Server shutdown")
  298. return 0
  299.  
  300. if __name__ == '__main__':
  301. sys.exit (main ())
  302. ---------------------------
以下都支持 http/https proxy,
trafficserver :http://trafficserver.apache.org/

mitmproxy mitmproxy.org/

还有跨平台且有GUI的 Charles proxy: 'Charles Web Debugging Proxy • HTTP Monitor / HTTP Proxy / HTTPS & SSL Proxy / Reverse Proxy', https://www.charlesproxy.com/
Charles 实在是太棒了!但还是不如fiddler方便,前者还是更侧重抓包而不是代理,还是 fiddler 更强大些。如果有条件还是在 windows 上开 fiddler 的代理吧。

如果就想用 nginx 做正向代理,可以参考:
https://github.com/chobits/ngx_http_proxy_connect_module

HTTP代理存在两种。
一种是基于RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing以及其他相关RFC文档中描述的普通代理,代理服务器充当的是一个中间的组件,通过在请求URI设置的对应authority来指定目标服务器。

另一种是基于RFC 2817 - Upgrading to TLS Within HTTP/1.1的5.2 Requesting a Tunnel with CONNECT(draft-luotonen-web-proxy-tunneling-01原草案)实现的TCP隧道代理。与上面的区别就在于“TCP隧道”,这种代理可以以HTTP协议的方式来实现理论上任意TCP之上的应用层协议代理,两端之间的通讯都是在HTTP的body部分完成的。因此就可以完成基于TLS的HTTPS通讯,被加密过的数据直接在body中传输。

Nginx不支持第二种代理,因此不能完成https代理。Nginx的作者对于这个功能有明确的回复(forum.nginx.org/read.ph),表示不会在近期实现,并且推荐使用Squid: "Not in near future: there is alreay good forward proxy Squid."