How the web works inside the client and server-side. This project focuses on browser rendering and web server implementation. 🗂️ Built with in Java, Rust, and Python.
- Project Structure
- Web Browser Rendering Engine
- Simple Container (Web Application Server)
- NIO HTTP Client/Server
- HTTP Protocol Notes
- Development
- References
This project consists of three main components:
- A toy web browser rendering engine — Implemented in Java, Rust, and Python
- A simple web application server (servlet container) — Implemented in Java
- NIO HTTP Client/Server — Minimal Java NIO example
Image from limpet.net/mbrubeck
The toy web browser engine is influenced by limpet.net/mbrubeck's Rust works.
Rendered output of test data with perf-rainbow.html and perf-rainbow.css.
jerry-web-render-was/
├── org.web.labs.inside.jerry/
│ └── src/jerry/
│ ├── render/ # Java rendering engine
│ ├── rust/ # Rust rendering engine
│ ├── python/ # Python rendering engine
│ ├── test/ # Test HTML/CSS files
│ ├── nio/ # NIO HTTP Client/Server
│ └── was/ # Web Application Server
├── render_test.sh # Test script (Linux/Mac)
├── render_test.bat # Test script (Windows)
└── README.md
The rendering engine parses HTML and CSS, builds a style tree, calculates layout, and paints pixels to an image.
- HTML Parsing → DOM Tree
- CSS Parsing → Stylesheet
- Style Tree → DOM + CSS Rules applied
- Layout Tree → Box model calculations
- Painting → Pixel output
| Language | Directory | Description |
|---|---|---|
| Java | render/ |
Original implementation |
| Rust | rust/ |
Reference implementation |
| Python | python/ |
New implementation |
Windows:
render_test.bat python # Run Python renderer
render_test.bat rust # Run Rust renderer
render_test.bat all # Run both and compare
# With custom files
render_test.bat python --html custom.html --css custom.cssLinux/Mac:
./render_test.sh python # Run Python renderer
./render_test.sh rust # Run Rust renderer
./render_test.sh all # Run both and compare
# With custom files
./render_test.sh python --html custom.html --css custom.cssPython:
cd org.web.labs.inside.jerry/src/jerry/python
pip install Pillow # Required for image output
python main.py -H ../test/test.html -c ../test/test.css -o output.png -vRust:
cd org.web.labs.inside.jerry/src/jerry/rust
cargo run -- -h ../test/test.html -c ../test/test.css -o output.pngJava:
# Compile and run RenderMain.java
# Update file paths in RenderMain.java firstA lightweight servlet container implementation that combines an HTTP server with a class loader.
graph TD
subgraph SimpleContainer
subgraph HTTP_Layer[HTTP Server & Handling]
SL(Socket Listener)
TP(Thread Pool<br/>cached executor)
RH(Request Handler<br/>Health & Servlets)
SL --> TP(Accepts & Delegates Connection)
TP --> RH(Executes Request)
end
subgraph Servlet_Management[Servlet Management]
CL(ClassLoader<br/>URLClassLoader)
SC(Servlet Cache<br/>Singleton Inst.)
CHM(Concurrent Map<br/>Thread-Safe Storage)
CL --> SC(Loads Class into Cache)
SC --> CHM(Stores Servlet Instances)
end
subgraph HTTP_Data_Model[HTTP Data Components]
REQ(HttpRequest)
RES(HttpResponse)
HDR(HttpHeader & Status)
end
end
subgraph Deployment_Structure[webapps/ Directory]
WEBINF[WEB-INF/]
CLASSES[classes/<br/>.class files]
LIB[lib/<br/>.jar dependencies]
WEBINF --> CLASSES
WEBINF --> LIB
end
RH --> CL(Needs Servlet for Request)
CL --> Deployment_Structure(Loads Class/Resource)
style SL fill:#ccf,stroke:#333
style RH fill:#bbf,stroke:#333
style CL fill:#fcf,stroke:#333
Request Flow:
flowchart TD
A[Client Request] --> B(Socket Listener<br/>Accept Connection);
B --> C(Thread Pool<br/>Execute Handler);
subgraph Request_Handler_Flow[Request Handler Logic]
C --> D{Route Matching: Request Path};
D -- /health or /servlets --> E1[Built-in JSON Response];
D -- /servlet/name --> F1(Check Servlet Cache);
F1 -- Found in Cache --> G1(IToy.doService<br/>Execute Servlet);
F1 -- Not Found --> G2[ClassLoader<br/>Load Servlet Class];
G2 --> H1[Store in Servlet Cache];
H1 --> G1;
D -- Other Paths --> I1[Static File Serving];
end
E1 --> J(Prepare HttpResponse);
G1 --> J;
I1 --> J;
J --> K[Send Response to Client];
style G1 fill:#9f9,stroke:#333
style G2 fill:#f99,stroke:#333
- Engine - Represents the entire Catalina servlet engine (e.g., Catalina)
- Host - Represents a virtual host with multiple contexts (e.g., localhost)
- Context - Represents a web application with one or more wrappers
- Wrapper - Represents an individual servlet
Each container has Realm (Authentication), Valve (Request/Response processing), and Logger.
- Servlet Registration & Caching - Thread-safe servlet management
- Configurable Context Path - Customize webapp location
- Graceful Shutdown - Proper resource cleanup
- Health Check Endpoint -
/healthreturns server status - Servlet List Endpoint -
/servletsreturns registered servlets - Query String Parsing - Full URL parameter support
- Thread Pool - Configurable concurrent request handling
# Default (port 8080)
java org.web.labs.inside.jerry.was.SimpleContainer
# Custom port and context
java org.web.labs.inside.jerry.was.SimpleContainer -p 9090 -c /path/to/webapps
# Show help
java org.web.labs.inside.jerry.was.SimpleContainer --help| Option | Description | Default |
|---|---|---|
-p, --port |
Server port | 8080 |
-c, --context |
Context path | ./webapps |
-h, --help |
Show help | - |
| Endpoint | Method | Description |
|---|---|---|
/servlet/<name> |
GET | Execute a servlet |
/health |
GET | Health check (JSON) |
/servlets |
GET | List registered servlets (JSON) |
/* |
GET | Serve static files |
Request: http://localhost:8080/servlet/ToyServlet
Create a servlet by implementing the IToy interface:
package org.web.labs.inside.jerry.was.toyservlet;
public class MyServlet implements IToy {
private String name = "MyServlet";
public void setName(String name) { this.name = name; }
public String getName() { return name; }
@Override
public String doService() {
return "<html><h1>Hello from " + name + "</h1></html>";
}
}webapps/
└── WEB-INF/
├── classes/ # Compiled servlet classes
└── lib/ # JAR dependencies
The project includes a non-blocking I/O (NIO) implementation for HTTP client and server operations.
org.web.labs.inside.jerry/src/jerry/nio/
├── NIOHttpClient.java # Non-blocking HTTP client
└── NIOHttpServer.java # Non-blocking HTTP server
- Non-blocking I/O using Java NIO Selectors and Channels
- State Machine for HTTP response parsing
- Chunked Transfer Encoding support
- Single-threaded Event Loop architecture
# Default port 8888
java org.web.labs.inside.jerry.nio.NIOHttpServer
# Custom port
java org.web.labs.inside.jerry.nio.NIOHttpServer -p 9999# Connect to localhost:8888
java org.web.labs.inside.jerry.nio.NIOHttpClient
# Connect to custom host/port
java org.web.labs.inside.jerry.nio.NIOHttpClient -h example.com -p 80| Endpoint | Description |
|---|---|
/ |
Welcome page with server info |
/health |
Health check (JSON) |
/echo |
Echoes the request back |
- Netty is a production-grade NIO framework offering battle‑tested event loops, backpressure, TLS, HTTP/2, and rich pipeline handlers.
- This repo’s
nio/server is intentionally minimal for learning: Selector loop, basic parsing, and simple handlers. - When building real services, prefer Netty for:
- Robust connection handling and performance tuning across OSes
- Mature HTTP codecs, chunked streaming, and WebSocket support
- Extensible pipeline (handlers for logging, compression, TLS, metrics)
- Migration path: keep your request routing and business logic, and swap the low‑level selector code with Netty
ChannelInboundHandlerimplementations.
- Blocking I/O (IO): Each connection handled by a dedicated thread;
read()blocks until data arrives. Simple to reason about, but threads are expensive under high concurrency and can suffer head‑of‑line blocking. - Non‑blocking I/O (NIO): Single (or few) selector loops multiplex many connections;
read()returns immediately if no data, and readiness is signaled by the selector. Great for thousands of sockets with lower thread counts; requires state machines for partial reads/writes. - Async I/O: OS or runtime completes operations and invokes callbacks/futures/promises. In Java, async often means NIO + completion handlers (e.g., Netty pipeline) or
CompletableFuture; in Rust/Python it’s typically event loops (tokio/asyncio). Reduces busy‑waiting and improves latency with structured concurrency. - Trade‑offs:
- IO → easiest API, highest thread usage, simpler debugging.
- NIO → scalable with explicit readiness handling, more complex state management.
- Async → scalable and expressive with backpressure, requires async‑aware libraries and careful design around cancellation/timeouts.
HTTP is based on two-way transfer (request and response). Both consist of:
- Header - Metadata (method, path, content-type, etc.)
- Body - Content payload
Each line is tokenized by CRLF (\r\n).
HTTP 1.1 supports chunked transfer encoding for streaming data:
<chunk-size-hex>\r\n
<chunk-data>\r\n
...
0\r\n
\r\n
- Java - JDK 8 or higher
- Rust - For Rust renderer (install via rustup)
- Python 3 - For Python renderer
- Pillow - Python image library (
pip install Pillow)
# Java (using javac or your IDE)
javac -d out src/jerry/render/*.java
# Rust
cd org.web.labs.inside.jerry/src/jerry/rust
cargo build --release
# Python (no build needed)
pip install Pillow- Let's build a browser engine! - Matt Brubeck
- HTTP Server (Japanese)
- Understanding of Servlet Container
This project is for educational purposes.

