uselesshttp.d/source/uselesshttpd/package.d

197 lines
7.3 KiB
D
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// module source;
/*
lost+skunk <git.macaw.me/skunky>, 2025;
Licensed under WTFPL
*/
import core.stdc.stdio;
import core.sys.linux.epoll;
import core.sys.linux.netinet.tcp;
import core.sys.posix.netinet.in_;
import core.sys.posix.unistd;
import core.sys.posix.sys.socket;
import uselesshttpd.util;
struct Response {
short status = 200;
void[] body;
string[string] headers;
string mimetype = "application/octet-stream";
}
struct Server {
string address;
short port;
private shared int epfd, sock;
void start(MD...)() { // TODO: реализовать "слушальщика" хрюникс сокетов (AF_UNIX)
// import core.stdc.signal;
// extern(C) void z (int s) nothrow @nogc @system {
// signal(s, SIG_IGN);
// this.epfd.close();
// this.sock.close();
// raise(0);
// }
// signal(SIGINT, &z);
bool ipv6;
for (short i; i < address.length; ++i)
if (address[i] == ':') {ipv6=true;break;}
if (ipv6) {
sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in6 sockt;
sockt.sin6_family = AF_INET6;
sockt.sin6_port = htons(port);
inet_pton(AF_INET6, cast(char*)address, &sockt.sin6_addr);
err(bind(sock, cast(sockaddr*)&sockt, sockt.sizeof), "bind");
} else {
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sockt;
sockt.sin_family = AF_INET;
sockt.sin_port = htons(port);
sockt.sin_addr.s_addr = inet_addr(cast(char*)address);
err(bind(sock, cast(sockaddr*)&sockt, sockt.sizeof), "bind");
}
int hz;
setsockopt(sock,
IPPROTO_TCP, SO_REUSEADDR,
cast(void*)hz, int.sizeof);
setsockopt(sock,
IPPROTO_TCP, TCP_NODELAY,
cast(void*)hz, int.sizeof);
// setsockopt(sock,
// IPPROTO_TCP, SO_REUSEPORT,
// cast(void*)hz, int.sizeof);
err(listen(sock, 512), "listen");
serve!MD;
}
void stop() @nogc {
epfd.close();
sock.close();
}
void serve(T...)() {
epfd = epoll_create(MAX_CLIENTS);
epoll_event ev;
epoll_event[MAX_EVENTS] evts;
ev.data.fd = sock;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev);
for (;;) {
auto w = epoll_wait(epfd, &evts[0], MAX_EVENTS, -1);
for (int i = 0; i < w; ++i) {
auto fd = evts[i].data.fd;
if (fd == sock) { // TODO: добавить поддержку ipv6
// sockaddr_in addr;
// socklen_t al = sockaddr_in.sizeof;
// ev.data.fd = accept4(sock, cast(sockaddr*)&addr, &al, SOCK_NONBLOCK);
ev.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
ev.data.fd = accept4(sock, null, null, SOCK_NONBLOCK);
epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
// ip = cast(string)inet_ntoa(addr.sin_addr);
}
if (evts[i].events & EPOLLRDHUP) {
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, null);
close(fd);
break;
}
rd:
ubyte[256] buf;
for (;;) { // обработчик запросов
auto rd = recv(fd, cast(void*)buf, buf.sizeof, 0);
if (rd < 1) break;
auto rqst = parseReq(buf);
if (parseAndValidateURL(rqst.path, &rqst)) {
static auto resp = "HTTP/1.1 400 Bad Request\r\nContent-length: 15\r\n\r\n400 Bad Request";
write(fd, cast(void*)resp, resp.length);
continue;
}
static foreach (mm; __traits(allMembers, T)) {
static if (__traits(isStaticFunction, __traits(getMember, T, mm))) {
foreach(attr; __traits(getAttributes, __traits(getMember, T, mm))) {
static if (is(typeof(attr) == Location)) {
if (rqst.path == attr.path) {
Response rsp;
__traits(getMember, T, mm)(&rsp, &rqst);
char[] headers;
foreach(header,content;rsp.headers)
headers ~= "\r\n" ~ header ~ ": " ~ content;
auto response =
"HTTP/1.1 " ~ intToStr(rsp.status) ~ ' ' ~ getStatus(rsp.status)
~ "\r\nContent-length: " ~ intToStr(rsp.body.length)
~ "\r\nContent-Type: " ~ rsp.mimetype
~ headers
~ "\r\n\r\n"
~ rsp.body;
write(fd, cast(void*)response, response.length);
goto rd;
} else continue;
}
}
}
}
static auto resp = "HTTP/1.1 404 Not Found\r\nContent-length: 13\r\n\r\n404 Not Found";
write(fd, cast(void*)resp, resp.length);
}
}
}
}
// TODO: реализовать парсинг заголовков, оформленных хер пойми как
Request parseReq(ubyte[] body) {
int prev;
short[] xxx;
Request req;
for (short i; i < body.length; ++i) {
if (body[i] == '\r') {
auto splitted = body[prev..i];
for (short x = 1; x < splitted.length; ++x) {
if (prev == 0) { // для прочего говна (метода, пути и протокола)
if (splitted[x] == ' ') xxx ~= x;
else if (xxx.length == 2) {
req.method = cast(req.Methods)splitted[0..xxx[0]];
req.path = cast(char[])splitted[xxx[0]+1..xxx[1]];
// if (splitted[xxx[1]..$] != " HTTP/1.1")
// throw new Exception("Unsupported HTTP version");
} else continue;
} else if (splitted[x-1] == ':') { // для заголовков
req.headers[cast(string)splitted[0..x-1]] = cast(string)splitted[x+1..$];
break;
}
}
prev = i+2;
if ((prev < body.length && prev + 2 <= body.length) && body[prev] == '\r') {
req.body = body[prev+2..$];
break;
}
}
}
return req;
}
}
private:
enum BACKLOG = 512;
enum MAX_EVENTS = 512;
enum MAX_CLIENTS = 512;
enum MAX_MESSAGE_LEN = 2048;
enum SOCK_NONBLOCK = 0x800;
enum MAX_RESPONSES = 512;
extern (C) int accept4(int, sockaddr*, socklen_t*, int);