Skip to content

Native HTTP client and server

Link Symbian::Http in a generated SDK application:

target_link_libraries(my_app PRIVATE Symbian::Http)

Include symbian/api/connectivity/http.h. The client and listener use native RSocket; TcpClient::ConnectHost resolves an ASCII hostname with the native resolver. Operations use one absolute deadline, cancel and drain expired native requests, and return absl::Status or StatusOr. Run these blocking functions on an SDK worker, outside UI/active-scheduler callbacks. They create no threads or scheduler themselves. Declare NetworkServices in symbian.toml.

Load a page and set request headers

This complete function fetches http://example.com/, checks the response status, and returns its body. It includes the request completion and body-read loop. The result is deliberately buffered by the application; streaming callers can process each chunk immediately instead of appending it.

#include <string>

#include "absl/time/clock.h"
#include "symbian/api/connectivity/http.h"

absl::StatusOr<std::string> LoadPage() {
  namespace net = symbian::api::connectivity;
  const auto deadline = absl::Now() + absl::Seconds(20);
  symbian::http::RequestHead request;
  request.authority = "example.com";
  request.path = "/";
  request.headers = {{"user-agent", "MySymbianApp/1.0"},
                     {"accept", "text/html"}};
  auto exchange = net::HttpClient::ConnectHost("example.com", 80,
                                               std::move(request), deadline);
  if (!exchange.ok()) {
    return exchange.status();
  }
  auto status = (*exchange)->Finish(deadline);
  if (!status.ok()) {
    return status;
  }
  status = (*exchange)->ReceiveHeaders(deadline);
  if (!status.ok()) {
    return status;
  }
  if ((*exchange)->response().status != 200) {
    return absl::UnavailableError("The page did not return HTTP 200");
  }
  std::string page;
  while (true) {
    auto chunk = (*exchange)->Read(deadline);
    if (!chunk.ok()) {
      return chunk.status();
    }
    if (!chunk->has_value()) {
      return page;
    }
    page.append(**chunk);
  }
}

Headers are an ordered sequence of name/value pairs, so repeated fields retain wire order. Add another application header with request.headers.emplace_back("x-request-id", "123"). Inspect an incoming field with symbian::http::GetHeader(exchange->response().headers, "content-type") when exchange is a Connection. HTTP/2 names are normalized to lowercase. The connection owns Host, Content-Length, Transfer-Encoding and Connection; set authority and the body-length argument instead of supplying those fields. Values containing control characters are rejected.

For an upload, set request.method = "POST" and pass the known size as the body_length argument to ConnectHost, then call Write for each chunk before Finish. Pass std::nullopt for an unknown length: HTTP/1.1 uses chunked transfer encoding, and HTTP/2 uses DATA followed by END_STREAM. Each Write accepts at most 32 KiB. An early Finish on a fixed-length body is an error.

Serve one request with a streamed response

Run this function on a worker. It listens on loopback port 8080, reads the request body incrementally, then sends the response in two writes. Call it again, or keep a listener and accept successive exchanges, to serve more requests. Accepted connections own their sockets independently of the listener.

#include "absl/time/clock.h"
#include "symbian/api/connectivity/http.h"

absl::Status ServeOnce() {
  namespace net = symbian::api::connectivity;
  auto server = net::HttpServer::ListenIpv4({127, 0, 0, 1}, 8080);
  if (!server.ok()) {
    return server.status();
  }
  const auto deadline = absl::Now() + absl::Seconds(30);
  auto exchange = server->Accept(deadline);
  if (!exchange.ok()) {
    return exchange.status();
  }
  while (true) {
    auto chunk = (*exchange)->Read(deadline);
    if (!chunk.ok()) {
      return chunk.status();
    }
    if (!chunk->has_value()) {
      break;
    }
    // Process this request-body chunk here.
  }
  auto status = (*exchange)->SendHeaders(
      {200, {{"content-type", "text/plain; charset=utf-8"}}}, deadline);
  if (!status.ok()) {
    return status;
  }
  status = (*exchange)->Write("Hello ", deadline);
  if (!status.ok()) {
    return status;
  }
  status = (*exchange)->Write("from Symbian!\n", deadline);
  if (!status.ok()) {
    return status;
  }
  return (*exchange)->Finish(deadline);
}

Accept returns after the request head arrives. Inspect (*exchange)->request().method, .path and .headers to route it. Default response framing supports an unknown length. Supply a third SendHeaders argument for a fixed length; the connection verifies the number of bytes sent. HEAD, 204 and 304 responses suppress body writes. Trailers are available through trailers() after Read reports the end. Abort() closes the owned transport.

Choose HTTP/2 or TLS

Pass symbian::http::Protocol::kHttp2 to the client or server for cleartext prior-knowledge HTTP/2. An ordinary HTTP/1.1 client cannot talk to that listener. For HTTPS, establish TlsStream::Connect, then give the verified transport to Connection::Client; the TLS guide includes complete client and server examples. The WebSocket guide shows the same transport and shared HTTP/2 primitives carrying RFC 8441 duplex WebSockets.

Streaming contract and bounds

Read returns a nonempty string chunk, nullopt at a clean end, or an error. Chunk boundaries are transport boundaries, not application messages. Do not interpret a timeout, reset, truncated Content-Length or incomplete chunked body as success. TLS close-delimited bodies require close_notify; an unannounced TLS transport EOF is an error. Fixed-length and HTTP/2 bodies finish from their own framing without waiting for transport closure.

Default limit Value
Header block / number of fields 16 KiB / 64
Buffered inbound/outbound body bytes 64 KiB per direction
Total request or response body 32 MiB
One application write 32 KiB

Pass symbian::http::Limits to change the bounds. HTTP/2 returns flow-control credit when the application reads DATA; a paused consumer bounds buffering and stalls the sender. HTTP/1.1 reads only when the application asks. The request read half and response writer are separate, following A11's body-stream model. The synchronous facade handles one exchange per connection. Connection pooling, redirects, decompression, server push, HTTP/1 Upgrade and multiplexed exchanges are not implemented. Expect requests are rejected explicitly. Extended CONNECT uses the shared Http2 duplex primitive below the WebSocket facade.