Skip to content

Commit 8f72ea1

Browse files
committed
fasthttp,veb: static files via sendfile
1 parent f53920b commit 8f72ea1

File tree

7 files changed

+152
-36
lines changed

7 files changed

+152
-36
lines changed

examples/fasthttp/main.v

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,32 @@ module main
22

33
import fasthttp
44

5-
fn handle_request(req fasthttp.HttpRequest) ![]u8 {
5+
fn handle_request(req fasthttp.HttpRequest) !fasthttp.HttpResponse {
66
method := req.buffer[req.method.start..req.method.start + req.method.len].bytestr()
77
path := req.buffer[req.path.start..req.path.start + req.path.len].bytestr()
88

99
if method == 'GET' {
1010
if path == '/' {
11-
return home_controller()!
11+
return fasthttp.HttpResponse{
12+
content: home_controller()!
13+
}
1214
} else if path.starts_with('/user/') {
1315
id := path[6..]
14-
return get_user_controller(id)!
16+
return fasthttp.HttpResponse{
17+
content: get_user_controller(id)!
18+
}
1519
}
1620
} else if method == 'POST' {
1721
if path == '/user' {
18-
return create_user_controller()!
22+
return fasthttp.HttpResponse{
23+
content: create_user_controller()!
24+
}
1925
}
2026
}
2127

22-
return not_found_response()!
28+
return fasthttp.HttpResponse{
29+
content: not_found_response()!
30+
}
2331
}
2432

2533
fn main() {

vlib/fasthttp/fasthttp.v

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,17 @@ pub mut:
6262
user_data voidptr // User-defined context data
6363
}
6464

65+
pub struct HttpResponse {
66+
pub:
67+
content []u8
68+
file_path string
69+
}
70+
6571
// ServerConfig bundles the parameters needed to start a fasthttp server.
6672
pub struct ServerConfig {
6773
pub:
6874
port int = 3000
6975
max_request_buffer_size int = 8192
70-
handler fn (HttpRequest) ![]u8 @[required]
76+
handler fn (HttpRequest) !HttpResponse @[required]
7177
user_data voidptr
7278
}

vlib/fasthttp/fasthttp_darwin.v

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ module fasthttp
33
import net
44

55
#include <sys/event.h>
6+
#include <sys/stat.h>
7+
#include <sys/types.h>
8+
#include <sys/socket.h>
9+
#include <sys/uio.h>
610

711
const buf_size = max_connection_size
812
const kqueue_max_events = 128
913
const backlog = max_connection_size
1014

1115
fn C.kevent(kq int, changelist &C.kevent, nchanges int, eventlist &C.kevent, nevents int, timeout &C.timespec) int
1216
fn C.kqueue() int
17+
fn C.fstat(fd int, buf &C.stat) int
18+
19+
// int sendfile(int fd, int s, off_t offset, off_t *len, struct sf_hdtr *hdtr, int flags);
20+
fn C.sendfile(fd int, s int, offset i64, len &i64, hdtr voidptr, flags int) int
1321

1422
struct C.kevent {
1523
ident u64
@@ -38,6 +46,11 @@ mut:
3846
read_len int
3947
write_buf []u8
4048
write_pos int
49+
50+
// Sendfile state
51+
file_fd int = -1
52+
file_len i64
53+
file_pos i64
4154
}
4255

4356
pub struct Server {
@@ -46,7 +59,7 @@ pub mut:
4659
socket_fd int
4760
poll_fd int // kqueue fd
4861
user_data voidptr
49-
request_handler fn (HttpRequest) ![]u8 @[required]
62+
request_handler fn (HttpRequest) !HttpResponse @[required]
5063
}
5164

5265
// new_server creates and initializes a new Server instance.
@@ -87,27 +100,67 @@ fn close_conn(kq int, c_ptr voidptr) {
87100
if c.write_buf.len > 0 {
88101
c.write_buf.clear()
89102
}
103+
if c.file_fd != -1 {
104+
C.close(c.file_fd)
105+
c.file_fd = -1
106+
}
90107
unsafe { free(c_ptr) }
91108
}
92109

93110
fn send_pending(c_ptr voidptr) bool {
94111
mut c := unsafe { &Conn(c_ptr) }
95-
if c.write_buf.len == 0 {
96-
return false
97-
}
98-
remaining := c.write_buf.len - c.write_pos
99-
if remaining <= 0 {
100-
return false
112+
113+
// 1. Send memory buffer (headers or small response)
114+
if c.write_pos < c.write_buf.len {
115+
remaining := c.write_buf.len - c.write_pos
116+
write_ptr := unsafe { &c.write_buf[0] + c.write_pos }
117+
sent := C.send(c.fd, write_ptr, remaining, 0)
118+
if sent > 0 {
119+
c.write_pos += int(sent)
120+
}
121+
if sent < 0 {
122+
if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK {
123+
return true
124+
}
125+
return false // Error
126+
}
101127
}
102-
write_ptr := unsafe { &c.write_buf[0] + c.write_pos }
103-
sent := C.send(c.fd, write_ptr, remaining, 0)
104-
if sent > 0 {
105-
c.write_pos += int(sent)
128+
129+
// 2. Send file if buffer is fully sent
130+
if c.write_pos >= c.write_buf.len && c.file_fd != -1 {
131+
mut len := i64(0) // Input 0 means send until EOF
132+
ret := C.sendfile(c.file_fd, c.fd, c.file_pos, &len, unsafe { nil }, 0)
133+
134+
if len > 0 {
135+
c.file_pos += len
136+
}
137+
138+
if ret == -1 {
139+
if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK {
140+
return true
141+
}
142+
// Error sending file
143+
C.close(c.file_fd)
144+
c.file_fd = -1
145+
return false
146+
}
147+
148+
if c.file_pos >= c.file_len {
149+
// Done sending file
150+
C.close(c.file_fd)
151+
c.file_fd = -1
152+
} else {
153+
// Not done yet
154+
return true
155+
}
106156
}
107-
if sent < 0 && (C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK) {
108-
return true
157+
158+
// Check if completely done (both buffer and file)
159+
if c.write_pos >= c.write_buf.len && c.file_fd == -1 {
160+
return false // Done
109161
}
110-
return c.write_pos < c.write_buf.len
162+
163+
return true // Still pending (partial buffer write or file not done)
111164
}
112165

113166
fn send_bad_request(fd int) {
@@ -169,7 +222,21 @@ fn handle_read(mut s Server, kq int, c_ptr voidptr) {
169222
return
170223
}
171224

172-
c.write_buf = resp.clone()
225+
c.write_buf = resp.content.clone()
226+
if resp.file_path != '' {
227+
fd := C.open(resp.file_path.str, C.O_RDONLY)
228+
if fd != -1 {
229+
mut st := C.stat{}
230+
if C.fstat(fd, &st) == 0 {
231+
c.file_fd = fd
232+
c.file_len = st.st_size
233+
c.file_pos = 0
234+
} else {
235+
C.close(fd)
236+
}
237+
}
238+
}
239+
173240
c.write_pos = 0
174241
c.read_len = 0
175242

@@ -196,6 +263,7 @@ fn accept_clients(kq int, listen_fd int) {
196263
mut c := &Conn{
197264
fd: client_fd
198265
user_data: unsafe { nil }
266+
file_fd: -1
199267
}
200268
add_event(kq, u64(client_fd), i16(C.EVFILT_READ), u16(C.EV_ADD | C.EV_ENABLE | C.EV_CLEAR),
201269
c)

vlib/fasthttp/fasthttp_linux.v

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module fasthttp
33
import net
44

55
#include <sys/epoll.h>
6+
#include <sys/sendfile.h>
7+
#include <sys/stat.h>
68
#include <netinet/tcp.h>
79

810
fn C.accept4(sockfd int, addr &net.Addr, addrlen &u32, flags int) int
@@ -13,6 +15,10 @@ fn C.epoll_ctl(__epfd int, __op int, __fd int, __event &C.epoll_event) int
1315

1416
fn C.epoll_wait(__epfd int, __events &C.epoll_event, __maxevents int, __timeout int) int
1517

18+
fn C.sendfile(out_fd int, in_fd int, offset &int, count usize) int
19+
20+
fn C.fstat(fd int, buf &C.stat) int
21+
1622
union C.epoll_data {
1723
ptr voidptr
1824
fd int
@@ -34,7 +40,7 @@ mut:
3440
listen_fds []int = []int{len: max_thread_pool_size, cap: max_thread_pool_size}
3541
epoll_fds []int = []int{len: max_thread_pool_size, cap: max_thread_pool_size}
3642
threads []thread = []thread{len: max_thread_pool_size, cap: max_thread_pool_size}
37-
request_handler fn (HttpRequest) ![]u8 @[required]
43+
request_handler fn (HttpRequest) !HttpResponse @[required]
3844
}
3945

4046
// new_server creates and initializes a new Server instance.
@@ -242,20 +248,34 @@ fn process_events(mut server Server, epoll_fd int, listen_fd int) {
242248
}
243249
decoded_http_request.client_conn_fd = client_fd
244250
decoded_http_request.user_data = server.user_data
245-
response_buffer := server.request_handler(decoded_http_request) or {
251+
response := server.request_handler(decoded_http_request) or {
246252
eprintln('Error handling request ${err}')
247253
C.send(client_fd, tiny_bad_request_response.data, tiny_bad_request_response.len,
248254
C.MSG_NOSIGNAL)
249255
handle_client_closure(epoll_fd, client_fd)
250256
continue
251257
}
252-
// Send response
253-
sent := C.send(client_fd, response_buffer.data, response_buffer.len,
254-
C.MSG_NOSIGNAL | C.MSG_DONTWAIT)
255-
if sent < 0 && C.errno != C.EAGAIN && C.errno != C.EWOULDBLOCK {
256-
eprintln('ERROR: send() failed with errno=${C.errno}')
257-
handle_client_closure(epoll_fd, client_fd)
258-
continue
258+
// Send response content (headers/body)
259+
if response.content.len > 0 {
260+
sent := C.send(client_fd, response.content.data, response.content.len,
261+
C.MSG_NOSIGNAL | C.MSG_DONTWAIT)
262+
if sent < 0 && C.errno != C.EAGAIN && C.errno != C.EWOULDBLOCK {
263+
eprintln('ERROR: send() failed with errno=${C.errno}')
264+
handle_client_closure(epoll_fd, client_fd)
265+
continue
266+
}
267+
}
268+
269+
// Send file if present
270+
if response.file_path != '' {
271+
fd := C.open(response.file_path.str, C.O_RDONLY)
272+
if fd != -1 {
273+
mut st := C.stat{}
274+
C.fstat(fd, &st)
275+
offset := 0
276+
C.sendfile(client_fd, fd, &offset, usize(st.st_size))
277+
C.close(fd)
278+
}
259279
}
260280
// Leave the connection open; closure is driven by client FIN or errors
261281
} else if bytes_read == 0 {

vlib/fasthttp/fasthttp_test.v

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ fn test_decode_http_request() {
9696
}
9797

9898
fn test_new_server() {
99-
handler := fn (req HttpRequest) ![]u8 {
100-
return 'HTTP/1.1 200 OK\r\n\r\nHello'.bytes()
99+
handler := fn (req HttpRequest) !HttpResponse {
100+
return HttpResponse{
101+
content: 'HTTP/1.1 200 OK\r\n\r\nHello'.bytes()
102+
}
101103
}
102104

103105
server := new_server(ServerConfig{

vlib/fasthttp/fasthttp_windows.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ struct Server {
44
pub:
55
port int = 3000
66
mut:
7-
request_handler fn (HttpRequest) ![]u8 @[required]
7+
request_handler fn (HttpRequest) !HttpResponse @[required]
88
}
99

1010
// new_server creates and initializes a new Server instance.

vlib/veb/veb_d_new_veb.v

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub fn run_new[A, X](mut global_app A, port int) ! {
5151
server.run() or { panic(err) }
5252
}
5353

54-
fn parallel_request_handler[A, X](req fasthttp.HttpRequest) ![]u8 {
54+
fn parallel_request_handler[A, X](req fasthttp.HttpRequest) !fasthttp.HttpResponse {
5555
// Get parameters from user_data - copy to avoid use-after-free
5656
params := unsafe { *(&RequestParams(req.user_data)) }
5757
mut global_app := unsafe { &A(params.global_app) }
@@ -61,7 +61,9 @@ fn parallel_request_handler[A, X](req fasthttp.HttpRequest) ![]u8 {
6161
s := req.buffer.bytestr()
6262
// Parse the raw request bytes into a standard `http.Request`.
6363
req2 := http.parse_request_str(s.clone()) or {
64-
return 'HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes()
64+
return fasthttp.HttpResponse{
65+
content: 'HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes()
66+
}
6567
}
6668
// Create and populate the `veb.Context`.
6769
completed_context := handle_request_and_route[A, X](mut global_app, req2, client_fd,
@@ -70,8 +72,18 @@ fn parallel_request_handler[A, X](req fasthttp.HttpRequest) ![]u8 {
7072
if completed_context.takeover {
7173
eprintln('[veb] WARNING: ctx.takeover_conn() was called, but this is not supported by this server backend. The connection will be closed after this response.')
7274
}
75+
76+
if completed_context.return_type == .file {
77+
return fasthttp.HttpResponse{
78+
content: completed_context.res.bytes()
79+
file_path: completed_context.return_file
80+
}
81+
}
82+
7383
// The fasthttp server expects a complete response buffer to be returned.
74-
return completed_context.res.bytes()
84+
return fasthttp.HttpResponse{
85+
content: completed_context.res.bytes()
86+
}
7587
} // handle_request_and_route is a unified function that creates the context,
7688

7789
// runs middleware, and finds the correct route for a request.

0 commit comments

Comments
 (0)