Skip to content

Commit 307a670

Browse files
committed
veb: add missing server_new_veb.v and veb_fasthttp.v files
1 parent b94964e commit 307a670

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

vlib/veb/server_new_veb.v

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
module veb
2+
3+
import fasthttp
4+
5+
@[heap]
6+
pub struct Server {
7+
handle fasthttp.ServerHandle
8+
lifecycle_control bool
9+
}
10+
11+
fn new_server_with_lifecycle(handle fasthttp.ServerHandle) &Server {
12+
return &Server{
13+
handle: handle
14+
lifecycle_control: true
15+
}
16+
}
17+
18+
fn new_server_without_lifecycle() &Server {
19+
return &Server{}
20+
}
21+
22+
fn (s &Server) ensure_lifecycle_control() ! {
23+
if !s.lifecycle_control {
24+
return error('veb server lifecycle control is not available with SSL')
25+
}
26+
}
27+
28+
// wait_till_running waits until the server starts accepting requests.
29+
pub fn (s &Server) wait_till_running(params WaitTillRunningParams) !int {
30+
s.ensure_lifecycle_control()!
31+
return s.handle.wait_till_running(fasthttp.WaitTillRunningParams{
32+
max_retries: params.max_retries
33+
retry_period_ms: params.retry_period_ms
34+
})!
35+
}
36+
37+
// shutdown gracefully stops accepting new requests and waits for in-flight requests to finish.
38+
pub fn (s &Server) shutdown(params ShutdownParams) ! {
39+
s.ensure_lifecycle_control()!
40+
s.handle.shutdown(fasthttp.ShutdownParams{
41+
timeout: params.timeout
42+
retry_period_ms: params.retry_period_ms
43+
})!
44+
}

vlib/veb/veb_fasthttp.v

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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.
4+
module veb
5+
6+
import fasthttp
7+
import net.http
8+
import time
9+
import net.urllib
10+
11+
struct RequestParams {
12+
global_app voidptr
13+
controllers_sorted []&ControllerPath
14+
routes &map[string]Route
15+
benchmark_page_generation bool
16+
}
17+
18+
const http_ok_response = 'HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n'.bytes()
19+
20+
pub fn run_at[A, X](mut global_app A, params RunParams) ! {
21+
run_new[A, X](mut global_app, params)!
22+
}
23+
24+
// run_new - start a new veb server using the parallel fasthttp backend.
25+
pub fn run_new[A, X](mut global_app A, params RunParams) ! {
26+
if params.port <= 0 || params.port > 65535 {
27+
return error('invalid port number `${params.port}`, it should be between 1 and 65535')
28+
}
29+
if ssl_enabled(params) {
30+
maybe_init_server[A](mut global_app, new_server_without_lifecycle())
31+
run_at_with_ssl[A, X](mut global_app, params)!
32+
return
33+
}
34+
35+
// Generate routes and controllers just like the original run() function.
36+
routes := generate_routes[A, X](global_app)!
37+
controllers_sorted := check_duplicate_routes_in_controllers[A](global_app, routes)!
38+
39+
// Allocate params on the heap to keep it valid for the server lifetime
40+
request_params := &RequestParams{
41+
global_app: unsafe { voidptr(&global_app) }
42+
controllers_sorted: controllers_sorted
43+
routes: &routes
44+
benchmark_page_generation: params.benchmark_page_generation
45+
}
46+
47+
// Configure and run the fasthttp server
48+
mut server := fasthttp.new_server(fasthttp.ServerConfig{
49+
family: params.family
50+
port: params.port
51+
handler: parallel_request_handler[A, X]
52+
max_request_buffer_size: params.max_request_buffer_size
53+
user_data: voidptr(request_params)
54+
}) or {
55+
eprintln('Failed to create server: ${err}')
56+
return
57+
}
58+
maybe_init_server[A](mut global_app, new_server_with_lifecycle(server.handle()))
59+
println('[veb] Running multi-threaded app on ${server_protocol(params)}://${startup_host(params)}:${params.port}/')
60+
flush_stdout()
61+
$if A is BeforeAcceptApp {
62+
global_app.before_accept_loop()
63+
}
64+
server.run() or { panic(err) }
65+
}
66+
67+
fn parallel_request_handler[A, X](req fasthttp.HttpRequest) !fasthttp.HttpResponse {
68+
// Get parameters from user_data - copy to avoid use-after-free
69+
params := unsafe { *(&RequestParams(req.user_data)) }
70+
mut global_app := unsafe { &A(params.global_app) }
71+
72+
client_fd := req.client_conn_fd
73+
74+
s := req.buffer.bytestr()
75+
// Parse the raw request bytes into a standard `http.Request`.
76+
req2 := http.parse_request_str(s.clone()) or {
77+
return fasthttp.HttpResponse{
78+
content: 'HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes()
79+
}
80+
}
81+
// Create and populate the `veb.Context`.
82+
completed_context := handle_request_and_route[A, X](mut global_app, req2, client_fd, params)
83+
84+
if completed_context.takeover {
85+
// The handler has taken over the connection (e.g. for SSE or WebSocket).
86+
// The response was already sent directly over ctx.conn.
87+
// Tell fasthttp to hand off the fd without closing it.
88+
return fasthttp.HttpResponse{
89+
takeover: true
90+
}
91+
}
92+
93+
if completed_context.return_type == .file {
94+
return fasthttp.HttpResponse{
95+
content: completed_context.res.bytes()
96+
file_path: completed_context.return_file
97+
should_close: completed_context.client_wants_to_close
98+
}
99+
}
100+
101+
// The fasthttp server expects a complete response buffer to be returned.
102+
return fasthttp.HttpResponse{
103+
content: completed_context.res.bytes()
104+
should_close: completed_context.client_wants_to_close
105+
}
106+
} // handle_request_and_route is a unified function that creates the context,
107+
108+
// runs middleware, and finds the correct route for a request.
109+
fn handle_request_and_route[A, X](mut app A, req http.Request, _client_fd int, params RequestParams) &Context {
110+
// Create and populate the `veb.Context` from the request.
111+
mut url := urllib.parse_request_uri(req.url) or {
112+
// This should be rare if http.parse_request succeeded.
113+
mut bad_ctx := &Context{
114+
req: req
115+
}
116+
bad_ctx.not_found()
117+
return bad_ctx
118+
}
119+
query := parse_query_from_url(url)
120+
form, files := parse_form_from_request(req) or {
121+
mut bad_ctx := &Context{
122+
req: req
123+
}
124+
bad_ctx.request_error('Failed to parse form data: ${err.msg()}')
125+
return bad_ctx
126+
}
127+
host_with_port := req.header.get(.host) or { '' }
128+
host, _ := urllib.split_host_port(host_with_port)
129+
page_gen_start := if params.benchmark_page_generation { time.ticks() } else { 0 }
130+
mut ctx := &Context{
131+
req: req
132+
page_gen_start: page_gen_start
133+
client_fd: _client_fd
134+
client_wants_to_close: true // fasthttp always closes connections after response
135+
query: query
136+
form: form
137+
files: files
138+
}
139+
$if A is StaticApp {
140+
ctx.custom_mime_types = app.static_mime_types.clone()
141+
mut user_context := X{}
142+
user_context.Context = ctx
143+
if serve_if_static[X](static_handler_config(app.static_files, app.static_mime_types,
144+
app.static_hosts, app.enable_static_gzip, app.enable_static_zstd,
145+
app.enable_static_compression, app.static_compression_max_size,
146+
app.static_compression_mime_types, app.enable_markdown_negotiation), mut user_context,
147+
url, host)
148+
{
149+
return &user_context.Context
150+
}
151+
}
152+
// Match controller paths first
153+
$if A is ControllerInterface {
154+
if completed_context := handle_controllers[X](params.controllers_sorted, ctx, mut url, host) {
155+
return completed_context
156+
}
157+
}
158+
// Create a new user context and pass veb's context
159+
mut user_context := X{}
160+
user_context.Context = ctx
161+
handle_route[A, X](mut app, mut user_context, url, host, params.routes)
162+
return &user_context.Context
163+
}

0 commit comments

Comments
 (0)