فهرست منبع

add `libhv` module

blueloveTH 1 سال پیش
والد
کامیت
3d12c9400c

+ 2 - 0
.gitignore

@@ -35,3 +35,5 @@ docs/references.md
 
 tests/00_tmp.py
 docs/C-API/functions.md
+
+*.log

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "3rd/libhv/libhv"]
+	path = 3rd/libhv/libhv
+	url = https://github.com/ithewei/libhv

+ 37 - 0
3rd/libhv/CMakeLists.txt

@@ -0,0 +1,37 @@
+cmake_minimum_required(VERSION 3.10)
+
+project(libhv_bindings)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+option(BUILD_SHARED "build shared library" OFF)
+option(BUILD_STATIC "build static library" ON)
+option(BUILD_EXAMPLES "build examples" OFF)
+option(WITH_OPENSSL "with openssl library" ON)
+
+add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/libhv)
+
+AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_LIST_DIR}/src LIBHV_BINDINGS_SRC)
+
+add_library(${PROJECT_NAME} STATIC ${LIBHV_BINDINGS_SRC})
+
+target_link_libraries(${PROJECT_NAME} hv_static)
+
+# define WITHOUT_HTTP_CONTENT
+target_compile_definitions(libhv_bindings PRIVATE WITHOUT_HTTP_CONTENT)
+target_compile_definitions(hv_static PRIVATE WITHOUT_HTTP_CONTENT)
+
+target_include_directories(${PROJECT_NAME} PRIVATE
+    ${CMAKE_CURRENT_LIST_DIR}/../../include
+    ${CMAKE_CURRENT_LIST_DIR}/include
+    ${CMAKE_CURRENT_LIST_DIR}/libhv
+    ${CMAKE_CURRENT_LIST_DIR}/libhv/base
+    ${CMAKE_CURRENT_LIST_DIR}/libhv/evpp
+    ${CMAKE_CURRENT_LIST_DIR}/libhv/event
+    ${CMAKE_CURRENT_LIST_DIR}/libhv/http
+    ${CMAKE_CURRENT_LIST_DIR}/libhv/ssl
+    ${CMAKE_CURRENT_LIST_DIR}/libhv/cpputil
+)

+ 15 - 0
3rd/libhv/compile_flags.txt

@@ -0,0 +1,15 @@
+-xc++
+-std=c++14
+
+-I../../include
+
+-Iinclude/
+-Ilibhv/
+-Ilibhv/base/
+-Ilibhv/evpp/
+-Ilibhv/event/
+-Ilibhv/http/
+-Ilibhv/ssl/
+-Ilibhv/cpputil/
+
+-DWITHOUT_HTTP_CONTENT

+ 10 - 0
3rd/libhv/include/libhv_bindings.hpp

@@ -0,0 +1,10 @@
+#pragma once
+
+#include "pocketpy.h"
+
+extern "C" void pk__add_module_libhv();
+
+py_Type libhv_register_HttpClient(py_GlobalRef mod);
+py_Type libhv_register_HttpServer(py_GlobalRef mod);
+py_Type libhv_register_WebSocketClient(py_GlobalRef mod);
+py_Type libhv_register_WebSocketServer(py_GlobalRef mod);

+ 305 - 0
3rd/libhv/src/HttpClient.cpp

@@ -0,0 +1,305 @@
+#include "libhv_bindings.hpp"
+#include "base/herr.h"
+#include "http/client/HttpClient.h"
+
+struct libhv_HttpResponse {
+    HttpResponsePtr ptr;
+    bool ok;
+
+    bool is_valid() { return ok && ptr != NULL; }
+
+    libhv_HttpResponse() : ptr(NULL), ok(false) {}
+};
+
+static bool libhv_HttpResponse_status_code(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    libhv_HttpResponse* resp = (libhv_HttpResponse*)py_touserdata(argv);
+    if(!resp->is_valid()) return RuntimeError("HttpResponse: no response");
+    py_newint(py_retval(), resp->ptr->status_code);
+    return true;
+};
+
+static bool libhv_HttpResponse_headers(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    libhv_HttpResponse* resp = (libhv_HttpResponse*)py_touserdata(argv);
+    if(!resp->is_valid()) return RuntimeError("HttpResponse: no response");
+    py_Ref headers = py_getslot(argv, 0);
+    if(py_isnil(headers)) {
+        py_newdict(headers);
+        py_Ref _0 = py_pushtmp();
+        py_Ref _1 = py_pushtmp();
+        for(auto& kv: resp->ptr->headers) {
+            py_newstr(_0, kv.first.c_str());
+            py_newstr(_1, kv.second.c_str());
+            py_dict_setitem(headers, _0, _1);
+        }
+        py_shrink(2);
+    }
+    py_assign(py_retval(), headers);
+    return true;
+};
+
+static bool libhv_HttpResponse_text(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    libhv_HttpResponse* resp = (libhv_HttpResponse*)py_touserdata(argv);
+    if(!resp->is_valid()) return RuntimeError("HttpResponse: no response");
+    py_Ref text = py_getslot(argv, 1);
+    if(py_isnil(text)) {
+        c11_sv sv;
+        sv.data = resp->ptr->body.c_str();
+        sv.size = resp->ptr->body.size();
+        py_newstrv(text, sv);
+    }
+    py_assign(py_retval(), text);
+    return true;
+};
+
+static bool libhv_HttpResponse_content(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    libhv_HttpResponse* resp = (libhv_HttpResponse*)py_touserdata(argv);
+    if(!resp->is_valid()) return RuntimeError("HttpResponse: no response");
+    py_Ref content = py_getslot(argv, 2);
+    if(py_isnil(content)) {
+        int size = resp->ptr->body.size();
+        unsigned char* buf = py_newbytes(content, size);
+        memcpy(buf, resp->ptr->body.data(), size);
+    }
+    py_assign(py_retval(), content);
+    return true;
+};
+
+static bool libhv_HttpResponse_json(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    libhv_HttpResponse* resp = (libhv_HttpResponse*)py_touserdata(argv);
+    if(!resp->is_valid()) return RuntimeError("HttpResponse: no response");
+    const char* source = resp->ptr->body.c_str();  // json string is null-terminated
+    return py_json_loads(source);
+};
+
+static bool libhv_HttpResponse_completed(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    libhv_HttpResponse* resp = (libhv_HttpResponse*)py_touserdata(argv);
+    py_newbool(py_retval(), resp->is_valid());
+    return true;
+}
+
+static bool libhv_HttpResponse__new__(int argc, py_Ref argv) {
+    return py_exception(tp_NotImplementedError, "");
+}
+
+static bool libhv_HttpResponse__iter__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    py_assign(py_retval(), argv);
+    return true;
+}
+
+static bool libhv_HttpResponse__next__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    libhv_HttpResponse* resp = (libhv_HttpResponse*)py_touserdata(argv);
+    if(!resp->is_valid()) {
+        py_newnone(py_retval());
+        return true;
+    } else {
+        if(!py_tpcall(tp_StopIteration, 1, argv)) return false;
+        return py_raise(py_retval());
+    }
+}
+
+static bool libhv_HttpResponse__repr__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    libhv_HttpResponse* resp = (libhv_HttpResponse*)py_touserdata(argv);
+    if(!resp->is_valid()) {
+        py_newstr(py_retval(), "<HttpResponse: no response>");
+    } else {
+        py_newfstr(py_retval(), "<HttpResponse: %d>", (int)resp->ptr->status_code);
+    }
+    return true;
+}
+
+static py_Type libhv_register_HttpResponse(py_GlobalRef mod) {
+    py_Type type = py_newtype("HttpResponse", tp_object, mod, [](void* ud) {
+        ((libhv_HttpResponse*)ud)->~libhv_HttpResponse();
+    });
+
+    py_bindproperty(type, "status_code", libhv_HttpResponse_status_code, NULL);
+    py_bindproperty(type, "headers", libhv_HttpResponse_headers, NULL);
+    py_bindproperty(type, "text", libhv_HttpResponse_text, NULL);
+    py_bindproperty(type, "content", libhv_HttpResponse_content, NULL);
+    py_bindmethod(type, "json", libhv_HttpResponse_json);
+
+    py_bindmagic(type, __new__, libhv_HttpResponse__new__);
+    py_bindmagic(type, __iter__, libhv_HttpResponse__iter__);
+    py_bindmagic(type, __next__, libhv_HttpResponse__next__);
+    py_bindmagic(type, __repr__, libhv_HttpResponse__repr__);
+    // completed
+    py_bindproperty(type, "completed", libhv_HttpResponse_completed, NULL);
+    return type;
+}
+
+static bool libhv_HttpClient__send_request(py_Ref arg_self,
+                                           enum http_method method,
+                                           py_Ref arg_url,
+                                           py_Ref arg_params,
+                                           py_Ref arg_headers,
+                                           py_Ref arg_data,
+                                           py_Ref arg_json,
+                                           py_Ref arg_timeout) {
+    hv::HttpClient* cli = (hv::HttpClient*)py_touserdata(arg_self);
+    if(!py_checkstr(arg_url)) return false;
+    const char* url = py_tostr(arg_url);
+    if(!py_checkint(arg_timeout)) return false;
+    int timeout = py_toint(arg_timeout);
+
+    auto req = std::make_shared<HttpRequest>();
+    req->method = method;
+    req->url = url;
+    if(!py_isnone(arg_params)) {
+        if(!py_checktype(arg_params, tp_dict)) return false;
+
+        bool ok = py_dict_apply(
+            arg_params,
+            [](py_Ref key, py_Ref value, void* ctx) {
+                HttpRequest* p_req = (HttpRequest*)ctx;
+                if(!py_checkstr(key)) return false;
+                if(!py_str(value)) return false;  // key: str(value)
+                p_req->SetParam(py_tostr(key), py_tostr(py_retval()));
+                return true;
+            },
+            req.get());
+        if(!ok) return false;
+    }
+
+    req->headers["Connection"] = "keep-alive";
+
+    if(!py_isnone(arg_headers)) {
+        if(!py_checktype(arg_headers, tp_dict)) return false;
+
+        bool ok = py_dict_apply(
+            arg_headers,
+            [](py_Ref key, py_Ref value, void* ctx) {
+                HttpRequest* p_req = (HttpRequest*)ctx;
+                if(!py_checkstr(key)) return false;
+                if(!py_str(value)) return false;  // key: str(value)
+                p_req->headers[py_tostr(key)] = py_tostr(py_retval());
+                return true;
+            },
+            req.get());
+        if(!ok) return false;
+    }
+
+    if(!py_isnone(arg_data)) {
+        // data must be str or bytes
+        if(py_istype(arg_data, tp_str)) {
+            req->body = py_tostr(arg_data);
+        } else if(py_istype(arg_data, tp_bytes)) {
+            int size;
+            unsigned char* buf = py_tobytes(arg_data, &size);
+            req->body.assign((const char*)buf, size);
+        } else {
+            return TypeError("HttpClient: data must be str or bytes");
+        }
+    }
+
+    if(!py_isnone(arg_json)) {
+        if(!py_isnone(arg_data)) {
+            return ValueError("HttpClient: data and json cannot be set at the same time");
+        }
+
+        if(!py_json_dumps(arg_json)) return false;
+        req->body = py_tostr(py_retval());
+        req->headers["Content-Type"] = "application/json";
+    }
+
+    req->timeout = timeout;
+
+    libhv_HttpResponse* retval =
+        (libhv_HttpResponse*)py_newobject(py_retval(),
+                                          py_gettype("libhv", py_name("HttpResponse")),
+                                          3,  // headers, text, content
+                                          sizeof(libhv_HttpResponse));
+    // placement new
+    new (retval) libhv_HttpResponse();
+
+    int code = cli->sendAsync(req, [retval](const HttpResponsePtr& resp) {
+        if(resp == NULL) {
+            retval->ok = false;
+            retval->ptr = NULL;
+        } else {
+            retval->ok = true;
+            retval->ptr = resp;
+        }
+    });
+    if(code != 0) {
+        const char* msg = hv_strerror(code);
+        return RuntimeError("HttpClient: %s (%d)", msg, code);
+    }
+    return true;
+}
+
+py_Type libhv_register_HttpClient(py_GlobalRef mod) {
+    py_Type type = py_newtype("HttpClient", tp_object, mod, [](void* ud) {
+        ((hv::HttpClient*)ud)->~HttpClient();
+    });
+    py_GlobalRef type_object = py_tpobject(type);
+    libhv_register_HttpResponse(mod);
+
+    py_bindmagic(type, __new__, [](int argc, py_Ref argv) {
+        hv::HttpClient* ud =
+            (hv::HttpClient*)py_newobject(py_retval(), py_totype(argv), 0, sizeof(hv::HttpClient));
+        new (ud) hv::HttpClient();
+        return true;
+    });
+
+    py_bind(type_object,
+            "get(self, url: str, params=None, headers=None, timeout=10)",
+            [](int argc, py_Ref argv) {
+                return libhv_HttpClient__send_request(py_arg(0),   // self
+                                                      HTTP_GET,    // method
+                                                      py_arg(1),   // url
+                                                      py_arg(2),   // params
+                                                      py_arg(3),   // headers
+                                                      py_None(),   // data
+                                                      py_None(),   // json
+                                                      py_arg(4));  // timeout
+            });
+
+    py_bind(type_object,
+            "post(self, url: str, params=None, headers=None, data=None, json=None, timeout=10)",
+            [](int argc, py_Ref argv) {
+                return libhv_HttpClient__send_request(py_arg(0),   // self
+                                                      HTTP_POST,   // method
+                                                      py_arg(1),   // url
+                                                      py_arg(2),   // params
+                                                      py_arg(3),   // headers
+                                                      py_arg(4),   // data
+                                                      py_arg(5),   // json
+                                                      py_arg(6));  // timeout
+            });
+
+    py_bind(type_object,
+            "put(self, url: str, params=None, headers=None, data=None, json=None, timeout=10)",
+            [](int argc, py_Ref argv) {
+                return libhv_HttpClient__send_request(py_arg(0),   // self
+                                                      HTTP_PUT,    // method
+                                                      py_arg(1),   // url
+                                                      py_arg(2),   // params
+                                                      py_arg(3),   // headers
+                                                      py_arg(4),   // data
+                                                      py_arg(5),   // json
+                                                      py_arg(6));  // timeout
+            });
+
+    py_bind(type_object,
+            "delete(self, url: str, params=None, headers=None, timeout=10)",
+            [](int argc, py_Ref argv) {
+                return libhv_HttpClient__send_request(py_arg(0),    // self
+                                                      HTTP_DELETE,  // method
+                                                      py_arg(1),    // url
+                                                      py_arg(2),    // params
+                                                      py_arg(3),    // headers
+                                                      py_None(),    // data
+                                                      py_None(),    // json
+                                                      py_arg(4));   // timeout
+            });
+    return type;
+}

+ 7 - 0
3rd/libhv/src/HttpServer.cpp

@@ -0,0 +1,7 @@
+#include "libhv_bindings.hpp"
+#include "http/server/HttpServer.h"
+
+py_Type libhv_register_HttpServer(py_GlobalRef mod){
+    py_Type type = py_newtype("HttpServer", tp_object, mod, NULL);
+    return type;
+}

+ 7 - 0
3rd/libhv/src/WebSocketClient.cpp

@@ -0,0 +1,7 @@
+#include "libhv_bindings.hpp"
+#include "http/client/WebSocketClient.h"
+
+py_Type libhv_register_WebSocketClient(py_GlobalRef mod) {
+    py_Type type = py_newtype("WebSocketClient", tp_object, mod, NULL);
+    return type;
+}

+ 7 - 0
3rd/libhv/src/WebSocketServer.cpp

@@ -0,0 +1,7 @@
+#include "libhv_bindings.hpp"
+#include "http/server/WebSocketServer.h"
+
+py_Type libhv_register_WebSocketServer(py_GlobalRef mod) {
+    py_Type type = py_newtype("WebSocketServer", tp_object, mod, NULL);
+    return type;
+}

+ 10 - 0
3rd/libhv/src/libhv_bindings.cpp

@@ -0,0 +1,10 @@
+#include "libhv_bindings.hpp"
+
+extern "C" void pk__add_module_libhv() {
+    py_GlobalRef mod = py_newmodule("libhv");
+
+    libhv_register_HttpClient(mod);
+    libhv_register_HttpServer(mod);
+    libhv_register_WebSocketClient(mod);
+    libhv_register_WebSocketServer(mod);
+}

+ 12 - 1
CMakeLists.txt

@@ -52,7 +52,14 @@ option(PK_BUILD_MODULE_LZ4 "" ON)
 if(PK_BUILD_MODULE_LZ4)
     add_subdirectory(3rd/lz4)
     include_directories(3rd/lz4)
-    add_definitions(-DPK_BUILD_MODULE_LZ4=1)
+    add_definitions(-DPK_BUILD_MODULE_LZ4)
+endif()
+
+option(PK_BUILD_MODULE_LIBHV "" OFF)
+if(PK_BUILD_MODULE_LIBHV)
+    add_subdirectory(3rd/libhv)
+    include_directories(3rd/libhv/include)
+    add_definitions(-DPK_BUILD_MODULE_LIBHV)
 endif()
 
 # PK_IS_MAIN determines whether the project is being used from root
@@ -97,3 +104,7 @@ endif()
 if(PK_BUILD_MODULE_LZ4)
     target_link_libraries(${PROJECT_NAME} lz4)
 endif()
+
+if(PK_BUILD_MODULE_LIBHV)
+    target_link_libraries(${PROJECT_NAME} libhv_bindings)
+endif()

+ 1 - 1
build_g_32.sh

@@ -4,7 +4,7 @@ python prebuild.py
 
 SRC=$(find src/ -name "*.c")
 
-FLAGS="-std=c11 -lm -ldl -I3rd/lz4 -Iinclude -O0 -Wfatal-errors -g -DDEBUG -DPK_ENABLE_OS=1 -DPK_BUILD_MODULE_LZ4=1"
+FLAGS="-std=c11 -lm -ldl -I3rd/lz4 -Iinclude -O0 -Wfatal-errors -g -DDEBUG -DPK_ENABLE_OS=1 -DPK_BUILD_MODULE_LZ4"
 
 SANITIZE_FLAGS="-fsanitize=address,leak,undefined"
 

+ 1 - 1
cmake_build.py

@@ -20,7 +20,7 @@ assert config in ['Debug', 'Release', 'RelWithDebInfo']
 
 os.chdir("build")
 
-code = os.system(f"cmake .. -DPK_ENABLE_OS=ON -DCMAKE_BUILD_TYPE={config} {extra_flags}")
+code = os.system(f"cmake .. -DPK_ENABLE_OS=ON -DPK_BUILD_MODULE_LZ4=ON -DPK_BUILD_MODULE_LIBHV=ON -DCMAKE_BUILD_TYPE={config} {extra_flags}")
 assert code == 0
 code = os.system(f"cmake --build . --config {config}")
 assert code == 0

+ 2 - 1
compile_flags.txt

@@ -3,4 +3,5 @@
 -xc
 -std=c11
 -Iinclude/
--I3rd/lz4/
+-I3rd/lz4/
+-I3rd/libhv/include/

+ 0 - 6
include/pocketpy/common/utils.h

@@ -48,9 +48,3 @@ typedef struct RefCounted {
         }                                                                                          \
     } while(0)
 
-// static assert
-#ifndef __cplusplus
-    #ifndef static_assert
-        #define static_assert(x, msg) if(!(x)) c11__abort("static_assert failed: %s", msg)
-    #endif
-#endif

+ 6 - 0
include/pocketpy/interpreter/modules.h

@@ -24,3 +24,9 @@ void pk__add_module_colorcvt();
 void pk__add_module_conio();
 void pk__add_module_lz4();
 void pk__add_module_pkpy();
+
+#ifdef PK_BUILD_MODULE_LIBHV
+void pk__add_module_libhv();
+#else
+#define pk__add_module_libhv()
+#endif

+ 2 - 0
include/pocketpy/pocketpy.h

@@ -159,6 +159,8 @@ PK_API void py_newstr(py_OutRef, const char*);
 PK_API char* py_newstrn(py_OutRef, int);
 /// Create a `str` object from a `c11_sv`.
 PK_API void py_newstrv(py_OutRef, c11_sv);
+/// Create a formatted `str` object.
+PK_API void py_newfstr(py_OutRef, const char*, ...);
 /// Create a `bytes` object with `n` UNINITIALIZED bytes.
 PK_API unsigned char* py_newbytes(py_OutRef, int n);
 /// Create a `None` object.

+ 34 - 0
include/typings/libhv.pyi

@@ -0,0 +1,34 @@
+from typing import Literal, Generator
+
+class Future[T]:
+    def completed(self) -> bool: ...
+    def __iter__(self) -> Generator[T, None, None]: ...
+
+class HttpResponse(Future['HttpResponse']):
+    @property
+    def status_code(self) -> int: ...
+    @property
+    def headers(self) -> dict[str, str]: ...
+    @property
+    def text(self) -> str: ...
+    @property
+    def content(self) -> bytes: ...
+
+    def json(self): ...
+
+
+class HttpClient:
+    def get(self, url: str, params=None, headers=None, timeout=10) -> HttpResponse: ...
+    def post(self, url: str, params=None, headers=None, data=None, json=None, timeout=10) -> HttpResponse: ...
+    def put(self, url: str, params=None, headers=None, data=None, json=None, timeout=10) -> HttpResponse: ...
+    def delete(self, url: str, params=None, headers=None, timeout=10) -> HttpResponse: ...
+
+
+class HttpServer:
+    pass
+
+class WebSocketClient:
+    pass
+
+class WebSocketServer:
+    pass

+ 2 - 1
src/interpreter/vm.c

@@ -221,7 +221,8 @@ void VM__ctor(VM* self) {
     pk__add_module_importlib();
 
     pk__add_module_conio();
-    pk__add_module_lz4();
+    pk__add_module_lz4();       // optional
+    pk__add_module_libhv();     // optional
     pk__add_module_pkpy();
 
     // add python builtins

+ 10 - 0
src/public/py_str.c

@@ -25,6 +25,16 @@ void py_newstrv(py_OutRef out, c11_sv sv) {
     memcpy(data, sv.data, sv.size);
 }
 
+void py_newfstr(py_OutRef out, const char* fmt, ...) {
+    c11_sbuf buf;
+    c11_sbuf__ctor(&buf);
+    va_list args;
+    va_start(args, fmt);
+    pk_vsprintf(&buf, fmt, args);
+    va_end(args);
+    c11_sbuf__py_submit(&buf, out);
+}
+
 unsigned char* py_newbytes(py_Ref out, int size) {
     ManagedHeap* heap = &pk_current_vm->heap;
     // 4 bytes size + data