net.http #
net.http
net.http is V's HTTP client and server. The client speaks HTTP/1.1 and HTTP/2, with HTTP/3 available behind -d http3; the server speaks HTTP/1.1 and, opt-in, HTTP/2 — over plain TCP or with TLS termination.
For building web applications on top of it, see veb, which has its own server and router.
Response and handler helpers
http.redirect(url, code) returns an empty redirect response with a Location header. http.not_found() returns a 404 response with the body 404 page not found. http.error_response(message, code) returns the message verbatim as plain text, with X-Content-Type-Options: nosniff. Both error helpers set a UTF-8 content type.
All three helpers return HTTP/1.1 responses with a byte-counted Content-Length, including zero for an empty body. Numeric status codes are retained even when no standard reason phrase exists. Use Response.set_version when another protocol version is needed.
import net.http
redirect := http.redirect('/login', 303)
assert redirect.status_code == 303
failure := http.error_response('Access denied', 403)
assert failure.body == 'Access denied'
http.strip_prefix(prefix, handler) wraps an http.Handler. It removes a literal prefix from the URL path before calling the wrapped handler, and returns 404 for a nonmatching or malformed URL. Query strings and escaped path suffixes are preserved. The decoded path and any preserved alternative escaped spelling must both match the prefix, so /api does not match /%61pi.
An empty prefix passes the request through unchanged. Removing the entire path leaves /. Matching uses a string prefix: use /api/ when a directory boundary is required. The wrapper supports mutable handlers and leaves the caller's request unchanged.
Status reason phrases
http.status_from_int(code).str() returns the standard HTTP reason phrase, matching Go's StatusText. Unknown and unassigned codes return an empty string, including legacy nonstandard statuses 306 and 509. Servers retain the numeric status code when the reason phrase is empty, and an explicit custom reason phrase is preserved.
The existing enum identifiers checkpoint_draft and unordered_collection remain available for compatibility. Their standard phrases are Early Hints (103) and Too Early (425). Status 203 uses Non-Authoritative Information, 207 uses Multi-Status, and 418 uses I'm a teapot.
A server without an explicit listener opens a socket on its configured addr, even when standard input is a socket. An explicitly supplied listener may use descriptor zero. Closing the server clears the closed listener, so a later listen_and_serve() binds a new socket on the server's address. Failed accepts other than timeouts pause briefly before retrying.
Making requests
import net.http
resp := http.get('https://example.com/')!
println(resp.status_code)
println(resp.body)
http.get checks the server certificate, and http.fetch(..., validate: false) skips that check. get, post, and the other helpers check the certificate. fetch, prepare, and download_file_with_progress accept validate: false. Leave verify empty to use the system trust store on HTTP/1.1 and HTTP/2. HTTP/3 ignores validate: false and has no system trust store, so set verify.
http.post, http.post_json, http.post_form, http.put, http.patch, http.head and http.delete cover the common verbs. http.fetch takes a FetchConfig when you need headers, cookies, timeouts, redirect control, client certificates or progress callbacks:
import net.http
resp := http.fetch(
url: 'https://example.com/api'
method: .post
header: http.new_header(key: .content_type, value: 'application/json')
data: '{"hello":"world"}'
)!
HTTPS requests use HTTP/1.1 by default. Set enable_http2: true in http.fetch or a http.Request to opt in to HTTP/2 when the server supports it.
Cookies given in cookies of http.fetch, or added with Request.add_cookie, are sent in one Cookie header as name=value pairs. A value goes through sanitize_cookie_value. A cookie whose name is empty or contains ;, =, a space, a control character or a non-ASCII byte is not sent. Request.cookie_header_value returns the header value that is sent.
A positive stop_copying_limit caps the stored HTTP/1.1 or HTTP/2 response body in bytes, independently of response headers and network read boundaries. HTTP/1.1 preserves the full body for nonpositive limits. Chunked HTTP/1.1 responses count body bytes after removing chunk framing. HTTP/1.1 still applies Content-Encoding decompression, and caps the resulting body at the limit. If the bounded compressed prefix cannot be decompressed, it retains the encoded prefix, as with ordinary response parsing. The client still reads the full response and invokes progress callbacks, so streaming downloads can keep a bounded preview without losing data in the callbacks.
With HTTP/2 negotiation disabled, returning an error from on_progress or on_progress_body stops an HTTP/1.1 request without retrying or replaying it, including on a reused connection. The caller receives the original callback error with its type and code preserved. This also applies when connection reuse is disabled. The Windows Schannel backend with connection reuse disabled supports on_progress; it does not invoke on_progress_body.
An HTTP/2 or HTTP/3 response with a field that can not be delivered is an error, not a response without that field: a field name that is not a token, or a field value with CR, LF or NUL, makes the response malformed, and more than http.max_headers fields, trailers included, is an http.HeaderLimitError.
Serving requests
A server is a Handler — anything with a handle(Request) Response method — plus an http.Server to run it:
import net.http
struct App {}
fn (mut a App) handle(req http.Request) http.Response {
return http.Response{
status_code: 200
body: 'you asked for ${req.url}'
}
}
fn main() {
mut server := &http.Server{
addr: ':8080'
handler: App{}
}
server.listen_and_serve()
}
Set cert and cert_key to terminate TLS (PEM strings when in_memory_verification is true, otherwise file paths). Set enable_http2 to serve HTTP/2: on the TLS listener it advertises ALPN h2, http/1.1, and on the plain listener it accepts prior-knowledge cleartext h2c. Either way a client that does not ask for HTTP/2 keeps the ordinary HTTP/1.1 path.
An HTTP/2 request with a field name that is not a token, or a field value with CR, LF or NUL, is malformed: its stream is reset and the handler is not called. A request with more than http.max_headers fields, counting the Host field that :authority becomes, is answered with 431 Request Header Fields Too Large.
Identifying the client
Request.remote_addr is the address of the peer that sent the request — the equivalent of Go's http.Request.RemoteAddr. Request.remote_ip() returns the same thing without the port:
import net.http
struct App {}
fn (mut a App) handle(req http.Request) http.Response {
return http.Response{
status_code: 200
body: 'you are ${req.remote_ip()} (${req.remote_addr})'
}
}
| request | remote_addr |
remote_ip() |
|---|---|---|
| IPv4 peer | 127.0.0.1:52134 |
127.0.0.1 |
| IPv6 peer | [::1]:52134 |
::1 |
| link-local IPv6 peer | [fe80::1%3]:52134 |
fe80::1%3 |
| built by you, to send | '' |
'' |
Three things are worth knowing about it:
It is server-side only. http.Server fills it in for every request it hands to a Handler, on all of its paths — plain HTTP/1.1, TLS HTTP/1.1, h2c and HTTP/2 over TLS. A Request you construct yourself to send with the client has an empty remote_addr, and so does a request served by veb, which has its own server: use ctx.ip() there.
It cannot be forged. The address is read from the accepted socket, not from anything the client sent. The server also mirrors the bare IP into a Remote-Addr request header, which predates this field; any Remote-Addr header the client sent is discarded first, in any casing, so it cannot shadow the real one. That header is best-effort — headers live in a fixed-size array, so a request that already filled it leaves no room — while remote_addr is always set. Prefer the field.
A scoped IPv6 address keeps its zone. A link-local peer is reported as [fe80::1%3]:52134, where 3 is the numeric interface index: a link-local address only identifies a host together with the interface it was seen on, and without the zone the address cannot be dialled back. net.split_address and net.dial_tcp both accept that form.
Behind a reverse proxy
If your server sits behind nginx, a load balancer or a CDN, then the proxy is the peer: remote_addr is the proxy's address, which is correct but probably not what you want to log or rate-limit on.
The original client address is only available in a header the proxy adds — X-Forwarded-For, X-Real-Ip, CF-Connecting-IP and friends. Those are request headers like any other: a client that can reach your server directly can send whatever it likes in them. They are trustworthy only if nothing but your proxy can open a connection to the server, and only after you have configured the proxy to overwrite (not append to) them.
So use remote_addr to decide whether the connection came from your proxy, and only then read the forwarded header:
import net.http
const trusted_proxy = '10.0.0.1'
fn client_ip(req http.Request) string {
if req.remote_ip() != trusted_proxy {
return req.remote_ip()
}
// X-Forwarded-For is a list, oldest first; the last entry is the one our
// own proxy appended, and the only one it vouches for.
forwarded := req.header.get(.x_forwarded_for) or { return req.remote_ip() }
return forwarded.all_after_last(',').trim_space()
}
Server-sent event encoding
http.SSEEvent formats server-sent events independently of a connection. Set its id, event, data and retry fields, then call encode() for a string or encode_into(mut builder) to append to a strings.Builder. Each event ends with a blank line. Data normalizes CRLF and CR line endings to LF and emits a data: field per line; CR and LF are removed from id and event. Empty fields and zero retry are omitted, so an empty event encodes as a single LF and does not dispatch a browser message.
Content type detection
http.detect_content_type(bytes) recognizes common image, audio, video, document and archive signatures. It examines at most 512 bytes, plus up to three bytes to finish a UTF-8 sequence crossing that boundary. Text without a recognized document signature returns text/plain; charset=utf-8; other unknown content returns application/octet-stream. Use sniffing only when no explicit content type is available. In particular, serving sniffed HTML from your application origin permits that content to run scripts.
Cookie jars
http.new_cookie_jar() creates an in-memory cookie jar. Store response cookies with jar.set_cookies(response_url, response.cookies()), using the URL that produced the response. Use jar.cookies_header(request_url) as the next request's Cookie header, or jar.cookies(request_url) to retrieve the matching Cookie values.
import net.http
mut jar := http.new_cookie_jar()
jar.set_cookies('https://example.com/account/login', [
http.Cookie{
name: 'sid'
value: 'abc'
},
])
assert jar.cookies_header('https://example.com/account/profile') == 'sid=abc'
assert jar.cookies_header('https://sub.example.com/account/profile') == ''
URLs must be absolute HTTP or HTTPS URLs with a host; other URLs are ignored. Cookies without Domain stay on the response host. An explicit domain must be valid and match that host; IP addresses require an exact match. Domains are compared without case distinctions or a leading dot, and equivalent name/domain/path entries replace or delete each other. The jar does not check a public suffix list.
Path matching uses the escaped URL path. Missing, empty, or relative Path attributes use the response URL's directory. Max-Age takes precedence over Expires, expired cookies are excluded, and Secure cookies are returned only for HTTPS. Header generation validates names and sanitizes and quotes values using the same rules as Request.cookie_header_value().
Cookies, headers and multipart
Header value validation also works in -check-overflow builds: permitted TAB bytes are kept.
Request.add_cookie/Request.cookie and Response.cookies handle cookies. Response.cookies reads Max-Age as a decimal integer: a positive number of seconds is stored in Cookie.max_age, zero or a negative number as -1 (delete now). Any other value remains in Cookie.unparsed, which holds only the attributes that were not read. Response.cookies parses Expires with time.parse_http_header_string. Invalid dates and years before 1601 remain in Cookie.unparsed; they do not replace a valid expiry. Cookie.raw_expires retains date text even when it cannot be parsed. Cookie.str() writes the attribute names Path, Domain and Expires as RFC 6265 spells them; Response.cookies matches attribute names in any case. When serializing Cookie, IPv4 literals such as 127.0.0.1 are accepted as domains; invalid domains are omitted. Cookie.str() omits SameSite for .same_site_not_set and .same_site_default_mode. The explicit modes write SameSite=Lax, SameSite=Strict or SameSite=None.
Cookie.str() writes a cookie only when its name is an RFC 9110 token: letters, digits and !#$%&'*+-.^_`|~. For any other name, such as one with a space, ;, ,, = or [, it returns an empty string. Reading is more lenient: Request.cookie, Response.cookies and read_cookies return any name made of visible ASCII characters.
Header provides both CommonHeader enum access (get, set, add) and string access (get_custom, set_custom, add_custom). http.parse_form, http.parse_multipart_form and http.post_multipart_form cover form bodies. http.parse_form decodes URL-encoded fields only; it does not detect or decode JSON. Select a JSON decoder by the request's content type when handling JSON bodies directly. veb makes JSON object fields available in its ctx.form map.
Uploaded file names are untrusted input. http.safe_filename(name) returns the last path component, treating both / and \ as separators, and returns an error for an empty component, ., .., or a component containing NUL. Check the returned name against your destination directory and platform's file naming rules before writing the upload.
A Header holds at most http.max_headers (50) fields. add_custom and set_custom return an http.HeaderLimitError for a field that does not fit, and so do http.parse_request, http.parse_response and the other parsers for a message that has more header fields. add, set, new_header and join have no error to return: they drop such a field.
A field value can not contain CR, LF or NUL (RFC 9110 section 5.5): written as they are, these bytes end the field line. add_custom and set_custom return an http.HeaderValueError for such a value, and so do the parsers for a received field whose value has a bare CR or a NUL. add, set, new_header and join store a space in place of each of these bytes. Request.do and http.fetch return the error for a user_agent that contains one of them.
The spaces and tabs at the start and end of a field value are optional whitespace, not part of the value (RFC 9110 section 5.5). Header.render and the HTTP/1 response writers remove this edge whitespace. The HTTP/1 request writer also trims values in Request.header other than Cookie: add_custom('X-Pad', ' padded ') is sent as X-Pad: padded, as Go's Header.Write does. The Request.user_agent fallback and explicit Cookie header values are not trimmed. This trimming guarantee does not apply to the HTTP/2 and HTTP/3 writers. get and values still return the value as it was stored.
Cookie.str() and sanitize_cookie_value remove invalid bytes from cookie values, including control characters, quotes, semicolons and backslashes. Spaces and commas are preserved; the sanitized value is quoted when it contains a space or comma.
Header.render(canonicalize: true) writes each field name in a canonical spelling instead of the stored one. A name that has a CommonHeader is spelled like CommonHeader.str(), the spelling of the IANA HTTP Field Name Registry for the names registered there: ETag, TE, WWW-Authenticate. Any other name gets its first letter and each letter after a - in upper case and the rest in lower case: x-request-id becomes X-Request-Id. Go's textproto.CanonicalMIMEHeaderKey applies the second rule to every name, so it has Etag, Te and Www-Authenticate instead; field names are case-insensitive (RFC 9110, section 5.1), so both spellings name the same field.
add_custom and set_custom return an error for a name that is not a valid HTTP field name: an empty one, or one with a space, a colon, a control character or any other byte that is not an RFC 9110 token character. The name is checked when it is added, because it is not checked again when the header is sent.
http.canonical_header_key(name) returns the canonical spelling of a header name in any case, for example Accept-Encoding for accept-ENCODING. It never fails: like Go's http.CanonicalHeaderKey, it returns a name that is not valid unchanged.
Idle connection reuse
Before reusing an idle plain TCP connection, the client checks for a server-side close. A closed connection is discarded before sending request bytes, so a POST can use a fresh connection safely. Healthy idle connections remain reusable.
Pooled connections expire after Transport.idle_timeout without requiring another request. A background worker checks idle pools at least once per second and stops when the pools are empty. Active HTTP/2 and HTTP/3 streams are preserved. close_idle_connections() can flush pools sooner.
After HTTP/2 GOAWAY, new requests use a fresh connection. The replaced connection stops accepting new streams, lets active streams finish, and stops its reader when idle.
If a non-idempotent request fails after transmission starts, the client returns an error without replaying it: a missing response does not prove that the server ignored the request.
Windows TLS handshake compatibility
On Windows, HTTPS uses Schannel first. If its handshake returns SEC_E_INVALID_TOKEN (0x80090308), the client retries the connection using the configured net.ssl backend (mbedTLS by default). This happens before sending any HTTP request bytes and also applies to pooled connections. Other Schannel errors, including certificate failures, propagate. Errors after sending a request do not trigger this backend fallback.
The retry preserves certificate validation, configured certificates, HTTP/2 negotiation, request headers, method, and body. -d no_vschannel continues to select net.ssl directly.
Redirect responses without a Location header, or with an empty Location, are returned to the caller without following another URL.
Reverse Proxy
http.new_single_host_reverse_proxy creates an HTTP reverse proxy that routes incoming requests to a target upstream server. It satisfies the http.Handler interface:
import net.http
fn main() {
mut rp := http.new_single_host_reverse_proxy('http://127.0.0.1:8080')!
mut server := &http.Server{
addr: ':9000'
handler: rp
}
server.listen_and_serve()
}
The reverse proxy strips hop-by-hop headers (such as Connection, Keep-Alive, Transfer-Encoding), manages forwarding headers (X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto), and supports director, modify_response, and error_handler hooks.
Paths retain percent-encoded separators when joined to the upstream base path. Forwarding metadata describes the incoming host and URL scheme, while preserve_host controls the upstream Host header. Relative incoming URLs default to http; a TLS terminator should set X-Forwarded-Proto to https in its director hook. Existing forwarding headers are preserved, so clear or replace values from untrusted clients in that hook before using them for authentication or access control. HTTPS upstream certificate validation is enabled.
Requests and responses are buffered by the HTTP client. WebSocket upgrades and other protocol tunnels are not supported. A director or response modifier may replace the body; its content length is recalculated when its size changes. Response modifiers receive the upstream representation, including any content encoding. A hook that changes the representation must also update or remove Content-Encoding. Response hop headers are removed before the hook; the hook is responsible for any headers it adds.
Diagnostic request and response dumps
http.dump_request(req, body) renders the stored method, URL, version and headers as HTTP/1-style text. An empty URL is shown as /. The URL is otherwise preserved as stored, and headers normally added by the transport are not synthesized. Header rendering follows the selected version, but HTTP/2 and HTTP/3 dumps are text, not wire frames.
http.dump_response(resp, body) uses the same representation as resp.bytestr(). For both helpers, setting body to true includes the stored body byte for byte; false omits it without changing the status line or headers such as Content-Length. Neither helper modifies its input.
import net.http
req := http.Request{
method: .post
url: '/upload'
data: 'hello'
}
assert http.dump_request(req, true) == 'POST /upload HTTP/1.1\r\n\r\nhello'
Constants #
const h2_client_preface = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
h2_client_preface is the fixed sequence a client sends to start an HTTP/2 connection, immediately followed by a SETTINGS frame (RFC 7540 Section 3.5).
const h2_default_initial_window = u32(65535)
h2_default_initial_window is the initial flow-control window for both the connection and new streams (RFC 7540 Section 6.9.2).
const h2_frame_data = u8(0x0)
HTTP/2 frame types (RFC 7540 Section 6).
const h2_frame_headers = u8(0x1)
const h2_frame_priority = u8(0x2)
const h2_frame_rst_stream = u8(0x3)
const h2_frame_settings = u8(0x4)
const h2_frame_push_promise = u8(0x5)
const h2_frame_ping = u8(0x6)
const h2_frame_goaway = u8(0x7)
const h2_frame_window_update = u8(0x8)
const h2_frame_continuation = u8(0x9)
const h2_flag_end_stream = u8(0x1)
HTTP/2 frame flags (RFC 7540 Section 6). The same bit is reused across frame types with different meanings.
const h2_flag_ack = u8(0x1)
const h2_flag_end_headers = u8(0x4)
const h2_flag_padded = u8(0x8)
const h2_flag_priority = u8(0x20)
const h2_frame_header_len = 9
h2_frame_header_len is the fixed size of a frame header in bytes.
const h2_default_max_frame_size = u32(16384)
h2_default_max_frame_size is the initial SETTINGS_MAX_FRAME_SIZE (RFC 7540 Section 6.5.2), and the smallest value a peer may set it to.
const h2_max_max_frame_size = u32(16777215)
h2_max_max_frame_size is the largest permitted SETTINGS_MAX_FRAME_SIZE (RFC 7540 Section 6.5.2): 2^24-1 bytes.
const h2_settings_header_table_size = u16(0x1)
HTTP/2 setting identifiers (RFC 7540 Section 6.5.2).
const h2_settings_enable_push = u16(0x2)
const h2_settings_max_concurrent_streams = u16(0x3)
const h2_settings_initial_window_size = u16(0x4)
const h2_settings_max_frame_size = u16(0x5)
const h2_settings_max_header_list_size = u16(0x6)
const h2_hpack_default_table_size = 4096
h2_hpack_default_table_size is the default maximum size of the HPACK dynamic table, in bytes (RFC 7541 Section 4.2, and the default value of SETTINGS_HEADER_TABLE_SIZE).
const h2_err_retryable_code = -20012
h2_err_retryable_code tags stream errors where the request provably never reached server processing (GOAWAY-unprocessed, connection closed before the request was sent, admission refused), so it is safe to retry on a fresh connection even for non-idempotent methods.
const max_headers = 50
max_headers is the number of fields that a Header can hold.
const default_server_port = 9009
const default_https_server_port = 9043
fn canonical_header_key #
fn canonical_header_key(key string) string
canonical_header_key returns the canonical form of the header name key, in the spelling that Header.render(canonicalize: true) writes. The name can be in any case. It never fails: like Go's http.CanonicalHeaderKey, it returns a key that is not a valid header name unchanged. That is an empty name, or one with a byte that is not an RFC 9110 token character, such as a space, =, a control character or a non-ASCII byte. Such a name is still rejected by Header.add_custom and Header.set_custom.
Examples
assert http.canonical_header_key('accept-ENCODING') == 'Accept-Encoding'
assert http.canonical_header_key('x custom') == 'x custom'
fn close_idle_connections #
fn close_idle_connections()
close_idle_connections closes every idle pooled connection held by the default Transport. Useful before fork()-style process handoffs, or in tests.
fn default_reverse_proxy_error_handler #
fn default_reverse_proxy_error_handler(mut req Request, err IError) Response
default_reverse_proxy_error_handler returns a 502 Bad Gateway response when upstream fails.
fn default_transport #
fn default_transport() &Transport
default_transport returns the process-global Transport that fetch() and Request.do() route through.
fn delete #
fn delete(url string) !Response
delete sends an HTTP DELETE request to the given url.
fn detect_content_type #
fn detect_content_type(data []u8) string
detect_content_type sniffs the MIME type of the first bytes of a body, so callers can serve files whose extension is unknown instead of failing. The algorithm follows Go's net/http sniffing: exact binary magic first, then text signatures for text-looking input, text/plain; charset=utf-8 for other text, and application/octet-stream as the last resort. (The motivation comes from v-mime style content detection.) Detection examines at most 512 bytes, plus up to three available continuation bytes when a UTF-8 sequence crosses that boundary.
Only use the result when no better type is known: a sniffed text/html served from your own origin executes scripts, so never relabel an explicitly configured type with this.
fn dial_tcp_via_proxy #
fn dial_tcp_via_proxy(proxy_url string, host string) !&net.TcpConn
dial_tcp_via_proxy connects to host through the proxy specified by proxy_url. host should be in host:port form.
fn download_file #
fn download_file(url string, out_file_path string) !
download_file retrieves a document from the URL url, and saves it in the output file path out_file_path.
fn download_file_with_progress #
fn download_file_with_progress(url string, path string, params DownloaderParams) !Response
download_file_with_progress will save the URL url to the filepath path . Unlike download_file/2, it does not load the whole content in memory, but instead streams it chunk by chunk to the target path, as the chunks are received from the network. This makes it suitable for downloading big files, without increasing the memory consumption of your application.
By default, it will also show a progress line, while the download happens. If you do not want a status line, you can call it like this: http.download_file_with_progress(url, path, downloader: http.SilentStreamingDownloader{}), or you can implement your own http.Downloader and pass that instead.
Note: the returned response by this function, will have a truncated .body, after the firstfew KBs, because it does not accumulate all its data in memory, instead relying on the downloaders to save the received data chunk by chunk. You can parametrise this by using stop_copying_limit:, which limits body bytes while preserving all response headers. The default is 65536 bytes; nonpositive limits keep the whole body.
fn dump_request #
fn dump_request(req Request, body bool) string
dump_request returns an HTTP/1-style diagnostic representation of req. It uses the stored method, URL (or '/' when empty), version and headers; it does not apply transport defaults or rewrite the URL. When body is true, the stored body is included without changing its bytes. req is not modified.
fn dump_response #
fn dump_response(resp Response, body bool) string
dump_response returns the same diagnostic representation as resp.bytestr(). When body is false, only the body is omitted; the status line and headers, including Content-Length, are unchanged. resp is not modified.
fn error_response #
fn error_response(msg string, code int) Response
error_response returns an HTTP/1.1 plain-text response with the given status code and message. The message is preserved verbatim, including any newlines.
fn fetch #
fn fetch(config FetchConfig) !Response
Todo: @[noinline] attribute is used for temporary fix the 'get_text()' intermittent segfault / nil value when compiling with GCC 13.2.x and -prod option ( Issue #20506 )fetch sends an HTTP request to the url with the given method and configuration. When config.url uses a scheme registered via register_scheme (e.g. s3://), the call is delegated to that handler instead of the native HTTP path.
fn get #
fn get(url string) !Response
get sends a GET HTTP request to the given url.
fn get_text #
fn get_text(url string) string
get_text sends an HTTP GET request to the given url and returns the text content of the response.
fn h2_parse_frame #
fn h2_parse_frame(header H2FrameHeader, payload []u8) !H2Frame
h2_parse_frame decodes a frame from an already-parsed header and its payload.
fn h2_parse_frame_header #
fn h2_parse_frame_header(buf []u8) !H2FrameHeader
h2_parse_frame_header parses the 9-byte frame header at the start of buf.
fn h2_read_frame #
fn h2_read_frame(buf []u8) !(H2Frame, int)
h2_read_frame parses one frame (header + payload) from the start of buf, returning the frame and the number of bytes consumed. The caller is responsible for enforcing the negotiated SETTINGS_MAX_FRAME_SIZE.
fn head #
fn head(url string) !Response
head sends an HTTP HEAD request to the given url.
fn method_from_str #
fn method_from_str(m string) Method
method_from_str returns the corresponding Method enum field given a string m, e.g. 'GET' would return Method.get.
Currently, the default value is Method.get for unsupported string value.
fn new_custom_header_from_map #
fn new_custom_header_from_map(kvs map[string]string) !Header
new_custom_header_from_map creates a Header from string key value pairs It returns a HeaderValueError for a value that contains CR, LF or NUL.
fn new_h2_conn #
fn new_h2_conn(transport H2Transport) &H2Conn
new_h2_conn creates a client connection over transport. The HTTP/2 connection preface is sent lazily on the first request.
fn new_h2_mux_conn #
fn new_h2_mux_conn(transport H2Transport, close_transport fn ()) &H2MuxConn
new_h2_mux_conn creates a multiplexed connection over transport and starts its background reader. The connection preface is sent lazily with the first request. close_transport is REQUIRED (non-nil): it is called once at final teardown and MUST close/interrupt the transport so the blocked reader exits. A nil closer is a programming error and panics here, because the reader can only be unblocked by closing the transport (see the field comment).
CONCURRENCY REQUIREMENT: the background reader calls transport.read() while request threads call transport.write(), so transport MUST be safe for a read and a write to run simultaneously on separate threads. A raw TCP socket is (it is full-duplex); a TLS connection is NOT — one OpenSSL/mbedTLS SSL object must not be read and written at the same time. Wiring this to a TLS backend therefore requires a concurrent-safe transport adapter (a serialized or non-blocking I/O loop); a single blocking I/O mutex is insufficient because it would deadlock the blocked reader. That adapter is provided when the pooled transport is wired up (see the HTTP connection-pooling Phase 3 work); until then this type is exercised only over a full-duplex in-memory pipe in tests.
fn new_header #
fn new_header(kvs ...HeaderConfig) Header
new_header creates a new Header object. Only the first max_headers entries of kvs are stored. CR, LF and NUL in a value are replaced by a space, see add.
fn new_header_from_map #
fn new_header_from_map(kvs map[CommonHeader]string) Header
new_header_from_map creates a Header from key value pairs CR, LF and NUL in a value are replaced by a space, see add.
fn new_http_proxy #
fn new_http_proxy(raw_url string) !&HttpProxy
new_http_proxy creates a new HttpProxy instance, from the given http proxy url in raw_url
fn new_request #
fn new_request(method Method, url_ string, data string) Request
new_request creates a new Request given the request method, url_, and data.
fn new_response #
fn new_response(conf ResponseConfig) Response
new_response creates a Response object from the configuration. This function will add a Content-Length header if body is not empty.
fn new_reverse_proxy #
fn new_reverse_proxy(config ReverseProxyConfig) !&ReverseProxy
new_reverse_proxy returns a new ReverseProxy configured with config.
fn new_single_host_reverse_proxy #
fn new_single_host_reverse_proxy(target_url string) !&ReverseProxy
new_single_host_reverse_proxy returns a new ReverseProxy that routes requests to the specified upstream target_url.
fn new_transport #
fn new_transport() &Transport
new_transport creates an empty Transport with default limits.
fn not_found #
fn not_found() Response
not_found returns an HTTP/1.1 404 response with a plain-text error body.
fn parse_form #
fn parse_form(body string) map[string]string
parse_form decodes URL-encoded key=value&key=value forms without detecting JSON bodies.
Fixme: Some servers can require theparameter in a specific order.
a possible solution is to use the a list of QueryValue
fn parse_multipart_form #
fn parse_multipart_form(body string, boundary string) (map[string]string, map[string][]FileData)
parse_multipart_form parses an http request body, given a boundary string For more details about multipart forms, see: https://datatracker.ietf.org/doc/html/rfc2183 https://datatracker.ietf.org/doc/html/rfc2388 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
fn parse_request #
fn parse_request(mut reader io.BufferedReader) !Request
parse_request parses a raw HTTP request into a Request object. See also: parse_request_head, which parses only the headers.
fn parse_request_head #
fn parse_request_head(mut reader io.BufferedReader) !Request
parse_request_head parses only the header of a raw HTTP request into a Request object
fn parse_request_head_str #
fn parse_request_head_str(s string) !Request
parse_request_head parses only the header of a raw HTTP request into a Request object
fn parse_request_str #
fn parse_request_str(s string) !Request
parse_request_str parses a raw HTTP request string into a Request object.
fn parse_response #
fn parse_response(resp string) !Response
parse_response parses an HTTP response and decodes its transfer and content encodings.
fn patch #
fn patch(url string, data string) !Response
patch sends string data as an HTTP PATCH request to the given url.
fn post #
fn post(url string, data string) !Response
post sends the string data as an HTTP POST request to the given url.
fn post_form #
fn post_form(url string, data map[string]string) !Response
post_form sends the map data as X-WWW-FORM-URLENCODED data to an HTTP POST request to the given url.
fn post_json #
fn post_json(url string, data string) !Response
post_json sends the JSON data as an HTTP POST request to the given url.
fn post_multipart_form #
fn post_multipart_form(url string, conf PostMultipartFormConfig) !Response
post_multipart_form sends multipart form data conf as an HTTP POST request to the given url.
fn prepare #
fn prepare(config FetchConfig) !Request
prepare prepares a new request for fetching, but does not call its .do() method. It is useful, if you want to reuse request objects, for several requests in a row, modifying the request each time, then calling .do() to get the new response.
fn put #
fn put(url string, data string) !Response
put sends string data as an HTTP PUT request to the given url.
fn redirect #
fn redirect(url string, code int) Response
redirect returns an empty HTTP/1.1 response with a Location header and the given status code (typically 301, 302, 303, 307 or 308).
fn register_scheme #
fn register_scheme(scheme string, handler SchemeHandlerFn)
register_scheme attaches handler as the dispatcher for URLs with the given scheme (e.g. 's3'). Handlers are looked up by fetch() before the native HTTP path runs. Modules typically call this from init().
fn safe_filename #
fn safe_filename(name string) !string
safe_filename extracts a safe bare file name from a multipart filename value: only the final path component (after the last / or \) is kept, and empty, ., .., or NUL-containing results are rejected. Use it before trusting an uploaded file name for storage, and still validate the result against the directory you write to. Rule ported from Tuntii/viltrum.
fn status_from_int #
fn status_from_int(code int) Status
status_from_int returns the corresponding enum field of Status given the code in integer value.
fn strip_prefix #
fn strip_prefix(prefix string, handler Handler) StripPrefixHandler
strip_prefix wraps handler, removing prefix from matching request URL paths. An empty prefix passes requests through unchanged. An empty resulting path becomes "/". The prefix is literal, so use a trailing slash for a directory.
fn unregister_scheme #
fn unregister_scheme(scheme string)
unregister_scheme removes a previously-registered scheme handler. Mostly useful in tests that install a temporary handler.
fn url_encode_form_data #
fn url_encode_form_data(data map[string]string) string
url_encode_form_data converts mapped data to a URL encoded string.
fn version_from_str #
fn version_from_str(v string) Version
fn CommonHeader.from #
fn CommonHeader.from[W](input W) !CommonHeader
from returns the CommonHeader value of an integer or of a field name, or an error if there is none.
fn H2ErrorCode.from #
fn H2ErrorCode.from[W](input W) !H2ErrorCode
from returns the H2ErrorCode value of an integer or of a field name, or an error if there is none.
fn Method.from #
fn Method.from[W](input W) !Method
from returns the Method value of an integer or of a field name, or an error if there is none.
fn SameSite.from #
fn SameSite.from[W](input W) !SameSite
from returns the SameSite value of an integer or of a field name, or an error if there is none.
fn ServerStatus.from #
fn ServerStatus.from[W](input W) !ServerStatus
from returns the ServerStatus value of an integer or of a field name, or an error if there is none.
fn Status.from #
fn Status.from[W](input W) !Status
from returns the Status value of an integer or of a field name, or an error if there is none.
fn Version.from #
fn Version.from[W](input W) !Version
from returns the Version value of an integer or of a field name, or an error if there is none.
interface Downloader #
interface Downloader {
// Called once, at the start of the streaming download. You can do setup here,
// like opening a target file, changing request.stop_copying_limit to a different value,
// if you need it.
mut:
on_start(mut request Request, path string) !
// Called many times, once a chunk of data is received
on_chunk(request &Request, chunk []u8, already_received u64, expected u64) !
// Called once, at the end of the download attempt. Do cleanup here,
// like closing a file (opened in on_start), reporting stats etc.
// `response` will be empty when the request fails before a response is parsed.
on_finish(request &Request, response &Response) !
}
Downloader is the interface that you have to implement, if you need to customise how download_file_with_progress works, and what output it produces while a file is downloaded.
interface H2Transport #
interface H2Transport {
mut:
read(mut buf []u8) !int
write(buf []u8) !int
}
H2Transport is the byte transport an H2Conn runs over: typically an ALPN-negotiated h2 TLS connection, but any reader/writer works (which makes the connection testable without a socket). Its method signatures match net.ssl.SSLConn, so an SSLConn satisfies it directly.
interface Handler #
interface Handler {
mut:
handle(Request) Response
}
type DirectorFn #
type DirectorFn = fn (mut req Request)
DirectorFn is a hook invoked to modify the outbound request before sending it to the upstream.
type ErrorHandlerFn #
type ErrorHandlerFn = fn (mut req Request, err IError) Response
ErrorHandlerFn produces an HTTP response when communicating with the upstream fails.
type H2DataFn #
type H2DataFn = fn (chunk []u8, body_so_far u64, body_expected u64, status int) !
H2DataFn is called for each DATA frame received on the response stream, with the chunk's bytes, the cumulative body bytes received (including this chunk), the body length from Content-Length if known (else 0), and the response status code.
type H2Frame #
type H2Frame = H2ContinuationFrame
| H2DataFrame
| H2GoawayFrame
| H2HeadersFrame
| H2PingFrame
| H2PriorityFrame
| H2PushPromiseFrame
| H2RstStreamFrame
| H2SettingsFrame
| H2UnknownFrame
| H2WindowUpdateFrame
H2Frame is any HTTP/2 frame.
fn (H2Frame) encode #
fn (f H2Frame) encode() []u8
encode serialises a frame to its on-the-wire bytes. The encoder never emits padding.
fn (HeaderKeyError) msg #
fn (err HeaderKeyError) msg() string
fn (HeaderKeyError) code #
fn (err HeaderKeyError) code() int
fn (HttpProxy) str #
fn (pr &HttpProxy) str() string
str returns the configured proxy URL for logging and debugging.
type ModifyResponseFn #
type ModifyResponseFn = fn (mut resp Response) !
ModifyResponseFn is a hook invoked to modify the response received from the upstream before returning it.
type RequestFinishFn #
type RequestFinishFn = fn (request &Request, final_size u64) !
type RequestProgressBodyFn #
type RequestProgressBodyFn = fn (request &Request, chunk []u8, body_read_so_far u64, body_expected_size u64, status_code int) !
type RequestProgressFn #
type RequestProgressFn = fn (request &Request, chunk []u8, read_so_far u64) !
type RequestRedirectFn #
type RequestRedirectFn = fn (request &Request, nredirects int, new_url string) !
type SchemeHandlerFn #
type SchemeHandlerFn = fn (config FetchConfig) !Response
SchemeHandlerFn dispatches a fetch() call for a non-HTTP scheme. Used by out-of-tree-friendly modules like net.s3 to register themselves at init time without forcing net.http to know about them statically.
enum CommonHeader #
enum CommonHeader {
accept
accept_ch
accept_charset
accept_ch_lifetime
accept_encoding
accept_language
accept_patch
accept_post
accept_ranges
access_control_allow_credentials
access_control_allow_headers
access_control_allow_methods
access_control_allow_origin
access_control_expose_headers
access_control_max_age
access_control_request_headers
access_control_request_method
age
allow
alt_svc
authorization
authority
cache_control
clear_site_data
connection
content_disposition
content_encoding
content_language
content_length
content_location
content_range
content_security_policy
content_security_policy_report_only
content_type
cookie
cross_origin_embedder_policy
cross_origin_opener_policy
cross_origin_resource_policy
date
device_memory
digest
dnt
early_data
etag
expect
expect_ct
expires
feature_policy
forwarded
from
host
if_match
if_modified_since
if_none_match
if_range
if_unmodified_since
index
keep_alive
large_allocation
last_modified
link
location
nel
origin
pragma
proxy_authenticate
proxy_authorization
range
referer
referrer_policy
retry_after
save_data
sec_fetch_dest
sec_fetch_mode
sec_fetch_site
sec_fetch_user
sec_websocket_accept
sec_websocket_key
server
server_timing
set_cookie
sourcemap
strict_transport_security
te
timing_allow_origin
tk
trailer
transfer_encoding
upgrade
upgrade_insecure_requests
user_agent
vary
via
want_digest
warning
www_authenticate
x_content_type_options
x_dns_prefetch_control
x_forwarded_for
x_forwarded_host
x_forwarded_proto
x_frame_options
x_xss_protection
}
CommonHeader is an enum of the most common HTTP headers
fn (CommonHeader) str #
fn (h CommonHeader) str() string
enum H2ErrorCode #
enum H2ErrorCode as u32 {
no_error = 0x0
protocol_error = 0x1
internal_error = 0x2
flow_control_error = 0x3
settings_timeout = 0x4
stream_closed = 0x5
frame_size_error = 0x6
refused_stream = 0x7
cancel = 0x8
compression_error = 0x9
connect_error = 0xa
enhance_your_calm = 0xb
inadequate_security = 0xc
http_1_1_required = 0xd
}
H2ErrorCode is an HTTP/2 error code, as used in RST_STREAM and GOAWAY frames (RFC 7540 Section 7).
fn (H2ErrorCode) str #
fn (e H2ErrorCode) str() string
str returns the RFC name of the error code.
enum Method #
enum Method { // as of 2023-06-20
get // Note: get ***should*** remain the first value here, to ensure that http.fetch() by default will use it
head
post
put
// uncommon ones:
acl
baseline_control
bind
checkin
checkout
connect
copy
delete
label
link
lock
merge
mkactivity
mkcalendar
mkcol
mkredirectref
mkworkspace
move
options
orderpatch
patch
pri
propfind
proppatch
rebind
report
search
trace
unbind
uncheckout
unlink
unlock
update
updateredirectref
version_control
}
The methods listed here are all of those on the list available at: https://www.iana.org/assignments/http-methods/http-methods.xhtml
fn (Method) str #
fn (m Method) str() string
str returns the string representation of the HTTP Method m.
enum SameSite #
enum SameSite {
same_site_not_set
same_site_default_mode = 1
same_site_lax_mode
same_site_strict_mode
same_site_none_mode
}
SameSite allows a server to define a cookie attribute making it impossible for the browser to send this cookie along with cross-site requests. The main goal is to mitigate the risk of cross-origin information leakage, and provide some protection against cross-site request forgery attacks.
See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
enum ServerStatus #
enum ServerStatus {
closed
running
stopped
}
enum Status #
enum Status {
unknown = -1
unassigned = 0
cont = 100
switching_protocols = 101
processing = 102
checkpoint_draft = 103
ok = 200
created = 201
accepted = 202
non_authoritative_information = 203
no_content = 204
reset_content = 205
partial_content = 206
multi_status = 207
already_reported = 208
im_used = 226
multiple_choices = 300
moved_permanently = 301
found = 302
see_other = 303
not_modified = 304
use_proxy = 305
switch_proxy = 306
temporary_redirect = 307
permanent_redirect = 308
bad_request = 400
unauthorized = 401
payment_required = 402
forbidden = 403
not_found = 404
method_not_allowed = 405
not_acceptable = 406
proxy_authentication_required = 407
request_timeout = 408
conflict = 409
gone = 410
length_required = 411
precondition_failed = 412
request_entity_too_large = 413
request_uri_too_long = 414
unsupported_media_type = 415
requested_range_not_satisfiable = 416
expectation_failed = 417
im_a_teapot = 418
misdirected_request = 421
unprocessable_entity = 422
locked = 423
failed_dependency = 424
unordered_collection = 425
upgrade_required = 426
precondition_required = 428
too_many_requests = 429
request_header_fields_too_large = 431
unavailable_for_legal_reasons = 451
client_closed_request = 499
internal_server_error = 500
not_implemented = 501
bad_gateway = 502
service_unavailable = 503
gateway_timeout = 504
http_version_not_supported = 505
variant_also_negotiates = 506
insufficient_storage = 507
loop_detected = 508
bandwidth_limit_exceeded = 509
not_extended = 510
network_authentication_required = 511
}
The status codes listed here are based on the comprehensive list, available at: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
fn (Status) str #
fn (code Status) str() string
str returns the standard HTTP reason phrase for Status code. Unknown, unassigned, and legacy nonstandard statuses have no standard phrase.
fn (Status) int #
fn (code Status) int() int
int converts an assigned and known Status to its integral equivalent. if a Status is unknown or unassigned, this method will return zero
fn (Status) is_valid #
fn (code Status) is_valid() bool
is_valid returns true if the status code is assigned and known
fn (Status) is_error #
fn (code Status) is_error() bool
is_error will return true if the status code represents either a client or a server error; otherwise will return false
fn (Status) is_success #
fn (code Status) is_success() bool
is_success will return true if the status code represents either an informational, success, or redirection response; otherwise will return false
enum Version #
enum Version {
unknown
v1_1
v2_0
v1_0
v3_0
}
The versions listed here are the most common ones.
fn (Version) str #
fn (v Version) str() string
fn (Version) protos #
fn (v Version) protos() (int, int)
protos returns the version major and minor numbers
struct C.TlsContext #
Available on Windows only.
struct C.TlsContext {}
struct Cookie #
struct Cookie {
pub mut:
name string
value string
path string // optional
domain string // optional
host_only bool // whether cookie is host-only (no Domain attribute)
expires time.Time // optional
raw_expires string // for reading cookies only. optional.
// max_age=0 means no 'Max-Age' attribute specified.
// max_age<0 means delete cookie now, equivalently 'Max-Age: 0'
// max_age>0 means Max-Age attribute present and given in seconds
max_age int
secure bool
http_only bool
same_site SameSite
raw string
unparsed []string // Raw text of unparsed attribute-value pairs
}
fn (Cookie) str #
fn (c &Cookie) str() string
str returns the serialization of the cookie for use in a Cookie header (if only Name and Value are set) or a Set-Cookie response header (if other fields are set).
If c.name is not an RFC 9110 token (letters, digits and any of !#$%&'*+-.^_`|~), the empty string is returned.
struct CookieJar #
struct CookieJar {
mut:
cookies []Cookie
}
CookieJar stores HTTP cookies with domain/path matching, expiry and Secure handling. It does not check a public suffix list.
struct DownloaderParams #
struct DownloaderParams {
FetchConfig
pub mut:
downloader &Downloader = &TerminalStreamingDownloader{}
}
DownloaderParams is similar to FetchConfig, but it also allows you to pass a downloader: your_downloader_instance parameter. See also http.SilentStreamingDownloader, and http.TerminalStreamingDownloader .
struct FetchConfig #
struct FetchConfig {
pub mut:
url string
method Method = .get
header Header
data string
params map[string]string
cookies map[string]string
user_agent string = 'v.http'
user_ptr voidptr = unsafe { nil }
verbose bool
proxy &HttpProxy = unsafe { nil }
read_timeout i64 = 30 * time.second // timeout for reading the response; applies to plain http and to direct https requests
write_timeout i64 = 30 * time.second // timeout for writing the request; applies to plain http (write timeouts are not enforced on the SSL write path yet)
validate bool = true // abort on an invalid certificate; false skips it, except HTTP/3 which always verifies
verify string // the path to a rootca.pem file, containing trusted CA certificate(s)
cert string // the path to a cert.pem file, containing client certificate(s) for the request
cert_key string // the path to a key.pem file, containing private keys for the client certificate(s)
in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file
allow_redirect bool = true // whether to allow redirect
max_retries int = 5 // maximum number of retries required when an underlying socket error occurs
enable_http2 bool // opt in to HTTP/2 for HTTPS: advertise ALPN `h2, http/1.1` and use HTTP/2 if selected. HTTP/1.1 is the default. Ignored for plain http://. Progress callbacks and receive limits work on HTTP/2; on_progress fires per DATA frame payload.
enable_http3 bool // when true and the URL is https, use HTTP/3 (QUIC over UDP) for this request instead of the TCP-based HTTP/1.1/2 path. Requires building with `-d http3`; without it, the request fails fast rather than including the QUIC/TLS/QPACK stack in ordinary net.http builds. Opt-in only (default false) and, unlike enable_http2, never automatically probed: UDP has no fast-fail signal the way a closed TCP port does, so there is no automatic fallback to HTTP/1.1/2 if the h3 attempt fails or times out -- that decision is the caller's. Ignored for plain http://. req.cert/req.cert_key (mutual TLS) are not supported by this v1 HTTP/3 client; setting either alongside enable_http3 fails the request immediately rather than silently ignoring them. req.validate is also not honorable yet: net.quic's TLS 1.3 stack has no skip-verification mode at all (v1 limitation), so certificate validation is always enforced regardless of this flag. **req.verify is effectively REQUIRED for HTTP/3 today**: net.quic's TLS 1.3 stack has no OS/default trust-store fallback (unlike the h1/h2 ssl.SSLConn path), so leaving req.verify unset means every h3 request fails certificate verification against every real server.
disable_connection_reuse bool // opt out of the shared connection pool: open a fresh connection for this request, send `Connection: close`, and close the connection after the response (the pre-pooling behavior)
// callbacks to allow custom reporting code to run, while the request is running, and to implement streaming
on_redirect RequestRedirectFn = unsafe { nil }
on_progress RequestProgressFn = unsafe { nil }
on_progress_body RequestProgressBodyFn = unsafe { nil }
on_finish RequestFinishFn = unsafe { nil }
stop_copying_limit i64 = -1 // positive limits cap body bytes copied to the response. Progress callbacks continue until the full response is read
stop_receiving_limit i64 = -1 // after this many bytes are received, break out of the loop that reads the response, effectively stopping the request early. No more on_progress callbacks will be fired. The on_finish callback will fire.
}
FetchConfig holds configuration data for the fetch function.
struct FileData #
struct FileData {
pub:
filename string
content_type string
data string
}
struct H2ClientRequest #
struct H2ClientRequest {
pub:
method string = 'GET'
scheme string = 'https'
authority string
path string = '/'
headers []H2HeaderField
body []u8
// Optional response chunk callback, called after each DATA frame's payload
// is received. The arguments are the chunk bytes (not yet copied into the
// response body), the cumulative body bytes received so far (including this
// chunk), the body length from Content-Length (0 when not present), and the
// response status code.
on_data H2DataFn = unsafe { nil }
// stop_copying_limit, when >= 0, caps the cumulative body bytes copied into
// the response body; further DATA chunks are dropped but the callback keeps
// firing and the stream is drained to completion.
stop_copying_limit i64 = -1
// stop_receiving_limit, when >= 0, causes the response read loop to break
// once that many body bytes have been received. The callback fires for the
// final chunk; no further callbacks fire after that.
stop_receiving_limit i64 = -1
// read_timeout, when > 0, bounds how long H2MuxConn.do() may wait for this
// request's response before failing it. Ignored by the one-shot H2Conn:
// its blocking transport read is already bounded by the underlying
// connection's own read_timeout, and any timeout there fails the whole
// (single-stream) connection directly. H2MuxConn's background reader, by
// contrast, treats every transport read timeout as a benign wake-up (it
// must survive one to keep servicing other streams), so nothing bounds an
// individual request's wait unless this field does.
read_timeout time.Duration
}
H2ClientRequest describes a single HTTP/2 request. Header names in headers must be lowercase (RFC 7540 Section 8.1.2); the pseudo-headers are filled in from the other fields.
struct H2ClientResponse #
struct H2ClientResponse {
pub mut:
status int
headers []H2HeaderField
body []u8
}
H2ClientResponse is the result of an HTTP/2 request.
struct H2Conn #
struct H2Conn {
mut:
transport H2Transport
encoder H2HpackEncoder
decoder H2HpackDecoder
peer H2PeerSettings
rbuf []u8 // buffered bytes read from the transport, not yet consumed
pending []H2Frame // stream frames read early (while sending), to replay
next_stream_id u32 = 1 // clients use odd stream ids
cur_stream_id u32 // the stream currently being driven by do()
send_window i64 = 65535 // connection-level send flow-control window
stream_send_window i64 // send window for cur_stream_id
handshaked bool
goaway bool
// aborted is set when this connection terminated a stream early
// (RST_STREAM sent without draining the remaining DATA). Subsequent
// requests on the same connection must fail rather than risk being starved
// by leftover DATA frames the peer had already sent for the cancelled
// stream.
aborted bool
}
H2Conn is a client-side HTTP/2 connection.
fn (H2Conn) do #
fn (mut c H2Conn) do(req H2ClientRequest) !H2ClientResponse
do sends req and returns the response, after the request's stream closes.
struct H2ContinuationFrame #
struct H2ContinuationFrame {
pub:
stream_id u32
fragment []u8
end_headers bool
}
struct H2DataFrame #
struct H2DataFrame {
pub:
stream_id u32
data []u8 // payload with any padding stripped
end_stream bool
// flow_size is the full received payload length (pad-length byte + data +
// padding) that counts toward flow control (RFC 7540 6.9.1). For outgoing
// frames it is left 0 and unused; the parser sets it for received frames.
flow_size int
}
struct H2FrameHeader #
struct H2FrameHeader {
pub:
length u32 // 24-bit payload length
typ u8
flags u8
stream_id u32 // 31-bit; the reserved bit is ignored on read
}
H2FrameHeader is the 9-byte header that precedes every HTTP/2 frame.
struct H2GoawayFrame #
struct H2GoawayFrame {
pub:
last_stream_id u32
error_code u32
debug_data []u8
}
struct H2HeaderField #
struct H2HeaderField {
pub:
name string
value string
}
H2HeaderField is a single HTTP/2 header field (a name/value pair). Field names are expected to be lowercase, as required by RFC 7540.
struct H2HeadersFrame #
struct H2HeadersFrame {
pub:
stream_id u32
fragment []u8 // HPACK header block fragment
end_stream bool
end_headers bool
// Priority information, present only when the PRIORITY flag is set.
has_priority bool
exclusive bool
stream_dep u32
weight u8
}
struct H2HpackDecoder #
struct H2HpackDecoder {
pub mut:
dyn_table H2DynTable
max_dynamic_size int = h2_hpack_default_table_size // upper bound we advertised to the peer
}
H2HpackDecoder decodes HPACK header blocks into header field lists, maintaining the dynamic table across calls on the same connection.
fn (H2HpackDecoder) decode #
fn (mut d H2HpackDecoder) decode(block []u8) ![]H2HeaderField
decode parses one HPACK header block and returns its header fields, updating the dynamic table as instructed by the block.
fn (H2HpackDecoder) set_max_dynamic_size #
fn (mut d H2HpackDecoder) set_max_dynamic_size(n int)
set_max_dynamic_size updates the maximum dynamic-table size the decoder will accept (i.e. the SETTINGS_HEADER_TABLE_SIZE value advertised to the peer), shrinking the table immediately if needed.
struct H2HpackEncoder #
struct H2HpackEncoder {
pub mut:
dyn_table H2DynTable
pending_max_table_size int = -1 // -1 = no pending update; ≥0 = emit size update on next encode
}
H2HpackEncoder encodes header field lists into HPACK header blocks. This encoder never adds entries to the dynamic table: it uses indexed representations for static-table hits and literal-without-indexing (or never-indexed, for sensitive headers) otherwise. That keeps encoder and decoder state trivially in sync while remaining fully interoperable.
fn (H2HpackEncoder) encode #
fn (mut e H2HpackEncoder) encode(fields []H2HeaderField) []u8
encode returns the HPACK header block for fields.
struct H2MuxConn #
struct H2MuxConn {
mut:
transport H2Transport
// close_transport is called exactly once during teardown and MUST close or
// interrupt the transport so the reader thread (blocked in transport.read())
// can exit. It is required (non-nil): the H2Transport interface has no close()
// of its own, so this is the only way to unblock the reader. new_h2_mux_conn
// panics on a nil closer.
close_transport fn () = unsafe { nil }
// --- guarded by wmu ---
wmu &sync.Mutex = sync.new_mutex()
encoder H2HpackEncoder
next_stream_id u32 = 1
handshaked bool
pending_settings_acks int // count of SETTINGS frames received before our preface; each needs one ACK
pending_ping_acks [][]u8 // PING frames received before our preface; each needs an ACK with the same data
peer_max_header_list_size u32 = max_u32 // peer SETTINGS_MAX_HEADER_LIST_SIZE (advisory, §6.5.2)
// --- guarded by fmu, fcv signals growth/death ---
fmu &sync.Mutex = unsafe { nil }
fcv &sync.Cond = unsafe { nil }
send_window i64 = i64(h2_default_initial_window)
peer_initial_window i64 = i64(h2_default_initial_window)
peer_max_frame u32 = h2_default_max_frame_size
fmu_dead bool // mirror of `closed` for blocked senders
// --- guarded by smu ---
smu &sync.Mutex = sync.new_mutex()
streams map[u32]&H2MuxStream
refs int = 1 // the owner's (pool's) reference; +1 per in-flight request
active_streams int
max_streams u32 = 100 // our own cap on concurrent streams
peer_max_streams u32 = max_u32
goaway bool
goaway_last u32
closed bool // the reader has exited; only the reader sets this
shutting_down bool
transport_torn_down bool // close_transport has been (or is being) called
conn_err string
idle_since time.Time
// --- guarded by recv_wmu ---
// Inbound flow-control: tracks how much more data the peer is allowed to
// send us (our advertised receive windows). Debited on DATA arrival,
// replenished when we send WINDOW_UPDATE. recv_wmu is always acquired
// solo — never while holding any other connection lock.
recv_wmu &sync.Mutex = sync.new_mutex()
conn_recv_window i64 = i64(h2_default_initial_window)
// --- reader-thread private (no locks) ---
decoder H2HpackDecoder
rbuf []u8
}
H2MuxConn is a multiplexed client-side HTTP/2 connection, safe for concurrent requests from multiple threads.
fn (H2MuxConn) can_take_new_request #
fn (mut c H2MuxConn) can_take_new_request() bool
can_take_new_request reports whether a new request may be admitted on this connection right now (it may still be refused later under smu).
fn (H2MuxConn) is_idle_expired #
fn (mut c H2MuxConn) is_idle_expired(idle_timeout time.Duration) bool
is_idle_expired reports whether this connection has had zero in-flight streams for longer than idle_timeout -- the h2 pool's counterpart to Transport.checkout's h1_idle expiry check (transport.v). Without this, h2_round_trip's fast path only checked can_take_new_request() (stream capacity), so a pooled h2 connection could sit idle indefinitely past the documented idle_timeout and still be reused, unlike h1 (Codex P2, vlang/v#27643 pullrequestreview-4631763931, discussion 3525390896).
fn (H2MuxConn) shutdown_when_idle #
fn (mut c H2MuxConn) shutdown_when_idle()
shutdown_when_idle asks the connection to retire: no new requests are admitted, and once no requests are in flight and the owner's reference is released, the transport is closed.
fn (H2MuxConn) release #
fn (mut c H2MuxConn) release()
release drops the owner's reference. The connection tears its transport down once the reader has exited and all references are gone.
fn (H2MuxConn) do #
fn (mut c H2MuxConn) do(req H2ClientRequest) !H2ClientResponse
do sends one request over the connection, concurrently with other streams, and returns its response. Errors carrying h2_err_retryable_code are safe to retry on a fresh connection.
struct H2PingFrame #
struct H2PingFrame {
pub:
ack bool
data []u8 // 8 opaque bytes
}
struct H2PriorityFrame #
struct H2PriorityFrame {
pub:
stream_id u32
exclusive bool
stream_dep u32
weight u8
}
struct H2PushPromiseFrame #
struct H2PushPromiseFrame {
pub:
stream_id u32
promised_stream_id u32
fragment []u8
end_headers bool
}
struct H2RstStreamFrame #
struct H2RstStreamFrame {
pub:
stream_id u32
error_code u32
}
struct H2Setting #
struct H2Setting {
pub:
id u16
value u32
}
struct H2SettingsFrame #
struct H2SettingsFrame {
pub:
ack bool
settings []H2Setting
}
struct H2UnknownFrame #
struct H2UnknownFrame {
pub:
header H2FrameHeader
payload []u8
}
H2UnknownFrame preserves a frame of an unrecognised type, which receivers must ignore (RFC 7540 Section 4.1) but may want to inspect or forward.
struct H2WindowUpdateFrame #
struct H2WindowUpdateFrame {
pub:
stream_id u32
window_size_increment u32
}
struct H3MuxConn #
struct H3MuxConn {
mut:
qmu &sync.Mutex = sync.new_mutex()
active_streams int
idle_since time.Time
}
H3MuxConn mirrors the real H3MuxConn's shape only as far as transport.v touches it: Transport.h3_conns holds &H3MuxConn, and close_idle/checkin/ evict_oldest_idle_locked read these fields and call these methods. The map stays empty without -d http3 (h3_round_trip errors before anything is pooled), so none of this runs -- it exists purely so transport.v type-checks.
fn (H3MuxConn) shutdown_when_idle #
fn (mut c H3MuxConn) shutdown_when_idle()
shutdown_when_idle is a no-op stub; see H3MuxConn above.
fn (H3MuxConn) release #
fn (mut c H3MuxConn) release()
release is a no-op stub; see H3MuxConn above.
struct Header #
struct Header {
pub mut:
// data map[string][]string
data [max_headers]HeaderKV
mut:
cur_pos int
// map of lowercase header keys to their original keys
// in order of appearance
// keys map[string][]string
}
Header represents the key-value pairs in an HTTP header. It holds at most max_headers fields. A field that does not fit is never stored: the writers that return a Result (add_custom, set_custom, add_custom_map, new_custom_header_from_map) fail with a HeaderLimitError, the others (add, set, add_map, new_header, new_header_from_map, join) drop it. A field value can not hold CR, LF or NUL (RFC 9110 section 5.5): the writers that return a Result fail with a HeaderValueError, the others store a space in place of each such byte.
fn (Header) free #
fn (mut h Header) free()
fn (Header) add #
fn (mut h Header) add(key CommonHeader, value string)
add appends a value to the header key. The value is dropped if the header already holds max_headers fields. CR, LF and NUL can not be part of a field value (RFC 9110 section 5.5): each of them is stored as a space.
fn (Header) add_custom #
fn (mut h Header) add_custom(key string, value string) !
add_custom appends a value to a custom header key. This function will return an error if the key contains invalid header characters, and a HeaderLimitError if the header already holds max_headers fields. The key is rejected here because it is not checked again when the header is sent: a name with a CR, LF, colon or space in it would otherwise be written to the connection as it is, and could add or split header fields there. To get the canonical form of a name that may be invalid, use canonical_header_key, which never fails. A value that contains CR, LF or NUL is rejected with a HeaderValueError.
fn (Header) add_map #
fn (mut h Header) add_map(kvs map[CommonHeader]string)
add_map appends the value for each header key. CR, LF and NUL in a value are replaced by a space, see add.
fn (Header) add_custom_map #
fn (mut h Header) add_custom_map(kvs map[string]string) !
add_custom_map appends the value for each custom header key. It returns a HeaderValueError for a value that contains CR, LF or NUL.
fn (Header) set #
fn (mut h Header) set(key CommonHeader, value string)
set sets the key-value pair. This function will clear any other values that exist for the CommonHeader. A key that is not present yet is dropped if the header already holds max_headers fields. CR, LF and NUL in the value are replaced by a space, see add.
fn (Header) set_custom #
fn (mut h Header) set_custom(key string, value string) !
set_custom sets the key-value pair for a custom header key. This function will clear any other values that exist for the header. This function will return an error if the key contains invalid header characters, and a HeaderLimitError if the key is not present yet and the header already holds max_headers fields. As in add_custom, the key is rejected here because it is not checked again when the header is sent. A value that contains CR, LF or NUL is rejected with a HeaderValueError.
fn (Header) delete #
fn (mut h Header) delete(key CommonHeader)
delete deletes all values for a key.
fn (Header) delete_custom #
fn (mut h Header) delete_custom(key string)
delete_custom deletes all values for a custom header key.
fn (Header) contains #
fn (h Header) contains(key CommonHeader) bool
contains returns whether the header key exists in the map.
fn (Header) contains_custom #
fn (h Header) contains_custom(key string, flags HeaderQueryConfig) bool
contains_custom returns whether the custom header key exists in the map.
fn (Header) get #
fn (h Header) get(key CommonHeader) ?string
get gets the first value for the CommonHeader, or none if the key does not exist.
fn (Header) get_custom #
fn (h Header) get_custom(key string, flags HeaderQueryConfig) ?string
get_custom gets the first value for the custom header, or none if the key does not exist.
fn (Header) starting_with #
fn (h Header) starting_with(key string) !string
starting_with gets the first header starting with key, or none if the key does not exist.
fn (Header) values #
fn (h Header) values(key CommonHeader) []string
values gets all values for the CommonHeader.
fn (Header) custom_values #
fn (h Header) custom_values(key string, flags HeaderQueryConfig) []string
custom_values gets all values for the custom header.
fn (Header) keys #
fn (h Header) keys() []string
keys gets all header keys as strings
fn (Header) unique_keys #
fn (h Header) unique_keys() []string
unique_keys gets header names deduplicated case-insensitively, retaining the first spelling. Use this when serializing HTTP fields.
fn (Header) render #
fn (h Header) render(flags HeaderRenderConfig) string
render renders the Header into a string for use in sending HTTP requests. All header lines will end in \r\n The SP and HTAB bytes at the start and end of a value are not written, as in Go's Header.Write: they are not part of a field value (RFC 9110 section 5.5). get and values still return the value as it was stored.
fn (Header) render_into_sb #
fn (h Header) render_into_sb(mut sb strings.Builder, flags HeaderRenderConfig)
render_into_sb works like render, but uses a preallocated string builder instead. This method should be used only for performance critical applications.
fn (Header) join #
fn (h Header) join(other Header) Header
join combines two Header structs into a new Header struct. The fields of other that do not fit in max_headers are dropped. CR, LF and NUL in a value of other are replaced by a space, see add.
fn (Header) str #
fn (h Header) str() string
str returns the headers string as seen in HTTP/1.1 requests. Key order is not guaranteed.
struct HeaderConfig #
struct HeaderConfig {
pub:
key CommonHeader
value string
}
struct HeaderLimitError #
struct HeaderLimitError {
Error
}
HeaderLimitError is the error for a field that does not fit in a Header, which holds at most max_headers fields. The request and response parsers return it for a message that has more header fields than that.
fn (HeaderLimitError) msg #
fn (err HeaderLimitError) msg() string
msg returns the description of the error.
struct HeaderQueryConfig #
struct HeaderQueryConfig {
pub:
exact bool
}
struct HeaderRenderConfig #
struct HeaderRenderConfig {
pub:
version Version
coerce bool
// canonicalize writes each field name in its canonical spelling instead of
// the stored one. A name that has a `CommonHeader` is spelled like its
// `CommonHeader.str()`: `etag` -> `ETag`, `www-authenticate` -> `WWW-Authenticate`.
// Any other name gets its first letter and each letter after a `-` in upper
// case and the rest in lower case: `x-request-id` -> `X-Request-Id`.
// It has no effect with `version: .v2_0`, which writes lower case names.
canonicalize bool
}
struct HeaderValueError #
struct HeaderValueError {
Error
pub:
header string // the name of the field
invalid_char u8 // the first forbidden byte of its value
}
HeaderValueError is the error for a field value that contains CR, LF or NUL. RFC 9110 section 5.5 forbids these bytes in a field value: written as they are, they would end the field line. The request and response parsers return it for a received field whose value has a bare CR or a NUL.
fn (HeaderValueError) msg #
fn (err HeaderValueError) msg() string
msg returns the description of the error. It names the field and the byte, and leaves the value out, which can be a credential.
struct MultiplePathAttributesError #
struct MultiplePathAttributesError {
Error
}
fn (MultiplePathAttributesError) msg #
fn (err MultiplePathAttributesError) msg() string
struct PostMultipartFormConfig #
struct PostMultipartFormConfig {
pub mut:
form map[string]string
files map[string][]FileData
header Header
}
struct Request #
struct Request {
mut:
cookies map[string]string
// ReverseProxy preserves the upstream representation and its encoding headers.
raw_response_body bool
pub mut:
version Version = .v1_1
method Method = .get
header Header
host string
data string
url string
user_agent string = 'v.http'
verbose bool
// remote_addr is the network address of the peer that sent this request,
// in `ip:port` form (`[ipv6]:port` for IPv6) -- the equivalent of Go's
// http.Request.RemoteAddr. `http.Server` fills it in for every request it
// hands to a `Handler`, over HTTP/1.1 and HTTP/2, plain and TLS, reading it
// straight off the accepted socket, so a client cannot forge it.
//
// It is empty for a request you build yourself to send with the client, and
// for one served by `veb`, which has its own server: use `ctx.ip()` there.
//
// A scoped IPv6 peer keeps its RFC 4007 zone, as in `[fe80::1%3]:8080`;
// without it a link-local address neither identifies an interface nor can
// be dialled back. The zone is the numeric interface index.
//
// When the server sits behind a reverse proxy this is the proxy's address.
// Recovering the original client then means trusting a header the proxy set
// (X-Forwarded-For, X-Real-Ip), which is only safe if nothing but that proxy
// can reach the server.
remote_addr string
user_ptr voidptr
proxy &HttpProxy = unsafe { nil }
// NOT implemented for ssl connections
// time = -1 for no timeout
read_timeout i64 = 30 * time.second
write_timeout i64 = 30 * time.second
validate bool = true // abort on an invalid certificate; false skips it, except HTTP/3 which always verifies
verify string
cert string
cert_key string
in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file
allow_redirect bool = true // whether to allow redirect
max_retries int = 5 // maximum number of retries required when an underlying socket error occurs
enable_http2 bool // opt in to HTTP/2 for HTTPS: advertise ALPN `h2, http/1.1` and use HTTP/2 if selected. HTTP/1.1 is the default. Ignored for plain http://. Progress callbacks and receive limits work on HTTP/2; on_progress fires per DATA frame payload.
enable_http3 bool // when true and the URL is https, use HTTP/3 (QUIC over UDP) for this request instead of the TCP-based HTTP/1.1/2 path. **Requires building with `-d http3`**: the QUIC/TLS/QPACK stack is compiled only on demand so ordinary net.http and veb builds do not pay its compile-time cost -- without the flag, an enable_http3 request fails fast with a "not compiled in" error. Opt-in only (default false) and, unlike enable_http2, never automatically probed: UDP has no fast-fail signal the way a closed TCP port does, so there is no automatic fallback to HTTP/1.1/2 if the h3 attempt fails or times out -- that decision is the caller's. Ignored for plain http://. req.cert/req.cert_key (mutual TLS) are not supported by this v1 HTTP/3 client; setting either alongside enable_http3 fails the request immediately rather than silently ignoring them. req.validate is also not honorable yet: net.quic's client TLS stack has no skip-verification mode at all (v1 limitation, unlike the h1/h2 ssl.SSLConn path), so certificate validation is always enforced regardless of this flag. **req.verify is effectively REQUIRED for HTTP/3 today**: net.quic's TLS 1.3 stack has no OS/default trust-store fallback of any kind (unlike the h1/h2 ssl.SSLConn path, which uses the platform's own trust store when req.verify is empty) -- leaving req.verify unset means every h3 request fails certificate verification against every real server, since there are no trust anchors to validate against at all. on_progress / on_progress_body / stop_copying_limit / stop_receiving_limit are not honored on the HTTP/3 path (see H3ClientRequest's own scope note).
disable_connection_reuse bool // opt out of the shared connection pool: open a fresh connection for this request, send `Connection: close`, and close the connection after the response (the pre-pooling behavior)
// callbacks to allow custom reporting code to run, while the request is running, and to implement streaming
on_redirect RequestRedirectFn = unsafe { nil }
on_progress RequestProgressFn = unsafe { nil }
on_progress_body RequestProgressBodyFn = unsafe { nil }
on_finish RequestFinishFn = unsafe { nil }
stop_copying_limit i64 = -1 // positive limits cap body bytes copied to the response. Progress callbacks continue until the full response is read
stop_receiving_limit i64 = -1 // after this many bytes are received, break out of the loop that reads the response, effectively stopping the request early. No more on_progress callbacks will be fired. The on_finish callback will fire.
}
Request holds information about an HTTP request (either received by a server or to be sent by a client)
fn (Request) reset #
fn (mut req Request) reset()
reset frees request-owned data and resets the request to default values.
fn (Request) add_header #
fn (mut req Request) add_header(key CommonHeader, val string)
add_header adds the key and value of an HTTP request header To add a custom header, use add_custom_header CR, LF and NUL in the value are replaced by a space, see Header.add
fn (Request) add_custom_header #
fn (mut req Request) add_custom_header(key string, val string) !
add_custom_header adds the key and value of an HTTP request header This method may fail if the key contains characters that are not permitted or the value contains CR, LF or NUL
fn (Request) remote_ip #
fn (req &Request) remote_ip() string
remote_ip returns just the IP part of req.remote_addr, without the port, for example 127.0.0.1 or ::1. A scoped IPv6 address keeps its zone (fe80::1%3), which is part of the address rather than of the port. It returns an empty string for a request that was not received by http.Server. See Request.remote_addr.
fn (Request) do #
fn (req &Request) do() !Response
do will send the HTTP request and returns http.Response as soon as the response is received It returns a HeaderValueError if user_agent contains CR, LF or NUL
fn (Request) referer #
fn (req &Request) referer() string
referer returns 'Referer' header value of the given request
struct Response #
struct Response {
pub mut:
body string
header Header
// trailers are emitted as a trailing HEADERS block by the HTTP/2 server
// path (net.http.h2_server.v) after the body, e.g. for `grpc-status`.
// Ignored by the HTTP/1.1 write path — H1 has no wire mechanism for this
// enabled here (chunked-encoding trailers are a separate, unimplemented
// feature), so fields set here simply do not appear on an H1 response.
trailers Header
status_code int
status_msg string
http_version string
}
Response represents the result of the request
fn (Response) bytes #
fn (resp Response) bytes() []u8
Formats resp to bytes suitable for HTTP response transmission
fn (Response) write_to #
fn (resp Response) write_to(mut sb strings.Builder)
write_to appends the raw HTTP response bytes (status line, headers and body) to sb, letting a caller serialize directly into an existing buffer instead of allocating a fresh one via bytes()/bytestr(). Since strings.Builder is a []u8, a server can reuse its per-connection write buffer as the target and avoid a per-response allocation and copy.
fn (Response) bytestr #
fn (resp Response) bytestr() string
Formats resp to a string suitable for HTTP response transmission
fn (Response) status #
fn (r Response) status() Status
status parses the status_code and returns a corresponding enum field of Status
fn (Response) set_status #
fn (mut r Response) set_status(s Status)
set_status sets the status_code and status_msg of the response
fn (Response) version #
fn (r Response) version() Version
version parses the version
fn (Response) set_version #
fn (mut r Response) set_version(v Version)
set_version sets the http_version string of the response
struct ResponseConfig #
struct ResponseConfig {
pub:
version Version = .v1_1
status Status = .ok
header Header
body string
}
struct ReverseProxy #
struct ReverseProxy {
pub mut:
config ReverseProxyConfig
}
ReverseProxy is an HTTP Handler that takes an incoming request and sends it to another server, proxying the response back to the client.
fn (ReverseProxy) handle #
fn (mut rp ReverseProxy) handle(req Request) Response
handle satisfies the http.Handler interface so ReverseProxy can be used directly as an http.Server handler.
fn (ReverseProxy) proxy #
fn (rp &ReverseProxy) proxy(mut req Request) !Response
proxy forwards req to the upstream server and returns the response.
struct ReverseProxyConfig #
struct ReverseProxyConfig {
pub mut:
// target is the upstream base URL to which requests will be proxied.
target urllib.URL
// director, if non-nil, modifies the outbound request before forwarding.
director DirectorFn = unsafe { nil }
// modify_response, if non-nil, inspects or alters the response returned from upstream.
modify_response ModifyResponseFn = unsafe { nil }
// error_handler produces an HTTP response when forwarding fails.
// Defaults to default_reverse_proxy_error_handler (502 Bad Gateway).
error_handler ErrorHandlerFn = unsafe { nil }
// preserve_host controls whether the incoming Host header is forwarded unchanged (true),
// or rewritten to target.host (false, default).
preserve_host bool
}
ReverseProxyConfig holds configuration options for ReverseProxy.
struct SSEEvent #
struct SSEEvent {
pub mut:
id string
event string
data string
retry int
}
SSEEvent is a single Server-Sent Events message, as defined by the event-stream format (https://html.spec.whatwg.org/multipage/server-sent-events.html). It is the shared SSE encoder for vlib: veb.sse, veb live-reload, and mcp stream responses all frame events through it, so every SSE producer in vlib emits byte-identical output for the same input.
fn (SSEEvent) encode #
fn (e SSEEvent) encode() string
encode serializes the event to the SSE wire format. Multi-line data is split into one data: line per line, as required by the specification: a bare newline inside data would otherwise terminate the field early and corrupt the event framing. CRLF and lone CR line endings are normalized to LF before splitting. id and event must be single-line, so line breaks are stripped from them to avoid injecting extra fields.
fn (SSEEvent) encode_into #
fn (e SSEEvent) encode_into(mut sb strings.Builder)
encode_into appends the wire format of the event to sb, reusing its buffer. Prefer it over encode when serializing several events into one payload, so only a single allocation backs the whole stream.
struct Server #
struct Server {
mut:
state ServerStatus = .closed
listener_opened bool
pub mut:
addr string = ':${default_server_port}'
handler Handler = DebugHandler{}
read_timeout time.Duration = 30 * time.second
write_timeout time.Duration = 30 * time.second
accept_timeout time.Duration = 30 * time.second
tls_handshake_timeout time.Duration = 30 * time.second // fallback handshake budget used when accept_timeout is zero or net.infinite_timeout; ignored on non-TLS servers
pool_channel_slots int = 1024
worker_num int = runtime.nr_jobs()
max_keep_alive_requests int = 100 // max requests per keep-alive connection (0 = unlimited)
listener net.TcpListener = net.TcpListener{
sock: net.TcpSocket{
Socket: net.Socket{
handle: -1
}
}
}
// TLS termination: when both `cert` and `cert_key` are set, the server
// accepts HTTPS connections instead of plain HTTP. With
// `in_memory_verification = true`, `cert` and `cert_key` are PEM strings;
// otherwise they are filesystem paths. Currently implemented on the
// default mbedtls backend; building with `-d use_openssl` reports a clear
// runtime error from listen_and_serve.
cert string
cert_key string
in_memory_verification bool
enable_http2 bool // opt in to HTTP/2. On the TLS listener, advertises ALPN `h2, http/1.1`; clients that select `h2` are served by the HTTP/2 driver, others keep the existing HTTP/1.1 path. On the plain listener, opts into prior-knowledge cleartext h2c (RFC 9113 §3.4): a connection whose first bytes are the HTTP/2 client preface is served by the HTTP/2 driver, others are parsed as HTTP/1.1 as before. A `Server` only ever runs one listener (see listen_and_serve), so one flag unambiguously covers both.
on_running fn (mut s Server) = unsafe { nil } // Blocking cb. If set, ran by the web server on transitions to its .running state.
on_stopped fn (mut s Server) = unsafe { nil } // Blocking cb. If set, ran by the web server on transitions to its .stopped state.
on_closed fn (mut s Server) = unsafe { nil } // Blocking cb. If set, ran by the web server on transitions to its .closed state.
show_startup_message bool = true // set to false, to remove the default `Listening on ...` message.
}
fn (Server) listen_and_serve #
fn (mut s Server) listen_and_serve()
listen_and_serve listens on s.addr, or uses an explicitly supplied TCP listener, and uses s.parse_and_respond to handle requests on incoming connections with s.handler.
fn (Server) stop #
fn (mut s Server) stop()
stop signals the server that it should not respond anymore.
fn (Server) close #
fn (mut s Server) close()
close immediately closes the port and signals the server that it has been closed. A successfully closed listener is cleared so listen_and_serve can reopen it.
fn (Server) status #
fn (s &Server) status() ServerStatus
status indicates whether the server is running, stopped, or closed.
fn (Server) wait_till_running #
fn (mut s Server) wait_till_running(params WaitTillRunningParams) !int
wait_till_running allows you to synchronise your calling (main) thread, with the state of the server (when the server is running in another thread). It returns an error, after params.max_retries * params.retry_period_ms milliseconds have passed, without that expected server transition.
struct SilentStreamingDownloader #
struct SilentStreamingDownloader {
pub mut:
path string
f os.File
}
SilentStreamingDownloader just saves the downloaded file chunks to the given path. It does no reporting at all.
Note: the folder part of the path should already exist, and has to be writable.
fn (SilentStreamingDownloader) on_start #
fn (mut d SilentStreamingDownloader) on_start(mut request Request, path string) !
on_start is called once at the start of the download.
fn (SilentStreamingDownloader) on_chunk #
fn (mut d SilentStreamingDownloader) on_chunk(request &Request, chunk []u8, already_received u64, expected u64) !
on_chunk is called multiple times, once per chunk of received content.
fn (SilentStreamingDownloader) on_finish #
fn (mut d SilentStreamingDownloader) on_finish(request &Request, response &Response) !
on_finish is called once at the end of the download.
struct StripPrefixHandler #
struct StripPrefixHandler {
pub:
prefix string
pub mut:
handler Handler
}
StripPrefixHandler wraps a handler, stripping a literal request path prefix.
fn (StripPrefixHandler) handle #
fn (mut s StripPrefixHandler) handle(req Request) Response
handle removes the prefix from matching URL paths, preserving query strings and escaped path suffixes. Unmatched or malformed URLs receive a 404 response.
struct TerminalStreamingDownloader #
struct TerminalStreamingDownloader {
SilentStreamingDownloader
mut:
start_time time.Time
past_time time.Time
past_received u64
}
TerminalStreamingDownloader is the same as http.SilentStreamingDownloader, but produces a progress line on stdout.
fn (TerminalStreamingDownloader) on_start #
fn (mut d TerminalStreamingDownloader) on_start(mut request Request, path string) !
on_start is called once at the start of the download.
fn (TerminalStreamingDownloader) on_chunk #
fn (mut d TerminalStreamingDownloader) on_chunk(request &Request, chunk []u8, already_received u64,
expected u64) !
on_chunk is called multiple times, once per chunk of received content.
fn (TerminalStreamingDownloader) on_finish #
fn (mut d TerminalStreamingDownloader) on_finish(request &Request, response &Response) !
on_finish is called once at the end of the download.
struct Transport #
struct Transport {
pub mut:
// max_idle_conns_per_host caps the idle keep-alive connections retained per
// pool key (origin + TLS configuration).
max_idle_conns_per_host int = 4
// max_idle_conns caps the total idle keep-alive connections kept across all
// pool keys, bounding file-descriptor use when many distinct origins are
// fetched. The least-recently-used idle connection is evicted on overflow.
// 0 disables the global cap.
max_idle_conns int = 100
// idle_timeout is how long an idle connection may sit in the pool before it
// is closed. Background cleanup runs at intervals of at most one second.
idle_timeout time.Duration = 90 * time.second
mut:
mu &sync.Mutex = sync.new_mutex()
h1_idle map[string][]&H1PooledConn
idle_reaper_running bool
// h2_conns holds the one pooled, multiplexed H2MuxConn per pool key (an h2
// connection multiplexes all concurrent requests to an origin, unlike the
// h1 pool's per-connection list). h2_dial_id tags which dial "owns" the
// current h2_conns[key] entry, so a superseded connection's own teardown
// closure can never evict a newer connection registered under the same
// key (see h2_dial_and_do in transport_h2.v). dialing singleflights the
// ALPN-probing dial itself: concurrent first requests to a fresh h2-enabled
// origin share one dial rather than each racing their own. key_proto
// memoizes the ALPN result (1 = http/1.1-only, 2 = h2) once known, so a
// key already proven http/1.1-only skips the h2 path entirely.
h2_conns map[string]&H2MuxConn
h2_dial_seq u64
h2_dial_id map[string]u64
dialing map[string]&H2DialCall
key_proto map[string]u8
// h3_conns holds the one pooled, multiplexed H3MuxConn per pool key,
// mirroring h2_conns exactly. h3_dial_id tags which dial "owns" the
// current h3_conns[key] entry, the same superseded-connection-can-
// never-evict-a-newer-one guard h2_dial_id provides. h3_dialing
// singleflights the QUIC dial itself, mirroring `dialing`'s role for
// h2 -- concurrent first requests to a fresh h3-enabled origin share
// one dial rather than each racing their own. No h3 counterpart to
// key_proto: h3 selection is 100% caller-declared via req.enable_http3,
// never auto-probed, so there is no ALPN-outcome to memoize.
h3_conns map[string]&H3MuxConn
h3_dial_seq u64
h3_dial_id map[string]u64
h3_dialing map[string]&H3DialCall
}
Transport holds the connection pools and reuse policy for HTTP requests. It is safe for concurrent use; fetch()/Request.do() share one process-global instance (see default_transport).
Idle connections expire in the background even when their origin is never revisited.
fn (Transport) close_idle #
fn (mut t Transport) close_idle()
close_idle closes every idle pooled connection held by this Transport, unconditionally — including a pooled h2 connection that is currently serving requests, mirroring how it already treats h1: this is the pool-wide flush used before fork()-style process handoffs and, in tests, to unblock a server thread reading a kept-alive connection at teardown (neither can wait out idle_timeout). In-flight requests are unaffected: shutdown_when_idle()+ release() only stop new admissions and drop the pool's own reference; drop_ref defers the actual teardown until any remaining in-flight streams finish (the same refcount mechanism H2MuxConn already relies on internally).
struct UnexpectedExtraAttributeError #
struct UnexpectedExtraAttributeError {
Error
pub:
attributes []string
}
fn (UnexpectedExtraAttributeError) msg #
fn (err UnexpectedExtraAttributeError) msg() string
struct WaitTillRunningParams #
struct WaitTillRunningParams {
pub:
max_retries int = 100 // how many times to check for the status, for each single s.wait_till_running() call
retry_period_ms int = 10 // how much time to wait between each check for the status, in milliseconds
}
WaitTillRunningParams allows for parametrising the calls to s.wait_till_running()
- README
- Constants
- fn canonical_header_key
- fn close_idle_connections
- fn default_reverse_proxy_error_handler
- fn default_transport
- fn delete
- fn detect_content_type
- fn dial_tcp_via_proxy
- fn download_file
- fn download_file_with_cookies
- fn download_file_with_progress
- fn dump_request
- fn dump_response
- fn error_response
- fn fetch
- fn get
- fn get_text
- fn h2_parse_frame
- fn h2_parse_frame_header
- fn h2_read_frame
- fn head
- fn is_cookie_domain_name
- fn method_from_str
- fn new_cookie_jar
- fn new_custom_header_from_map
- fn new_h2_conn
- fn new_h2_mux_conn
- fn new_header
- fn new_header_from_map
- fn new_http_proxy
- fn new_request
- fn new_response
- fn new_reverse_proxy
- fn new_single_host_reverse_proxy
- fn new_transport
- fn not_found
- fn parse_form
- fn parse_multipart_form
- fn parse_request
- fn parse_request_head
- fn parse_request_head_str
- fn parse_request_str
- fn parse_response
- fn patch
- fn post
- fn post_form
- fn post_form_with_cookies
- fn post_json
- fn post_multipart_form
- fn prepare
- fn put
- fn read_cookies
- fn redirect
- fn register_scheme
- fn safe_filename
- fn sanitize_cookie_value
- fn status_from_int
- fn strip_prefix
- fn unregister_scheme
- fn url_encode_form_data
- fn version_from_str
- fn CommonHeader.from
- fn H2ErrorCode.from
- fn Method.from
- fn SameSite.from
- fn ServerStatus.from
- fn Status.from
- fn Version.from
- interface Downloader
- interface H2Transport
- interface Handler
- type DirectorFn
- type ErrorHandlerFn
- type H2DataFn
- type H2Frame
- type HeaderKeyError
- type HttpProxy
- type ModifyResponseFn
- type RequestFinishFn
- type RequestProgressBodyFn
- type RequestProgressFn
- type RequestRedirectFn
- type SchemeHandlerFn
- enum CommonHeader
- enum H2ErrorCode
- enum Method
- enum SameSite
- enum ServerStatus
- enum Status
- enum Version
- struct C.TlsContext
- struct Cookie
- struct CookieJar
- struct DownloaderParams
- struct FetchConfig
- struct FileData
- struct H2ClientRequest
- struct H2ClientResponse
- struct H2Conn
- struct H2ContinuationFrame
- struct H2DataFrame
- struct H2FrameHeader
- struct H2GoawayFrame
- struct H2HeaderField
- struct H2HeadersFrame
- struct H2HpackDecoder
- struct H2HpackEncoder
- struct H2MuxConn
- struct H2PingFrame
- struct H2PriorityFrame
- struct H2PushPromiseFrame
- struct H2RstStreamFrame
- struct H2Setting
- struct H2SettingsFrame
- struct H2UnknownFrame
- struct H2WindowUpdateFrame
- struct H3MuxConn
- struct Header
- struct HeaderConfig
- struct HeaderLimitError
- struct HeaderQueryConfig
- struct HeaderRenderConfig
- struct HeaderValueError
- struct MultiplePathAttributesError
- struct PostMultipartFormConfig
- struct Request
- struct Response
- struct ResponseConfig
- struct ReverseProxy
- struct ReverseProxyConfig
- struct SSEEvent
- struct Server
- struct SilentStreamingDownloader
- struct StripPrefixHandler
- struct TerminalStreamingDownloader
- struct Transport
- struct UnexpectedExtraAttributeError
- struct WaitTillRunningParams