Open probonopd opened 7 years ago
For what it is worth, I have got this to work, have a look here
So you are saying it should be JsonObject& root = jsonBuffer.parseObject(request->getParam("body", true)->value());
? Where do you have that information from?
I think so (was a while ago when I last looked at this). IIRC I just cut and paste the content of the not found handler from the example. This outputs loads of useful info about the request.
Thank you. Can't seem to get it to work yet, though...
Tried it and works perfectly!
Apparently there is more than meets the eye. request->getParam("body", true)->value()
does not work for me since apparently there is no body
to begin with...
If I send the following request curl -H "Content-Type: application/json" -X POST -d '{"command": "Hello"}' http://192.168.0.22/
to the ESP, which looks like this:
POST / HTTP/1.1
User-Agent: curl/7.37.0
Host: 127.0.0.1
Accept: */*
Content-Length: 20
{"command": "Hello"}
then the sketch returns No body?!
:
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h> // https://github.com/alanswx/ESPAsyncWiFiManager/
#include "AsyncJson.h"
#include "ArduinoJson.h"
AsyncWebServer server(80);
DNSServer dns;
void setup() {
Serial.begin(115200);
AsyncWiFiManager wifiManager(&server, &dns);
wifiManager.autoConnect("AutoConnectAP");
server.on("/", HTTP_POST, [](AsyncWebServerRequest * request) {
// Count number of params
int params = request->params(); // 0
Serial.println(params);
if (request->hasParam("body", true)) { // This is important, otherwise the sketch will crash if there is no body
request->send(200, "text/plain", request->getParam("body", true)->value());
} else {
Serial.println("No body?!");
request->send(200, "text/plain", "No body?!\n");
}
});
server.begin();
}
void loop() {
}
It seems like others were having similar issues too but this never was clearly documented: https://github.com/me-no-dev/ESPAsyncWebServer/issues/123
@probonopd I can retrieve the JSON body if I use it this way: ->value().c_str()
.
But probably it is better to use the onRequestBody
callback.
That segfaults for me:
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h> // https://github.com/alanswx/ESPAsyncWiFiManager/
#include "AsyncJson.h"
#include "ArduinoJson.h"
AsyncWebServer server(80);
DNSServer dns;
void setup() {
Serial.begin(115200);
AsyncWiFiManager wifiManager(&server, &dns);
wifiManager.autoConnect("AutoConnectAP");
server.on("/", HTTP_POST, [](AsyncWebServerRequest * request) {
request->send(200, "text/plain", request->getParam("body", true)->value().c_str());
});
server.begin();
}
void loop() {
}
But probably it is better to use the onRequestBody callback.
How would we do that?
This seems to be working, thanks @sochs for the hint:
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h> // https://github.com/alanswx/ESPAsyncWiFiManager/
#include "AsyncJson.h"
#include "ArduinoJson.h"
AsyncWebServer server(80);
DNSServer dns;
void setup() {
Serial.begin(115200);
AsyncWiFiManager wifiManager(&server, &dns);
wifiManager.autoConnect("AutoConnectAP");
server.onRequestBody([](AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total) {
Serial.println("Running");
if (request->url() == "/test") {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((const char*)data);
if (root.success()) {
if (root.containsKey("command")) {
Serial.println(root["command"].asString()); // Hello
}
}
request->send(200, "text/plain", "end");
}
});
server.begin();
}
void loop() {
}
Going to try that! Thanks @probonopd
@probonopd It works great! Thanks for the example.
"Use the source Luke!"
I'm leaving this here in case anyone else is looking for this answer.
Using the onRequestBody() callback doesn't work for me, and even if it did, it is a single callback for all endpoints. the body param also didn't work for me, the received request never received a body parameter.
After looking through the source, I found that there are 3 versions of the server.on() method for setting callbacks. The first is the simple one, as seen in most examples:
server.on(endpoint, HTTP_POST, callback)
The second is a flavor used for file upload. It takes two callbacks, the second of which handles file chunks:
server.on(endpoint, HTTP_POST, callback, onFileUploadCB)
And drum roll... the third flavor takes 3 callbacks:
server.on(endpoint, HTTP_POST, callback, onFileUploadCB, onBodyCB)
The first two are same as in the previous case, and the third is an onBody handler, called when the body is received.
This can show how these work:
server.on("/test", HTTP_POST,
[](AsyncWebServerRequest *request)
{
Serial.println("1");
},
[](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final)
{
Serial.println("2");
},
[](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
{
Serial.println("3");
Serial.println(String("data=") + (char*)data);
});
From what I gather, when posting with a body, the 2nd callback isn't used. In theory, the 3rd callback can be called several times, because the body can be received in chunks. For me, it gets called only once for bodies up to about 1.5KB. Didn't test with bigger bodies.
This doesn't seem documented, but it works for me, and the semantics look correct. Anyways, I hope this helps somebody.
I tried the code of devyte with a jquery.post, but the third handler is never called for me. I don't understand why, it just gets skipped.
The code of jeremypoulter using the single request handler but checking for body data does work. It feels a bit like a hack to not use the intended onBodyHandler for though...
@probonopd Your example is nice (server.onRequestBody...
).
But I'm worry using server.onRequestBody
instead of server.on
... Any requests will come to server.onRequestBody
first? then going to server.on
. What happen if we response to user via request->send
before another routes handle?
For simple example:
_asyncWebServer.onRequestBody([this] (AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total) {
if (request->url() == "/api" && request->method() == HTTP_POST) {
const char* requestBody = (const char*) data;
this->debug("[POST /api]: %s", requestBody);
}
request->send(200, "application/json", "{}");
});
_asyncWebServer.on("/", [this] (AsyncWebServerRequest * request) {
request->send(200, "text/html", "<body>Example</body>");
});
So, if user enter /
. Is it reponse html contents from server.on
or json contents from server.onRequestBody
?
A more elegant way that works for me
server.on("/generate", HTTP_POST, [](AsyncWebServerRequest *request){
//nothing and dont remove it
}, NULL, [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((const char*)data);
if (root.success()) {
if (root.containsKey("cmd")) {
Serial.println(root["cmd"].asString());
}
if (root.containsKey("cmd1")) {
Serial.println(root["cmd1"].asString());
}
request->send(200, "text/plain", "");
} else {
request->send(404, "text/plain", "");
}
});
Hello I am trying to do the same on my project. I tried to add a callback in server.on, as done by @maxdd but then I hit the issue #287 where body callback is called multiple time, causing Json parsing to fail. So I tried the new AsuncCallbaclJsonWebHandler method doing:
AsyncCallbackJsonWebHandler* statusJson = new AsyncCallbackJsonWebHandler("/rest/status.json", statusPostJson);
server.addHandler(statusJson);
then in the html page I send a POST request with json body with jquery ajax request as:
function sendJSON() {
$.ajax({
url: 'rest/status.json',
type: 'POST',
data: JSON.stringify(Status),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
async: false,
success: function(msg) {
alert(msg);
}
});
}
The result is that the callback statusPostJson is never called. The ajax method does work, since I used with ESP8266WebServer and it is capable to push the Json in body since I receive it with the .on callback method. Any tips? There must be something I miss or doing wrong, but I cannot figure it out
I tried the code of @devyte and @probonopd .
server.on("/api/tokenverify",HTTP_POST,[](AsyncWebServerRequest request){ Serial.println("END"); },NULL,[](AsyncWebServerRequest request,uint8_t data,size_t len,size_t index,size_t total){ Serial.println("IN /api/tokenverify"); Serial.printf("[len]: %d [index]: %d [total]: %d \n",len,index,total); String strdata = (char)data; Serial.println(String("[data]:")+strdata); request->send(200, "text/plain", "END"); });
server.onRequestBody([](AsyncWebServerRequest request, uint8_t data, size_t len, size_t index, size_t total){ if (request->url() == "/test") { Serial.println("IN /test"); String strdata = (char*)data; Serial.println(String("[data]:")+strdata); } request->send(200, "text/plain", "END"); });
Post body data received a chunked response,One request was split twice.Results as:
IN /test
IN /test
IN /api/tokenverify
IN /api/tokenverify
END
The correct result is:
IN /test
Where is the problem?
[STALE_SET] This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.
[STALE_CLR] This issue has been removed from the stale queue. Please ensure activity to keep it openin the future.
Hi, what libraries are required to use server.arg? All I have is WiFiServer server(80); and it says: 'class WiFiServer' has no member named 'arg'
I am getting all the data I need (SEE BOLD TEXT) when my ESP32webServer gets JSON from a web browser but, so far, I am unable to get to the JSON data on the ESP32 side.
My Problem is: Get to the JSON data arriving from the browser.
What is displayed at the ESP32 console is:
`New Client. GET /?wifissid=&wifiPass=&delayPrimFoto=&delayEntreFotos=&timeGMT=2&smtpServer=&smtpUser=&smtpPass=&emailto=&emailTitle=&emailMessage=&ftpServer=&ftpUser=&ftpPass= HTTP/1.1 Host: 192.168.4.1 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 9; Mi A2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 EdgA/42.0.4.3928 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 Referer: http://192.168.4.1/ Accept-Encoding: gzip, deflate Accept-Language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7
Client disconnected.
New Client. ` Assistance welcome Paulo
Hi, Given this code:
httpServer.on("/api/v0/get", HTTP_GET, [](AsyncWebServerRequest *request) { v0_Get_restAPI_1(request); }, NULL, //-- leave NULL in or it won't compile .... [](AsyncWebServerRequest *request, uint8_t *bodyData, size_t bodyLen, size_t index, size_t total) { v0_Get_restAPI_3(request, bodyData, bodyLen); } );
for every request with params at the url like /api/v0/get/order?orderid="1234"
The function "v0_Get_restAPI_1(request)
" is called and processed.
But if I call curl -XGET --data '{"orderid" : "1234"}' http://server/api/v0/get/order
first v0_Get_restAPI_3(request, bodyData, bodyLen)
is executed and right after that v0_Get_restAPI_1(request)
is executed!
How can I prevent executing v0_Get_restAPI_1(request)
if v0_Get_restAPI_3(request, bodyData, bodyLen)
is already executed??
Did anyone here find a better solution or has a working solution they could share a little example of. I have spent 3 days and can't read the body of the message no matter what I have tried (all of the above too). Many thanks.
@sparkplug23 Could be the problem how you send the JSON data?
I tested the following and it works for me:
srv.onRequestBody(
[](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
{
if ((request->url() == "/rest/api/v2/test") &&
(request->method() == HTTP_POST))
{
const size_t JSON_DOC_SIZE = 512U;
DynamicJsonDocument jsonDoc(JSON_DOC_SIZE);
if (DeserializationError::Ok == deserializeJson(jsonDoc, (const char*)data))
{
JsonObject obj = jsonDoc.as<JsonObject>();
LOG_INFO("%s", obj["test"].as<String>().c_str());
}
request->send(200, "application/json", "{ \"status\": 0 }");
}
}
);
POST JSON via curl:
$ curl -H "Content-Type: application/json" --data '{ "test": "Hello!" }' -X POST http://192.168.2.91/rest/api/v2/test
@BlueAndi
Thank you so much! What my exact problem was, I have no clue. I was using almost exactly what you shared with me there, but for some reason it was not working. I tested your code with a clean install of ArduinoJson and Asyncwebserver in arduino ide (I use vscode normally) and managed to get your example working eventually.
I was using "Postman" and after comparing your curl example and mine, postman does in fact work, but I may have had it on the wrong thing. You know yourself, you change things on both ends and probably miss the fix that resolved the error due to some debug edit.
Anyway, sincere thanks to helping me resolve a 4 day code headache. Now to actually build the code up.
AsyncJson.h has a good example of a JSON request and response. Here's what I've adapted from it:
#include <ESPAsyncWebServer.h>
#include <AsyncJson.h>
#include <ArduinoJson.h>
...
server.addHandler(new AsyncCallbackJsonWebHandler(
"/api/endpoint",
[this](AsyncWebServerRequest* request, JsonVariant& json) {
if (not json.is<JsonObject>()) {
request->send(400, "text/plain", "Not an object");
return;
}
auto&& data = json.as<JsonObject>();
if (not data["name"].is<String>()) {
request->send(400, "text/plain", "name is not a string");
return;
}
String name = data["name"].as<String>();
if (name == "IDLE") {
set_mode_idle(request, data); // handle data and respond
} else if (name == "RANDOM") {
set_mode_random(request, data); // handle data and respond
} else {
request->send(400, "text/plain", "Invalid mode");
}
}));
...
Here's what I did, example of xhttp and JSON.
function parseResponse(p1){ jsonResponse = p1; document.getElementById("temperature").innerHTML = jsonResponse.TMP; document.getElementById("humidity").innerHTML = jsonResponse.HUM; document.getElementById("CO2Lvl").innerHTML = jsonResponse.CO2; document.getElementById("light").innerHTML = jsonResponse.LED; document.getElementById("time").innerHTML = jsonResponse.TIM; document.getElementById("dCount").innerHTML = jsonResponse.CNT; document.getElementById("error").innerHTML = jsonResponse.ERR; document.getElementById("wifi").innerHTML = jsonResponse.WIFI; } setInterval(function ( ) { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200){ var jsonResponse = JSON.parse(this.responseText); console.log(jsonResponse); parseResponse(jsonResponse); } }; xhttp.open("GET", "/Main", true); xhttp.send(); }, 2000 ) ;
The server.on route:
server.on("/Main", HTTP_GET, [](AsyncWebServerRequest *request){ webProc.mainUpdateJson(); request->send_P(200, "text/plain", MTemp);
});
And the main.UpdateJson function:
void WebProc::mainUpdateJson(){ StaticJsonDocument<200> doc; //document has to be bigger than the space required for the output json text. if(tempMode){ fltvar = (sense.getTemp()*9.0/5.0)+32; snprintf(MTemp, 10, "%3.1f %c%cF", fltvar, 0xC2, 0xB0); MTemp, sense.getTemp()); } else { snprintf(MTemp, 10, "%3.1f %c%cC",sense.getTemp(), 0xC2, 0xB0); } doc["TMP"] = MTemp; snprintf(MTemp, 10, "%2.2f%s",sense.getHumidity()," %"); doc["HUM"] = MTemp; snprintf(MTemp, 10, "%6.1f",sense.getCO2()); doc["CO2"] = MTemp; tempbool = leds.getLedState(); if(tempbool){ doc["LED"] = FPSTR(string_On); } else { doc ["LED"]= FPSTR(string_Off); } doc["TIM"] = timeClient.getFormattedTime(); snprintf(MTemp, 5, "%d", shroom.getDayCount()); doc["CNT"] = MTemp; snprintf(MTemp, 3, "%d", leds.getLedPattern()); doc["ERR"] = MTemp; intvar = wifiMgr.getWifiStatus(); switch(intvar){ case WiFiMgr::AP_STAconn: doc["WIFI"]= FPSTR(string_AP_STA); break; case WiFiMgr::APconn: doc["WIFI"] = FPSTR(string_APMode); break; case WiFiMgr::STAconn: doc["WIFI"]= FPSTR(string_CONN); break; case WiFiMgr::DISconn: doc["WIFI"]= FPSTR(string_DISCONN); break; default: WiFiMgr::AP_STAconn); doc["WIFI"]= FPSTR( string_NotConfigured); break; } serializeJson(doc, MTemp); }`
Hi, Given this code:
httpServer.on("/api/v0/get", HTTP_GET, [](AsyncWebServerRequest request) { v0_Get_restAPI_1(request); }, NULL, //-- leave NULL in or it won't compile .... [](AsyncWebServerRequest request, uint8_t *bodyData, size_t bodyLen, size_t index, size_t total) { v0_Get_restAPI_3(request, bodyData, bodyLen); } ); for every request with params at the url like
/api/v0/get/order?orderid="1234"
The function "v0_Get_restAPI_1(request)
" is called and processed.But if I call
curl -XGET --data '{"orderid" : "1234"}' http://server/api/v0/get/order
first
v0_Get_restAPI_3(request, bodyData, bodyLen)
is executed and right after thatv0_Get_restAPI_1(request)
is executed!How can I prevent executing
v0_Get_restAPI_1(request)
ifv0_Get_restAPI_3(request, bodyData, bodyLen)
is already executed??
Hi mrWheel,
Sorry if my answer comes so late, but you need to use solution like this (copied from the AsyncJson.h):
httpServer.on("/api/v0/get", HTTP_GET, [](AsyncWebServerRequest request) { DynamicJsonBuffer jsonBuffer; JsonVariant json = jsonBuffer.parse((uint8_t)(request->_tempObject)); // tempObject is a pointer, so you can also boxing it like any other type [ your own code here]_
}, NULL,
[](AsyncWebServerRequest *request, uint8_t *bodyData, size_t bodyLen, size_t index, size_t total)
{
if (total > 0 && request->_tempObject == NULL && total < 10240) { // you may use your own size instead of 10240
request->_tempObject = malloc(total);
}
if (request->_tempObject != NULL) {
memcpy((uint8_t*)(request->_tempObject) + index, bodyData, bodyLen);
}
});
As described in readme.md:
_If needed, the _tempObject
field on the request can be used to store a pointer to temporary data (e.g. from the body) associated with the request. If assigned, the pointer will automatically be freed along with the request._
How do we parse JSON POSTed to the ESP, i.e., what is the ESPAsyncWebServer equivalent of https://github.com/esp8266/Arduino/issues/1321#issuecomment-267676688
?
The following educated guess,
jsonBuffer.parseObject(request->getParam("plain", true)->value());
, seems not to work for me:Is this documented somewhere? If not, please document it.