id: net.http title: Net.Http
Modern software frequently needs to communicate over the network, whether to fetch data, submit forms, interact with web services, or integrate with REST APIs. Most of these tasks are performed using HTTP, the protocol that underpins the World Wide Web.
While HTTP is conceptually simple, real-world usage quickly becomes more complex. Tasks such as authentication, session handling, secure connections, and streaming large amounts of data all require the client to manage state and configuration across multiple requests.
The Net.Http module provides a modern HTTP client API for BlitzMax, designed to support these real-world usage patterns in a clear and structured way.
At its core, HTTP is a request–response protocol. A client sends a request to a server, and the server replies with a response.
A request typically includes:
GET, POST, or PUTA response includes:
Importantly, HTTP itself is stateless. Each request is independent, and the protocol does not retain memory of previous interactions.
Although HTTP is stateless, most applications are not.
Consider a typical interaction with a web service:
Treating each HTTP request as an isolated operation makes these tasks difficult and error-prone.
Net.Http addresses this by introducing a reusable HTTP client. The client acts as a container for shared state and configuration, allowing multiple requests to be executed within a consistent context.
An HTTP client represents your application's presence when communicating with servers.
It is responsible for:
Clients are typically long-lived objects and are reused for multiple requests. This mirrors how real-world applications interact with services over time, rather than through isolated one-off calls.
In Net.Http, an HTTP client is an active component. Internally, it owns a processing thread that is responsible for driving request execution, handling asynchronous operations, and managing shared state such as cookies.
Before a client can be used, it must be explicitly started.
Framework Net.Http
Local client:THttpClient = New THttpClient
client.Start()
Calling Start() initialises the client's internal processing thread. Once started, the
client is ready to create and execute requests. Applications should generally start the
client early and keep it alive for as long as HTTP communication is required.
When the client is no longer needed, it should be shut down explicitly:
client.Shutdown()
This stops the processing thread and releases any resources associated with the client.
HTTP requests are created by the client itself. This ensures that each request is correctly associated with the client's configuration and execution context, including cookies, security settings, and execution behaviour.
The most general way to create a request is to ask the client to construct one for a given URL:
Local request:THttpRequest = client.NewRequest("https://example.com")
At this stage, no network activity has occurred. The request object represents a configurable HTTP operation that can be modified before it is sent. This allows applications to set headers, choose an HTTP method, or attach request content as needed.
For convenience, THttpClient also provides helper methods for creating common request types:
Local getReq:THttpRequest = client.Get("https://example.com/data")
Local postReq:THttpRequest = client.Post("https://example.com/upload")
Local putReq:THttpRequest = client.Put("https://example.com/resource")
These helper methods create requests with the appropriate HTTP method already selected,
reducing boilerplate for common use cases. Aside from the predefined method, the returned
request objects behave exactly like those created with NewRequest() and can be further
configured in the same way.
Once a request has been created and configured, it can be sent either synchronously or asynchronously, depending on the needs of the application.
The simplest way to execute a request is to send it synchronously. In this mode, the call blocks until the server responds.
Local response:THttpResponse = request.Send()
The returned response object provides access to the HTTP status code, response headers, and any returned data. Synchronous execution is useful for simple tools, scripts, or situations where blocking behaviour is acceptable.
For non-blocking operation, requests can be sent asynchronously. In this mode, SendAsync()
returns immediately, and the client's internal processing thread performs the network work in
the background.
Because the request completes later, asynchronous sending uses a completion callback. You provide an ICompleteListener, which will be notified once the request has finished (whether it succeeded or failed).
Type TMyListener Implements ICompleteListener
Method OnComplete(result:THttpResult)
' The result contains both the original request and the response (if any).
If result.IsSucceeded() Then
Print "Request succeeded. Status = " + result.response.GetStatus()
Else
Print "Request failed."
End If
End Method
End Type
With a listener defined, you can send a request asynchronously like this:
Local request:THttpRequest = client.NewRequest("https://example.com")
request.SendAsync(New TMyListener)
When the request completes, OnComplete() is called with a THttpResult instance.
THttpResult provides:
request — the original THttpRequest (useful when issuing multiple requests with one listener)response — the THttpResponse, if one was receivedIsSucceeded() / IsFailed() — convenience methods to test whether the operation produced a
successful HTTP responseA common pattern is to use the listener to branch on success/failure, and then inspect the response when successful (status code, headers, and body/stream), or perform error handling when not.
Because the callback is invoked by the client's processing thread, it is good practice to
keep OnComplete() fast and avoid long-running work there. If you need to update UI state
or perform expensive processing, queue that work back to your application's main logic.
A single THttpClient instance is typically reused for multiple requests. This allows shared state—such as cookies, connection settings, and security configuration—to persist across requests automatically.
The typical lifecycle is therefore:
This model closely reflects how real-world HTTP clients operate and provides a clean, predictable structure for managing network communication.
Once a request has been created, it can be configured before being sent. This configuration step is where most of the useful work happens: selecting an HTTP method, setting headers, supplying request data, and deciding how the response should be handled.
In Net.Http, request configuration is explicit and fluent. Configuration methods return the request itself, allowing options to be chained together in a readable way.
Many HTTP requests include a body. For example, POST and PUT requests often send form
data, JSON, or binary content to the server.
A request body can be provided in several forms:
The simplest approach is to supply a string body directly:
client.Post("https://example.com/api").SetContent("Hello, world!").Send()
For binary data, a byte array can be used instead:
Local data:Byte[] = LoadByteArray("payload.bin")
client.Post("https://example.com/upload").SetContent(data).Send()
For larger payloads or streaming data, a TStream can be supplied. This allows data to be read incrementally rather than being held entirely in memory.
Custom request body behaviour can be implemented by providing a TContent instance. This is useful when generating data dynamically or integrating with non-standard data sources.
By default, response data is collected in memory using an internal "memory sink". This is convenient for small responses and allows the response body to be accessed directly once the request completes.
For larger responses, it is often preferable to stream the response data directly to an output stream. This avoids buffering the entire response in memory.
Local out:TStream = WriteFile("download.dat")
client.Get("https://example.com/large-file").SetOutputStream(out).Send()
out.Close()
When an output stream is provided, response data is written to the stream as it is received. The response object still provides access to metadata such as status and headers.
After a request is sent synchronously, a THttpResponse object, is returned. The response contains information about the outcome of the request, including:
Applications typically begin by checking whether the response indicates success, and then process the returned data accordingly.
For asynchronous requests, response handling is performed via the completion listener, which receives a THttpResult containing both the request and the response.
Many request options can also be configured at the client level. When set on the client, these options become defaults for any new requests created by that client instance.
This makes it easy to establish common behaviour—such as headers, timeouts, or streaming preferences—once, while still allowing individual requests to override those settings when needed.
This layered configuration model allows applications to combine sensible defaults with fine-grained control over individual HTTP operations.
A key design principle in Net.Http is that configuration and execution are separate phases.
No network activity occurs until a request is explicitly sent using Send() or SendAsync().
This separation makes request behaviour easier to reason about, simplifies debugging, and encourages clear, maintainable HTTP code.
Not all HTTP communication involves small, in-memory payloads. Downloads, uploads, and API responses can be large, or even unbounded in size.
Net.Http integrates with BlitzMax's stream system to support streaming request and response bodies. This allows applications to:
Streaming support is particularly useful for large files, media content, or long-running HTTP connections.
HTTP is a stateless protocol: each request is independent, and the server does not automatically remember previous interactions. In practice, however, many applications require state to be preserved across multiple requests.
Cookies are the primary mechanism used by HTTP servers to maintain this state.
When a server wants to associate multiple requests with the same client, it sends one or more cookies in its response. These cookies typically contain identifiers such as session tokens or user preferences.
On subsequent requests, the client sends the relevant cookies back to the server, allowing the server to recognise the client and continue the interaction.
This mechanism is commonly used for:
In Net.Http, cookies are managed by the HTTP client. When a response includes cookies, the client stores them automatically and applies them to future requests when appropriate.
This means that, in most cases, applications do not need to handle cookies explicitly. Once a client has performed an initial request—such as a login—subsequent requests issued through the same client instance automatically include the necessary cookies.
client.Start()
client.Post("https://example.com/login").SetContent("user=alice&password=secret").Send()
' Cookies set by the login response are now stored in the client
client.Get("https://example.com/account").Send()
client.Shutdown()
In this example, the second request automatically includes any cookies set by the login response, allowing the server to recognise the authenticated session.
Cookies are not sent indiscriminately. Each cookie defines rules that control when it is included in a request, such as:
The client enforces these rules automatically, ensuring that cookies are only sent when they are valid and applicable to the target request.
Although automatic handling is sufficient for most use cases, applications may sometimes need to inspect or manipulate cookies directly. For example, an application might want to:
Net.Http exposes cookies as first-class objects, allowing applications to access their properties and manage them as needed. Cookies are stored in a client-associated cookie store, which provides controlled access to the client's cookie state.
Direct cookie manipulation should generally be the exception rather than the norm, but the API allows it when finer control is required.
Because cookies are stored on the client, reusing the same THttpClient instance is key to maintaining state across requests.
If a new client is created, it starts with an empty cookie store. This makes it easy to create isolated clients when separate sessions are required, such as when interacting with multiple accounts or services concurrently.
Many HTTP interactions take place over HTTPS, which uses TLS to provide encryption and authentication. Encryption protects data in transit, while authentication ensures that the client is communicating with the intended server.
Authentication relies on trust. Before a secure connection can be established, the client must decide which certificate authorities (CAs) it considers trustworthy.
When an HTTPS connection is established, the server presents a certificate identifying itself. That certificate is signed by a certificate authority, which acts as a trusted third party.
To verify the server's identity, the client checks that:
If any of these checks fail, the connection cannot be trusted and the request fails.
By default, Net.Http attempts to use the platform's native certificate authority store. These native stores provide access to the system-level trust configuration maintained by the operating system.
Using the system CA store allows HTTPS connections to behave consistently with other tools and applications on the same platform, and is suitable for most interactions with public web services.
Native trust stores are selected automatically and require no additional configuration in common cases.
In some situations, the default system trust store is not sufficient or appropriate. Examples include:
For these cases, Net.Http allows applications to explicitly configure which certificate authorities the client should trust.
Trust configuration is applied at the client level and must be set before the client is started.
The HTTP client provides several ways to supply custom certificate authorities, depending on how certificates are stored or obtained.
A CA bundle can be loaded from a file path:
Local client:THttpClient = New THttpClient
client.SetCACerts("certs/ca-bundle.pem")
client.Start()
Certificates can also be provided directly as raw data:
Local certs:Byte[] = LoadByteArray("ca.pem")
Local client:THttpClient = New THttpClient
client.SetCACerts(certs)
client.Start()
For streaming or dynamically generated certificate data, a TStream can be used:
Local stream:TStream = ReadFile("ca.pem")
Local client:THttpClient = New THttpClient
client.SetCACerts(stream)
client.Start()
Finally, advanced use cases can supply a TCAStore directly. This allows applications to construct or customise a certificate store programmatically, or to reuse an existing store instance.
Local store:TMyCAStore = New TMyCAStore
' configure store here
Local client:THttpClient = New THttpClient
client.SetCACerts(store)
client.Start()
Type TMyCAStore Extends TCAStore
' ... implement
End Type
In all cases, providing custom certificate authorities replaces the default native trust store for that client instance.
Because certificate authorities are configured on the client, different clients can operate under different trust models within the same application.
For example, one client might trust only public certificate authorities, while another trusts a private internal CA. Keeping trust configuration at the client level makes these boundaries explicit and easy to reason about.
Once trust is configured and the client has been started, HTTPS requests are created and sent in the same way as non-secure requests.
client.Get("https://example.com/secure-endpoint")
.Send()
Certificate verification occurs automatically during connection setup. If verification fails, the request fails and no response is produced.
Internally, the module is backed by Net.libcurl, a mature and widely used networking library. This provides a robust foundation for HTTP communication, including support for modern protocol features and secure transport.
The libcurl implementation details are abstracted behind the Net.Http API, allowing developers to focus on application logic rather than low-level networking concerns.
Provides classes and methods for making HTTP requests and handling responses.
| Type | Description |
|---|---|
| THttpField | An HTTP Field/Header |
| THttpFields | A collection of HTTP fields/headers. |
| THttpClientException | An exception representing an HTTP client error. |
| TUrl | HTTP URL representation and builder. |
| TUrlBuilder | HTTP URL builder. |
| TCAStore | Base class for Certificate Authority (CA) stores. |
| THttpCookie | An HTTP Cookie representation. |
| THttpCookieBuilder | HTTP Cookie builder. |
| THttpResult | HTTP Result. |
| THttpResponse | An HTTP Response. |
| THttpRequest | HTTP Request. |
| THttpClient | HTTP Client for sending requests and receiving responses. |
| TContent | Abstract representation of HTTP request content. |
| TStringContent | String-based HTTP request content. |
| TStreamContent | Stream-based HTTP request content. |
| TBytePtrContent | Byte pointer-based HTTP request content. |
| TByteArrayContent | Byte array-based HTTP request content. |
| TBankContent | Bank-based HTTP request content. |
| TRetryPolicy | Retry policy configuration for HTTP requests. |
| Interface | Description |
|---|---|
| ICookie | HTTP Cookie interface |
| ICompleteListener | Listener interface for asynchronous HTTP request completion. |
| Enum | Description |
|---|---|
| EHttpMethod | HTTP methods. |
| EHttpHeader | HTTP headers. |
| EHttpAuthMethod | Authentication methods for HTTP requests. |
| EUrlCode | URL error codes. |
| EUrlPart | URL parts identifiers. |
| ECookieAttribute | Cookie attributes |