Skip to content

Commit 68c3d6a

Browse files
committed
Merge TASK-040: Rewrite examples/ for the v2.0 lambda-first idiom (PRD §3.4, PRD-HDL-REQ-001..006)
Three commits on task/TASK-040: 1. Rewrite the example suite to v2.0 idioms (`on_get` + lambda where it shines, `http_resource` class form with `std::make_unique` where shared state belongs in a class), add hello_world.cpp (≤10 LOC) and shared_state.cpp as the canonical pair, plus scripts/check-examples.sh (wired into `make check`) and scripts/verify-installed-examples.sh (compiles every example against an installed prefix to validate the consumer view of the v2.0 headers). 2. Validation-loop fixes: `override` everywhere, `make_shared` consistency, comment hygiene, hardened CI scripts (`set -eo pipefail`, mktemp trap cleanup, asserted compile count). 3. Snapshot of the unworked findings (1 major, 81 minor, 0 critical). Verified: `make check` 48/48 green, `scripts/check-examples.sh` and `scripts/verify-installed-examples.sh` both green on release and `--enable-debug` builds of every example.
2 parents 9a8f077 + f9b582e commit 68c3d6a

37 files changed

Lines changed: 1399 additions & 759 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/allowing_disallowing_methods.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222

2323
#include <httpserver.hpp>
2424

25+
// The class form is required here: disallow_all() and set_allowing() are
26+
// methods on http_resource that control which HTTP methods are accepted.
27+
// Lambda-registered routes (ws.on_get / ws.on_post / etc.) target a single
28+
// method by construction and do not expose these per-resource controls.
29+
// Use the class form whenever you need fine-grained per-instance method ACLs.
2530
class hello_world_resource : public httpserver::http_resource {
2631
public:
2732
httpserver::http_response render(const httpserver::http_request&) {

examples/args_processing.cpp

Lines changed: 15 additions & 22 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,25 +18,26 @@
1818
USA
1919
*/
2020

21-
#include <iostream>
22-
#include <memory>
23-
#include <sstream>
24-
#include <string>
25-
26-
#include <httpserver.hpp>
27-
28-
// This example demonstrates how to use get_args() and get_args_flat() to
21+
// args_processing.cpp - demonstrates get_args() and get_args_flat() to
2922
// process all query string and body arguments from an HTTP request.
3023
//
3124
// Try these URLs:
3225
// http://localhost:8080/args?name=john&age=30
3326
// http://localhost:8080/args?id=1&id=2&id=3 (multiple values for same key)
3427
// http://localhost:8080/args?colors=red&colors=green&colors=blue
3528

36-
class args_resource : public httpserver::http_resource {
37-
public:
38-
httpserver::http_response render(const httpserver::http_request& req) {
39-
std::stringstream response_body;
29+
#include <iostream>
30+
#include <sstream>
31+
#include <string>
32+
#include <string_view>
33+
34+
#include <httpserver.hpp>
35+
36+
int main() {
37+
httpserver::webserver ws{httpserver::create_webserver(8080)};
38+
39+
ws.on_get("/args", [](const httpserver::http_request& req) {
40+
std::ostringstream response_body;
4041

4142
response_body << "=== Using get_args() (supports multiple values per key) ===\n\n";
4243

@@ -83,20 +84,12 @@ class args_resource : public httpserver::http_resource {
8384
}
8485

8586
return httpserver::http_response::string(response_body.str());
86-
}
87-
};
88-
89-
int main() {
90-
httpserver::webserver ws{httpserver::create_webserver(8080)};
91-
92-
auto ar = std::make_shared<args_resource>();
93-
ws.register_path("/args", ar);
87+
});
9488

9589
std::cout << "Server running on http://localhost:8080/args\n";
9690
std::cout << "Try: http://localhost:8080/args?name=john&age=30\n";
9791
std::cout << "Or: http://localhost:8080/args?id=1&id=2&id=3\n";
9892

9993
ws.start(true);
100-
10194
return 0;
10295
}

examples/basic_authentication.cpp

Lines changed: 19 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,31 @@
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+
//
25+
// NOTE: Credentials are hardcoded here for illustration only. In production,
26+
// load expected values from environment variables or a secrets store — never
27+
// from source code. Never reflect the password in the response body.
28+
2229
#include <string>
2330

2431
#include <httpserver.hpp>
2532

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-
3833
int main() {
3934
httpserver::webserver ws{httpserver::create_webserver(8080)};
4035

41-
auto hwr = std::make_shared<user_pass_resource>();
42-
ws.register_path("/hello", hwr);
43-
ws.start(true);
36+
ws.on_get("/hello", [](const httpserver::http_request& req) {
37+
if (req.get_user() != "myuser" || req.get_pass() != "mypass") {
38+
return httpserver::http_response::unauthorized(
39+
"Basic", "test@example.com", "FAIL");
40+
}
41+
// Only echo the username — never reflect the password back to the client.
42+
return httpserver::http_response::string(
43+
"Hello, " + std::string(req.get_user()) + "!");
44+
});
4445

46+
ws.start(true);
4547
return 0;
4648
}

0 commit comments

Comments
 (0)