Internal Architecture
HTTP.Layers.Layer — TypeRequest Execution Stack
The Request Execution Stack is separated into composable layers.
Each layer is defined by a nested type Layer{Next} where the Next parameter defines the next layer in the stack. The request method for each layer takes a Layer{Next} type as its first argument and dispatches the request to the next layer using request(Next, ...).
The example below defines three layers and three stacks each with a different combination of layers.
abstract type Layer end
abstract type Layer1{Next <: Layer} <: Layer end
abstract type Layer2{Next <: Layer} <: Layer end
abstract type Layer3 <: Layer end
request(::Type{Layer1{Next}}, data) where Next = "L1", request(Next, data)
request(::Type{Layer2{Next}}, data) where Next = "L2", request(Next, data)
request(::Type{Layer3}, data) = "L3", data
const stack1 = Layer1{Layer2{Layer3}}
const stack2 = Layer2{Layer1{Layer3}}
const stack3 = Layer1{Layer3}julia> request(stack1, "foo")
("L1", ("L2", ("L3", "foo")))
julia> request(stack2, "bar")
("L2", ("L1", ("L3", "bar")))
julia> request(stack3, "boo")
("L1", ("L3", "boo"))This stack definition pattern gives the user flexibility in how layers are combined but still allows Julia to do whole-stack compile time optimisations.
e.g. the request(stack1, "foo") call above is optimised down to a single function:
julia> code_typed(request, (Type{stack1}, String))[1].first
CodeInfo(:(begin
return (Core.tuple)("L1", (Core.tuple)("L2", (Core.tuple)("L3", data)))
end))HTTP.stack — FunctionThe stack() function returns the default HTTP Layer-stack type. This type is passed as the first parameter to the HTTP.request function.
stack() accepts optional keyword arguments to enable/disable specific layers in the stack: request(method, args...; kw...) request(stack(; kw...), args...; kw...)
The minimal request execution stack is:
stack = MessageLayer{ConnectionPoolLayer{StreamLayer}}The figure below illustrates the full request execution stack and its relationship with HTTP.Response, HTTP.Parsers, HTTP.Stream and the HTTP.ConnectionPool.
┌────────────────────────────────────────────────────────────────────────────┐
│ ┌───────────────────┐ │
│ HTTP.jl Request Execution Stack │ HTTP.ParsingError ├ ─ ─ ─ ─ ┐ │
│ └───────────────────┘ │
│ ┌───────────────────┐ │ │
│ │ HTTP.IOError ├ ─ ─ ─ │
│ └───────────────────┘ │ │ │
│ ┌───────────────────┐ │
│ │ HTTP.StatusError │─ ─ │ │ │
│ └───────────────────┘ │ │
│ ┌───────────────────┐ │ │ │
│ request(method, url, headers, body) -> │ HTTP.Response │ │ │
│ ────────────────────────── └─────────▲─────────┘ │ │ │
│ ║ ║ │ │
│ ┌────────────────────────────────────────────────────────────┐ │ │ │
│ │ request(TopLayer, method, ::URI, ::Headers, body) │ │ │
│ ├────────────────────────────────────────────────────────────┤ │ │ │
│ │ request(BasicAuthLayer, method, ::URI, ::Headers, body) │ │ │
│ ├────────────────────────────────────────────────────────────┤ │ │ │
│ │ request(BasicAuthLayer, method, ::URI, ::Headers, body) │ │ │
│ ├────────────────────────────────────────────────────────────┤ │ │ │
│ │ request(CookieLayer, method, ::URI, ::Headers, body) │ │ │
│ ├────────────────────────────────────────────────────────────┤ │ │ │
│ │ request(CanonicalizeLayer, method, ::URI, ::Headers, body) │ │ │
│ ├────────────────────────────────────────────────────────────┤ │ │ │
│ │ request(MessageLayer, method, ::URI, ::Headers, body) │ │ │
│ ├────────────────────────────────────────────────────────────┤ │ │ │
│ │ request(AWS4AuthLayer, ::URI, ::Request, body) │ │ │
│ ├────────────────────────────────────────────────────────────┤ │ │ │
│ │ request(RetryLayer, ::URI, ::Request, body) │ │ │
│ ├────────────────────────────────────────────────────────────┤ │ │ │
│ │ request(ExceptionLayer, ::URI, ::Request, body) ├ ─ ┘ │
│ ├────────────────────────────────────────────────────────────┤ │ │ │
┌┼───┤ request(ConnectionPoolLayer, ::URI, ::Request, body) ├ ─ ─ ─ │
││ ├────────────────────────────────────────────────────────────┤ │ │
││ │ request(DebugLayer, ::IO, ::Request, body) │ │
││ ├────────────────────────────────────────────────────────────┤ │ │
││ │ request(TimeoutLayer, ::IO, ::Request, body) │ │
││ ├────────────────────────────────────────────────────────────┤ │ │
││ │ request(StreamLayer, ::IO, ::Request, body) │ │
││ └──────────────┬───────────────────┬─────────────────────────┘ │ │
│└──────────────────┼────────║──────────┼───────────────║─────────────────────┘
│ │ ║ │ ║ │
│┌──────────────────▼───────────────┐ │ ┌──────────────────────────────────┐
││ HTTP.Request │ │ │ HTTP.Response │ │
││ │ │ │ │
││ method::String ◀───┼──▶ status::Int │ │
││ target::String │ │ │ headers::Vector{Pair} │
││ headers::Vector{Pair} │ │ │ body::Vector{UInt8} │ │
││ body::Vector{UInt8} │ │ │ │
│└──────────────────▲───────────────┘ │ └───────────────▲────────────────┼─┘
│┌──────────────────┴────────║──────────▼───────────────║──┴──────────────────┐
││ HTTP.Stream <:IO ║ ╔══════╗ ║ │ │
││ ┌───────────────────────────┐ ║ ┌──▼─────────────────────────┐ │
││ │ startwrite(::Stream) │ ║ │ startread(::Stream) │ │ │
││ │ write(::Stream, body) │ ║ │ read(::Stream) -> body │ │
││ │ ... │ ║ │ ... │ │ │
││ │ closewrite(::Stream) │ ║ │ closeread(::Stream) │ │
││ └───────────────────────────┘ ║ └────────────────────────────┘ │ │
│└───────────────────────────║────────┬──║──────║───────║──┬──────────────────┘
│┌──────────────────────────────────┐ │ ║ ┌────▼───────║──▼────────────────┴─┐
││ HTTP.Messages │ │ ║ │ HTTP.Parsers │
││ │ │ ║ │ │
││ writestartline(::IO, ::Request) │ │ ║ │ parse_status_line(bytes, ::Req') │
││ writeheaders(::IO, ::Request) │ │ ║ │ parse_header_field(bytes, ::Req')│
│└──────────────────────────────────┘ │ ║ └──────────────────────────────────┘
│ ║ │ ║
│┌───────────────────────────║────────┼──║────────────────────────────────────┐
└▶ HTTP.ConnectionPool ║ │ ║ │
│ ┌──────────────▼────────┐ ┌───────────────────────┐ │
│ getconnection() -> │ HTTP.Transaction <:IO │ │ HTTP.Transaction <:IO │ │
│ └───────────────────────┘ └───────────────────────┘ │
│ ║ ╲│╱ ║ ╲│╱ │
│ ║ │ ║ │ │
│ ┌───────────▼───────────┐ ┌───────────▼───────────┐ │
│ pool: [│ HTTP.Connection │,│ HTTP.Connection │...]│
│ └───────────┬───────────┘ └───────────┬───────────┘ │
│ ║ │ ║ │ │
│ ┌───────────▼───────────┐ ┌───────────▼───────────┐ │
│ │ Base.TCPSocket <:IO │ │MbedTLS.SSLContext <:IO│ │
│ └───────────────────────┘ └───────────┬───────────┘ │
│ ║ ║ │ │
│ ║ ║ ┌───────────▼───────────┐ │
│ ║ ║ │ Base.TCPSocket <:IO │ │
│ ║ ║ └───────────────────────┘ │
└───────────────────────────║───────────║────────────────────────────────────┘
║ ║
┌───────────────────────────║───────────║──────────────┐ ┏━━━━━━━━━━━━━━━━━━┓
│ HTTP Server ▼ │ ┃ data flow: ════▶ ┃
│ Request Response │ ┃ reference: ────▶ ┃
└──────────────────────────────────────────────────────┘ ┗━━━━━━━━━━━━━━━━━━┛See docs/src/layers.monopic.
Request Execution Layers
HTTP.RedirectRequest.RedirectLayer — Typerequest(RedirectLayer, method, ::URI, headers, body) -> HTTP.ResponseRedirects the request in the case of 3xx response status.
HTTP.BasicAuthRequest.BasicAuthLayer — Typerequest(BasicAuthLayer, method, ::URI, headers, body) -> HTTP.ResponseAdd Authorization: Basic header using credentials from url userinfo.
HTTP.CookieRequest.CookieLayer — Typerequest(CookieLayer, method, ::URI, headers, body) -> HTTP.ResponseAdd locally stored Cookies to the request headers. Store new Cookies found in the response headers.
HTTP.CanonicalizeRequest.CanonicalizeLayer — Typerequest(CanonicalizeLayer, method, ::URI, headers, body) -> HTTP.ResponseRewrite request and response headers in Canonical-Camel-Dash-Format.
HTTP.MessageRequest.MessageLayer — Typerequest(MessageLayer, method, ::URI, headers, body) -> HTTP.ResponseConstruct a Request object and set mandatory headers.
HTTP.AWS4AuthRequest.AWS4AuthLayer — Typerequest(AWS4AuthLayer, ::URI, ::Request, body) -> HTTP.ResponseAdd a AWS Signature Version 4 Authorization header to a Request.
Credentials are read from environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN.
HTTP.RetryRequest.RetryLayer — Typerequest(RetryLayer, ::URI, ::Request, body) -> HTTP.ResponseRetry the request if it throws a recoverable exception.
Base.retry and Base.ExponentialBackOff implement a randomised exponentially increasing delay is introduced between attempts to avoid exacerbating network congestion.
Methods of isrecoverable(e) define which exception types lead to a retry. e.g. HTTP.IOError, Sockets.DNSError, Base.EOFError and HTTP.StatusError (if status is `5xx).
HTTP.ExceptionRequest.ExceptionLayer — Typerequest(ExceptionLayer, ::URI, ::Request, body) -> HTTP.ResponseThrow a StatusError if the request returns an error response status.
HTTP.ConnectionRequest.ConnectionPoolLayer — Typerequest(ConnectionPoolLayer, ::URI, ::Request, body) -> HTTP.ResponseRetrieve an IO connection from the ConnectionPool.
Close the connection if the request throws an exception. Otherwise leave it open so that it can be reused.
IO related exceptions from Base are wrapped in HTTP.IOError. See isioerror.
HTTP.TimeoutRequest.TimeoutLayer — Typerequest(TimeoutLayer, ::IO, ::Request, body) -> HTTP.ResponseClose IO if no data has been received for timeout seconds.
HTTP.StreamRequest.StreamLayer — Typerequest(StreamLayer, ::IO, ::Request, body) -> HTTP.ResponseCreate a Stream to send a Request and body to an IO stream and read the response.
Send the Request body in a background task and begins reading the response immediately so that the transmission can be aborted if the Response status indicates that the server does not wish to receive the message body. RFC7230 6.5.
Parser
Source: Parsers.jl
HTTP.Parsers — ModuleThe parser separates a raw HTTP Message into its component parts.
If the input data is invalid the Parser throws a HTTP.ParseError.
The parse_* functions processes a single element of a HTTP Message at a time and return a SubString containing the unused portion of the input.
The Parser does not interpret the Message Headers. It is beyond the scope of the Parser to deal with repeated header fields, multi-line values, cookies or case normalization.
The Parser has no knowledge of the high-level Request and Response structs defined in Messages.jl. However, the Request and Response structs must have field names compatible with those expected by the parse_status_line! and parse_request_line! functions.
Messages
Source: Messages.jl
HTTP.Messages — ModuleThe Messages module defines structs that represent HTTP.Request and HTTP.Response Messages.
The Response struct has a request field that points to the corresponding Request; and the Request struct has a response field. The Request struct also has a parent field that points to a Response in the case of HTTP Redirect.
The Messages module defines IO read and write methods for Messages but it does not deal with URIs, creating connections, or executing requests.
The read methods throw EOFError exceptions if input data is incomplete. and call parser functions that may throw HTTP.ParsingError exceptions. The read and write methods may also result in low level IO exceptions.
Sending Messages
Messages are formatted and written to an IO stream by Base.write(::IO,::HTTP.Messages.Message) and or HTTP.Messages.writeheaders.
Receiving Messages
Messages are parsed from IO stream data by HTTP.Messages.readheaders. This function calls HTTP.Parsers.parse_header_field and passes each header-field to HTTP.Messages.appendheader.
Headers
Headers are represented by Vector{Pair{String,String}}. As compared to Dict{String,String} this allows repeated header fields and preservation of order.
Header values can be accessed by name using HTTP.Messages.header and HTTP.Messages.setheader (case-insensitive).
The HTTP.Messages.appendheader function handles combining multi-line values, repeated header fields and special handling of multiple Set-Cookie headers.
Bodies
The HTTP.Message structs represent the Message Body as Vector{UInt8}.
Streaming of request and response bodies is handled by the HTTP.StreamLayer and the HTTP.Stream <: IO stream.
Streams
Source: Streams.jl
HTTP.Streams.Stream — TypeStream(::Request, ::IO)Creates a HTTP.Stream that wraps an existing IO stream.
startwrite(::Stream)sends theRequestheaders to theIOstream.write(::Stream, body)sends thebody(or a chunk of the body).closewrite(::Stream)sends the final0chunk (if needed) and callsclosewriteon theIOstream. When theIOstream is aHTTP.ConnectionPool.Transaction, callingclosewritereleases theHTTP.ConnectionPool.Connectionback into the pool for use by the next pipelined request.startread(::Stream)callsstartreadon theIOstream then reads and parses theResponseheaders. When theIOstream is aHTTP.ConnectionPool.Transaction, callingstartreadwaits for other pipelined responses to be read from theHTTP.ConnectionPool.Connection.eof(::Stream)andreadavailable(::Stream)parse the body from theIOstream.closeread(::Stream)reads the trailers and callsclosereadon theIOstream. When theIOstream is aHTTP.ConnectionPool.Transaction, callingclosereadreleases the readlock and allows the next pipelined response to be read by anotherStreamthat is waiting instartread. If a complete response has not been received,closereadthrowsEOFError.
Connections
Source: ConnectionPool.jl
HTTP.ConnectionPool — ModuleThis module provides the getconnection function with support for:
- Opening TCP and SSL connections.
- Reusing connections for multiple Request/Response Messages,
- Pipelining Request/Response Messages. i.e. allowing a new Request to be sent before previous Responses have been read.
This module defines a Connection struct to manage pipelining and connection reuse and a Transaction<: IO struct to manage a single pipelined request. Methods are provided for eof, readavailable, unsafe_write and close. This allows the Transaction object to act as a proxy for the TCPSocket or SSLContext that it wraps.
The POOL is used to manage connection pooling. Connections are identified by their host, port, pipeline limit, whether they require ssl verification, and whether they are a client or server connection. If a subsequent request matches these properties of a previous connection and limits are respected (reuse limit, idle timeout), and it wasn't otherwise remotely closed, a connection will be reused. Transactions pipeline their requests and responses concurrently on a Connection by calling startwrite and closewrite, with corresponding startread and closeread.