Skip to content

Commit cf6bb5d

Browse files
committed
Basic support for serving static files (based on BasicHTTPServer)
1 parent 9272e9b commit cf6bb5d

File tree

4 files changed

+149
-5
lines changed

4 files changed

+149
-5
lines changed

src/engine.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,21 @@ def default_config(app_path):
132132
'log.access.format': '{date} {time} {request_line} {response_status} {response_length} {request_ip} {request_user}',
133133

134134
'sessions.time': 2 * 86400,
135-
'sessions.cookie_name': 'hlib_sid'
135+
'sessions.cookie_name': 'hlib_sid',
136+
137+
'static.enabled': False,
138+
'static.root': '/tmp'
136139
}
137140

138141
def get_handler(self, requested):
142+
def __static_fallback():
143+
if 'static.enabled' in self.config and self.config['static.enabled']:
144+
import hlib.handlers.static
145+
146+
return hlib.handlers.static.StaticHandler().generate
147+
148+
raise hlib.http.NotFound()
149+
139150
h = self.root
140151

141152
for token in requested.split('?')[0].split('/')[1:]:
@@ -146,11 +157,11 @@ def get_handler(self, requested):
146157
h = getattr(h, token)
147158
continue
148159

149-
raise hlib.http.NotFound()
160+
return __static_fallback()
150161

151162
if isinstance(h, hlib.handlers.GenericHandler):
152163
if not hasattr(h, 'index'):
153-
raise hlib.http.NotFound()
164+
return __static_fallback()
154165

155166
h = h.index
156167

@@ -430,6 +441,8 @@ def __init__(self):
430441
self.output = None
431442
self.raw_output = None
432443

444+
self.source_file = None
445+
433446
def __getattr__(self, name):
434447
if name == 'output':
435448
return self._output
@@ -480,6 +493,10 @@ def dumps(self):
480493
for name, cookie in self.cookies.iteritems():
481494
self.headers['Set-Cookie'] = '%s=%s; Max-Age=%s; Path=%s' % (cookie.name, urllib.quote(cookie.value), cookie.max_age, cookie.path)
482495

496+
if self.source_file:
497+
self.output = self.source_file.read()
498+
self.source_file.close()
499+
483500
if self.output:
484501
if hasattr(req.server.config, 'compress') and req.server.config.compress == True:
485502
compressed = hlib.compress.compress(self.output)
@@ -504,7 +521,7 @@ def dumps(self):
504521
if self.output != None and req.method != 'head':
505522
lines.append(self.output)
506523

507-
return '\r\n'.join(lines) + '\r\n'
524+
return '\r\n'.join(lines)
508525

509526
class DataBaseGCTask(hlib.scheduler.Task):
510527
def __init__(self):

src/handlers/static.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import mimetypes
2+
import os
3+
import os.path
4+
import posixpath
5+
import urllib
6+
import sys
7+
8+
import hlib.handlers
9+
10+
import hruntime
11+
12+
class StaticIORegime(hlib.handlers.IORegime):
13+
@staticmethod
14+
def check_session():
15+
hlib.auth.check_session()
16+
17+
@staticmethod
18+
def run_handler():
19+
try:
20+
return hruntime.request.handler()
21+
22+
except hlib.http.Redirect, e:
23+
hruntime.db.doom()
24+
raise e
25+
26+
except Exception, e:
27+
hruntime.db.doom()
28+
29+
e = hlib.error.error_from_exception(e)
30+
hlib.log.log_error(e)
31+
32+
return ''
33+
34+
@staticmethod
35+
def redirect(url):
36+
res = hruntime.response
37+
38+
res.status = 301
39+
res.output = None
40+
res.headers['Location'] = url
41+
42+
if 'Content-Type' in res.headers:
43+
del res.headers['Content-Type']
44+
45+
static = lambda f: hlib.handlers.tag_fn(f, 'ioregime', StaticIORegime)
46+
47+
class StaticHandler(object):
48+
if not mimetypes.inited:
49+
mimetypes.init()
50+
51+
extensions_map = mimetypes.types_map.copy()
52+
extensions_map.update({
53+
'': 'application/octet-stream', # Default
54+
})
55+
56+
def __init__(self):
57+
super(StaticHandler, self).__init__()
58+
59+
def translate_path(self, path):
60+
path = path.split('?',1)[0]
61+
path = path.split('#',1)[0]
62+
path = posixpath.normpath(urllib.unquote(path))
63+
words = path.split('/')
64+
words = filter(None, words)
65+
66+
path = hruntime.app.config.get('static.root')
67+
68+
for word in words:
69+
drive, word = os.path.splitdrive(word)
70+
head, word = os.path.split(word)
71+
if word in (os.curdir, os.pardir): continue
72+
path = os.path.join(path, word)
73+
74+
return path
75+
76+
def guess_type(self, path):
77+
base, ext = posixpath.splitext(path)
78+
79+
if ext in self.extensions_map:
80+
return self.extensions_map[ext]
81+
82+
ext = ext.lower()
83+
if ext in self.extensions_map:
84+
return self.extensions_map[ext]
85+
else:
86+
return self.extensions_map['']
87+
88+
@static
89+
def generate(self, _version_stamp = None):
90+
req = hruntime.request
91+
res = hruntime.response
92+
93+
path = self.translate_path(req.requested)
94+
content_type = self.guess_type(path)
95+
96+
sys.stdout.flush()
97+
98+
try:
99+
res.source_file = open(path, 'rb')
100+
except IOError:
101+
res.status = 404
102+
res.output = None
103+
return
104+
105+
stat = os.fstat(res.source_file.fileno())
106+
107+
res.status = 200
108+
res.headers['Content-Type'] = content_type
109+
res.headers['Content-Length'] = stat[6]
110+
res.headers['Last-Modified'] = hlib.http.stamp_to_string(stat.st_mtime)
111+
112+
return None

src/http/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
__contact__ = '[email protected]'
44
__license__ = 'http://www.php-suit.com/dpl'
55

6+
import time
7+
68
import hlib.error
79

810
# pylint: disable-msg=F0401
@@ -46,6 +48,15 @@
4648
500: 'Internal Server Error'
4749
}
4850

51+
def stamp_to_string(stamp):
52+
weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
53+
monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
54+
55+
stamp = stamp or time.time()
56+
57+
year, month, day, hh, mm, ss, wd, y, z = time.gmtime(stamp)
58+
return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekdayname[wd], day, monthname[month], year, hh, mm, ss)
59+
4960
class HTTPError(hlib.error.BaseError):
5061
def __init__(self, *args, **kwargs):
5162
super(HTTPError, self).__init__(*args, **kwargs)

src/runners/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def get(self, section, option):
3636
raise ConfigParser.NoSectionError(section)
3737

3838
if option not in self.default[section]:
39-
raise ConfigParser.NoOptionError(section, option)
39+
raise ConfigParser.NoOptionError(option, section)
4040

4141
return self.default[section][option]
4242

@@ -121,6 +121,10 @@ def __get_channel(name):
121121
app.channels.add('transactions', transactions)
122122
app.channels.add('events', stderr, events)
123123

124+
if config.has_section('static'):
125+
app.config['static.enabled'] = True
126+
app.config['static.root'] = config.get('static', 'root')
127+
124128
if on_app_config:
125129
on_app_config(app, config)
126130

0 commit comments

Comments
 (0)