I2: SO_RCVTIMEO=5s war ein Timeout pro recv(), nicht für die gesamte Anfrage. Ein Client, der 1 Byte alle 4,9 s schickt, konnte den einzigen HTTP-Server-Task damit stundenlang belegen (Spec §7.2 will 5 s pro Header). serve() rechnet jetzt eine absolute Deadline (nl_app_now_ms() + 5000 ms) und setzt SO_RCVTIMEO vor jedem recv() auf die verbleibende Zeit (mindestens 1 ms); ist die Deadline überschritten, wird die Verbindung sofort verworfen. Nicht host-testbar (nl_http_server.c bindet SDK-Sockets ein); über den Firmware-Build verifiziert.
122 lines
3.7 KiB
C
122 lines
3.7 KiB
C
#include <string.h>
|
|
|
|
#include "wm_include.h"
|
|
#include "wm_sockets.h"
|
|
|
|
#include "net/nl_http.h"
|
|
#include "net/nl_http_server.h"
|
|
#include "sys/nl_app.h"
|
|
|
|
#define NL_HTTP_PRIO 42
|
|
|
|
static u32 s_stack[1536]; /* 6 KB: cJSON + API handler */
|
|
static char s_buf[NL_HTTP_MAX_REQUEST];
|
|
static char s_out[NL_API_BODY_MAX + 160];
|
|
static nl_api_response_t s_resp;
|
|
static const nl_api_ctx_t *s_ctx;
|
|
|
|
static void send_all(int fd, const char *p, size_t n)
|
|
{
|
|
while (n) {
|
|
int w = send(fd, p, n, 0);
|
|
if (w <= 0)
|
|
return;
|
|
p += w;
|
|
n -= (size_t)w;
|
|
}
|
|
}
|
|
|
|
static void simple_error(int status, const char *msg)
|
|
{
|
|
s_resp.status = status;
|
|
s_resp.after = NL_API_AFTER_NONE;
|
|
strcpy(s_resp.body, msg);
|
|
}
|
|
|
|
#define NL_HTTP_REQUEST_TIMEOUT_MS 5000u
|
|
|
|
static void serve(int fd)
|
|
{
|
|
struct timeval tv;
|
|
nl_http_request_t req;
|
|
nl_http_parse_t pr = NL_HTTP_INCOMPLETE;
|
|
size_t len = 0, out;
|
|
uint32_t deadline = nl_app_now_ms() + NL_HTTP_REQUEST_TIMEOUT_MS;
|
|
|
|
tv.tv_sec = 5;
|
|
tv.tv_usec = 0;
|
|
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
|
|
/* I2: 5 s is a deadline for the whole header+body read, not a per-recv() timeout -
|
|
* otherwise a client trickling in 1 byte per <5 s occupies the single server task forever. */
|
|
while (pr == NL_HTTP_INCOMPLETE && len < sizeof s_buf) {
|
|
uint32_t now = nl_app_now_ms();
|
|
uint32_t remaining_ms;
|
|
int n;
|
|
|
|
if ((int32_t)(deadline - now) <= 0)
|
|
return; /* overall deadline passed: drop silently */
|
|
remaining_ms = deadline - now;
|
|
tv.tv_sec = remaining_ms / 1000u;
|
|
tv.tv_usec = (remaining_ms % 1000u) * 1000u;
|
|
if (tv.tv_sec == 0 && tv.tv_usec == 0)
|
|
tv.tv_usec = 1000; /* at least 1 ms */
|
|
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
|
|
n = recv(fd, s_buf + len, sizeof s_buf - len, 0);
|
|
if (n <= 0)
|
|
return; /* timeout or closed: drop silently */
|
|
len += (size_t)n;
|
|
pr = nl_http_parse(s_buf, len, &req);
|
|
}
|
|
if (pr == NL_HTTP_COMPLETE)
|
|
nl_api_handle(s_ctx, &req, &s_resp);
|
|
else if (pr == NL_HTTP_BAD_REQUEST)
|
|
simple_error(400, "{\"error\":\"bad request\"}");
|
|
else
|
|
simple_error(413, "{\"error\":\"request too large\"}");
|
|
out = nl_http_response(s_out, sizeof s_out, s_resp.status, s_resp.body);
|
|
if (out)
|
|
send_all(fd, s_out, out);
|
|
}
|
|
|
|
static void server_task(void *arg)
|
|
{
|
|
(void)arg;
|
|
for (;;) {
|
|
struct sockaddr_in addr;
|
|
int ls = socket(AF_INET, SOCK_STREAM, 0);
|
|
|
|
memset(&addr, 0, sizeof addr);
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(NL_HTTP_PORT);
|
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
if (ls < 0 || bind(ls, (struct sockaddr *)&addr, sizeof addr) != 0 || listen(ls, 4) != 0) {
|
|
if (ls >= 0)
|
|
closesocket(ls);
|
|
tls_os_time_delay(HZ);
|
|
continue;
|
|
}
|
|
for (;;) {
|
|
struct sockaddr_in peer;
|
|
socklen_t plen = sizeof peer;
|
|
int fd = accept(ls, (struct sockaddr *)&peer, &plen);
|
|
if (fd < 0) {
|
|
tls_os_time_delay(HZ / 10);
|
|
continue;
|
|
}
|
|
s_resp.after = NL_API_AFTER_NONE;
|
|
serve(fd);
|
|
closesocket(fd);
|
|
if (s_resp.after == NL_API_AFTER_REBOOT) {
|
|
tls_os_time_delay(HZ / 2); /* let the TCP stack flush the response */
|
|
nl_app_reboot();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void nl_http_server_start(const nl_api_ctx_t *ctx)
|
|
{
|
|
s_ctx = ctx;
|
|
tls_os_task_create(NULL, "nl_http", server_task, NULL, (u8 *)s_stack, sizeof(s_stack), NL_HTTP_PRIO, 0);
|
|
}
|