Create basic request/response abstract models
This commit is contained in:
@@ -2,34 +2,18 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from turbosloth import SlothApp
|
from turbosloth import SlothApp
|
||||||
from turbosloth.exceptions import NotFoundException
|
from turbosloth.exceptions import NotFoundException
|
||||||
from turbosloth.types import Scope, Receive, Send
|
from turbosloth.types import Scope, Receive, Send, BasicRequest, BasicResponse
|
||||||
|
|
||||||
app = SlothApp()
|
app = SlothApp()
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def index(scope: Scope, receive: Receive, send: Send):
|
async def index(req: BasicRequest) -> BasicResponse:
|
||||||
body = b"Hello, ASGI Router!"
|
return BasicResponse(200, {}, b'Hello, ASGI Router!')
|
||||||
await send({
|
|
||||||
"type": "http.response.start",
|
|
||||||
"status": 200,
|
|
||||||
"headers": [(b"content-type", b"text/plain")],
|
|
||||||
})
|
|
||||||
await send({
|
|
||||||
"type": "http.response.body",
|
|
||||||
"body": body,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/user/")
|
@app.get("/user/")
|
||||||
async def get_user(scope: Scope, receive: Receive, send: Send):
|
async def get_user(req: BasicRequest) -> BasicResponse:
|
||||||
text = f"User ID: ".encode("utf-8")
|
text = f"User ID: ".encode("utf-8")
|
||||||
await send({
|
|
||||||
"type": "http.response.start",
|
return BasicResponse(200, {}, b'Hello, User!')
|
||||||
"status": 200,
|
|
||||||
"headers": [(b"content-type", b"text/plain")],
|
|
||||||
})
|
|
||||||
await send({
|
|
||||||
"type": "http.response.body",
|
|
||||||
"body": text,
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import json
|
||||||
from typing import Optional, Callable, Awaitable, Protocol
|
from typing import Optional, Callable, Awaitable, Protocol
|
||||||
|
|
||||||
from .exceptions import HTTPException
|
from .exceptions import HTTPException
|
||||||
from .router import Router
|
from .router import Router
|
||||||
from .types import Scope, Receive, Send, MethodType, HandlerType
|
from .types import Scope, Receive, Send, MethodType, HandlerType, BasicRequest, BasicResponse
|
||||||
|
|
||||||
|
|
||||||
class ASGIApp(Protocol):
|
class ASGIApp(Protocol):
|
||||||
@@ -17,20 +18,27 @@ class HTTPApp(ASGIApp):
|
|||||||
method = scope['method']
|
method = scope['method']
|
||||||
path = scope['path']
|
path = scope['path']
|
||||||
|
|
||||||
|
body = b''
|
||||||
|
while True:
|
||||||
|
event = await receive()
|
||||||
|
body += event.get('body', b'')
|
||||||
|
if not event.get('more_body', False):
|
||||||
|
break
|
||||||
|
scope['body'] = body
|
||||||
|
|
||||||
|
req = BasicRequest.from_scope(scope)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handler = self.router.match(method, path)
|
handler = self.router.match(method, path)
|
||||||
await handler(scope, receive, send)
|
resp = await handler(req)
|
||||||
except HTTPException as e:
|
except HTTPException as e:
|
||||||
await send({
|
resp = BasicResponse(e.code, {'content-type': 'text/plain'}, str(e).encode())
|
||||||
'type': 'http.response.start',
|
|
||||||
'status': e.code,
|
await send(resp.into_start_message())
|
||||||
'headers': [(b'content-type', b'text/plain')],
|
await send({
|
||||||
})
|
'type': 'http.response.body',
|
||||||
await send({
|
'body': resp.body,
|
||||||
'type': 'http.response.body',
|
})
|
||||||
'body': str(e).encode(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class WSApp(ASGIApp):
|
class WSApp(ASGIApp):
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
from typing import Callable, Awaitable, Dict, Literal
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable, Awaitable, Dict, Literal, Any, TypeVar
|
||||||
|
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
type Scope = Dict
|
type Scope = Dict
|
||||||
type Receive = Callable[[], Awaitable[Dict]]
|
type Receive = Callable[[], Awaitable[Dict]]
|
||||||
type Send = Callable[[Dict], Awaitable[None]]
|
type Send = Callable[[Dict], Awaitable[None]]
|
||||||
type HandlerType = Callable[[Scope, Receive, Send], Awaitable[None]]
|
type HandlerType = Callable[[BasicRequest], Awaitable[BasicResponse]]
|
||||||
|
|
||||||
type MethodType = (
|
type MethodType = (
|
||||||
Literal['GET'] |
|
Literal['GET'] |
|
||||||
@@ -16,3 +22,47 @@ type MethodType = (
|
|||||||
Literal['CONNECT'] |
|
Literal['CONNECT'] |
|
||||||
Literal['OPTIONS'] |
|
Literal['OPTIONS'] |
|
||||||
Literal['TRACE'])
|
Literal['TRACE'])
|
||||||
|
|
||||||
|
K = TypeVar('K')
|
||||||
|
V = TypeVar('V')
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BasicRequest:
|
||||||
|
method: str
|
||||||
|
path: str
|
||||||
|
headers: dict[str, str]
|
||||||
|
query: dict[str, list[str]]
|
||||||
|
body: dict[str, Any]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_scope(cls, scope: Scope) -> BasicRequest:
|
||||||
|
path = scope['path']
|
||||||
|
method = scope['method']
|
||||||
|
headers = {}
|
||||||
|
for key, value in scope.get('headers', []):
|
||||||
|
headers[key.decode('latin1')] = value.decode('latin1')
|
||||||
|
|
||||||
|
query = urllib.parse.parse_qs(scope['query_string'].decode('latin1'))
|
||||||
|
|
||||||
|
body = scope['body']
|
||||||
|
|
||||||
|
return BasicRequest(method, path, headers, query, body)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BasicResponse:
|
||||||
|
code: int
|
||||||
|
headers: dict[str, str]
|
||||||
|
body: bytes
|
||||||
|
|
||||||
|
def into_start_message(self) -> dict:
|
||||||
|
enc_headers = []
|
||||||
|
for k, v in self.headers.items():
|
||||||
|
enc_headers.append((k.encode('latin1'), v.encode('latin1')))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'http.response.start',
|
||||||
|
'status': self.code,
|
||||||
|
'headers': enc_headers
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user