Skip to content

Commit 13f0818

Browse files
etrclaude
andcommitted
TASK-008: Internal detail::body hierarchy
Adds the polymorphic body hierarchy that http_response's SBO buffer will host (TASK-009) and the public body_kind enum that http_response::kind() will return (TASK-011). TASK-008 ships only the standalone hierarchy: each subclass is independently constructible, destructible, and materializable, mirroring the corresponding v1 *_response::get_raw_response. New public header (umbrella-included): - httpserver/body_kind.hpp: enum class body_kind : std::uint8_t { empty, string, file, iovec, pipe, deferred }; empty=0 so a value-initialised body_kind matches the no-body state. New private header (HTTPSERVER_COMPILATION-only, never installed): - httpserver/details/body.hpp: abstract detail::body + 6 final subclasses (empty_body, string_body, file_body, iovec_body, pipe_body, deferred_body) plus per-subclass static_assert(sizeof <= 64) and static_assert(alignof(deferred_body) <= 16) for the SBO budget (DR-005). Out-of-line definitions in src/details/body.cpp: - materialize() per subclass mirrors v1 byte-for-byte (string=PERSISTENT, file=open/fstat/lseek/from_fd, iovec=CWE-190 guard + reinterpret_cast to MHD_IoVec, pipe=from_pipe, deferred= from_callback with a static trampoline). - Layout-pinning static_asserts duplicated from iovec_response.cpp (TASK-013 will remove the originals). - pipe_body::~pipe_body() closes fd_ only if materialize() was never called (MHD owns it after a successful materialise). New test: - test/unit/body_test.cpp drives every subclass through MHD's daemon-independent inspection APIs (no daemon spun up). 12 tests, 29 checks; the deferred trampoline is exposed as a public static so it can be unit-tested directly. Linked with explicit -lmicrohttpd (mirrors uri_log). Observed sizes on libc++/arm64: empty=16, string=32, file=40, iovec=40, pipe=16, deferred=40. All well under the 64 B SBO budget — TASK-010 will not need the heap-fallback branch on supported toolchains. Out of scope (TASK-009/010): http_response wiring, body_inline_ fallback, kind() accessor, removal of v1 *_response subclasses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1228e20 commit 13f0818

7 files changed

Lines changed: 777 additions & 4 deletions

File tree

src/Makefile.am

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
AM_CPPFLAGS = -I../ -I$(srcdir)/httpserver/ -DHTTPSERVER_COMPILATION
2020
METASOURCES = AUTO
2121
lib_LTLIBRARIES = libhttpserver.la
22-
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp string_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp pipe_response.cpp empty_response.cpp iovec_response.cpp http_resource.cpp create_webserver.cpp details/http_endpoint.cpp
22+
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp string_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp pipe_response.cpp empty_response.cpp iovec_response.cpp http_resource.cpp create_webserver.cpp details/http_endpoint.cpp details/body.cpp
2323
# noinst_HEADERS: shipped in the tarball but NEVER installed under $prefix/include.
2424
# Detail headers (httpserver/details/*.hpp) live here so they cannot leak to
2525
# downstream consumers — the public surface comes in through <httpserver.hpp>.
26-
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/constants.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
26+
noinst_HEADERS = httpserver/string_utilities.hpp httpserver/details/modded_request.hpp httpserver/details/http_endpoint.hpp httpserver/details/body.hpp gettext.h
27+
nobase_include_HEADERS = httpserver.hpp httpserver/body_kind.hpp httpserver/constants.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/details/body.cpp

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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+
#include "httpserver/details/body.hpp"
22+
23+
#include <fcntl.h>
24+
#include <sys/stat.h>
25+
#include <sys/types.h>
26+
#include <sys/uio.h>
27+
#include <unistd.h>
28+
29+
#include <cstddef>
30+
#include <cstdint>
31+
#include <limits>
32+
#include <type_traits>
33+
#include <utility>
34+
35+
#include <microhttpd.h>
36+
37+
namespace httpserver {
38+
39+
namespace detail {
40+
41+
// ---------------------------------------------------------------------------
42+
// Layout-pinning static_asserts for iovec_entry → MHD_IoVec / struct iovec.
43+
// Duplicated from src/iovec_response.cpp during the M2 transition: the
44+
// asserts must live next to every cast site, and TASK-013 will delete
45+
// iovec_response.cpp once http_response::iovec() lands. Duplicate
46+
// static_asserts on identical layouts are harmless.
47+
//
48+
// LIBHTTPSERVER_TODO_TASK013: drop the originals from iovec_response.cpp
49+
// when iovec_response is removed.
50+
// ---------------------------------------------------------------------------
51+
static_assert(sizeof(::httpserver::iovec_entry) == sizeof(struct iovec),
52+
"iovec_entry size must match POSIX struct iovec — divergent platform; "
53+
"implement memcpy fallback (see TASK-004)");
54+
static_assert(offsetof(::httpserver::iovec_entry, base) ==
55+
offsetof(struct iovec, iov_base),
56+
"iovec_entry::base offset must match struct iovec::iov_base");
57+
static_assert(offsetof(::httpserver::iovec_entry, len) ==
58+
offsetof(struct iovec, iov_len),
59+
"iovec_entry::len offset must match struct iovec::iov_len");
60+
61+
static_assert(sizeof(::httpserver::iovec_entry) == sizeof(MHD_IoVec),
62+
"iovec_entry size must match libmicrohttpd MHD_IoVec — MHD layout drift");
63+
static_assert(offsetof(::httpserver::iovec_entry, base) ==
64+
offsetof(MHD_IoVec, iov_base),
65+
"iovec_entry::base offset must match MHD_IoVec::iov_base");
66+
static_assert(offsetof(::httpserver::iovec_entry, len) ==
67+
offsetof(MHD_IoVec, iov_len),
68+
"iovec_entry::len offset must match MHD_IoVec::iov_len");
69+
70+
static_assert(alignof(::httpserver::iovec_entry) == alignof(struct iovec),
71+
"iovec_entry alignment must match POSIX struct iovec — divergent platform; "
72+
"implement memcpy fallback (see TASK-004)");
73+
static_assert(alignof(::httpserver::iovec_entry) == alignof(MHD_IoVec),
74+
"iovec_entry alignment must match MHD_IoVec — MHD layout drift");
75+
76+
static_assert(std::is_standard_layout_v<::httpserver::iovec_entry>,
77+
"iovec_entry must be standard layout for reinterpret_cast to MHD_IoVec");
78+
79+
// ---------------------------------------------------------------------------
80+
// body — virtual destructor anchor (forces vtable emission in this TU).
81+
// ---------------------------------------------------------------------------
82+
body::~body() = default;
83+
84+
// ---------------------------------------------------------------------------
85+
// empty_body
86+
// ---------------------------------------------------------------------------
87+
MHD_Response* empty_body::materialize() {
88+
return MHD_create_response_empty(static_cast<MHD_ResponseFlags>(flags_));
89+
}
90+
91+
// ---------------------------------------------------------------------------
92+
// string_body
93+
// ---------------------------------------------------------------------------
94+
MHD_Response* string_body::materialize() {
95+
// PERSISTENT, not MUST_COPY: content_ is owned by *this and outlives the
96+
// returned MHD_Response (TASK-009 anchors the lifetime). This matches v1
97+
// string_response::get_raw_response.
98+
return MHD_create_response_from_buffer(
99+
content_.size(),
100+
const_cast<void*>(static_cast<const void*>(content_.data())),
101+
MHD_RESPMEM_PERSISTENT);
102+
}
103+
104+
// ---------------------------------------------------------------------------
105+
// file_body — replicates v1 file_response::get_raw_response exactly.
106+
// ---------------------------------------------------------------------------
107+
MHD_Response* file_body::materialize() {
108+
#ifndef _WIN32
109+
int fd = ::open(path_.c_str(), O_RDONLY | O_NOFOLLOW);
110+
#else
111+
int fd = ::open(path_.c_str(), O_RDONLY);
112+
#endif
113+
if (fd == -1) return nullptr;
114+
115+
struct stat sb;
116+
if (::fstat(fd, &sb) != 0 || !S_ISREG(sb.st_mode)) {
117+
::close(fd);
118+
return nullptr;
119+
}
120+
121+
off_t size = ::lseek(fd, 0, SEEK_END);
122+
if (size == static_cast<off_t>(-1)) {
123+
::close(fd);
124+
return nullptr;
125+
}
126+
127+
if (size) {
128+
size_cached_ = static_cast<std::size_t>(size);
129+
return MHD_create_response_from_fd(
130+
static_cast<std::size_t>(size), fd);
131+
}
132+
::close(fd);
133+
size_cached_ = 0;
134+
return MHD_create_response_from_buffer(
135+
0, nullptr, MHD_RESPMEM_PERSISTENT);
136+
}
137+
138+
// ---------------------------------------------------------------------------
139+
// iovec_body
140+
// ---------------------------------------------------------------------------
141+
MHD_Response* iovec_body::materialize() {
142+
// CWE-190 guard preserved from v1 iovec_response::get_raw_response.
143+
if (entries_.size() >
144+
static_cast<std::size_t>(
145+
std::numeric_limits<unsigned int>::max())) {
146+
return nullptr;
147+
}
148+
return MHD_create_response_from_iovec(
149+
reinterpret_cast<const MHD_IoVec*>(entries_.data()),
150+
static_cast<unsigned int>(entries_.size()),
151+
nullptr,
152+
nullptr);
153+
}
154+
155+
// ---------------------------------------------------------------------------
156+
// pipe_body
157+
// ---------------------------------------------------------------------------
158+
pipe_body::~pipe_body() {
159+
// Only close if MHD never took ownership. After a successful
160+
// materialize(), libmicrohttpd closes fd_ when the MHD_Response is
161+
// destroyed.
162+
if (!materialized_ && fd_ != -1) {
163+
::close(fd_);
164+
}
165+
}
166+
167+
MHD_Response* pipe_body::materialize() {
168+
MHD_Response* r = MHD_create_response_from_pipe(fd_);
169+
if (r != nullptr) {
170+
materialized_ = true; // MHD now owns fd_
171+
}
172+
return r;
173+
}
174+
175+
// ---------------------------------------------------------------------------
176+
// deferred_body — trampoline + materialize.
177+
// ---------------------------------------------------------------------------
178+
ssize_t deferred_body::trampoline(void* cls, std::uint64_t pos,
179+
char* buf, std::size_t max) {
180+
auto* self = static_cast<deferred_body*>(cls);
181+
return self->producer_(pos, buf, max);
182+
}
183+
184+
MHD_Response* deferred_body::materialize() {
185+
// Block size 1024 mirrors v1 deferred_response::get_raw_response_helper.
186+
// Free-callback is nullptr because *this owns producer_ and outlives the
187+
// MHD_Response (TASK-009 enforces this via http_response's lifetime).
188+
return MHD_create_response_from_callback(
189+
MHD_SIZE_UNKNOWN, 1024, &deferred_body::trampoline, this, nullptr);
190+
}
191+
192+
} // namespace detail
193+
194+
} // namespace httpserver

src/httpserver.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#ifdef HAVE_BAUTH
3131
#include "httpserver/basic_auth_fail_response.hpp"
3232
#endif // HAVE_BAUTH
33+
#include "httpserver/body_kind.hpp"
3334
#include "httpserver/constants.hpp"
3435
#include "httpserver/deferred_response.hpp"
3536
#ifdef HAVE_DAUTH

src/httpserver/body_kind.hpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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_BODY_KIND_HPP_
26+
#define SRC_HTTPSERVER_BODY_KIND_HPP_
27+
28+
#include <cstdint>
29+
30+
namespace httpserver {
31+
32+
// Tag identifying which subclass of detail::body a given http_response is
33+
// currently holding. Consumers reach this through http_response::kind()
34+
// (TASK-011) and should never have to name detail::body directly — the
35+
// enum is the only consumer-visible part of the body hierarchy.
36+
//
37+
// `empty` is enumerator 0 so a value-initialised body_kind{} matches the
38+
// "no body" state, which is what TASK-009's default-constructed
39+
// http_response will report.
40+
//
41+
// Underlying type is pinned to std::uint8_t so that future additions
42+
// stay within a single byte and do not silently grow http_response. The
43+
// fixed underlying type also makes the enum forward-declarable, although
44+
// http_response.hpp will still pull in this full header (consumers will
45+
// name the enumerators).
46+
enum class body_kind : std::uint8_t {
47+
empty,
48+
string,
49+
file,
50+
iovec,
51+
pipe,
52+
deferred,
53+
};
54+
55+
} // namespace httpserver
56+
#endif // SRC_HTTPSERVER_BODY_KIND_HPP_

0 commit comments

Comments
 (0)