v2 / vlib / veb / static_handler.v
193 lines · 177 sloc · 7.76 KB · 45545c2fda3dfafa31fb7341b31b786ad143e67d
Raw
1module veb
2
3import os
4
5pub interface StaticApp {
6mut:
7 static_files map[string]string
8 static_mime_types map[string]string
9 static_hosts map[string]string
10 static_prefixes []string
11 enable_static_gzip bool
12 enable_static_zstd bool
13 enable_static_compression bool
14 static_compression_max_size int
15 static_compression_mime_types []string
16 enable_markdown_negotiation bool
17}
18
19// StaticHandler provides methods to handle static files in your veb App
20pub struct StaticHandler {
21pub mut:
22 static_files map[string]string
23 static_mime_types map[string]string
24 static_hosts map[string]string
25 static_prefixes []string
26 // enable_static_gzip enables gzip compression for static files.
27 // Use this for gzip-only compression. For automatic zstd/gzip selection, use enable_static_compression.
28 // Default: false
29 enable_static_gzip bool
30 // enable_static_zstd enables zstd compression for static files.
31 // Use this for zstd-only compression. For automatic zstd/gzip selection, use enable_static_compression.
32 // Default: false
33 enable_static_zstd bool
34 // enable_static_compression enables automatic compression (zstd/gzip) for static files.
35 // When enabled, Veb will choose zstd over gzip when client supports both (better compression ratio).
36 // For gzip-only or zstd-only compression, use enable_static_gzip or enable_static_zstd instead.
37 // Default: false
38 enable_static_compression bool
39 // static_compression_max_size sets the maximum file size in bytes for auto-compression.
40 // Files larger than this threshold will not be auto-compressed.
41 // Manual `.zst`/`.gz` files are still served for allowed MIME types.
42 // Default: 1MB (1024*1024 bytes). Set to 0 to disable auto-compression completely.
43 // Auto-generated cache files are stored in os.cache_dir()/veb/static_compression/.
44 // If that cache directory is not writable, compressed content is served from memory as fallback.
45 static_compression_max_size int = 1048576
46 // static_compression_mime_types limits static compression to the listed MIME types.
47 // Manual `.zst`/`.gz` files and auto-generated compressed cache files are only used for matching types.
48 // Leave empty to preserve the default behavior and allow compression for all static MIME types.
49 static_compression_mime_types []string
50 // enable_markdown_negotiation allows the client sends Accept: text/markdown, then the server will serve .md files, if any.
51 // Default: false (for backward compatibility)
52 enable_markdown_negotiation bool
53}
54
55// scan_static_directory recursively scans `directory_path` and returns an error if
56// no valid MIME type can be found
57fn (mut sh StaticHandler) scan_static_directory(directory_path string, mount_path string, host string) ! {
58 files := os.ls(directory_path) or { panic(err) }
59 if files.len > 0 {
60 for file in files {
61 full_path := os.join_path(directory_path, file)
62 if os.is_dir(full_path) {
63 sh.scan_static_directory(full_path, mount_path.trim_right('/') + '/' + file, host)!
64 } else if file.contains('.') && !file.starts_with('.') && !file.ends_with('.') {
65 sh.host_serve_static(host, mount_path.trim_right('/') + '/' + file, full_path)!
66 }
67 }
68 }
69}
70
71// handle_static is used to mark a folder (relative to the current working folder)
72// as one that contains only static resources (css files, images etc).
73// If `root` is set the mount path for the dir will be in '/'
74// Usage:
75// ```v
76// os.chdir( os.executable() )?
77// app.handle_static('assets', true)
78// ```
79pub fn (mut sh StaticHandler) handle_static(directory_path string, root bool) !bool {
80 return sh.host_handle_static('', directory_path, root)!
81}
82
83// host_handle_static is used to mark a folder (relative to the current working folder)
84// as one that contains only static resources (css files, images etc).
85// If `root` is set the mount path for the dir will be in '/'
86// Usage:
87// ```v
88// os.chdir( os.executable() )?
89// app.host_handle_static('localhost', 'assets', true)
90// ```
91pub fn (mut sh StaticHandler) host_handle_static(host string, directory_path string, root bool) !bool {
92 if !os.exists(directory_path) {
93 return error('directory `${directory_path}` does not exist. The directory should be relative to the current working directory: ${os.getwd()}')
94 }
95 dir_path := directory_path.trim_space().trim_right('/')
96 mut mount_path := ''
97 if dir_path != '.' && os.is_dir(dir_path) && !root {
98 // Mount point hygiene, "./assets" => "/assets".
99 mount_path = '/' + dir_path.trim_left('.').trim('/')
100 }
101 sh.scan_static_directory(dir_path, mount_path, host)!
102 return true
103}
104
105// mount_static_folder_at - makes all static files in `directory_path` and inside it, available at http://server/mount_path
106// For example: suppose you have called .mount_static_folder_at('/var/share/myassets', '/assets'),
107// and you have a file /var/share/myassets/main.css .
108// => That file will be available at URL: http://server/assets/main.css .
109pub fn (mut sh StaticHandler) mount_static_folder_at(directory_path string, mount_path string) !bool {
110 return sh.host_mount_static_folder_at('', directory_path, mount_path)!
111}
112
113// host_mount_static_folder_at - makes all static files in `directory_path` and inside it, available at http://host/mount_path
114// For example: suppose you have called .host_mount_static_folder_at('localhost', '/var/share/myassets', '/assets'),
115// and you have a file /var/share/myassets/main.css .
116// => That file will be available at URL: http://localhost/assets/main.css .
117pub fn (mut sh StaticHandler) host_mount_static_folder_at(host string, directory_path string, mount_path string) !bool {
118 if mount_path == '' || mount_path[0] != `/` {
119 return error('invalid mount path! The path should start with `/`')
120 } else if !os.exists(directory_path) {
121 return error('directory `${directory_path}` does not exist. The directory should be relative to the current working directory: ${os.getwd()}')
122 }
123
124 dir_path := directory_path.trim_right('/')
125
126 trim_mount_path := mount_path.trim_left('/').trim_right('/')
127 sh.scan_static_directory(dir_path, '/${trim_mount_path}', host)!
128 return true
129}
130
131// Serves a file static
132// `url` is the access path on the site, `file_path` is the real path to the file, `mime_type` is the file type
133pub fn (mut sh StaticHandler) serve_static(url string, file_path string) ! {
134 sh.host_serve_static('', url, file_path)!
135}
136
137// Serves a file static
138// `url` is the access path on the site, `file_path` is the real path to the file
139// `host` is the host to serve the file from
140pub fn (mut sh StaticHandler) host_serve_static(host string, url string, file_path string) ! {
141 ext := os.file_ext(file_path).to_lower()
142
143 // Rudimentary guard against adding files not in mime_types.
144 if ext !in sh.static_mime_types && ext !in mime_types {
145 return error('unknown MIME type for file extension "${ext}". You can register your MIME type in `app.static_mime_types`')
146 }
147 sh.static_files[url] = file_path
148 sh.static_hosts[url] = host
149 sh.register_static_prefix(url)
150}
151
152fn static_prefix_for_url(url string) string {
153 if url.len == 0 || url[0] != `/` {
154 return url
155 }
156 mut slash_count := 0
157 for i in 0 .. url.len {
158 if url[i] == `/` {
159 slash_count++
160 if slash_count == 2 {
161 return url[..i + 1]
162 }
163 }
164 }
165 return url
166}
167
168fn (mut sh StaticHandler) register_static_prefix(url string) {
169 prefix := static_prefix_for_url(url)
170 if prefix !in sh.static_prefixes {
171 sh.static_prefixes << prefix
172 }
173}
174
175fn app_static_handler[A](app &A) StaticHandler {
176 $if A is $struct {
177 $for field in A.fields {
178 $if field.is_embed {
179 $if field.name == 'StaticHandler' {
180 return app.$(field.name)
181 } $else $if field.typ is $struct {
182 return app_static_handler(app.$(field.name))
183 }
184 }
185 }
186 }
187 return StaticHandler{
188 static_files: map[string]string{}
189 static_mime_types: map[string]string{}
190 static_hosts: map[string]string{}
191 static_prefixes: []string{}
192 }
193}
194