Skip to content

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.

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_cookies #

fn download_file_with_cookies(url string, out_file_path string, cookies map[string]string) !

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 #

@[noinline]
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 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_form_with_cookies #

fn post_form_with_cookies(url string, data map[string]string, cookies map[string]string) !Response

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 read_cookies #

fn read_cookies(h Header, filter string) []&Cookie

Parses all "Cookie" values from the header h and returns the successfully parsed Cookies.

if filter isn't empty, only cookies of that name are returned

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 {}

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.

fn (CookieJar) set_cookies #

fn (mut j CookieJar) set_cookies(raw_url string, cookies []Cookie)

set_cookies stores the cookies returned for raw_url. Existing cookies with the same name, domain and path are replaced. Invalid, hostless and non-HTTP(S) URLs are ignored, as are invalid or unrelated domains.

fn (CookieJar) cookies #

fn (j CookieJar) cookies(raw_url string) []Cookie

cookies returns the cookies that should be sent with a request to raw_url. Invalid, hostless and non-HTTP(S) URLs return no cookies.

fn (CookieJar) cookies_header #

fn (j CookieJar) cookies_header(raw_url string) string

cookies_header returns a "Cookie" header value for raw_url.

struct DownloaderParams #

@[params]
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 #

@[heap]
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 #

@[heap]
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.

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 #

@[manualfree]
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 #

@[params]
struct HeaderQueryConfig {
pub:
	exact bool
}

struct HeaderRenderConfig #

@[params]
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 #

@[params]
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 #

@[manualfree]
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) cookie #

fn (req &Request) cookie(name string) ?Cookie

cookie returns the named cookie provided in the request or none if not found. If multiple cookies match the given name, only one cookie will be returned.

fn (Request) cookie_header_value #

fn (req &Request) cookie_header_value() string

cookie_header_value returns the value net.http sends in the Cookie header, combining request cookies and explicit Cookie field values.

A request cookie (from add_cookie, or from cookies of http.fetch) is written as one name=value pair. Its value goes through sanitize_cookie_value, which removes control characters, quotes, semicolons, backslashes and non-ASCII bytes. A cookie whose name is empty, or contains ;, =, a space, a control character or a non-ASCII byte, is left out: rewriting the name would send another cookie. Explicit Cookie field values of the header are appended unchanged.

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) cookies #

fn (r Response) cookies() []Cookie

cookies parses the Set-Cookie headers into Cookie objects

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 #

@[inline]
fn (mut s Server) stop()

stop signals the server that it should not respond anymore.

fn (Server) close #

@[inline]
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 #

@[inline]
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 #

@[heap]
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 #

@[params]
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()