| 1 | /* TODO: |
|---|
| 2 | * Track referrers. |
|---|
| 3 | */ |
|---|
| 4 | #include <string> |
|---|
| 5 | #include <sstream> |
|---|
| 6 | #include <iostream> |
|---|
| 7 | |
|---|
| 8 | #include <cstdio> |
|---|
| 9 | #include <cstring> |
|---|
| 10 | #include <cstdlib> |
|---|
| 11 | |
|---|
| 12 | #include <arpa/inet.h> /* bind() */ |
|---|
| 13 | |
|---|
| 14 | #define PORT 31337 |
|---|
| 15 | #define BACKLOG 1000 |
|---|
| 16 | #define THREADS 10 |
|---|
| 17 | |
|---|
| 18 | #include "lmutil.h" |
|---|
| 19 | #include "lmresource.h" |
|---|
| 20 | |
|---|
| 21 | class Server; |
|---|
| 22 | |
|---|
| 23 | Server *g_server; |
|---|
| 24 | |
|---|
| 25 | class Worker |
|---|
| 26 | { |
|---|
| 27 | public: |
|---|
| 28 | Worker(Queue<BACKLOG> *queue) : |
|---|
| 29 | m_ready(0), |
|---|
| 30 | m_die(0), |
|---|
| 31 | m_queue(queue) |
|---|
| 32 | { |
|---|
| 33 | pthread_attr_t attr; |
|---|
| 34 | pthread_attr_init(&attr); |
|---|
| 35 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); |
|---|
| 36 | pthread_create(&m_thread, &attr, Helper, this); |
|---|
| 37 | |
|---|
| 38 | while (!m_ready) |
|---|
| 39 | ; /* spinlock is OK */ |
|---|
| 40 | } |
|---|
| 41 | |
|---|
| 42 | ~Worker() |
|---|
| 43 | { |
|---|
| 44 | pthread_join(m_thread, NULL); |
|---|
| 45 | } |
|---|
| 46 | |
|---|
| 47 | void HandleRequest(int client); |
|---|
| 48 | |
|---|
| 49 | private: |
|---|
| 50 | static void *Helper(void *data) |
|---|
| 51 | { |
|---|
| 52 | Worker *that = (Worker *)data; |
|---|
| 53 | that->m_ready = 1; /* signal main thread spinlock */ |
|---|
| 54 | |
|---|
| 55 | for (;;) |
|---|
| 56 | { |
|---|
| 57 | int client = that->m_queue->Pop(); |
|---|
| 58 | if (client < 0) |
|---|
| 59 | return NULL; |
|---|
| 60 | |
|---|
| 61 | that->HandleRequest(client); |
|---|
| 62 | } |
|---|
| 63 | return NULL; |
|---|
| 64 | } |
|---|
| 65 | |
|---|
| 66 | pthread_t m_thread; |
|---|
| 67 | volatile int m_ready, m_die; |
|---|
| 68 | Queue<BACKLOG> *m_queue; |
|---|
| 69 | }; |
|---|
| 70 | |
|---|
| 71 | class Server |
|---|
| 72 | { |
|---|
| 73 | public: |
|---|
| 74 | Server(int port, int backlog, int nworkers) : |
|---|
| 75 | m_fd(-1), |
|---|
| 76 | m_port(port), |
|---|
| 77 | m_nworkers(nworkers), |
|---|
| 78 | m_requests(0), |
|---|
| 79 | m_failures(0), |
|---|
| 80 | m_starttime(time(NULL)) |
|---|
| 81 | { |
|---|
| 82 | m_fd = socket(AF_INET, SOCK_STREAM, 0); |
|---|
| 83 | if (m_fd < 0) |
|---|
| 84 | Utils::OhShitShitShit("cannot create socket"); |
|---|
| 85 | |
|---|
| 86 | int reuse = 1; |
|---|
| 87 | int ret = setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, |
|---|
| 88 | &reuse, (socklen_t)sizeof(reuse)); |
|---|
| 89 | if (ret < 0) |
|---|
| 90 | Utils::OhShitShitShit("cannot set reuse flag on socket"); |
|---|
| 91 | |
|---|
| 92 | struct sockaddr_in saddr; |
|---|
| 93 | saddr.sin_family = AF_INET; |
|---|
| 94 | saddr.sin_port = htons(m_port); |
|---|
| 95 | saddr.sin_addr.s_addr = htonl(INADDR_ANY); |
|---|
| 96 | ret = bind(m_fd, (struct sockaddr *)&saddr, sizeof(saddr)); |
|---|
| 97 | if (ret < 0) |
|---|
| 98 | Utils::OhShitShitShit("cannot bind socket"); |
|---|
| 99 | |
|---|
| 100 | ret = listen(m_fd, backlog); |
|---|
| 101 | if (ret < 0) |
|---|
| 102 | Utils::OhShitShitShit("cannot listen to socket"); |
|---|
| 103 | |
|---|
| 104 | m_queue = new Queue<BACKLOG>(); |
|---|
| 105 | m_workers = new Worker *[m_nworkers]; |
|---|
| 106 | for (int i = 0; i < m_nworkers; i++) |
|---|
| 107 | m_workers[i] = new Worker(m_queue); |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | ~Server() |
|---|
| 111 | { |
|---|
| 112 | close(m_fd); |
|---|
| 113 | for (int i = 0; i < m_nworkers; i++) |
|---|
| 114 | m_queue->Push(-1); |
|---|
| 115 | for (int i = 0; i < m_nworkers; i++) |
|---|
| 116 | delete m_workers[i]; |
|---|
| 117 | delete[] m_workers; |
|---|
| 118 | delete m_queue; |
|---|
| 119 | } |
|---|
| 120 | |
|---|
| 121 | void Run() |
|---|
| 122 | { |
|---|
| 123 | for (;;) |
|---|
| 124 | { |
|---|
| 125 | int client = GetNextRequest(); |
|---|
| 126 | if (client < 0) |
|---|
| 127 | continue; |
|---|
| 128 | m_queue->Push(client); |
|---|
| 129 | } |
|---|
| 130 | } |
|---|
| 131 | |
|---|
| 132 | int GetNextRequest() |
|---|
| 133 | { |
|---|
| 134 | struct sockaddr_in caddr; |
|---|
| 135 | struct in_addr client_ip_addr; |
|---|
| 136 | socklen_t addr_len; |
|---|
| 137 | addr_len = sizeof(caddr); |
|---|
| 138 | |
|---|
| 139 | int ret = accept(m_fd, (struct sockaddr *)&caddr, &addr_len); |
|---|
| 140 | if (ret < 0) |
|---|
| 141 | m_failures++; |
|---|
| 142 | else |
|---|
| 143 | m_requests++; |
|---|
| 144 | |
|---|
| 145 | return ret; |
|---|
| 146 | } |
|---|
| 147 | |
|---|
| 148 | std::string Status() |
|---|
| 149 | { |
|---|
| 150 | std::stringstream ret(""); |
|---|
| 151 | ret << "<p>Server status: " << m_requests << " requests served.</p>"; |
|---|
| 152 | ret << "<p>Uptime: " << (long)difftime(time(NULL), m_starttime) << " seconds.</p>"; |
|---|
| 153 | |
|---|
| 154 | return ret.str(); |
|---|
| 155 | } |
|---|
| 156 | |
|---|
| 157 | private: |
|---|
| 158 | int m_fd, m_port, m_nworkers; |
|---|
| 159 | uint64_t m_requests, m_failures; |
|---|
| 160 | time_t m_starttime; |
|---|
| 161 | Queue<BACKLOG> *m_queue; |
|---|
| 162 | Worker **m_workers; |
|---|
| 163 | }; |
|---|
| 164 | |
|---|
| 165 | int main(void) |
|---|
| 166 | { |
|---|
| 167 | g_server = new Server(PORT, BACKLOG, THREADS); |
|---|
| 168 | |
|---|
| 169 | Utils::Chroot(); |
|---|
| 170 | Utils::DropPrivileges(); |
|---|
| 171 | |
|---|
| 172 | Resources::LoadAll(); |
|---|
| 173 | |
|---|
| 174 | g_server->Run(); |
|---|
| 175 | |
|---|
| 176 | delete g_server; |
|---|
| 177 | |
|---|
| 178 | return 0; |
|---|
| 179 | } |
|---|
| 180 | |
|---|
| 181 | void Worker::HandleRequest(int client) |
|---|
| 182 | { |
|---|
| 183 | enum { REQUEST_GET, REQUEST_POST, REQUEST_HEAD, REQUEST_UNKNOWN }; |
|---|
| 184 | |
|---|
| 185 | char inbuf[BUFSIZ]; |
|---|
| 186 | int ret = recv(client, inbuf, BUFSIZ, 0); |
|---|
| 187 | if (ret < 0) |
|---|
| 188 | { |
|---|
| 189 | close(client); |
|---|
| 190 | return; |
|---|
| 191 | } |
|---|
| 192 | inbuf[ret] = '\0'; |
|---|
| 193 | |
|---|
| 194 | int request = !strncmp(inbuf, "GET ", 4) ? REQUEST_GET |
|---|
| 195 | : !strncmp(inbuf, "POST ", 5) ? REQUEST_POST |
|---|
| 196 | : !strncmp(inbuf, "HEAD ", 5) ? REQUEST_HEAD |
|---|
| 197 | : REQUEST_UNKNOWN; |
|---|
| 198 | if (request == REQUEST_UNKNOWN) |
|---|
| 199 | { |
|---|
| 200 | close(client); |
|---|
| 201 | return; |
|---|
| 202 | } |
|---|
| 203 | |
|---|
| 204 | /* Find information in the request */ |
|---|
| 205 | /* TODO: Perhaps make `query' an array. Example: |
|---|
| 206 | * `?u=dongs&popup=1' |
|---|
| 207 | * * query[0] = "u=dongs"; |
|---|
| 208 | * * query[1] = "popup=1"; |
|---|
| 209 | */ |
|---|
| 210 | char const *resource = "/", *query = "", |
|---|
| 211 | *agent = "Unknown", *host = "127.0.0.1"; |
|---|
| 212 | |
|---|
| 213 | char *resourceparser = strstr(inbuf, " "); |
|---|
| 214 | char *queryparser = NULL; |
|---|
| 215 | if (resourceparser) |
|---|
| 216 | { |
|---|
| 217 | resource = resourceparser + 1; |
|---|
| 218 | queryparser = strstr(resourceparser + 1, "?"); |
|---|
| 219 | resourceparser = strstr(resourceparser + 1, " "); |
|---|
| 220 | |
|---|
| 221 | if (queryparser && resourceparser && queryparser < resourceparser) |
|---|
| 222 | { |
|---|
| 223 | query = queryparser + 1; |
|---|
| 224 | } |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | char *agentparser = strstr(inbuf, "\r\nUser-Agent: "); |
|---|
| 228 | if (agentparser) |
|---|
| 229 | { |
|---|
| 230 | agent = agentparser + 14; |
|---|
| 231 | agentparser = strstr(agentparser + 14, "\r\n"); |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | char *hostparser = strstr(inbuf, "\r\nHost: "); |
|---|
| 235 | if (hostparser) |
|---|
| 236 | { |
|---|
| 237 | host = hostparser + 8; |
|---|
| 238 | hostparser = strstr(hostparser + 8, "\r\n"); |
|---|
| 239 | } |
|---|
| 240 | |
|---|
| 241 | /* Put zeroes in the request for convenience */ |
|---|
| 242 | if (resourceparser) |
|---|
| 243 | resourceparser[0] = '\0'; |
|---|
| 244 | if (queryparser) |
|---|
| 245 | queryparser[0] = '\0'; |
|---|
| 246 | if (agentparser) |
|---|
| 247 | agentparser[0] = '\0'; |
|---|
| 248 | if (hostparser) |
|---|
| 249 | hostparser[0] = '\0'; |
|---|
| 250 | |
|---|
| 251 | /* Now build the response */ |
|---|
| 252 | std::stringstream response(""); |
|---|
| 253 | Response data = Resources::Get(resource, query, host, agent); |
|---|
| 254 | response << "HTTP/1.1 200 OK\r\n" |
|---|
| 255 | << "Date: " << Utils::Time() << "\r\n" |
|---|
| 256 | << "Server: " << Random::ServerName() << "\r\n" |
|---|
| 257 | << "Content-Length: " << data.first.length() << "\r\n" |
|---|
| 258 | //<< "Connection: Keep-Alive\r\n" |
|---|
| 259 | << "Connection: Close\r\n" |
|---|
| 260 | << "Content-Type: " << data.second << "\r\n" |
|---|
| 261 | << "\r\n" |
|---|
| 262 | << data.first; |
|---|
| 263 | |
|---|
| 264 | send(client, response.str().c_str(), response.str().length(), 0); |
|---|
| 265 | |
|---|
| 266 | /* FIXME: we could reuse the connection here */ |
|---|
| 267 | close(client); |
|---|
| 268 | } |
|---|
| 269 | |
|---|