# 你肯定得有个类似`BaseHandler`的类classBaseHandler(tornado.web.RequestHandler):def__init__(self,*args,**kwargs):"""此处省略若干自定义初始化过程"""passdefexport(self,abs_path,file_name=None,content_type=None):defset_content_length(this,path,req_range):size=os.path.getsize(path)ifreq_range:start,end=req_rangeif(startisnotNoneandstart>=size)orend==0:# As per RFC 2616 14.35.1, a range is not satisfiable only: if# the first requested byte is equal to or greater than the# content, or when a suffix with length 0 is specifiedthis.set_status(416)# Range Not Satisfiablethis.set_header("Content-Type","text/plain")this.set_header("Content-Range","bytes */%s"%(size,))returnstart,endifstartisnotNoneandstart<0:start+=sizeifendisnotNoneandend>size:# Clients sometimes blindly use a large range to limit their# download size; cap the endpoint at the actual file size.end=size# Note: only return HTTP 206 if less than the entire range has been# requested. Not only is this semantically correct, but Chrome# refuses to play audio if it gets an HTTP 206 in response to# ``Range: bytes=0-``.ifsize!=(endorsize)-(startor0):this.set_status(206)# Partial Content# pylint: disable=protected-accessthis.set_header("Content-Range",httputil._get_content_range(start,end,size))else:start=end=NoneifstartisnotNoneandendisnotNone:length=end-startelifendisnotNone:length=endelifstartisnotNone:length=size-startelse:length=sizethis.set_header("Content-Length",length)returnstart,enddefget_content_type(path):mime_type,encoding=mimetypes.guess_type(path)# per RFC 6713, use the appropriate type for a gzip compressed fileifencoding=="gzip":return"application/gzip"# As of 2015-07-21 there is no bzip2 encoding defined at# http://www.iana.org/assignments/media-types/media-types.xhtml# So for that (and any other encoding), use octet-stream.elifencodingisnotNone:return"application/octet-stream"elifmime_typeisnotNone:returnmime_type# if mime_type not detected, use application/octet-streamelse:return"application/octet-stream"defget_content(abspath,start=None,end=None):withopen(abspath,"rb")asfile:ifstartisnotNone:file.seek(start)ifendisnotNone:remaining=end-(startor0)else:remaining=NonewhileTrue:chunk_size=64*1024ifremainingisnotNoneandremaining<chunk_size:chunk_size=remainingchunk=file.read(chunk_size)ifchunk:ifremainingisnotNone:remaining-=len(chunk)yieldchunkelse:ifremainingisnotNone:assertremaining==0returnifisinstance(abs_path,bytes):self.set_header('Content-Type',f'application/{content_type}')iffile_name:file_name=urllib.parse.quote(file_name)self.set_header('Content-Disposition',f'attachment; filename={file_name}')self.finish(abs_path)ifnotos.path.exists(abs_path):raiseCustomError(_("File not found"))ifnotfile_name:file_name=os.path.basename(abs_path)file_name=urllib.parse.quote(file_name)self.set_header('Content-Disposition',f'attachment; filename={file_name}')ifnotcontent_type:content_type=get_content_type(abs_path)self.set_header("Content-Type",content_type)self.set_header("Accept-Ranges","bytes")self.set_header("Last-Modified",datetime.datetime.utcfromtimestamp(os.path.getmtime(abs_path)))request_range=Nonerange_header=self.request.headers.get("Range")ifrange_header:# As per RFC 2616 14.16, if an invalid Range header is specified,# the request will be treated as if the header didn't exist.request_range=httputil._parse_request_range(range_header)# pylint: disable=protected-accessstart,end=set_content_length(self,abs_path,request_range)ifself.request.method=='GET':content=get_content(abs_path,start,end)ifisinstance(content,bytes):content=[content]forchunkincontent:try:self.write(chunk)exceptiostream.StreamClosedError:returnelse:assertself.request.method=="HEAD"
classBaseHandler(tornado.web.RequestHandler,SessionMixin):def__init__(self,application,request,**kwargs):super(BaseHandler,self).__init__(application,request,**kwargs)# 一系列init方法defprepare(self):"""
Called at the beginning of a request before `get`/`post`/etc.
"""ifself.request.methodin('POST','PUT'):self.adapt_req_headers()defadapt_req_headers(self):"""根据request body自适应修正Content-Type"""form_data_prefix='multipart/form-data;'content_disp=b'Content-Disposition: form-data;'www_form_prefix='application/x-www-form-urlencoded'defparse_body():self.request._parse_body()# pylint: disable=protected-accesslogging.warning('Content-Type does not match the request body, will correct it')ifself.request.body:if(notself.request.headers.get('Content-Type','').startswith(form_data_prefix)andcontent_dispinself.request.body):try:boundary=self.request.body.split(b'\r\n')[0][2:].decode()or'boundary'exceptUnicodeDecodeErrorasexp:logging.exception(exp)else:self.request.headers.update({'Content-Type':f'{form_data_prefix} boundary="{boundary}"'})parse_body()returnifnotself.request.headers.get('Content-Type','').startswith(www_form_prefix)andall([all(i.partition(b'='))foriinself.request.body.split(b'&')ifcontent_dispnotini]or(False,)):self.request.headers.update({'Content-Type':www_form_prefix})parse_body()return
defset_cors_header(self):origin=f'{self.request.headers.get("Origin","").rstrip("/")}'trust_hosts=[...]iforiginnotintrust_hosts:logging.warning(f'Untrusted source request detected: {origin}')returnNone# NOTE: Can't be set "*" because a valid request always sending with cookies# which will be blocked by CORS policyself.set_header("Access-Control-Allow-Origin",origin)# Must be set as "true"(case sensitive) to allow CORS with cookiesself.set_header("Access-Control-Allow-Credentials","true")self.set_header("Access-Control-Allow-Methods","POST, GET, PUT, DELETE, OPTIONS")# Must contain all possible request headers from frontend requestsself.set_header("Access-Control-Allow-Headers","Accept, Accept-Encoding, Accept-Language, Access-Control-Request-Headers, Access-Control-Request-Method, ""Cache-Control, Connection, Content-Type, ""Host, Origin, Pragma, Referer, Sec-Fetch-Mode, User-Agent, X-Csrftoken",)
def check_origin(self, origin):
"""Override to enable support for allowing alternate origins.
The ``origin`` argument is the value of the ``Origin`` HTTP header,
the url responsible for initiating this request.
.. versionadded:: 4.0
"""
parsed_origin = urlparse(origin)
origin = parsed_origin.netloc
origin = origin.lower()
host = self.request.headers.get("Host")
# Check to see that origin matches host directly, including ports
return origin == host
In order for your proxied websocket connection to still work you will need to override check origin on the WebSocketHandler and whitelist the domains that you care about. Something like this.
import re
from tornado import websocket
class YouConnection(websocket.WebSocketHandler):
def check_origin(self, origin):
return bool(re.match(r'^.*?\.mydomain\.com', origin))
This will let the connections coming through from info.mydomain.com to get through as before.
# NOTE: I am not responsible for any expired content.
create@2020-01-03T13:10:40+08:00
update@2021-11-30T04:16:11+08:00
comment@https://github.com/ferstar/blog/issues/14