Skip to content

Latest commit

History

47 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

HTTPServer

TestsLintSwift 6+macOS 26

A lightweight HTTP/1.1 server built from scratch in Swift using only the standard library and Darwin socket APIs, with no external frameworks.

The project implements a small but complete server that exposes a paginated user list API with filtering. It is written to demonstrate low level networking, HTTP protocol handling, and safe concurrency in Swift 6, while keeping the code testable end to end.

Features

FeatureDetail
TCP socket serversocket, bind, listen, accept on Darwin BSD sockets
HTTP/1.1 request parsingmethod, path, query parameters, and headers
Robust request readsaccumulates bytes until the message is complete, handling large and fragmented requests
Request size limitsrejects oversized requests with 413 Payload Too Large
Read timeoutsstalled clients are cut off with 408 Request Timeout
Paginationpage and size query parameters
Filteringstatus=active or status=inactive
Thread safe data storean actor guards the in memory users
Structured concurrencyone blocking acceptor thread, one detached task per connection
Graceful shutdownSIGINT closes the listening socket and exits cleanly
JSON encoding and decodingCodable models
Configurableport and host via environment variables
Command line executableruns as a standalone tool

Architecture

At a high level, main builds a ServerConfiguration, HTTPServer opens the listening socket and runs a blocking accept loop, and each connection is dispatched to its own detached Task. That task reads the request, frames the bytes with RequestFraming, routes it through RequestHandler, and serves data from the UserStore actor. For the full request lifecycle, component responsibilities, and the reasoning behind the concurrency and error handling choices, see docs/ARCHITECTURE.md.

Design decisions

A few choices are worth calling out, because they are the difference between a toy and something closer to production quality:

  • Reading uses a loop that accumulates bytes until a complete HTTP message has arrived, rather than a single fixed size read. The framing logic is a pure, byte level function (RequestFraming) so it is exhaustively unit tested without binding a port.
  • The blocking accept() call stays on a dedicated acceptor thread, and each connection runs in a detached Task. A TaskGroup was rejected because it would force the blocking accept into the cooperative thread pool.
  • A read timeout (SO_RCVTIMEO) and a request size cap protect the server from slow or oversized clients.
  • Startup failures throw a ServerError out of start() instead of calling exit from inside the server, so the entry point owns the exit behavior.
  • Shutdown state is held in a Mutex from the Synchronization module, so the server type is genuinely Sendable under Swift 6 strict concurrency.

API

List users

GET /users?page={page}&size={size}&status={status}
ParameterTypeDefaultDescription
pageInt1Current page
sizeInt10Items per page
statusStringnoneFilter by active or inactive

Response:

{
"metadata": {
"currentPage": 1,
"totalPages": 5,
"pageSize": 10
},
"users": [
{ "id": "u1", "name": "Orko 1", "status": "inactive" },
{ "id": "u2", "name": "Molly 2", "status": "active" }
]
}

Welcome

GET /
{ "message": "Welcome to the User API" }

Status codes

CodeWhen
200 OKA valid request was served
400 Bad RequestThe request could not be parsed or had invalid parameters
404 Not FoundThe path is not handled
405 Method Not AllowedThe method is not GET
408 Request TimeoutThe client stalled before completing the request
413 Payload Too LargeThe request exceeded the configured size limit
500 Internal Server ErrorThe response could not be encoded

Example requests

curl "http://localhost:8080/users?page=1&size=5"
curl "http://localhost:8080/users?page=2&size=10&status=active"
curl "http://localhost:8080/"

Sample console output

[2026-06-11 14:22:10] Server started on port 8080
[2026-06-11 14:22:15] Responding to GET /users?page=1&size=10&status=active with 200 OK
[2026-06-11 14:22:18] Invalid path: /invalid
[2026-06-11 14:22:20] Invalid query parameters: page=-1, size=10

Configuration

The server reads two environment variables, falling back to the defaults below.

VariableDefaultDescription
PORT8080The TCP port to listen on
HOST0.0.0.0The host the server reports itself as serving

Additional limits (request size cap, read timeout, backlog, and recv chunk size) are defined on ServerConfiguration with sensible defaults.

Build and run

Prerequisites

  • Xcode 26
  • macOS 26 or later

Compile

From the HTTPServer folder next to HTTPServer.xcodeproj:

swiftc *.swift -o httpserver

Run

./httpserver

Or run on a different port:

PORT=9090 ./httpserver

Press Ctrl + C to stop the server. It closes the listening socket and exits cleanly.

Testing

The suite is written with the Swift Testing framework and covers request framing, parsing, routing, response serialization, the socket read loop (over a socketpair), the data store, and concurrent access. Run it from Xcode, or from the command line:

xcodebuild test \
-project HTTPServer/HTTPServer.xcodeproj \
-scheme HTTPServer \
-destination 'platform=macOS'

Limitations

  • Handles GET requests only.
  • Responds with Connection: close, so there is no HTTP keep alive.
  • Data is held in memory and is not persisted.
  • No TLS.
  • No connection cap or backpressure under heavy load.

License

Released under the MIT License. © 2026 SarahUniverse

About

A from-scratch HTTP/1.1 server in Swift 6 using only the standard library and BSD sockets. Demonstrates low-level networking, protocol handling, structured concurrency, and a fully tested core.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages