Table of Contents

Class AppHttp

Namespace
McServerLauncher.Services
Assembly
McServerLauncher.dll

The one place an HttpClient of this app is made.

public static class AppHttp
Inheritance
AppHttp
Inherited Members

Remarks

Seventeen services each built their own, and they had drifted: six sent JuanP-G/MC-ServerLauncher as their User-Agent, UpdateService sent MC-ServerLauncher, and the rest sent none. Some CDNs (Forge's maven among them) refuse a request without one, and Modrinth asks for one that says who is calling.

Keeping the creation here is also what gives the rules about what may come back a single home: a ceiling on how much a response may weigh (ReadCappedAsync(HttpContent, long, CancellationToken)) and the hosts a download is allowed to come from (RequireAllowedUrl(string?, params string[])) belong to every client or to none.

Each service still keeps its own static client (one per service, with its own timeout), so nothing is shared that did not need to be: a slow download cannot hold up the version check.

Fields

MaxApiBytes

The most a JSON answer from an API is read for: far above any real one.

public const long MaxApiBytes = 8388608

Field Value

long

MaxSmallTextBytes

The most a checksum list or a signature is read for; the real ones are a few hundred bytes.

public const long MaxSmallTextBytes = 65536

Field Value

long

UserAgent

What every request says it is, so the services we call can tell who is asking.

public const string UserAgent = "JuanP-G/MC-ServerLauncher"

Field Value

string

Methods

Create(TimeSpan)

A client with this app's User-Agent and the given overall timeout.

public static HttpClient Create(TimeSpan timeout)

Parameters

timeout TimeSpan

How long one request may take in all. Use InfiniteTimeSpan when the caller sets its own deadline.

Returns

HttpClient

GetStringCappedAsync(HttpClient, string, long, CancellationToken)

A GET whose body is read as text, up to limit bytes.

public static Task<string> GetStringCappedAsync(HttpClient http, string url, long limit, CancellationToken ct)

Parameters

http HttpClient
url string
limit long
ct CancellationToken

Returns

Task<string>

Exceptions

HttpRequestException

The server answered with an error status.

InvalidDataException

The body is larger than the limit.

IsAllowedUrl(string?, params string[])

Whether url is an https address on one of hosts (or a subdomain of one), with no credentials in it.

public static bool IsAllowedUrl(string? url, params string[] hosts)

Parameters

url string
hosts string[]

Returns

bool

ReadCappedAsync(HttpContent, long, CancellationToken)

Reads a response body, refusing one that grows past limit.

public static Task<byte[]> ReadCappedAsync(HttpContent content, long limit, CancellationToken ct)

Parameters

content HttpContent
limit long
ct CancellationToken

Returns

Task<byte[]>

Remarks

GetStringAsync reads whatever the server sends, so a server that answers with gigabytes fills memory before any check on the result can run. The announced length is refused up front, and the bytes actually read are counted as well, since the announcement can be absent or false.

Exceptions

InvalidDataException

The body is larger than the limit.

ReadCappedStringAsync(HttpContent, long, CancellationToken)

A response body as text (UTF-8, or what its byte-order mark says), up to limit bytes.

public static Task<string> ReadCappedStringAsync(HttpContent content, long limit, CancellationToken ct)

Parameters

content HttpContent
limit long
ct CancellationToken

Returns

Task<string>

RequireAllowedUrl(string?, params string[])

Throws unless url passes IsAllowedUrl(string?, params string[]). For a download link that came out of an API response: the response says where the file is, and nothing in it should be able to say somewhere other than the publisher's own host.

public static void RequireAllowedUrl(string? url, params string[] hosts)

Parameters

url string
hosts string[]

Exceptions

UserFacingException

The address is not https or not on an allowed host.