Skip to content

Commit

Permalink
V: pure v server (#7932)
Browse files Browse the repository at this point in the history
* update v

* v: pure V server

* doc

* doc

* remove binary

* manualfree

* manualfree

* changes after code review

* fix: router problem removing Router. It also, make everything faster

* remove  until find a better solution

* remove gitignore

* update doc

* update V version

* fix: wrong http_ok_response

* ops
  • Loading branch information
enghitalo authored Dec 8, 2024
1 parent 629b809 commit 76c7c03
Show file tree
Hide file tree
Showing 12 changed files with 615 additions and 2 deletions.
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"
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

0 comments on commit 76c7c03

Please sign in to comment.