Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,290 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user