class MediaClient
Async HTTP client for binary Matrix media operations.
Handles raw byte uploads and downloads against the Matrix content repository. Unlike the JSON-only Client, this never wraps request bodies in JSON.generate and can return raw binary response bodies.
Not intended to be used directly — the Chain dispatches here automatically when the request path matches a BINARY_ROUTES entry.
media_client = Async::Matrix::MediaClient.new(config) media_client.upload("POST", path, bytes, "image/png") bytes = media_client.download(path)
Definitions
UPLOAD_RESPONSE_SIZE_LIMIT = 1 * 1024 * 1024
Upload JSON responses are small; error bodies are even smaller.
def upload(method, path, body, content_type = "application/octet-stream")
Upload raw bytes to the content repository.
Implementation
def upload(method, path, body, content_type = "application/octet-stream")
url = "#{@base}#{path}"
headers = @auth_headers + [["content-type", content_type]]
Console.debug(self) { "UPLOAD #{method} #{path} (#{content_type}, #{body.bytesize} bytes)" }
response = internet.call(method, url, headers, body)
status = response.status
unless (200..299).cover?(status)
payload = read_limited(response, ERROR_RESPONSE_SIZE_LIMIT)
parsed = ApplicationService::ErrorResponse.new(
begin; JSON.parse(payload); rescue; {} end
)
Console.error(self) { "Matrix media upload #{status}: #{parsed.errcode} — #{parsed.error}" }
raise HomeserverError.new(
parsed.errcode || "UNKNOWN",
parsed.error || payload.to_s[0..200],
status: status
)
end
payload = read_limited(response, UPLOAD_RESPONSE_SIZE_LIMIT)
payload && !payload.empty? ? JSON.parse(payload) : {}
end
def download(path)
Download from the content repository.
Returns the raw HTTP response object so the caller has access to both the body and response metadata:
response = media_client.download(path)
response.read # raw bytes (String, Encoding::BINARY)
response.headers["content-type"] # "image/png"
response.headers["content-disposition"] # "inline; filename="photo.jpg""
response.body.each |chunk| ... # streaming
Implementation
def download(path)
url = "#{@base}#{path}"
Console.debug(self) { "DOWNLOAD #{path}" }
response = internet.call("GET", url, @auth_headers, nil)
status = response.status
unless (200..299).cover?(status)
payload = response.read
parsed = ApplicationService::ErrorResponse.new(
begin; JSON.parse(payload); rescue; {} end
)
Console.error(self) { "Matrix media download #{status}: #{parsed.errcode} — #{parsed.error}" }
raise HomeserverError.new(
parsed.errcode || "UNKNOWN",
parsed.error || payload.to_s[0..200],
status: status
)
end
response
end
def read_limited(response, limit)
Read response body with a size limit. Raises ResponseTooLargeError if the body exceeds the limit.
Implementation
def read_limited(response, limit)
body = response.body
return nil unless body
if body.respond_to?(:length) && body.length && body.length > limit
body.close
raise ResponseTooLargeError.new(
"M_TOO_LARGE",
"Response Content-Length #{body.length} bytes exceeds limit of #{limit} bytes"
)
end
buffer = String.new(encoding: Encoding::BINARY)
body.each do |chunk|
buffer << chunk
if buffer.bytesize > limit
body.close
raise ResponseTooLargeError.new(
"M_TOO_LARGE",
"Response body exceeds limit of #{limit} bytes"
)
end
end
buffer.empty? ? nil : buffer
end