291 lines
7.6 KiB
V
291 lines
7.6 KiB
V
// Copyright (c) 2019-2025 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
module fasthttp
|
|
|
|
import runtime
|
|
import net
|
|
import time
|
|
|
|
#include <errno.h>
|
|
|
|
$if !windows {
|
|
#include <fcntl.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
}
|
|
|
|
const max_thread_pool_size = runtime.nr_cpus()
|
|
const max_connection_size = 65536 // Max events per epoll_wait
|
|
|
|
const tiny_bad_request_response = 'HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes()
|
|
const status_444_response = 'HTTP/1.1 444 No Response\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes()
|
|
const status_413_response = 'HTTP/1.1 413 Payload Too Large\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'.bytes()
|
|
|
|
fn C.socket(domain i32, typ i32, protocol i32) i32
|
|
|
|
fn C.bind(sockfd i32, addr &net.Addr, addrlen u32) i32
|
|
|
|
fn C.send(__fd i32, __buf voidptr, __n usize, __flags i32) i32
|
|
|
|
fn C.recv(__fd i32, __buf voidptr, __n usize, __flags i32) i32
|
|
|
|
fn C.setsockopt(__fd i32, __level i32, __optname i32, __optval voidptr, __optlen u32) i32
|
|
|
|
fn C.listen(__fd i32, __n i32) i32
|
|
|
|
fn C.perror(s &u8)
|
|
|
|
fn C.close(fd i32) i32
|
|
|
|
fn C.htons(__hostshort u16) u16
|
|
|
|
fn C.fcntl(fd i32, cmd i32, arg i32) i32
|
|
|
|
pub struct Slice {
|
|
pub:
|
|
start int
|
|
len int
|
|
}
|
|
|
|
// HttpRequest represents an HTTP request.
|
|
// TODO make fields immutable
|
|
pub struct HttpRequest {
|
|
pub mut:
|
|
buffer []u8 // A V slice of the read buffer for convenience
|
|
method Slice
|
|
path Slice
|
|
version Slice
|
|
header_fields Slice
|
|
body Slice
|
|
client_conn_fd int
|
|
user_data voidptr // User-defined context data
|
|
}
|
|
|
|
pub enum ResponseTakeoverMode {
|
|
none
|
|
manual
|
|
reusable
|
|
}
|
|
|
|
pub struct HttpResponse {
|
|
pub mut:
|
|
content []u8
|
|
file_path string
|
|
takeover_mode ResponseTakeoverMode
|
|
should_close bool // if true, close the connection after sending (Connection: close)
|
|
// content_owned lets the backend free or move content after it has been sent.
|
|
content_owned bool
|
|
// request_arena is a prealloc scope handle that must be freed after sending.
|
|
request_arena voidptr
|
|
}
|
|
|
|
fn (mut resp HttpResponse) free_owned_content() {
|
|
if resp.content_owned && resp.content.cap > 0 {
|
|
unsafe { resp.content.free() }
|
|
resp.content = []u8{}
|
|
}
|
|
}
|
|
|
|
fn (mut resp HttpResponse) take_or_clone_content() []u8 {
|
|
if resp.content_owned {
|
|
content := resp.content
|
|
resp.content = []u8{}
|
|
return content
|
|
}
|
|
return resp.content.clone()
|
|
}
|
|
|
|
fn end_request_arena_current_thread(request_arena voidptr) {
|
|
$if prealloc {
|
|
if request_arena != unsafe { nil } {
|
|
unsafe { prealloc_scope_end(request_arena) }
|
|
}
|
|
}
|
|
}
|
|
|
|
fn leave_request_arena_current_thread(request_arena voidptr) {
|
|
$if prealloc {
|
|
if request_arena != unsafe { nil } {
|
|
unsafe { prealloc_scope_leave(request_arena) }
|
|
}
|
|
}
|
|
}
|
|
|
|
fn abandon_request_arena_current_thread(request_arena voidptr) {
|
|
$if prealloc {
|
|
if request_arena != unsafe { nil } {
|
|
unsafe { prealloc_scope_abandon(request_arena) }
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut resp HttpResponse) attach_request_arena_if_empty(request_arena voidptr) {
|
|
if resp.request_arena == unsafe { nil } {
|
|
resp.request_arena = request_arena
|
|
}
|
|
}
|
|
|
|
fn (mut resp HttpResponse) end_request_arena_current_thread() {
|
|
end_request_arena_current_thread(resp.request_arena)
|
|
resp.request_arena = unsafe { nil }
|
|
}
|
|
|
|
fn (mut resp HttpResponse) abandon_request_arena_current_thread() {
|
|
abandon_request_arena_current_thread(resp.request_arena)
|
|
resp.request_arena = unsafe { nil }
|
|
}
|
|
|
|
fn (mut resp HttpResponse) take_request_arena() voidptr {
|
|
request_arena := resp.request_arena
|
|
resp.request_arena = unsafe { nil }
|
|
return request_arena
|
|
}
|
|
|
|
// ServerConfig bundles the parameters needed to start a fasthttp server.
|
|
pub struct ServerConfig {
|
|
pub:
|
|
family net.AddrFamily = .ip6
|
|
port int = 3000
|
|
max_request_buffer_size int = 8192
|
|
timeout_in_seconds int = 30
|
|
handler fn (HttpRequest) !HttpResponse @[required]
|
|
user_data voidptr
|
|
}
|
|
|
|
// ShutdownParams configures how long graceful shutdown should wait for in-flight requests.
|
|
@[params]
|
|
pub struct ShutdownParams {
|
|
pub:
|
|
timeout time.Duration = time.infinite
|
|
retry_period_ms int = 10
|
|
}
|
|
|
|
// WaitTillRunningParams allows parametrizing the calls to `ServerHandle.wait_till_running()`.
|
|
@[params]
|
|
pub struct WaitTillRunningParams {
|
|
pub:
|
|
max_retries int = 100
|
|
retry_period_ms int = 10
|
|
}
|
|
|
|
// ServerHandle exposes lifecycle controls for a running `fasthttp.Server`.
|
|
pub struct ServerHandle {
|
|
ptr voidptr
|
|
}
|
|
|
|
// handle returns a reusable handle for waiting on or shutting down the server.
|
|
pub fn (s &Server) handle() ServerHandle {
|
|
return ServerHandle{
|
|
ptr: s
|
|
}
|
|
}
|
|
|
|
// wait_till_running waits until the server transitions to its serving state.
|
|
pub fn (h ServerHandle) wait_till_running(params WaitTillRunningParams) !int {
|
|
if h.ptr == unsafe { nil } {
|
|
return error('server handle is not initialized')
|
|
}
|
|
$if linux || bsd {
|
|
mut server := unsafe { &Server(h.ptr) }
|
|
return server.wait_till_running_impl(params)!
|
|
} $else {
|
|
return error('fasthttp server lifecycle control is only supported on linux and BSD-family OSes')
|
|
}
|
|
}
|
|
|
|
// shutdown gracefully stops accepting new requests and waits for active requests to finish.
|
|
pub fn (h ServerHandle) shutdown(params ShutdownParams) ! {
|
|
if h.ptr == unsafe { nil } {
|
|
return error('server handle is not initialized')
|
|
}
|
|
$if linux || bsd {
|
|
mut server := unsafe { &Server(h.ptr) }
|
|
server.shutdown_impl(params)!
|
|
} $else {
|
|
return error('fasthttp server lifecycle control is only supported on linux and BSD-family OSes')
|
|
}
|
|
}
|
|
|
|
$if linux || bsd {
|
|
fn normalized_retry_period_ms(retry_period_ms int) int {
|
|
return if retry_period_ms > 0 { retry_period_ms } else { 1 }
|
|
}
|
|
|
|
fn (mut s Server) wait_till_running_impl(params WaitTillRunningParams) !int {
|
|
retry_period_ms := normalized_retry_period_ms(params.retry_period_ms)
|
|
mut attempts := 0
|
|
mut running := s.running
|
|
for !running.load() && attempts < params.max_retries {
|
|
time.sleep(retry_period_ms * time.millisecond)
|
|
attempts++
|
|
}
|
|
if !running.load() {
|
|
return error('maximum retries reached')
|
|
}
|
|
time.sleep(retry_period_ms * time.millisecond)
|
|
return attempts
|
|
}
|
|
|
|
fn (mut s Server) shutdown_impl(params ShutdownParams) ! {
|
|
mut stopped := s.stopped
|
|
if stopped.load() {
|
|
return
|
|
}
|
|
mut shutting_down := s.shutting_down
|
|
if shutting_down.compare_and_swap(false, true) {
|
|
s.stop_accepting()
|
|
}
|
|
retry_period_ms := normalized_retry_period_ms(params.retry_period_ms)
|
|
mut watch := time.new_stopwatch()
|
|
for !stopped.load() {
|
|
if params.timeout != time.infinite && watch.elapsed() >= params.timeout {
|
|
return error('graceful shutdown timed out after ${params.timeout}')
|
|
}
|
|
time.sleep(retry_period_ms * time.millisecond)
|
|
}
|
|
}
|
|
|
|
fn (s &Server) begin_request() {
|
|
mut active_requests := s.active_requests
|
|
active_requests.add(1)
|
|
}
|
|
|
|
fn (s &Server) end_request() {
|
|
mut active_requests := s.active_requests
|
|
active_requests.sub(1)
|
|
}
|
|
|
|
fn (s &Server) active_request_count() int {
|
|
mut active_requests := s.active_requests
|
|
return active_requests.load()
|
|
}
|
|
|
|
fn (s &Server) is_shutting_down() bool {
|
|
mut shutting_down := s.shutting_down
|
|
return shutting_down.load()
|
|
}
|
|
|
|
fn (s &Server) is_stopped() bool {
|
|
mut stopped := s.stopped
|
|
return stopped.load()
|
|
}
|
|
|
|
fn (mut s Server) mark_running() {
|
|
mut running := s.running
|
|
running.store(true)
|
|
mut stopped := s.stopped
|
|
stopped.store(false)
|
|
}
|
|
|
|
fn (mut s Server) mark_stopped() {
|
|
mut active_requests := s.active_requests
|
|
active_requests.store(0)
|
|
mut running := s.running
|
|
running.store(false)
|
|
mut stopped := s.stopped
|
|
stopped.store(true)
|
|
}
|
|
}
|