Some more HTTP multi-threading Fun. CGI is broken. (0.39.11)
This commit is contained in:
parent
5222f79434
commit
6a16e5fb45
8 changed files with 496 additions and 15 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(fun VERSION 0.39.10 LANGUAGES C)
|
||||
project(fun VERSION 0.39.11 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
|
|||
66
examples/net/http_mt_server.fun
Executable file
66
examples/net/http_mt_server.fun
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-03-26
|
||||
*/
|
||||
|
||||
#include <io/socket.fun>
|
||||
#include <io/thread.fun>
|
||||
|
||||
// Simple multi-threaded HTTP server (thread-per-connection)
|
||||
//
|
||||
// Features:
|
||||
// - Blocking accept() in the main thread
|
||||
// - Each client is handled by a separate Fun thread
|
||||
// - Sends a minimal HTTP 200 response with a small HTML body
|
||||
//
|
||||
// Usage:
|
||||
// ./examples/net/http_mt_server.fun
|
||||
// Open http://127.0.0.1:8089/ in the browser
|
||||
|
||||
PORT = 8080
|
||||
BACKLOG = 128
|
||||
|
||||
srv = TcpServer(PORT, BACKLOG)
|
||||
if (srv.listen() <= 0)
|
||||
print("HTTP MT server: listen failed on :" + to_string(PORT))
|
||||
return 0
|
||||
print("HTTP MT server on :" + to_string(PORT))
|
||||
|
||||
fun handle_client(fd)
|
||||
req = sock_recv(fd, 8192)
|
||||
if (len(req) == 0)
|
||||
sock_close(fd)
|
||||
return 0
|
||||
|
||||
body = "<html><body><h1>Hello from Fun multi-threaded server</h1></body></html>"
|
||||
b = to_string(body)
|
||||
resp = "HTTP/1.1 200 OK\r\n"
|
||||
resp = resp + "Content-Type: text/html; charset=utf-8\r\n"
|
||||
resp = resp + "Content-Length: " + to_string(len(b)) + "\r\n"
|
||||
resp = resp + "Connection: close\r\n\r\n" + b
|
||||
sock_send(fd, resp)
|
||||
sock_close(fd)
|
||||
return 1
|
||||
|
||||
th = Thread()
|
||||
|
||||
// Accept loop: spawn a thread per client
|
||||
while true
|
||||
fd = srv.accept()
|
||||
if (fd > 0)
|
||||
_ = th.spawn(handle_client, fd)
|
||||
|
||||
/* Expected output (on start):
|
||||
HTTP MT server on :8080
|
||||
|
||||
Then open http://127.0.0.1:8089/ in a browser; it will render:
|
||||
<html><body><h1>Hello from Fun multi-threaded server</h1></body></html>
|
||||
*/
|
||||
361
examples/net/http_mt_server_cgi.fun
Executable file
361
examples/net/http_mt_server_cgi.fun
Executable file
|
|
@ -0,0 +1,361 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-03-26
|
||||
*/
|
||||
|
||||
//#include order kept minimal to avoid global bloat
|
||||
#include <io/socket.fun>
|
||||
#include <io/thread.fun>
|
||||
// Note: Avoid relying on strings.fun helpers inside threads
|
||||
#include <net/cgi.fun>
|
||||
|
||||
// Multi-threaded HTTP server with CGI support (thread-per-connection)
|
||||
//
|
||||
// Features:
|
||||
// - Blocking accept() in the main thread
|
||||
// - Each client is handled by a separate Fun thread
|
||||
// - Serves static files from htdocs and executes .fun scripts via CGI helper
|
||||
//
|
||||
// Try:
|
||||
// http://127.0.0.1:8080/
|
||||
// http://127.0.0.1:8080/hello.fun?name=Fun
|
||||
// http://127.0.0.1:8080/info.fun
|
||||
|
||||
// Keep top-level globals minimal to avoid hitting the VM's global limit.
|
||||
|
||||
PORT = 8080
|
||||
BACKLOG = 128
|
||||
HTDOCS = "./examples/data/htdocs"
|
||||
|
||||
// Minimal local string helpers that avoid 'join' internally
|
||||
fun _trim(s)
|
||||
src = to_string(s)
|
||||
// ltrim
|
||||
i = 0
|
||||
ws = " \t\r\n"
|
||||
while (i < len(src))
|
||||
ch = substr(src, i, 1)
|
||||
if (find(ws, ch) < 0)
|
||||
break
|
||||
i = i + 1
|
||||
left = substr(src, i, len(src) - i)
|
||||
// rtrim
|
||||
j = len(left) - 1
|
||||
while (j >= 0)
|
||||
ch2 = substr(left, j, 1)
|
||||
if (find(ws, ch2) < 0)
|
||||
break
|
||||
j = j - 1
|
||||
return substr(left, 0, j + 1)
|
||||
|
||||
fun _ends_with(s, suf)
|
||||
a = to_string(s)
|
||||
p = to_string(suf)
|
||||
la = len(a)
|
||||
lp = len(p)
|
||||
if (lp > la)
|
||||
return 0
|
||||
return substr(a, la - lp, lp) == p
|
||||
|
||||
// Split by single character delimiter (first char of delim string)
|
||||
fun _split_char(s, delim)
|
||||
src = to_string(s)
|
||||
d = to_string(delim)
|
||||
if (len(d) == 0)
|
||||
return [src]
|
||||
dd = substr(d, 0, 1)
|
||||
parts = []
|
||||
buf = ""
|
||||
i = 0
|
||||
n = len(src)
|
||||
while (i < n)
|
||||
ch = substr(src, i, 1)
|
||||
if (ch == dd)
|
||||
push(parts, buf)
|
||||
buf = ""
|
||||
else
|
||||
buf = buf + ch
|
||||
i = i + 1
|
||||
push(parts, buf)
|
||||
return parts
|
||||
|
||||
fun _split_space(s)
|
||||
return _split_char(s, " ")
|
||||
|
||||
fun _split_lines(s)
|
||||
return _split_char(s, "\n")
|
||||
|
||||
fun _to_upper_ascii(s)
|
||||
src = to_string(s)
|
||||
U = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
L = "abcdefghijklmnopqrstuvwxyz"
|
||||
out = ""
|
||||
i = 0
|
||||
n = len(src)
|
||||
while (i < n)
|
||||
ch = substr(src, i, 1)
|
||||
idx = find(L, ch)
|
||||
if (idx >= 0)
|
||||
out = out + substr(U, idx, 1)
|
||||
else
|
||||
out = out + ch
|
||||
i = i + 1
|
||||
return out
|
||||
|
||||
// Minimal send helpers (few identifiers)
|
||||
fun _send(fd, code, text, body)
|
||||
b = to_string(body)
|
||||
resp = "HTTP/1.1 " + to_string(code) + " " + text + "\r\n"
|
||||
resp = resp + "Content-Type: text/html; charset=utf-8\r\n"
|
||||
resp = resp + "Content-Length: " + to_string(len(b)) + "\r\n"
|
||||
resp = resp + "Connection: close\r\n\r\n" + b
|
||||
sock_send(fd, resp)
|
||||
|
||||
// Convert CGI raw output (headers + body) to full HTTP/1.1 response without using strings.fun helpers
|
||||
fun _cgi_to_http_response(raw)
|
||||
out = to_string(raw)
|
||||
// Find header/body separator
|
||||
sep = find(out, "\r\n\r\n")
|
||||
seplen = 4
|
||||
if (sep < 0)
|
||||
sep = find(out, "\n\n")
|
||||
seplen = 2
|
||||
if (sep < 0)
|
||||
// No CGI headers, treat whole as body
|
||||
b = out
|
||||
resp = "HTTP/1.1 200 OK\r\n"
|
||||
resp = resp + "Content-Type: text/html; charset=utf-8\r\n"
|
||||
resp = resp + "Content-Length: " + to_string(len(b)) + "\r\n"
|
||||
resp = resp + "Connection: close\r\n\r\n" + b
|
||||
return resp
|
||||
|
||||
header_str = substr(out, 0, sep)
|
||||
body = substr(out, sep + seplen, len(out) - sep - seplen)
|
||||
|
||||
// Parse headers line-by-line
|
||||
lines = _split_lines(header_str)
|
||||
code = 200
|
||||
text = "OK"
|
||||
hh = [] // [key, value]
|
||||
i = 0
|
||||
if (typeof(lines) == "Array")
|
||||
while (i < len(lines))
|
||||
ln = _trim(lines[i])
|
||||
if (len(ln) > 0)
|
||||
colon = find(ln, ":")
|
||||
if (colon > 0)
|
||||
k = _trim(substr(ln, 0, colon))
|
||||
v = _trim(substr(ln, colon + 1, len(ln) - colon - 1))
|
||||
if (_to_upper_ascii(k) == "STATUS")
|
||||
sp = find(v, " ")
|
||||
if (sp > 0)
|
||||
code = to_number(substr(v, 0, sp))
|
||||
text = _trim(substr(v, sp + 1, len(v) - sp - 1))
|
||||
else
|
||||
code = to_number(v)
|
||||
if (code == 0) code = 200
|
||||
text = "OK"
|
||||
else
|
||||
push(hh, [k, v])
|
||||
i = i + 1
|
||||
|
||||
// Build HTTP response
|
||||
b = to_string(body)
|
||||
resp = "HTTP/1.1 " + to_string(code) + " " + text + "\r\n"
|
||||
j = 0
|
||||
m = len(hh)
|
||||
has_len = 0
|
||||
while (j < m)
|
||||
p = hh[j]
|
||||
if (typeof(p) == "Array" && len(p) >= 2)
|
||||
hk = to_string(p[0])
|
||||
hv = to_string(p[1])
|
||||
if (_to_upper_ascii(hk) == "CONTENT-LENGTH")
|
||||
has_len = 1
|
||||
resp = resp + hk + ": " + hv + "\r\n"
|
||||
j = j + 1
|
||||
if (!has_len)
|
||||
resp = resp + "Content-Length: " + to_string(len(b)) + "\r\n"
|
||||
resp = resp + "Connection: close\r\n\r\n" + b
|
||||
return resp
|
||||
|
||||
fun _send_cgi(fd, raw)
|
||||
resp = _cgi_to_http_response(raw)
|
||||
sock_send(fd, resp)
|
||||
|
||||
// Worker: parse request and serve static or .fun via CGI
|
||||
fun handle_client(fd)
|
||||
req = sock_recv(fd, 65536)
|
||||
if (len(req) == 0)
|
||||
sock_close(fd)
|
||||
return 0
|
||||
|
||||
s = to_string(req)
|
||||
nl = find(s, "\n")
|
||||
if (nl < 0)
|
||||
nl = find(s, "\r")
|
||||
if (nl < 0)
|
||||
sock_close(fd)
|
||||
return 0
|
||||
line = _trim(substr(s, 0, nl))
|
||||
ps = _split_space(line)
|
||||
if (!(typeof(ps) == "Array") || len(ps) < 2)
|
||||
sock_close(fd)
|
||||
return 0
|
||||
method = _trim(ps[0])
|
||||
target = _trim(ps[1])
|
||||
|
||||
// path + query
|
||||
path = target
|
||||
query = ""
|
||||
q = find(target, "?")
|
||||
if (q >= 0)
|
||||
path = substr(target, 0, q)
|
||||
query = substr(target, q + 1, len(target) - q - 1)
|
||||
if (path == "/")
|
||||
path = "/index.html"
|
||||
|
||||
htdocs = env("FUN_HTDOCS")
|
||||
if (htdocs == nil || len(htdocs) == 0)
|
||||
htdocs = HTDOCS
|
||||
file = htdocs + path
|
||||
|
||||
// parse headers small loop
|
||||
headers = {}
|
||||
lines = _split_lines(s)
|
||||
if (typeof(lines) == "Array")
|
||||
i = 1
|
||||
while (i < len(lines))
|
||||
ln = _trim(lines[i])
|
||||
if (len(ln) == 0)
|
||||
break
|
||||
cpos = find(ln, ":")
|
||||
if (cpos > 0)
|
||||
k = _to_upper_ascii(_trim(substr(ln, 0, cpos)))
|
||||
v = _trim(substr(ln, cpos + 1, len(ln) - cpos - 1))
|
||||
headers[k] = v
|
||||
i = i + 1
|
||||
|
||||
// body (optional)
|
||||
body = ""
|
||||
hend = find(s, "\r\n\r\n")
|
||||
if (hend >= 0)
|
||||
body = substr(s, hend + 4, len(s) - hend - 4)
|
||||
else
|
||||
hend = find(s, "\n\n")
|
||||
if (hend >= 0)
|
||||
body = substr(s, hend + 2, len(s) - hend - 2)
|
||||
|
||||
if (_ends_with(path, ".fun"))
|
||||
host = headers["HOST"]
|
||||
if (host == nil || len(host) == 0)
|
||||
host = "localhost"
|
||||
ua = headers["USER-AGENT"]
|
||||
cookie = headers["COOKIE"]
|
||||
ctype = headers["CONTENT-TYPE"]
|
||||
clen = headers["CONTENT-LENGTH"]
|
||||
|
||||
envs = []
|
||||
funlib = env("FUN_LIB_DIR")
|
||||
if (len(funlib) == 0)
|
||||
funlib = "./lib"
|
||||
push(envs, "FUN_LIB_DIR='" + funlib + "'")
|
||||
push(envs, "REQUEST_METHOD='" + method + "'")
|
||||
push(envs, "QUERY_STRING='" + query + "'")
|
||||
push(envs, "SCRIPT_NAME='" + path + "'")
|
||||
push(envs, "PATH_INFO='" + path + "'")
|
||||
push(envs, "SERVER_NAME='" + host + "'")
|
||||
pstr = env("FUN_PORT")
|
||||
if (pstr == nil || len(pstr) == 0)
|
||||
pstr = to_string(PORT)
|
||||
push(envs, "SERVER_PORT='" + pstr + "'")
|
||||
push(envs, "SERVER_PROTOCOL='HTTP/1.1'")
|
||||
push(envs, "HTTP_HOST='" + host + "'")
|
||||
if (!(ua == nil) && len(ua) > 0)
|
||||
push(envs, "HTTP_USER_AGENT='" + ua + "'")
|
||||
if (!(cookie == nil) && len(cookie) > 0)
|
||||
push(envs, "HTTP_COOKIE='" + cookie + "'")
|
||||
if (!(ctype == nil) && len(ctype) > 0)
|
||||
push(envs, "CONTENT_TYPE='" + ctype + "'")
|
||||
if (!(clen == nil) && len(clen) > 0)
|
||||
push(envs, "CONTENT_LENGTH='" + clen + "'")
|
||||
if (len(body) > 0)
|
||||
push(envs, "POST_DATA='" + body + "'")
|
||||
|
||||
exec = env("FUN_EXEC")
|
||||
if (len(exec) == 0)
|
||||
if (len(read_file("./build_debug/fun")) > 0)
|
||||
exec = "./build_debug/fun"
|
||||
else
|
||||
if (len(read_file("./build_release/fun")) > 0)
|
||||
exec = "./build_release/fun"
|
||||
else
|
||||
exec = "fun"
|
||||
|
||||
// Manually concatenate env exports to avoid join()
|
||||
envs_str = ""
|
||||
ei = 0
|
||||
en = len(envs)
|
||||
while (ei < en)
|
||||
if (ei > 0)
|
||||
envs_str = envs_str + " "
|
||||
envs_str = envs_str + envs[ei]
|
||||
ei = ei + 1
|
||||
cmd = envs_str + " " + exec + " " + file
|
||||
res = proc_run(cmd)
|
||||
if (res == nil || typeof(res) != "Map")
|
||||
_send(fd, 500, "Internal Server Error", "<h1>Failed to execute CGI</h1>")
|
||||
sock_close(fd)
|
||||
return 0
|
||||
out = res["out"]
|
||||
if (out == nil)
|
||||
out = ""
|
||||
code = res["code"]
|
||||
if (code == nil)
|
||||
code = 0
|
||||
if (len(out) == 0)
|
||||
if (to_string(code) != "0")
|
||||
_send(fd, 500, "Internal Server Error", "<h1>CGI failed (exit " + to_string(code) + ")</h1>")
|
||||
else
|
||||
_send(fd, 500, "Internal Server Error", "<h1>CGI produced no output</h1>")
|
||||
else
|
||||
_send_cgi(fd, out)
|
||||
else
|
||||
content = read_file(file)
|
||||
if (len(content) > 0)
|
||||
_send(fd, 200, "OK", content)
|
||||
else
|
||||
_send(fd, 404, "Not Found", "<h1>404 Not Found</h1>")
|
||||
|
||||
sock_close(fd)
|
||||
return 1
|
||||
|
||||
// Setup server and accept loop
|
||||
srv = TcpServer(PORT, BACKLOG)
|
||||
if (srv.listen() <= 0)
|
||||
print("HTTP MT CGI server: listen failed on :" + to_string(PORT))
|
||||
return 0
|
||||
print("HTTP MT CGI server: serving " + HTDOCS + " on :" + to_string(PORT))
|
||||
|
||||
th = Thread()
|
||||
while true
|
||||
fd = srv.accept()
|
||||
if (fd > 0)
|
||||
_ = th.spawn(handle_client, fd)
|
||||
|
||||
/*
|
||||
Expected output (on start):
|
||||
HTTP MT CGI server: serving ./examples/data/htdocs on :8080
|
||||
|
||||
Then open a browser:
|
||||
- http://127.0.0.1:8080/
|
||||
- http://127.0.0.1:8080/hello.fun?name=Fun
|
||||
*/
|
||||
|
|
@ -35,9 +35,23 @@ class HTTPCGIServer(number port)
|
|||
this.handle_client(fd)
|
||||
return 1
|
||||
|
||||
// Safe header getter: returns string or empty string when missing/invalid
|
||||
fun _hget(this, hdrs, key)
|
||||
if (typeof(hdrs) == "Map")
|
||||
v = hdrs[key]
|
||||
if (typeof(v) == "String")
|
||||
return v
|
||||
if (v != nil)
|
||||
// Coerce non-strings to string just in case
|
||||
return to_string(v)
|
||||
return ""
|
||||
|
||||
fun _parse_headers(this, request)
|
||||
headers = {}
|
||||
lines = str_split(request, "\n")
|
||||
// str_split should return an Array; if not, bail out with empty headers
|
||||
if (!(typeof(lines) == "Array"))
|
||||
return headers
|
||||
i = 1 // start after request line
|
||||
while (i < len(lines))
|
||||
ln = str_trim(lines[i])
|
||||
|
|
@ -57,19 +71,29 @@ class HTTPCGIServer(number port)
|
|||
sock_close(fd)
|
||||
return 0
|
||||
|
||||
lines = str_split(request, "\n")
|
||||
if (len(lines) == 0)
|
||||
// Robustly extract request line without relying on array indexing
|
||||
reqstr = to_string(request)
|
||||
// debug: mark start of handle_client
|
||||
// print("[DBG] handle_client: received request bytes=" + to_string(len(reqstr)))
|
||||
nl = find(reqstr, "\n")
|
||||
if (nl < 0)
|
||||
nl = find(reqstr, "\r")
|
||||
if (nl < 0)
|
||||
sock_close(fd)
|
||||
return 0
|
||||
reqline = str_trim(substr(reqstr, 0, nl))
|
||||
// print("[DBG] request line=\"" + reqline + "\"")
|
||||
|
||||
reqline = str_trim(lines[0])
|
||||
parts = str_split(reqline, " ")
|
||||
if (len(parts) < 2)
|
||||
// Guard against unexpected splitter behavior
|
||||
if (!(typeof(parts) == "Array") || len(parts) < 2)
|
||||
sock_close(fd)
|
||||
return 0
|
||||
// print("[DBG] parts ok, count=" + to_string(len(parts)) + ", t0=" + typeof(parts))
|
||||
|
||||
method = str_trim(parts[0])
|
||||
target = str_trim(parts[1])
|
||||
// print("[DBG] method=" + method + ", target=" + target)
|
||||
|
||||
// Split query string
|
||||
path = target
|
||||
|
|
@ -85,7 +109,12 @@ class HTTPCGIServer(number port)
|
|||
file_path = this.htdocs + path
|
||||
|
||||
// Headers and body
|
||||
headers = this._parse_headers(request)
|
||||
// Parse headers; if anything goes wrong, fall back to an empty map
|
||||
headers = {}
|
||||
tmp_headers = this._parse_headers(request)
|
||||
if (typeof(tmp_headers) == "Map")
|
||||
headers = tmp_headers
|
||||
// print("[DBG] headers parsed, type=" + typeof(headers))
|
||||
|
||||
// Extract POST body
|
||||
body = ""
|
||||
|
|
@ -99,19 +128,19 @@ class HTTPCGIServer(number port)
|
|||
|
||||
// Serve CGI when file ends with .fun
|
||||
if (str_ends_with(path, ".fun"))
|
||||
// Build env vars according to CGI conventions
|
||||
host = headers["HOST"]
|
||||
// Build env vars according to CGI conventions.
|
||||
// Extract selected request headers safely (map lookups may yield nil).
|
||||
host = this._hget(headers, "HOST")
|
||||
if (len(host) == 0) host = "localhost"
|
||||
ua = headers["USER-AGENT"]
|
||||
cookie = headers["COOKIE"]
|
||||
ctype = headers["CONTENT-TYPE"]
|
||||
clen = headers["CONTENT-LENGTH"]
|
||||
ua = this._hget(headers, "USER-AGENT")
|
||||
cookie = this._hget(headers, "COOKIE")
|
||||
ctype = this._hget(headers, "CONTENT-TYPE")
|
||||
clen = this._hget(headers, "CONTENT-LENGTH")
|
||||
|
||||
envs = []
|
||||
// Ensure Fun stdlib is available to CGI child process
|
||||
funlib = env("FUN_LIB_DIR")
|
||||
if (len(funlib) == 0)
|
||||
// Best-effort default when running from project root
|
||||
funlib = "./lib"
|
||||
push(envs, "FUN_LIB_DIR='" + funlib + "'")
|
||||
push(envs, "REQUEST_METHOD='" + method + "'")
|
||||
|
|
@ -146,16 +175,41 @@ class HTTPCGIServer(number port)
|
|||
|
||||
// Run the Fun script as a CGI program
|
||||
cmd = join(envs, " ") + " " + exec + " " + file_path
|
||||
// print("[DBG] CGI exec: " + cmd)
|
||||
res = proc_run(cmd)
|
||||
out = res["out"]
|
||||
|
||||
// Be defensive: proc_run() may fail or return a non-map depending on platform
|
||||
if (res == nil || typeof(res) != "Map")
|
||||
print("[CGI] proc_run failed for: " + file_path)
|
||||
this._send(fd, 500, "Internal Server Error", "<h1>Failed to execute CGI</h1>")
|
||||
sock_close(fd)
|
||||
return 0
|
||||
|
||||
// Safely extract fields from result map; guard each access
|
||||
out = ""
|
||||
code = 0
|
||||
tmp = nil
|
||||
if (typeof(res) == "Map")
|
||||
tmp = res["out"]
|
||||
if (tmp != nil)
|
||||
out = tmp
|
||||
tmpc = nil
|
||||
if (typeof(res) == "Map")
|
||||
tmpc = res["code"]
|
||||
if (tmpc != nil)
|
||||
code = tmpc
|
||||
|
||||
if (len(out) == 0)
|
||||
this._send(fd, 500, "Internal Server Error", "<h1>CGI produced no output</h1>")
|
||||
if (to_string(code) != "0")
|
||||
this._send(fd, 500, "Internal Server Error", "<h1>CGI failed (exit " + to_string(code) + ")</h1>")
|
||||
else
|
||||
this._send(fd, 500, "Internal Server Error", "<h1>CGI produced no output</h1>")
|
||||
else
|
||||
this._send_cgi_response(fd, out)
|
||||
else
|
||||
// Static file
|
||||
content = read_file(file_path)
|
||||
// print("[DBG] static path: " + file_path + ", content_len=" + to_string(len(content)))
|
||||
if (len(content) > 0)
|
||||
this._send(fd, 200, "OK", content)
|
||||
else
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue