| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* router.c - Routing module for the web server (TLS version) | ||
| 2 | * | ||
| 3 | * This module examines the HTTP request (HttpRequest) and, based on the routes | ||
| 4 | * defined in the configuration (ServerConfig), decides whether to: | ||
| 5 | * - Serve a static file (using sendfile for zero-copy). | ||
| 6 | * - Forward the request to a backend (reverse proxy). | ||
| 7 | * | ||
| 8 | * In TLS mode, data sent to the client uses SSL_write() and data is read | ||
| 9 | * using SSL_read(). | ||
| 10 | */ | ||
| 11 | |||
| 12 | #include <stdio.h> | ||
| 13 | #include <stdlib.h> | ||
| 14 | #include <string.h> | ||
| 15 | #include <unistd.h> | ||
| 16 | #include <fcntl.h> | ||
| 17 | #include <sys/sendfile.h> | ||
| 18 | #include <arpa/inet.h> | ||
| 19 | #include <poll.h> | ||
| 20 | #include <errno.h> | ||
| 21 | #include "http2_response.h" | ||
| 22 | #include "router.h" | ||
| 23 | #include "server.h" | ||
| 24 | |||
| 25 | /* serve_static_tls() | ||
| 26 | * | ||
| 27 | * If the HTTP request's path starts with a static route, constructs the full file path, | ||
| 28 | * opens the file, and sends it to the client using SSL_write() and sendfile() for zero-copy. | ||
| 29 | * If the file is not found, a 404 response is sent. | ||
| 30 | */ | ||
| 31 | ✗ | int serve_static_tls(HttpRequest *req, ServerConfig *config, SSL *ssl) | |
| 32 | { | ||
| 33 | ✗ | if (!req || !req->path) | |
| 34 | ✗ | return -1; | |
| 35 | |||
| 36 | ✗ | for (int i = 0; i < config->route_count; i++) | |
| 37 | { | ||
| 38 | ✗ | if (strcmp(config->routes[i].technology, "static") == 0) | |
| 39 | { | ||
| 40 | ✗ | size_t prefix_len = strlen(config->routes[i].path); | |
| 41 | ✗ | if (strlen(req->path) < prefix_len) | |
| 42 | ✗ | continue; | |
| 43 | ✗ | if (strncmp(req->path, config->routes[i].path, prefix_len) == 0) | |
| 44 | { | ||
| 45 | char filepath[512]; | ||
| 46 | ✗ | const char *root = config->routes[i].document_root; | |
| 47 | ✗ | bool has_slash = (root[strlen(root) - 1] == '/'); | |
| 48 | ✗ | int written = snprintf(filepath, sizeof(filepath), | |
| 49 | has_slash ? "%s%s" : "%s/%s", | ||
| 50 | root, | ||
| 51 | ✗ | req->path + prefix_len); | |
| 52 | ✗ | if ((size_t)written >= sizeof(filepath)) | |
| 53 | { | ||
| 54 | ✗ | fprintf(stderr, "serve_static_tls: Path too long\n"); | |
| 55 | ✗ | return -1; | |
| 56 | } | ||
| 57 | |||
| 58 | ✗ | int fd = open(filepath, O_RDONLY); | |
| 59 | ✗ | if (fd < 0) | |
| 60 | { | ||
| 61 | ✗ | const char *not_found = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; | |
| 62 | ✗ | SSL_write(ssl, not_found, strlen(not_found)); | |
| 63 | ✗ | return 0; | |
| 64 | } | ||
| 65 | ✗ | off_t filesize = lseek(fd, 0, SEEK_END); | |
| 66 | ✗ | lseek(fd, 0, SEEK_SET); | |
| 67 | char header[256]; | ||
| 68 | ✗ | snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\nContent-Length: %ld\r\n\r\n", filesize); | |
| 69 | ✗ | SSL_write(ssl, header, strlen(header)); | |
| 70 | |||
| 71 | ✗ | printf("Sending static file: %s\n", filepath); | |
| 72 | char filebuf[BUFFER_SIZE]; | ||
| 73 | ssize_t bytes; | ||
| 74 | ✗ | while ((bytes = read(fd, filebuf, sizeof(filebuf))) > 0) | |
| 75 | { | ||
| 76 | ✗ | printf("Sending %zd bytes from file\n", bytes); | |
| 77 | ✗ | int sent = 0; | |
| 78 | ✗ | while (sent < bytes) { | |
| 79 | ✗ | int n = SSL_write(ssl, filebuf + sent, bytes - sent); | |
| 80 | ✗ | if (n <= 0) { | |
| 81 | ✗ | close(fd); | |
| 82 | ✗ | return -1; | |
| 83 | } | ||
| 84 | ✗ | sent += n; | |
| 85 | } | ||
| 86 | } | ||
| 87 | ✗ | close(fd); | |
| 88 | |||
| 89 | ✗ | return 0; | |
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | ✗ | return -1; | |
| 94 | } | ||
| 95 | |||
| 96 | /* proxy_bidirectional_tls() | ||
| 97 | * | ||
| 98 | * Implements a bidirectional forwarding loop between a TLS client and a backend server. | ||
| 99 | * Data from the client is read with SSL_read() and forwarded to the backend via write(), | ||
| 100 | * while data from the backend is read with read() and forwarded to the client using SSL_write(). | ||
| 101 | */ | ||
| 102 | ✗ | int proxy_bidirectional_tls(SSL *ssl, int backend_fd) | |
| 103 | { | ||
| 104 | ✗ | int done = 0; | |
| 105 | char buf[BUFFER_SIZE]; | ||
| 106 | ✗ | while (!done) | |
| 107 | { | ||
| 108 | /* Read from backend (plain) */ | ||
| 109 | ✗ | int n = read(backend_fd, buf, sizeof(buf)); | |
| 110 | ✗ | if (n > 0) | |
| 111 | { | ||
| 112 | ✗ | int sent = 0; | |
| 113 | ✗ | while (sent < n) | |
| 114 | { | ||
| 115 | ✗ | int s = SSL_write(ssl, buf + sent, n - sent); | |
| 116 | ✗ | if (s <= 0) | |
| 117 | { | ||
| 118 | ✗ | done = 1; | |
| 119 | ✗ | break; | |
| 120 | } | ||
| 121 | ✗ | sent += s; | |
| 122 | } | ||
| 123 | } | ||
| 124 | ✗ | else if (n < 0) | |
| 125 | { | ||
| 126 | ✗ | done = 1; | |
| 127 | } | ||
| 128 | /* Read from client (TLS) */ | ||
| 129 | ✗ | n = SSL_read(ssl, buf, sizeof(buf)); | |
| 130 | ✗ | if (n > 0) | |
| 131 | { | ||
| 132 | ✗ | int sent = 0; | |
| 133 | ✗ | while (sent < n) | |
| 134 | { | ||
| 135 | ✗ | int s = send(backend_fd, buf + sent, n - sent, 0); | |
| 136 | ✗ | if (s <= 0) | |
| 137 | { | ||
| 138 | ✗ | done = 1; | |
| 139 | ✗ | break; | |
| 140 | } | ||
| 141 | ✗ | sent += s; | |
| 142 | } | ||
| 143 | } | ||
| 144 | ✗ | else if (n < 0) | |
| 145 | { | ||
| 146 | ✗ | done = 1; | |
| 147 | } | ||
| 148 | } | ||
| 149 | ✗ | return 0; | |
| 150 | } | ||
| 151 | |||
| 152 | /* proxy_request_tls() | ||
| 153 | * | ||
| 154 | * Forwards the entire HTTP request to a backend for reverse proxy functionality. | ||
| 155 | * Then activates proxy_bidirectional_tls() to forward data bidirectionally. | ||
| 156 | */ | ||
| 157 | ✗ | int proxy_request_tls(HttpRequest *req, char *raw_request, int req_len, ServerConfig *config, SSL *ssl) | |
| 158 | { | ||
| 159 | ✗ | for (int i = 0; i < config->route_count; i++) | |
| 160 | { | ||
| 161 | ✗ | if (strcmp(config->routes[i].technology, "reverse_proxy") == 0) | |
| 162 | { | ||
| 163 | ✗ | size_t prefix_len = strlen(config->routes[i].path); | |
| 164 | ✗ | if (strlen(req->path) < prefix_len) | |
| 165 | ✗ | continue; | |
| 166 | ✗ | if (strncmp(req->path, config->routes[i].path, prefix_len) == 0) | |
| 167 | { | ||
| 168 | char ip[64]; | ||
| 169 | int port; | ||
| 170 | ✗ | if (sscanf(config->routes[i].backend, "%63[^:]:%d", ip, &port) != 2) | |
| 171 | ✗ | return -1; | |
| 172 | ✗ | int backend_fd = socket(AF_INET, SOCK_STREAM, 0); | |
| 173 | ✗ | if (backend_fd < 0) | |
| 174 | ✗ | return -1; | |
| 175 | struct sockaddr_in backend_addr; | ||
| 176 | ✗ | memset(&backend_addr, 0, sizeof(backend_addr)); | |
| 177 | ✗ | backend_addr.sin_family = AF_INET; | |
| 178 | ✗ | backend_addr.sin_port = htons(port); | |
| 179 | ✗ | if (inet_pton(AF_INET, ip, &backend_addr.sin_addr) <= 0) | |
| 180 | { | ||
| 181 | ✗ | close(backend_fd); | |
| 182 | ✗ | return -1; | |
| 183 | } | ||
| 184 | ✗ | if (connect(backend_fd, (struct sockaddr *)&backend_addr, sizeof(backend_addr)) < 0) | |
| 185 | { | ||
| 186 | ✗ | close(backend_fd); | |
| 187 | ✗ | return -1; | |
| 188 | } | ||
| 189 | ✗ | int sent = 0; | |
| 190 | ✗ | while (sent < req_len) | |
| 191 | { | ||
| 192 | ✗ | int n = send(backend_fd, raw_request + sent, req_len - sent, 0); | |
| 193 | ✗ | if (n <= 0) | |
| 194 | ✗ | break; | |
| 195 | ✗ | sent += n; | |
| 196 | } | ||
| 197 | ✗ | proxy_bidirectional_tls(ssl, backend_fd); | |
| 198 | ✗ | close(backend_fd); | |
| 199 | ✗ | return 0; | |
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | ✗ | return -1; | |
| 204 | } | ||
| 205 | |||
| 206 | /* route_request_tls() | ||
| 207 | * | ||
| 208 | * Decides how to handle the request: | ||
| 209 | * - If the request path is "/", serves a default HTML page. | ||
| 210 | * - Otherwise, it first tries to serve the request as a static file. | ||
| 211 | * - If that fails, it attempts to forward the request to the backend via reverse proxy. | ||
| 212 | * Note: All communication with the client is via the SSL pointer. | ||
| 213 | */ | ||
| 214 | ✗ | int route_request_tls(HttpRequest *req, const char *raw, size_t raw_len, ServerConfig *config, SSL *ssl, Http2Response *h2resp) | |
| 215 | { | ||
| 216 | ✗ | if (h2resp) | |
| 217 | { | ||
| 218 | ✗ | snprintf(h2resp->body, sizeof(h2resp->body), "<html><body>Hello, HTTP/2!</body></html>"); | |
| 219 | ✗ | h2resp->body_len = strlen(h2resp->body); | |
| 220 | ✗ | if (h2resp->body_len == 0) { | |
| 221 | ✗ | h2resp->body[0] = '\n'; // or ' ' | |
| 222 | ✗ | h2resp->body[1] = '\0'; | |
| 223 | ✗ | h2resp->body_len = 1; | |
| 224 | } | ||
| 225 | ✗ | h2resp->status_code = 200; | |
| 226 | ✗ | strcpy(h2resp->status_text, "OK"); | |
| 227 | ✗ | strcpy(h2resp->content_type, "text/html"); | |
| 228 | ✗ | h2resp->num_headers = 0; | |
| 229 | char status_str[4]; | ||
| 230 | ✗ | snprintf(status_str, sizeof(status_str), "%d", h2resp->status_code); | |
| 231 | ✗ | h2resp->headers[h2resp->num_headers++] = MAKE_NV(":status", status_str); | |
| 232 | ✗ | h2resp->headers[h2resp->num_headers++] = MAKE_NV("content-type", h2resp->content_type); | |
| 233 | char clen[32]; | ||
| 234 | ✗ | snprintf(clen, sizeof(clen), "%zu", h2resp->body_len); | |
| 235 | ✗ | h2resp->headers[h2resp->num_headers++] = MAKE_NV("content-length", clen); | |
| 236 | // Add more headers as needed | ||
| 237 | ✗ | return 0; | |
| 238 | } | ||
| 239 | |||
| 240 | ✗ | if (strcmp(req->path, "/") == 0) | |
| 241 | { | ||
| 242 | ✗ | const char *html_content = "<html><head><title>High Performance Web Server</title></head>" | |
| 243 | "<body><h1>Welcome to High Performance Web Server</h1>" | ||
| 244 | "<p>This server is designed to outperform Nginx and Apache by utilizing " | ||
| 245 | "advanced I/O techniques, a modular architecture, and an efficient reverse proxy mechanism.</p>" | ||
| 246 | "</body></html>"; | ||
| 247 | ✗ | size_t content_length = strlen(html_content); | |
| 248 | char header[256]; | ||
| 249 | ✗ | snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %zu\r\n\r\n", content_length); | |
| 250 | ✗ | SSL_write(ssl, header, strlen(header)); | |
| 251 | ✗ | SSL_write(ssl, html_content, content_length); | |
| 252 | ✗ | return 0; | |
| 253 | } | ||
| 254 | |||
| 255 | ✗ | if (serve_static_tls(req, config, ssl) == 0) | |
| 256 | ✗ | return 0; | |
| 257 | ✗ | if (proxy_request_tls(req, (char *)raw, raw_len, config, ssl) == 0) | |
| 258 | ✗ | return 0; | |
| 259 | |||
| 260 | ✗ | const char *not_found = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; | |
| 261 | ✗ | SSL_write(ssl, not_found, strlen(not_found)); | |
| 262 | ✗ | return -1; | |
| 263 | } | ||
| 264 |