Tornado的请求与响应

Tornado中的输入与输出

指的是站在服务器端的角度,输入表示客户端发送请求到服务器端,输出表示服务器端返回数据给客户端

1.输入

回想一下,客户端使用HTTP协议向服务器端传参有哪几种途径?

  1. url,问号后面的查询字符串(query_str),形如:?key1=value1&key2=value2
  2. 提取url特定部分,如/blogs/2018/09/13,可以在服务器端的路由中用正则表达式截取
  3. 请求头(header),如获取cookie,或自定义字段,如X-XSRFToken=secret_key
  4. 请求体(body),表单,json/xml数据

现在来看tornado中为我们提供了哪些方法来获取请求信息:

  1. 获取查询字符串参数

    get_query_argument(name, default=_ARG_DEFAULT, strip=True)

    • 从请求的查询字符串中返回指定参数name的值,如果出现多个同名参数,则返回最后一个的值;

    • default为路由中未传name参数时返回的默认值,如default未设置,则会抛出tornado.web.MissingArgumentError异常;

    • strip表示是否过滤掉左右两边的空白字符,默认为过滤。

    get_query_arguments(name, strip=True)

    • 从请求的查询字符串中返回指定参数name的值,注意返回的是list列表(即使对应name参数只有一个值),若未找到name参数,则返回空列表。
  2. 获取请求体数据

    get_body_argument(name, default=_ARG_DEFAULT, strip=True)

    • 从请求体中返回指定参数name的值,如出现多个同名参数,则返回最后一个的值;
    • default与strip同上。

    get_body_arguments(name, strip=True)

    • 从请求体中返回指定参数name的值,注意返回的是list列表(即使对应name参数只有一个值),若未找到name参数,则返回空列表。

    说明:对于请求体中数据为json或xml的,无法通过这两个方法获取。

  3. 前两类方法的整合

    get_argument(name, default=_ARG_DEFAULT, strip=True)

    get_arguments(name, strip=True)

    说明:对于请求体中数据为json或xml的,无法通过这两个方法获取。

    这两个方法最常用

  4. 获取请求体中的json或xml数据

    body_json = self.request.body

    body_dict = json.loads(body_json)

    需要先判断请求体的数据是否为 application/json 格式:
    
    if self.reuqest.headers.get("Content-Type", "").startswith("application/json"):
    
    try:
        # 防止请求头内容是application/json,实际内容不是json数据
        body_dict = json.loads(self.request.body)
    except Exception as e:
        logger({"message": e})
            body_dict = dict()
  5. 正则提取uri中的参数

    第一种是未命名的方式,按照匹配顺序进行传递:

    ...
    def get(self, subject, city):
    
    ...
    (r"/subject/(.+)/(.+)", SubjectCityHandler),
    ...

    第二种是以命名的方式进行传递:

    ...
    def get(self, city, date):
    
    ...
    (r"/subject/(?P<date>.+)/(?P<city>.+)", SubjectCityHandler),
    ...

    在提取多个值的时候最好使用命名的方式

  6. 关于请求对象的其他信息

    RequestHandler.request 对象存储了关于请求的相关信息,具体属性有:

    1. method:HTTP的请求方式,如GET或POST;

    2. host:被请求的主机名;

    3. uri:请求的完整资源表示,包括路径和查询字符串;

    4. path:请求的路径部分;

    5. version:使用的HTTP协议的版本;

    6. headers:请求的协议头,是类字典型的对象,支持关键字索引的方式获取特定协议头信息,例如:request.headers[“Content-Type”]

    7. body:请求体数据;

    8. remote_ip:客户端的IP地址;

    9. files:用户上传的文件,为字典类型,例如:

    {
     "form_filename1":[<tornado.httputil.HTTPFile>, <tornado.httputil.HTTPFile>],
     "form_filename2":[<tornado.httputil.HTTPFile>,],
     ... 
    }

    tornado.httputil.HTTPFile是接收的文件对象,它有三个属性:

    • filename:文件的实际名字,与form_filename1不同,字典中的键名代表的是表单对应项的名字;
    • body:文件的数据实体;
    • content_type:文件的类型,这三个对象属性可以像字典一样支持关键字索引,如:request.files[“form_filename1”][0][“body”]

2.输出

1.write(chunk)

将chunk数据写到缓冲区,我们可以在同一个视图中多次使用write方法

使用 write 方法写入 json 数据:

import json

class IndexHandler(RequestHandler):
    def get(self):
        stu = {
            "name": "johny",
            "gender":"girl",
            "age":12
        }

        stu_json = json.dumps(stu)
        self.write(stu_json)

实际上,我们可以不用自己手动去做json序列化,当write方法检测到传入的chunk参数是字典类型后,会自动转换成json字符串,并在响应头header中设置Content-Type: application/json;charset=UTF-8

自己手动序列化时为:Content-Type: text/html; charset=UTF-8

2.set_header(name, value)

使用set_header()方法,可以手动设置一个名为name,值为value的响应头header字段。

用set_header()方法来完成上面write()方法所做的工作:

import json

class IndexHandler(RequestHandler):
    def get(self):
        stu = {
            "name": "johny",
            "gender": "girl",
            "age": 12
        }

        stu_json = json.dumps(stu)
        self.write(stu_json)
        self.set_header("Content-Type", "application/json; charset=UTF-8")

3.set_default_headers()

该方法在进入HTTP处理方法之前先被调用,可以重写此方法来预先设置默认的response_header。

注意:在HTTP处理方法中使用set_header()方法会覆盖在set_default_headers()方法中同名的header

4.set_status(status_code, reson=None)

设置响应状态码

参数说明:

  • status_code:int类型,状态码,若reason为None,则必须是标准的状态码,否则需要设置reason。
  • reason:string类型,描述状态码的词组,若reason为None,则会被自动填充为标准的状态码。
CodeEnum NameDetails
100CONTINUEHTTP/1.1 RFC 7231, Section 6.2.1
101SWITCHING_PROTOCOLSHTTP/1.1 RFC 7231, Section 6.2.2
102PROCESSINGWebDAV RFC 2518, Section 10.1
200OKHTTP/1.1 RFC 7231, Section 6.3.1
201CREATEDHTTP/1.1 RFC 7231, Section 6.3.2
202ACCEPTEDHTTP/1.1 RFC 7231, Section 6.3.3
203NON_AUTHORITATIVE_INFORMATIONHTTP/1.1 RFC 7231, Section 6.3.4
204NO_CONTENTHTTP/1.1 RFC 7231, Section 6.3.5
205RESET_CONTENTHTTP/1.1 RFC 7231, Section 6.3.6
206PARTIAL_CONTENTHTTP/1.1 RFC 7233, Section 4.1
207MULTI_STATUSWebDAV RFC 4918, Section 11.1
208ALREADY_REPORTEDWebDAV Binding Extensions RFC 5842, Section 7.1 (Experimental)
226IM_USEDDelta Encoding in HTTP RFC 3229, Section 10.4.1
300MULTIPLE_CHOICESHTTP/1.1 RFC 7231, Section 6.4.1
301MOVED_PERMANENTLYHTTP/1.1 RFC 7231, Section 6.4.2
302FOUNDHTTP/1.1 RFC 7231, Section 6.4.3
303SEE_OTHERHTTP/1.1 RFC 7231, Section 6.4.4
304NOT_MODIFIEDHTTP/1.1 RFC 7232, Section 4.1
305USE_PROXYHTTP/1.1 RFC 7231, Section 6.4.5
307TEMPORARY_REDIRECTHTTP/1.1 RFC 7231, Section 6.4.7
308PERMANENT_REDIRECTPermanent Redirect RFC 7238, Section 3 (Experimental)
400BAD_REQUESTHTTP/1.1 RFC 7231, Section 6.5.1
401UNAUTHORIZEDHTTP/1.1 Authentication RFC 7235, Section 3.1
402PAYMENT_REQUIREDHTTP/1.1 RFC 7231, Section 6.5.2
403FORBIDDENHTTP/1.1 RFC 7231, Section 6.5.3
404NOT_FOUNDHTTP/1.1 RFC 7231, Section 6.5.4
405METHOD_NOT_ALLOWEDHTTP/1.1 RFC 7231, Section 6.5.5
406NOT_ACCEPTABLEHTTP/1.1 RFC 7231, Section 6.5.6
407PROXY_AUTHENTICATION_REQUIREDHTTP/1.1 Authentication RFC 7235, Section 3.2
408REQUEST_TIMEOUTHTTP/1.1 RFC 7231, Section 6.5.7
409CONFLICTHTTP/1.1 RFC 7231, Section 6.5.8
410GONEHTTP/1.1 RFC 7231, Section 6.5.9
411LENGTH_REQUIREDHTTP/1.1 RFC 7231, Section 6.5.10
412PRECONDITION_FAILEDHTTP/1.1 RFC 7232, Section 4.2
413REQUEST_ENTITY_TOO_LARGEHTTP/1.1 RFC 7231, Section 6.5.11
414REQUEST_URI_TOO_LONGHTTP/1.1 RFC 7231, Section 6.5.12
415UNSUPPORTED_MEDIA_TYPEHTTP/1.1 RFC 7231, Section 6.5.13
416REQUEST_RANGE_NOT_SATISFIABLEHTTP/1.1 Range Requests RFC 7233, Section 4.4
417EXPECTATION_FAILEDHTTP/1.1 RFC 7231, Section 6.5.14
422UNPROCESSABLE_ENTITYWebDAV RFC 4918, Section 11.2
423LOCKEDWebDAV RFC 4918, Section 11.3
424FAILED_DEPENDENCYWebDAV RFC 4918, Section 11.4
426UPGRADE_REQUIREDHTTP/1.1 RFC 7231, Section 6.5.15
428PRECONDITION_REQUIREDAdditional HTTP Status Codes RFC 6585
429TOO_MANY_REQUESTSAdditional HTTP Status Codes RFC 6585
431REQUEST_HEADER_FIELDS_TOO_LARGE AdditionalHTTP Status Codes RFC 6585
500INTERNAL_SERVER_ERRORHTTP/1.1 RFC 7231, Section 6.6.1
501NOT_IMPLEMENTEDHTTP/1.1 RFC 7231, Section 6.6.2
502BAD_GATEWAYHTTP/1.1 RFC 7231, Section 6.6.3
503SERVICE_UNAVAILABLEHTTP/1.1 RFC 7231, Section 6.6.4
504GATEWAY_TIMEOUTHTTP/1.1 RFC 7231, Section 6.6.5
505HTTP_VERSION_NOT_SUPPORTEDHTTP/1.1 RFC 7231, Section 6.6.6
506VARIANT_ALSO_NEGOTIATESTransparent Content Negotiation in HTTP RFC 2295, Section 8.1 (Experimental)
507INSUFFICIENT_STORAGEWebDAV RFC 4918, Section 11.5
508LOOP_DETECTEDWebDAV Binding Extensions RFC 5842, Section 7.2 (Experimental)
510NOT_EXTENDEDAn HTTP Extension Framework RFC 2774, Section 7 (Experimental)
511NETWORK_AUTHENTICATION_REQUIREDAdditional HTTP Status Codes RFC 6585, Section 6

5.redirect(url)

告知浏览器跳转到url

6.send_error(status_code=500, **kwargs)

抛出HTTP错误状态码status_code,默认为500,kwargs为可变命名参数。使用send_error抛出错误后tornado会调用write_error()方法进行处理,并返回给浏览器处理后的错误页面。

7.write_error(status_code, **kwargs)

用来处理send_error抛出的错误信息并返回给浏览器错误信息页面,可以重写此方法来定制自己的错误显示页面。

class IndexHandler(RequestHandler):
    def get(self):
        self.write("主页")
        error_data = {
            "title": "页面丢了",
            "content": "您访问的页面不存在"
        }

        # **error_data在这里相当于解包的过程
        self.send_error(404, **error_data)

    def write_error(self, status_code, **kwargs):
        self.write(f"<h1>出错了,程序员正在赶来</h1>")
        self.write(f"<p>错误名:{kwargs["title"]}</p>")
        self.write(f"<p>错误详情:{kwargs["content"]}</p>")

if __name__ == "__main__":
    ...

3.接口与调用顺序

1.initialize()

对应每个请求的处理类Handler在构造一个实例后首先执行initialize()方法,在讲输入时提到,路由映射中的第三个字典参数会作为该方法的命名参数传递,如:

class IndexHandler(RequestHandler):
    def initialize(self, database):
        self.database = database
    def get(self):
        ...

app = Application(
    [(r"/user/(.*)", IndexHandler, {"database": database})]
)
此方法通常用来初始化参数(对象属性),很少使用

2.prepare()

预处理,即在执行对应请求方式的HTTP方法(如 get \ post 等)前先执行,注意:不论以何种HTTP请求方式,都会执行prepare()方法。

以预处理请求体中的json数据为例:

import json

class IndexHandler(RequestHandler):
    def prepare(self):
        if self.request.headers.get("Content-Type", "").startswith("application/json"):
            self.json_dict = json.loads(self.request.body)
        else:
            self.json_dict = None

    def post(self):
        if self.json_dict:
            for key, value in self.json_dict.items()
                self.write(f"<h3>{key}</h3><p>{value}</p>")

3.HTTP方法

方法描述
get请求指定的页面信息,并返回实体主体。
head类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
post向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
delete请求服务器删除指定的内容。
patch请求修改局部数据。
put从客户端向服务器传送的数据取代指定的文档的内容。
options返回给定URL支持的所有HTTP方法。

4.on_filish()

在请求处理结束后调用,即在调用HTTP方法后调用。通常该方法用来进行资源清理释放或处理日志等。注意:请尽量不要在此方法中进行响应输出。

5. set_default_headers()

6. write_error()

7. 调用顺序

我们通过一段代码来看上面这些接口的调用顺序。

“`python
class IndexHandler(RequestHandler):

def set_default_headers(self):
    print "调用了set_default_headers()" 

def initialize(self):
    print "调用了initialize()"

def prepare(self):
    print "调用了prepare()"

def get(self):
    print "调用了get()"

def post(self):
    print "调用了post()"
    self.send_error(200)  # 注意此出抛出了错误

def write_error(self, status_code, **kwargs):
    print "调用了write_error()"

def on_finish(self):
    print "调用了on_finish()"

“`

在正常情况未抛出错误时,调用顺序为:

  1. set_defautl_headers()
  2. initialize()
  3. prepare()
  4. HTTP方法
  5. on_finish()

在有错误抛出时,调用顺序为:

  1. set_default_headers()
  2. initialize()
  3. prepare()
  4. HTTP方法
  5. set_default_headers()
  6. write_error()
  7. on_finish()

先总结到这里,往后再补充

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值