HttpServer

struct HttpServer

A blocking HTTP/1.1 server with exact-path GET routing.

Routes are prepended, so later registrations shadow earlier equal paths. Each accepted connection serves one request of at most 8192 bytes and then closes. This is a minimal server, not a streaming or production HTTP stack.

Properties

let routes: RouteNode?

The head of the reverse-registration-order route chain.

Methods

mut func get(consume path: String, consume handler: () -> String) -> ()

Registers an exact-path GET handler; later registrations take precedence.

View Source
pub mut func get(consume path: String, consume handler: () -> String) -> () {
	let wrapped = RouteHandler(invoke: handler)
	let node = RouteNode(path: path, handler: wrapped, next: self.routes)
	self.routes = Optional.some(node)
	self.route_count = self.route_count + 1
	()
}

func dispatch(path: Substring) -> Response

Invokes the first exact-path route, returning a 404 response when none matches.

View Source
pub func dispatch(path: Substring) -> Response {
	route_walk(current: self.routes, path: path)
}

func render(response: Response) -> String

Serializes a response as HTTP/1.1 plain text.

Status text is defined for 200 and 404; all other codes use Error. The byte count, not grapheme count, becomes Content-Length, and every response advertises connection close.

View Source
pub func render(response: Response) -> String {
	let text: String = match response.status {
		200 -> "OK",
		404 -> "Not Found",
		_ -> "Error"
	}
	let status_line_a: String = "HTTP/1.1 ".add(response.status.show())
	let status_line_b: String = status_line_a.add(" ")
	let status_line_c: String = status_line_b.add(text)
	let status_line: String = status_line_c.add("\r\n")
	let content_type = "Content-Type: text/plain; charset=utf-8\r\n"
	let content_length_a: String = "Content-Length: ".add(
		response.body.byte_count.show()
	)
	let content_length: String = content_length_a.add("\r\n")
	let connection = "Connection: close\r\n"
	let wire_a: String = status_line.add(content_type)
	let wire_b: String = wire_a.add(content_length)
	let wire_c: String = wire_b.add(connection)
	let wire_d: String = wire_c.add("\r\n")
	wire_d.add(response.body)
}

func handle(raw_request: String) -> String

Handles one raw GET request; unsupported methods and unknown paths return 404.

View Source
pub func handle(raw_request: String) -> String {
	let method: Substring = self.extract_method(raw_request: raw_request)
	if method.equals_string("GET") == false { return self.render(
		response: Response(status: 404, body: "Not Found")
	) }
	let path: Substring = self.extract_path(raw_request: raw_request)
	self.render(response: self.dispatch(path: path))
}

func run(port: Int) -> ()

Runs forever on all IPv4 interfaces using a blocking accept loop.

Connections are handled serially, one request each, with an 8192-byte read cap. Socket setup and I/O errors currently surface only through raw host results; there is no graceful shutdown path.

View Source
pub func run(port: Int) -> () {
	#unsafe {
		let listener_fd = _io_socket(domain: AF_INET, socktype: SOCK_STREAM, proto: 0)
		_io_bind(fd: listener_fd, addr: 0, port: port)
		_io_listen(fd: listener_fd, backlog: 128)
		loop {
			let client_fd = _io_accept(fd: listener_fd)
			if client_fd < 0 {
				continue
			}
			let max_request_size = 8192
			let buf = _alloc<Byte>(count: max_request_size)
			let n = _io_read(fd: client_fd, buf: buf, count: max_request_size)
			if n <= 0 {
				_io_close(fd: client_fd)
				continue
			}
			let n = _io_clamp(count: n, capacity: max_request_size)
			let raw_request = String(
				base: ByteStorage(base: buf),
				byte_count: n,
				capacity: max_request_size
			)
			let wire = self.handle(raw_request: raw_request)
			_io_write(fd: client_fd, buf: wire.storage.base, count: wire.byte_count)
			_io_close(fd: client_fd)
		}
	}
}