ply-network.h
Networking
Plywood provides a portable API for TCP/IP networking supporting both IPv4 and IPv6 addresses, with optional HTTP support.
Before using any networking functions, call Network::initialize(). When finished, call Network::shutdown().
Network
The Network class provides static methods for network initialization and hostname resolution.
static void Network::initialize(IPVersion ipVersion)Initializes the networking subsystem. Must be called before any other networking functions. Specify
IPVersion::V4orIPVersion::V6.static void Network::shutdown()Shuts down the networking subsystem and releases resources.
static IPAddress Network::resolveHostName(StringView hostName, IPVersion ipVersion)Resolves a hostname (e.g., "example.com") to an IP address using DNS.
static NetResult Network::lastResult()Returns the result code from the most recent network operation.
NetResult can take on any of the following values:
| Value | Description |
|---|---|
OK |
The operation completed successfully. |
NoSocket |
A socket could not be created because a system resource limit was reached. |
Unreachable |
The destination network could not be reached. |
Refused |
The remote host refused the connection. |
InUse |
The requested local address or port is already in use. |
NotListening |
TCPListener::stopListening() was called. |
AccessDenied |
The operation was denied by an operating system security policy. |
Unknown |
An unrecognized or unexpected networking error occurred. |
IPAddress
Represents an IP address (either IPv4 or IPv6).
u32 netOrdered[4] |
The raw address bytes in network byte order. For IPv4, only netOrdered[0] is used. |
IPVersion IPAddress::version() constReturns
IPVersion::V4orIPVersion::V6.bool IPAddress::isNull() constReturns
trueif this is a null/uninitialized address.static constexpr IPAddress IPAddress::localHost(IPVersion ipVersion)Returns the localhost address (
127.0.0.1for IPv4,::1for IPv6).static constexpr IPAddress IPAddress::fromIPv4(u32 netOrdered)Creates an IPv4 address from a 32-bit value in network byte order.
String IPAddress::toString() constReturns a human-readable string representation of the address.
static IPAddress IPAddress::fromString()Parses an IP address from a string.
TCPConnection
Represents an established TCP connection to a remote host. Use createInStream() and createOutStream() to obtain a Stream interface.
IPAddress remoteAddr |
The IP address of the remote host. |
u16 remotePort |
The port number of the remote host. |
Owned<Pipe> pipe |
A bidirectional pipe that handles both sending and receiving data. |
static Owned<TCPConnection> TCPConnection::connectTo(const IPAddress& address, u16 port)Establishes a TCP connection to the specified address and port. Returns null on failure.
Stream TCPConnection::createInStream()Creates a buffered stream for reading data from the connection.
Stream TCPConnection::createOutStream()Creates a buffered stream for writing data to the connection.
TCPListener
A TCPListener listens for incoming TCP connections on a specific port.
static Owned<TCPListener> TCPListener::create(const IPAddress& bindAddress, u16 port)Creates a TCP listener bound to the specified address and port. A null
bindAddresslistens on every local interface.bool TCPListener::isListening()Returns
trueifstopListening()has not been called.void TCPListener::stopListening()Immediately stops accepting new connections on the listening port. Existing connections are not closed. Future calls to
accept()return null after this. If called while another thread is waiting insideaccept(), the waiting thread will immediately return.Owned<TCPConnection> TCPListener::accept()Blocks until either a client connects or
stopListening()is called. If a client connects, the new connection is returned.
// Simple echo server
Network::initialize(IPVersion::V4);
Owned<TCPListener> listener = TCPListener::create({}, 8080);
while (true) {
Owned<TCPConnection> conn = listener->accept();
if (!conn) break;
Stream in = conn->createInStream();
Stream out = conn->createOutStream();
String line = readLine(in);
out.write(line);
out.write("\n");
out.flush();
}
Network::shutdown();
HTTPClient
HTTPClient is an optional class for making HTTP requests to remote servers.
To enable it, set the preprocessor definition PLY_WITH_HTTP_CLIENT=1 in your project's settings.
HTTPClient's member functions aren't thread-safe and are meant to be called from a single thread except for HTTPClient::wakeUp.
All member functions are designed to return as quickly as possible, so they can be used in an application's main loop without causing frame delays.
Requires libcurl to be linked and initialized.
#include <ply-network.h>
#include <curl/curl.h>
using namespace ply;
int main() {
// Initialize libcurl.
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK)
return 1;
// Perform an HTTP request.
Owned<HTTPClient> client = HTTPClient::create();
HTTPClient::Args args;
args.url = "https://plywood.dev";
args.callback = [](const HTTPClient::Event& event) {
if (auto* headers = event.as<HTTPClient::Headers>()) {
getStdOut().format("HTTP status: {}\n", headers->statusCode);
} else if (auto* data = event.as<HTTPClient::Data>()) {
getStdOut().write(data->bytes);
} else if (event.is<HTTPClient::End>()) {
getStdOut().write("Request complete\n");
} else if (auto* error = event.as<HTTPClient::Error>()) {
getStdErr().format("Request failed: {}\n", error->message);
}
};
client->beginRequest(std::move(args));
while (client->receiveResponse()) {
// Response data is delivered to `callback` inside receiveResponse().
}
client.clear();
// Shut down libcurl.
curl_global_cleanup();
return 0;
}
static Owned<HTTPClient> HTTPClient::create()Creates a new
HTTPClient.void HTTPClient::destroy(HTTPClient* httpClient)Destroys an
HTTPClient. Any in-progress request is immediately canceled.void HTTPClient::beginRequest(Args&& args)Starts a new request. Must not be called while a request is already in progress.
HTTPClient::Argshas the following data members. All members are moved from when the function is called, leaving the originalargsin an empty state.String urlThe URL to request. Map<String, String> headersHTTP headers to send with the request. String bodyThe request body. Functor<void(const Event&)> callbackReceives response events. bool useBundledCaCertIf true, verifies the peer using the bundled CA certificates. Iffalse, disables TLS verification and should only be used for trusted localhost endpoints. Default is true.void HTTPClient::cancelRequest()Cancels any request in progress. After this returns,
isRequestInProgress()returnsfalse. It's safe to callbeginRequest()on the sameHTTPClientagain after this.bool HTTPClient::isRequestInProgress() constReturns
truewhile a request is in progress.bool HTTPClient::receiveResponse(u32 timeOutMillis)Drives the request, delivering response events to the request's callback. If an event is available, it invokes the callback and returns immediately. If no data is available, it waits up to
timeOutMillisfor data to arrive, but can be interrupted by other threads callingwakeUp(). WhentimeOutMillisis 0, returns as soon as all available data is processed. Returnsfalseif no request is in progress;trueotherwise.void HTTPClient::wakeUp()Can be called from any thread. If
receiveResponse()is currently blocked in another thread, it returns immediately; otherwise the nextreceiveResponse()call returns immediately.
HTTPClient::Event
The
HTTPClient::Eventobject received by the response callback is a variant with the following subtypes:
HTTPClient::HeadersContains the HTTP status code and response headers. Header keys are stored in lowercase. HTTPClient::DataContains the next response body chunk. Its bytesmember is only valid for the duration of the callback.HTTPClient::EndIndicates that the HTTP transfer completed successfully. HTTP error status codes are still successful transfers. HTTPClient::ErrorContains a libcurl error message and terminates the event stream. It is never followed by End.A successful request produces
Headers, zero or moreDataevents and oneEndevent. A transport failure producesError, possibly afterHeadersandDataevents were already delivered.cancelRequest()does not generate an event.
HTTPServer
HTTPServer is an optional class for running a local HTTP server.
To enable it, set the preprocessor definition PLY_WITH_HTTP_SERVER=1 in your projects's settings.
No encryption capabilities are provided.
#include <ply-network.h>
using namespace ply;
void serverCallback(HTTPServer::Request& request) {
HTTPServer::Response response{HTTPServer::Response::OK};
*response.headers.insert("content-type").value = "text/plain";
String body = String::format("Request URI: {}\n", request.uri);
request.sendFullResponse(std::move(response), body);
}
int main() {
// Initialize the network.
Network::initialize(IPv4);
// Run a webserver.
HTTPServer::run({}, 8080, serverCallback);
// Shut down the network.
Network::shutdown();
return 0;
}
static void HTTPServer::run(const IPAddress& bindAddress, u16 port, const Functor<void(Request& request)>& requestHandler)When this function is called, the calling thread is blocked for as long as the server keeps running.
Network::initialize()must be called first.requestHandleris a user-provided callback function that handles individual HTTP requests. A nullbindAddresslistens on every local interface.
HTTPServer::Request
For each incoming HTTP request, the requestHandler is called with an HTTPServer::Request object,
which exposes the following public data members and member functions:
IPAddress clientAddr |
The remote TCP peer address. |
u16 clientPort |
The remote TCP peer port. |
String method |
The request method, such as GET, HEAD, POST, PUT or PATCH. |
String uri |
The raw request URI. |
String httpVersion |
The HTTP version token, such as HTTP/1.1. |
Map<String, String> headers |
Request headers indexed by lower-case header name. For example, keys contain "content-type" rather than "Content-Type". |
String body |
The complete request body. This string can contain arbitrary binary data and is not guaranteed to be null-terminated. |
void HTTPServer::Request::sendFullResponse(HTTPServer::Response&& response, StringView body)Sends a complete response. The
responseargument is moved from, leaving the original argument in an empty state. The underlying connection can be reused for additional requests when HTTP rules permit it.Stream HTTPServer::Request::beginStreamingResponse(HTTPServer::Response&& response)Sends response headers only and returns a TCP stream for writing the body. The connection is closed when the caller destroys the stream.
void HTTPServer::Request::sendGenericResponse(u32 responseCode)Writes a generic HTML error page with the given status code.
HTTPServer::Response
HTTPServer::Response is used as an argument to HTTPServer::Request member functions and has the following public data members:
u32 codeThe HTTP status code to emit. Default is 500 (InternalError). Map<String, String> headersResponse headers to emit.
HTTPServer::Response exposes constants for the following status codes:
Response::OK |
200 |
Response::PermanentRedirect |
301 |
Response::TemporaryRedirect |
302 |
Response::BadRequest |
400 |
Response::Unauthorized |
401 |
Response::Forbidden |
403 |
Response::NotFound |
404 |
Response::MethodNotAllowed |
405 |
Response::RequestTimeout |
408 |
Response::Conflict |
409 |
Response::UnprocessableContent |
422 |
Response::TooManyRequests |
429 |
Response::InternalError |
500 |
Response::BadGateway |
502 |
Response::ServiceUnavailable |
503 |
Response::GatewayTimeout |
504 |