@noita-ts/wininet 0.0.0-stage → 0.1.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/asm_link.lua +47 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.lua +457 -0
- package/dist/lualib_bundle.lua +301 -0
- package/dist/thread.d.ts +46 -0
- package/dist/thread.lua +382 -0
- package/package.json +31 -4
- package/readme.md +66 -0
- package/README.md +0 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- AUTO-GENERATED by @noita-ts/nasm. Do not edit.
|
|
2
|
+
local M = {}
|
|
3
|
+
local band, rshift = bit.band, bit.rshift
|
|
4
|
+
|
|
5
|
+
local function add32(bytes, off, value)
|
|
6
|
+
value = value
|
|
7
|
+
+ bytes[off + 1]
|
|
8
|
+
+ bytes[off + 2] * 0x100
|
|
9
|
+
+ bytes[off + 3] * 0x10000
|
|
10
|
+
+ bytes[off + 4] * 0x1000000
|
|
11
|
+
bytes[off + 1] = band(value, 0xFF)
|
|
12
|
+
bytes[off + 2] = band(rshift(value, 8), 0xFF)
|
|
13
|
+
bytes[off + 3] = band(rshift(value, 16), 0xFF)
|
|
14
|
+
bytes[off + 4] = band(rshift(value, 24), 0xFF)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
function M.link(patch, values)
|
|
18
|
+
local vars = patch.vars
|
|
19
|
+
if vars.BASE and not (values and values.BASE) then
|
|
20
|
+
return function(base)
|
|
21
|
+
local withBase = { BASE = base }
|
|
22
|
+
if values then
|
|
23
|
+
for name, value in pairs(values) do
|
|
24
|
+
withBase[name] = value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
return M.link(patch, withBase)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
local raw = patch.raw
|
|
31
|
+
local bytes = { entry = patch.labels.entry or 0 }
|
|
32
|
+
for i = 1, #raw do
|
|
33
|
+
bytes[i] = raw[i]
|
|
34
|
+
end
|
|
35
|
+
for name, offsets in pairs(vars) do
|
|
36
|
+
local value = values and values[name]
|
|
37
|
+
if value == nil then
|
|
38
|
+
error(string.format("asm: no value given for reloc '%s'", name), 2)
|
|
39
|
+
end
|
|
40
|
+
for i = 1, #offsets do
|
|
41
|
+
add32(bytes, offsets[i], value)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
return bytes
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return M
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** @noSelfInFile */
|
|
2
|
+
export declare class HttpResponse {
|
|
3
|
+
status: number;
|
|
4
|
+
headers: string;
|
|
5
|
+
body: string;
|
|
6
|
+
constructor(status: number, headers: string, body: string);
|
|
7
|
+
/**
|
|
8
|
+
* Parse the raw header block into a map.
|
|
9
|
+
* Header names seen more than once collapse into an array of their values, in
|
|
10
|
+
* order.
|
|
11
|
+
*/
|
|
12
|
+
parseHeaders(): Record<string, string | string[]>;
|
|
13
|
+
}
|
|
14
|
+
/** The headers of a request: a map, or lines separated by `\n`. */
|
|
15
|
+
export type Headers = Record<string, string | string[]> | string;
|
|
16
|
+
/**
|
|
17
|
+
* Anything that looks, walks and quacks like a scheduler (to not depend on
|
|
18
|
+
* `@noita-ts/base` directly).
|
|
19
|
+
* Import and set up a `Scheduler` from `@noita-ts/base/async` for this.
|
|
20
|
+
*/
|
|
21
|
+
interface Scheduler {
|
|
22
|
+
spawn<T>(body: () => T): Promise<T>;
|
|
23
|
+
}
|
|
24
|
+
export interface HttpClientOptions {
|
|
25
|
+
/** The `User-Agent` of the requests. Defaults to `noita-ts/wininet`. */
|
|
26
|
+
userAgent?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The timeout of a connection, of a send and of a receive, in
|
|
29
|
+
* milliseconds. Defaults to 30 seconds.
|
|
30
|
+
*/
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* An async HTTP client built on top of a {@link Scheduler}.
|
|
35
|
+
*
|
|
36
|
+
* Requests run in coroutines and resolve to an {@link HttpResponse}. The
|
|
37
|
+
* blocking WinINet calls run on native threads, so the game runs while a
|
|
38
|
+
* request waits.
|
|
39
|
+
*
|
|
40
|
+
* A client keeps a WinINet session until the Lua state is closed, so make one
|
|
41
|
+
* client and keep it. When the state is closed, the client closes its handles
|
|
42
|
+
* and frees its memory; the thread of a request that still runs frees the
|
|
43
|
+
* memory of its call when the call ends.
|
|
44
|
+
*/
|
|
45
|
+
export declare class HttpClient {
|
|
46
|
+
private scheduler;
|
|
47
|
+
private userAgent;
|
|
48
|
+
private timeoutMs;
|
|
49
|
+
private internet;
|
|
50
|
+
/** The open request and connection handles, with their kinds. */
|
|
51
|
+
private handles;
|
|
52
|
+
/** The memory that the threads of the requests use. */
|
|
53
|
+
private pool;
|
|
54
|
+
private released;
|
|
55
|
+
/** Removes the release of this client from the release of the state. */
|
|
56
|
+
private unregister;
|
|
57
|
+
constructor(scheduler: Scheduler, options?: HttpClientOptions);
|
|
58
|
+
/**
|
|
59
|
+
* Perform an HTTP GET.
|
|
60
|
+
*/
|
|
61
|
+
get(url: string, headers?: Headers): Promise<HttpResponse>;
|
|
62
|
+
/**
|
|
63
|
+
* Perform an HTTP POST with an optional request `body`.
|
|
64
|
+
*
|
|
65
|
+
* Without `headers`, the request has
|
|
66
|
+
* `content-type: application/x-www-form-urlencoded`, as in pollnet.
|
|
67
|
+
*/
|
|
68
|
+
post(url: string, headers?: Headers, body?: string): Promise<HttpResponse>;
|
|
69
|
+
/**
|
|
70
|
+
* Does now what closing the Lua state does: closes the WinINet handles of
|
|
71
|
+
* the client and frees its memory. A request that runs is abandoned: its
|
|
72
|
+
* promise rejects, and its thread frees the memory of its call when the call
|
|
73
|
+
* ends. The client takes no requests after that.
|
|
74
|
+
*
|
|
75
|
+
* Only for tests.
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
_release(): void;
|
|
80
|
+
/** Remembers `handle` until `close`, or until the client is released. */
|
|
81
|
+
private track;
|
|
82
|
+
/** Closes a handle of `track`, unless the release closed it already. */
|
|
83
|
+
private close;
|
|
84
|
+
/**
|
|
85
|
+
* The WinINet session of this client, made on first use. It stays open
|
|
86
|
+
* until the client is released, so that WinINet can keep the connections
|
|
87
|
+
* alive.
|
|
88
|
+
*/
|
|
89
|
+
private open;
|
|
90
|
+
/**
|
|
91
|
+
* Sets the timeouts of a handle. Wine takes the send and receive timeouts
|
|
92
|
+
* only from a connection handle, and waits forever without them, so each
|
|
93
|
+
* connection gets them too.
|
|
94
|
+
*/
|
|
95
|
+
private setTimeouts;
|
|
96
|
+
private request;
|
|
97
|
+
/** Sends the request and reads the response. */
|
|
98
|
+
private exchange;
|
|
99
|
+
}
|
|
100
|
+
export {};
|
package/dist/index.lua
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
local ____lualib = require("lualib_bundle")
|
|
2
|
+
local __TS__Class = ____lualib.__TS__Class
|
|
3
|
+
local __TS__StringSlice = ____lualib.__TS__StringSlice
|
|
4
|
+
local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray
|
|
5
|
+
local __TS__StringSplit = ____lualib.__TS__StringSplit
|
|
6
|
+
local Error = ____lualib.Error
|
|
7
|
+
local RangeError = ____lualib.RangeError
|
|
8
|
+
local ReferenceError = ____lualib.ReferenceError
|
|
9
|
+
local SyntaxError = ____lualib.SyntaxError
|
|
10
|
+
local TypeError = ____lualib.TypeError
|
|
11
|
+
local URIError = ____lualib.URIError
|
|
12
|
+
local __TS__New = ____lualib.__TS__New
|
|
13
|
+
local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith
|
|
14
|
+
local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys
|
|
15
|
+
local __TS__ArraySort = ____lualib.__TS__ArraySort
|
|
16
|
+
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
|
|
17
|
+
local __TS__ArrayMap = ____lualib.__TS__ArrayMap
|
|
18
|
+
local __TS__ArraySlice = ____lualib.__TS__ArraySlice
|
|
19
|
+
local ____exports = {}
|
|
20
|
+
local ____ffi = require("@noita-ts/ffi")
|
|
21
|
+
local ffi = ____ffi.default
|
|
22
|
+
local ____thread = require("thread")
|
|
23
|
+
local Pool = ____thread.Pool
|
|
24
|
+
ffi.cdef("\n void* InternetOpenA(const char* agent, uint32_t access, const char* proxy, const char* bypass, uint32_t flags);\n void* InternetConnectA(void* internet, const char* server, uint16_t port, const char* user, const char* password, uint32_t service, uint32_t flags, uintptr_t context);\n void* HttpOpenRequestA(void* connect, const char* verb, const char* object, const char* version, const char* referrer, const char** accept, uint32_t flags, uintptr_t context);\n int HttpSendRequestA(void* request, const char* headers, uint32_t headers_length, void* optional, uint32_t optional_length);\n int InternetSetOptionA(void* internet, uint32_t option, void* buffer, uint32_t length);\n int InternetReadFile(void* file, void* buffer, uint32_t size, uint32_t* read);\n int HttpQueryInfoA(void* request, uint32_t info, void* buffer, uint32_t* length, uint32_t* index);\n int InternetCloseHandle(void* internet);\n uint32_t GetLastError();\n")
|
|
25
|
+
local wininet = ffi.load("wininet.dll")
|
|
26
|
+
local INTERNET_OPEN_TYPE_PRECONFIG = 0
|
|
27
|
+
local INTERNET_SERVICE_HTTP = 3
|
|
28
|
+
local INTERNET_OPTION_CONNECT_TIMEOUT = 2
|
|
29
|
+
local INTERNET_OPTION_SEND_TIMEOUT = 5
|
|
30
|
+
local INTERNET_OPTION_RECEIVE_TIMEOUT = 6
|
|
31
|
+
local INTERNET_FLAG_RELOAD = 2147483648
|
|
32
|
+
local INTERNET_FLAG_NO_CACHE_WRITE = 67108864
|
|
33
|
+
local INTERNET_FLAG_SECURE = 8388608
|
|
34
|
+
local INTERNET_FLAG_NO_COOKIES = 524288
|
|
35
|
+
local INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP = 32768
|
|
36
|
+
local INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS = 16384
|
|
37
|
+
local INTERNET_FLAG_NO_UI = 512
|
|
38
|
+
local HTTP_QUERY_STATUS_CODE = 19
|
|
39
|
+
local HTTP_QUERY_RAW_HEADERS_CRLF = 22
|
|
40
|
+
local HTTP_QUERY_FLAG_NUMBER = 536870912
|
|
41
|
+
--- The size of one read of the body.
|
|
42
|
+
local CHUNK = 256 * 1024
|
|
43
|
+
--- Names of the common WinINet errors, for the error messages.
|
|
44
|
+
local ERRORS = {
|
|
45
|
+
[12002] = "timeout",
|
|
46
|
+
[12005] = "invalid URL",
|
|
47
|
+
[12006] = "unrecognized scheme",
|
|
48
|
+
[12007] = "name not resolved",
|
|
49
|
+
[12029] = "cannot connect",
|
|
50
|
+
[12030] = "connection aborted",
|
|
51
|
+
[12031] = "connection reset",
|
|
52
|
+
[12037] = "certificate date invalid",
|
|
53
|
+
[12038] = "certificate name invalid",
|
|
54
|
+
[12045] = "invalid certificate authority",
|
|
55
|
+
[12057] = "certificate revocation check failed",
|
|
56
|
+
[12152] = "invalid server response",
|
|
57
|
+
[12157] = "security channel error",
|
|
58
|
+
[12169] = "invalid certificate",
|
|
59
|
+
[12170] = "certificate revoked",
|
|
60
|
+
[12175] = "decoding failed"
|
|
61
|
+
}
|
|
62
|
+
local function describe(____error)
|
|
63
|
+
local name = ERRORS[____error]
|
|
64
|
+
return name ~= nil and ((("WinINet error " .. tostring(____error)) .. " (") .. name) .. ")" or "WinINet error " .. tostring(____error)
|
|
65
|
+
end
|
|
66
|
+
____exports.HttpResponse = __TS__Class()
|
|
67
|
+
local HttpResponse = ____exports.HttpResponse
|
|
68
|
+
HttpResponse.name = "HttpResponse"
|
|
69
|
+
function HttpResponse.prototype.____constructor(self, status, headers, body)
|
|
70
|
+
self.status = status
|
|
71
|
+
self.headers = headers
|
|
72
|
+
self.body = body
|
|
73
|
+
end
|
|
74
|
+
function HttpResponse.prototype.parseHeaders(self)
|
|
75
|
+
local headers = {}
|
|
76
|
+
for ____, line in ipairs(__TS__StringSplit(self.headers, "\n")) do
|
|
77
|
+
do
|
|
78
|
+
local sep = (string.find(line, ": ", nil, true) or 0) - 1
|
|
79
|
+
local key = __TS__StringSlice(line, 0, sep)
|
|
80
|
+
local value = __TS__StringSlice(line, sep + 2)
|
|
81
|
+
if sep <= 0 or not value then
|
|
82
|
+
goto __continue5
|
|
83
|
+
end
|
|
84
|
+
local prev = headers[key]
|
|
85
|
+
if not prev then
|
|
86
|
+
headers[key] = value
|
|
87
|
+
goto __continue5
|
|
88
|
+
end
|
|
89
|
+
if __TS__ArrayIsArray(prev) then
|
|
90
|
+
prev[#prev + 1] = value
|
|
91
|
+
else
|
|
92
|
+
headers[key] = {prev, value}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
::__continue5::
|
|
96
|
+
end
|
|
97
|
+
return headers
|
|
98
|
+
end
|
|
99
|
+
--- A URL, split into the parts that WinINet takes.
|
|
100
|
+
local function parseUrl(url)
|
|
101
|
+
local scheme, host, port, rest = string.match(url, "^(%a[%w+.-]*)://([^/:?#]+):?(%d*)(.*)$")
|
|
102
|
+
local ____opt_0 = scheme
|
|
103
|
+
local lower = ____opt_0 and string.lower(scheme)
|
|
104
|
+
if lower ~= "http" and lower ~= "https" then
|
|
105
|
+
error(
|
|
106
|
+
__TS__New(Error, "not an http or https URL: " .. url),
|
|
107
|
+
0
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
local secure = lower == "https"
|
|
111
|
+
local path = (string.gsub(rest, "#.*$", ""))
|
|
112
|
+
if not __TS__StringStartsWith(path, "/") then
|
|
113
|
+
path = "/" .. path
|
|
114
|
+
end
|
|
115
|
+
return {
|
|
116
|
+
secure = secure,
|
|
117
|
+
host = host,
|
|
118
|
+
port = port ~= "" and tonumber(port) or (secure and 443 or 80),
|
|
119
|
+
path = path
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
--- Headers as WinINet takes them: `Key: value` lines, each ended by CRLF.
|
|
123
|
+
local function formatHeaders(headers)
|
|
124
|
+
local lines = {}
|
|
125
|
+
if type(headers) == "string" then
|
|
126
|
+
for ____, line in ipairs(__TS__StringSplit(headers, "\n")) do
|
|
127
|
+
lines[#lines + 1] = (string.gsub(line, "\r$", ""))
|
|
128
|
+
end
|
|
129
|
+
else
|
|
130
|
+
local keys = __TS__ObjectKeys(headers)
|
|
131
|
+
__TS__ArraySort(keys)
|
|
132
|
+
for ____, key in ipairs(keys) do
|
|
133
|
+
local value = headers[key]
|
|
134
|
+
for ____, v in ipairs(type(value) == "string" and ({value}) or value) do
|
|
135
|
+
lines[#lines + 1] = (key .. ": ") .. v
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
return table.concat(
|
|
140
|
+
__TS__ArrayMap(
|
|
141
|
+
__TS__ArrayFilter(
|
|
142
|
+
lines,
|
|
143
|
+
function(____, line) return line ~= "" end
|
|
144
|
+
),
|
|
145
|
+
function(____, line) return line .. "\r\n" end
|
|
146
|
+
),
|
|
147
|
+
""
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
--- The response headers as `HttpResponse` of pollnet has them: `Key: value`
|
|
151
|
+
-- lines separated by `\n`, without the status line.
|
|
152
|
+
local function responseHeaders(raw)
|
|
153
|
+
local lines = __TS__StringSplit((string.gsub(raw, "\r\n", "\n")), "\n")
|
|
154
|
+
return table.concat(
|
|
155
|
+
__TS__ArrayFilter(
|
|
156
|
+
__TS__ArraySlice(lines, 1),
|
|
157
|
+
function(____, line) return line ~= "" end
|
|
158
|
+
),
|
|
159
|
+
"\n"
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
--- The kinds of the WinINet handles of a request, children first.
|
|
163
|
+
local REQUEST = 1
|
|
164
|
+
local CONNECT = 2
|
|
165
|
+
--- An async HTTP client built on top of a {@link Scheduler}.
|
|
166
|
+
--
|
|
167
|
+
-- Requests run in coroutines and resolve to an {@link HttpResponse}. The
|
|
168
|
+
-- blocking WinINet calls run on native threads, so the game runs while a
|
|
169
|
+
-- request waits.
|
|
170
|
+
--
|
|
171
|
+
-- A client keeps a WinINet session until the Lua state is closed, so make one
|
|
172
|
+
-- client and keep it. When the state is closed, the client closes its handles
|
|
173
|
+
-- and frees its memory; the thread of a request that still runs frees the
|
|
174
|
+
-- memory of its call when the call ends.
|
|
175
|
+
____exports.HttpClient = __TS__Class()
|
|
176
|
+
local HttpClient = ____exports.HttpClient
|
|
177
|
+
HttpClient.name = "HttpClient"
|
|
178
|
+
function HttpClient.prototype.____constructor(self, scheduler, options)
|
|
179
|
+
self.handles = {}
|
|
180
|
+
self.pool = __TS__New(Pool)
|
|
181
|
+
self.released = false
|
|
182
|
+
self.scheduler = scheduler
|
|
183
|
+
self.userAgent = options and options.userAgent or "noita-ts/wininet"
|
|
184
|
+
self.timeoutMs = options and options.timeoutMs or 30000
|
|
185
|
+
self.unregister = ffi.onRelease(function() return self:_release() end)
|
|
186
|
+
end
|
|
187
|
+
function HttpClient.prototype.get(self, url, headers)
|
|
188
|
+
return self:request("GET", url, headers or "", "")
|
|
189
|
+
end
|
|
190
|
+
function HttpClient.prototype.post(self, url, headers, body)
|
|
191
|
+
return self:request("POST", url, headers or ({["content-type"] = "application/x-www-form-urlencoded"}), body or "")
|
|
192
|
+
end
|
|
193
|
+
function HttpClient.prototype._release(self)
|
|
194
|
+
if self.released then
|
|
195
|
+
return
|
|
196
|
+
end
|
|
197
|
+
self.released = true
|
|
198
|
+
self.unregister()
|
|
199
|
+
for ____, kind in ipairs({REQUEST, CONNECT}) do
|
|
200
|
+
for handle, k in pairs(self.handles) do
|
|
201
|
+
if k == kind then
|
|
202
|
+
wininet.InternetCloseHandle(handle)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
self.handles = {}
|
|
207
|
+
if self.internet ~= nil then
|
|
208
|
+
wininet.InternetCloseHandle(self.internet)
|
|
209
|
+
self.internet = nil
|
|
210
|
+
end
|
|
211
|
+
self.pool:release()
|
|
212
|
+
end
|
|
213
|
+
function HttpClient.prototype.track(self, handle, kind)
|
|
214
|
+
if handle ~= nil then
|
|
215
|
+
self.handles[handle] = kind
|
|
216
|
+
end
|
|
217
|
+
return handle
|
|
218
|
+
end
|
|
219
|
+
function HttpClient.prototype.close(self, handle)
|
|
220
|
+
if self.handles[handle] ~= nil then
|
|
221
|
+
self.handles[handle] = nil
|
|
222
|
+
wininet.InternetCloseHandle(handle)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
function HttpClient.prototype.open(self)
|
|
226
|
+
if self.released then
|
|
227
|
+
error(
|
|
228
|
+
__TS__New(Error, "the client is released"),
|
|
229
|
+
0
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
if self.internet == nil then
|
|
233
|
+
local internet = wininet.InternetOpenA(
|
|
234
|
+
self.userAgent,
|
|
235
|
+
INTERNET_OPEN_TYPE_PRECONFIG,
|
|
236
|
+
nil,
|
|
237
|
+
nil,
|
|
238
|
+
0
|
|
239
|
+
)
|
|
240
|
+
if internet == nil then
|
|
241
|
+
error(
|
|
242
|
+
__TS__New(
|
|
243
|
+
Error,
|
|
244
|
+
"cannot open WinINet: " .. describe(ffi.C.GetLastError())
|
|
245
|
+
),
|
|
246
|
+
0
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
self:setTimeouts(internet)
|
|
250
|
+
self.internet = internet
|
|
251
|
+
end
|
|
252
|
+
return self.internet
|
|
253
|
+
end
|
|
254
|
+
function HttpClient.prototype.setTimeouts(self, handle)
|
|
255
|
+
local timeout = ffi.new("uint32_t[1]", self.timeoutMs)
|
|
256
|
+
for ____, option in ipairs({INTERNET_OPTION_CONNECT_TIMEOUT, INTERNET_OPTION_SEND_TIMEOUT, INTERNET_OPTION_RECEIVE_TIMEOUT}) do
|
|
257
|
+
wininet.InternetSetOptionA(handle, option, timeout, 4)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
function HttpClient.prototype.request(self, method, url, headers, body)
|
|
261
|
+
return self.scheduler:spawn(function()
|
|
262
|
+
local ____parseUrl_result_6 = parseUrl(url)
|
|
263
|
+
local secure = ____parseUrl_result_6.secure
|
|
264
|
+
local host = ____parseUrl_result_6.host
|
|
265
|
+
local port = ____parseUrl_result_6.port
|
|
266
|
+
local path = ____parseUrl_result_6.path
|
|
267
|
+
local function fail(what, ____error)
|
|
268
|
+
error(
|
|
269
|
+
__TS__New(
|
|
270
|
+
Error,
|
|
271
|
+
(((((method .. " ") .. url) .. ": ") .. what) .. ": ") .. describe(____error)
|
|
272
|
+
),
|
|
273
|
+
0
|
|
274
|
+
)
|
|
275
|
+
end
|
|
276
|
+
local connect = self:track(
|
|
277
|
+
wininet.InternetConnectA(
|
|
278
|
+
self:open(),
|
|
279
|
+
host,
|
|
280
|
+
port,
|
|
281
|
+
nil,
|
|
282
|
+
nil,
|
|
283
|
+
INTERNET_SERVICE_HTTP,
|
|
284
|
+
0,
|
|
285
|
+
0
|
|
286
|
+
),
|
|
287
|
+
CONNECT
|
|
288
|
+
)
|
|
289
|
+
if connect == nil then
|
|
290
|
+
fail(
|
|
291
|
+
"cannot connect",
|
|
292
|
+
ffi.C.GetLastError()
|
|
293
|
+
)
|
|
294
|
+
end
|
|
295
|
+
do
|
|
296
|
+
local ____try, ____hasReturned, ____returnValue = pcall(function()
|
|
297
|
+
self:setTimeouts(connect)
|
|
298
|
+
local flags = bit.bor(
|
|
299
|
+
bit.bor(
|
|
300
|
+
bit.bor(
|
|
301
|
+
bit.bor(
|
|
302
|
+
bit.bor(INTERNET_FLAG_RELOAD, INTERNET_FLAG_NO_CACHE_WRITE),
|
|
303
|
+
INTERNET_FLAG_NO_COOKIES
|
|
304
|
+
),
|
|
305
|
+
INTERNET_FLAG_NO_UI
|
|
306
|
+
),
|
|
307
|
+
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
|
|
308
|
+
),
|
|
309
|
+
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
|
|
310
|
+
)
|
|
311
|
+
if secure then
|
|
312
|
+
flags = bit.bor(flags, INTERNET_FLAG_SECURE)
|
|
313
|
+
end
|
|
314
|
+
local request = self:track(
|
|
315
|
+
wininet.HttpOpenRequestA(
|
|
316
|
+
connect,
|
|
317
|
+
method,
|
|
318
|
+
path,
|
|
319
|
+
nil,
|
|
320
|
+
nil,
|
|
321
|
+
nil,
|
|
322
|
+
flags,
|
|
323
|
+
0
|
|
324
|
+
),
|
|
325
|
+
REQUEST
|
|
326
|
+
)
|
|
327
|
+
if request == nil then
|
|
328
|
+
fail(
|
|
329
|
+
"cannot open the request",
|
|
330
|
+
ffi.C.GetLastError()
|
|
331
|
+
)
|
|
332
|
+
end
|
|
333
|
+
do
|
|
334
|
+
local ____try, ____hasReturned, ____returnValue = pcall(function()
|
|
335
|
+
return true, self:exchange(request, headers, body, fail)
|
|
336
|
+
end)
|
|
337
|
+
do
|
|
338
|
+
self:close(request)
|
|
339
|
+
end
|
|
340
|
+
if not ____try then
|
|
341
|
+
error(____hasReturned, 0)
|
|
342
|
+
end
|
|
343
|
+
if ____try and ____hasReturned then
|
|
344
|
+
return true, ____returnValue
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end)
|
|
348
|
+
do
|
|
349
|
+
self:close(connect)
|
|
350
|
+
end
|
|
351
|
+
if not ____try then
|
|
352
|
+
error(____hasReturned, 0)
|
|
353
|
+
end
|
|
354
|
+
if ____try and ____hasReturned then
|
|
355
|
+
return ____returnValue
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end)
|
|
359
|
+
end
|
|
360
|
+
function HttpClient.prototype.exchange(self, request, headers, body, fail)
|
|
361
|
+
local formatted = formatHeaders(headers)
|
|
362
|
+
local sent, sendError = unpack(
|
|
363
|
+
self.pool:run(
|
|
364
|
+
wininet.HttpSendRequestA,
|
|
365
|
+
request,
|
|
366
|
+
formatted ~= "" and formatted or 0,
|
|
367
|
+
#formatted,
|
|
368
|
+
body ~= "" and body or 0,
|
|
369
|
+
#body
|
|
370
|
+
),
|
|
371
|
+
1,
|
|
372
|
+
2
|
|
373
|
+
)
|
|
374
|
+
if sent == 0 then
|
|
375
|
+
fail("cannot send the request", sendError)
|
|
376
|
+
end
|
|
377
|
+
local status = ffi.new("uint32_t[1]")
|
|
378
|
+
local length = ffi.new("uint32_t[1]", 4)
|
|
379
|
+
if wininet.HttpQueryInfoA(
|
|
380
|
+
request,
|
|
381
|
+
bit.bor(HTTP_QUERY_STATUS_CODE, HTTP_QUERY_FLAG_NUMBER),
|
|
382
|
+
status,
|
|
383
|
+
length,
|
|
384
|
+
nil
|
|
385
|
+
) == 0 then
|
|
386
|
+
fail(
|
|
387
|
+
"no status in the response",
|
|
388
|
+
ffi.C.GetLastError()
|
|
389
|
+
)
|
|
390
|
+
end
|
|
391
|
+
length[0] = 0
|
|
392
|
+
wininet.HttpQueryInfoA(
|
|
393
|
+
request,
|
|
394
|
+
HTTP_QUERY_RAW_HEADERS_CRLF,
|
|
395
|
+
nil,
|
|
396
|
+
length,
|
|
397
|
+
nil
|
|
398
|
+
)
|
|
399
|
+
local raw = ""
|
|
400
|
+
if length[0] > 0 then
|
|
401
|
+
local buffer = ffi.new("char[?]", length[0] + 1)
|
|
402
|
+
if wininet.HttpQueryInfoA(
|
|
403
|
+
request,
|
|
404
|
+
HTTP_QUERY_RAW_HEADERS_CRLF,
|
|
405
|
+
buffer,
|
|
406
|
+
length,
|
|
407
|
+
nil
|
|
408
|
+
) == 0 then
|
|
409
|
+
fail(
|
|
410
|
+
"cannot read the response headers",
|
|
411
|
+
ffi.C.GetLastError()
|
|
412
|
+
)
|
|
413
|
+
end
|
|
414
|
+
raw = ffi.string(buffer, length[0])
|
|
415
|
+
end
|
|
416
|
+
local buffer = self.pool:alloc("uint8_t*", CHUNK)
|
|
417
|
+
local read = self.pool:alloc("uint32_t*", 4)
|
|
418
|
+
local parts = {}
|
|
419
|
+
do
|
|
420
|
+
local ____try, ____error = pcall(function()
|
|
421
|
+
while true do
|
|
422
|
+
local ok, ____error = unpack(
|
|
423
|
+
self.pool:run(
|
|
424
|
+
wininet.InternetReadFile,
|
|
425
|
+
request,
|
|
426
|
+
buffer,
|
|
427
|
+
CHUNK,
|
|
428
|
+
read
|
|
429
|
+
),
|
|
430
|
+
1,
|
|
431
|
+
2
|
|
432
|
+
)
|
|
433
|
+
if ok == 0 then
|
|
434
|
+
fail("cannot read the response", ____error)
|
|
435
|
+
end
|
|
436
|
+
if read[0] == 0 then
|
|
437
|
+
break
|
|
438
|
+
end
|
|
439
|
+
parts[#parts + 1] = ffi.string(buffer, read[0])
|
|
440
|
+
end
|
|
441
|
+
end)
|
|
442
|
+
do
|
|
443
|
+
self.pool:free(buffer)
|
|
444
|
+
self.pool:free(read)
|
|
445
|
+
end
|
|
446
|
+
if not ____try then
|
|
447
|
+
error(____error, 0)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
return __TS__New(
|
|
451
|
+
____exports.HttpResponse,
|
|
452
|
+
status[0],
|
|
453
|
+
responseHeaders(raw),
|
|
454
|
+
table.concat(parts, "")
|
|
455
|
+
)
|
|
456
|
+
end
|
|
457
|
+
return ____exports
|