public class Server : Soup.Server { public string directory { get; construct; } private const int BUFFER_SIZE = 1024 * 1024; private void write_chunk (Soup.Message msg) { unowned GLib.InputStream stream = msg.get_data ("input-stream"); uint8[] buffer = new uint8[BUFFER_SIZE]; stream.read_async.begin (buffer, GLib.Priority.DEFAULT, null, (obj, res) => { try { buffer.length = (int) stream.read_async.end (res); if (buffer.length > 0) { msg.response_body.append_take ((owned) buffer); } else { msg.response_body.complete (); } } catch (GLib.Error e) { GLib.warning (e.message); msg.response_body.complete (); } this.unpause_message (msg); }); } private async void handle_async (Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { string filename = GLib.Path.build_filename (directory, path); GLib.File file = GLib.File.new_for_path (filename); GLib.FileInputStream stream; uint64 content_length = 0; this.pause_message (msg); try { GLib.FileInfo info = file.query_info ( string.joinv (",", { GLib.FileAttribute.STANDARD_SIZE, GLib.FileAttribute.STANDARD_CONTENT_TYPE }), 0); content_length = info.get_attribute_uint64 (GLib.FileAttribute.STANDARD_SIZE); msg.response_headers.append ("Content-type", info.get_attribute_as_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE)); stream = yield file.read_async (); } catch (GLib.Error e) { bool e_handled = false; if (e is GLib.IOError) { if (e is GLib.IOError.NOT_FOUND) { msg.set_status (Soup.Status.NOT_FOUND); e_handled = true; } } if (!e_handled) { msg.set_status (Soup.Status.INTERNAL_SERVER_ERROR); } msg.response_headers.append ("Content-type", "text/html"); string reason = GLib.Markup.escape_text (msg.reason_phrase); msg.response_body.append (Soup.MemoryUse.COPY, "%s

%s

\n".printf (reason, reason).data); this.unpause_message (msg); return; } msg.set_status (Soup.Status.OK); if (content_length > 0 && content_length < BUFFER_SIZE) { try { GLib.MappedFile mapped = new GLib.MappedFile (filename, false); unowned uint8[] data = (uint8[]) mapped.get_contents (); data.length = (int) mapped.get_length (); msg.response_headers.set_encoding (Soup.Encoding.CONTENT_LENGTH); msg.response_body.append (Soup.MemoryUse.TEMPORARY, data); /* This will keep the mapped file alive until the message is * destroyed, which allows us to use Soup.MemoryUse.TEMPORARY * above and avoid a copy. */ msg.set_data ("memory-mapped-file", (owned) mapped); this.unpause_message (msg); return; } catch (GLib.Error e) { } } msg.response_headers.set_encoding (Soup.Encoding.CHUNKED); msg.response_body.set_accumulate (false); msg.set_data ("input-stream", stream); msg.wrote_chunk.connect(write_chunk); write_chunk (msg); } private void handler (Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { this.handle_async.begin (server, msg, path, query, client); } construct { this.add_handler ("/", handler); } public Server (string directory, uint port = 8080) { GLib.Object (directory: directory, port: 8080); } } static int main (string[] args) { if (args.length < 2 || args.length > 3) { stderr.printf ("USAGE: %s dir [port]\nport defaults to 8080\n", args[0]); return -1; } Server server = new Server (args[1], (args.length >= 3) ? int.parse (args[2]) : 8080); server.run (); return 0; }