Skip to content

Commit d3abf40

Browse files
committed
Refactor HTTP daemon handler storage
Move `HttpDaemon` initialization and request dispatching into a new `httpd.cpp` implementation file, and replace the header-defined handler array with explicitly managed raw storage. This keeps the HTTP handler table in the dedicated linker section while avoiding direct static object construction in the header and clarifying the lifetime and ownership of per-connection request handlers.
1 parent 10d42f0 commit d3abf40

2 files changed

Lines changed: 126 additions & 34 deletions

File tree

lib-remoteconfig/include/httpd/httpd.h

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,12 @@
3030
#define HTTPD_HTTPD_H_
3131

3232
#include <cstdint>
33-
#include <cassert>
34-
#include <new>
3533

3634
#include "core/protocol/iana.h"
3735
#include "httpdhandlerequest.h"
3836
#include "network_tcp.h"
3937
#include "apps/mdns.h"
4038
#include "../../lib-network/config/net_config.h"
41-
#include "firmware/debug/debug_debug.h"
4239

4340
namespace httpd {
4441
inline constexpr auto kPort =
@@ -57,41 +54,34 @@ inline constexpr auto kService =
5754

5855
class HttpDaemon {
5956
public:
60-
HttpDaemon() {
61-
DEBUG_ENTRY();
62-
assert(is_listening_ == false);
63-
64-
is_listening_ = network::tcp::Listen(httpd::kPort, Data);
65-
assert(is_listening_ == true);
66-
67-
// IMPORTANT:
68-
// Connection handles are GLOBAL indices into s_Tcbs[].
69-
// Therefore the HTTP request handler table must also be global-sized.
70-
for (uint32_t i = 0; i < TCP_MAX_TCBS_ALLOWED; ++i) {
71-
// Each HttpDeamonHandleRequest corresponds to ONE possible TCB slot.
72-
// It can be addressed directly by conn_handle.
73-
new (&s_handle_request[i]) HttpDeamonHandleRequest(i);
74-
}
75-
76-
network::apps::mdns::ServiceRecordAdd(nullptr, httpd::kService);
77-
78-
DEBUG_EXIT();
79-
}
57+
HttpDaemon();
8058

59+
// The daemon and its handlers live for the lifetime of the firmware.
8160
~HttpDaemon() = default;
8261

83-
private:
84-
static void Data(network::tcp::ConnHandle conn_handle, const uint8_t* buffer, uint32_t size, [[maybe_unused]] void* context) {
85-
assert(conn_handle < TCP_MAX_TCBS_ALLOWED);
86-
s_handle_request[conn_handle].HandleRequest(size, const_cast<char*>(reinterpret_cast<const char*>(buffer)));
87-
}
62+
HttpDaemon(const HttpDaemon&) = delete;
63+
HttpDaemon& operator=(const HttpDaemon&) = delete;
64+
HttpDaemon(HttpDaemon&&) = delete;
65+
HttpDaemon& operator=(HttpDaemon&&) = delete;
8866

89-
#if defined(GD32F207RG) || defined(GD32F450VE) || defined(GD32F470ZK)
90-
#define SECTION_HTTPD __attribute__((section(".httpd")))
91-
#else
92-
#define SECTION_HTTPD
93-
#endif
94-
static inline HttpDeamonHandleRequest s_handle_request[TCP_MAX_TCBS_ALLOWED] __attribute__((aligned(4))) SECTION_HTTPD;
67+
private:
68+
/**
69+
* Raw storage for one HttpDeamonHandleRequest.
70+
*
71+
* The storage itself has trivial initialization, so it can safely be
72+
* placed in a NOLOAD linker section. The actual handler object is created
73+
* explicitly with placement new in HttpDaemon::HttpDaemon().
74+
*/
75+
struct HandlerStorage {
76+
alignas(HttpDeamonHandleRequest) unsigned char data[sizeof(HttpDeamonHandleRequest)];
77+
};
78+
79+
[[nodiscard]]
80+
static HttpDeamonHandleRequest& GetHandler(uint32_t index);
81+
82+
static void Data(network::tcp::ConnHandle conn_handle, const uint8_t* buffer, uint32_t size, void* context);
83+
84+
static HandlerStorage s_handle_request_storage[TCP_MAX_TCBS_ALLOWED];
9585

9686
bool is_listening_{false};
9787
};
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @file httpd.cpp
3+
* @brief HTTP daemon class for managing HTTP server tasks.
4+
*
5+
* This class handles HTTP requests and integrates with the network and mDNS subsystems.
6+
* It uses placement new to construct and destruct request handlers explicitly.
7+
*/
8+
/* Copyright (C) 2025-2026 by Arjan van Vught mailto:info@gd32-dmx.org
9+
*
10+
* Permission is hereby granted, free of charge, to any person obtaining a copy
11+
* of this software and associated documentation files (the "Software"), to deal
12+
* in the Software without restriction, including without limitation the rights
13+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14+
* copies of the Software, and to permit persons to whom the Software is
15+
* furnished to do so, subject to the following conditions:
16+
17+
* The above copyright notice and this permission notice shall be included in
18+
* all copies or substantial portions of the Software.
19+
20+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26+
* THE SOFTWARE.
27+
*/
28+
29+
#include <cassert>
30+
#include <new>
31+
32+
#include "firmware/debug/debug_debug.h"
33+
#include "httpd/httpd.h"
34+
#include "network_tcp.h"
35+
#include "apps/mdns.h"
36+
#include "../../lib-network/config/net_config.h"
37+
38+
/*
39+
* Define the storage exactly once, outside the header.
40+
*
41+
* gnu::used:
42+
* Ensures that the compiler emits the object.
43+
*
44+
* .httpd:
45+
* Is collected by the linker script into the dedicated HTTP RAM section.
46+
*
47+
* The linker script should contain:
48+
*
49+
* .http (NOLOAD) :
50+
* {
51+
* . = ALIGN(4);
52+
* _shttp = .;
53+
* KEEP(*(.http))
54+
* KEEP(*(.http.*))
55+
* KEEP(*(.httpd))
56+
* KEEP(*(.httpd.*))
57+
* . = ALIGN(4);
58+
* _ehttp = .;
59+
* } > RAM2
60+
*/
61+
[[gnu::section(".httpd"), gnu::aligned(alignof(HttpDeamonHandleRequest)), gnu::used]]
62+
HttpDaemon::HandlerStorage HttpDaemon::s_handle_request_storage[TCP_MAX_TCBS_ALLOWED];
63+
64+
HttpDaemon::HttpDaemon() {
65+
DEBUG_ENTRY();
66+
67+
assert(!is_listening_);
68+
69+
is_listening_ = network::tcp::Listen(httpd::kPort, Data);
70+
assert(is_listening_);
71+
72+
/*
73+
* Connection handles are global indices into s_Tcbs[].
74+
* Therefore the request-handler table must have one entry for every
75+
* possible global TCB slot.
76+
*/
77+
for (uint32_t i = 0; i < TCP_MAX_TCBS_ALLOWED; ++i) {
78+
// Construct exactly one handler in each raw-storage slot.
79+
::new (static_cast<void*>(s_handle_request_storage[i].data)) HttpDeamonHandleRequest(i);
80+
}
81+
82+
network::apps::mdns::ServiceRecordAdd(nullptr, httpd::kService);
83+
84+
DEBUG_EXIT();
85+
}
86+
87+
HttpDeamonHandleRequest& HttpDaemon::GetHandler(uint32_t index) {
88+
assert(index < TCP_MAX_TCBS_ALLOWED);
89+
90+
/*
91+
* std::launder is appropriate after constructing an object in raw storage
92+
* and subsequently obtaining a pointer through the storage address.
93+
*/
94+
return *std::launder(reinterpret_cast<HttpDeamonHandleRequest*>(s_handle_request_storage[index].data));
95+
}
96+
97+
void HttpDaemon::Data(network::tcp::ConnHandle conn_handle, const uint8_t* buffer, uint32_t size, [[maybe_unused]] void* context) {
98+
assert(conn_handle < TCP_MAX_TCBS_ALLOWED);
99+
assert(buffer != nullptr);
100+
101+
GetHandler(conn_handle).HandleRequest(size, const_cast<char*>(reinterpret_cast<const char*>(buffer)));
102+
}

0 commit comments

Comments
 (0)