SecureWiFiclient donloaded json file is incomplete using Client_web.read();

Hello everybody,

I would like to print on serial port a json file with weather forecasts on my esp8266.
Host is in SHTTP

For small json files, this works perfectly…

But on this site : https://www.prevision-meteo.ch/services/json/nantes the file is not complete, i get only roughly between 1440 and 3000 chars…

I m’ not sure how to enable wificlient debug strings… but no crash / error is reported when this function is executed:

Thanks a lots !

Matthieu

    /////////////////////////////////////////////////////////////////////////////////
    // void Recuperer_MTO(void)
    // MORE INFOS : https://github.com/esp8266/Arduino/issues/4814
    //            : https://github.com/bblanchon/ArduinoJson/blob/6.x/examples/JsonHttpClient/JsonHttpClient.ino
    //
    // Connect on https://prevision-meteo.ch website then get json document and parse it
    // We don't care about certificate thanks to  ===>  Client_web.setInsecure();
    // Json File is there: https://www.prevision-meteo.ch/services/json/nantes
    // Use libraries bblanchon/ArduinoJson, WiFiClientSecure, ESP8266WiFi
    //
    // (Fonction qui se connecte a un serveur de données MTO mondial renvoyant un JSON)
    // Ignore les certificats grace à la commande ===>  Client_web.setInsecure();
    /////////////////////////////////////////////////////////////////////////////////

    void Recuperer_MTO(void)

    {
    // FOR DEBUGGING...
    #define DEBUGV(fmt, ...) ::printf((PGM_P)PSTR(fmt), ## __VA_ARGS__)
    #define DEBUG_BSSL(fmt, ...) ::printf((PGM_P)PSTR(fmt), ## __VA_ARGS__)

    const char* host = "prevision-meteo.ch"; // Web site web où se connecter / Host @ to connect
    const int httpsPort = 443;               // PORT HTTPS

    #include <WiFiClientSecure.h>   

    //if (!WiFi_connecte)  return;  // Si l'on est pas connecté on quitte / Exit if not connected

    #ifndef STASSID
    #define STASSID "MY SSID"
    #define STAPSK  "A_BIG_PASSWORD"
    #endif

    const char* ssid = STASSID;
    const char* password = STAPSK;

    // Use WiFiClientSecure class to create TLS connection
    Serial.print("Connecting to ");

    Serial.println(ssid);
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) 

      {
      delay(500);
      Serial.print(".");
      }

    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    
    WiFiClientSecure Client_web;              

    Client_web.setInsecure();  // Ignore l'autenticité du site sur lequel on se connecte  / Discard certificate


    Serial.print("Connecting to ");
    Serial.println(host);

    Client_web.setTimeout(10000);           // needed  ?
    Client_web.setBufferSizes(10000, 1000); // needed  ?

       
    if (!Client_web.connect(host, httpsPort)) {Serial.println("Connection failed");    return;}
    String url = "/services/json/nantes/";
    Serial.print("Requesting URL: ");
    Serial.println(url);

    // Send HTTP request
    Client_web.prinlnt(String("GET ") + url + " HTTP/1.0\r\n" + "Host: " + host + "\r\nConnection: close\r\n"); // HTTP 1.0 pour éviter les réponses découpées (chunked) / Use HTTP 1.0 to avoid chunked answers
    if (Client_web.println() == 0) {Serial.println(F("ERROR : Failed to send request"));  return;}
    Serial.println(F("OK : Request sent"));

    while (Client_web.connected())  // Get header: "OK 200... " ==> need to add  header validity check (err 404, etc)

      {
      String line = Client_web.readStringUntil('\n');
      if (line == "\r")
        {
        Serial.println("headers received");
        break;
        }
      }

    while (Client_web.available())   // FOR NOW, JUST PRINT RECEIVED RAW JSON FILE, BUF FILE IS NOT COMPLETE !! (+/-1440 chars out of +/-54151 chars)
      {
      char c = Client_web.read();
      Serial.write(c);
      }

    // Disconnect
    Client_web.stop();
    }

But if you’re constantly checking for available data, there might be points in time where you have read all currently buffered received data but the connection is still and open and the server will send more. So you are prematurely exiting this loop. Since you’re already specifying Connection: Close in the HTTP header, just make the condtion so that you don’t exit the read loop if the connection is still open with

while (Client_web.available() || Client_web.connected()) {

} 

Also this is

  1. wrong because of no error handling (function returns int with error code -1)
  2. extremely inefficient to read and read and write byte-wise. Create a buffer on the stack and read in it, e.g. using the read(buf, size) function
char rxData[256];
while (Client_web.available() || Client_web.connected()) {
    int ret = Client_web.read(rxData, sizeof(rxData));
    if (ret <= 0) { /* error (-1) or no data (0) */ }
    else {
       //length of actually received data is ret, if positive.
       Serial.print(rxData, ret);
   }
} 

Also consider using the premade ESP8266HTTPClient library which has HTTPS examples and does streaming much better.

Thanks a lots maxgerhardt, I will try that this evening.

char c = Client_web.read();

This was just for a small test :wink: Il will use bblanchon/ArduinoJson lib to parse the data, but on my last try, he tolds me “NoMemory” (I gess due to my truncated data)

Does ESP8266HTTPClient allows ignoring fingerprint (like Client_web.setInsecure(); ?

Matthieu

As you can see by reading the code

The HTTP client is instantiated with the client object of type WiFiClientSecure, you can call the same function on that as you do in your initial sketch, e.g. in line 42.

Great! Thanks it’s works ! :o)

The next challenge was to parse the big json file with the little memory of the ESP…
After trying several json parser’s libraries, I wrote my own (minimalist) parser (not a perfect one, but its sufficient for what I need, and it parse incoming stream without store it in ram).

Here I give you the code, hope it can help someone :o)

/////////////////////////////////////////////////////////////////////////////////
// void Recuperer_MTO(void)
//            
//
// Connect on prevision-meteo ch website then get json document and parse it
// We don't care about certificate thanks to  ===>  Client_web.setInsecure();
// Use libraries WiFiClientSecure, ESP8266WiFi
//
// (Fonction qui se connecte a un serveur de données MTO renvoyant un JSON)
// Ignore les certificats grace à la commande ===>  Client_web.setInsecure();
/////////////////////////////////////////////////////////////////////////////////
void Recuperer_MTO(void)
{
const char* host = "prevision-meteo.ch"; // Web site web où se connecter / Host @ to connect
const int httpsPort = 443;               // PORT HTTPS


//if (!WiFi_connecte)  return;  // Si l'on est pas connecté on quitte / Exit if not connected



#ifndef STASSID
#define STASSID "myssid"
#define STAPSK  "mypassword,"
#endif

const char* ssid = STASSID;
const char* password = STAPSK;

char  ville[20]   = {0};
char  sunrise[6]  = {0};
char  sunset[6]   = {0};
char  tmp[20]     = {0};
char  prev_j0[20] = {0};

float latitude    = 0;
float longitude   = 0;
short t_min_j0    = 0;
short t_max_j0    = 0;

Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) 
  {
  delay(500);
  Serial.print(".");
  }
Serial.println(F(""));
Serial.println(F("WiFi connected"));
Serial.println(F("IP address: "));
Serial.println(WiFi.localIP());

// Create TLS connection
std::unique_ptr<BearSSL::WiFiClientSecure> client(new BearSSL::WiFiClientSecure);
bool mfln = client->probeMaxFragmentLength(host, 443, 1024);
#ifdef DEBUG_VERBOSE   
  Serial.printf("Maximum fragment Length negotiation supported: %s\n", mfln ? "yes" : "no");
  Serial.print(F("[HTTPS] begin...\n"));
#endif  
if (mfln) client->setBufferSizes(1024, 1024);

client->setInsecure();  // Ignore l'autenticité du site sur lequel on se connecte  / Discard certificate

HTTPClient https;


if (https.begin(*client, "https://www.prevision-meteo.ch/services/json/nantes/")) 
  {  
  #ifdef DEBUG_VERBOSE       
Serial.print(F("[HTTPS] GET...\n"));
  #endif  
  // start connection and send HTTP header
  int httpCode = https.GET();
  if (httpCode > 0) 
{
// HTTP header has been send and Server response header has been handled
#ifdef DEBUG_VERBOSE    
  Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
#endif 
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) 
  {  
  // get lenght of document (is -1 when Server sends no Content-Length header)
  int len = https.getSize();

  #define NB_TOKEN 10  // Number of tokens to be identified in the json file (sections and key)
  char* tokens[NB_TOKEN] = {"city_info\":{", "name\":\"", "latitude\":\"", "longitude\":\"", "sunrise\":\"", "sunset\":\"", "fcst_day_0\":{", "tmin\":", "tmax\":", "condition_key\":\""}; // Token's names
  
  short          tokens_found[NB_TOKEN] = {0}; // If a specified token was found
  unsigned short idx_tokens[NB_TOKEN]   = {0}; // Position in a specified token to be parsed

  char ville_idx    = 0; // JSON query result data storaged: City
  char sunrise_idx  = 0; // JSON query result data storaged: Sunrise
  char sunset_idx   = 0; // JSON query result data storaged: Sunset
  char tmp_idx      = 0; // JSON query result data storaged: Temprary file used for float or int conversions
  char prev_j0_idx  = 0; // JSON query result data storaged: String showing forecast for today

 
  while (https.connected() && (len > 0 || len == -1))  // read all data from server
    { 
    size_t size = client->available();  // get available data size
    if (size) // There is still data to be red
      {
      uint8_t buff[128] = { 0 };     // create buffer for read, (erased on every loop)    
      int c = client->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));  // read up to 128 byte, store data read into buff           
      
  for (unsigned short k = 0; k < 128; k++)  // Parse the jsonfile piece by piece... (=> Very low memory usage...)
    {
    for (short tok = 0; tok < NB_TOKEN; tok++)  // For each searched token...
      {
      if((tokens_found[tok] == 0) && (buff[k] == tokens[tok][idx_tokens[tok]])) // Compare each char of not yet found tokens with the received buff data
        {
        idx_tokens[tok]++; // A char match for current token
        if (idx_tokens[tok] == strlen(tokens[tok])) {tokens_found[tok] = 1; k++;}  // If a token was totally identified
        }
      else idx_tokens[tok] = 0; // No matching char for a specified token => Sentence was starting like the token, but eventually this is not the token => erase idx_tokens[tok] to try again later
      }

    short end = buff[k] != '\"';  // A key end with " char
    if (tokens_found[1] == 1) {if (tokens_found[0] == 0) tokens_found[1] = 0; else {if (end) ville[ville_idx++]     = buff[k]; else {tokens_found[1] = 2;}}}                // Ville / City
    if (tokens_found[2] == 1) {if (tokens_found[0] == 0) tokens_found[2] = 0; else {if (end) tmp[tmp_idx++]         = buff[k]; else {tokens_found[2] = 2; latitude  = atof(tmp); for (unsigned short j = 0; j < 20; j++)tmp[j] = 0; tmp_idx = 0;}}}  // Latitude
    if (tokens_found[3] == 1) {if (tokens_found[0] == 0) tokens_found[3] = 0; else {if (end) tmp[tmp_idx++]         = buff[k]; else {tokens_found[3] = 2; longitude = atof(tmp); for (unsigned short j = 0; j < 20; j++)tmp[j] = 0; tmp_idx = 0;}}}  // Longitude
    if (tokens_found[4] == 1) {if (tokens_found[0] == 0) tokens_found[4] = 0; else {if (end) sunrise[sunrise_idx++] = buff[k]; else {tokens_found[4] = 2;}}}                // Lever du soleil / Sunrise
    if (tokens_found[5] == 1) {if (tokens_found[0] == 0) tokens_found[5] = 0; else {if (end) sunset[sunset_idx++]   = buff[k]; else {tokens_found[5] = 2;}}}                // Coucher du soleil / Sunset
    if (tokens_found[7] == 1) {if (tokens_found[6] == 0) tokens_found[7] = 0; else {if (end) tmp[tmp_idx++]         = buff[k]; else {tokens_found[7] = 2; t_min_j0  = atoi(tmp); for (unsigned short j = 0; j < 20; j++)tmp[j] = 0; tmp_idx = 0;}}}  // Temperature max j0 (today)  
    if (tokens_found[8] == 1) {if (tokens_found[6] == 0) tokens_found[8] = 0; else {if (end) tmp[tmp_idx++]         = buff[k]; else {tokens_found[8] = 2; t_max_j0  = atoi(tmp); for (unsigned short j = 0; j < 20; j++)tmp[j] = 0; tmp_idx = 0;}}}  // Temperature max j0 (today) 
    if (tokens_found[9] == 1) {if (tokens_found[6] == 0) tokens_found[9] = 0; else {if (end) prev_j0[prev_j0_idx++] = buff[k]; else {tokens_found[9] = 2;}}}                // Prévision j0 / Forecast (today)
    }
      
 
  yield(); // Avoid watchdog trigger...
 
  if (len > 0) len -= c;                 // Count the number of chars to be read
  if (tokens_found[NB_TOKEN-1])  break;  // If we have found all we need, stop parsing file and close connection
  }
delay(1);
}

  //  Serial.print(reponse);  // For debug
  #ifdef DEBUG_VERBOSE   
    Serial.print("[HTTPS] connection closed or file end.\n");
  #endif
  }

#ifdef DEBUG_VERBOSE   
 // Display results  
  Serial.println("Ville: " + String(ville));                 // City's name
  Serial.println("latitude: " + String(latitude));           // Latitude (decimal)
  Serial.println("longitude: " + String(longitude));         // Longitude (decimal)
  Serial.println("Lever du soleil: " + String(sunrise));     // Sunrise   
  Serial.println("Coucher du soleil: " + String(sunset));    // Sunset
  Serial.println("Temperature min/max: " + String(t_min_j0) + String(t_max_j0)); // Temperature of the day (°C)
  Serial.println("Prévision du jour: " + String(prev_j0));   // Today forecast
#endif
}
  #ifdef DEBUG_VERBOSE       
else  Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());  
  #endif  
  } 
#ifdef DEBUG_VERBOSE   
  else Serial.printf("Unable to connect\n");
#endif  
https.end();
}