Having trouble reading/catching POST request from a client

Hi,
I am trying to create server and client interaction on Microcontroller ESP32. It'll consist of simple HTTP server and a client. So the client will send a POST request to the server, and the server will do some logic with the data it received.

And for more context, here is the server-side code:

#include <WiFi.h>
#include <iostream>
#include <string>

// Replace with your network credentials
const char* ssid = "Steins";
const char* password = "elpsycongroo";

// Set web server port number to 80
WiFiServer server(80);

// Variable to store the HTTP request
String header;

// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0; 
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;

void setup() {

  Serial.begin(115200);
  Serial.println();
  Serial.println("Configuring access point...");

  // You can remove the password parameter if you want the AP to be open.
  // a valid password must have more than 7 characters
  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);  
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());
  server.begin();

  Serial.println("Server started");
}

void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    currentTime = millis();
    previousTime = currentTime;
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";           
    String requestBody = "";      // make a String to hold incoming data from the client
    while (client.connected() && currentTime - previousTime <= timeoutTime) {  // loop while the client's connected
      currentTime = millis();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            Serial.print("STATUS REPORT <header> : ");
            Serial.println(header);

            // Find the Content-Length header
            int contentLength = header.indexOf("Content-Length: ");
            if (contentLength != -1) {
              Serial.print("STATUS REPORT <contentLength> : ");
              Serial.println(contentLength);
              // Find the end of the Content-Length line
              int endOfContentLength = header.indexOf("\r\n", contentLength);
              Serial.print("STATUS REPORT <endOfContentLength> : ");
              Serial.println(endOfContentLength);
              if (endOfContentLength != -1) {
                // Extract the Content-Length value as an integer
                contentLength = header.substring(contentLength + 16, endOfContentLength).toInt();
                int bodyRead = 0;
                while (bodyRead < contentLength && client.available()) {
                  char c = client.read();
                  requestBody += c;
                  bodyRead++;
                }
              }
            }

            // Separate direction and vehicle id
            int sd = requestBody.indexOf('=');
            int vd = requestBody.indexOf('&');

            String direction = requestBody.substring(sd + 1, vd);

            int pos = requestBody.indexOf('=', vd);

            String vehicle = requestBody.substring(pos + 1);
     
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println("Got it, " + direction + " for : " + vehicle);
            client.println();
            Serial.println("Request Body : " + requestBody);
            Serial.println("Direction : " + direction);
            Serial.println("Vehicle : " + vehicle);
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

and here is the client-side code

#include <WiFi.h>
#include <HTTPClient.h>

const char* ssid = "Steins";
const char* password = "elpsycongroo";

//Your Domain name with URL path or IP address with path
const char* serverName = "http://192.168.160.248:80/";

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastTime = 0;
// Timer set to 10 minutes (600000)
//unsigned long timerDelay = 600000;
// Set timer to 5 seconds (5000)
unsigned long timerDelay = 5000;

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);  
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());
 
  Serial.println("Timer set to 5 seconds (timerDelay variable), it will take 5 seconds before publishing the first reading.");
}

void loop() {
  //Send an HTTP POST request every 10 minutes
  if ((millis() - lastTime) > timerDelay) {
    //Check WiFi connection status
    if(WiFi.status()== WL_CONNECTED){
      WiFiClient client;
      HTTPClient http;
    
      // Your Domain name with URL path or IP address with path
      http.begin(client, serverName);
      
      // Specify content-type header
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");
      http.addHeader("Connection", "keep-alive");
      // Data to send with HTTP POST
      String httpRequestData = "from=sout&id=mill";           
      // Send HTTP POST request
      int httpResponseCode = http.POST(httpRequestData);
     
      Serial.print("HTTP Response code: ");
      Serial.println(httpResponseCode);
        
      // Free resources
      http.end();
    }
    else {
      Serial.println("WiFi Disconnected");
    }
    lastTime = millis();
  }
}

Now, the server-code is actually working. I tested it using API Tester (mobile apps) to send POST request and the server is able to read and print out the data on serial monitor.
But, when sending the data from client, the server for some reason cannot read/catch the data.
For context, here is what the server printed out when receiving request from :

  1. API Tester

New Client.
POST / HTTP/1.1
user-agent: apitester.org Android/7.5(641)
accept: */*
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
Host: 192.168.160.248
Connection: Keep-Alive
Accept-Encoding: gzip

STATUS REPORT <header> : POST / HTTP/1.1
user-agent: apitester.org Android/7.5(641)
accept: */*
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
Host: 192.168.160.248
Connection: Keep-Alive
Accept-Encoding: gzip


STATUS REPORT <contentLength> : 123
STATUS REPORT <endOfContentLength> : 141
Request Body : from=nort&id=ambu
Direction : nort
Vehicle : ambu
Client disconnected.
  1. from Client
New Client.
POST / HTTP/1.1
Host: 192.168.160.248
User-Agent: ESP32HTTPClient
Connection: keep-alive
Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 17

STATUS REPORT <header> : POST / HTTP/1.1
Host: 192.168.160.248
User-Agent: ESP32HTTPClient
Connection: keep-alive
Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 17


STATUS REPORT <contentLength> : 193
STATUS REPORT <endOfContentLength> : 211
Request Body : 
Direction : 
Vehicle : 
Client disconnected.

I've try asking on other forum and even to chatGPT, but I still cannot really understand where the problem is or why the problem is there.
Both (forum and chatGPT) says the problem lays in how the server index / locate the request body, that's why I try adding the status report to compare them.
request from ESP32 have more character than the one sent by API Tester, but they still have the same difference. So, I don't where to look / from where I could solve this problem.

Any feedback is appreciated. Thank you

Try changing the condition that controls the body-read loop to

while (bodyRead < contentLength && client.connected()) {
  if (client.available()) {

This accounts for any "stall" in the bytes going "over the wire". In particular, some clients might be switching between the headers and the body. Before, the server would give up immediately. But the Content-Length header said to expect "this many" bytes, so you can wait for them.

You could also add your timeout check; although make sure you are updating that as intended if you want any bytes at all to reset that timer.

Hey, thanks for replying.
If I'm correct, the the code would look like this?


if (endOfContentLength != -1) {
  // Extract the Content-Length value as an integer
  contentLength = header.substring(contentLength + 16, endOfContentLength).toInt();
  int bodyRead = 0;
  while (bodyRead < contentLength && client.available()) {
    if (client.available()) {
      char c = client.read();
      requestBody += c;
      bodyRead++;
    }
  }
}

Yes.

BTW, the "blank line" in the response should be after the last header and before the body ("Got it"). And you're not sending HTML, so the header should be

Content-Type: text/plain

Thank you for response,
And I think found new clue to this problem,
So I added getString() after sending the POST request on client side.

      http.addHeader("Content-Type", "application/x-www-form-urlencoded");
      http.addHeader("Connection", "keep-alive");
      // Data to send with HTTP POST
      String httpRequestData = "from=sout&id=mill";
      // Send HTTP POST request
      int httpResponseCode = http.POST(httpRequestData);
      // Check HTTP 
      Serial.println("HTTP Request : ");
      Serial.print(http.getString());
     
      Serial.print("HTTP Response code: ");
      Serial.println(httpResponseCode);

Like so, and I don't know why but http.getString()); doesn't return anything.
Here is the result from Serial Monitor:

Connected to WiFi network with IP Address: 192.168.160.74
Timer set to 5 seconds (timerDelay variable), it will take 5 seconds before publishing the first reading.
HTTP Request : 
HTTP Response code: -1
HTTP Request : 
HTTP Response code: 200
HTTP Request : 
HTTP Response code: 200

Is this normal?

Did you move the blank line yet?

As I recall, http.POST will send the request-line
POST /somewhere HTTP/1.1
and the headers, and the body; wait for a response, parse the status code
HTTP/1.1 200 OK
and the headers, so that you can fetch them if you want; return that status code, and then wait at the start of the body. If you expect the body to be short, then you can use getString, but if it's bigger, you might handle it as a stream, for example.

So when you say

      Serial.println("HTTP Request : ");
      Serial.print(http.getString());

That's not the Request, that's the Response body. And if you haven't moved that blank line, then the body is in fact blank. (The "Got it" line is treated as a bad header.) The -1 response code isn't right though.

I have, and I hope this images will provides better context about this problem ( left server; right client)


And as you can see, the server still can't catch the request, even though it returns 200 http code.

I was considering to change library to ArduinoHttpClient, and decided to try that. But I got an error the first time I try to compile the code (example code, without modifying anything). So I decided to keep try tackle this problem.

Are there any particular reasons why you are implementing features from scratch that are already included in the WebServer library?

You can simply add the handler to your server tied to the client request and you don't need to rewrite such amount of code:

void handleClientRequest() {
  String reply;
  if(server.hasArg("from")) {
    reply += "You have submitted ";
    reply += server.arg("from");
  }
  if(server.hasArg("id")) {
    reply += "and ";
    reply += server.arg("id");
  }
  Serial.println(reply);
  server.send(200, "text/plain", reply);
}

.....
.....

void setup() {
  ....
  server.on("/", HTTP_POST, handleClientRequest);
  server.begin();

Oh I misread when I said yes before. #3 had available twice. In that case, the code is no different than before.

I was curious about those possible stalls, so I used this

int bodyRead = 0;
unsigned long stall = 0;
while (bodyRead < contentLength && client.connected()) {
  if (client.available()) {
    if (stall) {
      Serial.printf("stall after %d read: %dms\n", bodyRead, millis() - stall);
      stall = 0;
    }
    char c = client.read();
    requestBody += c;
    bodyRead++;
  } else if (stall == 0) {
    stall = millis();
  }
}

For me, from an ESP32-S3 client to ESP32 server, it would sometimes not stall, but it usually would, and often on the order of a half-second.

as @cotestatnt has pointed out:

on server side: use the WebServer and fetch your data from the parameters.
on client side: send your payload data as POST Parameters, with tag=value&tag=value....

Imho the easiest way when using HTTP.

Thanks all @cotestatnt @kenb4 @noiasca
I'll try both feedback,
As of why I didn't use WebServer, actually I am new to this and was just trying to follow a tutorial I found while also "use" chatGPT for the code. I am new to microcontroller programming and don't have c++ background yet, still trying to make sense of it.

Thanks all @noiasca @cotestatnt @kenb4
Using WebServer works, and yes it's more simpler than previous code.
But I have question,
From the WebServer.h code, there is this comment :

Supports only one simultaneous client, knows how to handle GET and POST.

Does that mean this library can only working when the device is connected (connected as in WiFi connection) to ONLY single client?
Because I'll need this WebServer to be installed in multiple device so they can communicate to each other.
For better context,


So the client (right side) will send POST request to a controller (rectangle). Then that controller will process the information based on that request and then send the result to the feed (triangle), so I think I'll also need to be use WebServer on those feed.

The server will process only one client at a time.
A potential second client will see a slight "delay" before he will get his answer from the server because the server is handling the other client.
In your schematic there is no communication where two clients send to the same server - so you will not encounter this "problem".

That's true, I've tried it.
Thanks all, for your suggestion. Now I can continue with my project.
Once again, thank you so much.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.