最近有需求需要提供一个简单的 web 服务器,用于客户端上报一些内容,采用 post 方式上报并支持 gzip 压缩,代码如下:
#!python
#-*- coding:utf-8 -*-
'''
上报数据用 gzip 压缩了,所以用 http 服务器接收数据并输出接收到的数据
'''
import SimpleHTTPServer
import SocketServer
import StringIO, gzip
import urllib, time
import socket
import logging
import os
PORT = 10006
from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger('report')
logger.setLevel(logging.DEBUG)
## 只保存过去7天的日志,按天创建新的日志
handler = TimedRotatingFileHandler('log/timed.log',
when = 'midnight',
interval=1,
backupCount=7)
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(fmt=fmt)
logger.addHandler(handler)
class myHttpServer(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
logStr = ('\n%s %s %s\r\n%s' % (self.command, self.path, self.request_version, str(self.headers)))
logger.warn('%s\r\n' % logStr)
self.send_response(200)
self.end_headers()
def do_POST(self):
'''接收上报的文件,参数为:
imei=xxx&startTime=timestamp&urlNum=1&url=xxx&thisTime=now
使用 gzip 上报,接收到上报文件后,按照 imei 创建文件夹;
相同的 startTime 表示是同一次请求的数据;
urlNum 表示是该次请求的第几次跳转,每次跳转会是一个新的文件;
url表示本次url;
thisTime 表示本次请求的开始时间;
最终保存结果如下:
+-- urlNum_thisTime.gzip
|-- startTime ——|-- urlNum_thisTime.gzip
| +-- readme.txt 每个文件上传ip,开始时间,对应的 url
imei ——-|
ip: xxx
startTime: xxx
url: xxx
------
'''
global logger
clientIp = self.client_address
# 解析参数
uri = self.path
try:
tmpUrlParam = uri.split('?')[1]
tmpUrlParam = tmpUrlParam.split('&')
urlParam = {'imei': '', 'url': '', 'startTime': '', 'thisTime': '', 'urlNum': '', 'offer':''}
for kv in tmpUrlParam:
tmpArr = kv.split('=')
urlParam[tmpArr[0]] = tmpArr[1]
for key in urlParam:
if not urlParam[key]:
logger.error("\nclientIp = %s, uri = %s, is wrong" % (clientIp, uri))
self.errorResponse('parameter %s is empty' % key)
except Exception, e:
logger.error("uri = %s, analyse error" % uri)
self.errorResponse('parameter error')
return
# 判断文件是否已存在,已存在则添加到后面,否则新建文件
filePath = os.path.join(urlParam['imei'], urlParam['startTime'], urlParam['urlNum'] + '_' + urlParam['thisTime'] + '.zip')
# 文件已经存在,则添加到文件末尾
if os.path.isfile(filePath):
fp = open(filePath, 'ab')
else:
## 文件不存在表示这个该文件第一个数据包,新建文件并添加记录到 readme.txt
dirName = os.path.dirname(filePath)
if not os.path.isdir(dirName):
os.makedirs(dirName)
fp = open(filePath, 'ab+')
readmePath = os.path.join(dirName, 'readme.txt')
if os.path.isfile(readmePath):
tmpfp = open(readmePath, 'a')
else:
tmpfp = open(readmePath, 'a+')
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
tmpContent = 'ip: %s\nstartTime: %s\nurl: %s\noffer: %s\n\n' % (str(clientIp), now, urllib.unquote(urlParam['url']), urllib.unquote(urlParam['offer']))
tmpfp.write(tmpContent)
tmpfp.close()
# 获取本次上报数据
length = int(self.headers.getheader('content-length'))
qs = self.rfile.read(length)
compressMethod = self.headers.get('Accept-Encoding', "")
if compressMethod == 'gzip':
try:
compressedData = StringIO.StringIO(qs)
gziper = gzip.GzipFile(fileobj=compressedData)
content = gziper.read()
fp.write(content)
fp.close()
except Exception, e:
self.errorResponse('uncompress data failed')
logStr = '\nuncompress gzip failed[clientIp: %s]: %s\n' % (clientIp, str(e))
logStr += 'data is:%s' % qs
logger.error(logStr)
return
finally:
if not fp.closed:
fp.close()
else:
fp.write(qs)
fp.close()
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.finish()
def errorResponse(self, msg):
self.send_response(400)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(msg)
self.finish()
def main():
global PORT
socket.setdefaulttimeout(45)
handler = myHttpServer
httpd = SocketServer.TCPServer(("", PORT), handler)
httpd.serve_forever()
if __name__ == '__main__':
main()
经实际使用,对不高的并发处理完全可以应付。这种自定义的上报工作,要是用其他语言来写不知道可以怎么实现