// The `http` package: a small HTTP/1.1 server, and the types a handler sees.
//
// It is a library with no `main`. The program that uses it is
// `examples/hello-http`:
//
// cd examples/hello-http
// nikaia run
// http: listening on 127.0.0.1:8080
//
// Shows: a package written in Nikaia and reached by a path, a function passed
// as a value, options with defaults after the `;`, and `catch`.
//
// It speaks `GET` and `POST`, reads a body by `Content-Length`, and closes each
// connection after one answer. There is no chunked transfer and no TLS. The
// socket is `std::net` and the request parsing is `std::http1`; this package
// decides what a request means. A program hands it one handler function, not
// a route table: the handler looks at the path and decides.
use std::net
use std::http1
use std::io
use std::fs
// Only two methods. `std::http1` answers `400` to any other method before a
// handler runs, so a `match` over this covers every request a handler can get.
pub enum Method {
Get,
Post,
}
// What a handler is given. The accessors return views into the bytes read from
// the connection, so nothing here allocates. A handler that wants to keep a
// value after it returns has to copy it.
pub struct Request {
// The head as `std::http1` parsed it. It is `pub` so a handler can ask it
// for things the accessors below do not cover, such as the header count.
pub head: http1::Head,
// Filled by `listen`. A handler reads it through `body()`; the field is
// `pub` for a program that wants to take the bytes and own them.
pub body: String,
}
impl Request {
pub fn method(ref self) -> Method {
if self.head.method() == "POST" {
return Method::Post
}
return Method::Get
}
// The method as the client wrote it, for a log.
pub fn method_text(ref self) -> String {
return self.head.method()
}
// The path without the query string. It is not decoded: a `%20` stays
// `%20`.
pub fn path(ref self) -> String {
return self.head.path()
}
// The whole request target as the client wrote it, for a log.
pub fn target(ref self) -> String {
return self.head.target()
}
// What the client sent after the head, empty if it sent nothing. Treat it
// as untrusted: it came off a socket.
pub fn body(ref self) -> String {
return self.body
}
// One query parameter, or nothing if the query does not have it. The `?`
// in the return type is what allows "nothing". Not decoded either.
pub fn query(ref self, name: ref String) -> String? {
return self.head.query(name)
}
// One header, or nothing if the client did not send it. The name is
// matched case-insensitively, as HTTP requires.
pub fn header(ref self, name: ref String) -> String? {
return self.head.header(name)
}
}
// What a handler gives back: a status, a content type and a body.
pub struct Response {
pub status: i32,
pub content_type: String,
pub body: Body,
}
// What a response carries (ADR-289 D18, D22, D23): text the program made,
// bytes it holds, or a file it never read.
pub enum Body {
Text(String),
Bytes(Bytes),
File(File),
}
// **A file as a response body, which the program never reads** (ADR-289 D23,
// D27, D28). The file is looked at when this is made: its size becomes the
// `content-length`, a file that is not there is a `500`, and a name that
// leaves the root is a `404`. Nothing is read until the answer is sent.
pub struct File {
pub path: String,
pub root: fs::Root,
// `200`, or the status the file already decided: `404` or `500`.
pub status: i32,
pub size: i64,
}
impl File {
pub fn(path: String, root: fs::Root) -> File {
let mut status = 200
let mut size = 0
let known = fs::metadata(path, root) catch {
status = match error {
io::IoError::Outside(_) => 404
else => 500
}
return File { path: path.clone(), root: root, status: status, size: 0 }
}
if !known.is_file() {
status = 404
} else {
size = known.len()
}
return File { path: path.clone(), root: root, status: status, size: size }
}
// What the name's extension says the file is, and what is not named
// is bytes (ADR-289 D23).
pub fn content_type(ref self) -> String {
let name = self.path.to_lowercase()
if name.ends_with(".html") || name.ends_with(".htm") {
return "text/html; charset=utf-8"
} else if name.ends_with(".txt") || name.ends_with(".nika") || name.ends_with(".toml") {
return "text/plain; charset=utf-8"
} else if name.ends_with(".css") {
return "text/css; charset=utf-8"
} else if name.ends_with(".js") {
return "text/javascript; charset=utf-8"
} else if name.ends_with(".json") {
return "application/json"
} else if name.ends_with(".svg") {
return "image/svg+xml"
} else if name.ends_with(".png") {
return "image/png"
} else if name.ends_with(".jpg") || name.ends_with(".jpeg") {
return "image/jpeg"
}
return "application/octet-stream"
}
}
impl Response {
// A `200` with a plain-text body.
pub fn text(body: String) -> Response {
return Response { status: 200, content_type: "text/plain", body: Body::Text(body) }
}
// A `200` with bytes the program holds (ADR-289 D22).
pub fn bytes(body: Bytes) -> Response {
return Response { status: 200, content_type: "application/octet-stream", body: Body::Bytes(body) }
}
// A file, answered as `File` decided: `200` with its type, or the status
// it already knows (ADR-289 D23, D27, D28).
pub fn file(file: File) -> Response {
if file.status != 200 {
let status = file.status
return Response { status: status, content_type: "text/plain", body: Body::Text(status_line(status)) }
}
let content_type = file.content_type()
return Response { status: 200, content_type: content_type, body: Body::File(file) }
}
// A `404`, for a path the handler does not answer.
pub fn not_found() -> Response {
return Response { status: 404, content_type: "text/plain", body: Body::Text("not found") }
}
}
// How long the body is, as `content-length` says it.
fn length_of(body: ref Body) -> i64 {
match body {
Body::Text(text) => text.len()
Body::Bytes(bytes) => bytes.len()
Body::File(file) => file.size
}
}
// The status line and the headers, as the text that goes on the wire before
// the body.
pub fn render_head(response: ref Response) -> String {
let status = status_line(response.status)
let length = length_of(response.body)
return f"HTTP/1.1 {status}\r\ncontent-type: {response.content_type}\r\ncontent-length: {length}\r\n\r\n"
}
// The head and a text body, as the text that goes on the wire. A body of
// bytes or a file is written after the head by `send`.
pub fn render(response: ref Response) -> String {
let head = render_head(response)
match response.body {
Body::Text(text) => f"{head}{text}"
else => head
}
}
// The answer, written to the connection. What the client sees was settled by
// the head: a file that fails to read after it closes the connection short,
// and the failure is logged (ADR-289 D27).
fn send(mut connection: net::Connection, response: ref Response) throws {
connection.write(render_head(response))
match response.body {
Body::Text(text) => connection.write(text)
Body::Bytes(bytes) => connection.write(bytes)
Body::File(file) => {
let read = fs::read(file.path, file.root) catch {
println(f"http: {file.path} could not be read after its head was sent: {error}")
return
}
connection.write(read)
}
}
}
// The reason phrase for each status this package sends.
pub fn status_line(status: i32) -> String {
if status == 200 {
return "200 OK"
} else if status == 404 {
return "404 Not Found"
} else if status == 400 {
return "400 Bad Request"
} else if status == 408 {
return "408 Request Timeout"
} else if status == 413 {
return "413 Content Too Large"
} else if status == 431 {
return "431 Request Header Fields Too Large"
} else {
return "500 Internal Server Error"
}
}
// The server. It takes one connection at a time: read the request, run the
// handler, send the answer, close. A slow client holds up everyone else, and
// `head_wait` limits how long. The limits are options after the `;`, so a
// program names only the ones it changes.
//
// Nothing here waits in a special way. `accept` and `read` are plain calls
// with no `async` or `await`.
pub fn listen(
at: String,
handler: fn(Request) -> Response;
body_cap: i64 = 1048576,
head_cap: i64 = 16384,
head_wait: i64 = 10,
connections: i64 = 0
) throws {
let listener = net::listen(at)
let bound = listener.address()
println(f"http: listening on {bound}")
let mut served = 0
// `connections: 0` means serve forever. Any other number stops after that
// many connections.
while connections == 0 || served < connections {
let mut connection = listener.accept()
answer(connection, handler; body_cap: body_cap, head_cap: head_cap, head_wait: head_wait)
served = served + 1
}
}
// One connection, from its first byte to its close.
//
// A bad request is answered with a status instead of thrown, because it is
// one client's problem and must not stop the server. `catch` is
// where that happens; inside it, `error` is what was thrown.
//
// `mut connection` in the signature lends the connection for changing. The
// call site writes nothing extra.
fn answer(
mut connection: net::Connection,
handler: fn(Request) -> Response;
body_cap: i64 = 1048576,
head_cap: i64 = 16384,
head_wait: i64 = 10
) {
let mut buffer = http1::Buffer()
// Read until the head ends. `head_end` returns `0` for "not yet" and throws
// once the head is over `head_cap`. `head_wait` is how many reads a client
// gets, so one that sends a byte at a time cannot hold the server forever.
let mut ended = 0
let mut reads = 0
while ended == 0 && reads < head_wait {
let more = connection.read() catch { return }
if more.len() == 0 {
// The client went away. There is nobody to answer.
return
}
buffer.take(more)
ended = buffer.head_end(head_cap) catch {
refuse(connection, 431, f"{error}")
return
}
reads = reads + 1
}
if ended == 0 {
refuse(connection, 408, "408 a request head that never ended")
return
}
let asked = buffer.head() catch {
refuse(connection, 400, f"{error}")
return
}
// The body limit is checked against the length the head announced, before
// any of the body is read. Refusing is cheap only at this point.
if asked.length() > body_cap {
refuse(connection, 413, "413 a body over the cap")
return
}
// Read the rest of the body. Some of it may have arrived with the head,
// so this counts from where the head ends.
while buffer.len() - asked.size() < asked.length() {
let more = connection.read() catch { return }
if more.len() == 0 { return }
buffer.take(more)
}
let body = buffer.text_from(asked.size()) catch {
refuse(connection, 400, f"{error}")
return
}
let answered = handler(Request { head: asked, body: body })
send(connection, answered) catch { return }
}
// Answers a request the server refuses. The text `http1` throws already starts
// with the status code, so it is passed on as the body.
fn refuse(mut connection: net::Connection, status: i32, why: String) {
let answered = Response { status: status, content_type: "text/plain", body: Body::Text(why.clone()) }
send(connection, answered) catch { return }
}
The file itself · all the examples