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 all 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.49"
version: "latest"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could master be considered as stable ?

I mean since v is not at 1.0, it could be ok to point to master, but it could be inconvenient to fix compilation issues (or else) in there is some

Copy link

@ylluminate ylluminate Dec 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems reasonable to me. V is so fast moving at the moment that this definitely makes sense @waghanza. I frankly don't think anyone is going to want to support older snapshots with so many updates coming in constantly and consistently... but I should defer to @enghitalo on his intention here.

Copy link
Contributor Author

@enghitalo enghitalo Dec 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@waghanza For now, master is as stable as previous releases. I changed to master because "weekly.2024.49" is breaking the CI.

files:
- "**/*.v"
- server
bootstrap:
- cd /opt/vlang && git fetch --all --tags && git checkout tags/weekly.2024.49 && make && v -version && cd /app
- cd /opt/vlang && git fetch --all --tags && git checkout master && 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
91 changes: 91 additions & 0 deletions v/v/README.md
Original file line number Diff line number Diff line change
@@ -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
```
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
42 changes: 42 additions & 0 deletions v/v/src/controllers.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module main

import strings

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 []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
}
37 changes: 37 additions & 0 deletions v/v/src/main.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module main

const port = 3000
const max_thread_pool_size = 8

fn main() {
mut server := Server{}

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)
}
70 changes: 70 additions & 0 deletions v/v/src/request_parser.v
Original file line number Diff line number Diff line change
@@ -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()
}
23 changes: 23 additions & 0 deletions v/v/src/router.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module main

// 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
}
Loading
Loading