Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
## Description
Prefer `import pico_http_parser` for new code. The original
`import picohttpparser` path remains available for compatibility.
`picohttpparser` is V implementation of
[picohttpparser](https://github.com/h2o/picohttpparser),
which in turn is "a tiny, primitive, fast HTTP request/response parser."
+75
View File
@@ -0,0 +1,75 @@
module picohttpparser
// vfmt off
const g_digits_lut = [
`0`, `0`, `0`, `1`, `0`, `2`, `0`, `3`, `0`, `4`, `0`, `5`, `0`, `6`, `0`, `7`, `0`, `8`, `0`, `9`,
`1`, `0`, `1`, `1`, `1`, `2`, `1`, `3`, `1`, `4`, `1`, `5`, `1`, `6`, `1`, `7`, `1`, `8`, `1`, `9`,
`2`, `0`, `2`, `1`, `2`, `2`, `2`, `3`, `2`, `4`, `2`, `5`, `2`, `6`, `2`, `7`, `2`, `8`, `2`, `9`,
`3`, `0`, `3`, `1`, `3`, `2`, `3`, `3`, `3`, `4`, `3`, `5`, `3`, `6`, `3`, `7`, `3`, `8`, `3`, `9`,
`4`, `0`, `4`, `1`, `4`, `2`, `4`, `3`, `4`, `4`, `4`, `5`, `4`, `6`, `4`, `7`, `4`, `8`, `4`, `9`,
`5`, `0`, `5`, `1`, `5`, `2`, `5`, `3`, `5`, `4`, `5`, `5`, `5`, `6`, `5`, `7`, `5`, `8`, `5`, `9`,
`6`, `0`, `6`, `1`, `6`, `2`, `6`, `3`, `6`, `4`, `6`, `5`, `6`, `6`, `6`, `7`, `6`, `8`, `6`, `9`,
`7`, `0`, `7`, `1`, `7`, `2`, `7`, `3`, `7`, `4`, `7`, `5`, `7`, `6`, `7`, `7`, `7`, `8`, `7`, `9`,
`8`, `0`, `8`, `1`, `8`, `2`, `8`, `3`, `8`, `4`, `8`, `5`, `8`, `6`, `8`, `7`, `8`, `8`, `8`, `9`,
`9`, `0`, `9`, `1`, `9`, `2`, `9`, `3`, `9`, `4`, `9`, `5`, `9`, `6`, `9`, `7`, `9`, `8`, `9`, `9`,
]!
// vfmt on
// u64toa converts `value` to an ASCII string and stores it at `buf_start`.
// It returns the length of the ASCII string (branch lookup table implementation).
// Note: `buf_start` should point to a memory buffer, that is *at least* 9 bytes long.
@[direct_array_access; unsafe]
pub fn u64toa(buf_start &u8, value u64) !int {
mut buf := unsafe { buf_start }
// set maximum length to 100MB
if value >= 100_000_000 {
return error('Maximum size of 100MB exceeded!')
}
v := u32(value)
if v < 10_000 {
d1 := u32((v / 100) << 1)
d2 := u32((v % 100) << 1)
unsafe {
if v >= 1000 {
*buf++ = g_digits_lut[d1]
}
if v >= 100 {
*buf++ = g_digits_lut[d1 + 1]
}
if v >= 10 {
*buf++ = g_digits_lut[d2]
}
*buf++ = g_digits_lut[d2 + 1]
}
} else {
b := v / 10_000
c := v % 10_000
d1 := u32((b / 100) << 1)
d2 := u32((b % 100) << 1)
d3 := u32((c / 100) << 1)
d4 := u32((c % 100) << 1)
unsafe {
if value >= 10_000_000 {
*buf++ = g_digits_lut[d1]
}
if value >= 1_000_000 {
*buf++ = g_digits_lut[d1 + 1]
}
if value >= 100_000 {
*buf++ = g_digits_lut[d2]
}
*buf++ = g_digits_lut[d2 + 1]
*buf++ = g_digits_lut[d3]
*buf++ = g_digits_lut[d3 + 1]
*buf++ = g_digits_lut[d4]
*buf++ = g_digits_lut[d4 + 1]
}
}
return unsafe { buf - buf_start }
}
+71
View File
@@ -0,0 +1,71 @@
module picohttpparser
// Test various values under 10,000
pub fn test_u64toa_small_values() {
for v in [u64(0), 1, 10, 99, 100, 999, 1000, 9999] {
mut buf := [10]u8{}
len := unsafe { u64toa(&buf[0], v) or { 0 } }
assert len == expected_len(v)
// Check the actual string for accuracy
assert buf[0..len] == v.str().bytes()
}
}
// Test various values above 10,000 and error handling
pub fn test_u64toa_large_values() {
for i, v in [u64(10000), 12345, 99999, 100000, 999999, 12345678, 99_999_999, 100_000_000] {
mut buf := [20]u8{}
len := unsafe {
u64toa(&buf[0], v) or {
assert err.msg() == 'Maximum size of 100MB exceeded!'
0
}
}
if v < 100_000_000 {
assert len == expected_len(v)
assert buf[0..len] == v.str().bytes()
} else {
assert len == 0
}
}
}
// Test edge cases
pub fn test_u64toa_edge_cases() {
mut buf := [10]u8{}
// Test zero value
len := unsafe {
u64toa(&buf[0], 0) or {
assert false
0
}
}
assert len == 1
assert buf[0] == `0`
}
// Helper functions for expected values
fn expected_len(v u64) int {
if v == 0 {
return 1
}
// return int(math.ceil(math.log10(f64(v + 1))))
mut count := 0
mut temp := v
for temp > 0 {
temp /= 10
count++
}
return count
}
@@ -0,0 +1,487 @@
module picohttpparser
// NOTE: picohttpparser is designed for speed. Please do some benchmarks when
// you change something in this file
// token_char_map contains all allowed characters in HTTP headers
const token_char_map = '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
'\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0' +
'\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1' +
'\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0' +
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
fn (mut r Request) phr_parse_request_path(buf_start &u8, buf_end &u8, mut pret Pret) {
mut buf := unsafe { buf_start + 0 }
// ADVANCE_TOKEN
method := advance_token(buf, buf_end, mut pret)
if pret.ret < 0 {
return
}
unsafe {
buf += pret.ret
}
$if trace_parse ? {
eprintln('method: ${method}')
}
// skip spaces
for {
unsafe { buf++ }
if *buf != ` ` {
break
}
}
path := advance_token(buf, buf_end, mut pret)
if pret.ret < 0 {
return
}
$if trace_parse ? {
eprintln('path: ${path}')
}
unsafe {
buf += pret.ret
}
// skip spaces
for {
unsafe { buf++ }
if *buf != ` ` {
break
}
}
// validate
if method == '' || path == '' {
pret.ret = -1
pret.err = 'error parsing request: invalid method or path'
return
}
r.method = method
r.path = path
pret.ret = unsafe { buf - buf_start }
}
fn (mut r Request) phr_parse_request_path_pipeline(buf_start &u8, buf_end &u8, mut pret Pret) {
mut buf := unsafe { buf_start }
method := advance_token2(buf, buf_end, mut pret)
if pret.ret < 0 {
return
}
unsafe {
buf += pret.ret
}
path := advance_token2(buf, buf_end, mut pret)
if pret.ret < 0 {
return
}
unsafe {
buf += pret.ret
}
// validate
if method == '' || path == '' {
pret.ret = -1
pret.err = 'error parsing request: invalid method or path'
return
}
r.method = method
r.path = path
for buf < buf_end {
unsafe { buf++ }
// check if following 4 characters are '\r\n\r\n' indicating a new request line
if unsafe { *(&u32(buf)) == 0x0a0d0a0d } {
unsafe {
buf += 4
}
pret.ret = unsafe { buf - buf_start }
return
}
}
pret.ret = -1
pret.err = 'error parsing request: no request found'
}
fn (mut r Request) phr_parse_request(buf_start &u8, buf_end &u8, mut pret Pret) &u8 {
// make copy of `buf_start` that can be mutated
mut buf := unsafe { buf_start }
// skip first empty line (some clients add CRLF after POST content)
// CHECK_EOF
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
if *buf == `\r` {
unsafe { buf++ }
// EXPECT_CHAR
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
if *buf != `\n` {
pret.ret = -1
pret.err = 'error parsing request: expected "\n" after "\r"'
return unsafe { nil }
}
}
// parse request line
r.phr_parse_request_path(buf, buf_end, mut pret)
if pret.ret < 0 {
return unsafe { nil }
}
unsafe {
buf += pret.ret
}
minor_version := parse_http_version(buf, buf_end, mut pret)
if pret.ret < 0 {
return unsafe { nil }
}
$if trace_parse ? {
eprintln('minor_version: ${minor_version}')
}
unsafe {
buf += pret.ret
}
// CHECK_EOF
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
if *buf == `\r` {
unsafe { buf++ }
// EXPECT_CHAR
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
if *buf != `\n` {
pret.ret = -1
pret.err = 'error parsing request: expected "\n" after "\r"'
return unsafe { nil }
}
unsafe { buf++ }
} else if *buf == `\n` {
unsafe { buf++ }
} else {
pret.ret = -1
pret.err = 'error parsing request: expecting "\r\n" after HTTP version'
return unsafe { nil }
}
return r.parse_headers(buf, buf_end, mut pret)
}
@[direct_array_access]
fn (mut r Request) parse_headers(buf_start &u8, buf_end &u8, mut pret Pret) &u8 {
mut buf := unsafe { buf_start }
mut i := 0
for i = r.num_headers; i < max_headers; i++ {
// CHECK_EOF
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
if *buf == `\r` {
unsafe { buf++ }
// EXPECT_CHAR
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
if *buf != `\n` {
pret.ret = -1
pret.err = 'error parsing request: expected "\n" after "\r"'
return unsafe { nil }
}
unsafe { buf++ }
break
} else if *buf == `\n` {
unsafe { buf++ }
break
}
if !(*buf == ` ` || *buf == `\t`) {
name_start := buf
// parsing name, but do not discard SP before colon, see
// http://www.mozilla.org/security/announce/2006/mfsa2006-33.html
for *buf != `:` {
// check if the current character is allowed in an HTTP header
if token_char_map[*buf] == 0 {
$if trace_parse ? {
eprintln('invalid character! ${*buf}')
}
pret.ret = -1
pret.err = 'error parsing request: invalid character in header "${*buf}"'
return unsafe { nil }
}
unsafe { buf++ }
// CHECK_EOF
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
}
name_len := unsafe { buf - name_start }
if name_len == 0 {
pret.ret = -1
pret.err = 'error parsing request: invalid header name'
return unsafe { nil }
}
r.headers[i].name = unsafe { tos(name_start, name_len) }
unsafe { buf++ }
for { // CHECK_EOF
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
if !(*buf == ` ` || *buf == `\t`) {
break
}
unsafe { buf++ }
}
} else {
r.headers[i].name = ''
}
mut value_len := get_token_length_to_eol(buf, buf_end, mut pret)
if pret.ret < 0 {
return unsafe { nil }
}
// TODO: strip characters
value_end := unsafe { buf + value_len }
for value_end != buf {
c := unsafe { *(value_end - 1) }
if !(c == ` ` || c == `\t`) {
break
}
unsafe { value_end-- }
}
r.headers[i].value = unsafe { tos(buf, value_end - buf) }
r.num_headers++
unsafe {
buf += pret.ret
}
}
if i == max_headers {
// too many headers
eprintln('Too many headers!')
pret.ret = -1
pret.err = 'error parsing request: too many headers!'
return unsafe { nil }
}
pret.ret = unsafe { buf - buf_start }
return buf
}
// is_complete checks if an http request is done
fn is_complete(buf_start &u8, buf_end &u8, last_len int, mut pret Pret) &u8 {
mut ret_cnt := 0
// get the last 3 characters of the request buffer
buf := if last_len < 3 { buf_start } else { unsafe { buf_start + last_len - 3 } }
for {
// CHECK_EOF
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
// We expect a line of an http request to end with '\r\n'
if *buf == `\r` {
unsafe { buf++ }
// CHECK_EOF
if buf == buf_end {
pret.ret = -2
return unsafe { nil }
}
// EXPECT_CHAR_NO_CHECK
if *buf != `\n` {
// no '\n' after '\r' indicates a parse error
pret.ret = -1
pret.err = 'error parsing request: expected "\n" after "\r"'
return unsafe { nil }
}
unsafe { buf++ }
ret_cnt++
} else if *buf == `\n` {
unsafe { buf++ }
ret_cnt++
} else {
// other character
unsafe { buf++ }
ret_cnt = 0
}
if ret_cnt == 2 {
return buf
}
}
pret.ret = -2
return unsafe { nil }
}
fn parse_http_version(buf_start &u8, buf_end &u8, mut pret Pret) int {
// we want at least [HTTP/1.<two chars>] to try to parse
if unsafe { buf_end - buf_start } < 9 {
pret.ret = -2
return 0
}
if unsafe { tos(buf_start, 7) != 'HTTP/1.' } {
pret.ret = -1
pret.err = 'error parsing request: picohttpparser only supports HTTP/1.x'
return 0
}
// PARSE_INT
c := unsafe { *(buf_start + 7) }
if c < `0` || c > `9` {
pret.ret = -1
pret.err = 'error parsing request: invalid HTTP version'
return 0
}
pret.ret = 8
return int(c - `0`)
}
fn get_token_length_to_eol(buf_start &u8, buf_end &u8, mut pret Pret) int {
mut buf := unsafe { buf_start }
mut token_len := 0
// find non-printable char within the next 8 bytes
// HOT code: (TODO: should be manually inlined)
for _likely_(unsafe { buf_end - buf >= 8 }) {
for _ in 0 .. 8 {
if _unlikely_(!is_printable_ascii(*buf)) {
// non printable
unsafe {
goto non_printable
}
}
unsafe { buf++ }
continue
non_printable:
// allow space and horizontal tab
if _likely_(*buf < ` ` && *buf != 9) || _unlikely_(*buf == 127) {
// found clear the line (CTL)
unsafe {
goto found_ctl
}
}
unsafe { buf++ }
}
}
// remaining characters
for {
// CHECK_EOF
if buf == buf_end {
pret.ret = -2
return 0
}
if _likely_(*buf < ` ` && *buf != 9) || _unlikely_(*buf == 127) {
// found clear the line (CTL)
unsafe {
goto found_ctl
}
}
unsafe { buf++ }
}
found_ctl:
if _likely_(*buf == `\r`) {
unsafe { buf++ }
// EXPECT_CHAR
if buf == buf_end {
pret.ret = -2
return 0
}
if *buf != `\n` {
// no '\n' after '\r' indicates a parse error
pret.ret = -1
pret.err = 'error parsing request: expected "\n" after "\r"'
return 0
}
unsafe { buf++ }
token_len = unsafe { buf - 2 - buf_start }
} else if *buf == `\n` {
token_len = unsafe { buf - buf_start }
unsafe { buf++ }
} else {
pret.ret = -1
pret.err = 'error parsing request: expecting "\r\n" after header'
return 0
}
if token_len == 0 {
pret.ret = 0
return 0
}
pret.ret = unsafe { buf - buf_start }
return token_len
}
// following functions are #define in the C version, but inline here for better readability
@[inline]
fn advance_token(tok_start &u8, tok_end &u8, mut pret Pret) string {
mut buf := unsafe { tok_start }
for *buf != ` ` {
if _unlikely_(!is_printable_ascii(*buf)) {
if *buf < ` ` || *buf == 127 {
pret.ret = -1
pret.err = 'error parsing request: invalid character "${*buf}"'
return ''
}
}
unsafe { buf++ }
// CHECK_EOF
if buf == tok_end {
pret.ret = -2
return ''
}
}
pret.ret = unsafe { buf - tok_start }
return unsafe { tos(tok_start, pret.ret) }
}
// advance_token2 is a less safe version of advance_token
@[inline]
fn advance_token2(tok_start &u8, _tok_end &u8, mut pret Pret) string {
mut len := 0
mut i := 0
for {
if unsafe { *(tok_start + i) == ` ` } {
len = i
for unsafe { *(tok_start + i) == ` ` } {
i++
}
break
}
i++
}
pret.ret = i
return unsafe { tos(tok_start, len) }
}
@[inline]
fn is_printable_ascii(c u8) bool {
return u32(c - 32) < 95
}
+97
View File
@@ -0,0 +1,97 @@
module picohttpparser
const max_headers = 100
pub struct Header {
pub mut:
name string
value string
}
pub struct Request {
mut:
prev_len int
pub mut:
method string
path string
headers [max_headers]Header
num_headers int
body string
}
// Pret contains the nr of bytes read, a negative number indicates an error
struct Pret {
pub mut:
err string
// -1 indicates a parse error and -2 means the request is parsed
ret int
}
// parse_request parses a raw HTTP request and returns the number of bytes read.
// -1 indicates a parse error and -2 means the request is parsed
@[inline]
pub fn (mut r Request) parse_request(s string) !int {
mut buf := s.str
buf_end := unsafe { s.str + s.len }
mut pret := Pret{}
// if prev_len != 0, check if the request is complete
// (a fast countermeasure against slowloris)
if r.prev_len != 0 && unsafe { is_complete(buf, buf_end, r.prev_len, mut pret) == nil } {
if pret.ret == -1 {
return error(pret.err)
}
return pret.ret
}
buf = r.phr_parse_request(buf, buf_end, mut pret)
if pret.ret == -1 {
return error(pret.err)
}
if unsafe { buf == nil } {
return pret.ret
}
pret.ret = unsafe { buf - s.str }
r.body = unsafe { (&s.str[pret.ret]).vstring_literal_with_len(s.len - pret.ret) }
r.prev_len = s.len
// return nr of bytes
return pret.ret
}
// parse_request_path sets the `path` and `method` fields
@[inline]
pub fn (mut r Request) parse_request_path(s string) !int {
mut buf := s.str
buf_end := unsafe { s.str + s.len }
mut pret := Pret{}
r.phr_parse_request_path(buf, buf_end, mut pret)
if pret.ret == -1 {
return error(pret.err)
}
return pret.ret
}
// parse_request_path_pipeline can parse the `path` and `method` of HTTP/1.1 pipelines.
// Call it again to parse the next request
@[inline]
pub fn (mut r Request) parse_request_path_pipeline(s string) !int {
mut buf := unsafe { s.str + r.prev_len }
buf_end := unsafe { s.str + s.len }
mut pret := Pret{}
r.phr_parse_request_path_pipeline(buf, buf_end, mut pret)
if pret.ret == -1 {
return error(pret.err)
}
if pret.ret > 0 {
r.prev_len = pret.ret
}
return pret.ret
}
@@ -0,0 +1,63 @@
module picohttpparser
pub fn test_parses_a_simple_get_request() {
mut req := Request{}
parsed := req.parse_request('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') or {
assert false, 'error while parse request: ${err}'
0
}
assert parsed == 37
assert req.method == 'GET'
assert req.path == '/'
assert req.headers[0].name == 'Host'
assert req.headers[0].value == 'example.com'
}
pub fn test_parses_multiple_headers() {
mut req := Request{}
parsed := req.parse_request('GET /foo?bar=baz HTTP/1.1\r\nHeader1: value1\r\nHeader2: value2\r\n\r\n') or {
assert false, 'error while parse request: ${err}'
0
}
assert parsed == 63
assert req.headers[1].name == 'Header2'
assert req.headers[1].value == 'value2'
}
pub fn test_parses_requests_with_bodies() {
mut req := Request{}
parsed := req.parse_request('POST /data HTTP/1.1\r\nContent-Length: 10\r\n\r\nsomedata') or {
assert false, 'error while parse request: ${err}'
0
}
assert parsed == 43
assert req.body == 'somedata'
}
pub fn test_handles_empty_requests() {
mut req := Request{}
parsed := req.parse_request('') or {
assert false, 'error while parse request: ${err}'
0
}
assert parsed == -2
}
pub fn test_handles_incomplete_requests() {
mut req := Request{}
partial_parsed := req.parse_request('GET /partial') or {
assert false, 'error while parse request: ${err}'
0
}
assert partial_parsed == -2
assert req.prev_len == 0
remaining_parsed := req.parse_request(' HTTP/1.1\r\n\r\n') or {
assert err.msg() == 'error parsing request: invalid character "13"'
0
}
assert remaining_parsed == 0
assert req.method == ''
assert req.path == ''
}
+122
View File
@@ -0,0 +1,122 @@
module picohttpparser
$if !windows {
#include <sys/socket.h>
}
pub struct Response {
pub:
fd int
date &u8 = unsafe { nil }
buf_start &u8 = unsafe { nil }
pub mut:
buf &u8 = unsafe { nil }
}
@[inline]
pub fn (mut r Response) write_string(s string) {
unsafe {
vmemcpy(r.buf, s.str, s.len)
r.buf += s.len
}
}
@[inline]
pub fn (mut r Response) http_ok() &Response {
r.write_string('HTTP/1.1 200 OK\r\n')
return unsafe { r }
}
@[inline]
pub fn (mut r Response) header(k string, v string) &Response {
r.write_string(k)
r.write_string(': ')
r.write_string(v)
r.write_string('\r\n')
return unsafe { r }
}
@[inline]
pub fn (mut r Response) header_date() &Response {
r.write_string('Date: ')
unsafe {
vmemcpy(r.buf, r.date, 29)
r.buf += 29
}
r.write_string('\r\n')
return unsafe { r }
}
@[inline]
pub fn (mut r Response) header_server() &Response {
r.write_string('Server: V\r\n')
return unsafe { r }
}
@[inline]
pub fn (mut r Response) content_type(s string) &Response {
r.write_string('Content-Type: ')
r.write_string(s)
r.write_string('\r\n')
return unsafe { r }
}
@[inline]
pub fn (mut r Response) html() &Response {
r.write_string('Content-Type: text/html\r\n')
return unsafe { r }
}
@[inline]
pub fn (mut r Response) plain() &Response {
r.write_string('Content-Type: text/plain\r\n')
return unsafe { r }
}
@[inline]
pub fn (mut r Response) json() &Response {
r.write_string('Content-Type: application/json\r\n')
return unsafe { r }
}
@[inline]
pub fn (mut r Response) body(body string) {
r.write_string('Content-Length: ')
unsafe {
r.buf += u64toa(r.buf, u64(body.len)) or { panic(err) }
}
r.write_string('\r\n\r\n')
r.write_string(body)
}
@[inline]
pub fn (mut r Response) http_404() {
r.write_string('HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n')
}
@[inline]
pub fn (mut r Response) http_405() {
r.write_string('HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n')
}
@[inline]
pub fn (mut r Response) http_500() {
r.write_string('HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n')
}
@[inline]
pub fn (mut r Response) raw(response string) {
r.write_string(response)
}
fn C.send(sockfd i32, buf voidptr, len usize, flags i32) i32
@[inline]
pub fn (mut r Response) end() int {
n := int(i64(r.buf) - i64(r.buf_start))
// use send instead of write for windows compatibility
if C.send(r.fd, r.buf_start, n, 0) != n {
return -1
}
return n
}