module lib; /* 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 util; struct Response { short status = 200; ubyte[] body; string[string] headers; string mimetype = "application/octet-stream"; } struct Request { enum Methods { GET = "GET", PUT = "PUT", POST = "POST", DELETE = "DELETE", OPTIONS = "OPTIONS", // остальное лень реализовывать, да и не трэба.. } Methods method; string path, body; string[string] headers; } struct Server { string address; short port; private shared int epfd, sock; void start(MD...)() { // реализовать прокидывания структуры/класса для роутинга sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, cast(void*)(new int), int.sizeof); setsockopt(sock, IPPROTO_TCP, SO_REUSEADDR, cast(void*)(new int), int.sizeof); 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"); err(listen(sock, 512), "listen"); serve!MD; } void shutdown() { epfd.close(); sock.close(); } void serve(T...)() { epfd = epoll_create(MAX_CLIENTS); epoll_event ev; epoll_event[MAX_EVENTS] evts; ev.events = EPOLLIN | EPOLLOUT | EPOLLET; ev.data.fd = sock; 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) { sockaddr_in addr; socklen_t al = sockaddr_in.sizeof; ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP | EPOLLHUP; ev.data.fd = accept4(sock, cast(sockaddr*)&addr, &al, SOCK_NONBLOCK); epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev); } else if (evts[i].events & EPOLLIN) { rd: ubyte[1024] buf; for (;;) { // обработчик запросов if (read(fd, cast(void*)buf, buf.sizeof) > 0) { auto rqst = parseReq(cast(string)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)) { 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 head = "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"; write(fd, cast(void*)head, head.length); write(fd, cast(void*)rsp.body, rsp.body.length); goto rd; } else continue; } } } } static auto resp="HTTP/1.1 404 Not Found\r\nContent-length: 0\r\n\r\n"; write(fd, cast(void*)resp, resp.length); } else break; } } if (evts[i].events & (EPOLLRDHUP | EPOLLHUP)) { epoll_ctl(epfd, EPOLL_CTL_DEL, fd, null); close(fd); break; } } } } Request parseReq(string 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 = splitted[0..xxx[0]]; req.path = 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[splitted[0..x-1]] = splitted[x+1..$]; break; } } prev = i+2; if (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);