Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V: pure v server #7932

Merged
merged 22 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions v/config.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
language:
version: "weekly.2024.44"
version: "weekly.2024.45"
files:
- "**/*.v"
- server
bootstrap:
- cd /opt/vlang && git fetch --all --tags && git checkout tags/weekly.2024.44 && make && v -version && cd /app
- cd /opt/vlang && git fetch --all --tags && git checkout tags/weekly.2024.45 && make && v -version && cd /app
build_flags:
- -prod -cc gcc

Expand Down
8 changes: 8 additions & 0 deletions v/v/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.v]
indent_style = tab
8 changes: 8 additions & 0 deletions v/v/.gitattributes
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions v/v/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Binaries for programs and plugins
main
a
*.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
94 changes: 94 additions & 0 deletions v/v/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# 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)
}
```

### Router

The router setup and route handling are implemented in [src/router.v](v/vanilla/src/router.v). The `setup_router` function initializes the router and adds routes with their respective handler functions:

```v
fn setup_router() Router {
mut router := Router{
root: RadixNode{
children: map[string]&RadixNode{}
}
}
// Adding routes with handler functions
router.add_route('GET', '/', home_controller)
router.add_route('GET', '/user', get_users_controller)
router.add_route('GET', '/user/:id', get_user_controller)
router.add_route('POST', '/user', create_user_controller)
return router
}
```
2 changes: 2 additions & 0 deletions v/v/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
framework:
github: vlang/v
41 changes: 41 additions & 0 deletions v/v/src/controllers.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module main

const h_response_body = '{"message": "Hello, world!"}'
enghitalo marked this conversation as resolved.
Show resolved Hide resolved
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()

fn home_controller(params map[string]string) ![]u8 {
return http_ok_response
}

fn get_users_controller(params map[string]string) ![]u8 {
return http_ok_response
}

@[manualfree]
fn get_user_controller(params map[string]string) ![]u8 {
id := params['id'] or { return error('User ID required') }
response_body := id

response := 'HTTP/1.1 200 OK\r
Content-Type: text/plain\r
Content-Length: ${response_body.len}\r
Connection: keep-alive\r
\r
${response_body}'.bytes()

defer {
unsafe {
id.free()
response_body.free()
response.free()
}
}

return response
}

fn create_user_controller(params map[string]string) ![]u8 {
return http_ok_response
}
39 changes: 39 additions & 0 deletions v/v/src/main.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module main

const port = 3000
const max_thread_pool_size = 8

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 < max_thread_pool_size; i++ {
server.threads[i] = spawn worker_thread(&server)
}
println('listening on http://localhost:${port}/')
event_loop(&server)
}
127 changes: 127 additions & 0 deletions v/v/src/request_parser.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
module main

struct Slice {
start int
len int
}

struct HttpRequest {
mut:
buffer []u8
method Slice
path Slice
version Slice
headers map[string]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 parse_headers(mut req HttpRequest, offset &int) ! {
for offset < req.buffer.len {
// End of headers
if req.buffer[*offset] == `\r` && req.buffer[*offset + 1] == `\n` {
unsafe {
*offset += 2
}
break
}

// Parse header name
mut header_start := *offset
for offset < req.buffer.len && req.buffer[*offset] != `:` {
unsafe {
*offset = *offset + 1
}
}
header_name := req.buffer[header_start..*offset].bytestr()
unsafe {
*offset++ // Skip the colon
}
// Skip whitespace after the colon
for offset < req.buffer.len && req.buffer[*offset] == ` ` {
unsafe {
*offset++
}
}

// Parse header value
mut value_start := *offset
for offset < req.buffer.len && req.buffer[*offset] != `\r` {
unsafe {
*offset++
}
}
header_value := Slice{
start: value_start
len: *offset - value_start
}
req.headers[header_name] = header_value

// Move to the next line
if *offset + 1 < req.buffer.len && req.buffer[*offset] == `\r`
&& req.buffer[*offset + 1] == `\n` {
unsafe {
*offset += 2
}
} else {
return error('Invalid header line')
}
}
}

fn decode_http_request(buffer []u8) !HttpRequest {
mut req := HttpRequest{
buffer: buffer
headers: map[string]Slice{}
}
offset := 0

parse_request_line(mut req)!
parse_headers(mut req, &offset)!

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()
}
Loading
Loading