Created
April 11, 2023 00:08
-
-
Save emctague/a1dfe5ddfbd8ad1180e6c5cfe7119240 to your computer and use it in GitHub Desktop.
The Payload Server: A Fast Single-File HTTP Server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// THE PAYLOAD SERVER | |
// by Ethan McTague | |
// Apr 10, 2023 | |
// | |
// This is a single-file HTTP Server. It serves a single file, compressed, | |
// as quickly as it possibly can, to every single client that connects. | |
// The entire HTTP response is pre-buffered once at server startup. | |
// | |
// Free to use and modify, released as public-domain software. Enjoy! | |
// | |
// Compile-time Dependencies: ZLIB | |
#include <string.h> | |
#include <stdio.h> | |
#include <zlib.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
int main(int argc, char **argv) | |
{ | |
if (argc != 4) { | |
fprintf(stderr, "THE PAYLOAD SERVER\n"); | |
fprintf(stderr, "Usage: %s path/to/file user_facing_file_name port\n", argv[0]); | |
fprintf(stderr, "(e.g.: %s ../archives/my_big_download_final.zip big_download.zip 8080)\n\n", argv[0]); | |
fprintf(stderr, "THE PAYLOAD SERVER is an HTTP server that serves a single file.\n" | |
"It is optimized to respond to requests as quickly as possible,\n" | |
"totally ignoring the actual request text.\n" | |
"The response is pre-prepared in a buffer before requests even come in.\n"); | |
return 1; | |
} | |
const short port = (short)atoi(argv[3]);; | |
const char* fileName = argv[2]; | |
const char* filePath = argv[1]; | |
fprintf(stdout, "PAYLOAD SERVER will serve file \"%s\" as \"%s\" on port %d\n", filePath, fileName, port); | |
fprintf(stdout, "Reading file...\n"); | |
// Read payload file | |
size_t in_file_size; | |
unsigned char* file_buf; | |
{ | |
FILE *in_file = fopen(filePath, "rb"); | |
if (!in_file) { | |
perror("Failed to open payload file. Verify the file name is correct."); | |
return 1; | |
} | |
fseek(in_file, 0, SEEK_END); | |
in_file_size = ftell(in_file); | |
fseek(in_file, 0, SEEK_SET); | |
file_buf = malloc(in_file_size); | |
fread(file_buf, in_file_size, 1, in_file); | |
fclose(in_file); | |
} | |
fprintf(stdout, "Compressing file...\n"); | |
// Compress to secondary buffer | |
unsigned long compressed_buf_len = compressBound(in_file_size); | |
unsigned char* compressed_buf = malloc(compressed_buf_len); | |
compress(compressed_buf, &compressed_buf_len, file_buf, in_file_size); | |
free(file_buf); | |
fprintf(stdout, "Preparing constant response message...\n"); | |
// Format as HTTP response | |
unsigned char* response = NULL; | |
size_t response_len = 0; | |
{ | |
const char *fmt = "HTTP/1.1 200 OK\r\nConnection: Close\r\nContent-Length: %lu\r\nContent-Encoding: deflate\r\nContent-disposition: attachment; filename=%s\r\n\r\n"; | |
size_t str_len = snprintf(NULL, 0, fmt, compressed_buf_len, fileName); | |
response_len = str_len + compressed_buf_len; | |
response = malloc(response_len); | |
sprintf((char *) response, fmt, compressed_buf_len, fileName); | |
memcpy(response + str_len, compressed_buf, compressed_buf_len); | |
free(compressed_buf); | |
} | |
fprintf(stdout, "Preparing server socket...\n"); | |
// Set up socket | |
int sock = socket(AF_INET, SOCK_STREAM, 0); | |
if (sock < 0) { | |
perror("Unable to create socket"); | |
return 1; | |
} | |
int opt_value = 1; | |
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt_value, sizeof(opt_value)); | |
struct sockaddr_in serv_addr = {}; | |
serv_addr.sin_family = AF_INET; | |
serv_addr.sin_port = htons(port); | |
serv_addr.sin_addr.s_addr = INADDR_ANY; | |
if (bind(sock, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr)) < 0) { | |
perror("Unable to bind socket"); | |
return 1; | |
} | |
if (listen(sock, 64) < 0) { | |
perror("Failed to listen"); | |
return 1; | |
} | |
fprintf(stdout, "Ready to serve!\n"); | |
// Handle all requests for eternity | |
struct sockaddr_in remote_addr = {}; | |
socklen_t remote_addr_len = sizeof(remote_addr); | |
for (int client;;) { | |
client = accept(sock, (struct sockaddr*)&remote_addr, &remote_addr_len); | |
if (client < 0) { | |
perror("Failed to accept a client"); | |
continue; | |
} | |
send(client, response, response_len, 0); | |
close(client); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment