v / vlib / net / http / server.v
333 lines · 295 sloc · 9.78 KB · c5ce895e263322da49a8c8d16c494d78667cde48
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module http
5
6import io
7import net
8import time
9import runtime
10// ServerStatus is the current status of the server.
11// .closed means that the server is completely inactive (the default on creation, and after calling .close()).
12// .running means that the server is active and serving (after .listen_and_serve()).
13// .stopped means that the server is not active but still listening (after .stop() ).
14
15pub enum ServerStatus {
16 closed
17 running
18 stopped
19}
20
21pub interface Handler {
22mut:
23 handle(Request) Response
24}
25
26pub const default_server_port = 9009
27
28pub const default_https_server_port = 9043
29
30pub struct Server {
31mut:
32 state ServerStatus = .closed
33 listener_opened bool
34pub mut:
35 addr string = ':${default_server_port}'
36 handler Handler = DebugHandler{}
37 read_timeout time.Duration = 30 * time.second
38 write_timeout time.Duration = 30 * time.second
39 accept_timeout time.Duration = 30 * time.second
40 tls_handshake_timeout time.Duration = 30 * time.second // fallback handshake budget used when accept_timeout is zero or net.infinite_timeout; ignored on non-TLS servers
41 pool_channel_slots int = 1024
42 worker_num int = runtime.nr_jobs()
43 max_keep_alive_requests int = 100 // max requests per keep-alive connection (0 = unlimited)
44 listener net.TcpListener
45
46 // TLS termination: when both `cert` and `cert_key` are set, the server
47 // accepts HTTPS connections instead of plain HTTP. With
48 // `in_memory_verification = true`, `cert` and `cert_key` are PEM strings;
49 // otherwise they are filesystem paths. Currently implemented on the
50 // default mbedtls backend; building with `-d use_openssl` reports a clear
51 // runtime error from listen_and_serve.
52 cert string
53 cert_key string
54 in_memory_verification bool
55 enable_http2 bool // opt in to HTTP/2 on the TLS listener: advertises ALPN `h2, http/1.1`. Clients that select `h2` are served by the HTTP/2 driver; clients that select `http/1.1` (or send no ALPN) keep the existing HTTP/1.1 path.
56
57 on_running fn (mut s Server) = unsafe { nil } // Blocking cb. If set, ran by the web server on transitions to its .running state.
58 on_stopped fn (mut s Server) = unsafe { nil } // Blocking cb. If set, ran by the web server on transitions to its .stopped state.
59 on_closed fn (mut s Server) = unsafe { nil } // Blocking cb. If set, ran by the web server on transitions to its .closed state.
60
61 show_startup_message bool = true // set to false, to remove the default `Listening on ...` message.
62}
63
64// listen_and_serve listens on the server port `s.port` over TCP network and
65// uses `s.parse_and_respond` to handle requests on incoming connections with `s.handler`.
66pub fn (mut s Server) listen_and_serve() {
67 if s.handler is DebugHandler {
68 eprintln('Server handler not set, using debug handler')
69 }
70
71 if s.cert != '' && s.cert_key != '' {
72 s.listen_and_serve_tls()
73 return
74 }
75
76 mut l := s.listener.addr() or {
77 eprintln('Failed getting listener address, err: ${err}')
78 return
79 }
80 if l.family() == net.AddrFamily.unspec {
81 listening_address := if s.addr == '' || s.addr == ':0' { 'localhost:0' } else { s.addr }
82 listen_family := net.AddrFamily.ip
83 // listen_family := $if windows { net.AddrFamily.ip } $else { net.AddrFamily.ip6 }
84 s.listener = net.listen_tcp(listen_family, listening_address) or {
85 eprintln('Listening on ${s.addr} failed, err: ${err}')
86 return
87 }
88 l = s.listener.addr() or {
89 eprintln('Failed getting listener address 2, err: ${err}')
90 return
91 }
92 }
93 s.addr = l.str()
94 s.listener_opened = true
95 s.listener.set_accept_timeout(s.accept_timeout)
96
97 // Create tcp connection channel
98 ch := chan &net.TcpConn{cap: s.pool_channel_slots}
99 // Create workers
100 mut ws := []thread{cap: s.worker_num}
101 for wid in 0 .. s.worker_num {
102 ws << new_handler_worker(wid, ch, s.handler, s.max_keep_alive_requests)
103 }
104
105 if s.show_startup_message {
106 println('Listening on http://${s.addr}/')
107 flush_stdout()
108 }
109
110 time.sleep(20 * time.millisecond)
111 s.state = .running
112 if s.on_running != unsafe { nil } {
113 s.on_running(mut s)
114 }
115 for s.state == .running {
116 mut conn := s.listener.accept_only() or {
117 if err.code() == net.err_timed_out_code {
118 // Skip network timeouts, they are normal
119 continue
120 }
121 eprintln('accept() failed, reason: ${err}; skipping')
122 continue
123 }
124 conn.set_read_timeout(s.read_timeout)
125 conn.set_write_timeout(s.write_timeout)
126 ch <- conn
127 }
128 if s.state == .stopped {
129 s.close()
130 }
131}
132
133// stop signals the server that it should not respond anymore.
134@[inline]
135pub fn (mut s Server) stop() {
136 s.state = .stopped
137 if s.on_stopped != unsafe { nil } {
138 s.on_stopped(mut s)
139 }
140}
141
142// close immediately closes the port and signals the server that it has been closed.
143@[inline]
144pub fn (mut s Server) close() {
145 s.state = .closed
146 if s.listener_opened {
147 s.listener.close() or { return }
148 s.listener_opened = false
149 }
150 if s.on_closed != unsafe { nil } {
151 s.on_closed(mut s)
152 }
153}
154
155// status indicates whether the server is running, stopped, or closed.
156@[inline]
157pub fn (s &Server) status() ServerStatus {
158 return s.state
159}
160
161// WaitTillRunningParams allows for parametrising the calls to s.wait_till_running()
162@[params]
163pub struct WaitTillRunningParams {
164pub:
165 max_retries int = 100 // how many times to check for the status, for each single s.wait_till_running() call
166 retry_period_ms int = 10 // how much time to wait between each check for the status, in milliseconds
167}
168
169// wait_till_running allows you to synchronise your calling (main) thread, with the state of the server
170// (when the server is running in another thread).
171// It returns an error, after params.max_retries * params.retry_period_ms
172// milliseconds have passed, without that expected server transition.
173pub fn (mut s Server) wait_till_running(params WaitTillRunningParams) !int {
174 mut i := 0
175 for s.status() != .running && i < params.max_retries {
176 time.sleep(params.retry_period_ms * time.millisecond)
177 i++
178 }
179 if i >= params.max_retries {
180 return error('maximum retries reached')
181 }
182 time.sleep(params.retry_period_ms)
183 return i
184}
185
186struct HandlerWorker {
187 id int
188 ch chan &net.TcpConn
189 max_keep_alive_requests int
190pub mut:
191 handler Handler
192}
193
194fn new_handler_worker(wid int, ch chan &net.TcpConn, handler Handler, max_keep_alive_requests int) thread {
195 mut w := &HandlerWorker{
196 id: wid
197 ch: ch
198 handler: handler
199 max_keep_alive_requests: max_keep_alive_requests
200 }
201 return spawn w.process_requests()
202}
203
204fn (mut w HandlerWorker) process_requests() {
205 for {
206 mut conn := <-w.ch or { break }
207 w.handle_conn(mut conn)
208 }
209}
210
211fn (mut w HandlerWorker) handle_conn(mut conn net.TcpConn) {
212 conn.set_sock() or {
213 net.close(conn.handle) or {}
214 eprintln('set_sock() failed: ${err}')
215 return
216 }
217 defer {
218 conn.close() or { eprintln('close() failed: ${err}') }
219 }
220
221 mut reader := io.new_buffered_reader(reader: conn)
222 defer {
223 unsafe {
224 reader.free()
225 }
226 }
227
228 mut request_count := 0
229 for {
230 mut req := parse_request(mut reader) or {
231 if err !is io.Eof {
232 $if debug {
233 // only show in debug mode to prevent abuse
234 eprintln('error parsing request: ${err}')
235 }
236 }
237 return
238 }
239 request_count++
240
241 remote_ip := conn.peer_ip() or { '0.0.0.0' }
242 req.header.add_custom('Remote-Addr', remote_ip) or {}
243
244 mut resp := w.handler.handle(req)
245 normalize_server_response(mut resp, req)
246
247 // Implemented by developers?
248 if !resp.header.contains(.content_length) {
249 resp.header.set(.content_length, '${resp.body.len}')
250 }
251
252 // Check if max keep-alive requests limit reached
253 max_reached := w.max_keep_alive_requests > 0 && request_count >= w.max_keep_alive_requests
254
255 // Determine if connection should be kept alive
256 // HTTP/1.1 defaults to keep-alive, HTTP/1.0 defaults to close
257 req_conn := (req.header.get(.connection) or { '' }).to_lower()
258 resp_conn := (resp.header.get(.connection) or { '' }).to_lower()
259 keep_alive := if max_reached {
260 false
261 } else if resp_conn == 'close' {
262 false
263 } else if resp_conn == 'keep-alive' {
264 true
265 } else if req_conn == 'close' {
266 false
267 } else if req_conn == 'keep-alive' {
268 true
269 } else {
270 // Default behavior based on HTTP version
271 req.version == .v1_1
272 }
273
274 // Set Connection header in response
275 // Always override if max requests reached, otherwise only set if not already present
276 if max_reached || !resp.header.contains(.connection) {
277 if keep_alive {
278 resp.header.set(.connection, 'keep-alive')
279 } else {
280 resp.header.set(.connection, 'close')
281 }
282 }
283
284 conn.write(resp.bytes()) or {
285 eprintln('error sending response: ${err}')
286 return
287 }
288
289 if !keep_alive {
290 return
291 }
292 }
293}
294
295fn normalize_server_response(mut resp Response, req Request) {
296 server_version := if req.version == .unknown { Version.v1_1 } else { req.version }
297 match resp.http_version {
298 '1.0', '1.1', '2.0' {}
299 else {
300 resp.set_version(server_version)
301 }
302 }
303
304 status := status_from_int(resp.status_code)
305 if status.is_valid() {
306 if resp.status_msg == '' {
307 resp.status_msg = status.str()
308 }
309 } else if resp.status_code == 0 && resp.status_msg == '' {
310 resp.set_status(.ok)
311 } else {
312 resp.set_status(.internal_server_error)
313 }
314}
315
316// DebugHandler implements the Handler interface by echoing the request
317// in the response.
318struct DebugHandler {}
319
320fn (d DebugHandler) handle(req Request) Response {
321 $if debug {
322 eprintln('[${time.now()}] ${req.method} ${req.url}\n\r${req.header}\n\r${req.data} - 200 OK')
323 } $else {
324 eprintln('[${time.now()}] ${req.method} ${req.url} - 200')
325 }
326 mut r := Response{
327 body: req.data
328 header: req.header
329 }
330 r.set_status(.ok)
331 r.set_version(req.version)
332 return r
333}
334