[Solved] HTTP 400 Bad Request ESP32 WiFiClientSecure

I am working with the ESP32-Cam, I have been able to get it online and capture an image. What I want to do now is pass that along to a REST webservice with the image and the data. I have been trying to solve this for 3 days now and have finally thrown my hand up. I have tried many different variations, i cannot seem to figure out why the server claims the request is bad. I am in the process of trying to figure out WireShark to see if that offers any additional insight.

Here is most of everything i know so far, i hope someone is able to help.

myhost.com is fake, i replaced the host that was in there in order to post online, i am able to connect to the web server, the server access log shows this each time I attempt to post:
[06/Mar/2020:00:25:35 +0000] "POST /api/image/add HTTP/1.1" 400 762

I have tested the webservice in PostMan, This is what is shows as the code output for HTTP:

POST /api/image/add HTTP/1.1
Host: myhost.com:8443
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="serialNumber"

1234-5687-1234-5678
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="imageData"; filename="photo.jpg"
Content-Type: image/jpeg

(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW

This is the function i have to upload the image along with the serial number

bool upload() {
	WiFiClientSecure client;

	if (WiFi.status() != WL_CONNECTED) {
		Serial.println("WiFi Not connected!");
		return false;
	}
	Serial.println("Connecting to portal...");

	if (!client.connect("myhost.com", 8443)) {
		Serial.println("Connection to portal failed!");
		return false;
	}
	Serial.println("Posting image ...");
	
	client.println("POST /api/image/add HTTP/1.1");
	client.println("Host: myhost.com:8443");
	client.println("Content-Type: multipart/form-data; boundary=--7MA4YWxkTrZu0gW");
	client.println("Connection: keep-alive\r\n");
	client.println("--7MA4YWxkTrZu0gW");
	client.println("Content-Disposition: form-data; name = \"serialNumber\"\r\n");
	client.println("1234-5687-1234-5678");
	client.println("--7MA4YWxkTrZu0gW");
	client.println("Content-Disposition: form-data; name=\"imageData\"; filename=\"photo.jpg\"");
	client.println("Content-Type: image/jpeg\r\n");
	
	//Send actual image
	Serial.println("(data)");
	
	File file = SPIFFS.open(FILE_PHOTO);
	
	//send using buffer
	while (file.available()) {
		int nextPacketSize = file.available();
		if (nextPacketSize > 1350) {
			nextPacketSize = 1350;
		}
		String buffer = "";
		for (int i = 0; i < nextPacketSize; i++) {
			buffer += (char)file.read();
		}
		client.print(buffer);
	}
	client.println("\r\n--7MA4YWxkTrZu0gW--");
	file.close();
	
	client.print("\r\n");

	Serial.println("Response:");
	while (client.connected()) {
		if (client.available()) {
			String response = client.readStringUntil('\n');
			Serial.println(response.c_str());
		}
	}
	Serial.println("");

	client.stop();

	Serial.println("Connection closed");
	return true;
}

This is the response i get:

HTTP/1.1 400 
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 762
Date: Fri, 06 Mar 2020 00:25:35 GMT
Connection: close

<!doctype html><html lang="en"><head><title>HTTP Status 400 – Bad Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 400 – Bad Request</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Description</b> The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).</p><hr class="line" /><h3>Apache Tomcat/9.0.30</h3></body></html>

I turned on the ESP logging level 5, this is what i get out of the serial console window

Taking a photo...
The picture has been saved in /photo.jpg - Size: 3328 bytes
Uploading
Connecting to portal...
[V][ssl_client.cpp:56] start_ssl_client(): Free internal heap before TLS 201252
[V][ssl_client.cpp:58] start_ssl_client(): Starting socket
[V][ssl_client.cpp:93] start_ssl_client(): Seeding the random number generator
[V][ssl_client.cpp:102] start_ssl_client(): Setting up the SSL/TLS structure...
[I][ssl_client.cpp:156] start_ssl_client(): WARNING: Use certificates for a more secure communication!
[V][ssl_client.cpp:180] start_ssl_client(): Setting hostname for TLS session...
[V][ssl_client.cpp:195] start_ssl_client(): Performing the SSL/TLS handshake...
[V][ssl_client.cpp:216] start_ssl_client(): Verifying peer X.509 certificate...
[V][ssl_client.cpp:225] start_ssl_client(): Certificate verified.
[V][ssl_client.cpp:240] start_ssl_client(): Free internal heap after TLS 160948
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
(data)
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
Response:
HTTP/1.1 400 
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 762
Date: Fri, 06 Mar 2020 00:25:35 GMT
Connection: close

[V][ssl_client.cpp:248] stop_ssl_socket(): Cleaning SSL connection.
<!doctype html><html lang="en"><head><title>HTTP Status 400 – Bad Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 400 – Bad Request</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Description</b> The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).</p><hr class="line" /><h3>Apache Tomcat/9.0.30</h3></body></html>

[V][ssl_client.cpp:248] stop_ssl_socket(): Cleaning SSL connection.
[V][ssl_client.cpp:248] stop_ssl_socket(): Cleaning SSL connection.

"What I want to do now is pass that along to a REST webservice with the image and the data."

Does the URL to the service start with "HTTPS"?

yes it does, on port 8443, i know that doesnt match up in the code snippits, but it does on my end.

https://myhost.com:8443/api/image/add

and i verified with postman that the service works and accepts the images but will not accept from the esp

"but will not accept from the esp"

And where in your code do you do what is needed to make an SSL connection? Check out the SSL examples in the IDE.

I am using the WiFiClientSecure library, the SSL stuff is baked into the library.

"I am using the WiFiClientSecure library, the SSL stuff is baked into the library."

Does that library have any examples that work? If so, then compare the request string you are using to the one in the example. A 400 error generally indicates something is wrong with the request sent.

I looked through the examples, they are very simple GET requests, and not similar to the POST request, unless there is something that I missed the examples are not relevant to what i am trying to accomplish.

"Host: myhost.com:8443"

Remove colon + port number.

No dice, same 400 response.

Updated code

bool upload() {
 WiFiClientSecure client;

 if (WiFi.status() != WL_CONNECTED) {
 Serial.println("WiFi Not connected!");
 return false;
 }
 Serial.println("Connecting to portal...");
 
 if (!client.connect("myhost.com", 8443)) {
 Serial.println("Connection to portal failed!");
 return false;
 }
 Serial.println("Posting image ...");
 

 client.println("POST /api/image/add HTTP/1.1");
 client.println("Host: myhost.com");
 client.println("Content-Type: multipart/form-data; boundary=--7MA4YWxkTrZu0gW");
 client.println("Connection: keep-alive\r\n");
 client.println("--7MA4YWxkTrZu0gW");
 client.println("Content-Disposition: form-data; name = \"serialNumber\"\r\n");
 client.println("1234-5678-9874-5632");
 client.println("--7MA4YWxkTrZu0gW");
 client.println("Content-Disposition: form-data; name=\"imageData\"; filename=\"/photo.jpg\"");
 client.println("Content-Type: image/jpeg\r\n");
 
 //Send actual image
 Serial.println("(data)");
 
 File file = SPIFFS.open(FILE_PHOTO);
 
 //send using buffer
 while (file.available()) {
 int nextPacketSize = file.available();
 if (nextPacketSize > 1350) {
 nextPacketSize = 1350;
 }
 String buffer = "";
 for (int i = 0; i < nextPacketSize; i++) {
 buffer += (char)file.read();
 }
 client.print(buffer);
 }
 client.println("\r\n--7MA4YWxkTrZu0gW--");
 file.close();
 
 client.print("\r\n");

 Serial.println("Response:");
 while (client.connected()) {
 if (client.available()) {
 String response = client.readStringUntil('\n');
 Serial.println(response.c_str());
 }
 }
 Serial.println("");

 Serial.println("Stopping");
 client.stop();
 delay(50);
 Serial.println("Connection closed");
 return true;
}

Response

[V][ssl_client.cpp:56] start_ssl_client(): Free internal heap before TLS 202204
[V][ssl_client.cpp:58] start_ssl_client(): Starting socket
[V][ssl_client.cpp:93] start_ssl_client(): Seeding the random number generator
[V][ssl_client.cpp:102] start_ssl_client(): Setting up the SSL/TLS structure...
[I][ssl_client.cpp:156] start_ssl_client(): WARNING: Use certificates for a more secure communication!
[V][ssl_client.cpp:180] start_ssl_client(): Setting hostname for TLS session...
[V][ssl_client.cpp:195] start_ssl_client(): Performing the SSL/TLS handshake...
[V][ssl_client.cpp:216] start_ssl_client(): Verifying peer X.509 certificate...
[V][ssl_client.cpp:225] start_ssl_client(): Certificate verified.
[V][ssl_client.cpp:240] start_ssl_client(): Free internal heap after TLS 161808
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
(data)
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
[V][ssl_client.cpp:279] send_ssl_data(): Writing HTTP request...
Response:
HTTP/1.1 400 
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Transfer-Encoding: chunked
Date: Fri, 06 Mar 2020 15:52:59 GMT
Connection: close

0

[V][ssl_client.cpp:248] stop_ssl_socket(): Cleaning SSL connection.

Stopping
[V][ssl_client.cpp:248] stop_ssl_socket(): Cleaning SSL connection.
Connection closed
[V][ssl_client.cpp:248] stop_ssl_socket(): Cleaning SSL connection.

"https://myhost.com:8443/api/image/add"

I think 443 is the standard incoming SSL port. Have you tried that instead of 8443?

No, the server is configured to listen in 8443

Are you sure that "client.println" is using CRLF and not LF for line endings? Could you try to use "client.print("...\r\n")" to make sure the request is using the correct line endings?

EDIT: Looks like that is not the problem either.

I think I have spottet some other issues, you may persuit:

//Spaces around "=" should be removed
client.println("Content-Disposition: form-data; name = \"serialNumber\"\r\n");

You are reading the contents of a binary file into a "String" object and sending it with "print" - that's a no-go, here's how to go:

#define BUFSIZE 100
uint8_t buffer[BUFSIZE];

while (file.available())
{
  int cc = file.read(buffer, BUFSIZE);
  client.write(buffer, cc);
}

A last problem is that you need to specify a "Content-Length" header in the request headers to let the server know how much data it is supposed to receive.

I got it working, your were on the right track. What I ended up doing is turn off SSL on the server and use wireshark to capture a picture being sent from postman and one from the esp and compare. There were some header issues, I removed the space, added the content length calculation, and fixed the boundary. Actual boundries have what is defined in the header with a -- before it even if -- is added to the header then a boundary becomes ---- the footer is --header defined boundry--

I didnt change my buffer, but i like yours its cleaner, i will implement that.

You do not have to use content length, you can use chunking with the transport encoding header you will need to add an extra crlf at the very end to indicate
finished sending. I am using content length, I discovered chunking after I solve content length. Fyi for anyone else, content length is the length from the beginning of the first boundary (after the empty line past the header) to the end of the footer, including the file and and other body data.

TIL: http headers are picky about formatting.

1 Like