upload
This commit is contained in:
152
app/subroutines/_c_route.c
Normal file
152
app/subroutines/_c_route.c
Normal file
@@ -0,0 +1,152 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define SEP 0x01
|
||||
#define FORMAT_START 0x03
|
||||
#define FORMAT_END 0x00
|
||||
#define DOUBLE_STAR 0x09
|
||||
|
||||
void free_parsed_route(char *parsed) {
|
||||
free(parsed);
|
||||
}
|
||||
|
||||
// Helper function to write an Exact component to the output buffer
|
||||
static void write_exact(const char *value, size_t value_len, char **output, size_t *output_len) {
|
||||
memcpy(*output, value, value_len);
|
||||
*output += value_len;
|
||||
**output = SEP;
|
||||
(*output)++;
|
||||
*output_len += value_len + 1;
|
||||
}
|
||||
|
||||
// Helper function to write a Format component to the output buffer
|
||||
static void write_format(const char *name, size_t name_len, size_t minl, size_t maxl, int ignore_sep, char **output, size_t *output_len) {
|
||||
// Write FORMAT_START marker
|
||||
**output = FORMAT_START;
|
||||
(*output)++;
|
||||
*output_len += 1;
|
||||
|
||||
// Write the name
|
||||
memcpy(*output, name, name_len);
|
||||
*output += name_len;
|
||||
**output = FORMAT_END;
|
||||
(*output)++;
|
||||
*output_len += name_len + 1;
|
||||
|
||||
// Write minl and maxl as size_t values
|
||||
memcpy(*output, &minl, sizeof(size_t));
|
||||
*output += sizeof(size_t);
|
||||
memcpy(*output, &maxl, sizeof(size_t));
|
||||
*output += sizeof(size_t);
|
||||
*output_len += 2 * sizeof(size_t);
|
||||
|
||||
// Write ignore_sep as a single byte
|
||||
**output = (char)ignore_sep;
|
||||
(*output)++;
|
||||
**output = SEP;
|
||||
(*output)++;
|
||||
*output_len += 2;
|
||||
}
|
||||
|
||||
// Main parsing function
|
||||
char *parse_route(const char *route, size_t route_len, size_t *output_len) {
|
||||
char *output = malloc(route_len * 8); // Allocate enough space for the output
|
||||
if (!output) return NULL;
|
||||
char *output_start = output;
|
||||
*output_len = 0;
|
||||
|
||||
const char *p = route;
|
||||
const char *end = route + route_len;
|
||||
|
||||
while (p < end) {
|
||||
if (*p == '/') {
|
||||
// Skip leading slashes
|
||||
write_exact("/", 1, &output, output_len);
|
||||
p++;
|
||||
continue;
|
||||
} else if (*p == '{') {
|
||||
// Handle Format component
|
||||
const char *start = p + 1; // Skip '{'
|
||||
const char *end_brace = memchr(start, '}', end - start);
|
||||
if (!end_brace) break; // Invalid format, stop parsing
|
||||
|
||||
// Extract name and constraints
|
||||
const char *colon = memchr(start, ':', end_brace - start);
|
||||
size_t name_len = colon ? (size_t)(colon - start) : (size_t)(end_brace - start);
|
||||
const char *name = start;
|
||||
size_t minl = 0, maxl = (size_t)-1, ignore_sep = 0;
|
||||
|
||||
if (colon) {
|
||||
const char *constraint = colon + 1;
|
||||
if (memchr(constraint, '-', end_brace - constraint)) {
|
||||
// Parse "minl-maxl"
|
||||
sscanf(constraint, "%zu-%zu", &minl, &maxl);
|
||||
} else {
|
||||
// Parse "minl" (fixed length)
|
||||
sscanf(constraint, "%zu", &minl);
|
||||
maxl = minl;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the Format component
|
||||
write_format(name, name_len, minl, maxl, ignore_sep, &output, output_len);
|
||||
|
||||
p = end_brace + 1; // Move past '}'
|
||||
} else if (*p == '*' && *(p + 1) == '*') {
|
||||
// Handle "**" as a special marker (\x09)
|
||||
*output = DOUBLE_STAR;
|
||||
output++;
|
||||
*output = SEP;
|
||||
output++;
|
||||
*output_len += 2;
|
||||
p += 2; // Move past "**"
|
||||
} else {
|
||||
// Handle Exact component
|
||||
const char *next_slash = memchr(p, '/', end - p);
|
||||
size_t segment_len = next_slash ? (size_t)(next_slash - p) : (size_t)(end - p);
|
||||
write_exact(p, segment_len, &output, output_len);
|
||||
p += segment_len;
|
||||
}
|
||||
}
|
||||
|
||||
// Null-terminate the output
|
||||
*output = '\0';
|
||||
*output_len += 1;
|
||||
|
||||
return output_start;
|
||||
}
|
||||
|
||||
// // Example usage
|
||||
// int main() {
|
||||
// const char *route = "/foo/{n:5}/bar/**/baz";
|
||||
// size_t route_len = strlen(route);
|
||||
// size_t output_len;
|
||||
|
||||
// char *result = parse_route(route, route_len, &output_len);
|
||||
// if (!result) {
|
||||
// fprintf(stderr, "Memory allocation failed\n");
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// printf("Parsed result (%zu bytes):\n", output_len);
|
||||
// for (size_t i = 0; i < output_len; i++) {
|
||||
// if (result[i] == SEP) {
|
||||
// printf("\\x01");
|
||||
// } else if (result[i] == FORMAT_START) {
|
||||
// printf("\\x03");
|
||||
// } else if (result[i] == FORMAT_END) {
|
||||
// printf("\\x00");
|
||||
// } else if (result[i] == DOUBLE_STAR) {
|
||||
// printf("\\x09");
|
||||
// } else {
|
||||
// printf("%c", result[i]);
|
||||
// }
|
||||
// }
|
||||
// printf("\n");
|
||||
|
||||
// free(result);
|
||||
// return 0;
|
||||
// }
|
||||
138
app/subroutines/http.py
Normal file
138
app/subroutines/http.py
Normal file
@@ -0,0 +1,138 @@
|
||||
from collections.abc import MutableMapping
|
||||
from dataclasses import InitVar, dataclass, field
|
||||
from http.cookies import BaseCookie
|
||||
from types import TracebackType
|
||||
from typing import Any, Self
|
||||
|
||||
from app.exceptions import ConnectionClosed
|
||||
from app.types_ import CommonMapping, ReceiveHTTP, Send
|
||||
|
||||
|
||||
class SimpleRequest:
|
||||
body: bytes
|
||||
body_parts: list[bytes]
|
||||
body_complete: bool
|
||||
done: bool
|
||||
keep_unset_body: bool = False
|
||||
|
||||
def __init__(self, keep_unset_body: bool | None = None) -> None:
|
||||
self.body = b""
|
||||
self.body_parts = []
|
||||
self.body_complete = False
|
||||
self.done = False
|
||||
if keep_unset_body is not None:
|
||||
self.keep_unset_body = keep_unset_body
|
||||
|
||||
def receive(self, received: ReceiveHTTP) -> bool:
|
||||
if received["type"] == "http.disconnect":
|
||||
self.done = True
|
||||
return True
|
||||
|
||||
if "body" in received:
|
||||
self.body_parts.append(received["body"])
|
||||
elif self.keep_unset_body:
|
||||
self.body_parts.append(b"")
|
||||
|
||||
if not ("more_body" in received and received["more_body"]):
|
||||
self.body = b"".join(self.body_parts)
|
||||
self.body_complete = True
|
||||
|
||||
return self.body_complete or self.done
|
||||
|
||||
|
||||
class SimpleResponse:
|
||||
send: Send
|
||||
headers: list[tuple[bytes, bytes]]
|
||||
status: int
|
||||
trailers: bool
|
||||
done: bool
|
||||
|
||||
def __init__(self, send: Send) -> None:
|
||||
self.send = send
|
||||
self.headers = []
|
||||
self.status = 200
|
||||
self.trailers = False
|
||||
self.done = False
|
||||
|
||||
def add_header(self, name: str, value: object) -> None:
|
||||
self.headers.append((name.lower().encode(), str(value).encode()))
|
||||
|
||||
def prepare(self, status: int = 200, trailers: bool = False, headers: MutableMapping[str, str] | None = None) -> Self:
|
||||
self.status = status
|
||||
self.trailers = trailers
|
||||
if headers:
|
||||
for k, v in headers.items():
|
||||
self.add_header(k, v)
|
||||
return self
|
||||
|
||||
async def start(self) -> None:
|
||||
if self.done:
|
||||
raise ConnectionClosed
|
||||
await self.send({
|
||||
"type": "http.response.start",
|
||||
"status": self.status,
|
||||
"headers": self.headers,
|
||||
"trailers": self.trailers
|
||||
})
|
||||
|
||||
async def body(self, data: bytes = b"", *, done: bool = True) -> None:
|
||||
if self.done:
|
||||
raise ConnectionClosed
|
||||
await self.send({
|
||||
"type": "http.response.body",
|
||||
"body": data,
|
||||
"more_body": not done
|
||||
})
|
||||
self.done = done and not self.trailers
|
||||
|
||||
async def part(self, data: bytes = b"") -> None:
|
||||
"""Same as `Response.body(data, done=False)`"""
|
||||
return await self.body(data, done=False)
|
||||
|
||||
async def finish(self, data: bytes = b"") -> None:
|
||||
"""Same as `Response.body(data, done=True)`"""
|
||||
return await self.body(data, done=True)
|
||||
|
||||
async def trail(self) -> None:
|
||||
if self.done:
|
||||
raise ConnectionClosed
|
||||
await self.send({"type": "http.response.trailers"})
|
||||
self.done = True
|
||||
|
||||
async def __aenter__(self) -> Self:
|
||||
await self.start()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /) -> bool | None:
|
||||
if not (self.done or exc_type):
|
||||
await self.finish()
|
||||
if exc_type and self.trailers:
|
||||
await self.trail()
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Response:
|
||||
status: int = 200
|
||||
body: bytes | None = None
|
||||
headers: CommonMapping = field(default_factory=dict[str, Any])
|
||||
content_type: InitVar[str | None] = None
|
||||
cookies: InitVar[BaseCookie[bytes] | None] = None
|
||||
|
||||
def __post_init__(self, content_type: str | None, cookies: BaseCookie[bytes] | None) -> None:
|
||||
if self.body is not None:
|
||||
self.headers["content-length"] = len(self.body)
|
||||
if content_type is not None:
|
||||
self.headers["content-type"] = content_type
|
||||
if cookies is not None:
|
||||
self.headers["set-cookie"] = cookies.output(header="").strip()
|
||||
|
||||
|
||||
@dataclass
|
||||
class HTMLResponse(Response):
|
||||
content: InitVar[str] = ""
|
||||
encoding: InitVar[str] = "utf-8"
|
||||
|
||||
def __post_init__(self, content_type: str | None, cookies: BaseCookie[bytes] | None, content: str, encoding: str) -> None:
|
||||
self.body: bytes | None = content.encode(encoding)
|
||||
return super().__post_init__("text/html", cookies)
|
||||
48
app/subroutines/route.py
Normal file
48
app/subroutines/route.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# / -> Exact("/")
|
||||
# /foo -> Exact("/foo")
|
||||
# /foo/ -> Exact("/foo/")
|
||||
# /foo/bar -> Exact("/foo/bar")
|
||||
# /foo/bar/{n} -> Exact("/foo/bar/"), Format("n")
|
||||
# /foo/{n}/bar -> Exact("/foo/"), Format("n"), Exact("/bar")
|
||||
# /foo{n:5}/bar -> Exact("/foo"), Format("n", minl=5, maxl=5), Exact("/bar")
|
||||
# /foo/{m:1-}/{n:3-5}bar -> Exact("/foo/"), Format("m", minl=1, maxl=-1), Exact("/"), Format("n", minl=3, maxl=5), Exact("bar")
|
||||
# /* -> Exact("/"), Format("_")
|
||||
# /foo/*bar -> Exact("/foo/"), Format("_"), Exact("bar")
|
||||
# /foo/*/bar -> Exact("/foo/"), Format("_"), Exact("/bar")
|
||||
# /foo/**/bar -> Exact("/foo/"), Format("_", ignore_sep=True), Exact("/bar")
|
||||
|
||||
# ["foo", Format("m", minl=1, maxl=-1), [Format("n", minl=3, maxl=5), "bar"], "*"]
|
||||
|
||||
|
||||
import ctypes
|
||||
|
||||
dll = ctypes.CDLL("./c_route.so")
|
||||
dll.parse_route.restype = type("c_char_alloc", (ctypes.c_char_p,), {})
|
||||
dll.free_parsed_route.argtypes = (ctypes.c_void_p,)
|
||||
dll.free_parsed_route.restype = None
|
||||
|
||||
|
||||
def compile_route(pattern: str) -> list[bytes]:
|
||||
if pattern[0] != "/":
|
||||
from app.exceptions import ParseError
|
||||
raise ParseError("The first character in route pattern must be '/'.")
|
||||
|
||||
# res: list[str] = []
|
||||
# for path in pattern[1:].split("/"):
|
||||
# if (star_idx := path.find("*")) != -1:
|
||||
# path = "".join((path[:star_idx], "{_}", path[star_idx + 1:]))
|
||||
# if "{" not in path and "}" not in path and "*" not in path:
|
||||
# res.append(path)
|
||||
|
||||
outlen = ctypes.c_size_t()
|
||||
data = pattern[1:].encode()
|
||||
ret = dll.parse_route(data, len(data), ctypes.byref(outlen))
|
||||
res = ctypes.string_at(ret, outlen.value - 2).split(b"\x01")
|
||||
dll.free_parsed_route(ret)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(compile_route("/foo/{n:5}foo/bar/**/baz"))
|
||||
print(compile_route("/"))
|
||||
Reference in New Issue
Block a user