- cross-posted to:
- frontend
- cross-posted to:
- frontend
I don’t even want to think about how much time and electricity is getting wasted each day because of all these massively bloated websites and webapps. Modern computers are insanely fast, yet webapps sometimes take more than 10 seconds to start up on a good day.
Personally I like my WebApps in C89
/* app.c */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <sys/socket.h> #ifdef __linux__ #include <linux/net.h> /* For SO_REUSEPORT */ #endif #define LISTEN_PORT 9000 #define REQUEST_TYPE_UNKNOWN 0 #define REQUEST_TYPE_GET 1 static const char *html_main = ( "<!DOCTYPE html>" "<html>" "<header>" "<title>My first Web App</title>" "</header>" "<body>" "<h1>Hello World</h1>" "</body>" "</html>" ); static size_t html_main_length; static void http_reply(int cl_sock_fd, const uint16_t status, const char *status_msg, const char *mimetype, const size_t content_length) { char buffer[0x400]; int length; length = sprintf( buffer, "HTTP/1.1 %u %s\r\n" "Content-Type: %s\r\n" "Content-Length: %lu\r\n" "\r\n", status, status_msg, mimetype, content_length ); write(cl_sock_fd, buffer, length); } int main(void) { int32_t sv_sock_fd, cl_sock_fd; struct sockaddr_in sv_addr, cl_addr; socklen_t cl_addr_size; uint32_t option; char *input_buffer; size_t input_length, input_buffer_length; uint8_t request_type; memset(&sv_addr, 0, sizeof(sv_addr)); sv_addr.sin_family = AF_INET; sv_addr.sin_addr.s_addr = INADDR_ANY; sv_addr.sin_port = htons(LISTEN_PORT); sv_sock_fd = socket(sv_addr.sin_family, SOCK_STREAM, 0); if (sv_sock_fd == -1) { perror("Unable to create server socket"); return EXIT_FAILURE; } /* Tell it to re-use the address and port... */ option = 1; if (setsockopt(sv_sock_fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option))) { close(sv_sock_fd); perror("Unable to setsockopt on socket"); return EXIT_FAILURE; } if (setsockopt(sv_sock_fd, SOL_SOCKET, SO_REUSEPORT, &option, sizeof(option))) { close(sv_sock_fd); perror("Unable to setsockopt on socket"); return EXIT_FAILURE; } if (bind(sv_sock_fd, (struct sockaddr *)&sv_addr, sizeof(sv_addr))) { close(sv_sock_fd); perror("Unable to bind socket to address"); return EXIT_FAILURE; } if (listen(sv_sock_fd, 0)) { close(sv_sock_fd); perror("Unable to listen on socket"); return EXIT_FAILURE; } input_buffer_length = 0x400; input_buffer = malloc(input_buffer_length); if (!input_buffer) { close(sv_sock_fd); perror("input_buffer == NULL"); return EXIT_FAILURE; } html_main_length = strlen(html_main); cl_addr_size = sizeof(cl_addr); printf("Waiting for new connections on port %u ...\n", LISTEN_PORT); while (1) { cl_sock_fd = accept(sv_sock_fd, (struct sockaddr *)&cl_addr, &cl_addr_size); if (cl_sock_fd == -1) { perror("Error when accept()ing"); break; } request_type = REQUEST_TYPE_UNKNOWN; input_length = 0; /* Read from the client */ while (read(cl_sock_fd, input_buffer + input_length, 1) > 0) { if (input_length >= input_buffer_length) { input_buffer_length += 0x400; input_buffer = realloc(input_buffer, input_buffer_length); if (!input_buffer) { close(sv_sock_fd); close(cl_sock_fd); perror("Failed to realloc() input_buffer"); return EXIT_FAILURE; } } if (input_buffer[input_length] == ' ') { if (request_type == REQUEST_TYPE_GET) { printf("GET request: %.*s\n", (int)input_length, input_buffer); if (strncmp(input_buffer, "/", input_length) == 0) { http_reply(cl_sock_fd, 200, "OK", "text/html; charset=UTF-8", html_main_length); write(cl_sock_fd, html_main, html_main_length); break; } http_reply(cl_sock_fd, 404, "Not Found", "", 0); break; } if (strncmp(input_buffer, "GET", input_length) == 0) { request_type = REQUEST_TYPE_GET; input_length = 0; continue; } break; } ++input_length; } close(cl_sock_fd); } free(input_buffer); close(sv_sock_fd); return EXIT_SUCCESS; }
To compile on a UNIX-like system:
$ cc -o app app.c -std=c89 -Wall -Wextra
Interesting article and approach. While I absolutely agree it’s too easy to bloat with unneeded complexity on the off chance you might want it… I’m suspect of how his app was built. These frameworks and tools wouldn’t be widely used if they were shit.
But in general, good lesson in “less is more”.
Having worked with a lot of these tools and frameworks extensively I really do think they are shit for most use cases. I also find that software industry love doing cargo culting where people just use whatever tools big companies like Google or Meta put out without actually considering whether these tools are useful in their specific circumstance. The big companies also encourage this because they effectively get to outsource training on their internal tools to other companies, and then poach top talent that’s already familiar with their stack. The way he structured the app is perfectly reasonable. Treating the UI simply as a presentation layer removes a ton of complexity from the architecture.