@noita-ts/pollnet 0.3.8 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +221 -0
- package/dist/api.lua +490 -0
- package/dist/index.d.ts +53 -211
- package/dist/index.lua +171 -490
- package/dist/lualib_bundle.lua +7 -211
- package/package.json +4 -4
- package/dist/async.d.ts +0 -76
- package/dist/async.lua +0 -167
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DISCLAIMER:
|
|
3
|
+
* Similar to nxml, this typedef was semi-vibecoded, this will likely be tested
|
|
4
|
+
* more thoroughly though.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** @noSelfInFile */
|
|
8
|
+
|
|
9
|
+
declare namespace pollnet {
|
|
10
|
+
const VERSION: string;
|
|
11
|
+
|
|
12
|
+
type SocketStatus =
|
|
13
|
+
| "unpolled"
|
|
14
|
+
| "invalid"
|
|
15
|
+
| "invalid_handle"
|
|
16
|
+
| "open"
|
|
17
|
+
| "opening"
|
|
18
|
+
| "error"
|
|
19
|
+
| "closed";
|
|
20
|
+
|
|
21
|
+
/** Initializes the pollnet context */
|
|
22
|
+
function init(): void;
|
|
23
|
+
|
|
24
|
+
/** Initializes the pollnet context using the static hack */
|
|
25
|
+
function init_hack_static(): void;
|
|
26
|
+
|
|
27
|
+
/** Shuts down the pollnet context */
|
|
28
|
+
function shutdown(): void;
|
|
29
|
+
|
|
30
|
+
/** Sleeps (blocks) for the given milliseconds */
|
|
31
|
+
function sleep_ms(ms: number): void;
|
|
32
|
+
|
|
33
|
+
/** Generates a nanoid string */
|
|
34
|
+
function nanoid(): string;
|
|
35
|
+
|
|
36
|
+
/** Formats headers as a string */
|
|
37
|
+
function format_headers(
|
|
38
|
+
headers: Record<string, string | string[]> | string,
|
|
39
|
+
): string;
|
|
40
|
+
|
|
41
|
+
/** Parses headers from a string */
|
|
42
|
+
function parse_headers(headers: string): Record<string, string>;
|
|
43
|
+
|
|
44
|
+
/** Parses HTTP method line */
|
|
45
|
+
function parse_method(
|
|
46
|
+
line: string,
|
|
47
|
+
): LuaMultiReturn<
|
|
48
|
+
[method: string, path: string, query: Record<string, string>]
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
/** Wraps a request handler for HTTP server */
|
|
52
|
+
function wrap_req_handler(
|
|
53
|
+
handler: (req: {
|
|
54
|
+
addr: string;
|
|
55
|
+
method: string;
|
|
56
|
+
path: string;
|
|
57
|
+
query: Record<string, string>;
|
|
58
|
+
headers: Record<string, string>;
|
|
59
|
+
body: string;
|
|
60
|
+
raw: string[];
|
|
61
|
+
}) => {
|
|
62
|
+
status?: string;
|
|
63
|
+
headers?: Record<string, string | string[]>;
|
|
64
|
+
body?: string;
|
|
65
|
+
},
|
|
66
|
+
expose_errors?: boolean,
|
|
67
|
+
): (req_sock: Socket, addr: string) => void;
|
|
68
|
+
|
|
69
|
+
/** Opens a websocket client */
|
|
70
|
+
function open_ws(url: string): Socket;
|
|
71
|
+
|
|
72
|
+
/** Listens for websocket connections */
|
|
73
|
+
function listen_ws(
|
|
74
|
+
addr: string,
|
|
75
|
+
callback?: (sock: Socket, addr: string) => void,
|
|
76
|
+
): Socket;
|
|
77
|
+
|
|
78
|
+
/** Opens a TCP client */
|
|
79
|
+
function open_tcp(addr: string): Socket;
|
|
80
|
+
|
|
81
|
+
/** Listens for TCP connections */
|
|
82
|
+
function listen_tcp(
|
|
83
|
+
addr: string,
|
|
84
|
+
callback?: (sock: Socket, addr: string) => void,
|
|
85
|
+
): Socket;
|
|
86
|
+
|
|
87
|
+
/** Serves HTTP (static or dynamic) */
|
|
88
|
+
function serve_http(addr: string, dir?: string): Socket;
|
|
89
|
+
|
|
90
|
+
/** Serves dynamic HTTP */
|
|
91
|
+
function serve_dynamic_http(
|
|
92
|
+
addr: string,
|
|
93
|
+
keep_alive?: boolean,
|
|
94
|
+
callback?: (sock: Socket, addr: string) => void,
|
|
95
|
+
): Socket;
|
|
96
|
+
|
|
97
|
+
/** Performs an HTTP GET */
|
|
98
|
+
function http_get(
|
|
99
|
+
url: string,
|
|
100
|
+
headers?: Record<string, string | string[]> | string,
|
|
101
|
+
ret_body_only?: boolean,
|
|
102
|
+
): Socket;
|
|
103
|
+
|
|
104
|
+
/** Performs an HTTP POST */
|
|
105
|
+
function http_post(
|
|
106
|
+
url: string,
|
|
107
|
+
headers?: Record<string, string | string[]> | string,
|
|
108
|
+
body?: string,
|
|
109
|
+
ret_body_only?: boolean,
|
|
110
|
+
): Socket;
|
|
111
|
+
|
|
112
|
+
function Socket(): Socket;
|
|
113
|
+
|
|
114
|
+
interface Socket {
|
|
115
|
+
/** Opens a websocket client */
|
|
116
|
+
open_ws(url: string): this;
|
|
117
|
+
|
|
118
|
+
/** Opens a TCP client */
|
|
119
|
+
open_tcp(addr: string): this;
|
|
120
|
+
|
|
121
|
+
/** Serves HTTP (static or dynamic) */
|
|
122
|
+
serve_http(addr: string, dir?: string): this;
|
|
123
|
+
|
|
124
|
+
/** Adds a virtual file to HTTP server */
|
|
125
|
+
add_virtual_file(filename: string, filedata: string): void;
|
|
126
|
+
|
|
127
|
+
/** Removes a virtual file from HTTP server */
|
|
128
|
+
remove_virtual_file(filename: string): void;
|
|
129
|
+
|
|
130
|
+
/** Listens for websocket connections */
|
|
131
|
+
listen_ws(
|
|
132
|
+
addr: string,
|
|
133
|
+
callback?: (sock: Socket, addr: string) => void,
|
|
134
|
+
): this;
|
|
135
|
+
|
|
136
|
+
/** Listens for TCP connections */
|
|
137
|
+
listen_tcp(
|
|
138
|
+
addr: string,
|
|
139
|
+
callback?: (sock: Socket, addr: string) => void,
|
|
140
|
+
): this;
|
|
141
|
+
|
|
142
|
+
/** Serves dynamic HTTP */
|
|
143
|
+
serve_dynamic_http(
|
|
144
|
+
addr: string,
|
|
145
|
+
keep_alive?: boolean,
|
|
146
|
+
callback?: (sock: Socket, addr: string) => void,
|
|
147
|
+
): this;
|
|
148
|
+
|
|
149
|
+
/** Sets a connection handler for server sockets */
|
|
150
|
+
on_connection(callback: (sock: Socket, addr: string) => void): this;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Polls for socket events.
|
|
154
|
+
* Returns [ok, message], where on success the message may or may not be
|
|
155
|
+
* available (non-blocking), and on failure the message is an error string.
|
|
156
|
+
*/
|
|
157
|
+
poll():
|
|
158
|
+
| LuaMultiReturn<[true, string | undefined]>
|
|
159
|
+
| LuaMultiReturn<[false, string]>;
|
|
160
|
+
|
|
161
|
+
/** Awaits a message (coroutine.yield) */
|
|
162
|
+
await():
|
|
163
|
+
LuaMultiReturn<[string, undefined]> | LuaMultiReturn<[false, string]>;
|
|
164
|
+
|
|
165
|
+
/** Awaits N messages */
|
|
166
|
+
await_n(
|
|
167
|
+
count: number,
|
|
168
|
+
): LuaMultiReturn<[string[], undefined]> | LuaMultiReturn<[false, string]>;
|
|
169
|
+
|
|
170
|
+
/** Gets the last message */
|
|
171
|
+
last_message(): string | undefined;
|
|
172
|
+
|
|
173
|
+
/** Gets the status */
|
|
174
|
+
status(): SocketStatus | undefined;
|
|
175
|
+
|
|
176
|
+
/** Sends a string message */
|
|
177
|
+
send(msg: string): void;
|
|
178
|
+
|
|
179
|
+
/** Sends a binary message */
|
|
180
|
+
send_binary(msg: string): void;
|
|
181
|
+
|
|
182
|
+
/** Closes the socket */
|
|
183
|
+
close(): void;
|
|
184
|
+
|
|
185
|
+
/** Is HTTP server */
|
|
186
|
+
is_http_server?: boolean;
|
|
187
|
+
|
|
188
|
+
/** Parent socket (for server connections) */
|
|
189
|
+
parent?: Socket;
|
|
190
|
+
|
|
191
|
+
/** Remote address (for server connections) */
|
|
192
|
+
remote_addr?: string;
|
|
193
|
+
|
|
194
|
+
/** Timeout for await */
|
|
195
|
+
timeout?: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function Reactor(): Reactor;
|
|
199
|
+
|
|
200
|
+
interface Reactor {
|
|
201
|
+
/** Initializes a reactor */
|
|
202
|
+
init(): void;
|
|
203
|
+
|
|
204
|
+
/** Runs a coroutine thread */
|
|
205
|
+
run(thread_body: (this: Reactor) => void): void;
|
|
206
|
+
|
|
207
|
+
/** Runs a server with client handler */
|
|
208
|
+
run_server(
|
|
209
|
+
server_sock: Socket,
|
|
210
|
+
client_body: (sock: Socket, addr: string) => void,
|
|
211
|
+
): void;
|
|
212
|
+
|
|
213
|
+
/** Logs messages */
|
|
214
|
+
log(...args: any[]): void;
|
|
215
|
+
|
|
216
|
+
/** Updates reactor (runs threads) */
|
|
217
|
+
update(): number;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export default pollnet;
|
package/dist/api.lua
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
-- From https://github.com/probable-basilisk/pollnet/commit/b9361aabd82e568d7e0c6b00bbe991e3dc8c56bf
|
|
2
|
+
-- with minimal adjustment to use NoitaTS-specific path for ffi.load
|
|
3
|
+
|
|
4
|
+
-- pollnet bindings for luajit+ffi
|
|
5
|
+
|
|
6
|
+
local API_VERSION = "1.0.0"
|
|
7
|
+
|
|
8
|
+
local ffi = require("ffi")
|
|
9
|
+
ffi.cdef[[
|
|
10
|
+
typedef struct pollnet_ctx pollnet_ctx;
|
|
11
|
+
typedef uint64_t sockethandle_t;
|
|
12
|
+
typedef uint32_t socketstatus_t;
|
|
13
|
+
|
|
14
|
+
const char* pollnet_version();
|
|
15
|
+
bool pollnet_handle_is_valid(sockethandle_t handle);
|
|
16
|
+
sockethandle_t pollnet_invalid_handle();
|
|
17
|
+
|
|
18
|
+
pollnet_ctx* pollnet_init();
|
|
19
|
+
pollnet_ctx* pollnet_get_or_init_static();
|
|
20
|
+
void pollnet_shutdown(pollnet_ctx* ctx);
|
|
21
|
+
sockethandle_t pollnet_open_tcp(pollnet_ctx* ctx, const char* addr);
|
|
22
|
+
sockethandle_t pollnet_listen_tcp(pollnet_ctx* ctx, const char* addr);
|
|
23
|
+
sockethandle_t pollnet_open_ws(pollnet_ctx* ctx, const char* url);
|
|
24
|
+
sockethandle_t pollnet_simple_http_get(pollnet_ctx* ctx, const char* url, const char* headers, bool ret_body_only);
|
|
25
|
+
sockethandle_t pollnet_simple_http_post(pollnet_ctx* ctx, const char* url, const char* headers, const char* data, uint32_t datasize, bool ret_body_only);
|
|
26
|
+
void pollnet_close(pollnet_ctx* ctx, sockethandle_t handle);
|
|
27
|
+
void pollnet_close_all(pollnet_ctx* ctx);
|
|
28
|
+
void pollnet_send(pollnet_ctx* ctx, sockethandle_t handle, const char* msg);
|
|
29
|
+
void pollnet_send_binary(pollnet_ctx* ctx, sockethandle_t handle, const unsigned char* msg, uint32_t msgsize);
|
|
30
|
+
socketstatus_t pollnet_update(pollnet_ctx* ctx, sockethandle_t handle);
|
|
31
|
+
socketstatus_t pollnet_update_blocking(pollnet_ctx* ctx, sockethandle_t handle);
|
|
32
|
+
uint32_t pollnet_get_data_size(pollnet_ctx* ctx, sockethandle_t handle);
|
|
33
|
+
uint32_t pollnet_get_data(pollnet_ctx* ctx, sockethandle_t handle, char* dest, uint32_t dest_size);
|
|
34
|
+
const uint8_t* pollnet_unsafe_get_data_ptr(pollnet_ctx* ctx, sockethandle_t handle);
|
|
35
|
+
void pollnet_clear_data(pollnet_ctx* ctx, sockethandle_t handle);
|
|
36
|
+
uint32_t pollnet_get_error(pollnet_ctx* ctx, sockethandle_t handle, char* dest, uint32_t dest_size);
|
|
37
|
+
sockethandle_t pollnet_get_connected_client_handle(pollnet_ctx* ctx, sockethandle_t handle);
|
|
38
|
+
sockethandle_t pollnet_listen_ws(pollnet_ctx* ctx, const char* addr);
|
|
39
|
+
sockethandle_t pollnet_serve_static_http(pollnet_ctx* ctx, const char* addr, const char* serve_dir);
|
|
40
|
+
sockethandle_t pollnet_serve_dynamic_http(pollnet_ctx* ctx, const char* addr, bool keep_alive);
|
|
41
|
+
sockethandle_t pollnet_serve_http(pollnet_ctx* ctx, const char* addr);
|
|
42
|
+
void pollnet_add_virtual_file(pollnet_ctx* ctx, sockethandle_t handle, const char* filename, const char* filedata, uint32_t filesize);
|
|
43
|
+
void pollnet_remove_virtual_file(pollnet_ctx* ctx, sockethandle_t handle, const char* filename);
|
|
44
|
+
uint32_t pollnet_get_nanoid(char* dest, uint32_t dest_size);
|
|
45
|
+
void pollnet_sleep_ms(uint32_t milliseconds);
|
|
46
|
+
]]
|
|
47
|
+
|
|
48
|
+
--- @noita-ts-include ./pollnet.dll
|
|
49
|
+
local pollnet = ffi.load("mods/" .. require("$mod").MOD_ID .. "/lua_modules/@noita-ts/pollnet/dist/pollnet.dll")
|
|
50
|
+
|
|
51
|
+
local POLLNET_VERSION = ffi.string(pollnet.pollnet_version())
|
|
52
|
+
|
|
53
|
+
do
|
|
54
|
+
local function split_version(v)
|
|
55
|
+
local major, minor, patch = v:match("(%d+)%.(%d+)%.(%d+)")
|
|
56
|
+
return tonumber(major), tonumber(minor), tonumber(patch)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
local maj_req, min_req, pat_req = split_version(API_VERSION)
|
|
60
|
+
local maj_dll, min_dll, pat_dll = split_version(POLLNET_VERSION)
|
|
61
|
+
if maj_dll ~= maj_req then
|
|
62
|
+
error("Incompatible Pollnet binary: expected " .. API_VERSION
|
|
63
|
+
.. " got " .. POLLNET_VERSION)
|
|
64
|
+
end
|
|
65
|
+
if (min_dll < min_req) or (min_dll == min_req and pat_dll < pat_req) then
|
|
66
|
+
error("Incompatible Pollnet binary: expected " .. API_VERSION
|
|
67
|
+
.. " got " .. POLLNET_VERSION)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
local POLLNET_RESULT_CODES = {
|
|
72
|
+
[0] = "invalid_handle",
|
|
73
|
+
[1] = "error",
|
|
74
|
+
[2] = "closed",
|
|
75
|
+
[3] = "opening",
|
|
76
|
+
[4] = "nodata",
|
|
77
|
+
[5] = "hasdata",
|
|
78
|
+
[6] = "newclient"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
local _ctx = nil
|
|
82
|
+
|
|
83
|
+
local function init_ctx()
|
|
84
|
+
if _ctx then return end
|
|
85
|
+
_ctx = ffi.gc(pollnet.pollnet_init(), pollnet.pollnet_shutdown)
|
|
86
|
+
assert(_ctx ~= nil)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
local function init_ctx_hack_static()
|
|
90
|
+
if _ctx then return end
|
|
91
|
+
_ctx = pollnet.pollnet_get_or_init_static()
|
|
92
|
+
assert(_ctx ~= nil)
|
|
93
|
+
pollnet.pollnet_close_all(_ctx)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
local function shutdown_ctx()
|
|
97
|
+
if not _ctx then return end
|
|
98
|
+
pollnet.pollnet_shutdown(ffi.gc(_ctx, nil))
|
|
99
|
+
_ctx = nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
local socket_mt = {}
|
|
103
|
+
local function Socket()
|
|
104
|
+
return setmetatable({}, {__index = socket_mt})
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
function socket_mt:_from_handle(handle)
|
|
108
|
+
init_ctx()
|
|
109
|
+
if self._socket then self:close() end
|
|
110
|
+
self._socket = handle
|
|
111
|
+
self._status = "unpolled"
|
|
112
|
+
return self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
function socket_mt:_open(opener, ...)
|
|
116
|
+
init_ctx()
|
|
117
|
+
if self._socket then self:close() end
|
|
118
|
+
self._socket = opener(_ctx, ...)
|
|
119
|
+
self._status = "unpolled"
|
|
120
|
+
return self
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
local function format_headers(headers)
|
|
124
|
+
if type(headers) == 'string' then return headers end
|
|
125
|
+
if type(headers) ~= 'table' then
|
|
126
|
+
error("HTTP headers must be table|string, got: " .. tostring(headers))
|
|
127
|
+
end
|
|
128
|
+
local keys = {}
|
|
129
|
+
for name, _ in pairs(headers) do
|
|
130
|
+
table.insert(keys, name)
|
|
131
|
+
end
|
|
132
|
+
table.sort(keys)
|
|
133
|
+
local frags = {}
|
|
134
|
+
for idx, name in ipairs(keys) do
|
|
135
|
+
local val = headers[name]
|
|
136
|
+
if type(val) == 'string' then
|
|
137
|
+
table.insert(frags, ("%s:%s"):format(name, val))
|
|
138
|
+
else -- assume table representing a duplicated header
|
|
139
|
+
for _, subval in ipairs(val) do
|
|
140
|
+
table.insert(frags, ("%s:%s"):format(name, subval))
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
return table.concat(frags, "\n")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
local function parse_headers(headers_str)
|
|
148
|
+
local headers = {}
|
|
149
|
+
for line in headers_str:gmatch("[^\n]+") do
|
|
150
|
+
local key, val = line:match("^([^:]*):(.*)$")
|
|
151
|
+
if key then headers[key:lower()] = val end
|
|
152
|
+
end
|
|
153
|
+
return headers
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
local function parse_query(s)
|
|
157
|
+
local queries = {[1]=s}
|
|
158
|
+
for k, v in s:gmatch("([^=&]+)=([^&]+)") do
|
|
159
|
+
queries[k] = v
|
|
160
|
+
end
|
|
161
|
+
return queries
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
local function parse_method(s)
|
|
165
|
+
local method, path, query = s:match("^(%w+) ([^?]+)%??(.*)$")
|
|
166
|
+
local queries = parse_query(query)
|
|
167
|
+
return method, path, queries
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
function socket_mt:http_get(url, headers, ret_body_only)
|
|
171
|
+
headers = format_headers(headers or "")
|
|
172
|
+
ret_body_only = not not ret_body_only
|
|
173
|
+
return self:_open(
|
|
174
|
+
pollnet.pollnet_simple_http_get,
|
|
175
|
+
url,
|
|
176
|
+
headers,
|
|
177
|
+
ret_body_only
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
function socket_mt:http_post(url, headers, body, ret_body_only)
|
|
182
|
+
body = body or ""
|
|
183
|
+
headers = format_headers(headers or {
|
|
184
|
+
["content-type"] = "application/x-www-form-urlencoded"
|
|
185
|
+
})
|
|
186
|
+
ret_body_only = not not ret_body_only
|
|
187
|
+
return self:_open(
|
|
188
|
+
pollnet.pollnet_simple_http_post,
|
|
189
|
+
url,
|
|
190
|
+
headers,
|
|
191
|
+
body,
|
|
192
|
+
#body,
|
|
193
|
+
ret_body_only
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
function socket_mt:open_ws(url)
|
|
198
|
+
return self:_open(pollnet.pollnet_open_ws, url)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
function socket_mt:open_tcp(addr)
|
|
202
|
+
return self:_open(pollnet.pollnet_open_tcp, addr)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
function socket_mt:serve_http(addr, dir)
|
|
206
|
+
self.is_http_server = true
|
|
207
|
+
if dir and dir ~= "" then
|
|
208
|
+
return self:_open(pollnet.pollnet_serve_static_http, addr, dir)
|
|
209
|
+
else
|
|
210
|
+
return self:_open(pollnet.pollnet_serve_http, addr)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
function socket_mt:add_virtual_file(filename, filedata)
|
|
215
|
+
assert(filedata and type(filedata) == 'string', "filedata must be provided as string!")
|
|
216
|
+
if filename:sub(1,1) ~= "/" then
|
|
217
|
+
-- url paths start from root at "/"
|
|
218
|
+
filename = "/" .. filename
|
|
219
|
+
end
|
|
220
|
+
local dsize = #filedata
|
|
221
|
+
pollnet.pollnet_add_virtual_file(_ctx, self._socket, filename, filedata, dsize)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
function socket_mt:remove_virtual_file(filename)
|
|
225
|
+
pollnet.pollnet_remove_virtual_file(_ctx, self._socket, filename)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
function socket_mt:listen_ws(addr, callback)
|
|
229
|
+
if callback then self:on_connection(callback) end
|
|
230
|
+
return self:_open(pollnet.pollnet_listen_ws, addr)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
function socket_mt:listen_tcp(addr, callback)
|
|
234
|
+
if callback then self:on_connection(callback) end
|
|
235
|
+
return self:_open(pollnet.pollnet_listen_tcp, addr)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
function socket_mt:serve_dynamic_http(addr, keep_alive, callback)
|
|
239
|
+
if callback then self:on_connection(callback) end
|
|
240
|
+
return self:_open(pollnet.pollnet_serve_dynamic_http, addr, keep_alive or false)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
function socket_mt:on_connection(f)
|
|
244
|
+
self._on_connection = f
|
|
245
|
+
return self
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
function socket_mt:_get_message()
|
|
249
|
+
local msg_size = pollnet.pollnet_get_data_size(_ctx, self._socket)
|
|
250
|
+
if msg_size > 0 then
|
|
251
|
+
-- Note: unsafe_get_data_ptr requires careful consideration to use safely!
|
|
252
|
+
-- Here we are OK because ffi.string copies the data to a new Lua string,
|
|
253
|
+
-- so we only hang on to the pointer long enough for the copy.
|
|
254
|
+
local raw_pointer = pollnet.pollnet_unsafe_get_data_ptr(_ctx, self._socket)
|
|
255
|
+
if raw_pointer == nil then
|
|
256
|
+
error("Impossible situation: msg_size > 0 but null data pointer")
|
|
257
|
+
end
|
|
258
|
+
return ffi.string(raw_pointer, msg_size)
|
|
259
|
+
else
|
|
260
|
+
return ""
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
function socket_mt:poll()
|
|
265
|
+
self._last_message = nil
|
|
266
|
+
if not self._socket then
|
|
267
|
+
self._status = "invalid"
|
|
268
|
+
return false, "invalid"
|
|
269
|
+
end
|
|
270
|
+
local res = POLLNET_RESULT_CODES[pollnet.pollnet_update(_ctx, self._socket)] or "error"
|
|
271
|
+
self._status = res
|
|
272
|
+
if res == "hasdata" then
|
|
273
|
+
self._status = "open"
|
|
274
|
+
self._last_message = self:_get_message()
|
|
275
|
+
return true, self._last_message
|
|
276
|
+
elseif res == "nodata" then
|
|
277
|
+
self._status = "open"
|
|
278
|
+
return true
|
|
279
|
+
elseif res == "opening" then
|
|
280
|
+
self._status = "opening"
|
|
281
|
+
return true
|
|
282
|
+
elseif res == "error" then
|
|
283
|
+
self._status = "error"
|
|
284
|
+
self._last_message = self:_get_message()
|
|
285
|
+
return false, self._last_message
|
|
286
|
+
elseif res == "closed" then
|
|
287
|
+
self._status = "closed"
|
|
288
|
+
self._last_message = "closed"
|
|
289
|
+
return false, "closed"
|
|
290
|
+
elseif res == "newclient" then
|
|
291
|
+
self._status = "open"
|
|
292
|
+
local client_addr = self:_get_message()
|
|
293
|
+
local client_handle = pollnet.pollnet_get_connected_client_handle(_ctx, self._socket)
|
|
294
|
+
assert(client_handle > 0)
|
|
295
|
+
local client_sock = Socket():_from_handle(client_handle)
|
|
296
|
+
client_sock.parent = self
|
|
297
|
+
client_sock.remote_addr = client_addr
|
|
298
|
+
if self._on_connection then
|
|
299
|
+
self._on_connection(client_sock, client_addr)
|
|
300
|
+
else
|
|
301
|
+
print("Incoming connection but no :on_connection handler! Just closing it!")
|
|
302
|
+
client_sock:close()
|
|
303
|
+
end
|
|
304
|
+
return true
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
function socket_mt:await()
|
|
309
|
+
local yield_count = 0
|
|
310
|
+
while true do
|
|
311
|
+
if self.timeout and (yield_count > self.timeout) then
|
|
312
|
+
return false, "timeout"
|
|
313
|
+
end
|
|
314
|
+
local happy, msg = self:poll()
|
|
315
|
+
if not happy then
|
|
316
|
+
self:close()
|
|
317
|
+
return false, "error: " .. tostring(msg)
|
|
318
|
+
end
|
|
319
|
+
if msg then return msg end
|
|
320
|
+
yield_count = yield_count + 1
|
|
321
|
+
coroutine.yield()
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
function socket_mt:await_n(count)
|
|
326
|
+
local parts = {}
|
|
327
|
+
for idx = 1, count do
|
|
328
|
+
local part, err = self:await()
|
|
329
|
+
if not part then return false, err end
|
|
330
|
+
parts[idx] = part
|
|
331
|
+
end
|
|
332
|
+
return parts
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
function socket_mt:last_message()
|
|
336
|
+
return self._last_message
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
function socket_mt:status()
|
|
340
|
+
return self._status
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
function socket_mt:send(msg)
|
|
344
|
+
assert(self._socket)
|
|
345
|
+
assert(type(msg) == 'string', "Argument to send must be a string")
|
|
346
|
+
pollnet.pollnet_send(_ctx, self._socket, msg)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
function socket_mt:send_binary(msg)
|
|
350
|
+
assert(self._socket)
|
|
351
|
+
assert(type(msg) == 'string', "Argument to send must be a string")
|
|
352
|
+
pollnet.pollnet_send_binary(_ctx, self._socket, msg, #msg)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
function socket_mt:close()
|
|
356
|
+
if not self._socket then return end
|
|
357
|
+
pollnet.pollnet_close(_ctx, self._socket)
|
|
358
|
+
self._socket = nil
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
local function get_nanoid()
|
|
362
|
+
local _id_scratch = ffi.new("int8_t[?]", 128)
|
|
363
|
+
local msg_size = pollnet.pollnet_get_nanoid(_id_scratch, 128)
|
|
364
|
+
return ffi.string(_id_scratch, msg_size)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
local function sleep_ms(ms)
|
|
368
|
+
pollnet.pollnet_sleep_ms(ms)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
local reactor_mt = {}
|
|
372
|
+
local function Reactor()
|
|
373
|
+
local ret = setmetatable({}, {__index = reactor_mt})
|
|
374
|
+
ret:init()
|
|
375
|
+
return ret
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
function reactor_mt:init()
|
|
379
|
+
self.threads = {}
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
function reactor_mt:run(thread_body)
|
|
383
|
+
local thread = coroutine.create(function()
|
|
384
|
+
thread_body(self)
|
|
385
|
+
end)
|
|
386
|
+
self.threads[thread] = true
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
function reactor_mt:run_server(server_sock, client_body)
|
|
390
|
+
server_sock:on_connection(function(client_sock, addr)
|
|
391
|
+
self:run(function()
|
|
392
|
+
client_body(client_sock, addr)
|
|
393
|
+
end)
|
|
394
|
+
end)
|
|
395
|
+
self:run(function()
|
|
396
|
+
while true do server_sock:await() end
|
|
397
|
+
end)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
function reactor_mt:log(...)
|
|
401
|
+
print(...)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
function reactor_mt:update()
|
|
405
|
+
local live_count = 0
|
|
406
|
+
local cur_threads = self.threads
|
|
407
|
+
self.threads = {}
|
|
408
|
+
for thread, _ in pairs(cur_threads) do
|
|
409
|
+
if coroutine.status(thread) == "dead" then
|
|
410
|
+
cur_threads[thread] = nil
|
|
411
|
+
else
|
|
412
|
+
live_count = live_count + 1
|
|
413
|
+
local happy, err = coroutine.resume(thread)
|
|
414
|
+
if not happy then self:log("Error", err) end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
for thread, _ in pairs(self.threads) do
|
|
418
|
+
live_count = live_count + 1
|
|
419
|
+
cur_threads[thread] = true
|
|
420
|
+
end
|
|
421
|
+
self.threads = cur_threads
|
|
422
|
+
return live_count
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
local function invoke_handler(handler, req, expose_errors)
|
|
426
|
+
local happy, res = pcall(handler, req)
|
|
427
|
+
if happy then
|
|
428
|
+
return res
|
|
429
|
+
else
|
|
430
|
+
return {
|
|
431
|
+
status = "500",
|
|
432
|
+
body = (expose_errors and tostring(res)) or "Internal Error"
|
|
433
|
+
}
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
local function wrap_req_handler(handler, expose_errors)
|
|
438
|
+
return function(req_sock, addr)
|
|
439
|
+
while true do
|
|
440
|
+
local raw_req = req_sock:await_n(3)
|
|
441
|
+
if not raw_req then break end
|
|
442
|
+
local method, path, query = parse_method(raw_req[1])
|
|
443
|
+
local headers = parse_headers(raw_req[2])
|
|
444
|
+
local reply = invoke_handler(handler, {
|
|
445
|
+
addr = addr,
|
|
446
|
+
method = method,
|
|
447
|
+
path = path,
|
|
448
|
+
query = query,
|
|
449
|
+
headers = headers,
|
|
450
|
+
body = raw_req[3],
|
|
451
|
+
raw = raw_req
|
|
452
|
+
}, expose_errors)
|
|
453
|
+
req_sock:send(reply.status or "404")
|
|
454
|
+
req_sock:send(format_headers(reply.headers or {}))
|
|
455
|
+
req_sock:send_binary(reply.body or "")
|
|
456
|
+
end
|
|
457
|
+
req_sock:close()
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
local exports = {
|
|
462
|
+
VERSION = POLLNET_VERSION,
|
|
463
|
+
init = init_ctx,
|
|
464
|
+
init_hack_static = init_ctx_hack_static,
|
|
465
|
+
shutdown = shutdown_ctx,
|
|
466
|
+
Socket = Socket,
|
|
467
|
+
Reactor = Reactor,
|
|
468
|
+
pollnet = pollnet,
|
|
469
|
+
nanoid = get_nanoid,
|
|
470
|
+
sleep_ms = sleep_ms,
|
|
471
|
+
format_headers = format_headers,
|
|
472
|
+
parse_headers = parse_headers,
|
|
473
|
+
parse_method = parse_method,
|
|
474
|
+
wrap_req_handler = wrap_req_handler
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
local fnames = {
|
|
478
|
+
"open_ws", "listen_ws", "open_tcp", "listen_tcp",
|
|
479
|
+
"serve_http", "serve_dynamic_http", "http_get", "http_post"
|
|
480
|
+
}
|
|
481
|
+
for _, name in ipairs(fnames) do
|
|
482
|
+
exports[name] = function(...)
|
|
483
|
+
local sock = Socket()
|
|
484
|
+
return sock[name](sock, ...)
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
exports.default = exports
|
|
489
|
+
|
|
490
|
+
return exports
|