module file import os import log import time import runtime import net.http import net.http.mime @[params] pub struct StaticServeParams { pub mut: folder string = '.' // the folder, that will be used as a base for serving all static resources; If it was /tmp, then: http://localhost:4001/x.txt => /tmp/x.txt on string = 'localhost:4001' // on which address:port to listen for http requests workers int = runtime.nr_jobs() // how many worker threads to use for serving the responses, by default it is limited to the number of available cores; can be controlled with setting VJOBS shutdown_after time.Duration = time.infinite // after this time has passed, the webserver will gracefully shutdown on its own filter_myexe bool = true // whether to filter the name of the static file executable from the automatic folder listings for / . Useful with `v -e 'import net.http.file; file.serve()'` } // serve will start a static files web server. // // The most common usage is the following: // v -e 'import net.http.file; file.serve()' // which will listen for http requests on port 4001 by default, and serve all the files in the current folder. // // Another example: `v -e 'import net.http.file; file.serve(folder: "/tmp")` , same but will serve all files inside the /tmp folder. // Another example: `v -e 'import net.http.file; file.serve(folder: "~/Projects", on: ":5002")` , expose all the files inside the ~/Projects folder, on http://localhost:5002/ . pub fn serve(params StaticServeParams) { mut nparams := params nparams.folder = os.norm_path(os.real_path(params.folder)) mut server := &http.Server{ handler: StaticHttpHandler{ params: nparams } addr: params.on worker_num: params.workers } if params.shutdown_after != time.infinite { spawn fn (params StaticServeParams, mut server http.Server) { log.warn('This file server, will shutdown itself after ${params.shutdown_after}.') time.sleep(params.shutdown_after) log.warn('Graceful shutdown, because the file server started ${params.shutdown_after} ago.') server.stop() }(params, mut server) } log.warn('${@METHOD}, starting...') server.listen_and_serve() log.warn('${@METHOD}, done.') } // implementation details: struct StaticHttpHandler { params StaticServeParams } fn (mut h StaticHttpHandler) handle(req http.Request) http.Response { mut res := http.new_response(body: '') sw := time.new_stopwatch() defer { log.info('took: ${sw.elapsed().microseconds():6}µs, status: ${res.status_code}, size: ${res.body.len:9}, url: ${req.url}') } mut uri_path := req.url.all_after_first('/').trim_right('/') requested_file_path := os.norm_path(os.real_path(os.join_path_single(h.params.folder, uri_path))) if !requested_file_path.starts_with(h.params.folder) { log.warn('forbidden request; base folder: ${h.params.folder}, requested_file_path: ${requested_file_path}, ') res = http.new_response(body: '

forbidden

') res.set_status(.forbidden) res.header.add(.content_type, 'text/html; charset=utf-8') return res } if !os.exists(requested_file_path) { res.set_status(.not_found) res.body = '

no such file

' res.header.add(.content_type, 'text/html; charset=utf-8') return res } // mut body := '' mut content_type := 'text/html; charset=utf-8' if os.is_dir(requested_file_path) { body = get_folder_index_html(requested_file_path, uri_path, h.params.filter_myexe) } else { body = os.read_file(requested_file_path) or { res.set_status(.not_found) '' } mt := mime.get_mime_type(os.file_ext(requested_file_path).all_after_first('.')) content_type = mime.get_content_type(mt) } res = http.new_response(body: body) res.body = body res.header.add(.content_type, content_type) return res }