v / vlib / fasthttp / fasthttp.v
290 lines · 245 sloc · 7.62 KB · 45545c2fda3dfafa31fb7341b31b786ad143e67d
Raw
1// Copyright (c) 2019-2025 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 fasthttp
5
6import runtime
7import net
8import time
9
10#include <errno.h>
11
12$if !windows {
13 #include <fcntl.h>
14 #include <sys/socket.h>
15 #include <netinet/in.h>
16 #include <netinet/tcp.h>
17}
18
19const max_thread_pool_size = runtime.nr_cpus()
20const max_connection_size = 65536 // Max events per epoll_wait
21
22const tiny_bad_request_response = 'HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes()
23const status_444_response = 'HTTP/1.1 444 No Response\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes()
24const status_413_response = 'HTTP/1.1 413 Payload Too Large\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes()
25
26fn C.socket(domain i32, typ i32, protocol i32) i32
27
28fn C.bind(sockfd i32, addr &net.Addr, addrlen u32) i32
29
30fn C.send(__fd i32, __buf voidptr, __n usize, __flags i32) i32
31
32fn C.recv(__fd i32, __buf voidptr, __n usize, __flags i32) i32
33
34fn C.setsockopt(__fd i32, __level i32, __optname i32, __optval voidptr, __optlen u32) i32
35
36fn C.listen(__fd i32, __n i32) i32
37
38fn C.perror(s &u8)
39
40fn C.close(fd i32) i32
41
42fn C.htons(__hostshort u16) u16
43
44fn C.fcntl(fd i32, cmd i32, arg i32) i32
45
46pub struct Slice {
47pub:
48 start int
49 len int
50}
51
52// HttpRequest represents an HTTP request.
53// TODO make fields immutable
54pub struct HttpRequest {
55pub mut:
56 buffer []u8 // A V slice of the read buffer for convenience
57 method Slice
58 path Slice
59 version Slice
60 header_fields Slice
61 body Slice
62 client_conn_fd int
63 user_data voidptr // User-defined context data
64}
65
66pub enum ResponseTakeoverMode {
67 none
68 manual
69 reusable
70}
71
72pub struct HttpResponse {
73pub mut:
74 content []u8
75 file_path string
76 takeover_mode ResponseTakeoverMode
77 should_close bool // if true, close the connection after sending (Connection: close)
78 // content_owned lets the backend free or move content after it has been sent.
79 content_owned bool
80 // request_arena is a prealloc scope handle that must be freed after sending.
81 request_arena voidptr
82}
83
84fn (mut resp HttpResponse) free_owned_content() {
85 if resp.content_owned && resp.content.cap > 0 {
86 unsafe { resp.content.free() }
87 resp.content = []u8{}
88 }
89}
90
91fn (mut resp HttpResponse) take_or_clone_content() []u8 {
92 if resp.content_owned {
93 content := resp.content
94 resp.content = []u8{}
95 return content
96 }
97 return resp.content.clone()
98}
99
100fn end_request_arena_current_thread(request_arena voidptr) {
101 $if prealloc {
102 if request_arena != unsafe { nil } {
103 unsafe { prealloc_scope_end(request_arena) }
104 }
105 }
106}
107
108fn leave_request_arena_current_thread(request_arena voidptr) {
109 $if prealloc {
110 if request_arena != unsafe { nil } {
111 unsafe { prealloc_scope_leave(request_arena) }
112 }
113 }
114}
115
116fn abandon_request_arena_current_thread(request_arena voidptr) {
117 $if prealloc {
118 if request_arena != unsafe { nil } {
119 unsafe { prealloc_scope_abandon(request_arena) }
120 }
121 }
122}
123
124fn (mut resp HttpResponse) attach_request_arena_if_empty(request_arena voidptr) {
125 if resp.request_arena == unsafe { nil } {
126 resp.request_arena = request_arena
127 }
128}
129
130fn (mut resp HttpResponse) end_request_arena_current_thread() {
131 end_request_arena_current_thread(resp.request_arena)
132 resp.request_arena = unsafe { nil }
133}
134
135fn (mut resp HttpResponse) abandon_request_arena_current_thread() {
136 abandon_request_arena_current_thread(resp.request_arena)
137 resp.request_arena = unsafe { nil }
138}
139
140fn (mut resp HttpResponse) take_request_arena() voidptr {
141 request_arena := resp.request_arena
142 resp.request_arena = unsafe { nil }
143 return request_arena
144}
145
146// ServerConfig bundles the parameters needed to start a fasthttp server.
147pub struct ServerConfig {
148pub:
149 family net.AddrFamily = .ip6
150 port int = 3000
151 max_request_buffer_size int = 8192
152 timeout_in_seconds int = 30
153 handler fn (HttpRequest) !HttpResponse @[required]
154 user_data voidptr
155}
156
157// ShutdownParams configures how long graceful shutdown should wait for in-flight requests.
158@[params]
159pub struct ShutdownParams {
160pub:
161 timeout time.Duration = time.infinite
162 retry_period_ms int = 10
163}
164
165// WaitTillRunningParams allows parametrizing the calls to `ServerHandle.wait_till_running()`.
166@[params]
167pub struct WaitTillRunningParams {
168pub:
169 max_retries int = 100
170 retry_period_ms int = 10
171}
172
173// ServerHandle exposes lifecycle controls for a running `fasthttp.Server`.
174pub struct ServerHandle {
175 ptr voidptr
176}
177
178// handle returns a reusable handle for waiting on or shutting down the server.
179pub fn (s &Server) handle() ServerHandle {
180 return ServerHandle{
181 ptr: s
182 }
183}
184
185// wait_till_running waits until the server transitions to its serving state.
186pub fn (h ServerHandle) wait_till_running(params WaitTillRunningParams) !int {
187 if h.ptr == unsafe { nil } {
188 return error('server handle is not initialized')
189 }
190 $if linux || bsd {
191 mut server := unsafe { &Server(h.ptr) }
192 return server.wait_till_running_impl(params)!
193 } $else {
194 return error('fasthttp server lifecycle control is only supported on linux and BSD-family OSes')
195 }
196}
197
198// shutdown gracefully stops accepting new requests and waits for active requests to finish.
199pub fn (h ServerHandle) shutdown(params ShutdownParams) ! {
200 if h.ptr == unsafe { nil } {
201 return error('server handle is not initialized')
202 }
203 $if linux || bsd {
204 mut server := unsafe { &Server(h.ptr) }
205 server.shutdown_impl(params)!
206 } $else {
207 return error('fasthttp server lifecycle control is only supported on linux and BSD-family OSes')
208 }
209}
210
211$if linux || bsd {
212 fn normalized_retry_period_ms(retry_period_ms int) int {
213 return if retry_period_ms > 0 { retry_period_ms } else { 1 }
214 }
215
216 fn (mut s Server) wait_till_running_impl(params WaitTillRunningParams) !int {
217 retry_period_ms := normalized_retry_period_ms(params.retry_period_ms)
218 mut attempts := 0
219 mut running := s.running
220 for !running.load() && attempts < params.max_retries {
221 time.sleep(retry_period_ms * time.millisecond)
222 attempts++
223 }
224 if !running.load() {
225 return error('maximum retries reached')
226 }
227 time.sleep(retry_period_ms * time.millisecond)
228 return attempts
229 }
230
231 fn (mut s Server) shutdown_impl(params ShutdownParams) ! {
232 mut stopped := s.stopped
233 if stopped.load() {
234 return
235 }
236 mut shutting_down := s.shutting_down
237 if shutting_down.compare_and_swap(false, true) {
238 s.stop_accepting()
239 }
240 retry_period_ms := normalized_retry_period_ms(params.retry_period_ms)
241 mut watch := time.new_stopwatch()
242 for !stopped.load() {
243 if params.timeout != time.infinite && watch.elapsed() >= params.timeout {
244 return error('graceful shutdown timed out after ${params.timeout}')
245 }
246 time.sleep(retry_period_ms * time.millisecond)
247 }
248 }
249
250 fn (s &Server) begin_request() {
251 mut active_requests := s.active_requests
252 active_requests.add(1)
253 }
254
255 fn (s &Server) end_request() {
256 mut active_requests := s.active_requests
257 active_requests.sub(1)
258 }
259
260 fn (s &Server) active_request_count() int {
261 mut active_requests := s.active_requests
262 return active_requests.load()
263 }
264
265 fn (s &Server) is_shutting_down() bool {
266 mut shutting_down := s.shutting_down
267 return shutting_down.load()
268 }
269
270 fn (s &Server) is_stopped() bool {
271 mut stopped := s.stopped
272 return stopped.load()
273 }
274
275 fn (mut s Server) mark_running() {
276 mut running := s.running
277 running.store(true)
278 mut stopped := s.stopped
279 stopped.store(false)
280 }
281
282 fn (mut s Server) mark_stopped() {
283 mut active_requests := s.active_requests
284 active_requests.store(0)
285 mut running := s.running
286 running.store(false)
287 mut stopped := s.stopped
288 stopped.store(true)
289 }
290}
291