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