diff --git a/libc/mem/vasprintf.c b/libc/mem/vasprintf.c index 6c7fa26b3..040f11199 100644 --- a/libc/mem/vasprintf.c +++ b/libc/mem/vasprintf.c @@ -29,23 +29,21 @@ * @see xasprintf() for a better API */ int(vasprintf)(char **strp, const char *fmt, va_list va) { - /* - * This implementation guarantees the smallest possible allocation, - * using an optimistic approach w/o changing asymptotic complexity. - */ - size_t size = 32; - if ((*strp = malloc(size))) { - va_list va2; + int wrote; + char *p; + size_t size; + va_list va2; + if ((*strp = malloc((size = 512)))) { va_copy(va2, va); - int wrote = (vsnprintf)(*strp, size, fmt, va); + wrote = (vsnprintf)(*strp, size, fmt, va); if (wrote == -1) return -1; - if (wrote <= size - 1) { + if (wrote < size) { + if ((p = realloc(*strp, wrote + 1))) *strp = p; return wrote; } else { size = wrote + 1; - char *buf2 = realloc(*strp, size); - if (buf2) { - *strp = buf2; + if ((p = realloc(*strp, size))) { + *strp = p; wrote = (vsnprintf)(*strp, size, fmt, va2); assert(wrote == size - 1); return wrote; diff --git a/libc/sock/socket-sysv.c b/libc/sock/socket-sysv.c index c40cab59c..e11e42bcf 100644 --- a/libc/sock/socket-sysv.c +++ b/libc/sock/socket-sysv.c @@ -22,15 +22,29 @@ #include "libc/sysv/consts/sock.h" int sys_socket(int family, int type, int protocol) { - int rc, olderr, modernflags; - olderr = errno; - rc = __sys_socket(family, type, protocol); - if ((SupportsLinux() || SupportsXnu()) && - (rc == -1 && errno == EINVAL /* rhel5 behavior */) && - (modernflags = (type & (SOCK_CLOEXEC | SOCK_NONBLOCK)))) { - errno = olderr; - rc = __fixupnewsockfd(__sys_socket(family, type & ~modernflags, protocol), - modernflags); + static bool once, demodernize; + int sock, olderr; + if (!once && (type & (SOCK_CLOEXEC | SOCK_NONBLOCK))) { + if (IsXnu()) { + demodernize = true; + once = true; + } else { + olderr = errno; + if ((sock = __sys_socket(family, type, protocol)) != -1) { + once = true; + return sock; + } else { + errno = olderr; + demodernize = true; + once = true; + } + } + } + if (!demodernize) { + return __sys_socket(family, type, protocol); + } else { + return __fixupnewsockfd( + __sys_socket(family, type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK), protocol), + type); } - return rc; } diff --git a/net/http/gethttpmethod.gperf b/net/http/gethttpmethod.gperf index 1e44e350b..bc7e90a3f 100644 --- a/net/http/gethttpmethod.gperf +++ b/net/http/gethttpmethod.gperf @@ -16,8 +16,8 @@ GET, kHttpGet HEAD, kHttpHead POST, kHttpPost PUT, kHttpPut -CONNECT, kHttpConnect OPTIONS, kHttpOptions +CONNECT, kHttpConnect TRACE, kHttpTrace COPY, kHttpCopy LOCK, kHttpLock diff --git a/net/http/http.h b/net/http/http.h index 761eaff17..79766d17a 100644 --- a/net/http/http.h +++ b/net/http/http.h @@ -8,8 +8,8 @@ #define kHttpPost 2 #define kHttpPut 3 #define kHttpDelete 4 -#define kHttpConnect 5 -#define kHttpOptions 6 +#define kHttpOptions 5 +#define kHttpConnect 6 #define kHttpTrace 7 #define kHttpCopy 8 #define kHttpLock 9 @@ -89,8 +89,7 @@ struct HttpRequestSlice { }; struct HttpRequest { - int i, t, a; - int method; + int i, t, a, method; struct HttpRequestSlice k; struct HttpRequestSlice uri; struct HttpRequestSlice version; diff --git a/net/http/khttpmethod.c b/net/http/khttpmethod.c index 777346e32..09eb42d74 100644 --- a/net/http/khttpmethod.c +++ b/net/http/khttpmethod.c @@ -24,8 +24,8 @@ const char kHttpMethod[17][8] = { "POST", // "PUT", // "DELETE", // - "CONNECT", // "OPTIONS", // + "CONNECT", // "TRACE", // "COPY", // "LOCK", // diff --git a/third_party/lua/llex.c b/third_party/lua/llex.c index f6b86be83..644d4a09e 100644 --- a/third_party/lua/llex.c +++ b/third_party/lua/llex.c @@ -389,6 +389,7 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) { int c; /* final character to be saved */ save_and_next(ls); /* keep '\\' for error messages */ switch (ls->current) { + case 'e': c = '\e'; goto read_save; case 'a': c = '\a'; goto read_save; case 'b': c = '\b'; goto read_save; case 'f': c = '\f'; goto read_save; diff --git a/tool/net/.init.lua b/tool/net/.init.lua index 0c16759d8..7f4a47716 100644 --- a/tool/net/.init.lua +++ b/tool/net/.init.lua @@ -1 +1,2 @@ -- special script called by main redbean process at startup +ProgramRedirect(0, '/favicon.ico', '/tool/net/redbean.ico') diff --git a/tool/net/redbean-xhr.lua b/tool/net/redbean-xhr.lua index b4dafd0b1..cd93d5d4e 100644 --- a/tool/net/redbean-xhr.lua +++ b/tool/net/redbean-xhr.lua @@ -1,2 +1,3 @@ -- redbean xhr handler demo +SetHeader('Vary', 'X-Custom-Header') SetHeader('X-Custom-Header', 'hello ' .. GetHeader('x-custom-header')) diff --git a/tool/net/redbean.c b/tool/net/redbean.c index dfdf40aeb..1242014a5 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -34,6 +34,7 @@ #include "libc/log/log.h" #include "libc/macros.internal.h" #include "libc/math.h" +#include "libc/mem/fmt.h" #include "libc/mem/mem.h" #include "libc/nexgen32e/bsf.h" #include "libc/nexgen32e/bsr.h" @@ -99,16 +100,18 @@ FLAGS\n\ -z print port\n\ -m log messages\n\ -b log message bodies\n\ + -k encourage keep-alive\n\ -D DIR serve assets from directory\n\ -c INT cache seconds\n\ -r /X=/Y redirect X to Y\n\ + -R /X=/Y rewrite X to Y\n\ -l ADDR listen ip [default 0.0.0.0]\n\ -p PORT listen port [default 8080]\n\ -L PATH log file location\n\ -P PATH pid file location\n\ -U INT daemon set user id\n\ -G INT daemon set group id\n\ - -B STR changes server header\n\ + -B STR changes brand\n\ \n\ FEATURES\n\ \n\ @@ -149,10 +152,6 @@ USAGE\n\ connection processes, which grow to whatever number your system\n\ limits and tcp stack configuration allow. If fork() should fail\n\ then redbean starts shutting idle connections down.\n\ -\n\ - Redirects emit a 307 response unless the location exists in\n\ - the zip directory, in which case it transparently rewrites.\n\ -\n\ \n" #define HASH_LOAD_FACTOR /* 1. / */ 4 @@ -281,6 +280,7 @@ static struct Freelist { static struct Redirects { size_t n; struct Redirect { + int code; const char *path; size_t pathlen; const char *location; @@ -301,12 +301,17 @@ static struct Assets { } * p; } assets; +static struct Shared { + int workers; // +} * shared; + static bool killed; static bool istext; static bool zombied; static bool gzipped; static bool branded; static bool meltdown; +static bool unbranded; static bool heartless; static bool printport; static bool heartbeat; @@ -319,6 +324,7 @@ static bool logmessages; static bool checkedmethod; static bool connectionclose; static bool keyboardinterrupt; +static bool encouragekeepalive; static int frags; static int gmtoff; @@ -340,6 +346,8 @@ static size_t msgsize; static size_t amtread; static char *luaheaderp; struct Strings stagedirs; +static const char *sauce; +static const char *brand; static const char *pidpath; static const char *logpath; static int64_t programtime; @@ -348,6 +356,7 @@ static int64_t cacheseconds; static uint8_t gzip_footer[8]; static const char *serverheader; +static struct Buffer logo; static struct Buffer inbuf; static struct Buffer hdrbuf; static struct Buffer outbuf; @@ -355,6 +364,7 @@ static struct Request request; static long double nowish; static long double startread; +static long double lastmeltdown; static long double startrequest; static long double startconnection; static struct sockaddr_in serveraddr; @@ -436,15 +446,17 @@ static long FindRedirect(const char *path, size_t n) { return -1; } -static void AddRedirect(const char *arg) { +static void ProgramRedirect(int code, const char *src, const char *dst) { long i, j; - const char *p; struct Redirect r; - CHECK_NOTNULL((p = strchr(arg, '='))); - CHECK_GT(p - arg, 0); - r.path = arg; - r.pathlen = p - arg; - r.location = p + 1; + if (code && code != 301 && code != 302 && code != 307 && code != 308) { + fprintf(stderr, "error: unsupported redirect code %d\n", code); + exit(1); + } + r.code = code; + r.path = strdup(src); + r.pathlen = strlen(src); + r.location = strdup(dst); if ((i = FindRedirect(r.path, r.pathlen)) != -1) { redirects.p[i] = r; } else { @@ -463,6 +475,17 @@ static void AddRedirect(const char *arg) { } } +static void ProgramRedirectArg(int code, const char *arg) { + char *s; + const char *p; + if (!(p = strchr(arg, '='))) { + fprintf(stderr, "error: redirect arg missing '='\n"); + exit(1); + } + ProgramRedirect(code, (s = strndup(arg, p - arg)), p + 1); + free(s); +} + static int CompareInts(const uint64_t x, uint64_t y) { return x > y ? 1 : x < y ? -1 : 0; } @@ -512,15 +535,32 @@ static void DescribeAddress(char buf[32], const struct sockaddr_in *addr) { *p = '\0'; } +static void ProgramBrand(const char *s) { + free(brand); + free(serverheader); + brand = strdup(s); + if (!strstr(s, "redbean")) unbranded = true; + if (!(serverheader = EncodeHttpHeaderValue(brand, -1, 0))) { + fprintf(stderr, "error: brand isn't latin1 encodable: %`'s", brand); + exit(1); + } +} + +static void ProgramCache(long x) { + cacheseconds = x; +} + +static void ProgramPort(long x) { + serveraddr.sin_port = htons(x); +} + static void SetDefaults(void) { - cacheseconds = -1; - serverheader = "redbean/0.2"; + ProgramBrand("redbean/0.2"); + ProgramCache(-1); + ProgramPort(DEFAULT_PORT); serveraddr.sin_family = AF_INET; - serveraddr.sin_port = htons(DEFAULT_PORT); serveraddr.sin_addr.s_addr = INADDR_ANY; - AddRedirect("/=/tool/net/redbean.html"); - AddRedirect("/index.html=/tool/net/redbean.html"); - AddRedirect("/favicon.ico=/tool/net/redbean.ico"); + if (IsWindows()) uniprocess = true; } static wontreturn void PrintUsage(FILE *f, int rc) { @@ -548,7 +588,7 @@ static void AddStagingDirectory(const char *dirpath) { static void GetOpts(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:c:L:P:U:G:B:D:")) != -1) { + while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:R:c:L:P:U:G:B:D:")) != -1) { switch (opt) { case 'v': __log_level++; @@ -568,23 +608,29 @@ static void GetOpts(int argc, char *argv[]) { case 'z': printport = true; break; + case 'k': + encouragekeepalive = true; + break; case 'r': - AddRedirect(optarg); + ProgramRedirectArg(307, optarg); + break; + case 'R': + ProgramRedirectArg(0, optarg); break; case 'D': AddStagingDirectory(optarg); break; case 'c': - cacheseconds = strtol(optarg, NULL, 0); + ProgramCache(strtol(optarg, NULL, 0)); break; case 'p': - CHECK_NE(0xFFFF, (serveraddr.sin_port = htons(parseport(optarg)))); + ProgramPort(strtol(optarg, NULL, 0)); break; case 'l': CHECK_EQ(1, inet_pton(AF_INET, optarg, &serveraddr.sin_addr)); break; case 'B': - serverheader = emptytonull(EncodeHttpHeaderValue(optarg, -1, 0)); + ProgramBrand(optarg); break; case 'L': logpath = optarg; @@ -631,14 +677,18 @@ static void Daemonize(void) { } static void OnWorkerExit(int pid, int ws) { + int w; + w = --shared->workers; if (WIFEXITED(ws)) { if (WEXITSTATUS(ws)) { - WARNF("worker %d exited with %d", pid, WEXITSTATUS(ws)); + WARNF("worker %d exited with %d (%,d workers remain)", pid, + WEXITSTATUS(ws), w); } else { - DEBUGF("worker %d exited", pid); + DEBUGF("worker %d exited (%,d workers remain)", pid, w); } } else { - WARNF("worker %d terminated with %s", pid, strsignal(WTERMSIG(ws))); + WARNF("worker %d terminated with %s (%,d workers remain)", pid, + strsignal(WTERMSIG(ws)), w); } } @@ -651,7 +701,9 @@ static void WaitAll(void) { if (errno == ECHILD) break; if (errno == EINTR) { if (killed) { - WARNF("%s terminating harder", serveraddrstr); + killed = false; + terminated = false; + WARNF("%s redbean shall terminate harder", serveraddrstr); LOGIFNEG1(kill(0, SIGTERM)); } continue; @@ -803,11 +855,18 @@ static void *FreeLater(void *p) { static void CollectGarbage(void) { size_t i; for (i = 0; i < freelist.n; ++i) free(freelist.p[i]); + free(freelist.p); + freelist.p = 0; freelist.n = 0; + free(outbuf.p); free(request.params.p); DestroyHttpRequest(&msg); } +static bool IsCompressionMethodSupported(int method) { + return method == kZipCompressionNone || method == kZipCompressionDeflate; +} + static void IndexAssets(void) { int64_t lm; struct Asset *p; @@ -819,6 +878,12 @@ static void IndexAssets(void) { CHECK_EQ(kZipCdirHdrMagic, ZIP_CDIR_MAGIC(zdir)); for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) { CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf)); + if (!IsCompressionMethodSupported(ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf))) { + WARNF("don't understand zip compression method %d used by %`'.*s", + ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf), + ZIP_CFILE_NAMESIZE(zmap + cf), ZIP_CFILE_NAME(zmap + cf)); + continue; + } hash = Hash(ZIP_CFILE_NAME(zmap + cf), ZIP_CFILE_NAMESIZE(zmap + cf)); step = 0; do { @@ -1019,7 +1084,7 @@ static void ParseFragment(struct Parser *u, struct Buffer *h) { u->q = u->p; } -static bool ParseRequestUri(void) { +static void ParseRequestUri(void) { struct Parser u; u.i = 0; u.c = 0; @@ -1032,8 +1097,6 @@ static bool ParseRequestUri(void) { ParsePath(&u, &request.path); if (u.c == '?') ParseParams(&u, &request.params); if (u.c == '#') ParseFragment(&u, &request.fragment); - return u.i == u.size && - IsAcceptableHttpRequestPath(request.path.p, request.path.n); } static void ParseFormParams(void) { @@ -1310,6 +1373,43 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { return p; } +static void AppendData(const char *data, size_t size) { + outbuf.p = xrealloc(outbuf.p, outbuf.n + size); + memcpy(outbuf.p + outbuf.n, data, size); + outbuf.n += size; +} + +static void AppendString(const char *s) { + AppendData(s, strlen(s)); +} + +static void AppendFmt(const char *fmt, ...) { + int size; + char *data; + va_list va; + data = NULL; + va_start(va, fmt); + CHECK_NE(-1, (size = vasprintf(&data, fmt, va))); + va_end(va); + AppendData(data, size); + free(data); +} + +static char *CommitOutput(char *p) { + if (istext && outbuf.n >= 100 && ClientAcceptsGzip()) { + gzipped = true; + p = AppendHeader(p, "Content-Encoding", "gzip"); + p = AppendHeader(p, "Vary", "Accept-Encoding"); + WRITE32LE(gzip_footer + 0, crc32_z(0, outbuf.p, outbuf.n)); + WRITE32LE(gzip_footer + 4, outbuf.n); + content = FreeLater(Deflate(outbuf.p, outbuf.n, &contentlength)); + } else { + content = outbuf.p; + contentlength = outbuf.n; + } + return p; +} + static int LuaServeAsset(lua_State *L) { size_t pathlen; struct Asset *a; @@ -1389,11 +1489,6 @@ static int LuaGetPath(lua_State *L) { return 1; } -static int LuaGetFragment(lua_State *L) { - lua_pushlstring(L, request.fragment.p, request.fragment.n); - return 1; -} - static void LuaPushLatin1(lua_State *L, const char *s, size_t n) { char *t; size_t m; @@ -1588,9 +1683,7 @@ static int LuaWrite(lua_State *L) { size_t size; const char *data; data = luaL_checklstring(L, 1, &size); - outbuf.p = xrealloc(outbuf.p, outbuf.n + size); - memcpy(outbuf.p + outbuf.n, data, size); - outbuf.n += size; + AppendData(data, size); return 0; } @@ -1697,11 +1790,58 @@ static int LuaCrc32c(lua_State *L) { return 1; } +static int LuaProgramPort(lua_State *L) { + ProgramPort(luaL_checkinteger(L, 1)); + return 0; +} + +static int LuaProgramCache(lua_State *L) { + ProgramCache(luaL_checkinteger(L, 1)); + return 0; +} + +static int LuaProgramBrand(lua_State *L) { + ProgramBrand(luaL_checkstring(L, 1)); + return 0; +} + +static int LuaProgramRedirect(lua_State *L) { + ProgramRedirect(luaL_checkinteger(L, 1), luaL_checkstring(L, 2), + luaL_checkstring(L, 3)); + return 0; +} + +static int LuaGetLogLevel(lua_State *L) { + lua_pushinteger(L, __log_level); + return 1; +} + +static int LuaSetLogLevel(lua_State *L) { + __log_level = luaL_checkinteger(L, 1); + return 0; +} + +static int LuaLog(lua_State *L) { + int level; + lua_Debug ar; + const char *msg, *module; + level = luaL_checkinteger(L, 1); + if (LOGGABLE(level)) { + msg = luaL_checkstring(L, 2); + lua_getstack(L, 1, &ar); + lua_getinfo(L, "nSl", &ar); + module = !strcmp(ar.name, "main") ? sauce : ar.name; + flogf(level, module, ar.currentline, NULL, "%s", msg); + } + return 0; +} + static void LuaRun(const char *path) { struct Asset *a; const char *code; if ((a = LocateAsset(path, strlen(path)))) { code = LoadAsset(a, NULL); + sauce = path + 1; if (luaL_dostring(L, code) != LUA_OK) { WARNF("%s %s", path, lua_tostring(L, -1)); } @@ -1723,9 +1863,9 @@ static const luaL_Reg kLuaFuncs[] = { {"FormatHttpDateTime", LuaFormatHttpDateTime}, // {"GetClientAddr", LuaGetClientAddr}, // {"GetDate", LuaGetDate}, // - {"GetFragment", LuaGetFragment}, // {"GetHeader", LuaGetHeader}, // {"GetHeaders", LuaGetHeaders}, // + {"GetLogLevel", LuaGetLogLevel}, // {"GetMethod", LuaGetMethod}, // {"GetParam", LuaGetParam}, // {"GetParams", LuaGetParams}, // @@ -1736,10 +1876,16 @@ static const luaL_Reg kLuaFuncs[] = { {"GetVersion", LuaGetVersion}, // {"HasParam", LuaHasParam}, // {"LoadAsset", LuaLoadAsset}, // + {"Log", LuaLog}, // {"ParseHttpDateTime", LuaParseHttpDateTime}, // + {"ProgramBrand", LuaProgramBrand}, // + {"ProgramCache", LuaProgramCache}, // + {"ProgramPort", LuaProgramPort}, // + {"ProgramRedirect", LuaProgramRedirect}, // {"ServeAsset", LuaServeAsset}, // {"ServeError", LuaServeError}, // {"SetHeader", LuaSetHeader}, // + {"SetLogLevel", LuaSetLogLevel}, // {"SetStatus", LuaSetStatus}, // {"Write", LuaWrite}, // {"bsf", LuaBsf}, // @@ -1749,7 +1895,7 @@ static const luaL_Reg kLuaFuncs[] = { {"popcnt", LuaPopcnt}, // }; -static void LuaSetArgv(void) { +static void LuaSetArgv(lua_State *L) { size_t i; lua_newtable(L); for (i = optind; i < __argc; ++i) { @@ -1759,6 +1905,11 @@ static void LuaSetArgv(void) { lua_setglobal(L, "argv"); } +static void LuaSetConstant(lua_State *L, const char *s, long x) { + lua_pushinteger(L, x); + lua_setglobal(L, s); +} + static void LuaInit(void) { size_t i; L = luaL_newstate(); @@ -1767,7 +1918,13 @@ static void LuaInit(void) { lua_pushcfunction(L, kLuaFuncs[i].func); lua_setglobal(L, kLuaFuncs[i].name); } - LuaSetArgv(); + LuaSetArgv(L); + LuaSetConstant(L, "kLogDebug", kLogDebug); + LuaSetConstant(L, "kLogVerbose", kLogVerbose); + LuaSetConstant(L, "kLogInfo", kLogInfo); + LuaSetConstant(L, "kLogWarn", kLogWarn); + LuaSetConstant(L, "kLogError", kLogError); + LuaSetConstant(L, "kLogFatal", kLogFatal); LuaRun("/tool/net/.init.lua"); } @@ -1777,25 +1934,15 @@ static void LuaReload(void) { static char *ServeLua(struct Asset *a) { char *p; - outbuf.n = 0; luaheaderp = NULL; + sauce = FreeLater(strndup(request.path.p + 1, request.path.n - 1)); if (luaL_dostring(L, FreeLater(LoadAsset(a, NULL))) == LUA_OK) { if (!(p = luaheaderp)) { p = SetStatus(200, "OK"); p = AppendContentType(p, "text/html"); } if (outbuf.n) { - if (istext && outbuf.n >= 100 && ClientAcceptsGzip()) { - gzipped = true; - p = AppendHeader(p, "Content-Encoding", "gzip"); - p = AppendHeader(p, "Vary", "Accept-Encoding"); - WRITE32LE(gzip_footer + 0, crc32_z(0, outbuf.p, outbuf.n)); - WRITE32LE(gzip_footer + 4, outbuf.n); - content = FreeLater(Deflate(outbuf.p, outbuf.n, &contentlength)); - } else { - content = outbuf.p; - contentlength = outbuf.n; - } + p = CommitOutput(p); } return p; } else { @@ -1828,24 +1975,25 @@ static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) { } static char *HandleRedirect(struct Redirect *r) { - char *p; struct Asset *a; - if ((a = LocateAsset(r->location, strlen(r->location)))) { - DEBUGF("%s %s %`'.*s rewritten %`'s", clientaddrstr, - kHttpMethod[msg.method], request.path.n, request.path.p, - r->location); - p = HandleAsset(a, r->location, strlen(r->location)); + if (!r->code) { + if ((a = LocateAsset(r->location, strlen(r->location)))) { + DEBUGF("%s %s %`'.*s rewritten %`'s", clientaddrstr, + kHttpMethod[msg.method], request.path.n, request.path.p, + r->location); + return HandleAsset(a, r->location, strlen(r->location)); + } else { + return ServeError(505, "HTTP Version Not Supported"); + } } else if (httpversion == 9) { - p = ServeError(505, "HTTP Version Not Supported"); + return ServeError(505, "HTTP Version Not Supported"); } else { - DEBUGF("%s %s %`'.*s redirecting %`'s", clientaddrstr, - kHttpMethod[msg.method], request.path.n, request.path.p, + DEBUGF("%s %s %`'.*s %d redirecting %`'s", clientaddrstr, + kHttpMethod[msg.method], request.path.n, request.path.p, r->code, r->location); - p = SetStatus(307, "Temporary Redirect"); - p = AppendHeader(p, "Location", - FreeLater(EncodeHttpHeaderValue(r->location, -1, 0))); + return AppendHeader(SetStatus(r->code, GetHttpReason(r->code)), "Location", + FreeLater(EncodeHttpHeaderValue(r->location, -1, 0))); } - return p; } static void LogMessage(const char *d, const char *s, size_t n) { @@ -1930,6 +2078,140 @@ static const char *DescribeClose(void) { return "destroyed"; } +static const char *DescribeCompressionRatio(char rb[8], uint32_t cf) { + long percent; + if (ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf) == kZipCompressionNone) { + return "n/a"; + } else { + percent = lround(100 - (double)ZIP_CFILE_COMPRESSEDSIZE(zmap + cf) / + ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf) * 100); + sprintf(rb, "%ld%%", MIN(999, MAX(-999, percent))); + return rb; + } +} + +static void LoadLogo(void) { + char *p; + size_t n; + struct Asset *a; + const char *logopath; + logopath = "/tool/net/redbean.png"; + if ((a = LocateAsset(logopath, strlen(logopath)))) { + p = LoadAsset(a, &n); + logo.p = EncodeBase64(p, n, &logo.n); + free(p); + } +} + +static char *ServeListing(void) { + char rb[8]; + char tb[64]; + int w, x, y; + struct tm tm; + int64_t lastmod; + uint32_t cf, lf; + size_t n, n1, n2; + char *p, *p1, *p2; + struct EscapeResult r[3]; + AppendString("\ +\n\ +\n\ +
\n"); + w = x = 0; + n = ZIP_CDIR_RECORDS(zdir); + CHECK_EQ(kZipCdirHdrMagic, ZIP_CDIR_MAGIC(zdir)); + for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) { + CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf)); + y = strnwidth(ZIP_CFILE_NAME(zmap + cf), ZIP_CFILE_NAMESIZE(zmap + cf), 0); + w = MIN(80, MAX(w, y + 2)); + y = ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf); + y = y ? llog10(y) : 1; + x = MIN(80, MAX(x, y + (y - 1) / 3 + 2)); + } + n = ZIP_CDIR_RECORDS(zdir); + for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) { + CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf)); + p1 = ZIP_CFILE_NAME(zmap + cf); + n1 = ZIP_CFILE_NAMESIZE(zmap + cf); + p2 = malloc(1 + n1); + n2 = 1 + n1; + *p2 = '/'; + memcpy(p2 + 1, p1, n1); + r[0] = EscapeHtml(p2, n2); + r[1] = EscapeUrlPath(p2, n2); + r[2] = EscapeHtml(r[1].data, r[1].size); + lastmod = GetLastModifiedZip(zmap + cf); + localtime_r(&lastmod, &tm); + strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", &tm); + if (IsCompressionMethodSupported(ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf)) && + IsAcceptableHttpRequestPath(p2, n2)) { + AppendFmt("%-*.*s %s %4s %,*ld\n", r[2].size, + r[2].data, w, r[0].size, r[0].data, tb, + DescribeCompressionRatio(rb, cf), x, + ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf)); + } else { + AppendFmt("%-*.*s %s %4s %,*ld\n", w, r[0].size, r[0].data, tb, + DescribeCompressionRatio(rb, cf), x, + ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf)); + } + free(r[2].data); + free(r[1].data); + free(r[0].data); + free(p2); + } + AppendString("
\n");
+ if (!unbranded) {
+ AppendString("\
+this listing is for /\n\
+when there's no /index.lua or /index.html in your zip
\n\
+redbean is based on\n\
+cosmopolitan and\n\
+αcτµαlly\n\
+pδrταblε εxεcµταblε
\n\
+redbean is authored by Justine Tunney who's on\n\
+GitHub and\n\
+Twitter
\n\
+your redbean is ");
+ }
+ w = shared->workers;
+ AppendFmt("currently servicing %,d connection%s\n", w, w == 1 ? "" : "s");
+ p = SetStatus(200, "OK");
+ p = AppendCache(p, 0);
+ return CommitOutput(p);
+}
+
+static char *ServeServerOptions(void) {
+ char *p;
+ p = SetStatus(200, "OK");
+ p = AppendHeader(p, "Accept", "*/*");
+ p = AppendHeader(p, "Accept-Charset", "utf-8");
+ p = AppendHeader(p, "Allow", "GET, HEAD, POST, PUT, DELETE, OPTIONS");
+ VERBOSEF("%s pinged our server with OPTIONS *", clientaddrstr);
+ return p;
+}
+
static char *HandleMessage(void) {
long r;
ssize_t cl, rc;
@@ -1940,7 +2222,7 @@ static char *HandleMessage(void) {
if (httpversion > 101) {
return ServeError(505, "HTTP Version Not Supported");
}
- if (msg.method > kHttpPost ||
+ if (msg.method > kHttpOptions ||
(HasHeader(kHttpTransferEncoding) &&
!HeaderEquals(kHttpTransferEncoding, "identity"))) {
return ServeError(501, "Not Implemented");
@@ -1954,7 +2236,7 @@ static char *HandleMessage(void) {
if (HasHeader(kHttpContentLength)) {
return ServeError(400, "Bad Request");
} else if (msg.method != kHttpGet && msg.method != kHttpHead &&
- msg.method != kHttpOptions) {
+ msg.method != kHttpDelete && msg.method != kHttpOptions) {
return ServeError(411, "Length Required");
} else {
cl = 0;
@@ -1969,7 +2251,7 @@ static char *HandleMessage(void) {
}
while (amtread < need) {
if (++frags == 64) {
- LogClose("payload fragged");
+ LogClose("payload fragged!");
return ServeError(408, "Request Timeout");
}
if ((rc = read(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) {
@@ -1996,7 +2278,12 @@ static char *HandleMessage(void) {
if (httpversion != 101 || IsConnectionClose()) {
connectionclose = true;
}
- if (!ParseRequestUri()) {
+ ParseRequestUri();
+ if (msg.method == kHttpOptions &&
+ !CompareSlices(request.path.p, request.path.n, "*", 1)) {
+ return ServeServerOptions();
+ }
+ if (!IsAcceptableHttpRequestPath(request.path.p, request.path.n)) {
WARNF("%s could not parse request request %`'.*s", clientaddrstr,
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a);
connectionclose = true;
@@ -2013,6 +2300,8 @@ static char *HandleMessage(void) {
return HandleAsset(a, request.path.p, request.path.n);
} else if ((r = FindRedirect(request.path.p, request.path.n)) != -1) {
return HandleRedirect(redirects.p + r);
+ } else if (!CompareSlices(request.path.p, request.path.n, "/", 1)) {
+ return ServeListing();
} else {
return ServeError(404, "Not Found");
}
@@ -2051,6 +2340,8 @@ static bool HandleRequest(void) {
}
if (connectionclose) {
p = AppendHeader(p, "Connection", "close");
+ } else if (encouragekeepalive && httpversion >= 101) {
+ p = AppendHeader(p, "Connection", "keep-alive");
}
actualcontentlength = contentlength;
if (gzipped) {
@@ -2091,6 +2382,8 @@ static bool HandleRequest(void) {
static void InitRequest(void) {
frags = 0;
msgsize = 0;
+ outbuf.p = 0;
+ outbuf.n = 0;
content = NULL;
gzipped = false;
branded = false;
@@ -2118,7 +2411,7 @@ static void ProcessRequests(void) {
} else if (got) {
if (++frags == 32) {
SendTimeout();
- LogClose("fragged");
+ LogClose("fragged!");
return;
} else {
DEBUGF("%s fragmented msg %,ld %,ld", clientaddrstr, amtread,
@@ -2151,6 +2444,13 @@ static void ProcessRequests(void) {
}
}
+static void EnterMeltdownMode(void) {
+ if (lastmeltdown && nowl() - lastmeltdown < 1) return;
+ WARNF("redbean is entering meltdown mode with %,d workers", shared->workers);
+ LOGIFNEG1(kill(0, SIGUSR2));
+ lastmeltdown = nowl();
+}
+
static void ProcessConnection(void) {
int pid;
clientaddrsize = sizeof(clientaddr);
@@ -2167,11 +2467,11 @@ static void ProcessConnection(void) {
connectionclose = false;
break;
case -1:
- WARNF("redbean is entering meltdown mode");
- LOGIFNEG1(kill(0, SIGUSR2));
+ EnterMeltdownMode();
SendServiceUnavailable();
/* fallthrough */
default:
+ ++shared->workers;
close(client);
return;
}
@@ -2195,12 +2495,20 @@ static void TuneServerSocket(void) {
LOGIFNEG1(setsockopt(server, IPPROTO_TCP, TCP_QUICKACK, &yes, sizeof(yes)));
}
-void RedBean(void) {
+void RedBean(int argc, char *argv[]) {
uint32_t addrsize;
- if (IsWindows()) uniprocess = true;
gmtoff = GetGmtOffset();
+ CHECK_NE(MAP_FAILED,
+ (shared = mmap(NULL, ROUNDUP(sizeof(struct Shared), FRAMESIZE),
+ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
+ -1, 0)));
OpenZip((const char *)getauxval(AT_EXECFN));
IndexAssets();
+ LoadLogo();
+ SetDefaults();
+ GetOpts(argc, argv);
+ LuaInit();
+ if (uniprocess) shared->workers = 1;
xsigaction(SIGINT, OnInt, 0, 0, 0);
xsigaction(SIGHUP, OnHup, 0, 0, 0);
xsigaction(SIGTERM, OnTerm, 0, 0, 0);
@@ -2239,7 +2547,6 @@ void RedBean(void) {
inbuf.p = xvalloc(inbuf.n);
hdrbuf.n = 4 * 1024;
hdrbuf.p = xvalloc(hdrbuf.n);
- LuaInit();
while (!terminated) {
if (zombied) {
ReapZombies();
@@ -2256,7 +2563,7 @@ void RedBean(void) {
ProcessConnection();
}
}
- VERBOSEF("%s terminated", serveraddrstr);
+ VERBOSEF("%s shutting down", serveraddrstr);
LOGIFNEG1(close(server));
if (!keyboardinterrupt) {
if (!killed) {
@@ -2269,8 +2576,6 @@ void RedBean(void) {
int main(int argc, char *argv[]) {
showcrashreports();
- SetDefaults();
- GetOpts(argc, argv);
- RedBean();
+ RedBean(argc, argv);
return 0;
}
diff --git a/tool/net/redbean.lua b/tool/net/redbean.lua
index ba042023e..ec8716733 100644
--- a/tool/net/redbean.lua
+++ b/tool/net/redbean.lua
@@ -1,6 +1,9 @@
-- redbean lua server page demo
local function main()
+ -- This is the best way to print data to the console or log file.
+ Log(kLogWarn, "hello from \e[1mlua\e[0m!")
+
-- This check is pedantic but might be good to have.
if GetMethod() ~= 'GET' and GetMethod() ~= 'HEAD' then
ServeError(405)
@@ -19,6 +22,12 @@ local function main()
Write('