-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfetch.lua
More file actions
188 lines (163 loc) · 5.43 KB
/
fetch.lua
File metadata and controls
188 lines (163 loc) · 5.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
--[[
Fetch for Playdate
A wrapper to simplify HTTP requests
SETUP
Make sure you call HTTP.update()
in your playdate.update handler
PARAMS
HTTP.fetch(url, [options], callback, [reason])
- url: string of the full URL (including http:// or https://)
- options: (optional) table with additional options for the request
- callback: function that is called with (response, error)
- reason: (optional) string that is shown in the network access popup
The response table contains:
{ ok: boolean, status: number, statusText: string, body: string, headers: table }
The options table can contain the following (all optional):
- method: string of the HTTP verb to use
- headers: string or table to set the request headers
- body: string of the data to send with the request
BASIC EXAMPLE
HTTP.fetch("http://example.com", function(res, err)
if not err and res.ok then
print(res.body)
end
end)
ADVANCED EXAMPLE
HTTP.fetch("https://example.com/auth", {
method: "POST",
headers = { ["Content-Type"] = "application/json" },
body = json.encode({ username = "crankles", password = "*****" })
}, function(res)
local token = res.headers["Authorization"]
-- ...
end)
]] --
local function noop() end
local function parseURL(url)
local scheme, host, port, path = url:match("^([a-zA-Z][a-zA-Z0-9+.-]*)://([^/:]+):?(%d*)(/?.*)$")
return scheme, host, tonumber(port), path
end
local statusText <const> = {
[100] = "Continue",
[101] = "Switching Protocols",
[102] = "Processing",
[103] = "Early Hints",
[200] = "OK",
[201] = "Created",
[202] = "Accepted",
[203] = "Non-Authoritative Information",
[204] = "No Content",
[205] = "Reset Content",
[206] = "Partial Content",
[207] = "Multi-Status",
[208] = "Already Reported",
[226] = "IM Used",
-- 3xx resolved internally
[400] = "Bad Request",
[401] = "Unauthorized",
[402] = "Payment Required",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[406] = "Not Acceptable",
[407] = "Proxy Authentication Required",
[408] = "Request Timeout",
[409] = "Conflict",
[410] = "Gone",
[411] = "Length Required",
[412] = "Precondition Failed",
[413] = "Content Too Large",
[414] = "URI Too Long",
[415] = "Unsupported Media Type",
[416] = "Range Not Satisfiable",
[417] = "Expectation Failed",
[418] = "I'm a teapot",
[421] = "Misdirected Request",
[422] = "Unprocessable Content",
[423] = "Locked",
[424] = "Failed Dependency",
[425] = "Too Early",
[426] = "Upgrade Required",
[428] = "Precondition Required",
[429] = "Too Many Requests",
[431] = "Request Header Fields Too Large",
[451] = "Unavailable For Legal Reasons",
[500] = "Internal Server Error",
[501] = "Not Implemented",
[502] = "Bad Gateway",
[503] = "Service Unavailable",
[504] = "Gateway Timeout",
[505] = "HTTP Version Not Supported",
[506] = "Variant Also Negotiates",
[507] = "Insufficient Storage",
[508] = "Loop Detected",
[510] = "Not Extended",
[511] = "Network Authentication Required",
}
local function runTask(task, callback)
local conn = playdate.network.http.new(task.host, task.port, task.ssl, task.reason)
if not conn then
callback(nil, "Permission denied")
return
end
conn:setConnectTimeout(5)
local status, headers
conn:setHeadersReadCallback(function()
status = conn:getResponseStatus()
headers = conn:getResponseHeaders()
end)
local buffer = {}
conn:setRequestCallback(function()
local bytes = conn:getBytesAvailable()
if bytes > 0 then
buffer[#buffer + 1] = conn:read(bytes)
end
end)
conn:setRequestCompleteCallback(function()
local err = conn:getError()
if err then
callback(nil, err)
else
callback({
ok = status >= 200 and status < 300,
status = status,
statusText = statusText[status] or "Unknown Status",
headers = headers,
body = table.concat(buffer, ""),
})
end
end)
local ok, err = conn:query(task.method, task.path, task.headers, task.body)
if not ok then
callback(nil, err)
end
end
HTTP = { isLoading = false }
local queue = {}
function HTTP.update()
if #queue == 0 or HTTP.isLoading then return end
local task = table.remove(queue, 1)
HTTP.isLoading = true
runTask(task, function(res, err)
HTTP.isLoading = false
task.onComplete(res, err)
end)
end
function HTTP.fetch(url, options, onComplete, reason)
HTTP.scheduled = true
if type(options) == 'function' then
options, onComplete, reason = {}, options, onComplete
end
local scheme, host, port, path = parseURL(url)
queue[#queue + 1] = {
host = host,
port = port or (scheme == "https" and 443 or 80),
ssl = scheme == "https",
path = path ~= "" and path or "/",
method = options.method or "GET",
headers = options.headers,
body = options.body,
reason = reason,
onComplete = onComplete or noop
}
end