// module source; /* lost+skunk , 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) 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"); } setsockopt(sock, IPPROTO_TCP, TCP_NODELAY | SO_REUSEADDR | SO_REUSEPORT, cast(void*)(new int), int.sizeof); err(listen(sock, 512), "listen"); serve!MD; } void stop() { 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 | EPOLLEXCLUSIVE; 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.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 (ev.data.fd == -1) { rd: ubyte[128] buf; for (;;) { // обработчик запросов auto rd = recv(fd, cast(void*)buf, buf.sizeof, 0); if (rd < 1) break; auto rqst = parseReq(buf); 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)) { parseAndValidateURL(rqst.path, &rqst); 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); } } } } Request parseReq(ubyte[] body) { // TODO: реализовать парсинг заголовков, оформленных хер пойми как 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);