v2 / vlib / net / http / file / static_server.v
123 lines · 115 sloc · 4.82 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1module file
2
3import os
4import log
5import time
6import runtime
7import net.http
8import net.http.mime
9import net.urllib
10
11@[params]
12pub struct StaticServeParams {
13pub mut:
14 folder string = $d('http_folder', '.') // 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 . Customize with `-d http_folder=vlib/_docs`.
15 index_file string = $d('http_index_file', 'index.html') // A request for http://localhost:4001/ will map to `index.html`, if that file is present.
16 auto_index bool = $d('http_auto_index', true) // when an index_file is *not* present, a request for http://localhost:4001/ will list automatically all files in the folder.
17 on string = $d('http_on', 'localhost:4001') // on which address:port to listen for http requests.
18 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()'`
19 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
20 shutdown_after time.Duration = time.infinite // after this time has passed, the webserver will gracefully shutdown on its own
21}
22
23// serve will start a static files web server.
24//
25// The most common usage is the following: `v -e 'import net.http.file; file.serve()'`
26// will listen for http requests on port 4001 by default, and serve all the files in the current folder.
27//
28// Another example: `v -e 'import net.http.file; file.serve(folder: "/tmp")'`
29// will serve all files inside the /tmp folder.
30//
31// Another example: `v -e 'import net.http.file; file.serve(folder: "~/Projects", on: ":5002")'`
32// will expose all the files inside the ~/Projects folder, on http://localhost:5002/ .
33pub fn serve(params StaticServeParams) {
34 mut nparams := params
35 nparams.folder = os.norm_path(os.real_path(params.folder))
36 mut server := &http.Server{
37 handler: StaticHttpHandler{
38 params: nparams
39 }
40 addr: params.on
41 worker_num: params.workers
42 }
43 if params.shutdown_after != time.infinite {
44 spawn fn (params StaticServeParams, mut server http.Server) {
45 log.warn('This file server, will shutdown itself after ${params.shutdown_after}.')
46 time.sleep(params.shutdown_after)
47 log.warn('Graceful shutdown, because the file server started ${params.shutdown_after} ago.')
48 server.stop()
49 }(params, mut server)
50 }
51 log.warn('${@METHOD}, starting...')
52 server.listen_and_serve()
53 log.warn('${@METHOD}, done.')
54}
55
56// implementation details:
57
58struct StaticHttpHandler {
59 params StaticServeParams
60}
61
62const no_such_file_doc = '<!DOCTYPE html><h1>no such file</h1>'
63
64fn (mut h StaticHttpHandler) handle(req http.Request) http.Response {
65 mut res := http.new_response(body: '')
66 sw := time.new_stopwatch()
67 mut url := urllib.query_unescape(req.url) or {
68 log.warn('bad request; url: ${req.url} ')
69 res.set_status(.bad_request)
70 res.body = '<!DOCTYPE html><h1>url decode fail</h1>'
71 res.header.add(.content_type, 'text/html; charset=utf-8')
72 return res
73 }
74 defer {
75 log.info('took: ${sw.elapsed().microseconds():6}µs, status: ${res.status_code}, size: ${res.body.len:9}, url: ${url}')
76 }
77 mut uri_path := url.all_after_first('/').all_before('?').trim_right('/')
78 requested_file_path :=
79 os.norm_path(os.real_path(os.join_path_single(h.params.folder, uri_path)))
80 if !requested_file_path.starts_with(h.params.folder) {
81 log.warn('forbidden request; base folder: ${h.params.folder}, requested_file_path: ${requested_file_path}, ')
82 res.set_status(.forbidden)
83 res.body = '<h1>forbidden</h1>'
84 res.header.add(.content_type, 'text/html; charset=utf-8')
85 return res
86 }
87 if !os.exists(requested_file_path) {
88 res.set_status(.not_found)
89 res.body = no_such_file_doc
90 res.header.add(.content_type, 'text/html; charset=utf-8')
91 return res
92 }
93
94 mut body := ''
95 mut content_type := 'text/html; charset=utf-8'
96 if os.is_dir(requested_file_path) {
97 ipath := os.join_path_single(requested_file_path, h.params.index_file)
98 if h.params.auto_index {
99 if h.params.index_file == '' {
100 body = get_folder_index_html(requested_file_path, uri_path, h.params.filter_myexe)
101 } else {
102 body = os.read_file(ipath) or {
103 get_folder_index_html(requested_file_path, uri_path, h.params.filter_myexe)
104 }
105 }
106 } else {
107 body = os.read_file(ipath) or {
108 res.set_status(.not_found)
109 no_such_file_doc
110 }
111 }
112 } else {
113 body = os.read_file(requested_file_path) or {
114 res.set_status(.not_found)
115 'not found'
116 }
117 mt := mime.get_mime_type(os.file_ext(requested_file_path).all_after_first('.'))
118 content_type = mime.get_content_type(mt)
119 }
120 res.body = body
121 res.header.add(.content_type, content_type)
122 return res
123}
124