boblemaire / asyncHTTPrequest

asynchronous HTTP for ESP using ESPasyncTCP. Works like XMLHTTPrequest in JS.
GNU General Public License v3.0
65 stars 31 forks source link

"Off by one" error in header values ? #47

Closed sigmdel closed 7 months ago

sigmdel commented 1 year ago

There seems to be an "off by one" error in

asyncHTTPrequest::_addHeader(const char* name, const char* value)`

An HTTP request made with curl looks like this:

~curl "http://192.168.1.55:8080/json.htm" 
OK

Received request() from 192.168.1.208:50882
  request->method: GET (1)
  request->url() = "/json.htm"
  request->headers() = 3
    1 Host: 192.168.1.55:8080
    2 Accept: */*
... 

The same HTTP request made with asyncHTTPrequest (code below) looks like this:

Received request() from 192.168.1.135:65086
  request->method: GET (1)
  request->url() = "/json.htm"
  request->headers() = 2
    1 host: 92.168.1.55:8080
    2 Accept: /*

Note how the first char of each header value is missing. To confirm the error add a space in front of the Accept value when making the request with asyncHTTPrequest

void sendRequest(){
    if (request.readyState() == 0 || request.readyState() == 4) {
        request.setTimeout(2);
        bool requestOpenResult = request.open("GET", "http://192.168.1.55:8080/json.htm");
        if (requestOpenResult) {
          request.setReqHeader("Accept", " */*");    //<===== space added before *
      request.send();
          Serial.println("Request sent");
        }
        else
          Serial.println("Error: could not open request");
    } else {
     Serial.println("request is not ready");
    }
}

and a space in front of the host name in asyncHTTPrequest::open(const char* method, const char* URL) at line 93

    char* hostName = new char[strlen(_URL->host)+10];
    sprintf(hostName," %s:%d", _URL->host, _URL->port);    //<===== space added before %s
    _addHeader("host",hostName);

then the headers are correct.

Received request() from 192.168.1.135:57547
  request->method: GET (1)
  request->url() = "/json.htm"
  request->headers() = 2
    1 host: 192.168.1.55:8080
    2 Accept: */*

This was tested with an ESP32 and ESP32-C3 with the latest version of 'me-no-dev/AsyncTCP' last updated in Oct. 2019.

Looking at the code for _addHeader and then _buildRequest, it all seems OK to me. But then I'm lost at C when it comes to C/C++.

sigmdel commented 7 months ago

There is no error in the code. The missing first character in a header value was a problem with a particular web server: ESPAsyncWebServer. It looks as if a fix was proposed back in Jan. 2021 but it has not been merged in the current me-no-dev release of ESPAsyncWebServer nor in the current esphome release.

Quoting The Hypertext Transfer Protocol -- HTTP/1.1 Section 4.2 Message Headers:

Each header field consists of a name followed by a colon (":") and the field value. Field names are case-insensitive. The field value MAY be preceded by any amount of LWS, though a single SP is preferred.

If white space is added after the colon it should be a single space. But that does not mean that a single space must precede the value. So I was wrong to raise the "off by one" error.

Interestingly, Tien Huy Huynh (TienHuyIoT) replaced _request->write(':'); with _request->write(": "); in the asyncHTTPrequest::_buildRequest() function (Dec 2, 2023 commit on his fork of asyncHTTPrequest). The original ESP8266HTTPClient also adds that space after the colon.