Lightweight Multi-Process Static Site Server

Built with PHP 8 — Inspired by micro_httpd — Production Ready

Overview

Joojoo is a lightweight static file server built with PHP 8. It focuses on serving static content with minimal overhead, inspired by the web server found in TP-Link modems.

One day I noticed my modem had a tiny built-in web server — it served the admin panel over plain HTTP with no frameworks, no dependencies, nothing. I got curious about how something so simple could actually work, and that curiosity turned into this project. I started building it to understand the system, and it naturally grew from there.

Development Roadmap

graph LR A["✓ Core"] --> B["✓ Keep-Alive"] --> C["✓ Gzip"] --> D["✓ Cache-Control + ETag"] --> E["→ Range"] --> F["→ SSL"] style A fill:#90EE90 style B fill:#90EE90 style C fill:#90EE90 style D fill:#90EE90 style E fill:#FFE4B5 style F fill:#FFE4B5

How It Works

Architecture

Show architecture diagram
graph TD A["Master Process"] --> B["Create Server Socket"] B --> C["Prefork Workers"] C --> W1["Worker 1"] C --> W2["Worker 2"] C --> WN["Worker N"] W1 --> L["Accept and Handle Client Connections"] W2 --> L WN --> L L --> H["Resolve Content-Type"] H --> C1["Cache-Control\n(always enabled)"] C1 --> E1["Generate ETag"] E1 --> R1["If-None-Match check\n(304 or 200)"] R1 --> K["Keep-Alive Reuse\n(timeout/max requests)"]

Request Flow

Show request flow diagram
sequenceDiagram participant Client participant Worker participant FileSystem Client->>Worker: HTTP GET /index.html (+/- Accept-Encoding, If-None-Match) Worker->>Worker: Parse path + headers Worker->>FileSystem: Check if file exists FileSystem-->>Worker: File found Worker->>Worker: Resolve Content-Type Worker->>Worker: Build ETag from mtime+size alt ETag matches If-None-Match Worker-->>Client: HTTP/1.1 304 Not Modified + ETag else ETag does not match Worker->>Worker: Set Cache-Control alt Client accepts gzip Worker->>FileSystem: Check if /index.html.gz exists alt Precompressed file exists FileSystem-->>Worker: .gz file found else No precompressed file FileSystem-->>Worker: .gz not found Worker->>Worker: gzip original file on the fly end Worker->>Worker: Set Content-Encoding: gzip Worker->>Worker: Set Vary: Accept-Encoding else Client does not accept gzip Worker->>Worker: Send original uncompressed file end Worker->>Worker: Set ETag and Content-Length Worker-->>Client: HTTP/1.1 200 OK + file end Worker->>Worker: Keep-alive: reuse connection

Quick Start

Local Usage

php server.php --web-dir /path/to/site

Configuration

CLI Arguments:

Environment Variables:

Precedence: CLI arguments override environment variables.

Docker

# Build
docker build -t joojoo .

# Run
docker run --name joojoo --init --rm \
  -v /path/to/your/website:/html \
  -p 80:8000 \
  joojoo

References