Lindenii Project Forge
add httpserv
use errors; use ev; use ev::server; use fmt; use io; use log; use memio; use net::http; use net::ip; use net::tcp; use net; use os; use unix::signal; type state = struct { loop: *ev::loop, exit: int, }; export fn main() void = { const loop = ev::newloop()!; defer ev::finish(&loop); const sock = match (ev::listen_tcp(&loop, ip::LOCAL_V4, 8080, tcp::reuseaddr)) { case let err: net::error => log::fatalf("Error: listen: {}", net::strerror(err)); case let err: errors::error => log::fatalf("Error: listen: {}", errors::strerror(err)); case let sock: *ev::file => yield sock; }; defer ev::close(sock); let state = state { loop = &loop, ... }; const server = server::http_serve(sock, &http_serve, &state); defer server::server_finish(server); const sig = ev::signal(&loop, &signal, signal::sig::INT, signal::sig::TERM)!; defer ev::close(sig); ev::setuser(sig, &state); log::println("Listening on 127.0.0.1:8080"); for (ev::dispatch(&loop, -1)!) void; os::exit(state.exit); }; fn signal(file: *ev::file, sig: signal::sig) void = { log::printfln("Exiting due to {}", signal::signame(sig)); const state = ev::getuser(file): *state; ev::stop(state.loop); }; fn http_serve( user: nullable *opaque, req: http::request, rw: http::response_writer ) void = { const state = user: *state; const (ip, port) = http::peeraddr(&rw); log::printfln("Serving from {}:{}", ip::string(ip), port); const buf = memio::dynamic(); defer io::close(&buf)!; fmt::fprintfln(&buf, "Method: {}", req.method)!; fmt::fprintfln(&buf, "Path: {}", req.target.path)!; fmt::fprintfln(&buf, "Fragment: {}", req.target.fragment)!; fmt::fprintfln(&buf, "Query: {}", req.target.query)!; fmt::fprintfln(&buf, "Headers:")!; http::write_header(&buf, &req.header)!; fmt::fprintln(&buf)!; io::copy(&buf, req.body)!; io::seek(&buf, 0, io::whence::SET)!; http::response_add_header(&rw, "Content-Type", "text/plain"); http::response_set_body(&rw, &buf); http::response_write(&rw)!; };
use ev; use bufio; use errors; use io; use memio; use net::http; use net::ip; use net::tcp; use net; use strings; use types; // A callback for an [[http_serve]] operation. export type http_servecb = fn(user: nullable *opaque, request: http::request, rw: http::response_writer) void; // Schedules an http serve operation on a socket. The user must free resources // when done with [[server_finish]] export fn http_serve( sock: *ev::file, cb: *http_servecb, user: nullable *opaque, ) *server = { const serv = newserver(sock, cb, user); ev::setuser(sock, serv); ev::accept(sock, &http_accept); return serv; }; fn http_accept(sock: *ev::file, r: (*ev::file | net::error)) void = { const server = ev::getuser(sock): *server; const sock = match (r) { case let sock: *ev::file => yield sock; case let err: net::error => // TODO handle it return; }; const client = newclient( server, sock, ); ev::setuser(client.sock, client); ev::read(client.sock, &http_read, client.rbuf); append(server.clients, client); ev::accept(server.sock, &http_accept); }; fn http_read(sock: *ev::file, r: (size | io::EOF | io::error)) void = { const client = ev::getuser(sock): *server_client; const n = match (r) { case let err: io::error => client_close(client); return; case io::EOF => client_close(client); return; case let n: size => yield n; }; io::seek(&client.buf, 0, io::whence::END)!; io::write(&client.buf, client.rbuf[..n])!; io::seek(&client.buf, 0, io::whence::SET)!; let scan = bufio::newscanner(&client.buf, types::SIZE_MAX); defer bufio::finish(&scan); let req = match (http::request_scan(&scan)) { case let req: http::request => yield req; case io::EOF => ev::read(client.sock, &http_read, client.rbuf); return; case let err: (http::protoerr | errors::unsupported | io::error) => client_close(client); return; }; defer http::parsed_request_finish(&req); memio::reset(&client.buf); const resp = http::response { version = (1, 1), status = http::STATUS_OK, reason = strings::dup(http::status_reason(http::STATUS_OK)), header = [], body = io::empty, }; defer io::close(resp.body)!; const cb = client.server.cb: *http_servecb; cb(client.server.user, req, http::response_writer { sock = ev::getfd(client.sock), resp = resp, writeto = &client.buf, }); client.wbuf = memio::buffer(&client.buf); ev::write(client.sock, &http_write, client.wbuf); }; fn http_write(sock: *ev::file, r: (size | io::error)) void = { const client = ev::getuser(sock): *server_client; const n = match (r) { case let err: io::error => client_close(client); return; case let n: size => yield n; }; static delete(client.wbuf[..n]); if (len(client.wbuf) != 0) { ev::write(client.sock, &http_write, client.wbuf); return; }; memio::reset(&client.buf); ev::read(client.sock, &http_read, client.rbuf); };
use ev; use io; use memio; use os; export type server = struct { sock: *ev::file, clients: []*server_client, cb: *opaque, user: nullable *opaque }; export type server_client = struct { sock: *ev::file, server: *server, rbuf: [os::BUFSZ]u8, wbuf: []u8, buf: memio::stream, }; export fn newserver(sock: *ev::file, cb: *opaque, user: nullable *opaque) *server = { return alloc(server { sock = sock, cb = cb, user = user, ... }); }; export fn server_finish(serv: *server) void = { for (let i = 0z; i < len(serv.clients); i += 1) { client_close(serv.clients[i]); i -= 1; }; free(serv); }; export fn newclient(serv: *server, sock: *ev::file) *server_client = { return alloc(server_client { server = serv, sock = sock, buf = memio::dynamic(), ... }); }; export fn client_close(client: *server_client) void = { const server = client.server; for (let i = 0z; i < len(server.clients); i += 1) { if (server.clients[i] == client) { delete(server.clients[i]); break; }; }; ev::close(client.sock); io::close(&client.buf)!; free(client); };