From 600456b09b958a844964fc05ea7a341bd9e0caed Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sat, 7 Dec 2024 14:07:33 -0400 Subject: [PATCH 01/10] example: fast, multthreaded, Non-blocking, port and host reuse, thread-safe, epoll server --- examples/server/.editorconfig | 8 + examples/server/.gitattributes | 8 + examples/server/.gitignore | 23 ++ examples/server/README.md | 91 ++++++++ examples/server/src/controllers.v | 43 ++++ examples/server/src/main.v | 61 ++++++ examples/server/src/request_parser.v | 70 +++++++ examples/server/src/router.v | 1 + examples/server/src/server.c.v | 302 +++++++++++++++++++++++++++ examples/server/v.mod | 7 + 10 files changed, 614 insertions(+) create mode 100644 examples/server/.editorconfig create mode 100644 examples/server/.gitattributes create mode 100644 examples/server/.gitignore create mode 100644 examples/server/README.md create mode 100644 examples/server/src/controllers.v create mode 100644 examples/server/src/main.v create mode 100644 examples/server/src/request_parser.v create mode 100644 examples/server/src/router.v create mode 100644 examples/server/src/server.c.v create mode 100644 examples/server/v.mod diff --git a/examples/server/.editorconfig b/examples/server/.editorconfig new file mode 100644 index 00000000000000..01072caf100d49 --- /dev/null +++ b/examples/server/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.v] +indent_style = tab diff --git a/examples/server/.gitattributes b/examples/server/.gitattributes new file mode 100644 index 00000000000000..9a98968cecfa2e --- /dev/null +++ b/examples/server/.gitattributes @@ -0,0 +1,8 @@ +* text=auto eol=lf +*.bat eol=crlf + +*.v linguist-language=V +*.vv linguist-language=V +*.vsh linguist-language=V +v.mod linguist-language=V +.vdocignore linguist-language=ignore diff --git a/examples/server/.gitignore b/examples/server/.gitignore new file mode 100644 index 00000000000000..bae1de6e7d9d6a --- /dev/null +++ b/examples/server/.gitignore @@ -0,0 +1,23 @@ +# Binaries for programs and plugins +main +*.exe +*.exe~ +*.so +*.dylib +*.dll + +# Ignore binary output folders +bin/ + +# Ignore common editor/system specific metadata +.DS_Store +.idea/ +.vscode/ +*.iml + +# ENV +.env + +# vweb and database +*.db +*.js diff --git a/examples/server/README.md b/examples/server/README.md new file mode 100644 index 00000000000000..f0921102d9eee3 --- /dev/null +++ b/examples/server/README.md @@ -0,0 +1,91 @@ +# Vanilla + +Vanilla is a raw V server. + +## Description + +This project is a simple server written in the V programming language. It aims to provide a minimalistic and efficient server implementation. + +## Features + +- Lightweight and fast +- Minimal dependencies +- Easy to understand and extend + +## Installation + +To install Vanilla, you need to have the V compiler installed. You can download it from the [official V website](https://vlang.io). + +## Usage + +To run the server, use the following command: + +```sh +v -prod crun . +``` + +This will start the server, and you can access it at `http://localhost:3000`. + +## Code Overview + +### Main Server + +The main server logic is implemented in [src/main.v](v/vanilla/src/main.v). The server is initialized and started in the `main` function: + +```v +module main + +const port = 3000 + +fn main() { + mut server := Server{ + router: setup_router() + } + + server.server_socket = create_server_socket(port) + if server.server_socket < 0 { + return + } + server.epoll_fd = C.epoll_create1(0) + if server.epoll_fd < 0 { + C.perror('epoll_create1 failed'.str) + C.close(server.server_socket) + return + } + + server.lock_flag.lock() + if add_fd_to_epoll(server.epoll_fd, server.server_socket, u32(C.EPOLLIN)) == -1 { + C.close(server.server_socket) + C.close(server.epoll_fd) + + server.lock_flag.unlock() + return + } + + server.lock_flag.unlock() + + server.lock_flag.init() + for i := 0; i < 16; i++ { + server.threads[i] = spawn worker_thread(&server) + } + println('listening on http://localhost:${port}/') + event_loop(&server) +} +``` + +## Test + +### CURL + +```sh +curl -X GET --verbose http://localhost:3000/ && +curl -X POST --verbose http://localhost:3000/user && +curl -X GET --verbose http://localhost:3000/user/1 + +``` + +### WRK + +```sh +wrk --connection 512 --threads 16 --duration 10s http://localhost:3000 +``` diff --git a/examples/server/src/controllers.v b/examples/server/src/controllers.v new file mode 100644 index 00000000000000..9abfbc16f04e54 --- /dev/null +++ b/examples/server/src/controllers.v @@ -0,0 +1,43 @@ +module main + +import strings + +const h_response_body = '{"message": "Hello, world!"}' +const http_ok_response = 'HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 1\r\nConnection: keep-alive\r\n\r\n1'.bytes() + +const http_created_response = 'HTTP/1.1 201 Created\r\nContent-Type: application/json\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n'.bytes() + +fn home_controller(params []string) ![]u8 { + return http_ok_response +} + +fn get_users_controller(params []string) ![]u8 { + return http_ok_response +} + +@[direct_array_access; manualfree] +fn get_user_controller(params []string) ![]u8 { + if params.len == 0 { + return tiny_bad_request_response + } + id := params[0] + response_body := id + + mut sb := strings.new_builder(200) + sb.write_string('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: ') + sb.write_string(response_body.len.str()) + sb.write_string('\r\nConnection: keep-alive\r\n\r\n') + sb.write_string(response_body) + + defer { + unsafe { + response_body.free() + params.free() + } + } + return sb +} + +fn create_user_controller(params []string) ![]u8 { + return http_created_response +} diff --git a/examples/server/src/main.v b/examples/server/src/main.v new file mode 100644 index 00000000000000..ef5e0e489ac6eb --- /dev/null +++ b/examples/server/src/main.v @@ -0,0 +1,61 @@ +module main + +const port = 3000 +const max_thread_pool_size = 8 + +// handle_request finds and executes the handler for a given route. +// It takes an HttpRequest object as an argument and returns the response as a byte array. +fn handle_request(req HttpRequest) ![]u8 { + method := unsafe { tos(&req.buffer[req.method.start], req.method.len) } + path := unsafe { tos(&req.buffer[req.path.start], req.path.len) } + + if method == 'GET' { + if path == '/' { + return home_controller([]) + } else if path.starts_with('/user/') { + id := path[6..] + return get_user_controller([id]) + } + } else if method == 'POST' { + if path == '/user' { + return create_user_controller([]) + } + } + + return tiny_bad_request_response +} + +fn main() { + mut server := Server{ + request_handler: handle_request + } + + server.server_socket = create_server_socket(port) + if server.server_socket < 0 { + return + } + server.epoll_fd = C.epoll_create1(0) + if server.epoll_fd < 0 { + C.perror('epoll_create1 failed'.str) + C.close(server.server_socket) + return + } + + server.lock_flag.lock() + if add_fd_to_epoll(server.epoll_fd, server.server_socket, u32(C.EPOLLIN)) == -1 { + C.close(server.server_socket) + C.close(server.epoll_fd) + + server.lock_flag.unlock() + return + } + + server.lock_flag.unlock() + + server.lock_flag.init() + for i := 0; i < max_thread_pool_size; i++ { + server.threads[i] = spawn worker_thread(&server) + } + println('listening on http://localhost:${port}/') + event_loop(&server) +} diff --git a/examples/server/src/request_parser.v b/examples/server/src/request_parser.v new file mode 100644 index 00000000000000..e8fe13bd7da225 --- /dev/null +++ b/examples/server/src/request_parser.v @@ -0,0 +1,70 @@ +module main + +struct Slice { + start int + len int +} + +struct HttpRequest { +mut: + buffer []u8 + method Slice + path Slice + version Slice +} + +fn parse_request_line(mut req HttpRequest) ! { + mut i := 0 + // Parse HTTP method + for i < req.buffer.len && req.buffer[i] != ` ` { + i++ + } + req.method = Slice{ + start: 0 + len: i + } + i++ + + // Parse path + mut path_start := i + for i < req.buffer.len && req.buffer[i] != ` ` { + i++ + } + req.path = Slice{ + start: path_start + len: i - path_start + } + i++ + + // Parse HTTP version + mut version_start := i + for i < req.buffer.len && req.buffer[i] != `\r` { + i++ + } + req.version = Slice{ + start: version_start + len: i - version_start + } + + // Move to the end of the request line + if i + 1 < req.buffer.len && req.buffer[i] == `\r` && req.buffer[i + 1] == `\n` { + i += 2 + } else { + return error('Invalid HTTP request line') + } +} + +fn decode_http_request(buffer []u8) !HttpRequest { + mut req := HttpRequest{ + buffer: buffer + } + + parse_request_line(mut req)! + + return req +} + +// Helper function to convert Slice to string for debugging +fn slice_to_string(buffer []u8, s Slice) string { + return buffer[s.start..s.start + s.len].bytestr() +} diff --git a/examples/server/src/router.v b/examples/server/src/router.v new file mode 100644 index 00000000000000..6bd0de5a06b7ee --- /dev/null +++ b/examples/server/src/router.v @@ -0,0 +1 @@ +module main diff --git a/examples/server/src/server.c.v b/examples/server/src/server.c.v new file mode 100644 index 00000000000000..93542dee8a950d --- /dev/null +++ b/examples/server/src/server.c.v @@ -0,0 +1,302 @@ +// This module implements a basic HTTP server using epoll for handling multiple client connections efficiently. +// The server is designed to be non-blocking and uses multiple threads to handle incoming requests concurrently. +// +// Performance Considerations: +// - Non-blocking I/O: The server uses non-blocking sockets to ensure that it can handle multiple connections without being blocked by any single connection. +// - Epoll: The use of epoll allows the server to efficiently monitor multiple file descriptors to see if I/O is possible on any of them. +// - Threading: The server spawns multiple threads to handle client requests, which can improve performance on multi-core systems. +// - Memory Management: Care is taken to allocate and free memory appropriately to avoid memory leaks and ensure efficient memory usage. +// - Error Handling: The server includes error handling to manage and log errors without crashing, ensuring robustness and reliability. +// - SO_REUSEPORT: The server sets the SO_REUSEPORT socket option to allow multiple sockets on the same host and port, which can improve performance in certain scenarios. +// - Connection Handling: The server efficiently handles client connections, including accepting new connections, reading requests, and sending responses. +// - Mutex Locking: The server uses mutex locks to manage access to shared resources, ensuring thread safety while minimizing contention. +module main + +import sync + +const tiny_bad_request_response = 'HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes() + +#include +#include +#include +#include +#include +#include + +fn C.socket(__domain int, __type int, __protocol int) int + +fn C.bind(sockfd int, addr &Addr, addrlen u32) int + +fn C.send(__fd int, __buf voidptr, __n usize, __flags int) int + +fn C.recv(__fd int, __buf voidptr, __n usize, __flags int) int + +fn C.setsockopt(__fd int, __level int, __optname int, __optval voidptr, __optlen u32) int + +fn C.listen(__fd int, __n int) int + +struct In_addr { + s_addr int +} + +enum In_port_t as u16 { + ipproto_hopopts = 0 + ipproto_routing = 43 + ipproto_fragment = 44 + ipproto_icmpv6 = 58 + ipproto_none = 59 + ipproto_dstopts = 60 + ipproto_mh = 135 +} + +struct Sockaddr_in { + sin_family u16 + sin_port u16 + sin_addr In_addr + sin_zero [8]u8 +} + +fn C.htons(__hostshort u16) u16 + +@[typedef] +union C.epoll_data { +mut: + ptr voidptr + fd int + u32 u32 + u64 u64 +} + +pub struct C.epoll_event { + events u32 + data C.epoll_data +} + +fn C.epoll_create1(__flags int) int + +fn C.epoll_ctl(__epfd int, __op int, __fd int, __event &C.epoll_event) int + +fn C.epoll_wait(__epfd int, __events &C.epoll_event, __maxevents int, __timeout int) int + +struct Server { +mut: + server_socket int + epoll_fd int + lock_flag sync.Mutex + has_clients int + threads [max_thread_pool_size]thread + request_handler fn (HttpRequest) ![]u8 @[required] +} + +fn C.fcntl(fd int, cmd int, arg int) int +fn C.perror(s &u8) voidptr +fn C.close(fd int) int +fn C.accept(sockfd int, address &C.sockaddr_in, addrlen &u32) int + +const sock_stream = C.SOCK_STREAM +const sock_nonblock = C.SOCK_NONBLOCK + +const max_unix_path = 108 + +const max_connection_size = 1024 + +@[_pack: '1'] +pub struct Ip6 { + port u16 + flow_info u32 + addr [16]u8 + scope_id u32 +} + +@[_pack: '1'] +pub struct Ip { + port u16 + addr [4]u8 + sin_pad [8]u8 +} + +pub struct Unix { + path [max_unix_path]u8 +} + +union AddrData { + Unix + Ip + Ip6 +} + +@[_pack: '1'] +pub struct Addr { +pub: + f u16 + addr AddrData +} + +fn set_blocking(fd int, blocking bool) { + flags := C.fcntl(fd, C.F_GETFL, 0) + if flags == -1 { + return + } + if blocking { + C.fcntl(fd, C.F_SETFL, flags & ~C.O_NONBLOCK) + } else { + C.fcntl(fd, C.F_SETFL, flags | C.O_NONBLOCK) + } +} + +fn create_server_socket(port int) int { + server_fd := C.socket(2, sock_stream | sock_nonblock, 0) + if server_fd < 0 { + eprintln(@LOCATION) + C.perror('Socket creation failed'.str) + return -1 + } + + // Enable SO_REUSEPORT + opt := 1 + if C.setsockopt(server_fd, C.SOL_SOCKET, C.SO_REUSEPORT, &opt, sizeof(opt)) < 0 { + eprintln(@LOCATION) + C.perror('setsockopt SO_REUSEPORT failed'.str) + C.close(server_fd) + return -1 + } + + server_addr := Sockaddr_in{ + sin_family: 2 // ip + sin_port: C.htons(port) + sin_addr: In_addr{C.INADDR_ANY} + sin_zero: [8]u8{} + } + + if C.bind(server_fd, voidptr(&server_addr), sizeof(server_addr)) < 0 { + eprintln(@LOCATION) + C.perror('Bind failed'.str) + C.close(server_fd) + return -1 + } + if C.listen(server_fd, max_connection_size) < 0 { + eprintln(@LOCATION) + C.perror('Listen failed'.str) + C.close(server_fd) + return -1 + } + return server_fd +} + +// Function to add a file descriptor to the epoll instance +fn add_fd_to_epoll(epoll_fd int, fd int, events u32) int { + mut ev := C.epoll_event{ + events: events + } + ev.data.fd = fd + if C.epoll_ctl(epoll_fd, C.EPOLL_CTL_ADD, fd, &ev) == -1 { + eprintln(@LOCATION) + C.perror('epoll_ctl'.str) + return -1 + } + return 0 +} + +// Function to remove a file descriptor from the epoll instance +fn remove_fd_from_epoll(epoll_fd int, fd int) { + C.epoll_ctl(epoll_fd, C.EPOLL_CTL_DEL, fd, C.NULL) +} + +fn handle_accept(server &Server) { + for { + client_fd := C.accept(server.server_socket, C.NULL, C.NULL) + if client_fd < 0 { + // Check for EAGAIN or EWOULDBLOCK, usually represented by errno 11. + if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK { + break // No more incoming connections; exit loop. + } + + eprintln(@LOCATION) + C.perror('Accept failed'.str) + return + } + + // Set the client socket to non-blocking mode if accepted successfully + set_blocking(client_fd, false) + unsafe { + server.lock_flag.lock() + if add_fd_to_epoll(server.epoll_fd, client_fd, u32(C.EPOLLIN | C.EPOLLET)) == -1 { + C.close(client_fd) + } + server.lock_flag.unlock() + } + } +} + +fn handle_client_closure(server &Server, client_fd int) { + unsafe { + server.lock_flag.lock() + remove_fd_from_epoll(client_fd, client_fd) + server.lock_flag.unlock() + } +} + +@[manualfree] +fn process_events(server &Server) { + events := [max_connection_size]C.epoll_event{} + num_events := C.epoll_wait(server.epoll_fd, &events[0], max_connection_size, -1) + for i := 0; i < num_events; i++ { + if events[i].events & u32((C.EPOLLHUP | C.EPOLLERR)) != 0 { + handle_client_closure(server, unsafe { events[i].data.fd }) + continue + } + if events[i].events & u32(C.EPOLLIN) != 0 { + request_buffer := [140]u8{} + bytes_read := C.recv(unsafe { events[i].data.fd }, &request_buffer[0], 140 - 1, + 0) + if bytes_read > 0 { + mut readed_request_buffer := []u8{cap: bytes_read} + + unsafe { + readed_request_buffer.push_many(&request_buffer[0], bytes_read) + } + + decoded_http_request := decode_http_request(readed_request_buffer) or { + eprintln('Error decoding request ${err}') + C.send(unsafe { events[i].data.fd }, tiny_bad_request_response.data, + tiny_bad_request_response.len, 0) + handle_client_closure(server, unsafe { events[i].data.fd }) + continue + } + + // This lock is a workaround for avoiding race condition in router.params + // This slows down the server, but it's a temporary solution + (*server).lock_flag.lock() + response_buffer := (*server).request_handler(decoded_http_request) or { + eprintln('Error handling request ${err}') + C.send(unsafe { events[i].data.fd }, tiny_bad_request_response.data, + tiny_bad_request_response.len, 0) + handle_client_closure(server, unsafe { events[i].data.fd }) + (*server).lock_flag.unlock() + continue + } + (*server).lock_flag.unlock() + + C.send(unsafe { events[i].data.fd }, response_buffer.data, response_buffer.len, + 0) + handle_client_closure(server, unsafe { events[i].data.fd }) + } else if bytes_read == 0 + || (bytes_read < 0 && C.errno != C.EAGAIN && C.errno != C.EWOULDBLOCK) { + handle_client_closure(server, unsafe { events[i].data.fd }) + } + } + } +} + +fn worker_thread(server &Server) { + for { + process_events(server) + } + return +} + +fn event_loop(server &Server) { + for { + handle_accept(server) + } +} diff --git a/examples/server/v.mod b/examples/server/v.mod new file mode 100644 index 00000000000000..78299892878e56 --- /dev/null +++ b/examples/server/v.mod @@ -0,0 +1,7 @@ +Module { + name: 'vanilla' + description: 'A raw V server' + version: '0.0.1' + license: 'MIT' + dependencies: [] +} From 1c9a1deccf0610380e12ee0deb33e017ea962aa8 Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sun, 8 Dec 2024 09:34:10 -0400 Subject: [PATCH 02/10] file renaming --- examples/{server => vanilla_http_server}/.editorconfig | 0 examples/{server => vanilla_http_server}/.gitattributes | 0 examples/{server => vanilla_http_server}/.gitignore | 0 examples/{server => vanilla_http_server}/README.md | 0 examples/{server => vanilla_http_server}/src/controllers.v | 0 examples/{server => vanilla_http_server}/src/main.v | 0 examples/{server => vanilla_http_server}/src/request_parser.v | 0 examples/{server => vanilla_http_server}/src/router.v | 0 examples/{server => vanilla_http_server}/src/server.c.v | 0 examples/{server => vanilla_http_server}/v.mod | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename examples/{server => vanilla_http_server}/.editorconfig (100%) rename examples/{server => vanilla_http_server}/.gitattributes (100%) rename examples/{server => vanilla_http_server}/.gitignore (100%) rename examples/{server => vanilla_http_server}/README.md (100%) rename examples/{server => vanilla_http_server}/src/controllers.v (100%) rename examples/{server => vanilla_http_server}/src/main.v (100%) rename examples/{server => vanilla_http_server}/src/request_parser.v (100%) rename examples/{server => vanilla_http_server}/src/router.v (100%) rename examples/{server => vanilla_http_server}/src/server.c.v (100%) rename examples/{server => vanilla_http_server}/v.mod (100%) diff --git a/examples/server/.editorconfig b/examples/vanilla_http_server/.editorconfig similarity index 100% rename from examples/server/.editorconfig rename to examples/vanilla_http_server/.editorconfig diff --git a/examples/server/.gitattributes b/examples/vanilla_http_server/.gitattributes similarity index 100% rename from examples/server/.gitattributes rename to examples/vanilla_http_server/.gitattributes diff --git a/examples/server/.gitignore b/examples/vanilla_http_server/.gitignore similarity index 100% rename from examples/server/.gitignore rename to examples/vanilla_http_server/.gitignore diff --git a/examples/server/README.md b/examples/vanilla_http_server/README.md similarity index 100% rename from examples/server/README.md rename to examples/vanilla_http_server/README.md diff --git a/examples/server/src/controllers.v b/examples/vanilla_http_server/src/controllers.v similarity index 100% rename from examples/server/src/controllers.v rename to examples/vanilla_http_server/src/controllers.v diff --git a/examples/server/src/main.v b/examples/vanilla_http_server/src/main.v similarity index 100% rename from examples/server/src/main.v rename to examples/vanilla_http_server/src/main.v diff --git a/examples/server/src/request_parser.v b/examples/vanilla_http_server/src/request_parser.v similarity index 100% rename from examples/server/src/request_parser.v rename to examples/vanilla_http_server/src/request_parser.v diff --git a/examples/server/src/router.v b/examples/vanilla_http_server/src/router.v similarity index 100% rename from examples/server/src/router.v rename to examples/vanilla_http_server/src/router.v diff --git a/examples/server/src/server.c.v b/examples/vanilla_http_server/src/server.c.v similarity index 100% rename from examples/server/src/server.c.v rename to examples/vanilla_http_server/src/server.c.v diff --git a/examples/server/v.mod b/examples/vanilla_http_server/v.mod similarity index 100% rename from examples/server/v.mod rename to examples/vanilla_http_server/v.mod From 1f23236df7f5b5dba0601c97a7839df55e38b156 Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sun, 8 Dec 2024 09:36:19 -0400 Subject: [PATCH 03/10] CI: include vanilla_http_server in efolders array --- cmd/tools/vbuild-examples.v | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/tools/vbuild-examples.v b/cmd/tools/vbuild-examples.v index 46cc3b9d6e04ce..7d1e535b869de3 100644 --- a/cmd/tools/vbuild-examples.v +++ b/cmd/tools/vbuild-examples.v @@ -10,6 +10,7 @@ const efolders = [ 'examples/viewer', 'examples/vweb_orm_jwt', 'examples/vweb_fullstack', + 'examples/vanilla_http_server', ] pub fn normalised_vroot_path(path string) string { From 909626fa22a6e33455fa4f2710b50faeed5ca480 Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sun, 8 Dec 2024 10:46:02 -0400 Subject: [PATCH 04/10] docs: ops --- examples/vanilla_http_server/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vanilla_http_server/README.md b/examples/vanilla_http_server/README.md index f0921102d9eee3..cb6ee719fd892c 100644 --- a/examples/vanilla_http_server/README.md +++ b/examples/vanilla_http_server/README.md @@ -32,7 +32,7 @@ This will start the server, and you can access it at `http://localhost:3000`. The main server logic is implemented in [src/main.v](v/vanilla/src/main.v). The server is initialized and started in the `main` function: -```v +```v ignore module main const port = 3000 From 5cb2a54cad5ee89aad6edb2f48e8b16b084c5205 Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sun, 8 Dec 2024 12:44:55 -0400 Subject: [PATCH 05/10] docs: ops --- examples/vanilla_http_server/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/vanilla_http_server/README.md b/examples/vanilla_http_server/README.md index cb6ee719fd892c..3fb82a52bc0932 100644 --- a/examples/vanilla_http_server/README.md +++ b/examples/vanilla_http_server/README.md @@ -4,7 +4,8 @@ Vanilla is a raw V server. ## Description -This project is a simple server written in the V programming language. It aims to provide a minimalistic and efficient server implementation. +This project is a simple server written in the V programming language. +It aims to provide a minimalistic and efficient server implementation. ## Features @@ -14,7 +15,8 @@ This project is a simple server written in the V programming language. It aims t ## Installation -To install Vanilla, you need to have the V compiler installed. You can download it from the [official V website](https://vlang.io). +To install Vanilla, you need to have the V compiler installed. +You can download it from the [official V website](https://vlang.io). ## Usage @@ -30,7 +32,8 @@ This will start the server, and you can access it at `http://localhost:3000`. ### Main Server -The main server logic is implemented in [src/main.v](v/vanilla/src/main.v). The server is initialized and started in the `main` function: +The main server logic is implemented in [src/main.v](v/vanilla/src/main.v). +The server is initialized and started in the `main` function: ```v ignore module main From b51592d8649f992eeb3af9a87e7dad73d999d187 Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sun, 8 Dec 2024 12:57:05 -0400 Subject: [PATCH 06/10] fix: try to make it works on Windows --- examples/vanilla_http_server/src/server.c.v | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/vanilla_http_server/src/server.c.v b/examples/vanilla_http_server/src/server.c.v index 93542dee8a950d..17792cf1009035 100644 --- a/examples/vanilla_http_server/src/server.c.v +++ b/examples/vanilla_http_server/src/server.c.v @@ -16,7 +16,11 @@ import sync const tiny_bad_request_response = 'HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes() -#include +$if windows { + #include +} $else { + #include +} #include #include #include From ca1f9352db48042c5b34aa2d8ed17aed4e462372 Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sun, 8 Dec 2024 21:13:29 -0400 Subject: [PATCH 07/10] improvements --- examples/vanilla_http_server/src/{main.v => main.c.v} | 0 examples/vanilla_http_server/src/request_parser.v | 1 + examples/vanilla_http_server/src/router.v | 1 - 3 files changed, 1 insertion(+), 1 deletion(-) rename examples/vanilla_http_server/src/{main.v => main.c.v} (100%) delete mode 100644 examples/vanilla_http_server/src/router.v diff --git a/examples/vanilla_http_server/src/main.v b/examples/vanilla_http_server/src/main.c.v similarity index 100% rename from examples/vanilla_http_server/src/main.v rename to examples/vanilla_http_server/src/main.c.v diff --git a/examples/vanilla_http_server/src/request_parser.v b/examples/vanilla_http_server/src/request_parser.v index e8fe13bd7da225..6ddb2dc13e0b98 100644 --- a/examples/vanilla_http_server/src/request_parser.v +++ b/examples/vanilla_http_server/src/request_parser.v @@ -13,6 +13,7 @@ mut: version Slice } +@[direct_array_access] fn parse_request_line(mut req HttpRequest) ! { mut i := 0 // Parse HTTP method diff --git a/examples/vanilla_http_server/src/router.v b/examples/vanilla_http_server/src/router.v deleted file mode 100644 index 6bd0de5a06b7ee..00000000000000 --- a/examples/vanilla_http_server/src/router.v +++ /dev/null @@ -1 +0,0 @@ -module main From 3895b264b9af2080d8d4ee9e60153f2c6090aa7e Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sun, 8 Dec 2024 21:50:04 -0400 Subject: [PATCH 08/10] remove wrong manualfree attribute --- examples/vanilla_http_server/src/server.c.v | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/vanilla_http_server/src/server.c.v b/examples/vanilla_http_server/src/server.c.v index 17792cf1009035..d0a831ac8711b9 100644 --- a/examples/vanilla_http_server/src/server.c.v +++ b/examples/vanilla_http_server/src/server.c.v @@ -240,7 +240,6 @@ fn handle_client_closure(server &Server, client_fd int) { } } -@[manualfree] fn process_events(server &Server) { events := [max_connection_size]C.epoll_event{} num_events := C.epoll_wait(server.epoll_fd, &events[0], max_connection_size, -1) From e192eec6ccd01968d5152bed96b8437a42c070e6 Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sun, 8 Dec 2024 22:03:34 -0400 Subject: [PATCH 09/10] ops --- examples/vanilla_http_server/src/controllers.v | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/vanilla_http_server/src/controllers.v b/examples/vanilla_http_server/src/controllers.v index 9abfbc16f04e54..03cfcee3ec284a 100644 --- a/examples/vanilla_http_server/src/controllers.v +++ b/examples/vanilla_http_server/src/controllers.v @@ -2,7 +2,6 @@ module main import strings -const h_response_body = '{"message": "Hello, world!"}' const http_ok_response = 'HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 1\r\nConnection: keep-alive\r\n\r\n1'.bytes() const http_created_response = 'HTTP/1.1 201 Created\r\nContent-Type: application/json\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n'.bytes() From 2abb3da0a852afaa616d585d010f178ac56c6d40 Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Sun, 8 Dec 2024 23:19:04 -0400 Subject: [PATCH 10/10] fix: http_ok_response --- examples/vanilla_http_server/src/controllers.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vanilla_http_server/src/controllers.v b/examples/vanilla_http_server/src/controllers.v index 03cfcee3ec284a..7a1cbcea1acc8a 100644 --- a/examples/vanilla_http_server/src/controllers.v +++ b/examples/vanilla_http_server/src/controllers.v @@ -2,7 +2,7 @@ module main import strings -const http_ok_response = 'HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 1\r\nConnection: keep-alive\r\n\r\n1'.bytes() +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() const http_created_response = 'HTTP/1.1 201 Created\r\nContent-Type: application/json\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n'.bytes()