class

ContributorMural::HTTPAvatarSource

Inherits ContributorMural::AvatarSource < Reference < Object

Fetches avatars over HTTP, or straight from the workspace when avatar_url is a plain relative path (org logos, folks without a GitHub account). HTTP::Client does not follow redirects and github.com/<login>.png answers with a 301, so redirects are handled here.

Constants

ALLOWED_CONTENT_TYPES = CONTENT_TYPES.values.to_set
CONTENT_TYPES = {".png" => "image/png", ".jpg" => "image/jpeg", ".jpeg" => "image/jpeg", ".gif" => "image/gif", ".webp" => "image/webp", ".svg" => "image/svg+xml"}
MAX_ATTEMPTS = 3
MAX_BYTES = (8 * 1024) * 1024

Avatars are small; anything larger is a misconfigured URL, and the bytes end up base64-encoded inside a committed file.

MAX_REDIRECTS = 5
MAX_RETRY_AFTER = 30.seconds

A server may name any delay it likes; we still have a job to finish.

RETRYABLE_STATUSES = {408, 425, 429, 500, 502, 503, 504}

Statuses worth another attempt. 429 and 408 are the reason this is a list rather than >= 500: a throttled or timed-out request used to count as a permanent failure, and the mural quietly lost that person's face — the one outcome nobody notices until the picture is already committed.

USER_AGENT = "contributor-mural/#{VERSION}"

Sent on every request: an unidentified client is the first thing a CDN throttles, and being throttled here costs us faces.

Constructors

new(workspace : String = Dir.current, backoff_base : Time::Span = 1.second, allow_local_redirects : Bool = false, pool : HTTPPool | Nil = nil, allow_local_targets : Bool | Nil = nil)

Both flags exist for specs, which fetch from and redirect between 127.0.0.1 ports; production keeps redirects on public https only and refuses to fetch from the runner's own network at all. allow_local_redirects implies allow_local_targets, since a spec that follows a local redirect had to reach the local server first.

Source

Class methods

local_path?(avatar_url : String) : Bool
Source

Instance methods

close

Hands back the connections this source opened.

Keep-alive means a source holds a socket open to every host it spoke to until something says otherwise, and a source that built its own pool had no way to say it. The connection then sat there until the garbage collector happened to reach it — invisible to the action, which owns one pool for the whole run and closes it, and unbounded for anything that builds a source per repository in a loop.

A no-op when the pool was handed in: closing someone else's pool hangs up on whoever else is still using it.

Source
fetch(user : ResolvedUser, size : Int32) : Tuple(Bytes, String)

Returns image bytes and content type for the avatar at size px.

Source
url_for(user : ResolvedUser, size : Int32) : String

Stable identity of the request, used as the cache/dedupe key.

Source