// 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 } }