11#!/usr/bin/env python
22#-*- coding: utf-8 -*-
33
4- '''使用 Python 从零开始构建一个 Web 服务器框架。 开发 Litefs 的是为了实现一个能快速、安\
5- 全、灵活的构建 Web 项目的服务器框架。 Litefs 是一个高性能的 HTTP 服务器。Litefs 具有高\
6- 稳定性、丰富的功能、系统消耗低的特点。
4+ '''Build a web server framework using Python. Litefs was developed to imple\
5+ ment a server framework that can quickly, securely, and flexibly build Web \
6+ projects. Litefs is a high-performance HTTP server. Litefs has the characte\
7+ ristics of high stability, rich functions, and low system consumption.
78
8- Name : leafcoder
9+ Author : leafcoder
910Email: leafcoder@gmail.com
1011
1112Copyright (c) 2017, Leafcoder.
1920import logging
2021import re
2122import sys
22- import _socket as socket
2323from collections import deque , Iterable
24- from Cookie import SimpleCookie
25- from cStringIO import StringIO
2624from errno import ENOTCONN , EMFILE , EWOULDBLOCK , EAGAIN , EPIPE
2725from functools import partial
2826from greenlet import greenlet , getcurrent , GreenletExit
2927from gzip import GzipFile
3028from hashlib import sha1
31- from httplib import responses as http_status_codes
3229from imp import find_module , load_module , new_module as imp_new_module
3330from mako import exceptions
3431from mako .lookup import TemplateLookup
4542from subprocess import Popen , PIPE
4643from tempfile import NamedTemporaryFile , TemporaryFile
4744from time import time , strftime , gmtime
48- from urllib import splitport , unquote_plus
49- from UserDict import UserDict
5045from uuid import uuid4
5146from watchdog .events import *
5247from watchdog .observers import Observer
5348from weakref import proxy as weakref_proxy
5449from zlib import compress as zlib_compress
5550from io import RawIOBase , BufferedRWPair , DEFAULT_BUFFER_SIZE
5651
52+ PY3 = sys .version_info .major > 2
53+
54+ if PY3 :
55+ # Import modules in py3
56+ import socket
57+ from http .client import responses as http_status_codes
58+ from http .cookies import SimpleCookie
59+ from io import BytesIO as StringIO
60+ from urllib .parse import splitport , unquote_plus
61+ from collections import UserDict
62+ else :
63+ # Import modules in py2
64+ import _socket as socket
65+ from Cookie import SimpleCookie
66+ from cStringIO import StringIO
67+ from httplib import responses as http_status_codes
68+ from urllib import splitport , unquote_plus
69+ from UserDict import UserDict
70+
5771default_404 = '404'
5872default_port = 9090
5973default_host = 'localhost'
@@ -178,6 +192,7 @@ def make_environ(app, rw, address):
178192 environ ['SERVER_NAME' ] = server_name
179193 environ ['SERVER_PORT' ] = int (app .server_info ['port' ])
180194 s = rw .readline (DEFAULT_BUFFER_SIZE )
195+ if PY3 : s = s .decode ('utf-8' )
181196 if not s :
182197 # 注意:读出来为空字符串时,代表着服务器在等待读
183198 raise HttpError ('invalid http headers' )
@@ -186,6 +201,7 @@ def make_environ(app, rw, address):
186201 path_info , query_string = path_info .split ('?' )
187202 else :
188203 path_info , query_string = path_info , ''
204+ path_info = unquote_plus (path_info )
189205 base_uri , script_name = path_info .split ('/' , 1 )
190206 if '' == script_name :
191207 script_name = app .config .default_page
@@ -198,6 +214,7 @@ def make_environ(app, rw, address):
198214 environ ['SCRIPT_NAME' ] = script_name
199215 environ ['PATH_INFO' ] = path_info
200216 s = rw .readline (DEFAULT_BUFFER_SIZE )
217+ if PY3 : s = s .decode ('utf-8' )
201218 while True :
202219 if s in EOFS :
203220 break
@@ -208,27 +225,31 @@ def make_environ(app, rw, address):
208225 continue
209226 environ ['HTTP_%s' % k ] = v
210227 s = rw .readline (DEFAULT_BUFFER_SIZE )
228+ if PY3 : s = s .decode ('utf-8' )
211229 size = environ .pop ('HTTP_CONTENT_LENGTH' , None )
212230 if not size :
213231 return environ
214232 size = int (size )
215233 content_type = environ .get ('HTTP_CONTENT_TYPE' , '' )
216234 if content_type .startswith ('multipart/form-data' ):
217- boundary = content_type .split ('=' )[1 ]
235+ boundary = content_type .split ('=' )[1 ]. strip ()
218236 begin_boundary = ('--%s' % boundary )
219237 end_boundary = ('--%s--' % boundary )
220238 files = {}
221239 s = rw .readline (DEFAULT_BUFFER_SIZE ).strip ()
240+ if PY3 : s = s .decode ('utf-8' )
222241 while True :
223242 if s .strip () != begin_boundary :
224243 assert s .strip () == end_boundary
225244 break
226245 headers = {}
227246 s = rw .readline (DEFAULT_BUFFER_SIZE ).strip ()
247+ if PY3 : s = s .decode ('utf-8' )
228248 while s :
229249 k , v = s .split (':' , 1 )
230250 headers [k .strip ().upper ()] = v .strip ()
231251 s = rw .readline (DEFAULT_BUFFER_SIZE ).strip ()
252+ if PY3 : s = s .decode ('utf-8' )
232253 disposition = headers ['CONTENT-DISPOSITION' ]
233254 h , m , t = disposition .split (';' )
234255 name = m .split ('=' )[1 ].strip ()
@@ -237,15 +258,20 @@ def make_environ(app, rw, address):
237258 else :
238259 fp = TemporaryFile (mode = 'w+b' )
239260 s = rw .readline (DEFAULT_BUFFER_SIZE )
261+ if PY3 : s = s .decode ('utf-8' )
240262 while s .strip () != begin_boundary \
241263 and s .strip () != end_boundary :
242- fp .write (s )
264+ fp .write (s . encode ( 'utf-8' ) )
243265 s = rw .readline (DEFAULT_BUFFER_SIZE )
266+ if PY3 : s = s .decode ('utf-8' )
244267 fp .seek (0 )
245268 files [name [1 :- 1 ]] = fp
246269 environ [FILES_HEADER_NAME ] = files
247270 else :
248271 environ ['POST_CONTENT' ] = rw .read (int (size ))
272+ if PY3 :
273+ environ ['POST_CONTENT' ] \
274+ = environ ['POST_CONTENT' ].decode ('utf-8' )
249275 environ ['CONTENT_LENGTH' ] = len (environ ['POST_CONTENT' ])
250276 return environ
251277
@@ -370,7 +396,7 @@ def __init__(self, path, base, name, text):
370396 self .zlib_text = zlib_text = zlib_compress (text , 9 )[2 :- 4 ]
371397 self .zlib_etag = sha1 (zlib_text ).hexdigest ()
372398 stream = StringIO ()
373- with GzipFile (fileobj = stream , mode = "w " ) as f :
399+ with GzipFile (fileobj = stream , mode = "wb " ) as f :
374400 f .write (text )
375401 self .gzip_text = gzip_text = stream .getvalue ()
376402 self .gzip_etag = sha1 (gzip_text ).hexdigest ()
@@ -576,7 +602,11 @@ def _new_session_id(self):
576602 sessions = app .sessions
577603 while 1 :
578604 token = '%s%s' % (urandom (24 ), time ())
605+ if PY3 :
606+ token = token .encode ('utf-8' )
579607 session_id = sha1 (token ).hexdigest ()
608+ if PY3 :
609+ session_id = session_id .encode ('utf-8' )
580610 session = sessions .get (session_id )
581611 if session is None :
582612 break
@@ -592,7 +622,7 @@ def address(self):
592622
593623 @property
594624 def files (self ):
595- return self ._files
625+ return self ._files or {}
596626
597627 @property
598628 def environ (self ):
@@ -655,21 +685,43 @@ def start_response(self, status_code=200, headers=None):
655685 response_headers ['Content-Type' ] = 'text/html;charset=utf-8'
656686 status_code = int (status_code )
657687 status_text = http_status_codes [status_code ]
658- buffers .write ('HTTP/1.1 %d %s\r \n ' % (status_code , status_text ))
659- for name , text in response_headers .items ():
660- buffers .write ('%s: %s\r \n ' % (name , text ))
688+ line = 'HTTP/1.1 %d %s\r \n ' % (status_code , status_text )
689+ if PY3 :
690+ line = line .encode ('utf-8' )
691+ buffers .write (line )
692+ header_names = []
661693 if headers is not None :
662694 for header in headers :
663- if isinstance (header , basestring ):
664- buffers .write (header )
665- else :
666- buffers .write ('%s: %s\r \n ' % header )
695+ if not isinstance (header , (list , tuple )):
696+ if PY3 :
697+ header = header .encode ('utf-8' )
698+ k , v = header .split (':' )
699+ k , v = k .strip (), v .strip ()
700+ header = (k , v )
701+ header_names .append (header [0 ])
702+ line = '%s: %s\r \n ' % header
703+ if PY3 :
704+ line = line .encode ('utf-8' )
705+ buffers .write (line )
706+ for name , text in response_headers .items ():
707+ if name in header_names :
708+ continue
709+ line = '%s: %s\r \n ' % (name , text )
710+ if PY3 :
711+ line = line .encode ('utf-8' )
712+ buffers .write (line )
667713 if self .session_id is None :
668714 cookie = SimpleCookie ()
669715 cookie [default_litefs_sid ] = self .session .id
670716 cookie [default_litefs_sid ]['path' ] = '/'
671- buffers .write ('%s\r \n ' % cookie .output ())
672- buffers .write ('\r \n ' )
717+ line = '%s\r \n ' % cookie .output ()
718+ if PY3 :
719+ line = line .encode ('utf-8' )
720+ buffers .write (line )
721+ if PY3 :
722+ buffers .write (b'\r \n ' )
723+ else :
724+ buffers .write ('\r \n ' )
673725 self ._headers_responsed = True
674726
675727 def redirect (self , url = None ):
@@ -693,18 +745,35 @@ def _finish(self, content):
693745 if not self ._headers_responsed :
694746 self .start_response (200 )
695747 rw .write (self ._buffers .getvalue ())
696- if isinstance (content , basestring ):
697- rw .write (content )
698- elif isinstance (content , dict ):
699- rw .write (repr (content ))
700- elif isinstance (content , Iterable ):
701- for s in content :
702- if isinstance (s , basestring ):
703- rw .write (s )
704- else :
705- rw .write (repr (s ))
748+ if PY3 :
749+ if isinstance (content , str ):
750+ rw .write (content .encode ('utf-8' ))
751+ elif isinstance (content , bytes ):
752+ rw .write (content )
753+ elif isinstance (content , dict ):
754+ rw .write (str (content ).encode ('utf-8' ))
755+ elif isinstance (content , Iterable ):
756+ for s in content :
757+ if isinstance (s , str ):
758+ rw .write (s .encode ('utf-8' ))
759+ elif isinstance (s , bytes ):
760+ rw .write (s )
761+ else :
762+ rw .write (str (s ).encode ('utf-8' ))
763+ else :
764+ rw .write (str (content ).encode ('utf-8' ))
706765 else :
707- rw .write (repr (content ))
766+ if isinstance (content , basestring ):
767+ rw .write (content )
768+ elif isinstance (content , unicode ):
769+ rw .write (content .encode ('utf-8' ))
770+ elif isinstance (content , dict ):
771+ rw .write (str (content ))
772+ elif isinstance (content , Iterable ):
773+ for s in content :
774+ rw .write (str (s ))
775+ else :
776+ rw .write (str (content ))
708777 try :
709778 rw .close ()
710779 except :
0 commit comments