2020-01-23 05:26:30 +03:00
|
|
|
module picoev
|
|
|
|
|
|
|
|
import picohttpparser
|
2023-07-12 09:40:16 +03:00
|
|
|
import time
|
|
|
|
|
|
|
|
pub const (
|
|
|
|
max_fds = 1024
|
|
|
|
max_queue = 4096
|
|
|
|
|
|
|
|
// events
|
|
|
|
picoev_read = 1
|
|
|
|
picoev_write = 2
|
|
|
|
picoev_timeout = 4
|
|
|
|
picoev_add = 0x40000000
|
|
|
|
picoev_del = 0x20000000
|
|
|
|
picoev_readwrite = 3 // 1 xor 2
|
2021-05-01 14:20:10 +03:00
|
|
|
)
|
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
// Target is a data representation of everything that needs to be associated with a single
|
|
|
|
// file descriptor (connection)
|
|
|
|
pub struct Target {
|
|
|
|
pub mut:
|
|
|
|
fd int
|
|
|
|
loop_id int = -1
|
|
|
|
events u32
|
|
|
|
cb fn (int, int, voidptr)
|
|
|
|
// used internally by the kqueue implementation
|
|
|
|
backend int
|
2021-05-01 14:20:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Config {
|
|
|
|
pub:
|
|
|
|
port int = 8080
|
|
|
|
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response)
|
|
|
|
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb
|
2022-07-21 21:01:30 +03:00
|
|
|
user_data voidptr = unsafe { nil }
|
2021-05-01 14:20:10 +03:00
|
|
|
timeout_secs int = 8
|
|
|
|
max_headers int = 100
|
2023-06-08 01:54:15 +03:00
|
|
|
max_read int = 4096
|
|
|
|
max_write int = 8192
|
2021-05-01 14:20:10 +03:00
|
|
|
}
|
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
[heap]
|
|
|
|
pub struct Picoev {
|
|
|
|
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response)
|
|
|
|
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb
|
|
|
|
user_data voidptr = unsafe { nil }
|
|
|
|
|
2021-05-01 14:20:10 +03:00
|
|
|
timeout_secs int
|
2023-07-12 09:40:16 +03:00
|
|
|
max_headers int = 100
|
|
|
|
max_read int = 4096
|
|
|
|
max_write int = 8192
|
2021-05-01 14:20:10 +03:00
|
|
|
mut:
|
2023-07-12 09:40:16 +03:00
|
|
|
loop &LoopType = unsafe { nil }
|
|
|
|
file_descriptors [max_fds]&Target
|
|
|
|
timeouts map[int]i64
|
|
|
|
num_loops int
|
|
|
|
|
|
|
|
buf &u8 = unsafe { nil }
|
|
|
|
idx [1024]int
|
|
|
|
out &u8 = unsafe { nil }
|
|
|
|
|
|
|
|
date string
|
2021-05-01 14:20:10 +03:00
|
|
|
}
|
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
// init fills the `file_descriptors` array
|
|
|
|
pub fn (mut pv Picoev) init() {
|
|
|
|
assert picoev.max_fds > 0
|
|
|
|
|
|
|
|
pv.num_loops = 0
|
|
|
|
|
|
|
|
for i in 0 .. picoev.max_fds {
|
|
|
|
pv.file_descriptors[i] = &Target{}
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
2023-07-12 09:40:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// add adds a file descriptor to the loop
|
|
|
|
[direct_array_access]
|
|
|
|
pub fn (mut pv Picoev) add(fd int, events int, timeout int, cb voidptr) int {
|
|
|
|
assert fd < picoev.max_fds
|
|
|
|
|
|
|
|
mut target := pv.file_descriptors[fd]
|
|
|
|
target.fd = fd
|
|
|
|
target.cb = cb
|
|
|
|
target.loop_id = pv.loop.id
|
|
|
|
target.events = 0
|
|
|
|
|
|
|
|
if pv.update_events(fd, events | picoev.picoev_add) != 0 {
|
|
|
|
pv.del(fd)
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// update timeout
|
|
|
|
pv.set_timeout(fd, timeout)
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// del removes a file descriptor from the loop
|
|
|
|
[direct_array_access]
|
|
|
|
fn (mut pv Picoev) del(fd int) int {
|
|
|
|
assert fd < picoev.max_fds
|
|
|
|
mut target := pv.file_descriptors[fd]
|
|
|
|
|
|
|
|
$if trace_fd ? {
|
|
|
|
eprintln('delete ${fd}')
|
|
|
|
}
|
|
|
|
|
|
|
|
if pv.update_events(fd, picoev.picoev_del) != 0 {
|
|
|
|
target.loop_id = -1
|
|
|
|
target.fd = 0
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
pv.set_timeout(fd, 0)
|
|
|
|
target.loop_id = -1
|
|
|
|
target.fd = 0
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
fn (mut pv Picoev) loop_once(max_wait int) int {
|
|
|
|
pv.loop.now = get_time()
|
|
|
|
|
|
|
|
if pv.poll_once(max_wait) != 0 {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
if max_wait != 0 {
|
|
|
|
pv.loop.now = get_time()
|
|
|
|
}
|
|
|
|
|
|
|
|
pv.handle_timeout()
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// set_timeout sets the timeout in seconds for a file descriptor. If a timeout occurs
|
|
|
|
// the file descriptors target callback is called with a timeout event
|
|
|
|
[direct_array_access; inline]
|
|
|
|
fn (mut pv Picoev) set_timeout(fd int, secs int) {
|
|
|
|
assert fd < picoev.max_fds
|
|
|
|
if secs != 0 {
|
|
|
|
pv.timeouts[fd] = pv.loop.now + secs
|
|
|
|
} else {
|
|
|
|
pv.timeouts.delete(fd)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle_timeout loops over all file descriptors and removes them from the loop
|
|
|
|
// if they are timed out. Also the file descriptors target callback is called with a
|
|
|
|
// timeout event
|
|
|
|
[direct_array_access; inline]
|
|
|
|
fn (mut pv Picoev) handle_timeout() {
|
|
|
|
for fd, timeout in pv.timeouts {
|
|
|
|
if timeout <= pv.loop.now {
|
|
|
|
target := pv.file_descriptors[fd]
|
|
|
|
assert target.loop_id == pv.loop.id
|
|
|
|
|
|
|
|
pv.timeouts.delete(fd)
|
|
|
|
unsafe { target.cb(fd, picoev.picoev_timeout, &pv) }
|
2023-06-20 09:04:07 +03:00
|
|
|
}
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
// accept_callback accepts a new connection from `listen_fd` and adds it to the loop
|
|
|
|
fn accept_callback(listen_fd int, events int, cb_arg voidptr) {
|
|
|
|
mut pv := unsafe { &Picoev(cb_arg) }
|
|
|
|
newfd := accept(listen_fd)
|
|
|
|
if newfd >= picoev.max_fds {
|
|
|
|
// should never happen
|
|
|
|
close_socket(newfd)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
$if trace_fd ? {
|
|
|
|
eprintln('accept ${newfd}')
|
|
|
|
}
|
|
|
|
|
|
|
|
if newfd != -1 {
|
|
|
|
setup_sock(newfd) or {
|
|
|
|
eprintln('setup_sock failed, fd: ${newfd}, listen_fd: ${listen_fd}, err: ${err.code()}')
|
|
|
|
pv.err_cb(pv.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{},
|
|
|
|
err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pv.add(newfd, picoev.picoev_read, pv.timeout_secs, raw_callback)
|
|
|
|
}
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
// close_conn closes the socket `fd` and removes it from the loop
|
2020-01-23 05:26:30 +03:00
|
|
|
[inline]
|
2023-07-12 09:40:16 +03:00
|
|
|
pub fn (mut pv Picoev) close_conn(fd int) {
|
|
|
|
pv.del(fd)
|
|
|
|
close_socket(fd)
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
[direct_array_access]
|
|
|
|
fn raw_callback(fd int, events int, context voidptr) {
|
|
|
|
mut pv := unsafe { &Picoev(context) }
|
2021-05-01 14:20:10 +03:00
|
|
|
defer {
|
2023-07-12 09:40:16 +03:00
|
|
|
pv.idx[fd] = 0
|
2021-05-01 14:20:10 +03:00
|
|
|
}
|
2023-07-12 09:40:16 +03:00
|
|
|
|
|
|
|
if events & picoev.picoev_timeout != 0 {
|
|
|
|
$if trace_fd ? {
|
|
|
|
eprintln('timeout ${fd}')
|
|
|
|
}
|
|
|
|
pv.close_conn(fd)
|
2020-01-23 05:26:30 +03:00
|
|
|
return
|
2023-07-12 09:40:16 +03:00
|
|
|
} else if events & picoev.picoev_read != 0 {
|
|
|
|
pv.set_timeout(fd, pv.timeout_secs)
|
2021-04-24 13:21:30 +03:00
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
mut buf := pv.buf
|
2020-07-03 19:10:10 +03:00
|
|
|
unsafe {
|
2023-07-12 09:40:16 +03:00
|
|
|
buf += fd * pv.max_read // pointer magic
|
2020-07-03 19:10:10 +03:00
|
|
|
}
|
2021-04-24 13:21:30 +03:00
|
|
|
mut req := picohttpparser.Request{}
|
2021-05-01 14:20:10 +03:00
|
|
|
|
|
|
|
// Response init
|
2023-07-12 09:40:16 +03:00
|
|
|
mut out := pv.out
|
2021-05-01 14:20:10 +03:00
|
|
|
unsafe {
|
2023-07-12 09:40:16 +03:00
|
|
|
out += fd * pv.max_write // pointer magic
|
2021-05-01 14:20:10 +03:00
|
|
|
}
|
|
|
|
mut res := picohttpparser.Response{
|
|
|
|
fd: fd
|
|
|
|
buf_start: out
|
|
|
|
buf: out
|
2023-07-12 09:40:16 +03:00
|
|
|
date: pv.date.str
|
2021-05-01 14:20:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Request parsing loop
|
2023-07-12 09:40:16 +03:00
|
|
|
r := req_read(fd, buf, pv.max_read, pv.idx[fd]) // Get data from socket
|
2021-05-01 14:20:10 +03:00
|
|
|
if r == 0 {
|
|
|
|
// connection closed by peer
|
2023-07-12 09:40:16 +03:00
|
|
|
pv.close_conn(fd)
|
2020-01-23 05:26:30 +03:00
|
|
|
return
|
2021-05-01 14:20:10 +03:00
|
|
|
} else if r == -1 {
|
2023-07-12 09:40:16 +03:00
|
|
|
if fatal_socket_error(fd) == false {
|
2021-04-24 13:21:30 +03:00
|
|
|
return
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
2023-07-12 09:40:16 +03:00
|
|
|
|
2021-10-01 13:49:38 +03:00
|
|
|
// fatal error
|
2023-07-12 09:40:16 +03:00
|
|
|
pv.close_conn(fd)
|
2021-10-01 13:49:38 +03:00
|
|
|
return
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
2023-07-12 09:40:16 +03:00
|
|
|
pv.idx[fd] += r
|
2021-04-24 13:21:30 +03:00
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
mut s := unsafe { tos(buf, pv.idx[fd]) }
|
|
|
|
pret := req.parse_request(s) or {
|
|
|
|
// Parse error
|
|
|
|
pv.err_cb(pv.user_data, req, mut &res, err)
|
|
|
|
return
|
|
|
|
}
|
2021-04-24 13:21:30 +03:00
|
|
|
if pret > 0 { // Success
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2021-05-01 14:20:10 +03:00
|
|
|
assert pret == -2
|
|
|
|
// request is incomplete, continue the loop
|
2023-07-12 09:40:16 +03:00
|
|
|
if pv.idx[fd] == sizeof(buf) {
|
|
|
|
pv.err_cb(pv.user_data, req, mut &res, error('RequestIsTooLongError'))
|
2021-04-24 13:21:30 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Callback (should call .end() itself)
|
2023-07-12 09:40:16 +03:00
|
|
|
pv.cb(pv.user_data, req, mut &res)
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-01 14:20:10 +03:00
|
|
|
fn default_err_cb(data voidptr, req picohttpparser.Request, mut res picohttpparser.Response, error IError) {
|
2022-11-15 16:53:13 +03:00
|
|
|
eprintln('picoev: ${error}')
|
2021-05-01 14:20:10 +03:00
|
|
|
res.end()
|
|
|
|
}
|
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
// new creates a `Picoev` struct and initializes the main loop
|
2021-05-01 14:20:10 +03:00
|
|
|
pub fn new(config Config) &Picoev {
|
2023-07-12 09:40:16 +03:00
|
|
|
listen_fd := listen(config)
|
2021-11-30 12:15:59 +03:00
|
|
|
|
2020-07-24 13:29:47 +03:00
|
|
|
mut pv := &Picoev{
|
2023-07-12 09:40:16 +03:00
|
|
|
num_loops: 1
|
2021-05-01 14:20:10 +03:00
|
|
|
cb: config.cb
|
|
|
|
err_cb: config.err_cb
|
|
|
|
user_data: config.user_data
|
|
|
|
timeout_secs: config.timeout_secs
|
|
|
|
max_headers: config.max_headers
|
2023-06-08 01:54:15 +03:00
|
|
|
max_read: config.max_read
|
|
|
|
max_write: config.max_write
|
|
|
|
buf: unsafe { malloc_noscan(picoev.max_fds * config.max_read + 1) }
|
|
|
|
out: unsafe { malloc_noscan(picoev.max_fds * config.max_write + 1) }
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
2021-11-30 12:15:59 +03:00
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
// epoll for linux
|
|
|
|
// kqueue for macos and bsd
|
|
|
|
// select for windows and others
|
|
|
|
$if linux {
|
|
|
|
pv.loop = create_epoll_loop(0) or { panic(err) }
|
|
|
|
} $else $if freebsd || macos {
|
|
|
|
pv.loop = create_kqueue_loop(0) or { panic(err) }
|
|
|
|
} $else {
|
|
|
|
pv.loop = create_select_loop(0) or { panic(err) }
|
|
|
|
}
|
|
|
|
|
|
|
|
pv.init()
|
|
|
|
|
|
|
|
pv.add(listen_fd, picoev.picoev_read, 0, accept_callback)
|
2020-01-23 05:26:30 +03:00
|
|
|
return pv
|
|
|
|
}
|
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
// serve starts the Picoev server
|
|
|
|
pub fn (mut pv Picoev) serve() {
|
|
|
|
spawn update_date(mut pv)
|
|
|
|
|
2020-01-23 05:26:30 +03:00
|
|
|
for {
|
2023-07-12 09:40:16 +03:00
|
|
|
pv.loop_once(1)
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-12 09:40:16 +03:00
|
|
|
// update_date updates `date` on `pv` every second.
|
|
|
|
fn update_date(mut pv Picoev) {
|
2020-01-23 05:26:30 +03:00
|
|
|
for {
|
2023-07-12 09:40:16 +03:00
|
|
|
// get GMT (UTC) time for the HTTP Date header
|
|
|
|
gmt := time.utc()
|
|
|
|
mut date := gmt.strftime('---, %d --- %Y %H:%M:%S GMT')
|
|
|
|
date = date.replace_once('---', gmt.weekday_str())
|
|
|
|
date = date.replace_once('---', gmt.smonth())
|
|
|
|
pv.date = date
|
|
|
|
time.sleep(time.second)
|
2020-01-23 05:26:30 +03:00
|
|
|
}
|
|
|
|
}
|