Created
April 27, 2018 04:32
-
-
Save christineponyl/2add7ad5e9f6daead0ab57234b2ce601 to your computer and use it in GitHub Desktop.
Shared via Pony Playground (https://playground.ponylang.org/?gist=2add7ad5e9f6daead0ab57234b2ce601)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use "net" | |
use "time" | |
use "json" | |
use "net/http" | |
use "promises" | |
use "buffered" | |
use "collections" | |
use @time[USize](ptr: USize) | |
type _RestResponse is Promise[JsonType val] | |
type _RestData is Option[(JsonObject | JsonArray)] val | |
class _RestClient | |
let _limiter: _RateLimiter | |
new create(auth: TCPConnectionAuth, token: String, logger: DiscordLogger) => | |
_limiter = _RateLimiter(auth, token, logger) | |
fun get(path: String, bucket: String): _RestResponse ? => | |
_request("GET", path, bucket, None)? | |
fun put(path: String, bucket: String, data: _RestData): _RestResponse ? => | |
_request("PUT", path, bucket, data)? | |
fun post(path: String, bucket: String, data: _RestData): _RestResponse ? => | |
_request("POST", path, bucket, data)? | |
fun delete(path: String, bucket: String, data: _RestData): _RestResponse ? => | |
_request("DELETE", path, bucket, data)? | |
fun _request(method: String, path: String, bucket: String, data: _RestData): _RestResponse ? => | |
let response = _RestResponse | |
let url = URL.valid(_Discord.base_url() + path)? | |
let request = _RestRequest(consume url, method, bucket, consume data, response) | |
_limiter.submit(consume request) | |
response | |
class _RestBucket | |
var is_rate_limited: Bool = false | |
embed queue: List[_RestRequest] = queue.create() | |
class _RestRateLimit is TimerNotify | |
let _bucket_id: String | |
let _limiter: _RateLimiter | |
new iso create(limiter: _RateLimiter, id: String) => | |
_bucket_id = id | |
_limiter = limiter | |
fun ref apply(timer: Timer, count: U64): Bool => | |
_limiter.release_ratelimit(_bucket_id) | |
false | |
actor _RateLimiter | |
let _token: String | |
let _logger: DiscordLogger | |
let _rate_limit: Timers = Timers | |
embed _client: HTTPClient | |
embed _global_bucket: _RestBucket = _RestBucket | |
embed _buckets: Map[String, _RestBucket] = _buckets.create() | |
new create(auth: TCPConnectionAuth, token: String, logger: DiscordLogger) => | |
_token = token | |
_logger = logger | |
_client = HTTPClient(consume auth) | |
fun _get_bucket(key: (Bool | String)): _RestBucket ? => | |
match key | |
| true => _global_bucket | |
| let bucket_id: String => | |
if bucket_id.size() isnt 0 | |
then _buckets(bucket_id)? | |
else _global_bucket end | |
else error end | |
fun ref _execute(request: _RestRequest val) => | |
try | |
let dispatcher = recover val _RestDispatcher.create(this, request) end | |
_client(request.to_payload(_token), consume dispatcher)? | |
else | |
request.respond.reject() | |
end | |
be release_ratelimit(bucket_id: String) => | |
if bucket_id.size() is 0 then _global_bucket | |
else _buckets(bucket_id)? | |
be submit(request: _RestRequest val) => | |
if _global_bucket.is_rate_limited then | |
_global_bucket.queue.push(request) | |
elseif request.bucket_id.size() is 0 then | |
_execute(request) | |
else | |
try _buckets(request.bucket_id)?.queue.push(request) else | |
_buckets(request.bucket_id) = _RestBucket | |
_execute(request) | |
end | |
end | |
be complete(request: _RestRequest val, result: _RestResult val, retry: Bool) => | |
if retry then submit(request); return end | |
if result.is_rate_limited then | |
try | |
let bucket = | |
if result.is_global then _global_bucket | |
else _buckets(request.bucket_id)? end | |
let bucket_id = | |
if bucket.is_global then "" | |
else request.bucket_id end | |
bucket.is_rate_limited = true | |
_rate_limit(_RestRateLimit(this, bucket_id)) | |
end | |
end | |
class _RestDispatcher is HandlerFactory | |
let _limiter: _RateLimiter | |
let _request: _RestRequest val | |
new iso create(limiter: _RateLimiter, request: _RestRequest val) => | |
_limiter = limiter | |
_request = request | |
fun apply(session: HTTPSession): HTTPHandler ref^ => | |
_RestHandler.create(_limiter, _request, session) | |
class _RestHandler is HTTPHandler | |
var _status: U16 = 0 | |
let _builder: Reader = Reader.create() | |
var _rate_limit: (Bool, Bool, U64) = (false, false, 0) | |
let _session: HTTPSession | |
let _request: _RestRequest | |
let _limiter: _RateLimiter | |
new ref create(limiter: _RateLimiter, request: _RestRequest, session: HTTPSession) => | |
_limiter = limiter | |
_request = request | |
_session = session | |
fun ref chunk(data: ByteSeq val) => | |
_builder.append(data) | |
fun ref finished() => _complete(false) | |
fun ref cancelled() => _complete(true) | |
fun ref _complete(retry: Bool) => | |
_session.dispose() | |
_limiter.complete(_request, _result(), retry) | |
fun ref _result(): _RestResult iso ^ => | |
(let global, let limited, let expires) = _rate_limit | |
let content = try String.from_array(_builder.block(_builder.size())?) else "." end | |
_RestResult(_status, expires, global, limited, consume content) | |
fun ref apply(resp: Payload val) => | |
_status = resp.status | |
let headers = resp.headers() | |
var expires = try headers("X-RateLimit-Reset")?.u64()? else 0 end | |
let is_global = try headers("X-RateLimit-Global")?; true else false end | |
let is_limited = try headers("X-RateLimit-Remaining")? == "0" else false end | |
_rate_limit = (is_global, is_limited, expires) | |
try | |
for buffer in resp.body()?.values() do | |
_builder.append(buffer) | |
end | |
end | |
class _RestResult | |
let status: U16 | |
let expires: U64 | |
let is_global: Bool | |
let is_rate_limited: Bool | |
let json: JsonDoc = JsonDoc | |
new iso create(stat: U16, exp: U64, global: Bool, limited: Bool, data: String) => | |
status = stat | |
expires = exp | |
is_global = global | |
is_rate_limited = limited | |
try json.parse(consume data)? end | |
class val _RestRequest | |
let _url: URL | |
let _method: String | |
let _data: _RestData | |
let bucket_id: String | |
let respond: _RestResponse | |
new val create(url: URL, | |
method: String, id: String, | |
data: _RestData, response: _RestResponse | |
) => | |
_url = url | |
_data = data | |
_method = method | |
bucket_id = id | |
respond = response | |
fun to_payload(token: String): Payload ^ => | |
let payload = Payload.request(_method, _url) | |
payload("Authorization") = "Bot " + token | |
payload("User-Agent") = _Discord.user_agent() | |
match _data | |
| let json: this->(JsonObject | JsonArray) => | |
payload("Content-Type") = "application/json;charset=utf-8" | |
payload.add_chunk(json.string()) | |
end | |
consume payload |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment