diff --git a/src/socketify/loop.py b/src/socketify/loop.py index 883a25a..fda8ce1 100644 --- a/src/socketify/loop.py +++ b/src/socketify/loop.py @@ -34,12 +34,17 @@ class Loop: asyncio.set_event_loop(self.loop) self.started = False + self.last_defer = False # self.loop_thread = None + def create_future(self): + return self.loop.create_future() + def start(self): self.started = True + #start relaxed until first task self.timer = self.uv_loop.create_timer(0, 100, lambda loop: loop.run_once_asyncio(), self) - + def run(self): self.uv_loop.run() @@ -50,6 +55,13 @@ class Loop: #run only one step self.loop.call_soon(self.loop.stop) self.loop.run_forever() + if self.started: + pending = len(asyncio.all_tasks(self.loop)) + if pending < 1: #relaxed if has no tasks + self.timer.set_repeat(100) + else: #urge when needs + self.timer.set_repeat(1) + def stop(self): if(self.started): self.timer.stop() @@ -59,6 +71,7 @@ class Loop: if self.loop.is_running(): self.loop.stop() + self.last_defer = None # Find all running tasks in main thread: pending = asyncio.all_tasks(self.loop) # Run loop until tasks done @@ -74,6 +87,9 @@ class Loop: #with threads future.add_done_callback(lambda f: future_handler(f, self.loop, self.exception_handler, response)) + + #force asyncio run once to enable req in async functions before first await + self.run_once_asyncio() return future diff --git a/src/socketify/native/src/libsocketify.c b/src/socketify/native/src/libsocketify.c index 24c0426..82f4e49 100644 --- a/src/socketify/native/src/libsocketify.c +++ b/src/socketify/native/src/libsocketify.c @@ -89,7 +89,7 @@ void socketify_destroy_loop(socketify_loop* loop){ free(loop); } -socketify_timer* socketify_create_timer(socketify_loop* loop, int64_t timeout, int64_t repeat, socketify_timer_handler handler, void* user_data){ +socketify_timer* socketify_create_timer(socketify_loop* loop, uint64_t timeout, uint64_t repeat, socketify_timer_handler handler, void* user_data){ uv_timer_t* uv_timer = malloc(sizeof(uv_timer_t)); // uv_timer_init(loop->uv_loop, uv_timer); @@ -109,6 +109,12 @@ socketify_timer* socketify_create_timer(socketify_loop* loop, int64_t timeout, i return timer; } +void socketify_timer_set_repeat(socketify_timer* timer, uint64_t repeat){ + uv_timer_set_repeat( timer->uv_timer_ptr, repeat); +} + + + //stops and destroy timer info void socketify_timer_destroy(socketify_timer* timer){ uv_timer_stop(timer->uv_timer_ptr); diff --git a/src/socketify/native/src/libsocketify.h b/src/socketify/native/src/libsocketify.h index fb0e06d..45679f1 100644 --- a/src/socketify/native/src/libsocketify.h +++ b/src/socketify/native/src/libsocketify.h @@ -26,6 +26,8 @@ typedef struct{ void* user_data; } socketify_timer; + + socketify_loop * socketify_create_loop(); bool socketify_constructor_failed(socketify_loop* loop); bool socketify_on_prepare(socketify_loop* loop, socketify_prepare_handler handler, void* user_data); @@ -36,6 +38,8 @@ void* socketify_get_native_loop(socketify_loop* loop); int socketify_loop_run(socketify_loop* loop, socketify_run_mode mode); void socketify_loop_stop(socketify_loop* loop); -socketify_timer* socketify_create_timer(socketify_loop* loop, int64_t timeout, int64_t repeat, socketify_timer_handler handler, void* user_data); +socketify_timer* socketify_create_timer(socketify_loop* loop, uint64_t timeout, uint64_t repeat, socketify_timer_handler handler, void* user_data); void socketify_timer_destroy(socketify_timer* timer); +void socketify_timer_set_repeat(socketify_timer* timer, uint64_t repeat); + #endif \ No newline at end of file diff --git a/src/socketify/native/uv.py b/src/socketify/native/uv.py index 1085c83..45b2ac6 100644 --- a/src/socketify/native/uv.py +++ b/src/socketify/native/uv.py @@ -8,6 +8,7 @@ ffi.cdef(""" typedef void (*socketify_prepare_handler)(void* user_data); typedef void (*socketify_timer_handler)(void* user_data); +typedef void (*socketify_async_handler)(void* user_data); typedef enum { SOCKETIFY_RUN_DEFAULT = 0, @@ -28,6 +29,12 @@ typedef struct{ void* user_data; } socketify_timer; +typedef struct{ + void* uv_async_ptr; + socketify_async_handler handler; + void* user_data; +} socketify_async; + socketify_loop * socketify_create_loop(); bool socketify_constructor_failed(socketify_loop* loop); bool socketify_on_prepare(socketify_loop* loop, socketify_prepare_handler handler, void* user_data); @@ -38,9 +45,10 @@ void* socketify_get_native_loop(socketify_loop* loop); int socketify_loop_run(socketify_loop* loop, socketify_run_mode mode); void socketify_loop_stop(socketify_loop* loop); -socketify_timer* socketify_create_timer(socketify_loop* loop, int64_t timeout, int64_t repeat, socketify_timer_handler handler, void* user_data); +socketify_timer* socketify_create_timer(socketify_loop* loop, uint64_t timeout, uint64_t repeat, socketify_timer_handler handler, void* user_data); void socketify_timer_destroy(socketify_timer* timer); - +bool socketify_async_call(socketify_loop* loop, socketify_async_handler handler, void* user_data); +void socketify_timer_set_repeat(socketify_timer* timer, uint64_t repeat); """) library_path = os.path.join(os.path.dirname(__file__), "libsocketify.so") @@ -56,17 +64,20 @@ def socketify_generic_handler(data): class UVTimer: def __init__(self, loop, timeout, repeat, handler, user_data): self._handler_data = ffi.new_handle((handler, user_data)) - self._ptr = lib.socketify_create_timer(loop, ffi.cast("int64_t", timeout), ffi.cast("int64_t", repeat), socketify_generic_handler, self._handler_data) + self._ptr = lib.socketify_create_timer(loop, ffi.cast("uint64_t", timeout), ffi.cast("uint64_t", repeat), socketify_generic_handler, self._handler_data) def stop(self): lib.socketify_timer_destroy(self._ptr) self._handler_data = None self._ptr = ffi.NULL + def set_repeat(self, repeat): + lib.socketify_timer_set_repeat(self._ptr, ffi.cast("uint64_t", repeat)) + def __del__(self): if self._ptr != ffi.NULL: lib.socketify_timer_destroy(self._ptr) - self.self._handler_data = None + self._handler_data = None class UVLoop: @@ -90,7 +101,7 @@ class UVLoop: def __del__(self): lib.socketify_destroy_loop(self._loop) - self.self._handler_data = None + self._handler_data = None def run(self): return lib.socketify_loop_run(self._loop, lib.SOCKETIFY_RUN_DEFAULT) diff --git a/src/socketify/socketify.py b/src/socketify/socketify.py index 56357b3..ea06dd2 100644 --- a/src/socketify/socketify.py +++ b/src/socketify/socketify.py @@ -5,7 +5,8 @@ from .status_codes import status_codes import json import inspect import signal - +from http import cookies +from datetime import datetime ffi = cffi.FFI() ffi.cdef(""" @@ -220,7 +221,7 @@ def uws_generic_method_handler(res, req, user_data): try: if inspect.iscoroutinefunction(handler): response.grab_aborted_handler() - app.run_async(handler(response, request), response) + response.run_async(handler(response, request)) else: handler(response, request) except Exception as err: @@ -236,7 +237,7 @@ def uws_generic_ssl_method_handler(res, req, user_data): try: if inspect.iscoroutinefunction(handler): response.grab_aborted_handler() - app.run_async(handler(response, request), response) + response.run_async(handler(response, request)) else: handler(response, request) except Exception as err: @@ -261,46 +262,112 @@ def uws_generic_aborted_handler(response, user_data): res = ffi.from_handle(user_data) res.trigger_aborted() +@ffi.callback("void(uws_res_t *, const char *, size_t, bool, void*)") +def uws_generic_on_data_handler(res, chunk, chunk_length, is_end, user_data): + if not user_data == ffi.NULL: + res = ffi.from_handle(user_data) + if chunk == ffi.NULL: + data = None + else: + data = ffi.unpack(chunk, chunk_length) + + res.trigger_data_handler(data, bool(is_end)) + +@ffi.callback("bool(uws_res_t *, uintmax_t, void*)") +def uws_generic_on_writable_handler(res, offset, user_data): + if not user_data == ffi.NULL: + res = ffi.from_handle(user_data) + return res.trigger_writable_handler(offset) + return False + class AppRequest: def __init__(self, request): self.req = request + self.read_jar = None + self.jar_parsed = False + + def get_cookie(self, name): + if self.read_jar == None: + if self.jar_parsed: + return None + raw_cookies = self.get_header("cookie") + if raw_cookies: + self.jar_parsed = True + self.read_jar = cookies.SimpleCookie(raw_cookies) + else: + self.jar_parsed = True + return None + try: + return self.read_jar[name].value + except Exception: + return None + def get_url(self): buffer = ffi.new("char**") length = lib.uws_req_get_url(self.req, buffer) buffer_address = ffi.addressof(buffer, 0)[0] if buffer_address == ffi.NULL: return None - return ffi.unpack(buffer_address, length).decode("utf-8") + try: + return ffi.unpack(buffer_address, length).decode("utf-8") + except Exception: #invalid utf-8 + return None def get_method(self): buffer = ffi.new("char**") length = lib.uws_req_get_method(self.req, buffer) buffer_address = ffi.addressof(buffer, 0)[0] if buffer_address == ffi.NULL: return None - return ffi.unpack(buffer_address, length).decode("utf-8") + + try: + return ffi.unpack(buffer_address, length).decode("utf-8") + except Exception: #invalid utf-8 + return None def get_header(self, lower_case_header): + if isinstance(lower_case_header, str): + data = lower_case_header.encode("utf-8") + elif isinstance(lower_case_header, bytes): + data = lower_case_header + else: + data = json.dumps(lower_case_header).encode("utf-8") + buffer = ffi.new("char**") - data = lower_case_header.encode("utf-8") length = lib.uws_req_get_header(self.req, data, len(data), buffer) buffer_address = ffi.addressof(buffer, 0)[0] if buffer_address == ffi.NULL: return None - return ffi.unpack(buffer_address, length).decode("utf-8") + try: + return ffi.unpack(buffer_address, length).decode("utf-8") + except Exception: #invalid utf-8 + return None def get_query(self, key): buffer = ffi.new("char**") - data = key.encode("utf-8") - length = lib.uws_req_get_query(self.req, data, len(data), buffer) + + if isinstance(key, str): + key_data = key.encode("utf-8") + elif isinstance(key, bytes): + key_data = key + else: + key_data = json.dumps(key).encode("utf-8") + + length = lib.uws_req_get_query(self.req, key_data, len(key_data), buffer) buffer_address = ffi.addressof(buffer, 0)[0] if buffer_address == ffi.NULL: return None - return ffi.unpack(buffer_address, length).decode("utf-8") + try: + return ffi.unpack(buffer_address, length).decode("utf-8") + except Exception: #invalid utf-8 + return None def get_parameter(self, index): buffer = ffi.new("char**") length = lib.uws_req_get_parameter(self.req, ffi.cast("unsigned short", index), buffer) buffer_address = ffi.addressof(buffer, 0)[0] if buffer_address == ffi.NULL: return None - return ffi.unpack(buffer_address, length).decode("utf-8") + try: + return ffi.unpack(buffer_address, length).decode("utf-8") + except Exception: #invalid utf-8 + return None def set_yield(self, has_yield): lib.uws_req_set_field(self.req, 1 if has_yield else 0) def get_yield(self): @@ -316,23 +383,105 @@ class AppResponse: self.aborted = False self._ptr = ffi.NULL self.loop = loop + self._aborted_handler = None + self._writable_handler = None + self._data_handler = None + self._ptr = ffi.new_handle(self) + self._grabed_abort_handler_once = False + self._write_jar = None + + def set_cookie(self, name, value, options={}): + if self._write_jar == None: + self._write_jar = cookies.SimpleCookie() + self._write_jar[name] = value + if isinstance(options, dict): + for key in options: + if key == "expires" and isinstance(options[key], datetime): + self._write_jar[name][key] = options[key].strftime("%a, %d %b %Y %H:%M:%S GMT") + else: + self._write_jar[name][key] = options[key] def trigger_aborted(self): self.aborted = True self.res = ffi.NULL self._ptr = ffi.NULL if hasattr(self, "_aborted_handler") and hasattr(self._aborted_handler, '__call__'): - self._aborted_handler() + try: + if inspect.iscoroutinefunction(self._aborted_handler): + self.run_async(self._aborted_handler(self)) + else: + self._aborted_handler(self) + except Exception as err: + print("Error on abort handler %s" % str(err)) return self + def trigger_data_handler(self, data, is_end): + if self.aborted: + return self + if hasattr(self, "_data_handler") and hasattr(self._data_handler, '__call__'): + try: + if inspect.iscoroutinefunction(self._data_handler): + self.run_async(self._data_handler(self, data, is_end)) + else: + self._data_handler(self, data, is_end) + except Exception as err: + print("Error on data handler %s" % str(err)) + + return self + + def trigger_writable_handler(self, offset): + if self.aborted: + return False + if hasattr(self, "_writable_handler") and hasattr(self._writable_handler, '__call__'): + try: + if inspect.iscoroutinefunction(self._writable_handler): + raise RuntimeError("AppResponse.on_writable must be synchronous") + return self._writable_handler(self, offset) + except Exception as err: + print("Error on writable handler %s" % str(err)) + return False + return False + def run_async(self, task): self.grab_aborted_handler() return self.loop.run_async(task, self) + + async def get_text(self, encoding='utf-8'): + data = await self.get_data() + try: + return b''.join(data).decode(encoding) + except Exception: + return None #invalid encoding + + async def get_json(self): + data = await self.get_data() + try: + return json.loads(b''.join(data).decode('utf-8')) + except Exception: + return None #invalid json + + + def get_data(self): + future = self.loop.create_future() + data = [] + def is_aborted(res): + future.set_result(data) + + def get_chunks(res, chunk, is_end): + data.append(chunk) + if is_end: + future.set_result(data) + + self.on_aborted(is_aborted) + self.on_data(get_chunks) + return future + + def grab_aborted_handler(self): #only needed if is async - if self._ptr == ffi.NULL and not self.aborted: - self._ptr = ffi.new_handle(self) + if not self.aborted and not self._grabed_abort_handler_once: + self._grabed_abort_handler_once = True lib.uws_res_on_aborted(self.SSL, self.res, uws_generic_aborted_handler, self._ptr) def redirect(self, location, status_code=302): @@ -342,6 +491,9 @@ class AppResponse: def end(self, message, end_connection=False): if not self.aborted: + if self._write_jar != None: + self.write_header("Set-Cookie", self._write_jar.output(header="")) + if isinstance(message, str): data = message.encode("utf-8") elif isinstance(message, bytes): @@ -350,7 +502,7 @@ class AppResponse: self.end_without_body(end_connection) return self else: - self.write_header("Content-Type", "application/json") + self.write_header(b'Content-Type', b'application/json') data = json.dumps(message).encode("utf-8") lib.uws_res_end(self.SSL, self.res, data, len(data), 1 if end_connection else 0) return self @@ -370,11 +522,17 @@ class AppResponse: lib.uws_res_write_continue(self.SSL, self.res) return self + # /* Try and end the response. Returns [true, true] on success. + # * Starts a timeout in some cases. Returns [ok, hasResponded] */ + # std::pair tryEnd(std::string_view data, uintmax_t totalSize = 0) { + # return {internalEnd(data, totalSize, true), hasResponded()}; + # } + def write_status(self, status_or_status_text): if not self.aborted: if isinstance(status_or_status_text, int): try: - data = status_codes[status_or_status_text].encode("utf-8") + data = status_codes[status_or_status_text] except: #invalid status raise RuntimeError("\"%d\" Is not an valid Status Code" % status_or_status_text) elif isinstance(status_text, str): @@ -389,7 +547,13 @@ class AppResponse: def write_header(self, key, value): if not self.aborted: - key_data = key.encode("utf-8") + if isinstance(key, str): + key_data = key.encode("utf-8") + elif isinstance(key, bytes): + key_data = key + else: + key_data = json.dumps(key).encode("utf-8") + if isinstance(value, int): lib.uws_res_write_header_int(self.SSL, self.res, key_data, len(key_data), ffi.cast("uint64_t", value)) elif isinstance(value, str): @@ -403,6 +567,8 @@ class AppResponse: def end_without_body(self, end_connection=False): if not self.aborted: + if self._write_jar != None: + self.write_header("Set-Cookie", self._write_jar.output(header="")) lib.uws_res_end_without_body(self.SSL, self.res, 1 if end_connection else 0) return self @@ -439,6 +605,22 @@ class AppResponse: self._aborted_handler = handler return self + def on_data(self, handler): + if not self.aborted: + if hasattr(handler, '__call__'): + self.grab_aborted_handler() + self._data_handler = handler + lib.uws_res_on_data(self.SSL, self.res, uws_generic_on_data_handler, self._ptr) + return self + + def on_writable(self, handler): + if not self.aborted: + if hasattr(handler, '__call__'): + self.grab_aborted_handler() + self._writable_handler = handler + lib.uws_res_on_writable(self.SSL, self.res, uws_generic_on_writable_handler, self._ptr) + return self + def __del__(self): self.res = ffi.NULL self._ptr = ffi.NULL @@ -546,9 +728,6 @@ class App: lib.uws_app_listen_with_config(self.SSL, self.app, options, uws_generic_listen_handler, self._ptr) return self - - def get_loop(): - return self.loop.loop def run_async(self, task, response=None): return self.loop.run_async(task, response) @@ -557,7 +736,6 @@ class App: signal.signal(signal.SIGINT, lambda sig, frame: self.close()) self.loop.start() self.loop.run() - # lib.uws_app_run(self.SSL, self.app) return self def close(self): diff --git a/src/socketify/status_codes.py b/src/socketify/status_codes.py index 4c82561..711da04 100644 --- a/src/socketify/status_codes.py +++ b/src/socketify/status_codes.py @@ -1,65 +1,65 @@ status_codes = { - 100 : "100 Continue", - 101 : "101 Switching Protocols", - 102 : "102 Processing", - 103 : "103 Early Hints", - 200 : "200 OK", - 201 : "201 Created", - 202 : "202 Accepted", - 203 : "203 Non-Authoritative Information", - 204 : "204 No Content", - 205 : "205 Reset Content", - 206 : "206 Partial Content", - 207 : "207 Multi-Status", - 208 : "208 Already Reported", - 226 : "226 IM Used (HTTP Delta encoding)", - 300 : "300 Multiple Choices", - 301 : "301 Moved Permanently", - 302 : "302 Found", - 303 : "303 See Other", - 304 : "304 Not Modified", - 305 : "305 Use Proxy Deprecated", - 306 : "306 unused", - 307 : "307 Temporary Redirect", - 308 : "308 Permanent Redirect", - 400 : "400 Bad Request", - 401 : "401 Unauthorized", - 402 : "402 Payment Required Experimental", - 403 : "403 Forbidden", - 404 : "404 Not Found", - 405 : "405 Method Not Allowed", - 406 : "406 Not Acceptable", - 407 : "407 Proxy Authentication Required", - 408 : "408 Request Timeout", - 409 : "409 Conflict", - 410 : "410 Gone", - 411 : "411 Length Required", - 412 : "412 Precondition Failed", - 413 : "413 Payload Too Large", - 414 : "414 URI Too Long", - 415 : "415 Unsupported Media Type", - 416 : "416 Range Not Satisfiable", - 417 : "417 Expectation Failed", - 418 : "418 I'm a teapot", - 421 : "421 Misdirected Request", - 422 : "422 Unprocessable Entity", - 423 : "423 Locked", - 424 : "424 Failed Dependency", - 425 : "425 Too Early Experimental", - 426 : "426 Upgrade Required", - 428 : "428 Precondition Required", - 429 : "429 Too Many Requests", - 431 : "431 Request Header Fields Too Large", - 451 : "451 Unavailable For Legal Reasons", - 500 : "500 Internal Server Error", - 501 : "501 Not Implemented", - 502 : "502 Bad Gateway", - 503 : "503 Service Unavailable", - 504 : "504 Gateway Timeout", - 505 : "505 HTTP Version Not Supported", - 506 : "506 Variant Also Negotiates", - 507 : "507 Insufficient Storage", - 508 : "508 Loop Detected", - 510 : "510 Not Extended", - 511 : "511 Network Authentication Required" + 100 : b'100 Continue', + 101 : b'101 Switching Protocols', + 102 : b'102 Processing', + 103 : b'103 Early Hints', + 200 : b'200 OK', + 201 : b'201 Created', + 202 : b'202 Accepted', + 203 : b'203 Non-Authoritative Information', + 204 : b'204 No Content', + 205 : b'205 Reset Content', + 206 : b'206 Partial Content', + 207 : b'207 Multi-Status', + 208 : b'208 Already Reported', + 226 : b'226 IM Used (HTTP Delta encoding)', + 300 : b'300 Multiple Choices', + 301 : b'301 Moved Permanently', + 302 : b'302 Found', + 303 : b'303 See Other', + 304 : b'304 Not Modified', + 305 : b'305 Use Proxy Deprecated', + 306 : b'306 unused', + 307 : b'307 Temporary Redirect', + 308 : b'308 Permanent Redirect', + 400 : b'400 Bad Request', + 401 : b'401 Unauthorized', + 402 : b'402 Payment Required Experimental', + 403 : b'403 Forbidden', + 404 : b'404 Not Found', + 405 : b'405 Method Not Allowed', + 406 : b'406 Not Acceptable', + 407 : b'407 Proxy Authentication Required', + 408 : b'408 Request Timeout', + 409 : b'409 Conflict', + 410 : b'410 Gone', + 411 : b'411 Length Required', + 412 : b'412 Precondition Failed', + 413 : b'413 Payload Too Large', + 414 : b'414 URI Too Long', + 415 : b'415 Unsupported Media Type', + 416 : b'416 Range Not Satisfiable', + 417 : b'417 Expectation Failed', + 418 : b'418 I\'m a teapot', + 421 : b'421 Misdirected Request', + 422 : b'422 Unprocessable Entity', + 423 : b'423 Locked', + 424 : b'424 Failed Dependency', + 425 : b'425 Too Early Experimental', + 426 : b'426 Upgrade Required', + 428 : b'428 Precondition Required', + 429 : b'429 Too Many Requests', + 431 : b'431 Request Header Fields Too Large', + 451 : b'451 Unavailable For Legal Reasons', + 500 : b'500 Internal Server Error', + 501 : b'501 Not Implemented', + 502 : b'502 Bad Gateway', + 503 : b'503 Service Unavailable', + 504 : b'504 Gateway Timeout', + 505 : b'505 HTTP Version Not Supported', + 506 : b'506 Variant Also Negotiates', + 507 : b'507 Insufficient Storage', + 508 : b'508 Loop Detected', + 510 : b'510 Not Extended', + 511 : b'511 Network Authentication Required' } \ No newline at end of file diff --git a/tests/examples/http_request_cache.py b/tests/examples/http_request_cache.py index d747a6e..707b8bf 100644 --- a/tests/examples/http_request_cache.py +++ b/tests/examples/http_request_cache.py @@ -8,8 +8,8 @@ from helpers.twolevel_cache import TwoLevelCache redis_pool = redis.ConnectionPool(host='localhost', port=6379, db=0) redis_conection = redis.Redis(connection_pool=redis_pool) # 2 LEVEL CACHE (Redis to share amoung workers, Memory to be much faster) -# cache in memory is 5s, cache in redis is 10s duration -cache = TwoLevelCache(redis_conection, 5, 10) +# cache in memory is 30s, cache in redis is 60s duration +cache = TwoLevelCache(redis_conection, 30, 60) ### # Model @@ -19,17 +19,17 @@ async def get_pokemon(number): async with aiohttp.ClientSession() as session: async with session.get(f'https://pokeapi.co/api/v2/pokemon/{number}') as response: pokemon = await response.text() - #cache only works with strings/bytes/buffer + #cache only works with strings/bytes #we will not change nothing here so no needs to parse json - return pokemon + return pokemon.encode("utf-8") async def get_original_pokemons(): async with aiohttp.ClientSession() as session: async with session.get(f'https://pokeapi.co/api/v2/pokemon?limit=151') as response: - #cache only works with strings/bytes/buffer + #cache only works with strings/bytes #we will not change nothing here so no needs to parse json pokemons = await response.text() - return pokemons + return pokemons.encode("utf-8") ### diff --git a/tests/examples/router_and_basics.py b/tests/examples/router_and_basics.py index cb1381d..5bab2b8 100644 --- a/tests/examples/router_and_basics.py +++ b/tests/examples/router_and_basics.py @@ -1,7 +1,8 @@ from socketify import App, AppOptions, AppListenOptions import asyncio - +from datetime import datetime +from datetime import timedelta app = App() def home(res, req): @@ -10,6 +11,27 @@ def home(res, req): def anything(res, req): res.end("Any route with method: %s" % req.get_method()) +def cookies(res, req): + #cookies are writen after end + res.set_cookie("spaciari", "1234567890",{ + # expires + # path + # comment + # domain + # max-age + # secure + # version + # httponly + # samesite + "path": "/", + # "domain": "*.test.com", + "httponly": True, + "samesite": "None", + "secure": True, + "expires": datetime.utcnow() + timedelta(minutes=30) + }) + res.end("Your session_id cookie is: %s" % req.get_cookie('session_id')); + def useragent(res,req): res.end("Your user agent is: %s" % req.get_header('user-agent')); @@ -77,9 +99,11 @@ app.get("/delayed", delayed) app.get("/json", json) app.get("/sleepy", sleepy_json) app.get("/custom_header", custom_header) +app.get("/cookies", cookies) app.get("/send_in_parts", send_in_parts) app.get("/redirect", redirect) app.get("/redirected", redirected) +# too see about app.post go to ./upload_or_post.py :D # Wildcard at last always :) app.any("/*", not_found)