2024年06月13日

后端服务器数据验证和序列化

前端浏览器传来的数据是使用json格式的字符串,而后端服务器调用函数时是需要函数参数的,那么如何将json中的数据转化成为函数调用的参数就成了一个问题。本文主要使用python作为编程语言,aiohttp来作为服务器框架。

基本方法

最简单的方法就是在每一个endpoint中从传来的request直接抽取参数,但是这样的话代码很麻烦,需要检查传来的参数项是否多了还是少了,类型是否正确,然后返回相应的状态码。

就像下面代码一样1:

from aiohttp import web
from model.foo_model import FooModel
from service.foo_service import FooService
class RequestHandler(web.View):
    async def post(self):
        self.service = self.request.app[FooService]
        self.api_version = request.match_info.get("api_version", 1)
        self.name = request.match_info.get("name", None)
        try:
            body = await self.request.json()
            foo_model = FooModel(
                name=self.name, version=self.api_version, **body
            )
        except ValueError as ex:
            raise web.HTTPBadRequest(
                reason=f"Failed to process request body."
            ) from ex
        await self.service.persist(foo_model)

使用类或wrapper进行封装

之前在廖雪峰Python教程的服务器开发实战中无意实现了类似于Data Serialization的功能2。他在教程中很简单地讲了一下,也就是将每个handler函数的参数名称给提取出来,然后在调用的时候从requst中自动提取数据,然后再用这些数据调用函数(他教程里的@get@post也是自己实现的,新版aiohttp已经实现了)。

@get('/api/comments')
def api_comments(*, page='1'):
    pass

URL处理函数不一定是一个coroutine,因此我们用RequestHandler()来封装一个URL处理函数。 RequestHandler是一个类,由于定义了__call__()方法,因此可以将其实例视为函数。

RequestHandler目的就是从URL函数中分析其需要接收的参数,从request中获取必要的参数,调用URL函数,然后把结果转换为web.Response对象,这样,就完全符合aiohttp框架的要求:

class RequestHandler(object):
    def __init__(self, app, fn):
    self._app = app
    self._func = fn
    ...

@asyncio.coroutine
def __call__(self, request):
    kw = ... 获取参数
    r = yield from self._func(**kw)
    return r

Serializing/Deserializing

将数据从request中转化为python相应类型数据叫做Serializing,将python中的数据转化为response的数据叫做Deserializing。很多服务器框架集成了相关的工作,比如Django Rest Framework

FastAPI使用了Pydantic来做数据序列化和验证3,不会过度影响业务逻辑,这使得代码更加简洁高效。考虑到FastAPI的热度,开发的便利性,并且在某些榜单上的测试性能上超过aiohttp,目前考虑将FastAPI纳入技术栈中。

Powered by Mume. Copyright © 2019-2024.

Euruson. All rights reserved.