/
web.py
1580 lines (1213 loc) · 53 KB
/
web.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
#coding:utf8 # Author : tuxpy
# Email : q8886888@qq.com
# Last modified : 2015-03-26 13:16:41
# Filename : gale/web.py
# Description :
from __future__ import unicode_literals, print_function
try:
import Cookie # py2
except ImportError:
import http.cookies as Cookie
from gale import template, cache
from gale.http import HTTPHeaders
from gale.e import HasFinished, NotSupportMethod, ErrorStatusCode, MissArgument, HTTPError, LoginHandlerNotExists, LocalPathNotExist, CookieError, CacheError
from gale.utils import parse_request_range, single_pattern, is_string, urlsplit, urlquote, urlunquote, ShareDict, made_uuid, get_mime_type, format_timestamp, code_mess_map # 存的是http响应代码与信息的映射关系
from gale.escape import utf8, param_decode, native_str, to_unicode
from gale.log import (access_log, config_logging, generate_request_log)
from gale.ipc import IPCDict
from gale.session import FileSessionManager
from gale.restapi import RestApi
import traceback
import time
from functools import wraps
import hashlib
import hmac
import os
import json
import glob
import gzip
import re
import base64
import gevent
import types
try:
from cStringIO import StringIO as s_io
except ImportError:
try:
from StringIO import StringIO as s_io
except ImportError: # py3
from io import BytesIO as s_io
try:
import urlparse # py2
except ImportError:
import urllib.parse as urlparse # py3
_ALL_METHOD = ('POST', 'GET', 'PUT', 'DELETE', 'HEAD')
re_signed_cookie = re.compile(r'^\|\d+')
BUFFER_SIZE = 4096 * 10
def auth_401(method):
@wraps(method)
def wrap(self, *args, **kwargs):
if self.current_user:
return method(self, *args, **kwargs)
else:
self.set_header('WWW-Authenticate', "Basic realm='Please input'")
raise HTTPError(401)
return wrap
class ResponseBody(object):
"""This is RequestHandler.push syntactic sugar"""
def __set__(self, handler, value):
self.handler._push_buffer = []
self.handler.push(value)
def __get__(self, handler, handler_class):
self.handler = handler
return self
def __add__(self, value):
self.handler.push(value)
class ResponseBodyBuffer(list):
def __add__(self, _buffer):
print(_buffer)
class RequestHandler(object):
"""主要类,在这里完成对用户的请求处理并返回"""
def __init__(self, application, request, kwargs = None):
self.kwargs = kwargs or {}
self.application = application
self.request = request
self.cache_page = False
self.response_body = None
self._finished = False
self._headers = HTTPHeaders()
self._buffer_md5 = hashlib.md5()
self.query = ParamsObject(self.request.query_arguments)
self._body = BodyParamsResponseObject(self.request.body_arguments)
self.__been_writen_headers = False
self.__is_sending_file = True # 表示正在发送文件, flush_body的方式会改变
self.request.connection.on_close_callback = self.on_connect_close
if self.request.method not in _ALL_METHOD:
raise NotSupportMethod
self.init_data()
self.init_headers()
@property
def body(self):
return self._body
@body.setter
def body(self, _buffer):
self._body.set_buffer(_buffer)
def init_data(self):
"""一些初始化工作都可以在这做"""
pass
def init_headers(self):
"""设置response的headers信息"""
self.set_headers({
'Content-Type' : 'text/plain; charset=utf-8',
'Server' : 'Gale Server',
'Date' : format_timestamp(),
})
self.set_status(200)
@property
def allow_methods(self):
return [ method for method in _ALL_METHOD if hasattr(self, method)]
def set_default_headers(self):
"""可以在这里设置一些默认的headers信息(使用set_header[s])"""
pass
def ALL(self, *args, **kwargs):
pass
"""
# 当http request到来时,会自动调用Handler下的对应方法
def HEAD(self, *args, **kwargs):
raise NotSupportMethod
def GET(self, *args, **kwargs):
raise NotSupportMethod
def POST(self, *args, **kwargs):
raise NotSupportMethod
def DELETE(self, *args, **kwargs):
raise NotSupportMethod
def PUT(self, *args, **kwargs):
raise NotSupportMethod
"""
@property
def server_settings(self):
return self.application.server_settings
@property
def login_url(self):
return self.settings.get('login_url')
@property
def current_user(self):
return self.get_current_user()
def _generate_abspath(self, *p):
return os.path.join(os.path.dirname(os.path.abspath(__file__)),
*p)
def get_current_user(self):
"""如果需要实现用户验证,请在此完成,登录成功返回一个非False的值就行了,如果返回的是False,则表示验证失败,会被转到登录url上。"""
return None
def push(self, _buffer):
if self.is_finished:
raise HasFinished('can\' push finished after')
if isinstance(_buffer, dict):
_buffer = json.dumps(_buffer)
self.set_header('Content-Type', 'application/json')
_buffer = utf8(_buffer)
if self.settings.get('dynamic_304', True):
self._buffer_md5.update(_buffer)
self.body + _buffer
def is_supported_http1_1(self):
return self.request.version == 'HTTP/1.1'
@property
def static_path(self):
return self.settings.get('static_path')
def redirect(self, url, temp = True, status_code = None):
if not status_code:
status_code = temp and 302 or 301 # 如果没有指定 status_code, 则根据是否是临时重定向来决定code
if not self.body.empty():
raise Exception("Can't redirect after push")
assert isinstance(status_code ,int) and (300 <= status_code <= 399)
self.set_status(status_code)
self.set_header('Location', urlparse.urljoin(utf8(self.request.uri), utf8(url)))
def render(self, template_name, **kwargs):
if self.is_finished:
return False
html = to_unicode(self.render_string(template_name, **kwargs))
# 自动加载js
load_js_list = self.__get_load_list('js')
if load_js_list:
offset = html.rfind('</body>')
js_html = '\n'.join(['<script src="{js_path}"></script>'.format(js_path = to_unicode(js_path)) for js_path in load_js_list])
html = html[:offset] + js_html + '\n' + html[offset:]
# 自动加载css
load_css_list = self.__get_load_list('css')
if load_css_list:
offset = html.rfind('</head>')
css_html = '\n'.join(['<link rel="stylesheet" href="{css_path}" />'.format(css_path = to_unicode(css_path)) for css_path in load_css_list])
html = html[:offset] + css_html + '\n' + html[offset:]
self.set_header('Content-Type', 'text/html;charset=UTF-8')
self.push(html)
def __get_load_list(self, _type):
assert _type in ('js', 'css')
_list = []
load_list = getattr(self, 'load_' + _type)()
if not load_list:
return []
load_list = isinstance(load_list, (list, tuple)) and load_list or [load_list]
for _item in load_list:
_abs_path = self.get_static_url(_item, is_abs = True)
if os.path.exists(_abs_path) == False:
raise OSError('%s not exist' % (_abs_path, ))
if os.path.isdir(_abs_path):
_list.extend([_url for _url in self.get_static_urls(_item) \
if _url.endswith('.' + _type)]) # 当css时,过滤掉非css的,js时,过滤掉非js的
else:
_list.append(self.get_static_url(_item))
return _list
def load_js(self):
return []
def load_css(self):
return []
def render_string(self, template_name, **kwargs):
for _param_key in kwargs:
kwargs[_param_key] = native_str(kwargs[_param_key])
_template_loader = self.application._template_cache.get(template_name)
if not _template_loader:
_template_loader = self.create_template_loader(self.get_template_path())
if self.settings.get('debug') != True:
self.application._template_cache[template_name] = _template_loader
t = _template_loader.load(template_name)
name_space = self.get_name_space()
kwargs.update(name_space)
kwargs['module'] = self._load_ui_module
return t.generate(**kwargs)
def string_render(self, string, **kwargs):
_template_loader = self.create_template_loader()
name_space = self.get_name_space()
kwargs.update(name_space)
t = _template_loader.load(string)
return t.generate(**kwargs)
def send_file(self, attr_path, attr_name = None, charset = 'utf-8', speed = None, is_attr = True):
"""speed: 下载速度, 单位是字节B"""
if not self.body.empty():
raise Exception("Can't redirect after push")
if not os.path.isfile(attr_path):
raise OSError('file: %s not found' % (attr_path, ))
attr_name = attr_name or os.path.basename(attr_path)
self.set_header('Content-Type', get_mime_type(attr_path))
if is_attr:
self.set_header('Content-Disposition',
native_str('attachment;filename="%s"') % (native_str(attr_name), ))
self.set_header('Accept-Ranges', 'bytes')
self.__is_sending_file = True
sleep_secs = speed and (1.0 / (1.0 * speed / BUFFER_SIZE)) or None
file_size = os.stat(attr_path).st_size
read_range = self.request.get_header('Range', None)
start, _ = parse_request_range(read_range) # 由于是断点续传,只考虑start
if start > 0:
self.set_header('Content-Range',
'bytes %s-%s/%s' % (start, file_size - 1, file_size))
self.set_status(206)
self.set_header('Content-Length', file_size - start)
self.__send_file(attr_path, sleep_secs, start = start)
self.finish()
def __send_file(self, attr_path, sleep_secs = None, start = 0):
with open(attr_path, 'rb') as fd:
fd.seek(start)
while not self.request.connection.closed:
_content = fd.read(BUFFER_SIZE)
if not _content:
break;
self.push(_content)
self.flush()
if sleep_secs:
gevent.sleep(sleep_secs)
self.body.flush_buffer()
def create_template_loader(self, template_path = None):
return template_path and template.Loader(template_path) or template.StringLoader()
def get_template_path(self):
return self.settings.get('template_path', 'template')
def get_name_space(self):
"""一些可以在模块中用的变量或方法"""
name_space = {
'client_ip' : self.client_ip,
'handler' : self,
'request' : self.request,
'static_url' : self.get_static_url,
'_tt_modules' : _UINameSpace(self),
'current_user' : self.get_current_user,
}
ext_name_space = self.get_ext_name_space()
if not isinstance(ext_name_space, dict):
raise TypeError
name_space.update(ext_name_space)
return name_space
def get_ext_name_space(self):
"""可以在这里定义一些额外的namespce"""
return {}
def get_static_url(self, file_path, is_abs = False):
assert self.static_path, "static_url must had 'static_path' in app\'s settings"
static_class = self.settings.get('static_class', StaticFileHandler)
if is_abs:
return static_class.make_absolute_path(self.static_path, file_path)
static_url = static_class.get_static_url(self.settings, file_path)
return static_url
def get_static_urls(self, path):
"""获取某一个目录下的所有静态资源"""
assert self.static_path, "get_static_urls must had 'static_path' in app\'s settings"
abs_path = self.get_static_url(path, is_abs = True)
if not os.path.isdir(abs_path):
return [abs_path]
urls = []
for dirname, _, _files in os.walk(abs_path):
for _file in _files:
url = os.path.join(dirname, _file)[len(self.static_path) + 1: ]
urls.append(self.get_static_url(url))
return urls
@property
def is_finished(self):
return self._finished
def process_buffer(self, _buffer):
if self.settings.get('gzip', False):
processor = GzipProcessor(self.request.headers)
return processor.process(_buffer, self._headers,
self.application._buffer_stringio)
return _buffer
def should_return_304(self, buffer_md5):
if self.get_status() not in (200, 304):
return False
if self.request.method != 'GET':
return False
dynamic_304 = self.settings.get('dynamic_304', True)
if dynamic_304 == False or self.request.get_header('Cache-Control') == 'no-cache':
return False
request_version = self.request.get_header('If-None-Match', '')
if not request_version:
return False
return request_version == buffer_md5
def _flush_body(self, response_body):
connection = self.request.connection
if self.__is_sending_file:
if response_body:
connection.send_body(response_body)
else:
connection.send_finish_tag()
return
_buffer_md5 = self._buffer_md5.hexdigest()
self.set_header('Etag', _buffer_md5)
is_return_304 = self._status_code == 304 or self.should_return_304(_buffer_md5)
if is_return_304:
self.set_status(304)
if is_return_304 != True and self.request.method != 'HEAD': # 如果是HEAD请求的话则不返回主体内容
connection.send_body(response_body)
connection.send_finish_tag()
def flush(self, _buffer = None):
self.before_flush()
if self.is_finished:
return
if self.request.connection.closed:
return
_buffer = _buffer or self.body.to_bytes()
_response_body = self.process_buffer(_buffer)
if self.__been_writen_headers == False:
self.__been_writen_headers = True
self.set_header('Connection', self.request.is_keep_alive() and 'Keep-Alive' or 'Close')
self.set_default_header('Content-Length', len(_response_body))
if hasattr(self, '_new_cookie'):
for cookie in self._new_cookie.values():
self.add_header('Set-Cookie', cookie.OutputString())
_headers = self._headers.get_response_headers_string(self.response_first_line) # 把http头信息生成字符串
self.request.connection.send_headers(_headers)
self._flush_body(_response_body)
self.body.flush_buffer()
if self.cache_page and self._status_code in (200, 304):
self.response_body = _buffer
else:
self.response_body = None
del _buffer
def log_print(self):
_log = generate_request_log(self)
# 根据不同的http状态代码来输出不同的日志
if self._status_code < 400:
access_log.info(_log)
elif self._status_code < 500:
access_log.warning(_log)
else:
access_log.error(_log)
@property
def client_ip(self):
return self.request.client_ip
def on_referrer_error(self):
raise HTTPError(403)
def on_finish(self):
"""会在执行完finish时执行"""
pass
def before_flush(self):
"""在刷新前做点事"""
pass
def finish(self, chunk = None):
if self._finished:
return
if not self.is_finished:
self.flush(chunk)
if not self.request.is_keep_alive():
self.request.connection.close()
self._finished = True # 不管是不是keep alive,都需要把它设置成True,因为连接跟这个没关系,每一次有新的请求时,都会生成一个新的RequestHandler
self.log_print()
self.on_finish()
def set_default_header(self, name, value):
self._headers.set_default_header(name, value)
def get_been_set_header(self, name, default = ''):
try:
return self._headers[name]
except KeyError:
return default
def set_header(self, name, value):
if value == None:
return
self._headers[name] = value
def clear_headers(self):
self._headers.clear()
def add_header(self, name, value):
self._headers.add(name, value)
def set_headers(self, headers):
if not isinstance(headers, dict):
raise TypeError
for _k, _v in headers.items():
self.set_header(_k, _v)
@property
def cookies(self):
"""本次请求中所有的cookies"""
return self.request.cookies
def set_cookie(self, name, value, domain=None, expires=None, path = '/', expires_day = None, **kwargs):
name, value = native_str(name), native_str(value)
self._new_cookie = getattr(self, '_new_cookie', Cookie.SimpleCookie())
self._new_cookie.pop(name, None) # 如果已经存在了,则删除它
self._new_cookie[name] = value
morsel = self._new_cookie[name]
if domain:
morsel['domain'] = domain
if path:
morsel['path'] = path
if expires_day and (not expires):
expires = expires_day * 60 * 60 * 24
if expires:
morsel['expires'] = format_timestamp(time.time() + expires)
for k, v in kwargs.items():
if k == 'max_age':
k = 'max-age'
morsel[k] = v
def clear_cookie(self, name, path = '/', domain = None):
self.set_cookie(name, value = '',
path = path, domain = domain, expires = -100)
def clear_all_cookies(self, path = '/', domain = None):
for _cookie_name in self.cookies:
self.clear_cookie(name, path = path, domain = domain)
def get_cookie(self, cookie_name, default = None):
value = self.cookies.get(cookie_name, default)
if value and value[0] == '\"' and value[-1] == '\"':
return value[1: -1]
else:
return value
def get_signed_cookie(self, cookie_name, default = None):
secret = self.settings.get('cookie_secret', None)
if not secret:
raise CookieError("use signed cookie app\'s settings must has 'cookie_secret'")
value = self.get_cookie(cookie_name)
if value == None:
return default
if (not re_signed_cookie.match(value)) and self.cookie_support_tornado == False:
return None
if self.cookie_support_tornado:
return decode_signed_cookie_tornado(secret, cookie_name, value)
else:
return decode_signed_cookie(secret, cookie_name, value)
def set_signed_cookie(self, name, value, *args, **kwargs):
secret = self.settings.get('cookie_secret', None)
if not secret:
raise CookieError("use signed cookie app\'s settings must has 'cookie_secret'")
signed_value = encode_signed_cookie(secret, name, value)
self.set_cookie(name, signed_value, *args, **kwargs)
@property
def cookie_support_tornado(self):
return bool(self.settings.get('cookie_support_tornado', False))
def set_status(self, status_code = 200, status_message = None):
self.status_message = status_message # 可以自定义状态代码描述
self._status_code = status_code
def get_status(self):
return self._status_code
def process_raise_error(self, e):
"""当出现错误的时候会被调用"""
_status_code = getattr(e, 'status_code', 500)
_status_mess = getattr(e, 'status_mess', None)
self.set_status(_status_code, _status_mess)
self.push_error() # 调用错误处理函数
def push_error(self):
"""处理http异常错误"""
if self.is_finished:
return
self.send_error(traceback.format_exc())
def raise_error(self, status_code = 500, status_msg = None):
raise HTTPError(status_code, status_msg)
def send_error(self, exc):
"""把异常信息推送出去"""
exc = utf8(exc)
self.body.flush_buffer()
self.push(self.response_first_line)
if not self.settings.get('debug', False): # 只允许 在debug情况下输出错误
return
if self.get_status() >= 500:
self.push(exc + b'\n')
else:
pass
@property
def _status_mess(self):
return self.status_message or code_mess_map.get(self._status_code) # 如果没有自定义状态代码描述的话,就根据标准来
@property
def response_first_line(self):
_message = self._status_mess
if not _message:
raise ErrorStatusCode
return "{version} {code} {message}".format(
version = self.request.version, code = self.get_status(),
message = _message)
def get_query_arguments(self, param):
return self.request.query_arguments.get(param, [])
def get_body_arguments(self, param):
return self.request.body_arguments.get(param, [])
def get_query_argument(self, param, default = None):
_args = self.get_query_arguments(param)
return self.__get_one_argument(_args, default)
def get_body_argument(self, param, default = None):
_args = self.get_body_arguments(param)
return self.__get_one_argument(_args, default)
def get_argument(self, param, default = None):
_args = self.request.all_arguments.get(param, [])
return self.__get_one_argument(_args, default)
def get_arguments(self, param):
return self.request.all_arguments.get(param, [])
def get_files(self, name):
return self.request.files.get(name, [])
def get_file(self, name):
return self.__get_one_argument(self.get_files(name))
@property
def session(self):
_session = getattr(self, '_session', None)
if _session != None:
return _session
from gale.session import Session
_session_manager = getattr(self.application, 'session_manager', None)
if not _session_manager:
_session_manager = FileSessionManager(session_secret = 'galesessionsecret' ,
session_timeout = 86400 * 60) # 如果application中没有配置session_manager,则自动生成一个FileSessionManager,推荐想用session的请自己在application中指定 session_manager
setattr(self.application, 'session_manager', _session_manager)
_session = Session(_session_manager, self)
setattr(self, '_session', _session)
return _session
def __get_one_argument(self, args, default = []):
if (args == []) and (default != []): # 如果没有参数,但是指定了默认值,就返回默认的
return default
if args == []:
raise MissArgument
return args[0]
@property
def settings(self):
return self.application.settings
@property
def ui_settings(self):
return self.application.ui_settings
def _load_ui_module(self, module_name, *args, **kwargs):
_module = self.ui_settings.get(module_name)
assert issubclass(_module, UIModule), 'module must extend from UIModule'
_module_instance = _module(self)
return _module_instance.render(*args, **kwargs)
def get_cache_manager(self, on = None):
s_cache_manager = self.settings.get('cache_manager', None)
if not s_cache_manager:
raise CacheError('use cache , app\'s settings must has cache_manager')
if on == None: # 表示获取默认的cache manager
if isinstance(s_cache_manager, (tuple, list)): # 如果有指定多个,则默认是以第一个为cache_manager
s_cache_manager = s_cache_manager[0]
return s_cache_manager
else:
on = utf8(on)
_cache_manager = getattr(self.application, on + '_cache_manager', None) # 这主要是用来减少判断的,对不同的cache_manager只需要做一个判断,就可以找出哪个on,对应的是哪个列表中的元素了
if _cache_manager:
return _cache_manager
if not isinstance(s_cache_manager, (tuple, list)):
raise CookieError("use 'on' arg, cache_manager setting must be tuple or list")
for _cm in s_cache_manager:
if _cm.__name__ == on:
setattr(self.application, on + '_cache_manager', _cm)
return _cm
raise CookieError('Not Found %s cache_manager' % (on, ))
def get_401_user_pwd(self):
"""在401验证时,获取用户输入的用户名和密码,返回tuple"""
authorization = self.request.headers.get('Authorization')
if not authorization:
return None, None
import base64
user_pwd = base64.decodestring(authorization.split()[-1].strip())
return user_pwd.split(':', 1)
def on_connect_close(self):
pass
class _FileItem(object):
def __init__(self, root, relative_path):
self.root = root
self.relative_path = relative_path or '/'
@property
def pretty_name(self):
basename = self.relative_path
if self.isdir and self.relative_path[-1] != '/':
return basename + '/'
else:
return basename
@property
def showpath(self):
return os.path.join(self.root, self.relative_path)
@property
def dirname(self):
return os.path.dirname(self.abspath)
@property
def isfile(self):
return os.path.isfile(self.abspath)
@property
def isdir(self):
return os.path.isdir(self.abspath)
@property
def name(self):
return os.path.basename(self.abspath)
@property
def ishidden(self):
basename = os.path.basename(self.abspath)
return basename[0] == '.'
def __repr__(self):
return self.abspath
@property
def raw_size(self):
return self.stat.st_size
@property
def pretty_size(self):
raw_size = self.raw_size
pretty_size = raw_size
unit = 'B'
for _unit in ['KB', 'MB', 'GB']:
pretty_size /= 1024.0
if pretty_size <= 1000:
unit = _unit
break
return '%.2f %s' % (pretty_size,
unit)
@property
def stat(self):
return os.stat(self.abspath)
@property
def ctime(self):
return time.ctime(self.stat.st_ctime)
@property
def mtime(self):
return time.ctime(self.stat.st_mtime)
@property
def isexist(self):
return os.path.exists(self.abspath)
@property
def abspath(self):
if self.relative_path[0] == '/':
path = self.relative_path[1:]
else:
path = self.relative_path
abspath = os.path.join(self.root, path)
return abspath
class RedirectHandler(RequestHandler):
def GET(self):
self.redirect(**self.kwargs)
class FileHandler(RequestHandler):
"""
列出所有本地所有文件及目录
kwargs
root: 本地文件目录
show_hidden: 显示隐藏文件(默认False)
hidden_list: 隐藏部分文件(正则表达式)
deny_list: 禁止部分文件访问(正则表达式)
deny_hidden: 禁止hidden_list的文件
base_username: 401用户名
base_password: 401密码
"""
def init_data(self):
hidden_list = self.kwargs.get('hidden_list', [])
deny_list = self.kwargs.get('deny_list', [])
if self.kwargs.get('deny_hidden'):
deny_list.extend(hidden_list)
self.hidden_re_list = [ re.compile(hidden_exp) for hidden_exp in hidden_list]
self.deny_re_list = [re.compile(deny_exp) for deny_exp in deny_list]
self.base_username = self.kwargs.get('base_username')
self.base_password = self.kwargs.get('base_password')
def GET(self, relative_path = '/'):
if self.base_username and self.base_password:
auth_401(self.__class__._GET)(self, relative_path)
else:
self._GET(relative_path)
def _GET(self, relative_path):
relative_path = relative_path or '/'
if relative_path.startswith('./'):
self.raise_error(403)
self.relative_path = relative_path
item = _FileItem(self.root, relative_path)
if not (item.isexist and self.allow_account(item)):
self.raise_error(404)
elif item.isfile:
self.set_header('Content-Type', get_mime_type(item.abspath))
self.send_file(item.abspath, is_attr = self.query.attr == '1')
else:
self.render('files.html', items = self.ls(item.dirname),
relative_path = relative_path, parent = item.dirname)
def get_current_user(self):
username, password = self.get_401_user_pwd()
if username == self.base_username and password == self.base_password:
return username
else:
return None
def allow_account(self, item):
item_urlpath = os.path.join(self.relative_path, item.pretty_name)
return not any([deny_re.search(item_urlpath) for deny_re in self.deny_re_list])
def allow_show(self, item):
allow_hiddent = self.kwargs.get('show_hidden', False)
if (not allow_hiddent) and item.ishidden:
return False
item_urlpath = os.path.join(self.relative_path, item.pretty_name)
return not any([hidden_re.search(item_urlpath) for hidden_re in self.hidden_re_list])
def ls(self, dir_path):
items = []
for item_name in os.listdir(dir_path):
new_item = _FileItem(dir_path, item_name)
if not self.allow_show(new_item):
continue
items.append(new_item)
return items
@property
def root(self):
return self.kwargs.get('root', '/')
def get_template_path(self):
return self._generate_abspath('template')
class ErrorHandler(RequestHandler):
def ALL(self):
status_code = self.kwargs.get('status_code', 500)
raise HTTPError(status_code)
class Application(object):
_buffer_stringio = s_io()
_template_cache = {}
def __init__(self, handlers = [], vhost_handlers = [], settings = {}, log_settings = {}, ui_settings = {}):
"""
log_settings : {'level': log level(default: DEBUG',
'datefmt': log date format(default: "%Y-%m-%d %H:%M:%S"),
'file': log save to file}
ui_settings: {module_name: module(extends from UIModule)}
"""
self.settings = settings
self.ui_settings = ui_settings
default_handlers = []
if settings.get('static_path'):
static_class = settings.get('static_class', StaticFileHandler)
for url_re in (r'%s(.*)' %
(settings.get('static_prefix', r'/static/')),
r'/(favicon\.ico)', r'/(robots\.txt)'):
default_handlers.append((url_re, static_class))
gale_static_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'static')
if settings.get('debug'):
_debug_prefix = settings.get('debug_prefix', '/')
_debug_handler_url = os.path.join(_debug_prefix, 'gale/debug')
_debug_static_url = os.path.join(_debug_prefix, 'gale/static')
default_handlers.append((_debug_handler_url, DebugHandler,
{'static_url_path': _debug_static_url}))
default_handlers.append((r'%s/(.*)' % (_debug_static_url),
StaticFileHandler, {'root_path':
gale_static_path}))
if 'session_manager' in settings:
self.session_manager = settings['session_manager']
vhost_handlers = vhost_handlers or [('.*$', handlers)]
self.vhost_handlers = []
for _vhost_handler in vhost_handlers:
self.add_handlers(*_vhost_handler)
# 缺省的一些handler被从vhost_handlers中分离出来, 使缺省的handler应用到所有的host handlers中去
self.default_handlers = []
for default_handler in default_handlers:
if len(default_handler) == 2:
_url, _hdl = default_handler
self.default_handlers.append([self.__re_compile(_url),
_hdl, {}])
elif len(default_handler) == 3:
_url, _hdl, _kwargs = default_handler
self.default_handlers.append([self.__re_compile(_url),
_hdl, _kwargs])
config_logging(log_settings)
def __re_compile(self, re_string):
"""把正则规则都编译了,加快速度"""