Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,28 @@
|
||||
module file
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
pub struct Entity {
|
||||
os.FileInfo
|
||||
path string
|
||||
mod_time time.Time
|
||||
url string
|
||||
fname string
|
||||
}
|
||||
|
||||
fn path_to_entity(path string, uri_path string) Entity {
|
||||
pinfo := os.inode(path)
|
||||
mut uri_base := ''
|
||||
if uri_path.len > 0 {
|
||||
uri_base = '/${uri_path}'
|
||||
}
|
||||
fname := os.file_name(path)
|
||||
return Entity{
|
||||
FileInfo: pinfo
|
||||
path: path
|
||||
mod_time: time.unix(pinfo.mtime)
|
||||
url: '${uri_base}/${fname}'
|
||||
fname: fname
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
module file
|
||||
|
||||
import os
|
||||
import time
|
||||
import strings
|
||||
|
||||
const myexe = os.executable()
|
||||
const myexe_prefix = os.file_name(myexe.all_before_last('.'))
|
||||
|
||||
fn get_folder_index_html(requested_file_path string, uri_path string, filter_myexe bool) string {
|
||||
sw := time.new_stopwatch()
|
||||
mut files := os.ls(requested_file_path) or { [] }
|
||||
if filter_myexe {
|
||||
files = files.filter(!it.contains(myexe_prefix))
|
||||
}
|
||||
mut sb := strings.new_builder(files.len * 200)
|
||||
write_page_header(mut sb, uri_path)
|
||||
write_page_crumbs(mut sb, uri_path)
|
||||
write_page_table(mut sb, uri_path, requested_file_path, mut files)
|
||||
sb.writeln('<p>Server time: <b>${time.now().format_ss()}</b>, generated in <b>${sw.elapsed().microseconds():6}µs</b></p>')
|
||||
write_page_footer(mut sb, uri_path)
|
||||
return sb.str()
|
||||
}
|
||||
|
||||
fn write_page_header(mut sb strings.Builder, uri_path string) {
|
||||
// html boilerplate for the header
|
||||
sb.writeln('<!DOCTYPE html>')
|
||||
sb.writeln('<html lang="en">')
|
||||
sb.writeln('<head>')
|
||||
sb.writeln('<meta charset="utf-8">')
|
||||
sb.writeln('<title>Index of local folder ${uri_path}</title>')
|
||||
sb.writeln('<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />')
|
||||
sb.writeln('</head>')
|
||||
sb.writeln('<body>')
|
||||
}
|
||||
|
||||
fn write_page_footer(mut sb strings.Builder, _uri_path string) {
|
||||
// html boilerplate for the footer
|
||||
sb.writeln('</body>')
|
||||
sb.writeln('</html>')
|
||||
}
|
||||
|
||||
fn write_page_crumbs(mut sb strings.Builder, uri_path string) {
|
||||
crumbs := uri_path.split('/')
|
||||
mut crlinks := []string{cap: crumbs.len}
|
||||
for cridx, crumb in crumbs {
|
||||
cr_so_far := crumbs#[0..cridx + 1].join('/')
|
||||
// eprintln('> cr_so_far: ${cr_so_far:20} | crumb: ${crumb:20}')
|
||||
crlinks << '<a href="/${cr_so_far}">${crumb}</a>'
|
||||
}
|
||||
crlinks_text := crlinks.join(' / ')
|
||||
sb.writeln('<h2>Index of <a href="/">/</a> ${crlinks_text} :</h2>')
|
||||
}
|
||||
|
||||
fn write_page_table(mut sb strings.Builder, uri_path string, requested_file_path string, mut files []string) {
|
||||
files.sort()
|
||||
sb.writeln('<table>')
|
||||
sb.writeln('<tr>')
|
||||
sb.writeln('<th align="left" style="width: 100px">Size</th>')
|
||||
sb.writeln('<th align="left" style="width: 200px">Last modified</th>')
|
||||
sb.writeln('<th align="left">Name</th>')
|
||||
sb.writeln('</tr>')
|
||||
if uri_path == '' {
|
||||
sb.writeln('<tr>')
|
||||
sb.writeln('<td>---</td>')
|
||||
sb.writeln('<td>---</td>')
|
||||
sb.writeln('<td>---</td>')
|
||||
sb.writeln('</tr>')
|
||||
} else {
|
||||
sb.writeln('<tr>')
|
||||
sb.writeln('<td></td>')
|
||||
sb.writeln('<td></td>')
|
||||
sb.writeln('<td><a href="/${uri_path}/..">..</td>')
|
||||
sb.writeln('</tr>')
|
||||
}
|
||||
mut entities := []Entity{cap: files.len}
|
||||
for fname in files {
|
||||
path := os.join_path(requested_file_path, fname)
|
||||
entities << path_to_entity(path, uri_path)
|
||||
}
|
||||
entities.sort_with_compare(fn (a &Entity, b &Entity) int {
|
||||
if a.typ == b.typ {
|
||||
if a.fname < b.fname {
|
||||
return -1
|
||||
}
|
||||
if a.fname > b.fname {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if a.typ == .directory {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
for entity in entities {
|
||||
if entity.typ == .directory {
|
||||
sb.writeln('<tr>')
|
||||
sb.writeln('<td></td>')
|
||||
sb.writeln('<td>${entity.mod_time.format_ss()}</td>')
|
||||
sb.writeln('<td><a href="${entity.url}">${entity.fname}/</a></td>')
|
||||
sb.writeln('</tr>')
|
||||
} else if entity.typ == .symbolic_link {
|
||||
sb.writeln('<tr>')
|
||||
sb.writeln('<td>${entity.size}</td>')
|
||||
sb.writeln('<td>${entity.mod_time.format_ss()}</td>')
|
||||
sb.writeln('<td><a href="${entity.url}">${entity.fname}@</a></td>')
|
||||
sb.writeln('</tr>')
|
||||
} else {
|
||||
sb.writeln('<tr>')
|
||||
sb.writeln('<td>${entity.size}</td>')
|
||||
sb.writeln('<td>${entity.mod_time.format_ss()}</td>')
|
||||
sb.writeln('<td><a href="${entity.url}">${entity.fname}</a></td>')
|
||||
sb.writeln('</tr>')
|
||||
}
|
||||
}
|
||||
sb.writeln('</table>')
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
module file
|
||||
|
||||
import os
|
||||
import log
|
||||
import time
|
||||
import runtime
|
||||
import net.http
|
||||
import net.http.mime
|
||||
import net.urllib
|
||||
|
||||
@[params]
|
||||
pub struct StaticServeParams {
|
||||
pub mut:
|
||||
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`.
|
||||
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.
|
||||
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.
|
||||
on string = $d('http_on', 'localhost:4001') // on which address:port to listen for http requests.
|
||||
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()'`
|
||||
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
|
||||
}
|
||||
|
||||
// serve will start a static files web server.
|
||||
//
|
||||
// The most common usage is the following: `v -e 'import net.http.file; file.serve()'`
|
||||
// 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")'`
|
||||
// will serve all files inside the /tmp folder.
|
||||
//
|
||||
// Another example: `v -e 'import net.http.file; file.serve(folder: "~/Projects", on: ":5002")'`
|
||||
// will 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
|
||||
}
|
||||
|
||||
const no_such_file_doc = '<!DOCTYPE html><h1>no such file</h1>'
|
||||
|
||||
fn (mut h StaticHttpHandler) handle(req http.Request) http.Response {
|
||||
mut res := http.new_response(body: '')
|
||||
sw := time.new_stopwatch()
|
||||
mut url := urllib.query_unescape(req.url) or {
|
||||
log.warn('bad request; url: ${req.url} ')
|
||||
res.set_status(.bad_request)
|
||||
res.body = '<!DOCTYPE html><h1>url decode fail</h1>'
|
||||
res.header.add(.content_type, 'text/html; charset=utf-8')
|
||||
return res
|
||||
}
|
||||
defer {
|
||||
log.info('took: ${sw.elapsed().microseconds():6}µs, status: ${res.status_code}, size: ${res.body.len:9}, url: ${url}')
|
||||
}
|
||||
mut uri_path := url.all_after_first('/').all_before('?').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.set_status(.forbidden)
|
||||
res.body = '<h1>forbidden</h1>'
|
||||
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_doc
|
||||
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) {
|
||||
ipath := os.join_path_single(requested_file_path, h.params.index_file)
|
||||
if h.params.auto_index {
|
||||
if h.params.index_file == '' {
|
||||
body = get_folder_index_html(requested_file_path, uri_path, h.params.filter_myexe)
|
||||
} else {
|
||||
body = os.read_file(ipath) or {
|
||||
get_folder_index_html(requested_file_path, uri_path, h.params.filter_myexe)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
body = os.read_file(ipath) or {
|
||||
res.set_status(.not_found)
|
||||
no_such_file_doc
|
||||
}
|
||||
}
|
||||
} else {
|
||||
body = os.read_file(requested_file_path) or {
|
||||
res.set_status(.not_found)
|
||||
'not found'
|
||||
}
|
||||
mt := mime.get_mime_type(os.file_ext(requested_file_path).all_after_first('.'))
|
||||
content_type = mime.get_content_type(mt)
|
||||
}
|
||||
res.body = body
|
||||
res.header.add(.content_type, content_type)
|
||||
return res
|
||||
}
|
||||
Reference in new issue
Block a user