Skip to content

Commit 1ea42cb

Browse files
etrclaude
andcommitted
TASK-040: Rewrite examples/ for the v2.0 lambda-first idiom
* Replace examples/hello_world.cpp with a 9-LOC lambda demo that satisfies the PRD §3.4 acceptance criterion (no http_resource subclass, no raw pointer, blocking start(true), <=10 LOC). * Add examples/shared_state.cpp -- the canonical example of when the class form is the right shape: an http_resource holding an int + std::mutex shared between render_get and render_post (PRD-HDL-REQ-005, AR-006). * Port the trivial single-method examples (handlers, hello_with_get_arg, setting_headers, custom_access_log, minimal_ip_ban, minimal_file_response, turbo_mode, daemon_info, minimal_https, minimal_https_psk, basic_authentication, digest_authentication, iovec_response_example, pipe_response_example, external_event_loop) to on_get / lambda form. * url_registration: lambda for stateless routes; keep one resource for the prefix + regex registration that on_* does not cover. * centralized_authentication: lambdify the two trivial resources; keep the free-function auth_handler. * Delete examples/minimal_hello_world.cpp -- superseded by the new hello_world.cpp (no v1-only files existed; this is an idiom-replacement, not v1 cleanup). * Rewrite examples/README.md as a categorized map of the suite, used as the input for TASK-041. * Add scripts/check-examples.sh -- static guard for hello_world LOC and shared_state structure. Wired into the top-level Makefile.am as a new check-examples target invoked by check-local, so it runs every `make check`. * Add scripts/verify-installed-examples.sh -- compiles every example against an installed prefix (the "consumer view" of the v2.0 headers). Verified: make check is 48/48 passing; scripts/check-examples.sh and scripts/verify-installed-examples.sh both green; release and --enable-debug (-Werror -Wextra) builds of every example are clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9a8f077 commit 1ea42cb

25 files changed

Lines changed: 699 additions & 495 deletions

Makefile.am

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ endif
3939
endif
4040

4141
EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh scripts/validate-version.sh \
42+
scripts/check-examples.sh scripts/verify-installed-examples.sh \
4243
test/headers/consumer_direct.cpp test/headers/consumer_detail.cpp test/headers/consumer_umbrella.cpp \
4344
test/headers/consumer_post_umbrella.cpp \
4445
test/headers/consumer_umbrella_no_backend.cpp
@@ -281,7 +282,7 @@ check-hygiene:
281282
# shared staged install to avoid paying two full `make install` costs on
282283
# every `make check`. Both sub-checks can still be invoked standalone (they
283284
# will do their own install when CHECK_*_SHARED is not set).
284-
check-local: check-headers
285+
check-local: check-headers check-examples
285286
@echo "=== Shared staged install for check-install-layout and check-hygiene ==="
286287
@rm -rf $(abs_top_builddir)/.shared-check-stage
287288
@$(MAKE) $(AM_MAKEFLAGS) install DESTDIR=$(abs_top_builddir)/.shared-check-stage >check-shared-install.log 2>&1 || { \
@@ -296,7 +297,14 @@ check-local: check-headers
296297
CHECK_HYGIENE_SHARED=yes
297298
@rm -rf $(abs_top_builddir)/.shared-check-stage
298299

299-
.PHONY: check-headers check-install-layout check-hygiene
300+
# TASK-040: enforce static invariants on the two flagship examples
301+
# (hello_world.cpp <= 10 LOC, lambda-only; shared_state.cpp class+mutex).
302+
# Run as part of `make check` via check-local.
303+
check-examples:
304+
@echo "=== check-examples: enforce TASK-040 invariants on examples/ ==="
305+
@$(top_srcdir)/scripts/check-examples.sh
306+
307+
.PHONY: check-headers check-install-layout check-hygiene check-examples
300308

301309
# TASK-039: top-level convenience rule that descends into test/ to
302310
# build and run the bench binaries (see test/Makefile.am and

examples/Makefile.am

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
LDADD = $(top_builddir)/src/libhttpserver.la
2020
AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/
2121
METASOURCES = AUTO
22-
noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg args_processing setting_headers custom_access_log minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload file_upload_with_callback empty_response_example iovec_response_example pipe_response_example daemon_info external_event_loop turbo_mode binary_buffer_response
22+
noinst_PROGRAMS = hello_world shared_state service custom_error allowing_disallowing_methods handlers hello_with_get_arg args_processing setting_headers custom_access_log minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload file_upload_with_callback empty_response_example iovec_response_example pipe_response_example daemon_info external_event_loop turbo_mode binary_buffer_response
2323

2424
hello_world_SOURCES = hello_world.cpp
25+
shared_state_SOURCES = shared_state.cpp
2526
service_SOURCES = service.cpp
26-
minimal_hello_world_SOURCES = minimal_hello_world.cpp
2727
custom_error_SOURCES = custom_error.cpp
2828
allowing_disallowing_methods_SOURCES = allowing_disallowing_methods.cpp
2929
handlers_SOURCES = handlers.cpp

examples/README.md

Lines changed: 109 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,122 @@
1-
Example Programs
2-
================
3-
4-
hello_world.cpp - a very simple example of using libhttpserver to
5-
create a Rest server capable of receiving and processing
6-
HTTP requests. The server will be listening on port
7-
8080.
8-
9-
10-
service.cpp - an example using more of the libhttpserver API.
11-
This creates a Rest server capable of running with
12-
HTTP or HTTPS (provided that libhttpserver and
13-
libmicrohttpd have been compiled with SSL support.
14-
15-
The server can be configured via command line
16-
arguments:
1+
libhttpserver Examples
2+
======================
173

18-
-p <port> - port number to listen on (default 8080)
19-
-s - enable HTTPS
20-
-k - server key filename (default "key.pem")
21-
-c - server certificate filename (default "cert.pem")
4+
Every example is a standalone program built from a single `.cpp`. To
5+
build the suite locally:
6+
7+
./bootstrap && mkdir build && cd build && ../configure && make
8+
9+
Each binary lands in `build/examples/`. To verify that an example also
10+
compiles against an *installed* libhttpserver (the consumer view), run
11+
`scripts/verify-installed-examples.sh` after `make install`. To enforce
12+
the structural invariants on `hello_world.cpp` and `shared_state.cpp`
13+
(LOC budget, lambda-only form, mutex usage), run
14+
`scripts/check-examples.sh`.
15+
16+
Start here
17+
----------
18+
19+
* `hello_world.cpp` — ten lines, no subclassing, no raw pointers. The
20+
canonical "hello world" demo and the PRD §3.4 acceptance fixture for
21+
the v2.0 lambda idiom.
22+
* `shared_state.cpp` — the canonical example of when the class form is
23+
the right shape: an `http_resource` subclass that shares a `std::mutex`-
24+
guarded counter between `render_get` and `render_post`.
25+
26+
HTTP basics
27+
-----------
28+
29+
* `setting_headers.cpp` — attach response headers fluently with
30+
`with_header`.
31+
* `hello_with_get_arg.cpp` — read a query-string parameter from
32+
`http_request::get_arg`.
33+
* `args_processing.cpp` — iterate every form/query argument.
34+
* `custom_error.cpp` — install custom `not_found_handler` and
35+
`method_not_allowed_handler` and let `set_allowing` restrict methods
36+
on a resource.
37+
* `allowing_disallowing_methods.cpp` — narrow a resource to a single
38+
HTTP method via the allow mask.
39+
* `url_registration.cpp` — exact paths, prefix matches, regex paths,
40+
and parametric segments (with optional per-segment regex).
41+
* `handlers.cpp` — register distinct lambdas for GET and POST on the
42+
same path; the dispatcher composes them.
43+
44+
Response shapes
45+
---------------
46+
47+
* `minimal_file_response.cpp` — stream a file from disk.
48+
* `binary_buffer_response.cpp` — return a binary buffer (e.g. a PNG).
49+
* `iovec_response_example.cpp` — gather a response body from multiple
50+
borrowed buffers without copying.
51+
* `pipe_response_example.cpp` — stream from a pipe filled by a
52+
background thread.
53+
* `empty_response_example.cpp` — bodyless responses for DELETE / HEAD.
54+
* `minimal_deferred.cpp` — deferred response: the body is produced
55+
asynchronously by a callback.
56+
* `deferred_with_accumulator.cpp` — deferred response that mutates
57+
shared state across calls.
58+
59+
TLS and authentication
60+
----------------------
61+
62+
* `minimal_https.cpp` — enable TLS with PEM key/cert files.
63+
* `minimal_https_psk.cpp` — TLS with pre-shared keys.
64+
* `basic_authentication.cpp` — per-request HTTP Basic auth inside the
65+
handler.
66+
* `centralized_authentication.cpp` — server-wide `auth_handler` and
67+
`auth_skip_paths`.
68+
* `digest_authentication.cpp` — per-request HTTP Digest auth via
69+
`http_request::check_digest_auth`.
70+
* `client_cert_auth.cpp` — mutual TLS with X.509 client certificates.
71+
Ships as a documentation artifact; not wired into `noinst_PROGRAMS`.
72+
73+
Operations
74+
----------
75+
76+
* `daemon_info.cpp` — introspect the running daemon (bound port,
77+
listen FD, active connections).
78+
* `external_event_loop.cpp` — drive `EXTERNAL_SELECT` from the
79+
application's loop via `run_wait`.
80+
* `custom_access_log.cpp` — server-wide access-log callback.
81+
* `minimal_ip_ban.cpp``block_ip` / `unblock_ip` under the default
82+
ACCEPT policy.
83+
* `turbo_mode.cpp` — turbo, suppressed Date header, fastopen queue,
84+
listen backlog.
85+
* `service.cpp` — the kitchen-sink reference example: CLI args,
86+
optional TLS, all `render_*` overrides.
87+
88+
Benchmarks
89+
----------
90+
91+
* `benchmark_select.cpp`, `benchmark_threads.cpp`,
92+
`benchmark_nodelay.cpp` — micro-benchmarks. See `test/v1_baseline/` for
93+
v1.x reference numbers.
94+
95+
WebSockets
96+
----------
97+
98+
* `websocket_echo.cpp``websocket_handler` subclass registered via
99+
`register_ws_resource` with `std::make_unique`.
100+
101+
File upload
102+
-----------
103+
104+
* `file_upload.cpp`, `file_upload_with_callback.cpp` — multipart
105+
upload, GET serves the HTML form and POST processes the parts.
22106

23107
Creating Certificates
24108
=====================
109+
25110
Self-signed certificates can be created using OpenSSL using the
26111
following steps:
27112

28-
$ openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
29-
$ openssl rsa -passin pass:x -in server.pass.key -out server.key
30-
$ openssl req -new -key server.key -out server.csr
31-
$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
113+
$ openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
114+
$ openssl rsa -passin pass:x -in server.pass.key -out server.key
115+
$ openssl req -new -key server.key -out server.csr
116+
$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
32117

33118
On the last step when prompted for a challenge password it can be left
34119
empty.
35120

36121
Thanks to https://devcenter.heroku.com/articles/ssl-certificate-self
37122
for these instructions.
38-
39-
Keystore configuration
40-
======================
41-
If using a local client such as RestClient
42-
(https://github.com/wiztools/rest-client) for testing the Rest server
43-
then a keystore needs to be established. These commands should be
44-
bundled with your Java installation.
45-
46-
$ keytool -noprompt -import -keystore /path/to/restclient.store -alias
47-
restclient -file /path/to/server.crt
48-
49-
The keys in the store can be listed as follows:
50-
51-
$ keytool -list -v -keystore /path/to/restclient.store
52-
53-
The client can then be configured to use this keystore. Thanks to
54-
http://rubensgomes.blogspot.com/2012/01/how-to-set-up-restclient-for-ssl.html
55-
for instructions on configuring RestClient.
56-
57-
58-

examples/basic_authentication.cpp

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
This file is part of libhttpserver
3-
Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino
3+
Copyright (C) 2011-2025 Sebastiano Merlino
44
55
This library is free software; you can redistribute it and/or
66
modify it under the terms of the GNU Lesser General Public
@@ -18,29 +18,26 @@
1818
USA
1919
*/
2020

21-
#include <memory>
21+
// basic_authentication.cpp - per-request HTTP Basic auth check inside a
22+
// lambda handler. For centralized auth that intercepts every request,
23+
// see centralized_authentication.cpp.
24+
2225
#include <string>
2326

2427
#include <httpserver.hpp>
2528

26-
class user_pass_resource : public httpserver::http_resource {
27-
public:
28-
httpserver::http_response render_get(const httpserver::http_request& req) {
29-
if (req.get_user() != "myuser" || req.get_pass() != "mypass") {
30-
return
31-
httpserver::http_response::unauthorized("Basic", "test@example.com", "FAIL");
32-
}
33-
34-
return httpserver::http_response::string(std::string(req.get_user()) + " " + std::string(req.get_pass()));
35-
}
36-
};
37-
3829
int main() {
3930
httpserver::webserver ws{httpserver::create_webserver(8080)};
4031

41-
auto hwr = std::make_shared<user_pass_resource>();
42-
ws.register_path("/hello", hwr);
43-
ws.start(true);
32+
ws.on_get("/hello", [](const httpserver::http_request& req) {
33+
if (req.get_user() != "myuser" || req.get_pass() != "mypass") {
34+
return httpserver::http_response::unauthorized(
35+
"Basic", "test@example.com", "FAIL");
36+
}
37+
return httpserver::http_response::string(
38+
std::string(req.get_user()) + " " + std::string(req.get_pass()));
39+
});
4440

41+
ws.start(true);
4542
return 0;
4643
}

examples/centralized_authentication.cpp

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
This file is part of libhttpserver
3-
Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino
3+
Copyright (C) 2011-2025 Sebastiano Merlino
44
55
This library is free software; you can redistribute it and/or
66
modify it under the terms of the GNU Lesser General Public
@@ -18,57 +18,39 @@
1818
USA
1919
*/
2020

21+
// centralized_authentication.cpp - install a server-wide auth_handler
22+
// that runs before every request. The handler returns nullptr to
23+
// accept, or an http_response to reject. auth_skip_paths lists routes
24+
// that bypass auth entirely.
25+
2126
#include <memory>
22-
#include <string>
2327

2428
#include <httpserver.hpp>
2529

26-
using httpserver::http_request;
27-
using httpserver::http_response;
28-
using httpserver::http_resource;
29-
using httpserver::webserver;
30-
using httpserver::create_webserver;
31-
// Simple resource that doesn't need to handle auth itself
32-
class hello_resource : public http_resource {
33-
public:
34-
http_response render_get(const http_request&) {
35-
return http_response::string("Hello, authenticated user!");
36-
}
37-
};
38-
39-
class health_resource : public http_resource {
40-
public:
41-
http_response render_get(const http_request&) {
42-
return http_response::string("OK");
43-
}
44-
};
45-
46-
// Centralized authentication handler
47-
// Returns nullptr to allow the request, or an http_response to reject it
48-
std::shared_ptr<http_response> auth_handler(const http_request& req) {
30+
// Returns nullptr to allow the request, or an http_response to reject it.
31+
std::shared_ptr<httpserver::http_response> auth_handler(
32+
const httpserver::http_request& req) {
4933
if (req.get_user() != "admin" || req.get_pass() != "secret") {
50-
return std::make_shared<http_response>(
51-
http_response::unauthorized("Basic", "MyRealm", "Unauthorized"));
34+
return std::make_shared<httpserver::http_response>(
35+
httpserver::http_response::unauthorized(
36+
"Basic", "MyRealm", "Unauthorized"));
5237
}
53-
return nullptr; // Allow request
38+
return nullptr;
5439
}
5540

5641
int main() {
57-
// Create webserver with centralized authentication
58-
// - auth_handler: called before every resource's render method
59-
// - auth_skip_paths: paths that bypass authentication
60-
webserver ws{create_webserver(8080)
61-
.auth_handler(auth_handler)
62-
.auth_skip_paths({"/health", "/public/*"})};
63-
64-
auto hello = std::make_shared<hello_resource>();
65-
auto health = std::make_shared<health_resource>();
42+
httpserver::webserver ws{httpserver::create_webserver(8080)
43+
.auth_handler(auth_handler)
44+
.auth_skip_paths({"/health", "/public/*"})};
6645

67-
ws.register_path("/api", hello);
68-
ws.register_path("/health", health);
46+
ws.on_get("/api", [](const httpserver::http_request&) {
47+
return httpserver::http_response::string("Hello, authenticated user!");
48+
});
49+
ws.on_get("/health", [](const httpserver::http_request&) {
50+
return httpserver::http_response::string("OK");
51+
});
6952

7053
ws.start(true);
71-
7254
return 0;
7355
}
7456

examples/custom_access_log.cpp

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
This file is part of libhttpserver
3-
Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino
3+
Copyright (C) 2011-2025 Sebastiano Merlino
44
55
This library is free software; you can redistribute it and/or
66
modify it under the terms of the GNU Lesser General Public
@@ -18,8 +18,11 @@
1818
USA
1919
*/
2020

21+
// custom_access_log.cpp - install a per-request access-log callback via
22+
// create_webserver().log_access(). The callback is server-wide; the
23+
// handler itself is a plain lambda.
24+
2125
#include <iostream>
22-
#include <memory>
2326
#include <string>
2427

2528
#include <httpserver.hpp>
@@ -28,20 +31,14 @@ void custom_access_log(const std::string& url) {
2831
std::cout << "ACCESSING: " << url << std::endl;
2932
}
3033

31-
class hello_world_resource : public httpserver::http_resource {
32-
public:
33-
httpserver::http_response render(const httpserver::http_request&) {
34-
return httpserver::http_response::string("Hello, World!");
35-
}
36-
};
37-
3834
int main() {
3935
httpserver::webserver ws{httpserver::create_webserver(8080)
40-
.log_access(custom_access_log)};
36+
.log_access(custom_access_log)};
4137

42-
auto hwr = std::make_shared<hello_world_resource>();
43-
ws.register_path("/hello", hwr);
44-
ws.start(true);
38+
ws.on_get("/hello", [](const httpserver::http_request&) {
39+
return httpserver::http_response::string("Hello, World!");
40+
});
4541

42+
ws.start(true);
4643
return 0;
4744
}

0 commit comments

Comments
 (0)