// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
#include "AsyncJson.h"
#include "AsyncWebServerLogging.h"
#include
#if ASYNC_JSON_SUPPORT == 1
// Json content type response classes
#if ARDUINOJSON_VERSION_MAJOR == 5
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray) {
_root = _jsonBuffer.createArray();
} else {
_root = _jsonBuffer.createObject();
}
}
#elif ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray) {
_root = _jsonBuffer.createNestedArray();
} else {
_root = _jsonBuffer.createNestedObject();
}
}
#else
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray) {
_root = _jsonBuffer.add();
} else {
_root = _jsonBuffer.add();
}
}
#endif
size_t AsyncJsonResponse::setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measureLength();
#else
_contentLength = measureJson(_root);
#endif
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
size_t AsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5
_root.printTo(dest);
#else
serializeJson(_root, dest);
#endif
return dest.written();
}
#if ARDUINOJSON_VERSION_MAJOR == 6
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
#else
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {}
#endif
size_t PrettyAsyncJsonResponse::setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measurePrettyLength();
#else
_contentLength = measureJsonPretty(_root);
#endif
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5
_root.prettyPrintTo(dest);
#else
serializeJsonPretty(_root, dest);
#endif
return dest.written();
}
// MessagePack content type response
#if ASYNC_MSG_PACK_SUPPORT == 1
size_t AsyncMessagePackResponse::setLength() {
_contentLength = measureMsgPack(_root);
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
size_t AsyncMessagePackResponse::_fillBuffer(uint8_t *data, size_t len) {
ChunkPrint dest(data, _sentLength, len);
serializeMsgPack(_root, dest);
return dest.written();
}
#endif
// Body handler supporting both content types: JSON and MessagePack
constexpr static WebRequestMethodComposite JsonHandlerMethods =
AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST | AsyncWebRequestMethod::HTTP_PUT | AsyncWebRequestMethod::HTTP_PATCH;
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
: _uri(std::move(uri)),
_method(AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST | AsyncWebRequestMethod::HTTP_PUT | AsyncWebRequestMethod::HTTP_PATCH),
_onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest)
: _uri(std::move(uri)),
_method(AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST | AsyncWebRequestMethod::HTTP_PUT | AsyncWebRequestMethod::HTTP_PATCH),
_onRequest(onRequest), _maxContentLength(16384) {}
#endif
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !_method.matches(request->method())) {
return false;
}
if (!_uri.matches(request)) {
return false;
}
#if ASYNC_MSG_PACK_SUPPORT == 1
return request->method() == AsyncWebRequestMethod::HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json)
|| request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack);
#else
return request->method() == AsyncWebRequestMethod::HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json);
#endif
}
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) {
// GET request:
if (request->method() == AsyncWebRequestMethod::HTTP_GET) {
JsonVariant json;
_onRequest(request, json);
return;
}
// POST / PUT / ... requests:
// check if JSON body is too large, if it is, don't deserialize
if (request->contentLength() > _maxContentLength) {
async_ws_log_w("Content length exceeds maximum allowed");
request->send(413);
return;
}
if (request->_tempObject == NULL) {
// there is no body
request->send(400);
return;
}
#if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer doc;
#elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument doc(this->maxJsonBufferSize);
#else
JsonDocument doc;
#endif
#if ARDUINOJSON_VERSION_MAJOR == 5
JsonVariant json = doc.parse((const char *)request->_tempObject);
if (json.success()) {
_onRequest(request, json);
return;
}
#else
DeserializationError error = request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack)
? deserializeMsgPack(doc, (uint8_t *)(request->_tempObject))
: deserializeJson(doc, (const char *)request->_tempObject);
if (!error) {
JsonVariant json = doc.as();
_onRequest(request, json);
return;
}
#endif
// error parsing the body
request->send(400);
}
}
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (_onRequest) {
// ignore callback if size is larger than maxContentLength
if (total > _maxContentLength) {
return;
}
if (index == 0) {
if (total == 0) {
// If total is 0, it is probably a chunked request without an
// X-Expected-Entity-Length header. In that case there is
// no way to know the actual length in advance. The best
// way to handle this would be to use a String instead of
// a fixed-length buffer, but for now we just reject.
async_ws_log_w("AsyncJson cannot handle chunked requests without X-Expected-Entity-Length");
request->abort();
return;
}
// this check allows request->_tempObject to be initialized from a middleware
if (request->_tempObject == NULL) {
request->_tempObject = calloc(total + 1, sizeof(uint8_t)); // null-terminated string
if (request->_tempObject == NULL) {
async_ws_log_e("Failed to allocate");
request->abort();
return;
}
}
}
if (request->_tempObject != NULL) {
uint8_t *buffer = (uint8_t *)request->_tempObject;
memcpy(buffer + index, data, len);
}
}
}
#endif // ASYNC_JSON_SUPPORT