Skip to content

Commit 03533c6

Browse files
etrclaude
andcommitted
TASK-005: Add http_method enum and method_set bitmask
Introduces the type-safe HTTP-method primitives that http_resource, the route table, and lambda registration will consume. - enum class http_method : std::uint8_t { get, head, post, put, del, connect, options, trace, patch, count_ }. Identifier `del` avoids the C++ keyword; wire token returned by to_string is "DELETE". - struct method_set { std::uint32_t bits = 0; } with constexpr contains/set/clear/set_all/clear_all and defaulted operator==. - Free constexpr noexcept bitwise operators (|, &, ^, ~, |=, &=, ^=) on http_method and method_set, including mixed (set, enum) overloads. All operators usable in constant expressions and at runtime ("consteval- friendly" without forbidding runtime use, which the route-table writer path needs). - to_string(http_method) returning std::string_view for logging and the 405 Allow: header. Total over the 9 enumerators; out-of-range returns an empty view so logging stays robust against stale values. - Layout/width invariants pinned at namespace scope: count_ <= 32, standard layout, trivially copyable, sizeof(method_set) == sizeof(uint32_t). - Re-exported from <httpserver.hpp> and installed via nobase_include_HEADERS in src/Makefile.am. - Test driver test/unit/http_method_test.cpp covers both compile-time static_asserts (round-trip, layout, bitwise composition, complement bounding, to_string totality) and 13 runtime LT_BEGIN_AUTO_TEST cases including a contract check that to_string matches libmicrohttpd's MHD_HTTP_METHOD_* tokens. All 22 testsuite entries pass under the default build and under --enable-debug (-Wall -Wextra -Werror -pedantic). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d2a2544 commit 03533c6

5 files changed

Lines changed: 568 additions & 2 deletions

File tree

src/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp fil
2424
# Detail headers (httpserver/details/*.hpp) live here so they cannot leak to
2525
# downstream consumers — the public surface comes in through <httpserver.hpp>.
2626
noinst_HEADERS = httpserver/string_utilities.hpp httpserver/details/modded_request.hpp httpserver/details/http_endpoint.hpp gettext.h
27-
nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/string_response.hpp httpserver/digest_auth_fail_response.hpp httpserver/deferred_response.hpp httpserver/file_response.hpp httpserver/pipe_response.hpp httpserver/empty_response.hpp httpserver/feature_unavailable.hpp httpserver/iovec_entry.hpp httpserver/iovec_response.hpp httpserver/http_arg_value.hpp
27+
nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/string_response.hpp httpserver/digest_auth_fail_response.hpp httpserver/deferred_response.hpp httpserver/file_response.hpp httpserver/pipe_response.hpp httpserver/empty_response.hpp httpserver/feature_unavailable.hpp httpserver/iovec_entry.hpp httpserver/iovec_response.hpp httpserver/http_arg_value.hpp httpserver/http_method.hpp
2828

2929
if HAVE_BAUTH
3030
libhttpserver_la_SOURCES += basic_auth_fail_response.cpp

src/httpserver.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "httpserver/feature_unavailable.hpp"
3939
#include "httpserver/file_response.hpp"
4040
#include "httpserver/http_arg_value.hpp"
41+
#include "httpserver/http_method.hpp"
4142
#include "httpserver/http_request.hpp"
4243
#include "httpserver/http_resource.hpp"
4344
#include "httpserver/http_response.hpp"

src/httpserver/http_method.hpp

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/*
2+
This file is part of libhttpserver
3+
Copyright (C) 2011-2019 Sebastiano Merlino
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18+
USA
19+
*/
20+
21+
#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION)
22+
#error "Only <httpserver.hpp> or <httpserverpp> can be included directly."
23+
#endif
24+
25+
#ifndef SRC_HTTPSERVER_HTTP_METHOD_HPP_
26+
#define SRC_HTTPSERVER_HTTP_METHOD_HPP_
27+
28+
#include <cstdint>
29+
#include <string_view>
30+
#include <type_traits>
31+
32+
namespace httpserver {
33+
34+
// Strongly-typed HTTP method primitive consumed by http_resource, the
35+
// route table, and lambda registration. The identifier `del` (rather
36+
// than `delete`) avoids the C++ keyword; the wire-protocol token
37+
// returned by to_string() is "DELETE".
38+
//
39+
// `count_` is a sentinel and must remain the last enumerator. Any new
40+
// method goes immediately before it; to_string()'s switch must also be
41+
// updated. The 32-bit underlying storage of method_set leaves 23 bits
42+
// of growth headroom past the 9 standard methods (PRD-REQ-REQ-003,
43+
// DR-006).
44+
enum class http_method : std::uint8_t {
45+
get,
46+
head,
47+
post,
48+
put,
49+
del, // wire token "DELETE"
50+
connect,
51+
options,
52+
trace,
53+
patch,
54+
count_ // sentinel; must remain last
55+
};
56+
57+
namespace detail {
58+
59+
// Bit position for an http_method enumerator. Defined here so member
60+
// functions and free operators can share one definition. Out-of-range
61+
// inputs (>= 32) are masked out by the caller; this helper is total.
62+
constexpr std::uint32_t method_bit(http_method m) noexcept {
63+
return std::uint32_t{1} << static_cast<std::uint8_t>(m);
64+
}
65+
66+
// All-valid-methods mask: bits 0 .. count_-1 set, the rest cleared.
67+
constexpr std::uint32_t valid_method_mask() noexcept {
68+
return (std::uint32_t{1}
69+
<< static_cast<std::uint8_t>(http_method::count_)) - 1u;
70+
}
71+
72+
} // namespace detail
73+
74+
// Fixed-size set of allowed HTTP methods (one bit per http_method
75+
// enumerator). Aggregate so it stays standard layout / trivially
76+
// copyable; brace-init with {bits} is fine, and default-init gives an
77+
// empty set. Comparison is defaulted (constexpr noexcept).
78+
struct method_set {
79+
std::uint32_t bits = 0;
80+
81+
constexpr bool contains(http_method m) const noexcept {
82+
return (bits & detail::method_bit(m)) != 0u;
83+
}
84+
85+
constexpr method_set& set(http_method m) noexcept {
86+
bits |= detail::method_bit(m);
87+
return *this;
88+
}
89+
90+
constexpr method_set& clear(http_method m) noexcept {
91+
bits &= ~detail::method_bit(m);
92+
return *this;
93+
}
94+
95+
// set_all() and clear_all() operate over the valid-method window
96+
// (bits 0 .. count_-1); bits beyond count_ stay zero so complement
97+
// round-trips cleanly.
98+
constexpr method_set& set_all() noexcept {
99+
bits = detail::valid_method_mask();
100+
return *this;
101+
}
102+
103+
constexpr method_set& clear_all() noexcept {
104+
bits = 0u;
105+
return *this;
106+
}
107+
108+
friend constexpr bool operator==(method_set, method_set) noexcept = default;
109+
};
110+
111+
// to_string returns the uppercase RFC 9110 wire token for use in logs
112+
// and the 405 Allow: header. Total over the 9 declared enumerators;
113+
// any other underlying value (only producible via static_cast) returns
114+
// an empty view rather than crashing — keeps logging robust against
115+
// stale enum values.
116+
constexpr std::string_view to_string(http_method m) noexcept {
117+
switch (m) {
118+
case http_method::get: return std::string_view{"GET"};
119+
case http_method::head: return std::string_view{"HEAD"};
120+
case http_method::post: return std::string_view{"POST"};
121+
case http_method::put: return std::string_view{"PUT"};
122+
case http_method::del: return std::string_view{"DELETE"};
123+
case http_method::connect: return std::string_view{"CONNECT"};
124+
case http_method::options: return std::string_view{"OPTIONS"};
125+
case http_method::trace: return std::string_view{"TRACE"};
126+
case http_method::patch: return std::string_view{"PATCH"};
127+
case http_method::count_: return std::string_view{};
128+
}
129+
return std::string_view{};
130+
}
131+
132+
// Bitwise composition. Operators on http_method yield a method_set so
133+
// `get | post` is a two-method set ready to feed into route_entry.
134+
// All operators are constexpr noexcept — usable in compile-time
135+
// context (the "consteval-friendly" requirement) AND at runtime, which
136+
// the route-table writer path needs.
137+
138+
constexpr method_set operator|(http_method a, http_method b) noexcept {
139+
return method_set{detail::method_bit(a) | detail::method_bit(b)};
140+
}
141+
142+
constexpr method_set operator&(http_method a, http_method b) noexcept {
143+
return method_set{detail::method_bit(a) & detail::method_bit(b)};
144+
}
145+
146+
constexpr method_set operator^(http_method a, http_method b) noexcept {
147+
return method_set{detail::method_bit(a) ^ detail::method_bit(b)};
148+
}
149+
150+
// ~http_method == "every valid method except this one" (bounded to the
151+
// count_ window).
152+
constexpr method_set operator~(http_method m) noexcept {
153+
return method_set{detail::valid_method_mask() & ~detail::method_bit(m)};
154+
}
155+
156+
constexpr method_set operator|(method_set a, method_set b) noexcept {
157+
return method_set{a.bits | b.bits};
158+
}
159+
160+
constexpr method_set operator&(method_set a, method_set b) noexcept {
161+
return method_set{a.bits & b.bits};
162+
}
163+
164+
constexpr method_set operator^(method_set a, method_set b) noexcept {
165+
return method_set{a.bits ^ b.bits};
166+
}
167+
168+
// ~method_set is also bounded to the valid-method window so
169+
// `~method_set{}.set_all() == method_set{}` holds — i.e. complement is
170+
// an involution within the 9-bit window. Without the masking, unused
171+
// upper bits would leak in and break round-tripping.
172+
constexpr method_set operator~(method_set s) noexcept {
173+
return method_set{detail::valid_method_mask() & ~s.bits};
174+
}
175+
176+
// Mixed (method_set, http_method) overloads — convenience for the
177+
// common "set | method" composition.
178+
constexpr method_set operator|(method_set s, http_method m) noexcept {
179+
return method_set{s.bits | detail::method_bit(m)};
180+
}
181+
182+
constexpr method_set operator|(http_method m, method_set s) noexcept {
183+
return s | m;
184+
}
185+
186+
constexpr method_set operator&(method_set s, http_method m) noexcept {
187+
return method_set{s.bits & detail::method_bit(m)};
188+
}
189+
190+
constexpr method_set operator&(http_method m, method_set s) noexcept {
191+
return s & m;
192+
}
193+
194+
constexpr method_set operator^(method_set s, http_method m) noexcept {
195+
return method_set{s.bits ^ detail::method_bit(m)};
196+
}
197+
198+
constexpr method_set operator^(http_method m, method_set s) noexcept {
199+
return s ^ m;
200+
}
201+
202+
// Compound assignment on method_set (free functions to match the
203+
// non-member binary operators above).
204+
constexpr method_set& operator|=(method_set& s, method_set rhs) noexcept {
205+
s.bits |= rhs.bits;
206+
return s;
207+
}
208+
209+
constexpr method_set& operator&=(method_set& s, method_set rhs) noexcept {
210+
s.bits &= rhs.bits;
211+
return s;
212+
}
213+
214+
constexpr method_set& operator^=(method_set& s, method_set rhs) noexcept {
215+
s.bits ^= rhs.bits;
216+
return s;
217+
}
218+
219+
constexpr method_set& operator|=(method_set& s, http_method m) noexcept {
220+
s.bits |= detail::method_bit(m);
221+
return s;
222+
}
223+
224+
constexpr method_set& operator&=(method_set& s, http_method m) noexcept {
225+
s.bits &= detail::method_bit(m);
226+
return s;
227+
}
228+
229+
constexpr method_set& operator^=(method_set& s, http_method m) noexcept {
230+
s.bits ^= detail::method_bit(m);
231+
return s;
232+
}
233+
234+
// Layout / width invariants — pinned once at namespace scope so every
235+
// TU including this header gets the protection. Placed AFTER the
236+
// method_set definition so is_standard_layout_v / sizeof are well-formed.
237+
static_assert(static_cast<std::uint8_t>(http_method::count_) <= 32,
238+
"http_method::count_ must fit in method_set's 32-bit bitmask");
239+
static_assert(std::is_standard_layout_v<method_set>,
240+
"method_set must be standard layout");
241+
static_assert(std::is_trivially_copyable_v<method_set>,
242+
"method_set must be trivially copyable");
243+
static_assert(sizeof(method_set) == sizeof(std::uint32_t),
244+
"method_set must be exactly the size of its underlying uint32_t");
245+
246+
} // namespace httpserver
247+
#endif // SRC_HTTPSERVER_HTTP_METHOD_HPP_

test/Makefile.am

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ LDADD += -lcurl
2626

2727
AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ -DHTTPSERVER_COMPILATION
2828
METASOURCES = AUTO
29-
check_PROGRAMS = basic file_upload http_utils threaded nodelay string_utilities http_endpoint ban_system ws_start_stop authentication deferred http_resource http_response create_webserver new_response_types daemon_info uri_log feature_unavailable header_hygiene_iovec iovec_entry iovec_response
29+
check_PROGRAMS = basic file_upload http_utils threaded nodelay string_utilities http_endpoint ban_system ws_start_stop authentication deferred http_resource http_response create_webserver new_response_types daemon_info uri_log feature_unavailable header_hygiene_iovec iovec_entry iovec_response http_method
3030

3131
MOSTLYCLEANFILES = *.gcda *.gcno *.gcov
3232

@@ -55,6 +55,7 @@ feature_unavailable_SOURCES = unit/feature_unavailable_test.cpp
5555
header_hygiene_iovec_SOURCES = unit/header_hygiene_iovec_test.cpp
5656
iovec_entry_SOURCES = unit/iovec_entry_test.cpp
5757
iovec_response_SOURCES = unit/iovec_response_test.cpp
58+
http_method_SOURCES = unit/http_method_test.cpp
5859

5960
noinst_HEADERS = littletest.hpp
6061
AM_CXXFLAGS += -Wall -fPIC -Wno-overloaded-virtual

0 commit comments

Comments
 (0)