Last active
November 1, 2025 15:52
-
-
Save Pikachuxxxx/b286fe57a23bd8b62a95581d097f17a0 to your computer and use it in GitHub Desktop.
love this song and wanted to write a small web server in C so yeah
This file contains hidden or 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
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <time.h> | |
| #include <arpa/inet.h> | |
| #define PORT 8080 | |
| #define MAX_MESSAGES 128 | |
| #define MAX_MSG_LEN 2048 | |
| #define MESSAGE_PREFIX "simon says" | |
| //------------------------------------------------------------ | |
| // Common HTTP Response Headers | |
| //------------------------------------------------------------ | |
| /* | |
| * The very first line (the "status line"): | |
| * Example: "HTTP/1.1 200 OK\r\n" | |
| * - "HTTP/1.1" --> protocol version | |
| * - "200" --> status code (e.g., 404 = Not Found, 500 = Internal Error) | |
| * - "OK" --> status message | |
| */ | |
| #define HTTP_RESPONSE_HEADER "HTTP/1.1 200 OK\r\n" | |
| //------------------------------------------------------------ | |
| // Content-Type | |
| //------------------------------------------------------------ | |
| /* | |
| * Specifies the MIME type of the response body. | |
| * Browsers use this to decide how to interpret the content. | |
| * | |
| * Common examples: | |
| * - text/html --> HTML document (rendered in browser) | |
| * - text/plain --> Raw plain text (shown as-is) | |
| * - application/json --> JSON data (parsed by fetch().json()) | |
| * - image/png, image/jpeg --> Image formats | |
| * - audio/mpeg, video/mp4 --> Media files | |
| * - application/javascript --> JS script | |
| */ | |
| #define HTTP_CONTENT_TYPE_HTML "Content-Type: text/html\r\n" | |
| #define HTTP_CONTENT_TYPE_TEXT "Content-Type: text/plain\r\n" | |
| #define HTTP_CONTENT_TYPE_JSON "Content-Type: application/json\r\n" | |
| #define HTTP_CONTENT_TYPE_JS "Content-Type: application/javascript\r\n" | |
| //------------------------------------------------------------ | |
| // Content-Length | |
| //------------------------------------------------------------ | |
| /* | |
| * Content-Length: <number> | |
| * Informs the client how many bytes to read for the body. | |
| * Required for persistent (keep-alive) connections. | |
| * If omitted and 'Connection: close' is used, the browser reads | |
| * until the server closes the TCP socket. | |
| * | |
| * Example: | |
| * "Content-Length: 1024\r\n" | |
| */ | |
| //------------------------------------------------------------ | |
| // Connection | |
| //------------------------------------------------------------ | |
| /* | |
| * Controls whether the TCP connection stays open after this response. | |
| * | |
| * Connection: close | |
| * --> Server closes socket after sending response. | |
| * Browser must open a new TCP connection for every request. | |
| * | |
| * Connection: keep-alive | |
| * --> Server keeps connection open for reuse. | |
| * Browser can send multiple requests on same connection. | |
| * Must include valid Content-Length for this to work. | |
| */ | |
| #define HTTP_CONNECTION_CLOSE "Connection: close\r\n\r\n" | |
| #define HTTP_CONNECTION_KEEP_ALIVE "Connection: keep-alive\r\n\r\n" | |
| //------------------------------------------------------------ | |
| // Cache-Control | |
| //------------------------------------------------------------ | |
| /* | |
| * Used to control browser/proxy caching behavior. | |
| * | |
| * Cache-Control: no-cache --> Always revalidate | |
| * Cache-Control: no-store --> Never store on disk | |
| * Cache-Control: max-age=3600 --> Cache for 1 hour | |
| * Cache-Control: public/private --> Who can cache | |
| */ | |
| //------------------------------------------------------------ | |
| // Access-Control-* (CORS) | |
| //------------------------------------------------------------ | |
| /* | |
| * Needed when making fetch() requests across origins. | |
| * | |
| * Access-Control-Allow-Origin: * --> Allow all | |
| * Access-Control-Allow-Methods: GET,POST --> Allowed HTTP verbs | |
| * Access-Control-Allow-Headers: Content-Type | |
| */ | |
| //------------------------------------------------------------ | |
| // Server and Date | |
| //------------------------------------------------------------ | |
| /* | |
| * Informational headers; optional but common. | |
| * | |
| * Server: tiny-c-server/1.0 | |
| * Date: Sat, 01 Nov 2025 15:00:00 GMT | |
| */ | |
| //------------------------------------------------------------ | |
| // HTTPS notes (vs HTTP) | |
| //------------------------------------------------------------ | |
| /* | |
| * HTTPS runs the same HTTP protocol but over TLS/SSL. | |
| * The headers are identical — only the transport layer changes. | |
| * | |
| * HTTP --> plain text over TCP port 80 | |
| * HTTPS --> encrypted stream over TLS (usually port 443) | |
| * | |
| * You don’t handle TLS in a raw socket server; you use: | |
| * - OpenSSL (C library) to wrap your socket with SSL_accept/SSL_read/SSL_write | |
| * - Or let nginx/apache handle HTTPS and forward HTTP to your app | |
| */ | |
| int main(int argc, char** argv) | |
| { | |
| int server_fd = -1; | |
| int client_fd = -1; | |
| struct sockaddr_in addr; | |
| memset(&addr, 0x0, sizeof(struct sockaddr_in)); | |
| const socklen_t addr_len = sizeof(struct sockaddr_in); | |
| char buf[MAX_MSG_LEN]; | |
| server_fd = socket(AF_INET, SOCK_STREAM, 0); // create a TCP socket | |
| if (server_fd < 0) { | |
| printf("[ERROR] failed to create server socket!"); | |
| return -1; | |
| } | |
| int opt = 1; | |
| // make sure we re-use the address | |
| setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| // bind the address to the server | |
| addr.sin_family = AF_INET; | |
| addr.sin_addr.s_addr = INADDR_ANY; | |
| addr.sin_port = htons(PORT); | |
| if(bind(server_fd, (const struct sockaddr*) &addr, sizeof(addr)) < 0) { | |
| printf("[ERROR] failed to bind address to socket!"); | |
| close(server_fd); | |
| return -1; | |
| } | |
| // listen for connections | |
| if(listen(server_fd, 10) < 0) { | |
| printf("[ERROR] failed to bind address to socket!"); | |
| close(server_fd); | |
| return -1; | |
| } | |
| printf("Server running at http://localhost:%d\n", PORT); | |
| while(1) { | |
| client_fd = accept(server_fd, (struct sockaddr*)&addr, (socklen_t*)&addr_len); | |
| if(client_fd < 0) { | |
| printf("[ERROR] failed to accept client!\n"); | |
| continue; | |
| } | |
| int bytes = read(client_fd, buf, MAX_MSG_LEN - 1); | |
| if (bytes <= 0) { close(client_fd); continue; } | |
| buf[bytes] = '\0'; | |
| printf("Request:\n%s\n", buf); | |
| // we first send a response and run some JS on client side to ask them to send a request and then we send more data to the client | |
| if(strncmp(buf, "GET /data", 9) == 0) { | |
| const char* json = | |
| HTTP_RESPONSE_HEADER | |
| HTTP_CONTENT_TYPE_TEXT | |
| HTTP_CONNECTION_CLOSE | |
| "\nI'm a Barbie girl in a Barbie world" | |
| "\nLife in plastic, it's fantastic" | |
| "\nYou can brush my hair, undress me everywhere" | |
| "\nImagination, life is your creation" | |
| "\nCome on Barbie, let's go party" | |
| "\nAh-ah-ah, yeah" | |
| "\nCome on Barbie, let's go party" | |
| "\nOoh-whoa-ooh, ooh-whoa-ooh" | |
| "\nCome on Barbie, let's go party" | |
| "\nAh-ah-ah, yeah" | |
| "\nCome on Barbie, let's go party" | |
| "\nOoh-whoa-ooh, ooh-whoa-ooh" | |
| "\nMake me walk, make me talk, do whatever you please" | |
| "\nI can act like a star, I can beg on my knees" | |
| "\nCome jump in, bimbo friend, let us do it again" | |
| "\nHit the town, fool around, let's go party" | |
| "\nYou can touch, you can play" | |
| "\nIf you say, I'm always yours" | |
| "\nYou can touch, you can play" | |
| "\nIf you say, I'm always yours" | |
| "\nCome on Barbie, let's go party" | |
| "\nAh-ah-ah, yeah" | |
| "\nCome on Barbie, let's go party" | |
| "\nOoh-whoa-ooh, ooh-whoa-ooh" | |
| "\nCome on Barbie, let's go party" | |
| "\nAh-ah-ah, yeah" | |
| "\nCome on Barbie, let's go party" | |
| "\nOoh-whoa-ooh, ooh-whoa-ooh" | |
| "\nI'm a Barbie girl in a Barbie world" | |
| "\nLife in plastic, it's fantastic" | |
| "\nYou can brush my hair, undress me everywhere" | |
| "\nImagination, life is your creation" | |
| "\nI'm a Barbie girl in a Barbie world" | |
| "\nLife in plastic, it's fantastic" | |
| "\nYou can brush my hair, undress me everywhere" | |
| "\nImagination, life is your creation" | |
| "\nCome on Barbie, let's go party" | |
| "\nAh-ah-ah, yeah" | |
| "\nCome on Barbie, let's go party" | |
| "\nOoh-whoa-ooh, ooh-whoa-ooh" | |
| "\nCome on Barbie, let's go party" | |
| "\nAh-ah-ah, yeah" | |
| "\nCome on Barbie, let's go party" | |
| "\nOoh-whoa-ooh, ooh-whoa-ooh" | |
| "\nOh, I'm having so much fun!" | |
| "\nWell Barbie, we're just getting started" | |
| "\nOh, I love you, Ken!"; | |
| write(client_fd, json, strlen(json)); | |
| } else { | |
| printf("sending HTML data \n"); | |
| // default response sent from server to client after successful connection | |
| const char* html = | |
| HTTP_RESPONSE_HEADER | |
| HTTP_CONTENT_TYPE_HTML | |
| HTTP_CONNECTION_CLOSE | |
| "<html><body><h1>Welcome</h1>" | |
| "<h2> Barbie Girl!<h2>" | |
| "<script>" | |
| "fetch('/data').then(r => r.text()).then(t => {" | |
| "document.body.innerHTML += `<pre>${t}</pre>`;" | |
| "});" | |
| "</script></body></html>"; | |
| write(client_fd, html, strlen(html)); | |
| } | |
| close(client_fd); // handle each client as they come, no persistent state, just for testing | |
| } | |
| close(server_fd); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment