PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/LMG/Tools/webframework.py

http://lh-abc.googlecode.com/
Python | 310 lines | 304 code | 6 blank | 0 comment | 0 complexity | 9b69879e63d44da43b70be9a3384e733 MD5 | raw file
  1. import re
  2. import StringIO
  3. import logging
  4. import socket
  5. from hashlib import md5
  6. import time
  7. import random
  8. import wsgiref
  9. import wsgiref.handlers
  10. import wsgiref.headers
  11. from wsgiref import simple_server
  12. from traceback import format_exc
  13. from httplib import responses as HTTP_STATUS_MAP
  14. from BitTornado.__init__ import product_name
  15. DEBUG = True
  16. class Request(object):
  17. """Request holder"""
  18. def __init__(self, environ):
  19. #http://ip:port/dir/file?param=1&another=2
  20. self.environ = environ
  21. self.host = environ.get('HTTP_HOST', '') # ip:port
  22. self.scheme = environ.get('wsgi.url_scheme', '') # http
  23. self.path = environ.get('PATH_INFO', '') # /dir/file
  24. self.query = environ.get('QUERY_STRING', '') # param=1&another=2
  25. self.method = environ.get('REQUEST_METHOD', '') # GET
  26. self.authorization = environ.get('HTTP_AUTHORIZATION')
  27. self.fullpath = environ.get('SCRIPT_NAME', '') + self.path
  28. def parameters(self):
  29. parameters = {}
  30. for param in self.query.split('&'):
  31. try:
  32. key, value = param.split("=")
  33. except ValueError:
  34. continue # bad parameter
  35. if key in parameters:
  36. if isinstance(parameters[key], list):
  37. parameters[key].append(value)
  38. else:
  39. parameters[key] = [parameters[key], value]
  40. else:
  41. parameters[key] = value
  42. return parameters
  43. class Response(object):
  44. """Response holder"""
  45. def __init__(self):
  46. self.out = StringIO.StringIO()
  47. self.set_status(200)
  48. self.headers = wsgiref.headers.Headers([])
  49. self.headers['Content-Type'] = "text/html; charset=utf-8"
  50. self.headers['Cache-Control'] = "no-cache"
  51. def set_status(self, code, message = None):
  52. if not message:
  53. message = HTTP_STATUS_MAP[code]
  54. self.status = (code, message)
  55. def clear(self):
  56. self.out.seek(0)
  57. self.out.truncate(0)
  58. def wsgi_write(self, start_response):
  59. body = self.out.getvalue()
  60. if isinstance(body, unicode):
  61. body = body.encode('utf-8')
  62. self.headers['Content-Length'] = str(len(body))
  63. start_response('%d %s' % self.status, self.headers.items())
  64. self.out.close()
  65. return body
  66. class RequestHandler(object):
  67. """Abstract Request Handler"""
  68. def initialize(self, request, response):
  69. self.request = request
  70. self.response = response
  71. def get(self, *args):
  72. """Handler method for GET requests."""
  73. self.error(405)
  74. def post(self, *args):
  75. """Handler method for POST requests."""
  76. self.error(405)
  77. def head(self, *args):
  78. """Handler method for HEAD requests."""
  79. self.error(405)
  80. def options(self, *args):
  81. """Handler method for OPTIONS requests."""
  82. self.error(405)
  83. def trace(self, *args):
  84. """Handler method for TRACE requests."""
  85. self.error(405)
  86. def error(self, code):
  87. """Clears the response output stream and sets the given HTTP error code."""
  88. self.response.set_status(code)
  89. self.response.clear()
  90. def handle_exception(self, exception):
  91. """Called if this handler throws an exception during execution."""
  92. self.error(500)
  93. logging.error(exception)
  94. if DEBUG:
  95. self.response.clear()
  96. self.response.headers['Content-Type'] = 'text/plain'
  97. self.response.out.write(exception)
  98. class WSGIApplication(object):
  99. """Web Application"""
  100. def __init__(self, mapping):
  101. self.mapping = []
  102. for regex, handler in mapping:
  103. self.mapping.append((re.compile(self.fix_regex(regex)), handler))
  104. # Authorization
  105. self.nonce = {}
  106. self.realm = product_name
  107. def fix_regex(self, regex):
  108. """ fix regular expressions """
  109. if not regex.startswith('^'):
  110. regex = '^' + regex
  111. if not regex.endswith('$'):
  112. regex += '$'
  113. return regex
  114. def bad_auth(self, response, start_response):
  115. """ Bad authorization """
  116. if utility.webconfig.Read("authorization") == "basic":
  117. response.headers['WWW-Authenticate'] = 'Basic realm=%s' % (self.realm)
  118. response.set_status(401)
  119. else:
  120. nonce = md5('%s:%s' % (time.time(), random.random())).hexdigest()
  121. opaque = md5('%s:%s' % (time.time(), random.random())).hexdigest()
  122. self.nonce[nonce] = None
  123. parts = {'realm':self.realm, 'qop':'auth', 'nonce':nonce, 'opaque':opaque}
  124. head = ', '.join(['%s="%s"' % (k, v) for (k, v) in parts.items()])
  125. response.headers['WWW-Authenticate'] = 'Digest %s' % head
  126. response.set_status(401)
  127. return response.wsgi_write(start_response)
  128. def bad_ip(self, response, start_response):
  129. """ Bad IP address """
  130. response.set_status(403)
  131. return response.wsgi_write(start_response)
  132. def auth(self, request):
  133. """ Check authorization """
  134. # Check if needed
  135. authorization = request.authorization
  136. conf_username = utility.webconfig.Read("username")
  137. conf_password = utility.webconfig.Read("password")
  138. if not conf_username:
  139. return True
  140. if authorization is None:
  141. return False
  142. if utility.webconfig.Read("authorization") == "basic":
  143. # Verify scheme
  144. authmeth, auth = authorization.split(' ', 1)
  145. if authmeth.lower() != 'basic':
  146. return False
  147. # Verify username and password
  148. auth = auth.strip().decode('base64')
  149. username, password = auth.split(':', 1)
  150. if username != conf_username or password != conf_password:
  151. return False
  152. return True
  153. else:
  154. # Verify scheme
  155. if not ' ' in authorization:
  156. return False
  157. authmeth, auth = authorization.split(' ', 1)
  158. if 'digest' != authmeth.lower():
  159. return False
  160. # Verify username and password
  161. amap = {}
  162. for itm in auth.split(','):
  163. k, v = [s.strip() for s in itm.split('=', 1)]
  164. amap[k] = v.replace('"', '')
  165. try:
  166. username = amap['username']
  167. authpath = amap['uri']
  168. nonce = amap['nonce']
  169. realm = amap['realm']
  170. response = amap['response']
  171. if not authpath.split('?', 1)[0] in request.fullpath:
  172. return False
  173. if realm != self.realm:
  174. return False
  175. qop = amap.get('qop', '')
  176. cnonce = amap.get('cnonce', '')
  177. nc = amap.get('nc', '00000000')
  178. if qop:
  179. if 'auth' != qop:
  180. return False
  181. if not (nonce and nc):
  182. return False
  183. except:
  184. return False
  185. ha1 = md5('%s:%s:%s' % (conf_username, self.realm, conf_password)).hexdigest()
  186. ha2 = md5('%s:%s' % (request.method, authpath)).hexdigest()
  187. if qop:
  188. chk = '%s:%s:%s:%s:%s:%s' % (ha1, nonce, nc, cnonce, qop, ha2)
  189. else:
  190. chk = '%s:%s:%s' % (ha1, nonce, ha2)
  191. if response != md5(chk).hexdigest():
  192. if nonce in self.nonce:
  193. del self.nonce[nonce]
  194. return False
  195. pnc = self.nonce.get(nonce, '00000000')
  196. if nc <= pnc:
  197. if nonce in self.nonce:
  198. del self.nonce[nonce]
  199. return False
  200. self.nonce[nonce] = nc
  201. return True
  202. def __call__(self, environ, start_response):
  203. """Handle incoming request"""
  204. # Create request and response objects
  205. request = Request(environ)
  206. response = Response()
  207. # Check scheme
  208. if request.scheme != 'http':
  209. response.set_status(404)
  210. return response.wsgi_write(start_response)
  211. # Check ip
  212. allowed = utility.webconfig.Read("allowed", "bencode-list")
  213. if allowed and environ.get('REMOTE_ADDR') not in allowed:
  214. return self.bad_ip(response, start_response)
  215. # Check authorization
  216. if not self.auth(request):
  217. return self.bad_auth(response, start_response)
  218. # Find the right handler
  219. handler = None
  220. groups = ()
  221. for regex, handler_class in self.mapping:
  222. match = regex.match(request.path)
  223. if match:
  224. handler = handler_class()
  225. handler.initialize(request, response)
  226. groups = match.groups()
  227. break
  228. # Handle the request
  229. if handler:
  230. methods = {'GET': handler.get, 'POST': handler.post, 'HEAD': handler.head,
  231. 'OPTIONS': handler.options, 'TRACE': handler.trace}
  232. try:
  233. func = methods.get(request.method)
  234. if func:
  235. func(*groups)
  236. else:
  237. handler.error(501)
  238. except Exception, e:
  239. handler.handle_exception(format_exc())
  240. else:
  241. response.set_status(404)
  242. return response.wsgi_write(start_response)
  243. class SimpleServer(simple_server.WSGIServer):
  244. def __init__(self, *args, **kwargs):
  245. simple_server.WSGIServer.__init__(self, *args, **kwargs)
  246. def DoStop(self):
  247. self.server_close()
  248. def DoStart(self):
  249. self.socket = socket.socket(self.address_family,
  250. self.socket_type)
  251. self.OnBind()
  252. self.OnActivate()
  253. def ChangeAddress(self, addr):
  254. self.server_address = addr
  255. def OnBind(self):
  256. simple_server.WSGIServer.server_bind(self)
  257. def server_bind(self):
  258. pass
  259. def OnActivate(self):
  260. simple_server.WSGIServer.server_activate(self)
  261. def server_activate(self):
  262. pass
  263. def app_get(mapping, host = '', port = 8080):
  264. return simple_server.make_server(host, port, WSGIApplication(mapping), SimpleServer)
  265. if __name__ == "__main__":
  266. app = app_get([('/', RequestHandler), ('/test', RequestHandler)])
  267. app.serve_forever()