Merge pull request #88 from mathieucarbou/issue-86

Fix header handling versus internal handling of content-type and content-length
This commit is contained in:
Mathieu Carbou
2024-09-03 11:43:35 +02:00
committed by GitHub
6 changed files with 75 additions and 38 deletions

View File

@@ -73,8 +73,11 @@ void setup() {
server.on("/download", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest* request) {
if (request->method() == HTTP_HEAD) {
AsyncWebServerResponse* response = request->beginResponse(200, "application/octet-stream");
response->setContentLength(1024); // myFile.getSize()
response->addHeader("Accept-Ranges", "bytes");
response->addHeader(asyncsrv::T_Accept_Ranges, "bytes");
response->addHeader(asyncsrv::T_Content_Length, 10);
response->setContentLength(1024); // overrides previous one
response->addHeader(asyncsrv::T_Content_Type, "foo");
response->setContentType("application/octet-stream"); // overrides previous one
// ...
request->send(response);
} else {

View File

@@ -392,7 +392,8 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
}
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) {
String out = _assembleHead(request->version());
String out;
_assembleHead(out, request->version());
request->client()->write(out.c_str(), _headLength);
_state = RESPONSE_WAIT_ACK;
}

View File

@@ -1192,7 +1192,8 @@ void AsyncWebSocketResponse::_respond(AsyncWebServerRequest* request) {
request->client()->close(true);
return;
}
String out(_assembleHead(request->version()));
String out;
_assembleHead(out, request->version());
request->client()->write(out.c_str(), _headLength);
_state = RESPONSE_WAIT_ACK;
}

View File

@@ -155,7 +155,9 @@ class AsyncWebHeader {
const String& name() const { return _name; }
const String& value() const { return _value; }
String toString() const {
String str = _name;
String str;
str.reserve(_name.length() + _value.length() + 2);
str.concat(_name);
str.concat((char)0x3a);
str.concat((char)0x20);
str.concat(_value);
@@ -583,8 +585,19 @@ class AsyncWebServerResponse {
virtual void setContentType(const char* type);
virtual bool addHeader(const char* name, const char* value, bool replaceExisting = true);
bool addHeader(const String& name, const String& value, bool replaceExisting = true) { return addHeader(name.c_str(), value.c_str(), replaceExisting); }
bool addHeader(const char* name, long value, bool replaceExisting = true) { return addHeader(name, String(value), replaceExisting); }
bool addHeader(const String& name, long value, bool replaceExisting = true) { return addHeader(name.c_str(), value, replaceExisting); }
virtual bool removeHeader(const char* name);
virtual String _assembleHead(uint8_t version);
virtual const AsyncWebHeader* getHeader(const char* name) const;
[[deprecated("Use instead: _assembleHead(String& buffer, uint8_t version)")]]
String _assembleHead(uint8_t version) {
String buffer;
_assembleHead(buffer, version);
return buffer;
}
virtual void _assembleHead(String& buffer, uint8_t version);
virtual bool _started() const;
virtual bool _finished() const;
virtual bool _failed() const;

View File

@@ -624,7 +624,6 @@ bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper* data) const {
const AsyncWebHeader* AsyncWebServerRequest::getHeader(const char* name) const {
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); });
return (iter == std::end(_headers)) ? nullptr : &(*iter);
}

View File

@@ -126,8 +126,7 @@ const char* AsyncWebServerResponse::responseCodeToString(int code) {
}
}
#else // ESP8266
const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code)
{
const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code) {
switch (code) {
case 100:
return FPSTR(T_HTTP_CODE_100);
@@ -230,12 +229,12 @@ void AsyncWebServerResponse::setCode(int code) {
}
void AsyncWebServerResponse::setContentLength(size_t len) {
if (_state == RESPONSE_SETUP)
if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true))
_contentLength = len;
}
void AsyncWebServerResponse::setContentType(const char* type) {
if (_state == RESPONSE_SETUP)
if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true))
_contentType = type;
}
@@ -249,6 +248,11 @@ bool AsyncWebServerResponse::removeHeader(const char* name) {
return false;
}
const AsyncWebHeader* AsyncWebServerResponse::getHeader(const char* name) const {
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); });
return (iter == std::end(_headers)) ? nullptr : &(*iter);
}
bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool replaceExisting) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name)) {
@@ -268,41 +272,56 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
return true;
}
String AsyncWebServerResponse::_assembleHead(uint8_t version) {
void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
if (version) {
addHeader(T_Accept_Ranges, T_none, false);
if (_chunked)
addHeader(T_Transfer_Encoding, T_chunked, false);
}
String out;
constexpr size_t bufSize = 300;
char buf[bufSize];
#ifndef ESP8266
snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, responseCodeToString(_code));
if (_sendContentLength)
addHeader(T_Content_Length, String(_contentLength), false);
if (_contentType.length())
addHeader(T_Content_Type, _contentType.c_str(), false);
// precompute buffer size to avoid reallocations by String class
size_t len = 0;
len += 50; // HTTP/1.1 200 <reason>\r\n
for (const auto& header : _headers)
len += header.name().length() + header.value().length() + 4;
// prepare buffer
buffer.reserve(len);
// HTTP header
#ifdef ESP8266
buffer.concat(PSTR("HTTP/1."));
#else
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, String(responseCodeToString(_code)).c_str());
buffer.concat("HTTP/1.");
#endif
out.concat(buf);
if (_sendContentLength) {
snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
out.concat(buf);
}
if (_contentType.length()) {
snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
out.concat(buf);
}
buffer.concat(version);
buffer.concat(' ');
buffer.concat(_code);
buffer.concat(' ');
buffer.concat(responseCodeToString(_code));
buffer.concat(T_rn);
// Add headers
for (const auto& header : _headers) {
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str());
out.concat(buf);
buffer.concat(header.name());
#ifdef ESP8266
buffer.concat(PSTR(": "));
#else
buffer.concat(": ");
#endif
buffer.concat(header.value());
buffer.concat(T_rn);
}
_headers.clear();
out.concat(T_rn);
_headLength = out.length();
return out;
buffer.concat(T_rn);
_headLength = buffer.length();
}
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
@@ -337,7 +356,8 @@ AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const
void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
_state = RESPONSE_HEADERS;
String out = _assembleHead(request->version());
String out;
_assembleHead(out, request->version());
size_t outLen = out.length();
size_t space = request->client()->space();
if (!_contentLength && space >= outLen) {
@@ -411,7 +431,7 @@ AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _c
void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) {
addHeader(T_Connection, T_close, false);
_head = _assembleHead(request->version());
_assembleHead(_head, request->version());
_state = RESPONSE_HEADERS;
_ack(request, 0, 0);
}