{"id":18907204,"url":"https://github.com/zzampax/simple-http","last_synced_at":"2025-06-26T08:35:42.988Z","repository":{"id":241185979,"uuid":"801163446","full_name":"zzampax/simple-http","owner":"zzampax","description":"This is a completly custom made HTTP server, it doesn't use any external library to handle the HTTP requests, it's all made from scratch. It's just a simple project to learn how to handle TCP in RUST.","archived":false,"fork":false,"pushed_at":"2024-05-26T12:39:15.000Z","size":4096,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-01T00:50:19.484Z","etag":null,"topics":["blog","computer-science","first-project","http","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zzampax.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-05-15T18:02:47.000Z","updated_at":"2025-01-13T15:17:48.000Z","dependencies_parsed_at":"2025-05-24T13:57:29.740Z","dependency_job_id":null,"html_url":"https://github.com/zzampax/simple-http","commit_stats":null,"previous_names":["zzampax/simple-http"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/zzampax/simple-http","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zzampax%2Fsimple-http","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zzampax%2Fsimple-http/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zzampax%2Fsimple-http/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zzampax%2Fsimple-http/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zzampax","download_url":"https://codeload.github.com/zzampax/simple-http/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zzampax%2Fsimple-http/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262030734,"owners_count":23247707,"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":["blog","computer-science","first-project","http","rust"],"created_at":"2024-11-08T09:20:04.662Z","updated_at":"2025-06-26T08:35:42.961Z","avatar_url":"https://github.com/zzampax.png","language":"Rust","readme":"# Simple RUST custom HTTP Blog Page\n\n\u003cdiv style=\"display:flex;justify-content:center;\"\u003e\u003cimg src='https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Frustacean.net%2Fmore-crabby-things%2Frustdocs.png\u0026f=1\u0026nofb=1\u0026ipt=e2106bf2df223e5190325f534d2420ae79a4a430b67bdfda06918721d3786544\u0026ipo=images' alt='Rust' width=\"400\"\u003e\u003c/div\u003e\n\n## Description\n\nThis is a completly custom made HTTP server, it doesn't use any external library to handle the HTTP requests, it's all made from scratch.\nThis is **not** a production ready server, it's just a simple project to learn how to handle HTTP requests in RUST.\nI am sure that my code can be substantially improved and refactored, this is my first real Rust project.\n\n## Project Structure\n\nThe libraries used in this project are:\n\n- **tokio**: To handle the asyncronous tasks.\n  - **tokio::net::TcpListener**: To listen for incoming connections.\n  - **tokio::io::AsyncReadExt**: To read the incoming data.\n  - **tokio::io::AsyncWriteExt**: To write the response.\n  - **tokio::fs**: To read the files from the disk.\n- _uuid_: To generate the UUIDs.\n- _sha256_: To hash the passwords.\n- _base64_: To encode and decode the base64 strings.\n- _urlencoding_: To encode and decode the URL strings.\n- _json_: To parse and create JSON objects.\n- _rusqlite_: To handle the SQLite database.\n- _colored_: To color the output in the terminal\n\nThe following is the project tree structure:\n\n```\nsrc/\n├── db.rs\n├── http\n│   ├── handle_get.rs\n│   ├── handle_post.rs\n│   ├── mod.rs\n│   └── token.rs\n├── main.rs\n└── multipart\n    ├── binary.rs\n    └── mod.rs\n```\n\n- **db.rs**: Contains the functions to interact with the SQLite database.\n- **http**: Contains the functions to handle the HTTP requests.\n  - **handle_get.rs**: Contains the functions to handle the GET requests.\n  - **handle_post.rs**: Contains the functions to handle the POST requests.\n  - **mod.rs**: Publishes the functions to handle the HTTP requests.\n  - **token.rs**: Contains the functions to handle the authentication tokens.\n- **multipart**: Contains the functions to handle the multipart requests.\n  - **binary.rs**: Contains the functions to handle the binary data sent in the multipart requests.\n  - **mod.rs**: Handles the multipart requests and calls the functions to handle the binary data.\n- **main.rs**: Contains the main function to start the server (TcpListener).\n\n## Database Structure\n\nThe database has the following tables:\n\n- **users**: Contains the users' data.\n  - **email**: The email of the user\n  - **password**: The hashed password of the user\n  - Primary key: **_email_**\n- **tokens**: Contains the tokens' data.\n  - **token**: The token of the user\n  - **email**: The email of the user\n  - Primary key: **_email_**\n  - Foreign key: **_email_** references **_users(email)_** on delete cascade\n- **posts**: Contains the posts' data.\n  - **post_id**: The UUID of the post\n  - **email**: The email of the user that created the post\n  - **title**: The title of the post\n  - **content**: The content of the post\n  - **image**: The UUID of the image of the post\n  - **datetime**: The date and time of the post\n  - Primary key: **_post_id_**\n  - Foreign key: **_email_** references **_users(email)_** on delete cascade\n- **comments**: Contains the comments' data.\n  - **comment_id**: The UUID of the comment\n  - **post_id**: The UUID of the post\n  - **email**: The email of the user that created the comment\n  - **content**: The content of the comment\n  - **datetime**: The date and time of the comment\n  - Primary key: **_comment_id_**\n  - Foreign key: **_post_id_** references **_posts(post_id)_** on delete cascade\n  - Foreign key: **_email_** references **_users(email)_** on delete cascade\n- **reactions**: Contains the reactions' data (heart, like, dislike)\n  - **reaction_id**: The UUID of the reaction\n  - **post_id**: The UUID of the post\n  - **email**: The email of the user that created the reaction\n  - **type**: The reaction of the user\n  - Primary key: **_reaction_id_**\n  - Foreign key: **_post_id_** references **_posts(post_id)_** on delete cascade\n  - Foreign key: **_email_** references **_users(email)_** on delete cascade\n\n\n![Database Structure](dbstructure.png)\n\n## How to run\n\nTo run the server, you need to have the RUST installed in your machine, you can install it by following the instructions in the [official website](https://www.rust-lang.org/tools/install).\n\nAfter installing the RUST, you can clone this repository and run the following command in the root folder of the project:\n\n```bash\ncargo run\n```\n\nThis will compile and run the server, you can access it by opening the browser and going to the address `http://\u003cany\u003e:8080`.\n\nThe `public` folder contains the files that will be served by the server, you can add more files to this folder and access them by going to the address `http://localhost:3000/\u003cfile_name\u003e`.\nIf the extension of the file is in `[\"png\", \"jpg\", \"jpeg\", \"gif\", \"ico\"]` the server will serve the file as a binary data, otherwise it will serve the file as a text data.\n\nPosts' images are stored in the `public/images` folder as `asset-\u003cuuid\u003e.\u003cext\u003e`, where `\u003cuuid\u003e` is the UUID of the post and `\u003cext\u003e` is the extension of the image.\n\nThe server will create a SQLite database in the root folder of the project called `blog.db`, you can use the `sqlite3` command to access the database and see the tables and data.\nI personally recommend adding the following script to the `.sqliterc` file in your home folder to make the output more readable:\n\n```sql\n.mode column\n.headers on\n.width 15 25 15\n```\n## APIs\n\nEvery API is in the route `/api`, the methods implemented are simply `GET` and `POST`, the following is the list of the APIs:\n### GET\n| Route | Description |\n| --- | --- |\n| **/api/posts** | Returns all the posts in the database |\n| **/api/comments?post_id=\u003cpost_id\u003e** | Returns all the comments of the post with the UUID `\u003cuuid\u003e` |\n| **/api/userreaction?post_id=\u003cpost_id\u003e** | Returns the type of reaction the user has set for a post (heart, thumbsUp, ...) |\n### POST\n| Route | Description |\n| --- | --- |\n| **/api/login** | Receives the email and password of the user and returns a token |\n| **/api/logout** | Receives the token of the user and deletes it from the database |\n| **/api/upload** | Receives title, content and image of the post and creates a new post (needs the token to be authenticated) |\n\nThe upload API is a multipart request, the client must send the data in the following format:\n\n```http\nPOST /api/upload HTTP/1.1\nHost: \u003cany\u003e:8080\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW\nContent-Length: 1234\nCookie: token=\u003ctoken\u003e\n...\n\n------WebKitFormBoundary7MA4YWxkTrZu0gW\nContent-Disposition: form-data; name=\"title\"\n\nTitle of the post\n------WebKitFormBoundary7MA4YWxkTrZu0gW\nContent-Disposition: form-data; name=\"content\"\n\nContent of the post\n------WebKitFormBoundary7MA4YWxkTrZu0gW\nContent-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\nContent-Type: image/\u003cext\u003e\n\n\u003cbinary data of the image\u003e\n------WebKitFormBoundary7MA4YWxkTrZu0gW--\n```\n\n## Worth mentioning\n\n- Content-Length Buffer Reader: The server uses a buffer reader to read the data from the client, this buffer reader reads the data until the end of the headers and then reads the body if there's a Content-Length header. This is an evolution from the old Semi-Dynamic Buffer, that has various problems with the reading of multipart requests. The following is the code of the buffer reader:\n\n```rust\nasync fn handle_buffer_data(stream: \u0026mut tokio::net::TcpStream) -\u003e std::io::Result\u003cVec\u003cu8\u003e\u003e {\n  let mut reader = tokio::io::BufReader::new(stream);\n  let mut request = Vec::new();\n  let mut headers = Vec::new();\n  let mut content_length = 0;\n\n  // Read the headers\n  loop {\n    let mut line = String::new();\n    reader.read_line(\u0026mut line).await?;\n    if line == \"\\r\\n\" {\n      break;\n    }\n    if line.starts_with(\"Content-Length:\") {\n      let parts: Vec\u003c\u0026str\u003e = line.split_whitespace().collect();\n      if let Some(length_str) = parts.get(1) {\n        content_length = length_str.parse::\u003cusize\u003e().unwrap_or(0);\n      }\n    }\n    headers.push(line.clone());\n    request.extend_from_slice(line.as_bytes());\n  }\n\n  // Read the body if there's a Content-Length\n  if content_length \u003e 0 {\n    let mut body: Vec\u003cu8\u003e = vec![0; content_length];\n    reader.read_exact(\u0026mut body).await?;\n    request.extend_from_slice(\"\\r\\n\".as_bytes());\n    request.extend_from_slice(\u0026body);\n  }\n\n  Ok(request)\n}\n```\nThe `handle_connection` function calls this function asynchronously to read the data sent from the client, The following is the unwrapping:\n\n```rust\nlet complete_buffer: Vec\u003cu8\u003e = match handle_buffer_data(\u0026mut socket).await {\n  Ok(buffer) =\u003e buffer,\n  Err(_) =\u003e {\n    println!(\"Error reading from socket\");\n    return;\n  }\n};\nlet string_buffer: std::string::String = String::from_utf8_lossy(\u0026complete_buffer).to_string();\n```\n\n- Responsive UI: By using DaisyUI, the server has a responsive UI that adapts to the screen size, making it easier to use on mobile devices. The UI is very simple and has only the necessary elements to interact with the server.\n- Authentication: The server uses a token-based authentication system, where the user sends the email and password to the server and the server returns a token that the user must use in the requests that require authentication.\n\n## Profile Pictures\nFor the profile pictures, the server uses the Gravatar API to get the profile pictures of the users, the server uses the email of the user to get the profile picture, this removes the need of the user to upload a profile picture to the server and for the server to store the profile picture of the user.\n\nThe services used is [DiceBear Avatars](https://avatars.dicebear.com/) to generate the profile pictures of the users, is API is able to generate a profile picture based on a seed, thus by using deterministic algorithms.\nThere are multiple styles of profile pictures that can be generated, the server uses the `notionists-neutral` style, simple but original!\nBy providing the same seed, the server can generate the same profile picture for the same user, this way the server doesn't need to store the profile picture in any database.\n\nThe image is processed as an SVG image, this way the image can be resized without losing quality.\nExample of the profile picture generated by the server:\n\n```html\nhttps://api.dicebear.com/8.x/notionists-neutral/svg?seed=rust.blog@zpx.it\n```\nResult:\n\n\n\u003cdiv style=\"display:flex;justify-content:center;\"\u003e\u003cimg src='https://api.dicebear.com/8.x/notionists-neutral/svg?seed=rust.blog@zpx.it' alt='Profile Picture' width=\"300\"\u003e\u003c/div\u003e\n\n## License\n\nCurrently, this code is under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007.\nRights remain with the original authors of the code.\n\n## Authors\n\nThe original authors of the code are:\n\n- [x] [zpx](https://github.com/zzampax) (the owner of the repository)\n- [x] [redux](https://github.com/th3-riddler) for helping with the sql intelliphense\n- [x] [fba06](https://github.com/fba06) for testing with WebKit Apple Products that are often a pain to work with\n- [x] [midee](https://github.com/MiDeee) for dumping multiple Gigabytes of Images as Test both for the application and my poor ThinkPad X270\n- [x] **_zpx's cat_** for testing the application\n\n![cat](cat.jpeg)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzzampax%2Fsimple-http","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzzampax%2Fsimple-http","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzzampax%2Fsimple-http/lists"}