How to correctly read POST REQUEST body on ESP32?

51 views Asked by At

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:

      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) {


            int contentLength = header.indexOf("Content-Length: ");
              if (contentLength != -1) {
                contentLength = header.substring(contentLength + 16).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
        }
      }

And here is the client-side code:

    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");
      // Data to send with HTTP POST
      String httpRequestData = "from=south&id=military";           
      // 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");
    }

Now, the problem is the server cannot correctly read the POST request.

And before I tested the client, I use API Tester (mobile application) to test the server, and it works as expected. So my server prints these out in the serial monitor:

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

Request Body : from=south&id=military
Direction : south
Vehicle : military
Client disconnected.

But when I send the POST request from a client, my server doesn't return any data on the serial monitor:

POST / HTTP/1.1
Host: 192.168.4.1
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: 23

Request Body : 
Direction : 
Vehicle : 
Client disconnected.

I still haven't figured out why this happens. I guess the mistake is on the client side? Because the server works when used with API Tester. But from many tutorials I have read, my client code should be working correctly.

Also, because there is not something like an error code, I don't know from where I should fix this issue. I hope you can help me with this.

[EDIT]:

      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()) {
                  if (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);
     
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/plain");
            client.println("Connection: close");
            client.println();
            client.println("Got it, " + direction + " for : " + vehicle);
            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
        }
      }
1

There are 1 answers

2
Remy Lebeau On

One problem I see is when you are extracting the Content-Length value from your header, you are expecting Content-Length to be the last HTTP header stored in the header, but it is not the case in your example.

The reason you are making that assumption is because when you are calling substring() to grab the Content-Length's integer value, you are extracting everything that immediately follows "Content-Length: " all the way to the end of the header. So, that only works when Content-Length is the last HTTP header.

You need to instead stop extracting the integer value at the '\r' or '\n' character that actually ends the Content-Length line. Which means you need to specify an end index when you call substring().

But, you are not storing either of those characters in your header, so you can't find them when you need them.


Also, be aware that HTTP headers are case-insensitive, and that whitespace is optional following the ':' character after a header name.