211 lines
4.8 KiB
JavaScript
211 lines
4.8 KiB
JavaScript
|
const http = {};
|
||
|
|
||
|
http.HEADERS = {};
|
||
|
|
||
|
http.requests_in_flight = 0;
|
||
|
|
||
|
http.request_queue = {};
|
||
|
http.request_queue.array = [];
|
||
|
|
||
|
http.request_queue.push =
|
||
|
function request_queue_push(func)
|
||
|
{
|
||
|
http.request_queue.array.push(func)
|
||
|
if (http.requests_in_flight == 0)
|
||
|
{
|
||
|
http.request_queue_next();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
http.request_queue.unshift =
|
||
|
function request_queue_unshift(func)
|
||
|
{
|
||
|
http.request_queue.array.unshift(func)
|
||
|
if (http.requests_in_flight == 0)
|
||
|
{
|
||
|
http.request_queue_next();
|
||
|
}
|
||
|
}
|
||
|
http.request_queue.pushleft = http.request_queue.unshift;
|
||
|
|
||
|
http.request_queue_next =
|
||
|
function request_queue_next()
|
||
|
{
|
||
|
if (http.requests_in_flight > 0)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if (http.request_queue.array.length === 0)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
const func = http.request_queue.array.shift();
|
||
|
func();
|
||
|
}
|
||
|
|
||
|
http.formdata =
|
||
|
function formdata(data)
|
||
|
{
|
||
|
const fd = new FormData();
|
||
|
for (let [key, value] of Object.entries(data))
|
||
|
{
|
||
|
if (value === undefined)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
if (value === null)
|
||
|
{
|
||
|
value = '';
|
||
|
}
|
||
|
fd.append(key, value);
|
||
|
}
|
||
|
return fd;
|
||
|
}
|
||
|
|
||
|
http._request =
|
||
|
function _request(kwargs)
|
||
|
{
|
||
|
/*
|
||
|
Perform an HTTP request and call the `callback` with the response.
|
||
|
|
||
|
The response will have the following structure:
|
||
|
{
|
||
|
"meta": {
|
||
|
"request": the XMLHttpRequest object,
|
||
|
"completed": true / false,
|
||
|
"status": If the connection failed or request otherwise could not
|
||
|
complete, `status` will be 0. If the request completed,
|
||
|
`status` will be the HTTP response code.
|
||
|
"json_ok": If the server responded with parseable json, `json_ok`
|
||
|
will be true, and that data will be in `response.data`. If the
|
||
|
server response was not parseable json, `json_ok` will be false
|
||
|
and `response.data` will be undefined.
|
||
|
"kwargs": The kwargs exactly as given to this call.
|
||
|
}
|
||
|
"data": {JSON parsed from server response if json_ok},
|
||
|
"retry": function you can call to retry the request.
|
||
|
}
|
||
|
|
||
|
So, from most lenient to most strict, error catching might look like:
|
||
|
if response.meta.completed
|
||
|
if response.meta.json_ok
|
||
|
if response.meta.status === 200
|
||
|
if response.meta.status === 200 and response.meta.json_ok
|
||
|
*/
|
||
|
const request = new XMLHttpRequest();
|
||
|
const response = {
|
||
|
"meta": {
|
||
|
"request": request,
|
||
|
"completed": false,
|
||
|
"status": 0,
|
||
|
"json_ok": false,
|
||
|
"kwargs": kwargs,
|
||
|
},
|
||
|
"retry": function(){http._request(kwargs)},
|
||
|
};
|
||
|
|
||
|
request.onreadystatechange = function()
|
||
|
{
|
||
|
/*
|
||
|
readystate values:
|
||
|
0 UNSENT / ABORTED
|
||
|
1 OPENED
|
||
|
2 HEADERS_RECEIVED
|
||
|
3 LOADING
|
||
|
4 DONE
|
||
|
*/
|
||
|
if (request.readyState != 4)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
http.requests_in_flight -= 1;
|
||
|
setTimeout(http.request_queue_next, 0);
|
||
|
|
||
|
if (kwargs["callback"] == null)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
response.meta.status = request.status;
|
||
|
|
||
|
if (request.status != 0)
|
||
|
{
|
||
|
response.meta.completed = true;
|
||
|
try
|
||
|
{
|
||
|
response.data = JSON.parse(request.responseText);
|
||
|
response.meta.json_ok = true;
|
||
|
}
|
||
|
catch (exc)
|
||
|
{
|
||
|
response.meta.json_ok = false;
|
||
|
}
|
||
|
}
|
||
|
kwargs["callback"](response);
|
||
|
};
|
||
|
|
||
|
// Headers
|
||
|
|
||
|
const asynchronous = "asynchronous" in kwargs ? kwargs["asynchronous"] : true;
|
||
|
request.open(kwargs["method"], kwargs["url"], asynchronous);
|
||
|
|
||
|
for (const [header, value] of Object.entries(http.HEADERS))
|
||
|
{
|
||
|
request.setRequestHeader(header, value);
|
||
|
}
|
||
|
|
||
|
const more_headers = kwargs["headers"] || {};
|
||
|
for (const [header, value] of Object.entries(more_headers))
|
||
|
{
|
||
|
request.setRequestHeader(header, value);
|
||
|
}
|
||
|
|
||
|
if (kwargs["with_credentials"])
|
||
|
{
|
||
|
request.withCredentials = true;
|
||
|
}
|
||
|
|
||
|
// Send
|
||
|
|
||
|
let data = kwargs["data"];
|
||
|
if (data === undefined || data === null)
|
||
|
{
|
||
|
request.send();
|
||
|
}
|
||
|
else if (data instanceof FormData)
|
||
|
{
|
||
|
request.send(data);
|
||
|
}
|
||
|
else if (typeof(data) === "string" || data instanceof String)
|
||
|
{
|
||
|
request.send(data);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
request.send(http.formdata(data));
|
||
|
}
|
||
|
http.requests_in_flight += 1;
|
||
|
|
||
|
return request;
|
||
|
}
|
||
|
|
||
|
http.get =
|
||
|
function get(kwargs)
|
||
|
{
|
||
|
kwargs["method"] = "GET";
|
||
|
return http._request(kwargs);
|
||
|
}
|
||
|
|
||
|
http.post =
|
||
|
function post(kwargs)
|
||
|
{
|
||
|
/*
|
||
|
`data`:
|
||
|
a FormData object which you have already filled with values, or a
|
||
|
dictionary from which a FormData will be made, using http.formdata.
|
||
|
*/
|
||
|
kwargs["method"] = "POST";
|
||
|
return http._request(kwargs);
|
||
|
}
|