Internal Architecture
HTTP.Layers.Layer
— Type.Request 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
— Function.The 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(RedirectLayer, 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
request(RedirectLayer, method, ::URI, headers, body) -> HTTP.Response
Redirects the request in the case of 3xx response status.
request(BasicAuthLayer, method, ::URI, headers, body) -> HTTP.Response
Add Authorization: Basic
header using credentials from url userinfo.
HTTP.CookieRequest.CookieLayer
— Type.request(CookieLayer, method, ::URI, headers, body) -> HTTP.Response
Add locally stored Cookies to the request headers. Store new Cookies found in the response headers.
request(CanonicalizeLayer, method, ::URI, headers, body) -> HTTP.Response
Rewrite request and response headers in Canonical-Camel-Dash-Format.
HTTP.MessageRequest.MessageLayer
— Type.request(MessageLayer, method, ::URI, headers, body) -> HTTP.Response
Construct a Request
object and set mandatory headers.
request(AWS4AuthLayer, ::URI, ::Request, body) -> HTTP.Response
Add 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
— Type.request(RetryLayer, ::URI, ::Request, body) -> HTTP.Response
Retry 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
).
request(ExceptionLayer, ::URI, ::Request, body) -> HTTP.Response
Throw a StatusError
if the request returns an error response status.
request(ConnectionPoolLayer, ::URI, ::Request, body) -> HTTP.Response
Retrieve 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
— Type.request(TimeoutLayer, ::IO, ::Request, body) -> HTTP.Response
Close IO
if no data has been received for timeout
seconds.
HTTP.StreamRequest.StreamLayer
— Type.request(StreamLayer, ::IO, ::Request, body) -> HTTP.Response
Create 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
— Module.The 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
— Module.The 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
— Type.Stream(::Request, ::IO)
Creates a HTTP.Stream
that wraps an existing IO
stream.
startwrite(::Stream)
sends theRequest
headers to theIO
stream.write(::Stream, body)
sends thebody
(or a chunk of the body).closewrite(::Stream)
sends the final0
chunk (if needed) and callsclosewrite
on theIO
stream. When theIO
stream is aHTTP.ConnectionPool.Transaction
, callingclosewrite
releases theHTTP.ConnectionPool.Connection
back into the pool for use by the next pipelined request.startread(::Stream)
callsstartread
on theIO
stream then reads and parses theResponse
headers. When theIO
stream is aHTTP.ConnectionPool.Transaction
, callingstartread
waits for other pipelined responses to be read from theHTTP.ConnectionPool.Connection
.eof(::Stream)
andreadavailable(::Stream)
parse the body from theIO
stream.closeread(::Stream)
reads the trailers and callscloseread
on theIO
stream. When theIO
stream is aHTTP.ConnectionPool.Transaction
, callingcloseread
releases the readlock and allows the next pipelined response to be read by anotherStream
that is waiting instartread
. If a complete response has not been received,closeread
throwsEOFError
.
Connections
Source: ConnectionPool.jl
HTTP.ConnectionPool
— Module.This 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
.