{"id":17047504,"url":"https://github.com/djmgit/cupcake","last_synced_at":"2025-04-12T15:51:59.834Z","repository":{"id":37010466,"uuid":"502362402","full_name":"djmgit/cupcake","owner":"djmgit","description":"A tiny, simple webserver in x86 (32 bit) assembly language from scratch for fun","archived":false,"fork":false,"pushed_at":"2022-06-19T13:14:07.000Z","size":101,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-26T10:21:18.870Z","etag":null,"topics":["assembly-x86","educational","linux","nasm","recreational","syscalls","systems"],"latest_commit_sha":null,"homepage":"","language":"Assembly","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/djmgit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-06-11T13:54:51.000Z","updated_at":"2023-12-10T08:53:44.000Z","dependencies_parsed_at":"2022-08-18T00:11:31.858Z","dependency_job_id":null,"html_url":"https://github.com/djmgit/cupcake","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djmgit%2Fcupcake","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djmgit%2Fcupcake/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djmgit%2Fcupcake/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djmgit%2Fcupcake/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/djmgit","download_url":"https://codeload.github.com/djmgit/cupcake/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248591996,"owners_count":21130162,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["assembly-x86","educational","linux","nasm","recreational","syscalls","systems"],"created_at":"2024-10-14T09:49:37.290Z","updated_at":"2025-04-12T15:51:59.815Z","avatar_url":"https://github.com/djmgit.png","language":"Assembly","readme":"# Cupcake\n\nCupcake is a tiny, simple webserver with very limited features written completely in x86 (32 bit) assembly from scratch and uses the Netwide Assembler\nalso known as NASM for assembling. As of the time of writing this doc, Cupcake only supports serving static files from a given docroot and is \ncapable of showing a 404 response page if asked for an unknown resource. Cupcake requires linux based operating system to run since it uses the standard \nlinux system calls for interacting with the kernel.\nCupcake is created with the pure intention of having fun with x86 assembly language and learning and exploring the same with NASM.\nThe other intension was to practically learn more about how system calls in linux kernel gets invoked at the low level and get more\nfamiliarity with the linux syscall ABI.\n\n## Building and running Cupcake\n\n### Linux based OS\n\nCupcake can be built and run in a pretty straight forward way on machines running a linux based OS. As a prerequisite make sure you have NASM and\nGNU binutils package installed. We require binutils as it provides the linker ld which we will use to link the outout of our assembler in order\nto generate an elf file.\nNote: Cupcake has been developed using NASM version : 2.13.02 and GNU binutils version : 2.30\n\n- First clone this repository to your local.\n- Open up the repository in your terminal.\n- Build Cupcake using : ```make```\n- This will produce the built and linked elf under the dist directory.\n\nCupcake requires you to provide a directory to serve as a command line arg, the only arg it takes mandatorily. So basically this directory becomes your\ndocroot.\nSo make a new directoy called docroot : ```mkdir docroot```\nSave the following HTML file as ```index.html``` under docroot or create any HTML content of your choice.\n\n```\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ch1\u003eHello from Cupcake\u003c/h1\u003e\n  \u003c/head\u003e\n\u003c/html\u003e\n```\n\nNow lets fire up Cupcake using the following command:\n\n```\n./dist/cupcake \u003cpath_to_the_created_docroot\u003e\n```\nI usually prefer providing absolute path when working with paths.\n\nCupcake should greet you with the following series of messages in your terminal:\n```\nStarting cupcake on port 9001 ...\nCreating socket ...\nBinding socket to 0.0.0.0:9001 ...\nAttempting to listen ...\nCupcake is listenning for new connections ...\n```\n\nThis means Cupcake has successfully started and is now waiting for new client connections on port 9001 (yeah the port is hardcoded).\n\nNow you can send a request to cupcake. I will use curl to send an HTTP request:\n\n```\ncurl -v http://127.0.0.1:9001/index.html\n```\n\nThe content of the index.html you just created should get printed as output on your terminal. You can also send the request using a web browser like\nfirefox or chrome.\n\n**What does the make command do?***\n\nIf you check the Makefile, then what you will see is that make simply runs the first recipe which is ```build```, which runs the build script.\nThe build script essentially first assembles our code using nasm assembler with elf32 as the target format.\n\n```\nnasm -felf main.asm\n```\n\nProviding only main.asm is enough since that is entry point of our code and other files are included as required using ```#include``` calls.\nThis provides a ```main.o``` assembled object file which is not executable.\nNext we invoke the linker to link this file and procvide us with a executable file.\n\n``` ld -m elf_i386 main.o -o cupcake ```\n\nThis provides a executable named ```cupcake``` targetted for the x86 (32 bit) arch processor.\n\n### Running via Docker\n\nThere is also a Dockerfile provided to build and run Cupcake in Docker.\nTo build the image, simply run:\n\n```\nmake dockerbuild\n```\n\nwhich simply runs:\n\n``` \ndocker build -t cupcake .\n```\n\nSo the image created has the name/tag cupcake. No versioning, however you can always fire this command directly to add versioning to your local images.\n\nTo run, you can use the following docker run command:\n\n```\ndocker run -p 9001:9001 --rm -v \u003cabsolute_path_to_docroot_on_your_host\u003e/:/docroot --name cupcake cupcake\n```\n\nIn this command, we forward 9001 port on the host machine to 9001 port of docker since thats where cupcake will be listenning. Also we mount the folder\nthat we want to serve on docker at ```/docroot``` mount point. If you see the Dockerfile we start the server with\n\n```\nCMD [\"dumb-init\", \"/dist/cupcake\", \"/docroot\"]\n```\nhence the mount point is ```/docroot```\n\n### Running via docker in debug mode (Usefull if developing on non-linux machine like MacOS)\n\nIf you dont have a linux machine but still want to play around may be on MacOS, then you can do so in debug mode which is nothing but\na ubuntu docker container running in interactive mode with the entire source code mounted to it. The container already has got the essential\ntools like NASM, ld etc. Yeah that simple and crude.\n\nStart the debug container:\n\n```\nmake rundebug\n```\n\nthis basically runs the following under the hood\n```\ndocker run -it -p 9001:9001 -v `pwd`/cupcake:/src -v `pwd`/build.sh:/src/build.sh -e mode=debug --rm --name cupcake-debug cupcake-debug:latest\n```\nOnce again we forward port 9001. Next we mount the source code directory which is ```cupcake``` under the project root\nto ```/src``` on docker as mountpoint.\nAlso we mount the build script ```build.sh``` at ```/src/build.sh``` the practical implication of which is we get the build script available in the\nsource code directory itself. The debug image has got ```/src``` set as the ```WORKDIR``` so as soon as we run the above command we find ourselves\nin the source code directory. \nNow all you have to do is make changes to the code files, then assemble and link using:\n\n```\n./build.sh\n```\n\nand then run using:\n\n```\n./cupcake \u003cpath_to_docroot\u003e\n```\n\n## How does Cupcake work\n\nI will try to not go too much into this since I have tried to provide as much inline documention as possible. Most of the interesting areas of\nthe code have enough comments (hopefully). Still, lets try to understand what Cupcake does and how does it do what it does.\n\nJust like any other webserver, Cupcake makes using of network sockets at its heart. In short, it sets up a listenning socket, listens for incoming\nconnections, accepts connections, reads data, generates response and writes back the response into the accepted client socket. It follows the\ntraditional fork model of web servers where every new connecting is handled in a separate process so that other connections are not waiting.\n\nThe entry point to the server is ```main.asm```. When it boots up the following things happen:\n\n- The cmdline arguments are parsed so that cupcake knows the docroot location.\n- A new linux network socket is created.\n- The created socket is ```bind```ed to an IP and PORT. Port is hardcoded to be 9001\n- The socket is then put to listenning mode.\n- The socket then starts to listen for new connections. This is the blocking step where our server loop is blocked and waiting for a new connection.\n- Whenever the accept call gets a new connection, we get a new client socket fd. When that happens we ```fork``` a new process.\n- This new process reads data from the client socket fd. Unlike a real world production grade webserver, we are not interested in the entire request data   since we only allow ```GET``` requests that too for static files from a given location. So we read a chunk of byte (hardcoded size).\n- From the chuck of data we read we try to extract required information which is the HTTP path requested. Next we prepend the docroot path infront of       this HTTP path read. For example if the path was ```index.html``` and the docroot is ```/var/docroot```, the final resurce path to read from becomes  ```/var/docroot/index.html```.\n- Next we try to read the resource (basically file) pointed to by the resource path. If we are not able to read that, may be because file does not         exists (or any other issue), we simple send back the 404 resource not found page.\n- If the file exists then we open it and read it byte by byte. Cupcake expects a file with a fixed given limit (hardcoded once agan).\n- Once the file is read, we generate the http response with the desired ```Content-Length``` header which is basically the size of the file read in         bytes.\n  The usual format of an HTTP response is as follows (example):\n  \n  ```\n  HTTP/1.1 200 OK\n  Content-Type: text/html\n  Content-Length: \u003cthe_length_of_content_in_bytes\u003e\n  \u003cother_such_headers_if_any\u003e\n  \u003cexactly_one_blank_line\u003e\n  the resource content returned by the server like\n  Hellow world from Cupcake, etc ...\n  ```\n  \n  The file content we read goes right after the blank line. Also you would not want to miss that blank line after the headers.\n  Without that innocent looking blank line the entire response becomes invalid and no http client will be able to render/show the response.\n- Finally we write the generated HTTP response back to the client socket fd and then close the socket.\n\n## Things I would like to add/improve\n\nRight now the project is pretty crude. As I mentioned already, ths is not for real world use, its not even close to the full feature suite provided\nby a real world webserver but that was never the intention anyways. Having said that, there is still quite a few ineteresting things that I would like\nto add/fix. Hope I will do that if I dont become distracted with something else...\n\n- Several things are hardcoded right now, for example how long a HTTP path can be or how long the content read should be and lots more. Those things       should be dynamic.\n- Several parts of the code are rigid and too specific, those should be made more generic and moved to its own file or a macro.\n- Implement a redirect (302) with a default page at ```/```. I should have done this, but I guess I am just being lazy.\n- Error messages.\n- The most interesting one I guess, reap the child processes created. Currently Cupcake will create several defunct process when running natively on the   host. This is because the forked processes are not being reaped by the parent.\n\nAlso I would like to mention ```gcc -S``` was not used while developing this, that would have killed the fun.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjmgit%2Fcupcake","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdjmgit%2Fcupcake","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjmgit%2Fcupcake/lists"}