Skip to content

Commit 36540eb

Browse files
committed
Add the option to add/remove multiple headers with the same name
There are some exceptions that are always allowed only once
1 parent 673431e commit 36540eb

File tree

5 files changed

+108
-4
lines changed

5 files changed

+108
-4
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,13 +577,13 @@ request->multipart(); // bool: True if the request has content type "mult
577577
int headers = request->headers();
578578
int i;
579579
for(i=0;i<headers;i++){
580-
AsyncWebHeader* h = request->getHeader(i);
580+
const AsyncWebHeader* h = request->getHeader(i);
581581
Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
582582
}
583583

584584
//get specific header by name
585585
if(request->hasHeader("MyHeader")){
586-
AsyncWebHeader* h = request->getHeader("MyHeader");
586+
const AsyncWebHeader* h = request->getHeader("MyHeader");
587587
Serial.printf("MyHeader: %s\n", h->value().c_str());
588588
}
589589

examples/Headers/Headers.ino

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
3+
4+
//
5+
// Query and send headers
6+
//
7+
8+
#include <Arduino.h>
9+
#ifdef ESP32
10+
#include <AsyncTCP.h>
11+
#include <WiFi.h>
12+
#elif defined(ESP8266)
13+
#include <ESP8266WiFi.h>
14+
#include <ESPAsyncTCP.h>
15+
#elif defined(TARGET_RP2040)
16+
#include <WebServer.h>
17+
#include <WiFi.h>
18+
#endif
19+
20+
#include <ESPAsyncWebServer.h>
21+
22+
static AsyncWebServer server(80);
23+
24+
void setup() {
25+
Serial.begin(115200);
26+
27+
#ifndef CONFIG_IDF_TARGET_ESP32H2
28+
WiFi.mode(WIFI_AP);
29+
WiFi.softAP("esp-captive");
30+
#endif
31+
32+
// Get query parameters
33+
//
34+
// curl -v http://192.168.4.1
35+
//
36+
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
37+
//List all collected headers
38+
int headers = request->headers();
39+
int i;
40+
for (i = 0; i < headers; i++) {
41+
const AsyncWebHeader *h = request->getHeader(i);
42+
Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
43+
}
44+
45+
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!");
46+
47+
//Add header to the response
48+
response->addHeader("Server", "ESP Async Web Server");
49+
50+
//Add multiple headers with the same name
51+
response->addHeader("Set-Cookie", "sessionId=38afes7a8", false);
52+
response->addHeader("Set-Cookie", "id=a3fWa; Max-Age=2592000", false);
53+
response->addHeader("Set-Cookie", "qwerty=219ffwef9w0f; Domain=example.com", false);
54+
55+
//Remove specific header
56+
response->removeHeader("Set-Cookie", "sessionId=38afes7a8");
57+
58+
//Remove all headers with the same name
59+
response->removeHeader("Set-Cookie");
60+
61+
request->send(response);
62+
});
63+
64+
server.begin();
65+
}
66+
67+
void loop() {
68+
//Sleep in the loop task to not keep the CPU busy
69+
delay(1000);
70+
}

src/ESPAsyncWebServer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,8 @@ class AsyncWebServerResponse {
953953
size_t _writtenLength;
954954
WebResponseState _state;
955955

956+
static bool headerMustBePresentOnce(const String &name);
957+
956958
public:
957959
static const char *responseCodeToString(int code);
958960

@@ -979,6 +981,7 @@ class AsyncWebServerResponse {
979981
return addHeader(name.c_str(), value, replaceExisting);
980982
}
981983
bool removeHeader(const char *name);
984+
bool removeHeader(const char *name, const char *value);
982985
const AsyncWebHeader *getHeader(const char *name) const;
983986
const std::list<AsyncWebHeader> &getHeaders() const {
984987
return _headers;

src/WebResponses.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,21 @@ void AsyncWebServerResponse::setContentType(const char *type) {
9696
}
9797

9898
bool AsyncWebServerResponse::removeHeader(const char *name) {
99-
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
99+
bool h_erased = false;
100+
for (auto i = _headers.begin(); i != _headers.end();) {
100101
if (i->name().equalsIgnoreCase(name)) {
102+
_headers.erase(i);
103+
h_erased = true;
104+
} else {
105+
++i;
106+
}
107+
}
108+
return h_erased;
109+
}
110+
111+
bool AsyncWebServerResponse::removeHeader(const char *name, const char *value) {
112+
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
113+
if (i->name().equalsIgnoreCase(name) && i->value().equalsIgnoreCase(value)) {
101114
_headers.erase(i);
102115
return true;
103116
}
@@ -112,6 +125,15 @@ const AsyncWebHeader *AsyncWebServerResponse::getHeader(const char *name) const
112125
return (iter == std::end(_headers)) ? nullptr : &(*iter);
113126
}
114127

128+
bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
129+
for (uint8_t i = 0; i < T_only_once_headers_len; i++) {
130+
if (name.equalsIgnoreCase(T_only_once_headers[i])) {
131+
return true;
132+
}
133+
}
134+
return false;
135+
}
136+
115137
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
116138
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
117139
if (i->name().equalsIgnoreCase(name)) {
@@ -120,9 +142,11 @@ bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool
120142
// remove, break and add the new one
121143
_headers.erase(i);
122144
break;
123-
} else {
145+
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
124146
// do not update
125147
return false;
148+
} else {
149+
break; // accept multiple headers with the same name
126150
}
127151
}
128152
}

src/literals.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ static constexpr const char *T_Content_Disposition = "content-disposition";
2828
static constexpr const char *T_Content_Encoding = "content-encoding";
2929
static constexpr const char *T_Content_Length = "content-length";
3030
static constexpr const char *T_Content_Type = "content-type";
31+
static constexpr const char *T_Content_Location = "content-location";
3132
static constexpr const char *T_Cookie = "cookie";
3233
static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials";
3334
static constexpr const char *T_CORS_ACAH = "access-control-allow-headers";
@@ -36,6 +37,7 @@ static constexpr const char *T_CORS_ACAO = "access-control-allow-origin";
3637
static constexpr const char *T_CORS_ACMA = "access-control-max-age";
3738
static constexpr const char *T_CORS_O = "origin";
3839
static constexpr const char *T_data_ = "data: ";
40+
static constexpr const char *T_Date = "date";
3941
static constexpr const char *T_DIGEST = "digest";
4042
static constexpr const char *T_DIGEST_ = "digest ";
4143
static constexpr const char *T_ETag = "etag";
@@ -71,6 +73,7 @@ static constexpr const char *T_retry_after = "retry-after";
7173
static constexpr const char *T_nn = "\n\n";
7274
static constexpr const char *T_rn = "\r\n";
7375
static constexpr const char *T_rnrn = "\r\n\r\n";
76+
static constexpr const char *T_Server = "server";
7477
static constexpr const char *T_Transfer_Encoding = "transfer-encoding";
7578
static constexpr const char *T_TRUE = "true";
7679
static constexpr const char *T_UPGRADE = "upgrade";
@@ -183,4 +186,8 @@ static constexpr const char *T_HTTP_CODE_504 = "Gateway Time-out";
183186
static constexpr const char *T_HTTP_CODE_505 = "HTTP Version not supported";
184187
static constexpr const char *T_HTTP_CODE_ANY = "Unknown code";
185188

189+
static constexpr const uint8_t T_only_once_headers_len = 11;
190+
static constexpr const char *T_only_once_headers[] = {T_Content_Length, T_Content_Type, T_Date, T_ETag, T_Last_Modified, T_LOCATION, T_retry_after,
191+
T_Transfer_Encoding, T_Content_Location, T_Server, T_WWW_AUTH};
192+
186193
} // namespace asyncsrv

0 commit comments

Comments
 (0)