First I wanted to say Hi, as it is my first post in my blog!
After countless hours spent with my friend Qiu. I decided that this is finally the time to start writing my own blog.
Of course, I could use WordPress or any other "blog engine", but then... where would the funniest part be?
Without further thinking I found my old small project of simple single threaded http server - assuming that I can ~200 lines of code call http server. It needed a little bit of tweaking. Let me briefly describe some part of it:
Implementation
code below was must have for me to keep my mental sanity, when using string operation in C (later I will delegate as much as possible of string operation to LUA):
//file: OwnedStr.h struct OwnedStr { char* str; size_t capacity; }; typedef struct OwnedStr OwnedStr; OwnedStr OwnedStr_Alloc(const char*); void OwnedStr_Concate(OwnedStr*,const char*); void OwnedStr_ConcateWithSize(OwnedStr* result, const char* in_str,size_t in_size); void OwnedStr_Free(OwnedStr*); OwnedStr OwnedStr_AllocFromFile(FILE *f); //file: OwnedStr.cpp #define JUST_IN_CASE 16 OwnedStr OwnedStr_Alloc(const char* in_c_str) { OwnedStr result; result.capacity = strlen(in_c_str); result.str = (char*)malloc(sizeof(char)*result.capacity+JUST_IN_CASE); strcpy(result.str,in_c_str); result.str[result.capacity] = '\0'; return result; } void OwnedStr_Concate(OwnedStr* result, const char* in_str) { result->capacity = result->capacity+strlen(in_str); char* old_str = result->str; result->str = (char*)malloc(sizeof(char)*result->capacity+JUST_IN_CASE); strcpy(result->str,old_str); strcat(result->str,in_str); result->str[result->capacity] = '\0'; free(old_str); } //...Below there is main loop for handling requests. It is far from perfect, probably it not handle half of error, but for now what is the most important: it works
//file: tcp_server.c typedef void (*TCPServer_OnRequest_t)(TCPServer_RequestState* requestState); #define BUFF_SIZE (1024*10) int TCPServer_run(int port,TCPServer_OnRequest_t onRequest, TCPServer_OnLogPrint_t OnLogPrint, void* forwardedState) { signal(SIGPIPE, SIG_IGN); //resolved wierd bug in my WSL const int socket_descriptor = socket(AF_INET, SOCK_STREAM, 0); if(socket_descriptor == -1) { OnLogPrint(forwardedState,"unable to open socket!",LogType_Error); return -1; } struct sockaddr_in host_addr; int addrlen = sizeof(host_addr); host_addr.sin_family= AF_INET; host_addr.sin_port= htons(port); host_addr.sin_addr.s_addr= htonl(INADDR_ANY); if(bind(socket_descriptor,(struct sockaddr*)&host_addr, addrlen) != 0) if(socket_descriptor == -1) { OnLogPrint(forwardedState,"unable to bind address!",LogType_Error); return -1; } if(listen(socket_descriptor,SOMAXCONN) != 0) { OnLogPrint(forwardedState,"unable to listen!",LogType_Error); return -1; } const int running = 1; char* buffer = (char*)malloc( BUFF_SIZE*sizeof(char)); OnLogPrint(forwardedState,"start listening!",LogType_Info); while(running) { TCPServer_RequestState req; req.forwardedState = forwardedState; req.onLogPrintCallback = OnLogPrint; req.connection = accept(socket_descriptor, (struct sockaddr*)&host_addr, (socklen_t *)&addrlen); struct sockaddr_in client_addr; int client_addrlen = sizeof(client_addr); int conn_name = getsockname(req.connection,(struct sockaddr*)&client_addr, (socklen_t*)&client_addrlen); if(conn_name < 0) { OnLogPrint(forwardedState,"unable to get connection name",LogType_Error); close(req.connection); continue; } if(req.connection < 0) { OnLogPrint(forwardedState,"unable to accept connection",LogType_Error); close(req.connection); continue; } const int read_status = read(req.connection,buffer,BUFF_SIZE); if(read_status < 0) { OnLogPrint(forwardedState,"unable to read from connection",LogType_Error); close(req.connection); continue; } req.request = buffer; onRequest(&req); close(req.connection); } free(buffer); close(socket_descriptor); return 0; }To keep up appearances of any project aggregations boundary: All TCP related functions are encapsulated in simple header style interface (I use this pattern a lot in this project)
//file: tcp_server.h typedef struct TCPServer_RequestState TCPServer_RequestState; const char* TCPServer_GetRequestString(TCPServer_RequestState*); void TCPServer_sendString(TCPServer_RequestState*,const char*); void TCPServer_sendStringWithLength(TCPServer_RequestState*,const char*,size_t); void TCPServer_sendFile(TCPServer_RequestState*,FILE*); void* TCPServer_GetForwardedState(TCPServer_RequestState*); typedef void (*TCPServer_OnRequest_t)(TCPServer_RequestState* requestState); typedef enum LogType_t { LogType_Error, LogType_Info } LogType_t; typedef void (* TCPServer_OnLogPrint_t)(void* forwardedState,const char*,LogType_t type); int TCPServer_run(int port,TCPServer_OnRequest_t onRequest,TCPServer_OnLogPrint_t OnLogPrint, void* forwardedState);For convince I wrote simple generic function to handle static file serving.
//file: staticFile.h void sendDefaultOkHeader(TCPServer_RequestState* s,const char* contentType); void sendDefault404Header(TCPServer_RequestState* s); void sendDefault500Header(TCPServer_RequestState* s); void serveStaticFile(TCPServer_RequestState* s, const char* filename); //file: staticFile.c typedef struct ExtFileInfo { enum {File_Binary, File_Text} type; const char* default_contentType; } ExtFileInfo; ExtFileInfo getExtFileInfo(const char* filename) { char *ext = strrchr(filename, '.'); ExtFileInfo def = {File_Text,"Content-Type: text/plain\n"}; if(!ext) return def; if (strcmp(ext, ".html") == 0) return (ExtFileInfo){File_Text,"Content-Type: text/html;charset=utf-8\n"}; if (strcmp(ext, ".css" ) == 0) return (ExtFileInfo){File_Text,"Content-Type: text/css;charset=utf-8\n"}; if (strcmp(ext, ".js" ) == 0) return (ExtFileInfo){File_Text,"Content-Type: application/javascript;charset=utf-8\n"}; if (strcmp(ext, ".json") == 0) return (ExtFileInfo){File_Text,"Content-Type: application/json;charset=utf-8\n"}; if (strcmp(ext, ".txt" ) == 0) return (ExtFileInfo){File_Text,"Content-Type: text/plain;charset=utf-8\n"}; if (strcmp(ext, ".jpg" ) == 0) return (ExtFileInfo){File_Binary,"Content-Type: image/jpg\n"}; if (strcmp(ext, ".png" ) == 0) return (ExtFileInfo){File_Binary,"Content-Type: image/png\n"}; if (strcmp(ext, ".gif" ) == 0) return (ExtFileInfo){File_Binary,"Content-Type: image/gif\n"}; if (strcmp(ext, ".ttf" ) == 0) return (ExtFileInfo){File_Binary,"Content-Type: font/ttf\n"}; return def; } void sendDefaultOkHeader(TCPServer_RequestState* s,const char* contentType) { TCPServer_sendString(s,"HTTP/1.1 200 OK\n"); TCPServer_sendString(s,"Server: qws\n"); TCPServer_sendString(s,contentType); TCPServer_sendString(s,"\n"); } void serveStaticFile(TCPServer_RequestState* s, const char* filename) { const ExtFileInfo extFileInfo = getExtFileInfo(filename); if(extFileInfo.type == File_Text) { FILE* file = fopen(filename, "r"); if (file) { sendDefaultOkHeader(s,extFileInfo.default_contentType); char buff[1024*10]; while (fgets(buff, 1024*10, file) != NULL) { TCPServer_sendString(s,buff); } fclose(file); printf("sent: %s \n",filename); return; } } if(extFileInfo.type == File_Binary) { FILE* file = fopen(filename, "rb"); if (file) { sendDefaultOkHeader(s,extFileInfo.default_contentType); TCPServer_sendFile(s,file); fclose(file); printf("sent: %s \n",filename); return; } } sendDefault404Header(s); }At that point this server was capable to server static hosted file, but I couldn't stop there:
I wanted to support at least two feature that requires some form of scripting:
- handling user friendly URL
- website templates, to inject one html file into another
- thinking of future: some way to represents data structures like post
Also, I wanted to write main content in something else than HTML.
Considering all of this: I decided to use LUA for scripting and Markdown (using Md4c) for content.
Please do not look at 2 dirty hack that I still don't believe that I wrote them in 2025
- First of them is using goto to clean up heap allocated memory
- Second is to cast ptr to (long long) in order to pass and receive it from LuaState in API functions. I wanted to avoid using static (global) memory
As you can see I used LUA to generate both header and content.
Is it bad? - yes
Is it work (for now)? - yes
I exposed only: "GetFileContent", "MdToHTML", "ServeStaticFile", "Print", "sendDefaultHtmlOkHeader", "sendDefault404Header" to LUA
Surprisingly, for website that you can see right now it is enough. and event not all of them are necessary. at least for now.
And finally bellow is "entry.lua'
require('common') function HandleRequest(request) local request_exploder = string.gmatch(request, "%S+") local method = request_exploder() local location = request_exploder() --Routes if location == "/quick-notes" then PrintDefaultHtmlOkHeader() Print(GetFileContent("template/main.html") % { content = GetFileContent("template/quickNotes.html")}) --Notes elseif location == "/quick-note/writing-http-server-and-blog-in-c-and-lua" then PrintDefaultHtmlOkHeader() Print(GetFileContent("template/main.html") % { content = MdToHTML(GetFileContent("quickNotes/writingHttpServerAndBlogInCAndLua.md"))}) --Static elseif location == "/public/style.css" then ServeStaticFile("public/style.css"); --Homepage else PrintDefaultHtmlOkHeader() Print(GetFileContent("template/main.html") % { content = GetFileContent("template/quickNotes.html")}) end end --file: common.lua function interp(s, tab) return (s:gsub('($%b{})', function(w) return tab[w:sub(3, -2)] or w end)) end getmetatable("").__mod = interpand simplified "template/main.html"
<!doctype html> <html lang="en"> <head> <title>Qikcik Blog</title> </head> <body> <h1>Qikcik</h1> ${content} </body> </html>Hosting issues
I wanted to host it on CT8 server, but there is a catch - it is running on freebsd. First issue was missing "struct sockaddr_in" header. it was simply fixable by including <netinet/in.h>
source/tcp_server.c:54:24: error: variable has incomplete type 'struct sockaddr_in'Second was trickier: SendFile is slightly different implemented in FreeBsd then in Linux. After something, I decided that this is too small project to care about host os. ...so I rewrote TCPServer_sendFile to use additional buffer and only read/write. (I keep telling to myself that at least I do not use garbage collector)
void TCPServer_sendFile(TCPServer_RequestState* s ,FILE* f) { OwnedStr buffor = OwnedStr_AllocFromFile(f); int write_status = write(s->connection,buffor.str,buffor.capacity); if(write_status < 0) s->onLogPrintCallback(s->forwardedState,"unable to write to connection",LogType_Error); OwnedStr_Free(&buffor); }End thought
Is all of this in any aspect commercial ready product? - absolutely not
Is it safe, or Is it blazingly fast? - absolutely not
Was it great journey? - absolutely yes
Is it enough to serve simple blog? - I hope so
PS: There you can find all the source code
.png)

