diff --git a/.gitignore b/.gitignore index 67e402d6..4631fb01 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ src/_generated.h profile.sh test tmp.rar +src/httplib.h diff --git a/amalgamate.py b/amalgamate.py index b6815710..73cf30da 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -9,7 +9,7 @@ pipeline = [ ["common.h", "memory.h", "vector.h", "str.h", "tuplelist.h", "namedict.h", "error.h", "lexer.h"], ["obj.h", "codeobject.h", "frame.h"], ["gc.h", "vm.h", "ceval.h", "expr.h", "compiler.h", "repl.h"], - ["iter.h", "cffi.h", "io.h", "_generated.h", "pocketpy.h"] + ["iter.h", "cffi.h", "requests.h", "io.h", "_generated.h", "pocketpy.h"] ] copied = set() diff --git a/python/_dict.py b/python/_dict.py index 24adfc9b..97a3515f 100644 --- a/python/_dict.py +++ b/python/_dict.py @@ -74,7 +74,7 @@ class dict: def items(self): for kv in self._a: if kv is not None: - yield kv + yield kv[0], kv[1] def clear(self): self._a = [None] * self._capacity diff --git a/python/requests.py b/python/requests.py new file mode 100644 index 00000000..2ebf35fb --- /dev/null +++ b/python/requests.py @@ -0,0 +1,40 @@ +class Response: + def __init__(self, status_code, reason, content): + self.status_code = status_code + self.reason = reason + self.content = content + + assert type(self.status_code) is int + assert type(self.reason) is str + assert type(self.content) is bytes + + @property + def text(self): + return self.content.decode() + + def __repr__(self): + code = self.status_code + return f'' + +def _parse_h(headers): + if headers is None: + return [] + if type(headers) is dict: + return list(headers.items()) + raise ValueError('headers must be dict or None') + +def get(url, headers=None): + headers = _parse_h(headers) + return _request('GET', url, headers, None) + +def post(url, data: bytes, headers=None): + headers = _parse_h(headers) + return _request('POST', url, headers, data) + +def put(url, data: bytes, headers=None): + headers = _parse_h(headers) + return _request('PUT', url, headers, data) + +def delete(url, headers=None): + headers = _parse_h(headers) + return _request('DELETE', url, headers, None) \ No newline at end of file diff --git a/src/pocketpy.h b/src/pocketpy.h index 2501c396..6b3f0b45 100644 --- a/src/pocketpy.h +++ b/src/pocketpy.h @@ -6,6 +6,7 @@ #include "repl.h" #include "iter.h" #include "cffi.h" +#include "requests.h" #include "io.h" #include "_generated.h" @@ -920,11 +921,6 @@ inline void VM::post_init(){ add_module_gc(this); add_module_random(this); - if(enable_os){ - add_module_io(this); - add_module_os(this); - } - for(const char* name: {"this", "functools", "collections", "heapq", "bisect"}){ _lazy_modules[name] = kPythonLibs[name]; } @@ -969,6 +965,12 @@ inline void VM::post_init(){ } return VAR(MappingProxy(args[0])); })); + + if(enable_os){ + add_module_io(this); + add_module_os(this); + add_module_requests(this); + } #endif } diff --git a/src/requests.h b/src/requests.h new file mode 100644 index 00000000..d510723c --- /dev/null +++ b/src/requests.h @@ -0,0 +1,105 @@ +#pragma once + +#include "common.h" +#include "obj.h" +#include "vm.h" +#include "_generated.h" + +#if __has_include("httplib.h") +#include "httplib.h" + +namespace pkpy { + +inline void add_module_requests(VM* vm){ + static StrName m_requests("requests"); + static StrName m_Response("Response"); + PyObject* mod = vm->new_module(m_requests); + CodeObject_ code = vm->compile(kPythonLibs["requests"], "requests.py", EXEC_MODE); + vm->_exec(code, mod); + + vm->bind_func<4>(mod, "_request", [](VM* vm, ArgsView args){ + Str method = CAST(Str&, args[0]); + Str url = CAST(Str&, args[1]); + PyObject* headers = args[2]; // a dict object + PyObject* body = args[3]; // a bytes object + + if(url.index("http://") != 0){ + vm->ValueError("url must start with http://"); + } + + for(char c: url){ + switch(c){ + case '.': + case '-': + case '_': + case '~': + case ':': + case '/': + break; + default: + if(!isalnum(c)){ + vm->ValueError(fmt("invalid character in url: '", c, "'")); + } + } + } + + int slash = url.index("/", 7); + Str path = "/"; + if(slash != -1){ + path = url.substr(slash); + url = url.substr(0, slash); + if(path.empty()) path = "/"; + } + + httplib::Client client(url.str()); + + httplib::Headers h; + if(headers != vm->None){ + List list = CAST(List&, headers); + for(auto& item : list){ + Tuple t = CAST(Tuple&, item); + Str key = CAST(Str&, t[0]); + Str value = CAST(Str&, t[1]); + h.emplace(key.str(), value.str()); + } + } + + auto _to_resp = [=](const httplib::Result& res){ + std::vector buf(res->body.size()); + for(int i=0; ibody.size(); i++) buf[i] = res->body[i]; + return vm->call( + vm->_modules[m_requests]->attr(m_Response), + VAR(res->status), + VAR(res->reason), + VAR(Bytes(std::move(buf))) + ); + }; + + if(method == "GET"){ + httplib::Result res = client.Get(path.str(), h); + return _to_resp(res); + }else if(method == "POST"){ + Bytes b = CAST(Bytes&, body); + httplib::Result res = client.Post(path.str(), h, b.data(), b.size(), "application/octet-stream"); + return _to_resp(res); + }else if(method == "PUT"){ + Bytes b = CAST(Bytes&, body); + httplib::Result res = client.Put(path.str(), h, b.data(), b.size(), "application/octet-stream"); + return _to_resp(res); + }else if(method == "DELETE"){ + httplib::Result res = client.Delete(path.str(), h); + return _to_resp(res); + }else{ + vm->ValueError("invalid method"); + } + UNREACHABLE(); + }); +} + +} // namespace pkpy + +#else + +inline void add_module_requests(VM* vm){ } + +#endif \ No newline at end of file