commits
- Add Request Body Handling section to request-lifecycle.md
- Document lazy body reading with body_reader
- Explain streaming for large bodies with read_stream()
- Document max_body_size configuration
- Note HTTP/2 flow control is automatic
- Update http2.md client documentation
- Document h2c (cleartext HTTP/2) support
- Add POST request examples
- Document large body handling with flow control
- Fix outdated 'no h2c support' note
- Update basic-requests.md
- Add section on large request body support
- Note automatic HTTP/2 flow control handling
- Add cross-reference in responses.md to body handling
- Fix H2 server read buffer tracking for bodies >16KB
- Track buf_off/buf_len for partial consumption
- Handle Yield state in read loop
- Compress buffer after partial reads
- Fix H2 client body flushing before close
- Use Body.Writer.flush with callback before closing
- Ensures all data is sent before marking stream done
- Add comprehensive large body test suite
- HTTP/1.1: 1KB-5MB POST, integrity, max_body_size
- HTTP/2 h2c: 1KB-5MB POST, integrity
- WebSocket: 1KB-64KB messages, integrity
- Add benchmark comparison tools
- bench_compare.ml: Compare results across runs
- bench_parser.ml: Parse benchmark output
All tests pass with default H2 window size (65535 bytes),
confirming proper WINDOW_UPDATE flow control.
Release highlights:
- Security: enforce max_body_size, read/write timeouts in h1/h2 servers
- Security: lock-free buffer pooling via Kcas
- Security: cryptographically secure random for CSRF, sessions, tokens
- Security: constant-time auth comparison, WebSocket origin validation
- Deps: add bytesrw, kcas_data, climate to dune-project
- CI: add Dagger pipeline with Python SDK (works with Podman)
- Add dagger.json and .dagger/ module with Python SDK
- CI pipeline: base, build, test, ci, doc functions
- Uses ocaml/opam:debian-12-ocaml-5.4 image
- Add missing deps to dune-project: kcas_data, climate
- Reports to Dagger Cloud, works with Podman
Replace unused zlib/zstd OCaml bindings with bytesrw package
which provides the actual Bytesrw_zlib and Bytesrw_zstd modules
used in Plug.Compress.
- Add max_body_size enforcement with 413 responses
- Add read_timeout and request_timeout with 408 responses
- Implement lock-free buffer pooling via Kcas for thread-safety
- Use cryptographically secure random for CSRF, sessions, tokens, WebSocket
- Add secure_compare for constant-time auth comparison
- Add WebSocket origin validation and version checking
- Fix h2_server silent :path fallback (now returns 400)
- Replace String.concat with Buffer for body accumulation
Previously reuse_port defaulted to true, allowing multiple processes to
bind the same port via SO_REUSEPORT. This caused confusing behavior where
a new server would start successfully but only receive a fraction of
connections (kernel load-balancing between processes).
Now reuse_port defaults to false. Binding to an already-used port fails
with a clear "Address already in use" error. Users who need SO_REUSEPORT
for intentional load balancing can enable it explicitly.
Bumps version to 0.2.1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Router.scope, compile_scopes, Route.plug to routing.md
- Add Pipeline module documentation to plug-system.md
- Update request-lifecycle.md with three-layer diagram
- Replace Endpoint.start with Endpoint.to_handler + Server.run
- Add scope-based auth examples to authentication.md
- Add per-route plug examples to rate-limiting.md
All examples now use the new API pattern:
let handler = Endpoint.to_handler endpoint in
Server.run ~sw ~net ~config handler
- Add Auth.require_authenticated as proper Plug.t
- Organize routes into scopes: browser, public, auth-protected
- Change Route.plug signature to plug-first for |> piping
- Apply negotiate plug to browser scope, auth plug to protected scope
- Per-route plugs for rate limiting on login/register/submit
Implement three-layer request flow: Endpoint → Router (scopes) → Route
New features:
- Pipeline module for reusable plug collections
- Router.scope for grouping routes with shared plugs
- Route.plug for per-route middleware
- Endpoint.to_handler replaces Endpoint.start (separation of concerns)
API changes:
- Endpoint no longer manages server config (port/bind/tls)
- Server.run now accepts config directly
- Router.compile_scopes for scope-based routing
Includes documentation updates for new API patterns.
- fix: isolate bench/example deps from hcs package
Remove public_name from bench executables so opam install
doesn't pull in dream, piaf, sqlite3, etc.
- fix: WebSocket upgrade response not flushed
Race condition where read_loop set shutdown before write_loop
could send the 101 Switching Protocols response.
- test: add WebSocket integration test
- Set version to 0.1.0 in dune-project
- Remove climate from runtime dependencies (CLI-only dep)
- Remove duplicate Date_cache module from h1_server.ml
- Update README.md with missing modules and plugs
- Fix broken docs/plugs/reference.md link
15 tests covering:
- query, query_or, query_int, query_int_or, query_bool, query_float
- query_all, query URL decoding, empty query strings
- form_field, form_field_or, form_int
- form + as space conversion, form URL decoding, empty body
- Remove custom parse_form_urlencoded and form_value from handlers
- Use Request.form_field_or, Request.form_int for form data
- Fix Request module to handle + as space in form decoding
- Add form_int helper for integer form fields
- Add form_field_or helper with consistent arg order (req first)
- Update routing.md with form data section
- Update authentication.md to use Request helpers
- All examples use new req-first argument style
Change from `query "key" req` to `query req "key"` to match
Dream's API style and enable `req |> Request.query "key"`.
Add query_or, query_int, query_int_or, query_bool, query_bool_or,
query_float, query_float_or to Request module. Update routing docs
to use these instead of manual parsing.
128x128 ICO with OCaml camel silhouette extracted from official
ocaml.org favicon. ASCII art representation in code for visibility.
No external image library required - generates ICO format directly.
- Create docs/client/tls.md covering:
- Client TLS (system certs, insecure mode, HTTP/2 ALPN)
- Server TLS (loading PEM certs, HTTPS server example)
- Certificate generation (OpenSSL, mkcert)
- Certificate formats and chains
- Error handling and production checklist
- Add HTTPS section to first-server.md
- Fixes broken link in docs/README.md
Validates that using cs.buffer + cs.off instead of Cstruct.to_bigarray:
- 54x faster (0.32ms vs 17.24ms for 1M ops)
- 25000x less memory allocation (28 vs 700,028 words per 100k ops)
Response.Cstruct creation is similar to String/Bigstring - benefit is
enabling zero-copy streaming writes when caller already has a Cstruct.
- Add Response.Cstruct for zero-copy response bodies
- Add Response.cstruct helper function
- Handle Cstruct variant in H1/H2 servers using cs.off/cs.buffer directly
- Fix streaming writes to use cs.off/cs.buffer instead of Cstruct.to_bigarray
(avoids copy when Cstruct is a slice)
- Update logger, etag, compress plugs for new variant
- Add sse-withcredentials=true to htmx-ext-sse config to send cookies
- Re-add Auth.require_auth to /events SSE route
Closes: hcs-rjd, hcs-a26
- handlers.ml: Use Response.* helpers (redirect, json, html, unauthorized, etc.)
- auth.ml: Use Response.redirect and Response.unauthorized
- README.md: Update code examples to show new Response API
Use Response.stream directly. The SSE module now uses Response.stream.
- Update streaming examples to use Response.stream (Server.respond_stream still works)
- Document Response.body type variants
- Clarify that both Response.* and Server.respond* APIs are available
- Add note that Server.respond_stream is an alias for Response.stream
- Extract Date_cache to separate module
- Rewrite Response.t with unified body variants (Empty, String, Bigstring, Stream, Prebuilt_body)
- Move Prebuilt module from Server to Response
- Update all plugs to use Response.* instead of Server.* types
- Update tests for new Response type references
Replaced incorrect Body_stream example with accurate Server.respond_stream
API explanation:
- Explained next function signature and behavior
- Added working examples for basic streaming and file streaming
- Documented content_length vs chunked encoding
Plugs:
- overview.md: plug concepts, composition, pipeline building
- writing-plugs.md: custom plug creation guide
Guides:
- responses.md: response helpers, status codes, modifiers
- error-handling.md: error patterns, recover plug, structured errors
Client:
- basic-requests.md: GET/POST, config, error handling
- http2.md: HTTP/2 protocol selection and benefits
- connection-pooling.md: pool config and lifecycle
Real-time:
- channels.md: topic-based WebSocket messaging
Recipes:
- static-files.md: file serving, directory listing, caching
- Getting started: installation, first-server, first-client
- Guides: request-lifecycle, routing, plug-system
- Real-time: websocket, sse, pubsub
- Recipes: authentication, rate-limiting, cors, json-api
- Add auth_rate_limit: 5 requests per 60s by IP for brute force protection
- Apply rate limiting to POST /login and POST /register
- Require authentication for POST /logout endpoint
- Fetch user votes in index/index_new/show_link handlers
- Display voted state with orange/gray pill background styling
- Separate count spans for SSE updates to preserve voted class
- Vote endpoint returns OOB HTML with user's voted state
- SSE broadcasts only update count numbers, not full element
- Add id attribute to comment count span for htmx OOB targeting
- Broadcast actual comment_count instead of comment_id
- Fetch updated link after creating comment to get new count
- Implement proper vote toggle in Db.vote: clicking same vote removes it, different vote changes direction
- Display separate upvote/downvote counts (▲ X ▼ Y) instead of net score
- Update broadcast_vote to send separate counts for real-time updates
- Add Models.direction type and conversion helpers
- Added sse_status() function to show connection state
- Added CSS styles for SSE indicator with pulsing animation
- Updated nav_links() to include SSE status for logged-in users
- Fixed CSS border-radius to use 50%%%% (4 percent signs) for proper 50% output
Add hx-headers attribute to body tag to send X-CSRF-Token header
on all HTMX requests, fixing 403 errors on vote buttons.
Vote buttons were using HTMX attributes but the library wasn't loaded.
- Add htmx.org 2.0.7 from CDN to layout head
- Change vote icons from +/- to ▲/▼ arrows
- Add hx-swap='none' since score updates via WebSocket
Browsers URL-encode special characters like '=' as '%3D' when submitting
forms. CSRF tokens using Base64 end with '=' padding, causing token
mismatch. Added Uri.pct_decode to decode form field names and values.
Also includes:
- LAS CLI options (-p/--port, -d/--database, -v/--verbose)
- Verbose logging via Hcs.Log.stderr()
- Session secure:false for HTTP localhost
- Remove legacy middleware modules
- Add multipart form parsing module
- Complete tutorial app showcasing HCS features
- Content negotiation (JSON/HTML from same endpoints)
- Session-based authentication with CSRF protection
- Real-time updates via WebSocket pub/sub
- SQLite3 database with direct bindings
- Pure-html templates with form handling
- Rate limiting and compression plugs
Fixed type compatibility:
- Updated handlers to use Server.request/response (not H1_server types)
- Fixed pure-html method_ attribute to use polymorphic variants
- Implemented form URL encoding parser
- All components now work with Endpoint API
Files: bin/las/{las,routes,handlers,auth,db,models,views,realtime}.ml + README + dune
- bench_pubsub.ml: throughput, memory per subscription, parallel broadcast
- bench_channel.ml: parse/serialize event performance
- test_plug.ml: add tests for compress, cors, csrf, basic_auth, retry, static
- Remove deprecated control.ml (use Plug.Circuit_breaker/Plug.Retry instead)
- Remove circuit_breaker/retry from middleware_eio.ml (use Plug versions)
- Migrate plug/rate_limit.ml to Kcas_data.Hashtbl (lock-free, no Eio.Mutex)
- Migrate plug/circuit_breaker.ml to Kcas.Loc (atomic state transitions)
- Migrate pool.ml to Kcas_data.Hashtbl + Kcas.Loc (lock-free pool ops)
- Session plug with fiber-local storage and kcas for concurrency safety
- Token plug for HMAC signing and AEAD encryption
- Negotiate plug for content negotiation (Accept header handling)
- Endpoint module for application bootstrap (router + plugs + server)
- PubSub module for lock-free topic-based messaging
- Channel module for WebSocket topic subscriptions
- Add 16 plug modules in lib/plug/ directory:
core, logger, request_id, head, timeout, recover, cors, rate_limit,
etag, cache_control, static, compress, circuit_breaker, retry,
basic_auth, csrf
- Update lib/dune with include_subdirs unqualified
- Update bin/hs.ml and bin/hc.ml to use new Plug API
- Keep Middleware/Middleware_eio exported for backward compatibility
- Update README with Plug documentation and usage examples
- All 114 tests pass
- Add gzip/zstd compression/decompression to middleware_eio
- Add compress middleware that compresses responses based on Accept-Encoding
- Add helper functions: accepts_gzip, accepts_zstd, parse_accept_encoding
- Add comprehensive compression tests
- Add zlib and zstd as dependencies
- Remove unused middleware tests (logging, timing, recover, when_, unless)
- H1_client and H2_client now own internal pools using pool.ml
- Client creates both internal clients, routes by protocol
- Backward-compatible stateless API preserved (get, post)
- New stateful API: create -> request -> close
- Deleted redundant pooled_client.ml
- Add Request Body Handling section to request-lifecycle.md
- Document lazy body reading with body_reader
- Explain streaming for large bodies with read_stream()
- Document max_body_size configuration
- Note HTTP/2 flow control is automatic
- Update http2.md client documentation
- Document h2c (cleartext HTTP/2) support
- Add POST request examples
- Document large body handling with flow control
- Fix outdated 'no h2c support' note
- Update basic-requests.md
- Add section on large request body support
- Note automatic HTTP/2 flow control handling
- Add cross-reference in responses.md to body handling
- Fix H2 server read buffer tracking for bodies >16KB
- Track buf_off/buf_len for partial consumption
- Handle Yield state in read loop
- Compress buffer after partial reads
- Fix H2 client body flushing before close
- Use Body.Writer.flush with callback before closing
- Ensures all data is sent before marking stream done
- Add comprehensive large body test suite
- HTTP/1.1: 1KB-5MB POST, integrity, max_body_size
- HTTP/2 h2c: 1KB-5MB POST, integrity
- WebSocket: 1KB-64KB messages, integrity
- Add benchmark comparison tools
- bench_compare.ml: Compare results across runs
- bench_parser.ml: Parse benchmark output
All tests pass with default H2 window size (65535 bytes),
confirming proper WINDOW_UPDATE flow control.
Release highlights:
- Security: enforce max_body_size, read/write timeouts in h1/h2 servers
- Security: lock-free buffer pooling via Kcas
- Security: cryptographically secure random for CSRF, sessions, tokens
- Security: constant-time auth comparison, WebSocket origin validation
- Deps: add bytesrw, kcas_data, climate to dune-project
- CI: add Dagger pipeline with Python SDK (works with Podman)
- Add max_body_size enforcement with 413 responses
- Add read_timeout and request_timeout with 408 responses
- Implement lock-free buffer pooling via Kcas for thread-safety
- Use cryptographically secure random for CSRF, sessions, tokens, WebSocket
- Add secure_compare for constant-time auth comparison
- Add WebSocket origin validation and version checking
- Fix h2_server silent :path fallback (now returns 400)
- Replace String.concat with Buffer for body accumulation
Previously reuse_port defaulted to true, allowing multiple processes to
bind the same port via SO_REUSEPORT. This caused confusing behavior where
a new server would start successfully but only receive a fraction of
connections (kernel load-balancing between processes).
Now reuse_port defaults to false. Binding to an already-used port fails
with a clear "Address already in use" error. Users who need SO_REUSEPORT
for intentional load balancing can enable it explicitly.
Bumps version to 0.2.1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Router.scope, compile_scopes, Route.plug to routing.md
- Add Pipeline module documentation to plug-system.md
- Update request-lifecycle.md with three-layer diagram
- Replace Endpoint.start with Endpoint.to_handler + Server.run
- Add scope-based auth examples to authentication.md
- Add per-route plug examples to rate-limiting.md
All examples now use the new API pattern:
let handler = Endpoint.to_handler endpoint in
Server.run ~sw ~net ~config handler
Implement three-layer request flow: Endpoint → Router (scopes) → Route
New features:
- Pipeline module for reusable plug collections
- Router.scope for grouping routes with shared plugs
- Route.plug for per-route middleware
- Endpoint.to_handler replaces Endpoint.start (separation of concerns)
API changes:
- Endpoint no longer manages server config (port/bind/tls)
- Server.run now accepts config directly
- Router.compile_scopes for scope-based routing
Includes documentation updates for new API patterns.
- fix: isolate bench/example deps from hcs package
Remove public_name from bench executables so opam install
doesn't pull in dream, piaf, sqlite3, etc.
- fix: WebSocket upgrade response not flushed
Race condition where read_loop set shutdown before write_loop
could send the 101 Switching Protocols response.
- test: add WebSocket integration test
- Set version to 0.1.0 in dune-project
- Remove climate from runtime dependencies (CLI-only dep)
- Remove duplicate Date_cache module from h1_server.ml
- Update README.md with missing modules and plugs
- Fix broken docs/plugs/reference.md link
- Create docs/client/tls.md covering:
- Client TLS (system certs, insecure mode, HTTP/2 ALPN)
- Server TLS (loading PEM certs, HTTPS server example)
- Certificate generation (OpenSSL, mkcert)
- Certificate formats and chains
- Error handling and production checklist
- Add HTTPS section to first-server.md
- Fixes broken link in docs/README.md
Validates that using cs.buffer + cs.off instead of Cstruct.to_bigarray:
- 54x faster (0.32ms vs 17.24ms for 1M ops)
- 25000x less memory allocation (28 vs 700,028 words per 100k ops)
Response.Cstruct creation is similar to String/Bigstring - benefit is
enabling zero-copy streaming writes when caller already has a Cstruct.
- Add Response.Cstruct for zero-copy response bodies
- Add Response.cstruct helper function
- Handle Cstruct variant in H1/H2 servers using cs.off/cs.buffer directly
- Fix streaming writes to use cs.off/cs.buffer instead of Cstruct.to_bigarray
(avoids copy when Cstruct is a slice)
- Update logger, etag, compress plugs for new variant
Plugs:
- overview.md: plug concepts, composition, pipeline building
- writing-plugs.md: custom plug creation guide
Guides:
- responses.md: response helpers, status codes, modifiers
- error-handling.md: error patterns, recover plug, structured errors
Client:
- basic-requests.md: GET/POST, config, error handling
- http2.md: HTTP/2 protocol selection and benefits
- connection-pooling.md: pool config and lifecycle
Real-time:
- channels.md: topic-based WebSocket messaging
Recipes:
- static-files.md: file serving, directory listing, caching
Browsers URL-encode special characters like '=' as '%3D' when submitting
forms. CSRF tokens using Base64 end with '=' padding, causing token
mismatch. Added Uri.pct_decode to decode form field names and values.
Also includes:
- LAS CLI options (-p/--port, -d/--database, -v/--verbose)
- Verbose logging via Hcs.Log.stderr()
- Session secure:false for HTTP localhost
- Remove legacy middleware modules
- Add multipart form parsing module
- Complete tutorial app showcasing HCS features
- Content negotiation (JSON/HTML from same endpoints)
- Session-based authentication with CSRF protection
- Real-time updates via WebSocket pub/sub
- SQLite3 database with direct bindings
- Pure-html templates with form handling
- Rate limiting and compression plugs
Fixed type compatibility:
- Updated handlers to use Server.request/response (not H1_server types)
- Fixed pure-html method_ attribute to use polymorphic variants
- Implemented form URL encoding parser
- All components now work with Endpoint API
Files: bin/las/{las,routes,handlers,auth,db,models,views,realtime}.ml + README + dune
- Remove deprecated control.ml (use Plug.Circuit_breaker/Plug.Retry instead)
- Remove circuit_breaker/retry from middleware_eio.ml (use Plug versions)
- Migrate plug/rate_limit.ml to Kcas_data.Hashtbl (lock-free, no Eio.Mutex)
- Migrate plug/circuit_breaker.ml to Kcas.Loc (atomic state transitions)
- Migrate pool.ml to Kcas_data.Hashtbl + Kcas.Loc (lock-free pool ops)
- Session plug with fiber-local storage and kcas for concurrency safety
- Token plug for HMAC signing and AEAD encryption
- Negotiate plug for content negotiation (Accept header handling)
- Endpoint module for application bootstrap (router + plugs + server)
- PubSub module for lock-free topic-based messaging
- Channel module for WebSocket topic subscriptions
- Add 16 plug modules in lib/plug/ directory:
core, logger, request_id, head, timeout, recover, cors, rate_limit,
etag, cache_control, static, compress, circuit_breaker, retry,
basic_auth, csrf
- Update lib/dune with include_subdirs unqualified
- Update bin/hs.ml and bin/hc.ml to use new Plug API
- Keep Middleware/Middleware_eio exported for backward compatibility
- Update README with Plug documentation and usage examples
- All 114 tests pass
- Add gzip/zstd compression/decompression to middleware_eio
- Add compress middleware that compresses responses based on Accept-Encoding
- Add helper functions: accepts_gzip, accepts_zstd, parse_accept_encoding
- Add comprehensive compression tests
- Add zlib and zstd as dependencies
- Remove unused middleware tests (logging, timing, recover, when_, unless)