1
1
#!/usr/bin/env python
2
2
#-*- coding: utf-8 -*-
3
3
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.
7
8
8
- Name : leafcoder
9
+ Author : leafcoder
9
10
Email: leafcoder@gmail.com
10
11
11
12
Copyright (c) 2017, Leafcoder.
19
20
import logging
20
21
import re
21
22
import sys
22
- import _socket as socket
23
23
from collections import deque , Iterable
24
- from Cookie import SimpleCookie
25
- from cStringIO import StringIO
26
24
from errno import ENOTCONN , EMFILE , EWOULDBLOCK , EAGAIN , EPIPE
27
25
from functools import partial
28
26
from greenlet import greenlet , getcurrent , GreenletExit
29
27
from gzip import GzipFile
30
28
from hashlib import sha1
31
- from httplib import responses as http_status_codes
32
29
from imp import find_module , load_module , new_module as imp_new_module
33
30
from mako import exceptions
34
31
from mako .lookup import TemplateLookup
45
42
from subprocess import Popen , PIPE
46
43
from tempfile import NamedTemporaryFile , TemporaryFile
47
44
from time import time , strftime , gmtime
48
- from urllib import splitport , unquote_plus
49
- from UserDict import UserDict
50
45
from uuid import uuid4
51
46
from watchdog .events import *
52
47
from watchdog .observers import Observer
53
48
from weakref import proxy as weakref_proxy
54
49
from zlib import compress as zlib_compress
55
50
from io import RawIOBase , BufferedRWPair , DEFAULT_BUFFER_SIZE
56
51
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
+
57
71
default_404 = '404'
58
72
default_port = 9090
59
73
default_host = 'localhost'
@@ -178,6 +192,7 @@ def make_environ(app, rw, address):
178
192
environ ['SERVER_NAME' ] = server_name
179
193
environ ['SERVER_PORT' ] = int (app .server_info ['port' ])
180
194
s = rw .readline (DEFAULT_BUFFER_SIZE )
195
+ if PY3 : s = s .decode ('utf-8' )
181
196
if not s :
182
197
# 注意:读出来为空字符串时,代表着服务器在等待读
183
198
raise HttpError ('invalid http headers' )
@@ -186,6 +201,7 @@ def make_environ(app, rw, address):
186
201
path_info , query_string = path_info .split ('?' )
187
202
else :
188
203
path_info , query_string = path_info , ''
204
+ path_info = unquote_plus (path_info )
189
205
base_uri , script_name = path_info .split ('/' , 1 )
190
206
if '' == script_name :
191
207
script_name = app .config .default_page
@@ -198,6 +214,7 @@ def make_environ(app, rw, address):
198
214
environ ['SCRIPT_NAME' ] = script_name
199
215
environ ['PATH_INFO' ] = path_info
200
216
s = rw .readline (DEFAULT_BUFFER_SIZE )
217
+ if PY3 : s = s .decode ('utf-8' )
201
218
while True :
202
219
if s in EOFS :
203
220
break
@@ -208,27 +225,31 @@ def make_environ(app, rw, address):
208
225
continue
209
226
environ ['HTTP_%s' % k ] = v
210
227
s = rw .readline (DEFAULT_BUFFER_SIZE )
228
+ if PY3 : s = s .decode ('utf-8' )
211
229
size = environ .pop ('HTTP_CONTENT_LENGTH' , None )
212
230
if not size :
213
231
return environ
214
232
size = int (size )
215
233
content_type = environ .get ('HTTP_CONTENT_TYPE' , '' )
216
234
if content_type .startswith ('multipart/form-data' ):
217
- boundary = content_type .split ('=' )[1 ]
235
+ boundary = content_type .split ('=' )[1 ]. strip ()
218
236
begin_boundary = ('--%s' % boundary )
219
237
end_boundary = ('--%s--' % boundary )
220
238
files = {}
221
239
s = rw .readline (DEFAULT_BUFFER_SIZE ).strip ()
240
+ if PY3 : s = s .decode ('utf-8' )
222
241
while True :
223
242
if s .strip () != begin_boundary :
224
243
assert s .strip () == end_boundary
225
244
break
226
245
headers = {}
227
246
s = rw .readline (DEFAULT_BUFFER_SIZE ).strip ()
247
+ if PY3 : s = s .decode ('utf-8' )
228
248
while s :
229
249
k , v = s .split (':' , 1 )
230
250
headers [k .strip ().upper ()] = v .strip ()
231
251
s = rw .readline (DEFAULT_BUFFER_SIZE ).strip ()
252
+ if PY3 : s = s .decode ('utf-8' )
232
253
disposition = headers ['CONTENT-DISPOSITION' ]
233
254
h , m , t = disposition .split (';' )
234
255
name = m .split ('=' )[1 ].strip ()
@@ -237,15 +258,20 @@ def make_environ(app, rw, address):
237
258
else :
238
259
fp = TemporaryFile (mode = 'w+b' )
239
260
s = rw .readline (DEFAULT_BUFFER_SIZE )
261
+ if PY3 : s = s .decode ('utf-8' )
240
262
while s .strip () != begin_boundary \
241
263
and s .strip () != end_boundary :
242
- fp .write (s )
264
+ fp .write (s . encode ( 'utf-8' ) )
243
265
s = rw .readline (DEFAULT_BUFFER_SIZE )
266
+ if PY3 : s = s .decode ('utf-8' )
244
267
fp .seek (0 )
245
268
files [name [1 :- 1 ]] = fp
246
269
environ [FILES_HEADER_NAME ] = files
247
270
else :
248
271
environ ['POST_CONTENT' ] = rw .read (int (size ))
272
+ if PY3 :
273
+ environ ['POST_CONTENT' ] \
274
+ = environ ['POST_CONTENT' ].decode ('utf-8' )
249
275
environ ['CONTENT_LENGTH' ] = len (environ ['POST_CONTENT' ])
250
276
return environ
251
277
@@ -370,7 +396,7 @@ def __init__(self, path, base, name, text):
370
396
self .zlib_text = zlib_text = zlib_compress (text , 9 )[2 :- 4 ]
371
397
self .zlib_etag = sha1 (zlib_text ).hexdigest ()
372
398
stream = StringIO ()
373
- with GzipFile (fileobj = stream , mode = "w " ) as f :
399
+ with GzipFile (fileobj = stream , mode = "wb " ) as f :
374
400
f .write (text )
375
401
self .gzip_text = gzip_text = stream .getvalue ()
376
402
self .gzip_etag = sha1 (gzip_text ).hexdigest ()
@@ -576,7 +602,11 @@ def _new_session_id(self):
576
602
sessions = app .sessions
577
603
while 1 :
578
604
token = '%s%s' % (urandom (24 ), time ())
605
+ if PY3 :
606
+ token = token .encode ('utf-8' )
579
607
session_id = sha1 (token ).hexdigest ()
608
+ if PY3 :
609
+ session_id = session_id .encode ('utf-8' )
580
610
session = sessions .get (session_id )
581
611
if session is None :
582
612
break
@@ -592,7 +622,7 @@ def address(self):
592
622
593
623
@property
594
624
def files (self ):
595
- return self ._files
625
+ return self ._files or {}
596
626
597
627
@property
598
628
def environ (self ):
@@ -655,21 +685,43 @@ def start_response(self, status_code=200, headers=None):
655
685
response_headers ['Content-Type' ] = 'text/html;charset=utf-8'
656
686
status_code = int (status_code )
657
687
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 = []
661
693
if headers is not None :
662
694
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 )
667
713
if self .session_id is None :
668
714
cookie = SimpleCookie ()
669
715
cookie [default_litefs_sid ] = self .session .id
670
716
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 ' )
673
725
self ._headers_responsed = True
674
726
675
727
def redirect (self , url = None ):
@@ -693,18 +745,35 @@ def _finish(self, content):
693
745
if not self ._headers_responsed :
694
746
self .start_response (200 )
695
747
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' ))
706
765
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 ))
708
777
try :
709
778
rw .close ()
710
779
except :
0 commit comments