Skip to content

Commit 66ced6e

Browse files
committed
feat: change code for supporting python 3
1 parent 274a8f1 commit 66ced6e

16 files changed

+125
-97
lines changed

demo/example.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

demo/site/index.html

Lines changed: 0 additions & 50 deletions
This file was deleted.

example.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python
2+
3+
import sys
4+
sys.dont_write_bytecode = True
5+
sys.path.insert(0, '..')
6+
7+
import litefs
8+
port = 8080
9+
if len(sys.argv) > 1:
10+
port = int(sys.argv[1])
11+
litefs = litefs.Litefs(
12+
address='0.0.0.0:%s' % port, webroot='./site', debug=True
13+
)
14+
litefs.run(timeout=2.)

litefs.py

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
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
910
Email: leafcoder@gmail.com
1011
1112
Copyright (c) 2017, Leafcoder.
@@ -19,16 +20,12 @@
1920
import logging
2021
import re
2122
import sys
22-
import _socket as socket
2323
from collections import deque, Iterable
24-
from Cookie import SimpleCookie
25-
from cStringIO import StringIO
2624
from errno import ENOTCONN, EMFILE, EWOULDBLOCK, EAGAIN, EPIPE
2725
from functools import partial
2826
from greenlet import greenlet, getcurrent, GreenletExit
2927
from gzip import GzipFile
3028
from hashlib import sha1
31-
from httplib import responses as http_status_codes
3229
from imp import find_module, load_module, new_module as imp_new_module
3330
from mako import exceptions
3431
from mako.lookup import TemplateLookup
@@ -45,15 +42,32 @@
4542
from subprocess import Popen, PIPE
4643
from tempfile import NamedTemporaryFile, TemporaryFile
4744
from time import time, strftime, gmtime
48-
from urllib import splitport, unquote_plus
49-
from UserDict import UserDict
5045
from uuid import uuid4
5146
from watchdog.events import *
5247
from watchdog.observers import Observer
5348
from weakref import proxy as weakref_proxy
5449
from zlib import compress as zlib_compress
5550
from 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+
5771
default_404 = '404'
5872
default_port = 9090
5973
default_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:

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ greenlet==0.4.13
33
Mako==1.0.6
44
MarkupSafe==1.0
55
pathtools==0.1.2
6-
pkg-resources==0.0.0
76
PyYAML==3.12
87
watchdog==0.8.3

site/database/index.html.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def handler(self):
2+
return 'ok'
File renamed without changes.

site/environ.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def handler(self):
2+
return self.environ
File renamed without changes.
File renamed without changes.
File renamed without changes.

site/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello world
File renamed without changes.
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
def handler(self):
22
files = self.files
3+
yield '<pre>'
34
for fobj in files.values():
4-
yield fobj.read()
5+
yield fobj.read()
6+
yield '</pre>'
File renamed without changes.
1.31 MB
Binary file not shown.

0 commit comments

Comments
 (0)