Skip to content

Instantly share code, notes, and snippets.

@emctague
Created April 11, 2023 00:08
Show Gist options
  • Save emctague/a1dfe5ddfbd8ad1180e6c5cfe7119240 to your computer and use it in GitHub Desktop.
Save emctague/a1dfe5ddfbd8ad1180e6c5cfe7119240 to your computer and use it in GitHub Desktop.
The Payload Server: A Fast Single-File HTTP Server
// 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