Files
Aurora.3/code/controllers/subsystems/http.dm
Werner 7404418903 Adds SShttp (#18105)
* Adds SShttp

Adds a HTTP Subsystem with a integrated Retry-Queue for Status-Code 429 and 503

Changes the discord subsystems send message function to the new http subsystem

The previous http_ request functions remain but have been renamed to old to signify that they should not be used for new implementations

* Fixes lint error

* DMDoc Pass

* marks build_options as private

* SHOULD_NOT_SLEEP where its relevant

* update_channels is not a private proc, as it can be called by the API

* Remove comments

* Make fluffy happy

* Makes fluffy even happier

---------

Co-authored-by: Werner <Arrow768@users.noreply.github.com>
2024-01-06 21:19:45 +00:00

106 lines
4.3 KiB
Plaintext

SUBSYSTEM_DEF(http)
name = "HTTP"
flags = SS_TICKER | SS_BACKGROUND | SS_NO_INIT // Measure in ticks, but also only run if we have the spare CPU.
wait = 1
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY // All the time
/// List of all async HTTP requests in the processing chain
var/list/datum/http_request/active_async_requests = list()
/// List of async HTTP Requests to retry
var/list/datum/http_request/retry_async_requests = list()
/// Variable to define if logging is enabled or not. Disabled by default since we know the requests the server is making. Enable with VV if you need to debug requests
var/logging_enabled = FALSE
/// Total requests the SS has processed in a round
var/total_requests
/// Maximum times a http request is retried
var/max_retry_count = 3
/datum/controller/subsystem/http/stat_entry(msg)
return "P: [length(active_async_requests)] | R: [length(retry_async_requests)] | T: [total_requests]"
/datum/controller/subsystem/http/fire(resumed)
for(var/r in retry_async_requests)
var/datum/http_request/req = r
//If the time to retry it has come move it to the active queue again
if(world.time >= req.retry_at)
active_async_requests += req
retry_async_requests -= req
for(var/r in active_async_requests)
var/datum/http_request/req = r
// Check if we are complete
if(req.is_complete())
// If so, take it out the processing list
active_async_requests -= req
var/datum/http_response/res = req.into_response()
var/list/log_data = list()
//Log the reponse we have got
log_data += "BEGIN ASYNC RESPONSE (ID: [req.id] RETRY: [req.retry_count])"
if(res.errored)
log_data += "\t ----- RESPONSE ERRROR -----"
log_data += "\t [res.error]"
else
log_data += "\tResponse status code: [res.status_code]"
log_data += "\tResponse body: [res.body]"
log_data += "\tResponse headers: [json_encode(res.headers)]"
log_data += "END ASYNC RESPONSE (ID: [req.id])"
log_subsystem_http(log_data.Join("\n"))
// Automatically Retry if we have a 429 or 503 Status with a retry-after header
if((res.status_code == 429 || res.status_code == 503) && ("retry-after" in res.headers) )
var/retry_after = text2num(res.headers["retry-after"])
if(retry_after == 0 && req.cb)
if(req.cb)
req.cb.InvokeAsync(res)
continue // If we get a Retry-After 0 back, we dont retry it
if(req.retry_count >= max_retry_count)
if(req.cb)
req.cb.InvokeAsync(res)
continue // If we exceed our maximum retry count, we dont retry it
req.retry_at = world.time + (retry_after*10)
req.retry_count++
retry_async_requests += req
continue //Dont invoke the callback yet as we dont have a final error
// If the request has a callback, invoke it. Async of course to avoid choking the SS
if(req.cb)
req.cb.InvokeAsync(res)
/**
* Async request creator
*
* Generates an async request, and adds it to the subsystem's processing list.
* These should be used as they do not lock the entire DD process up as they execute inside their own thread pool inside RUSTG.
* If a 429 or 503 is encountered and a retry-after header is returned the requested is retrued up to [/datum/controller/subsystem/http/var/max_retry_count] times.
* During the retries the retry-after header is respected.
* A callback can be supplied to this proc. If a callback is supplied it is only invoked when after the final request.
*
* Arguments:
* * method - HTTP method to use -> use the RUSTG_HTTP_METHOD_ MACROS
* * url - URL where the request should go to
* * body - The body of the request
* * headers - list of headers to pass along
* * proc_callback - A Callback to be invoced when a final status is reached
*/
/datum/controller/subsystem/http/proc/create_async_request(method, url, body = "", list/headers, datum/callback/proc_callback)
var/datum/http_request/req = new()
req.prepare(method, url, body, headers)
if(proc_callback)
req.cb = proc_callback
// Begin it and add it to the SS active list
req.begin_async()
active_async_requests += req
total_requests++
// Log it
var/list/log_data = list()
log_data += "BEGIN ASYNC REQUEST (ID: [req.id])"
log_data += "\t[uppertext(req.method)] [req.url]"
log_data += "\tRequest body: [req.body]"
log_data += "\tRequest headers: [req.headers]"
log_data += "END ASYNC REQUEST (ID: [req.id])"
log_subsystem_http(log_data.Join("\n"))