Skip to main content

远程 HTTP 模拟

项目描述

jj

编解码器 派皮 PyPI - 下载 蟒蛇版本

安装

pip3 install jj

用法

import jj

@jj.match("*")
async def handler(request: jj.Request) -> jj.Response:
    return jj.Response(body="200 OK")

jj.serve()

文档


匹配器

方法

匹配方法(method
from jj.http.methods import GET

@jj.match_method(GET)
async def handler(request):
    return jj.Response(body="Method: " + request.method)
匹配方法(methods
from jj.http.methods import PUT, PATCH

@jj.match_methods(PUT, PATCH)
async def handler(request):
    return jj.Response(body="Method: " + request.method)

小路

匹配路径(path
@jj.match_path("/users")
async def handler(request):
    return jj.Response(body="Path: " + request.path)

细分市场

@jj.match_path("/users/{users_id}")
async def handler(request):
    return jj.Response(body=f"Segments: {request.segments}")

此处提供更多信息https://docs.aiohttp.org/en/stable/web_quickstart.html#variable-resources

参数

匹配参数( name, val)
@jj.match_param("locale", "en_US")
async def handler(request):
    locales = request.params.getall('locale')
    return jj.Response(body="Locales: " + ",".join(locales))
匹配参数(params
@jj.match_params({"locale": "en_US", "timezone": "UTC"})
async def handler(request):
    # Literal String Interpolation (PEP 498)
    return jj.Response(body=f"Params: {request.params}")

标头

match_header( name, val)
@jj.match_header("X-Forwarded-Proto", "https")
async def handler(request):
    proto = request.headers.getone("X-Forwarded-Proto")
    return jj.Response(body="Proto: " + proto)
匹配标题(headers
@jj.match_headers({
    "x-user-id": "1432",
    "x-client-id": "iphone",
})
async def handler(request):
    return jj.Response(body=f"Headers: {request.headers}")

组合匹配器

匹配任意(matchers
from jj.http import PATCH, PUT

@jj.match_any([
    jj.match_method(PUT),
    jj.match_method(PATCH),
])
async def handler(request):
    return jj.Response(body="200 OK")
全部匹配(matchers
@jj.match_all([
    jj.match_method("*"),
    jj.match_path("/"),
    jj.match_params({"locale": "en_US"}),
    jj.match_headers({"x-request-id": "0fefbf48"}),
])
async def handler(request):
    return jj.Response(body="200 OK")
匹配(method,,,,)path_paramsheaders
@jj.match("*", "/", {"locale": "en_US"}, {"x-request-id": "0fefbf48"})
async def handler(request):
    return jj.Response(body="200 OK")

回应

回复

JSON 响应
@jj.match("*")
async def handler(request):
    return jj.Response(json={"message": "200 OK"})
HTML 响应
@jj.match("*")
async def handler(request):
    return jj.Response(body="<p>text<p>", headers={"Content-Type": "text/html"})
二进制响应
@jj.match("*")
async def handler(request):
    return jj.Response(body=b"<binary>")
未找到响应
@jj.match("*")
async def handler(request):
    return jj.Response(status=404, reason="Not Found")
预定义的主体
from jj.http import GET

@jj.match(GET, "/users")
async def handler(request):
    return jj.Response(body=open("responses/users.json", "rb"))
from jj.http import POST, CREATED

@jj.match(POST, "/users")
async def handler(request):
    return jj.Response(body=open("responses/created.json", "rb"), status=CREATED)

静态响应

内联内容
from jj.http import GET

@jj.match(GET, "/image")
async def handler(request):
    return jj.StaticResponse("public/image.jpg")
可下载文件
from jj.http import GET

@jj.match(GET, "/report")
async def handler(request):
    return jj.StaticResponse("public/report.csv", attachment=True)
from jj.http import GET

@jj.match(GET, "/")
async def handler(request):
    return jj.StaticResponse("public/report.csv", attachment="report.csv")

欲了解更多信息,请访问https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition

中继响应β

@jj.match("*")
async def handler(request):
    return jj.RelayResponse(target="https://httpbin.org/")

应用

单一应用

import jj
from jj.http.methods import GET, ANY
from jj.http.codes import OK, NOT_FOUND

class App(jj.App):
    @jj.match(GET, "/")
    async def root_handler(self, request: jj.Request) -> jj.Response:
        return jj.Response(status=OK, json={"message": "200 OK"})

    @jj.match(ANY)
    async def default_handler(self, request: jj.Request) -> jj.Response:
        return jj.Response(status=NOT_FOUND, json={"message": "Not Found"})

jj.serve(App(), port=5000)

多个应用程序

import jj

class App(jj.App):
    @jj.match("*")
    async def handler(self, request: jj.Request) -> jj.Response:
        return jj.Response(body="App")

class AnotherApp(jj.App):
    @jj.match("*")
    async def handler(self, request: jj.Request) -> jj.Response:
        return jj.Response(body="AnotherApp")

jj.start(App(), port=5001)
jj.start(AnotherApp(), port=5002)

jj.wait_for([KeyboardInterrupt])

应用继承

import jj

class UsersApp(jj.App):
    @jj.match("*", path="/users")
    async def handler(self, request: jj.Request) -> jj.Response:
        return jj.Response(body="Users")

class GroupsApp(jj.App):
    @jj.match("*", path="/groups")
    async def handler(self, request: jj.Request) -> jj.Response:
        return jj.Response(body="Groups")

class App(UsersApp, GroupsApp):
    pass

jj.serve(App())

中间件

处理程序中间件

import jj
from jj.http.codes import OK, FORBIDDEN

class Middleware(jj.Middleware):
    async def do(self, request, handler, app):
        if request.headers.get("x-secret-key") != "<SECRET_KEY>":
            return jj.Response(status=FORBIDDEN, body="Forbidden")
        return await handler(request)

class App(jj.App):
    @Middleware()
    @jj.match("*")
    async def handler(self, request: jj.Request) -> jj.Response:
        return jj.Response(status=OK, body="Ok")

jj.serve(App())

应用中间件

import jj
from jj.http.codes import OK, FORBIDDEN

class ReusableMiddleware(jj.Middleware):
    def __init__(self, secret_key):
        super().__init__()
        self._secret_key = secret_key

    async def do(self, request, handler, app):
        if request.headers.get("x-secret-key") != self._secret_key:
            return jj.Response(status=FORBIDDEN, body="Forbidden")
        return await handler(request)

private = ReusableMiddleware("<SECRET_KEY>")

@private
class App(jj.App):
    @jj.match("*")
    async def handler(self, request: jj.Request) -> jj.Response:
        return jj.Response(status=OK, body="Ok")

jj.serve(App())

远程模拟

服务器端

启动远程模拟
import jj
from jj.mock import Mock

jj.serve(Mock(), port=8080)

或通过码头工人

docker run -p 8080:80 nikitanovosibirsk/jj

客户端

import asyncio

import jj
from jj.mock import mocked


async def main():
    matcher = jj.match("GET", "/users")
    response = jj.Response(status=200, json=[])

    async with mocked(matcher, response) as mock:
        # Request GET /users
        # Returns status=200 body=[]
    assert len(mock.history) == 1

asyncio.run(main())

使用jj-district42测试请求

低级 API
注册远程处理程序
import asyncio

import jj
from jj.mock import RemoteMock


async def main():
    remote_mock = RemoteMock("http://localhost:8080")

    matcher = jj.match("GET", "/users")
    response = jj.Response(status=200, json=[])
    remote_handler = remote_mock.create_handler(matcher, response)
    await remote_handler.register()

    # Request GET /users
    # Returns status=200 body=[]

asyncio.run(main())
注销远程处理程序
import asyncio

import jj
from jj.mock import RemoteMock


async def main():
    remote_mock = RemoteMock("http://localhost:8080")

    matcher = jj.match("GET", "/users")
    response = jj.Response(status=200, json=[])
    remote_handler = remote_mock.create_handler(matcher, response)
    await remote_handler.register()

    # Request GET /users
    # Returns status=200 body=[]

    await remote_handler.deregister()

asyncio.run(main())
检索远程处理程序历史
import asyncio

import jj
from jj.mock import RemoteMock


async def main():
  remote_mock = RemoteMock("http://localhost:8080")

  matcher = jj.match("GET", "/users")
  response = jj.Response(status=200, json=[])
  remote_handler = remote_mock.create_handler(matcher, response)
  await remote_handler.register()

  # Request GET /users
  # Returns status=200 body=[]

  history = await remote_handler.fetch_history()
  print(history)

  await remote_handler.deregister()

asyncio.run(main())

历史:

[
    {
        'request': HistoryRequest(
            method='GET',
            path='/users',
            params=<MultiDictProxy()>,
            headers=<CIMultiDictProxy('Host': 'localhost:8080',
                                      'Accept': '*/*',
                                      'Accept-Encoding': 'gzip, deflate',
                                      'User-Agent': 'Python/3.8 aiohttp/3.7.3')>,
            body=b'',
        ),
        'response': HistoryResponse(
            status=200,
            reason='OK',
            headers=<CIMultiDictProxy('Content-Type': 'application/json',
                                      'Server': 'jj via aiohttp/3.7.3',
                                      'Content-Length': '2',
                                      'Date': 'Sun, 09 May 2021 08:08:19 GMT')>,
            body=b'[]',
        ),
        'tags': ['f75c2ab7-f68d-4b4a-85e0-1f38bb0abe9a']
    }
]

到期政策

import jj
from jj.mock import mocked
from jj.expiration_policy import ExpireAfterRequests
from httpx import AsyncClient

matcher = jj.match("GET", "/")
response = jj.Response(status=200)
policy = ExpireAfterRequests(1)

async with mocked(matcher, response, expiration_policy=policy):
    async with AsyncClient() as client:
        resp1 = await client.get("/")
        assert resp1.status_code == 200
        # expired
        resp2 = await client.get("/")
        assert resp2.status_code == 404

自定义记录器

导入 日志

 jj.logs导入jj
 jj.mock 导入 SimpleFormatter
导入Mock SystemLogFilter    


 格式化程序SimpleFormatter 
    def  format_request self  request  jj.Request record logging.LogRecord - > str return f - > { request.method } { request.url.path_qs } { request.headers } _ _  _ _ _ _ _