Python开发Http代理服务器 - socketref,再见!高德 - C++博客
之前开发酒店广告投放系统编写的Http代理服务程序,功能实现广告插播进Html DOM结构内。一般都是DIV被插入,当然包括script都是可以从数据库中动态获得。
简单修改之后当做Http代理服务器程序,在浏览器中设置Http转发程序的Ip即可,只要代理程序的机器能上网,客户机便能上网(其中涉及Page gzip的工作有点麻烦)1 # -*- coding:utf-8 -*-
2 # http代理服务器
3 # 1.ip限制,mac限制
4 #
5 # socketref@hotmail.com
6 # www.sw2us.com
7
8 "exec" "python" "-O" "$0" "$@"
9
10 __doc__ = """sw2us HTTP Proxy.
11
12 """
13
14 __version__ = "0.2.1"
15
16 import BaseHTTPServer, select, socket, SocketServer, urlparse
17 import httplib,traceback,re
18 import os,sys,re,mimetools,zlib,StringIO,gzip,time,StringIO
19
20
21 class ConfigProperty:
22 def __init__(self,owner):
23 self.key=''
24 self.value=''
25
26 def create(self,text):
27 #text - key=value
28 #@return: boolean
29 pos = text.find('#')
30 if(pos !=-1):
31 text = text[:pos]
32 pair = text.split('=')
33 if len(pair) !=2:
34 #print "Property Line Invalid:%s"%(text)
35 return False
36 k = pair[0].strip()
37 v = pair[1].strip()
38 self.key = k
39 self.value = v
40
41 return True
42
43 def toString(self):
44 s =''
45 try:
46 s = "%s=%s"%(self.key,self.value)
47 except:
48 return ''
49 return s
50
51 def toInt(self):
52 r=0
53 try:
54 r = int(self.value)
55 except:
56 r =0
57 return r
58
59 def toFloat(self):
60 r=0.0
61 try:
62 r = float(self.value)
63 except:
64 r=0.0
65 return r
66
67
68 #@def SimpleConfig
69 # 简单配置信息文件,基本格式 : key=value
70 class SimpleConfig:
71 def __init__(self):
72 self._file=''
73 self._props=[]
74 self._strip = True
75
76 def open(self,file,strip=True):
77 #打开配置文件
78 #@param strip - 是否裁剪不可见首尾两端的字符
79 try:
80 self._strip = strip
81 self._props=[]
82 fh = open(file,'r')
83 lines = fh.readlines()
84 for text in lines:
85 prop = ConfigProperty(self)
86 if prop.create(text) == False:
87 prop = None
88 else:
89 self._props.append(prop)
90 fh.close()
91 except:
92 return False
93 return True
94
95 def toString(self):
96 s=''
97 for p in self._props:
98 s = s + p.toString() +"\n"
99 return s
100
101 def saveAs(self,file):
102 #保存配置信息到文件
103 try:
104 fh = open(file,'w')
105 fh.write(toString())
106 fh.close()
107 except:
108 print "write File Failed!"
109 return False
110 return True
111
112 def getProperty(self,name):
113 #取属性值
114 prop=None
115 try:
116 for p in self._props:
117 if p.key == name:
118 prop = p
119 break
120 except:
121 pass
122
123 return prop
124
125 def getPropertyValue(self,key,default=''):
126 prop = self.getProperty(key)
127 if not prop:
128 return default
129 return prop.value
130
131 def getPropertyValueAsInt(self,name,default=0):
132 prop = self.getPropertyValue(name)
133
134 if not prop:
135 return default
136 r=default
137 try:
138 r = int(prop)
139 except:pass
140 return r
141
142 def getPropertyValueAsFloat(self,name,default=0.0):
143 prop = self.getPropertyValue(name)
144 if not prop:
145 return default
146 r = default
147 try:
148 r = float(r)
149 except:pass
150 return r
151
152
153 #===========================================#
154
155
156 #===========================================#
157
158 def getMacList():
159 maclist=[]
160 f = os.popen('arp -a','r')
161 while True:
162 line = f.readline()
163 if not line:
164 break
165 line = line.strip()
166 rst = re.match('^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+([0-9a-fA-F]{1,2}\-[0-9a-fA-F]{1,2}\-[0-9a-fA-F]{1,2}\-[0-9a-fA-F]{1,2}\-[0-9a-fA-F]{1,2}\-[0-9a-fA-F]{1,2}).*',line)
167 #rst = re.match('^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',line)
168 if rst:
169 #print rst.groups()
170 maclist.append(rst.groups())
171 #print maclist
172 return maclist
173
174
175
176 ##########################################
177 confile = SimpleConfig()
178 confile.open('proxy.conf')
179 dbconn = None
180
181 ##########################################
182 #初始化系统配置
183 def initConfiguration():
184 r = True
185
186 return r
187
188 ##########################################
189
190 class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler):
191 __base = BaseHTTPServer.BaseHTTPRequestHandler
192 __base_handle = __base.handle
193 server_version = "TinyHTTPProxy/" + __version__
194 rbufsize = 0 # self.rfile Be unbuffered
195
196
197 #######################################################33
198
199 #handle()是在单独线程中执行
200 def handle(self): # 调用入口,线程刚进入,携带socket进入
201 print 'client incoming'
202 #self.__base_handle()
203 #return
204 (ip, port) = self.client_address
205 if hasattr(self, 'allowed_clients') and ip not in self.allowed_clients:
206 self.raw_requestline = self.rfile.readline()
207 if self.parse_request():
208 self.send_error(403)
209 else:
210 self.__base_handle()
211
212 def _connect_to(self, netloc, soc):
213 i = netloc.find(':')
214 if i >= 0:
215 host_port = netloc[:i], int(netloc[i+1:])
216 else:
217 host_port = netloc, 80
218 #print "\t" "connect to %s:%d" % host_port
219 try: soc.connect(host_port)
220 except socket.error, arg:
221 try: msg = arg[1]
222 except: msg = arg
223 self.send_error(404, msg)
224 return 0
225 return 1
226
227 def do_CONNECT(self):
228 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
229 try:
230 if self._connect_to(self.path, soc):
231 self.log_request(200)
232 self.wfile.write(self.protocol_version +
233 " 200 Connection established\r\n")
234 self.wfile.write("Proxy-agent: %s\r\n" % self.version_string())
235 self.wfile.write("\r\n")
236 self._read_write(soc, 300)
237 finally:
238 print "\t" "bye"
239 soc.close()
240 self.connection.close()
241
242
243 def do_GET(self):
244 (scm, netloc, path, params, query, fragment) = urlparse.urlparse(
245 self.path, 'http')
246 piars = (scm, netloc, path, params, query, fragment)
247 if not netloc:
248 netloc = self.headers.get('Host', "")
249 #print ">>requester:",self.connection.getpeername(),"path:",self.path
250 #print '>>2. ',(scm, netloc, path, params, query, fragment)
251 #print 'next host:',netloc
252 if scm != 'http' or fragment or not netloc:
253 self.send_error(400, "bad url %s" % self.path)
254 return
255 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
256 try:
257 if self._connect_to(netloc, soc):
258 self.log_request()
259 soc.send("%s %s %s\r\n" % (
260 self.command,
261 urlparse.urlunparse(('', '', path, params, query, '')),
262 self.request_version))
263 self.headers['Connection'] = 'close'
264 del self.headers['Proxy-Connection']
265 for key_val in self.headers.items():
266 soc.send("%s: %s\r\n" % key_val)
267 soc.send("\r\n")
268 #到此完成发送请求和头部信息
269 self._read_write(soc)
270 finally:
271 print "\t" "bye"
272 soc.close()
273 self.connection.close()
274
275
276
277 def insertTags(self,tag,body,insert):
278 p1 = body.find('<%s'%tag)
279 if p1!=-1 :
280 p2 = body.find('>',p1)
281 if p2!=-1:
282 part1 = body[:p2+1]
283 part2 = body[p2+1:]
284 print '*-'*20
285 body = part1 + insert + part2
286 return body
287
288 # google页面的数据请求时,返回的数据进行的是gzip压缩,所以过滤文本存在问题,先要解压缩之后才可以
289 # 插入数据之后要重新计算 content-length 并返回给客户浏览器
290 # 发现压缩的有很多 , content-encoding:gzip
291
292 # 处理 'transfer-encoding': 'chunked'类型
293 #gzip 有两种存储,一种是直接gzip压缩的数据跟在header之后;另外一种是采用chunck块存储
294 #在这里将gzip数据全部解压,还原成原始数据传出到客户端
295 def sendBackResponse(self,command,headers,body):
296
297 insert='<h1>This is Test </h1>'
298 if headers.has_key('content-encoding') and headers['content-encoding'].strip().lower()=='gzip':
299 try:
300 del headers['content-encoding']
301 gzipdata=''
302 if headers.has_key('transfer-encoding') and headers['transfer-encoding']=='chunked':
303 del headers['transfer-encoding']
304
305 pos = 0
306 while pos < len(body):
307 p = body.find('\x0d\x0a',pos)
308 sizewidth = p-pos
309
310 chuncksize = int(body[pos:p],16)
311 #print 'chunck size:',body[pos:p]
312 p +=2
313 gzipdata+=body[p:p+chuncksize]
314 pos= p+chuncksize+2
315 if chuncksize ==0 :
316 break
317 #
318 body = gzipdata
319
320 #
321
322 #ss = zlib.decompress(gzipdata)
323 compressedstream = StringIO.StringIO(body)
324 gzipper = gzip.GzipFile(fileobj=compressedstream)
325 if gzipper == None:
326 print '*'*200
327 body = gzipper.read()
328 #f = open('body%s.txt'%time.time(),'wb')
329 #f.write(body)
330 #f.close()
331
332
333 #body = gzipdata
334 except:
335 print traceback.print_exc()
336 print 'decompress failed!'
337 #pos = body.find('\x0d\x0a')
338 #pos = body.find('\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff')
339 #if pos!=-1:
340 # body = body[pos+9:]
341 #
342 #compressedstream = StringIO.StringIO(body)
343 #gzipper = gzip.GzipFile(fileobj=compressedstream)
344 #if gzipper == None:
345 # print '*'*200
346 #body = gzipper.read()
347
348 #body = zlib.decompressobj().decompress('x\x9c'+body)
349
350 #m = re.search('(<body.*>)',body,re.I)
351 #if m:
352 # pos = m.start(0)
353 # part1 = body[:pos+len(m.group(0))]
354 # part2 = body[pos+len(m.group(0)):]
355 # body = part1 + insert + part2
356 # print '-*'*20,insert,'-*'*20
357
358 #self.insertTags('body',body,insert)
359
360 css=""" <style>
361 #kk{
362 border:1px dotted red;
363 width:200px;
364 height:300px;
365 float:left;
366 background:#0x00ff00;
367 }
368 </style>
369 """
370 #body =self.insertTags('head',body,css)
371
372 #body =self.insertTags('body',body,insert)
373 div="""
374 <div id="kk">
375 This is Test DIV Block!!
376 </div>
377 """
378
379 #read external html tags
380 try:
381 #ff = open('head.tag','r')
382 #div = ff.read()
383 #ff.close()
384 #body =self.insertTags('head',body,div)
385 body = self.publish_advertisement(body) #插入配置的广告信息
386 except:
387 pass
388
389 #p1 = body.find('<body')
390 #if p1!=-1 :
391 # p2 = body.find('>',p1)
392 # if p2!=-1:
393 # part1 = body[:p2+1]
394 # part2 = body[p2+1:]
395 # print '*-'*20
396 # body = part1 + insert + part2
397 #print m.group(0)
398 headers['Content-Length'] = str(len(body))
399
400 #if headers.has_key('content-length'):
401
402 self.connection.send(command)
403 self.connection.send('\r\n')
404 for k,v in headers.items():
405 self.connection.send("%s: %s\r\n"%(k,v))
406 self.connection.send("\r\n")
407 self.connection.sendall(body)
408
409
410
411 #----------------------------------------------------
412
413 def _read_write(self, soc, max_idling=20):
414 #getMacList()
415 iw = [self.connection, soc] # self.connnection - 内网主机连接,soc - 向外连接
416 ow = []
417 count = 0
418 #respfile = soc.makefile('rb', 1024)
419 httpCommand=''
420 httpBody=''
421 httpHeaders={}
422 isOkPageResponse=False
423 nextReadBytes=0
424 datacnt=0
425 NoContentLength = False
426 #print self.connection.getpeername()
427 while 1:
428 count += 1
429 datacnt+=1
430 (ins, _, exs) = select.select(iw, ow, iw, 3)
431 if exs:
432 print 'error occr!'
433 break #异常产生
434 if ins:
435 for i in ins:
436 if i is soc:
437 out = self.connection
438 else:
439 out = soc
440
441 data = i.recv(8192)
442 if data:
443 out.send(data)
444 count = 0
445 else:
446 if not isOkPageResponse:
447 return
448 else:
449 pass #print "\t" "idle", count
450 if count == max_idling:
451 print 'idling exit'
452 break # 指定时间内都接收不到双向数据便退出循环 20*3 = 60 secs
453
454
455 do_HEAD = do_GET
456 do_POST = do_GET
457 do_PUT = do_GET
458 do_DELETE=do_GET
459
460 class ThreadingHTTPServer (SocketServer.ThreadingMixIn,
461 BaseHTTPServer.HTTPServer): pass
462
463
464
465
466 def serving(HandlerClass,
467 ServerClass, protocol="HTTP/1.0"):
468
469 if len(sys.argv) <2 or sys.argv[1]!='www.sw2us.com':
470 sys.exit()
471
472 if sys.argv[2:]:
473 port = int(sys.argv[2])
474 else:
475
476 port = confile.getPropertyValueAsInt('httpport',8000)
477
478 #port = 8000
479
480 server_address = ('', port)
481
482 HandlerClass.protocol_version = protocol
483 httpd = ServerClass(server_address, HandlerClass)
484
485 sa = httpd.socket.getsockname()
486 print "www.sw2us.com@2010 v.1.0.0"
487 print "Serving HTTP on", sa[0], "port", sa[1], ""
488 sys.stdout = buff
489 sys.stderr = buff
490
491 httpd.serve_forever()
492
493
494
495 if __name__ == '__main__':
496 #getMacList()
497 from sys import argv
498
499 f = open('proxy.pid','w')
500 f.write(str(os.getpid()))
501 f.close()
502
503 #ProxyHandler.allowed_clients = []
504 try:
505 allowed = []
506 ss = confile.getPropertyValue('allowed_clients').strip()
507 hosts = ss.split(',')
508 for h in hosts:
509 if h:
510 client = socket.gethostbyname(h.strip())
511 allowed.append(client)
512 if len(allowed):
513 ProxyHandler.allowed_clients = allowed
514 buff = StringIO.StringIO()
515
516 serving(ProxyHandler, ThreadingHTTPServer)
517 except:
518 pass
519
520