See More

#include "AsyncHTTPClientLight.h" #define ASYNC_HTTP_DEBUG 1 #if ASYNC_HTTP_DEBUG #pragma message "### AsyncHTTPClientLight: Funzionalita DEBUG ATTIVATE ###" #define ASYNC_HTTP_LOG_SD //#define ASYNC_HTTP_LOG_SPIFFS //#define ASYNC_HTTP_LOG_LittleFS //#define MAXSIZEFILE_LOG 51200 #define MAXSIZEFILE_LOG 512000 #ifdef ASYNC_HTTP_LOG_SPIFFS #include #define FS_LOG SPIFFS #pragma message "### AsyncHTTPClientLight: Funzionalità 'SPIFFS' incluse. ###" #endif #ifdef ASYNC_HTTP_LOG_SD #include #define FS_LOG SD #pragma message "### AsyncHTTPClientLight: Funzionalità 'SD' incluse. ###" #endif #ifdef ASYNC_HTTP_LOG_LittleFS #include #include #define FS_LOG LittleFS #pragma message "### AsyncHTTPClientLight: Funzionalità 'LittleFS' incluse. ###" #endif #else #define log(x) #endif AsyncHTTPClientLight::AsyncHTTPClientLight() { reset(); timeoutMs = 10000; debugEnabled = false; requestCounter = 0; maxRetries = 1; maxRedirects = 1; retryCount = 1; redirectCount = 0; //------ sprintf(response.contentType,"application/octet-stream"); // default response.expectedLength = -1; response.contentLength = 0; response.isChunked = false; response.statusCode = -1; responsePayloadBuffer = lineBuffer; responsePayloadMaxLen = sizeof(lineBuffer); response.ptr_workbuffer = PATHBUFFER; } void AsyncHTTPClientLight::triggerEvent(HTTPEventType type, const char* data) { if (unifiedCallback) { snprintf(response.msg_error, sizeof(response.msg_error),"%s", data); unifiedCallback(type, &response); } } void AsyncHTTPClientLight::triggerEvent(HTTPEventType type, const String& message) { triggerEvent(type, message.c_str()); } void AsyncHTTPClientLight::setMaxRetries(int retries) { if (retries >= 1) maxRetries = retries; } void AsyncHTTPClientLight::setmaxRedirects(int ndirect) { if (ndirect >= 1) maxRedirects = ndirect; } void AsyncHTTPClientLight::setDebug(bool enabled) { debugEnabled = enabled; } #if ASYNC_HTTP_DEBUG void AsyncHTTPClientLight::log(const String& msg){ #define oldLogFile "/old_Log.txt" #define logFile "/http_log.txt" //String fullMsg = logPrefix + msg; if (debugEnabled) Serial.println(logPrefix + msg); #if defined(ASYNC_HTTP_LOG_SPIFFS) || defined(ASYNC_HTTP_LOG_SD) || defined(ASYNC_HTTP_LOG_LittleFS) if (logToFile) { File f = FS_LOG.open(logFile, FILE_APPEND); if (f) { f.print(logPrefix); f.println(msg); //f.println(fullMsg); //f.close(); } // Rotazione semplice se supera maxSize if (f.size() > MAXSIZEFILE_LOG) { f.close(); FS_LOG.remove(oldLogFile); FS_LOG.rename(logFile, oldLogFile); f = FS_LOG.open(logFile, FILE_WRITE); // Crea nuovo }else{ f.close(); } } #endif } #endif #if ASYNC_HTTP_DEBUG void AsyncHTTPClientLight::setLogToFile(bool enabled) { logToFile = enabled; #if defined(ASYNC_HTTP_LOG_SPIFFS) || defined(ASYNC_HTTP_LOG_SD) if (enabled && !FS_LOG.begin(true)) { log("[HTTP]FS_LOG non inizializzato"); }else{ log("[HTTP]FS_LOG inizializzato"); } #endif } #endif int AsyncHTTPClientLight::getLastHTTPcode() const { return response.statusCode; } void AsyncHTTPClientLight::reset() { client = nullptr; port = 80; useSSL = false; method = "GET"; finished = true; headersParsed = false; state = IDLE; redirectCount = 0; retryCount = 1; bufIndex = 0; // Resetta struttura response memset(&response, 0, sizeof(response)); response.statusCode = -1; // codice ritorno http response.contentLength = 0; response.expectedLength = -1; response.isStream = false; response.isChunked = false; if(!newPtrOut){ responsePayloadBuffer = lineBuffer; responsePayloadMaxLen = LINE_BUFFER_SIZE; }else{ newPtrOut = false; } responsePayloadBuffer[0] = '\0'; response.msg_error[0] = '\0'; response.ptr_workbuffer = WORKBUFFER; } bool AsyncHTTPClientLight::parseURL(const char* url) { useSSL = false; if(search_strbuf(url, "https://") == 0) useSSL = true; port = useSSL ? 443 : 80; int indexhost = search_strbuf(url, "://", 0); if (indexhost == -1) return false; indexhost = indexhost + 3; int pathIndex = search_strbuf(url, "/", indexhost); snprintf(PATHBUFFER, sizeof(PATHBUFFER),"%s", (pathIndex == -1)? "/" : &url[pathIndex] ); //http://10.255.255.1 if(pathIndex == -1)pathIndex = strlen(url); snprintf(host, pathIndex - indexhost +1,"%s", &url[indexhost] ); log("Protocollo: " + String(useSSL ? "HTTPS" : "HTTP")); log("Porta: " + String(port)); log("Host: " + String(host)); log("Path: " + String(PATHBUFFER)); return true; } void AsyncHTTPClientLight::addTitle(const String& title) { snprintf(pendingTitle, sizeof(pendingTitle),"%s", title.c_str()); } /** * @brief Esegue una richiesta HTTP sincrona. * @param url URL completo della richiesta. * @param method Metodo HTTP (GET, POST...). * @param payload Corpo della richiesta (solo per POST/PUT). * @return Codice di stato HTTP ricevuto. */ int AsyncHTTPClientLight::runSync(const char* url, const char* methodGET, const char* payload) { // aspetto se eventualmente c'è una richiesta asincrona in corso la porto a termine if(!finished){ log("Wait end Asincrona : " + String(response.inprogressTitle)); log("Pending Sincrona : " + String(pendingTitle)); //} while(!finished){ vTaskDelay(pdMS_TO_TICKS(10)); poll2(); } } _isSyncMode = true; unsigned long startTime; startTime = millis(); beginRequest(url, method, payload); while (!isFinished()) { vTaskDelay(pdMS_TO_TICKS(10)); //delay(100); poll2(); } // clean client->stop(); _isSyncMode = false; finished = true; log("SYNC CLOSED"); return response.statusCode; } void AsyncHTTPClientLight::beginRequest(const char* url, const char* method_, const char* payload_) { if (!isFinished() && redirectCount == 0) { log("Overload: richiesta già in corso: " + String(response.inprogressTitle)); triggerEvent(HTTPEventType::Overload, String(pendingTitle)); return; } requestCounter++; logPrefix = "[REQ " + String(requestCounter) + "] "; if (finished) { //nuova richiesta reset(); snprintf(response.inprogressTitle, sizeof(response.inprogressTitle), "%s", pendingTitle); log("\n === " + String(response.inprogressTitle) + " ==="); snprintf(pendingTitle, sizeof(pendingTitle), "%s", "(nessun titolo)"); inprogressHeaders.clear(); inprogressHeaders = pendingHeaders; pendingHeaders.clear(); } if (!parseURL(url)) { finished = true; //log("ERRORE URL non valido"); snprintf(response.msg_error, sizeof(response.msg_error), "ERRORE URL non valido"); log(response.msg_error); if (unifiedCallback) unifiedCallback(HTTPEventType::Response, &response); //triggerEvent(HTTPEventType::Error, logPrefix + "URL non valido"); return; } method = method_; if (!_isSyncMode && payload_ != nullptr) { //Serial.println("payload da allocare"); // questa funzione serve per fare una copia del payload in spazio memoria allocata in // caso di richieta asincrona e che il payload sia locale alla funzione chiamante // perdita del puntatore -- a meno che sia dichiarato globalmente size_t len = strlen(payload_); if(len > 1){ ptr_Inpayload = (char*)malloc(len + 1); if (ptr_Inpayload != nullptr) { strcpy(ptr_Inpayload, payload_); payloadAllocated = true; log("memoria allocata "); //Serial.print("memoria allocata "); //Serial.println(len + 1); } else { log("Errore malloc payload"); payloadAllocated = false; } } } state = CONNECTING; lastActivity = millis(); response.restime = lastActivity; finished = false; } void AsyncHTTPClientLight::addHeader(const String& key, const String& value) { pendingHeaders.push_back({ key, value });; } void AsyncHTTPClientLight::setTimeout(unsigned long ms) { timeoutMs = ms; } void AsyncHTTPClientLight::onEvent(UnifiedCallback cb) { unifiedCallback = cb; } bool AsyncHTTPClientLight::isFinished() { return finished; } void AsyncHTTPClientLight::poll2() { //unsigned long startTime; // if (finished || !client) return; if (finished || state == IDLE) return; //if (!_isSyncMode) checktimeout(); checktimeout(); switch (state) { case CONNECTING: connecting(); break; case SENDING: sending(); break; case RECEIVING: //startTime = millis(); //while (!client->available() && millis() - startTime < timeoutMs) { //vTaskDelay(pdMS_TO_TICKS(10)); // Ritardo di 10ms //delay(10); //} if (!_isSyncMode) vTaskDelay(pdMS_TO_TICKS(10)); if (client->available()) receiving(); break; default: break; } } // in caso di TASK freeRTOS e richiesta runSync in corso non esegue il poll dall'esterno(loop) void AsyncHTTPClientLight::poll(){ if(_isSyncMode)return; poll2(); } void AsyncHTTPClientLight::checktimeout() { if (millis() - lastActivity > timeoutMs) { log("Timeout"); snprintf(response.msg_error, sizeof(response.msg_error),"Timeout %d", retryCount); if(retryCount <= maxRetries){ // se non supero maxRetries solo messaggio triggerEvent(HTTPEventType::Timeout, logPrefix + "Timeout" + String(retryCount)); } client->stop(); retryCount++; state = CONNECTING; lastActivity = millis(); return; } } void AsyncHTTPClientLight::connecting() { //if (retryCount > maxRetries || redirectCount > maxRedirects){ if (retryCount > maxRetries){ releasePayload(); // if(redirectCount > maxRedirects){ // snprintf(response.msg_error, sizeof(response.msg_error), "Too many redirection"); // log(response.msg_error); // if (unifiedCallback) unifiedCallback(HTTPEventType::Response, &response); // } if (retryCount > maxRetries){ snprintf(response.msg_error, sizeof(response.msg_error), "Superato num tentativi: %d", retryCount); log(response.msg_error); if (unifiedCallback) unifiedCallback(HTTPEventType::Response, &response); } response.restime = (millis() - response.restime); log("Tempo:" + String(response.restime)); finished = true; state = IDLE; return; } responsePayloadBuffer[0] = '\0'; client = useSSL ? &secureClient : &plainClient; if (useSSL) secureClient.setInsecure(); // log("Client creato puntatore: " + String((uintptr_t)client)); //if(retryCount == 1 && redirectCount == 1){ if(redirectCount > 0){ log("REDIRECT.."); log("Host: " + String(host)); log("Path: " + String(PATHBUFFER)); } log("Tentativo n:" + String(retryCount) ); if (client->connect(host, port)) { state = SENDING; lastActivity = millis(); log("Connessione riuscita"); return; } else { client->stop(); log("Connessione fallita"); retryCount++; lastActivity = millis(); state = CONNECTING; } } void AsyncHTTPClientLight::sending() { int x = 0; //Serial.println(ptr_Inpayload); //Serial.println((unsigned long)&ptr_Inpayload, HEX); log("Sending.."); snprintf(lineBuffer, sizeof(lineBuffer), "%s %s HTTP/1.1\r\n", method, PATHBUFFER); log(lineBuffer); client->print(lineBuffer); // Host header snprintf(lineBuffer, sizeof(lineBuffer), "Host: %s\r\n", host); log(lineBuffer); client->print(lineBuffer); //headers for (auto& h : inprogressHeaders) { //Serial.println("headers trovato"); //log("aggiungo headers");// log(h.first + ": " + h.second + "\r\n");// client->print(h.first + ": " + h.second + "\r\n"); } if(ptr_Inpayload != nullptr && redirectCount == 0){ x = strlen(ptr_Inpayload); snprintf(lineBuffer, sizeof(lineBuffer), "Content-Length: %d\r\n", x); client->print(lineBuffer); client->print("Accept: text/html,application/json\r\n"); } client->print("Connection: close\r\n\r\n"); if(x >0) client->print(ptr_Inpayload); log("SHIPPED: " + (String(ptr_Inpayload != nullptr ? ptr_Inpayload : "done"))); headersParsed = false; state = RECEIVING; lastActivity = millis(); //client->flush(); } void AsyncHTTPClientLight::receiving() { int len; if(!headersParsed){ while (client->available()) { lastActivity = millis(); len = readUntilTerminator(client, lineBuffer, sizeof(lineBuffer)-1, '\n', timeoutMs); if (len < 0) break; // if (len < 0) { // log("Errore o Timeout durante la lettura degli header"); // client->stop(); // retryCount++; // state = CONNECTING; // Ripensa la connessione o dichiara FINISHED se superi i tentativi // return; // } // Fine degli header if (strlen(lineBuffer) > 0){ parseHeaders(); if(headersParsed)break; } else{ log("Fine header"); headersParsed = true; break; } } // lascio il tempo ad altre funzioni poi ritorno mentre aspetto altri header // fino a quando trovo un header vuoto if(!headersParsed)return; } response.msg_error[0] = '\0'; // reset errori precednti // --- Gestione redirect HTTP -------------------------------------------- if (response.statusCode >= 300 && response.statusCode <= 308) { // Copia il nuovo URL o path relativo //if (search_strbuf(lineBuffer, "Location:") == 0) { client->stop(); headersParsed = false; //response.contentLength = 0; //inprogressHeaders.clear(); redirectCount++; if(redirectCount > maxRedirects){ snprintf(response.msg_error, sizeof(response.msg_error), "Too many redirection"); log(response.msg_error); if (unifiedCallback) unifiedCallback(HTTPEventType::Response, &response); response.restime = (millis() - response.restime); log("Tempo:" + String(response.restime)); finished = true; state = IDLE; return; } //gia fatto da parseHeaders // trimmer(lineBuffer, 9); // if (lineBuffer[0] == '/') { // snprintf(PATHBUFFER, sizeof(PATHBUFFER), "%s", lineBuffer); // } else { // parseURL(lineBuffer); // } //} // Comportamento conforme a RFC 7231 switch (response.statusCode) { case 301: case 302: releasePayload(); // il vecchio payload non serve più method = "GET"; case 303: // Questi status implicano GET nella nuova richiesta //if (strcmp(method, "GET") != 0) { releasePayload(); // il vecchio payload non serve più method = "GET"; //} break; case 307: case 308: // Mantieni il metodo e il payload originali break; default: break; } // client->stop(); // headersParsed = false; response.contentLength = 0; inprogressHeaders.clear(); retryCount = 1; state = CONNECTING; //log("Redirect " + String(response.statusCode) + " -> " + String(PATHBUFFER) + " con metodo " + String(method)); return; } if(response.isStream) { lastActivity = millis(); len = 0; len = readStream(responsePayloadBuffer, responsePayloadMaxLen -1, response.contentLength ); if(len != response.contentLength) { log("Errore dati nello stream"); triggerEvent(HTTPEventType::Error, logPrefix + "n. dati non corrispondono"); } response.isStream = false; } if (response.isChunked){ lastActivity = millis(); if(readChunked())response.isChunked = false; // stop lettura chunked if(!response.isChunked){ //Serial.print(responsePayloadBuffer); len = strlen(responsePayloadBuffer); } // if (diagnostics.chunkMalformed) { // triggerEvent(HTTPEventType::Warning, "Chunk malformato"); // } } if(!response.isStream && !response.isChunked){ //finito state = IDLE; finished = true; client->stop(); releasePayload(); response.restime = (millis() - response.restime); log("Tempo:" + String(response.restime)); if (unifiedCallback) unifiedCallback(HTTPEventType::Response, &response); } } //end //----------------------- int AsyncHTTPClientLight::readStream(char* buffer, int lenbuffer, int ndati ){ int count_ch = 0; int readed = 0; if(ndati == 0)return ndati; log("verranno letti:" + String((ndati > lenbuffer -1)? lenbuffer -1:ndati) + " bytes di " +String(ndati)); if(lenbuffer -1 < ndati)triggerEvent(HTTPEventType::Error, logPrefix + "Buffer too small " + String(lenbuffer -1) + " need " + String(ndati)); while (millis() - lastActivity < timeoutMs) { if (client->available()) { char c = client->read(); readed++; #if ASYNC_HTTP_DEBUG if(c != '\r' && c != '\n') Serial.print(c); if (c == '\r')Serial.print("CR"); if (c == '\n')Serial.println("LF"); #endif if(count_ch < lenbuffer-1)buffer[count_ch++] = c; if (readed == ndati)break; } } Serial.println(); buffer[count_ch] = '\0'; response.expectedLength = readed; log("Letti n. bytes: " + String(readed)); return readed; } //--------------------------------------- bool AsyncHTTPClientLight::readChunked(){ bool ok_chunk = false; bool endchunk = false; int d = 0; while (millis() - lastActivity < timeoutMs) { while (client->available()) { char c = client->read(); if(c == '\n' && WORKBUFFER[bufIndex-1] == '\r'){ WORKBUFFER[bufIndex -1] = '\0'; ok_chunk = true; break; }else{ WORKBUFFER[bufIndex++] = c; if(bufIndex == sizeof(WORKBUFFER)-1){ WORKBUFFER[bufIndex] = '\0'; //response.sizeBuffer += bufIndex; // ok_chunk = true; break; } } } if(!ok_chunk)return ok_chunk; switch (chunkState) { case WAITING_SIZE: response.expectedLength = (int)strtol(WORKBUFFER, nullptr, 16); //Serial.printf("lunghezza chunk: %d\r\n", response.expectedLength);// if (response.expectedLength == 0) { chunkState = SKIPPING_CRLF; } else { chunkState = READING_DATA; } break; case READING_DATA: response.contentLength += strlen(WORKBUFFER); //Serial.printf("arrivati n.%d byte %d", strlen(WORKBUFFER), response.expectedLength); if(bufIndex == sizeof(WORKBUFFER)){ log("Portion Chunk: " + String(WORKBUFFER)); }else{ log("Chunk: " + String(WORKBUFFER)); chunkState = WAITING_SIZE; } if(strlen(WORKBUFFER) != response.expectedLength){ snprintf(responsePayloadBuffer, responsePayloadMaxLen ,"%s","Chunk malformato:\n" ); } if (unifiedCallback) unifiedCallback(HTTPEventType::Chunk, &response); d = snprintf(responsePayloadBuffer, responsePayloadMaxLen ,"%s%s",responsePayloadBuffer, WORKBUFFER ); //Serial.printf("#########################%d %d\n", d,responsePayloadMaxLen); if(d >= responsePayloadMaxLen)triggerEvent(HTTPEventType::Error, logPrefix + "Buffer too small"); break; case SKIPPING_CRLF: endchunk = true; chunkState = FINISHED; log("Chunked Finished"); return endchunk; break; default: break; } bufIndex = 0; return endchunk; } } //------------------------------------ void AsyncHTTPClientLight::parseHeaders(){ log("header: " + String(lineBuffer)); if (search_strbuf(lineBuffer, "HTTP/") == 0) { int space = search_strbuf(lineBuffer, " "); response.statusCode = atoi(&lineBuffer[space + 1]); log("Status code: " + String(response.statusCode)); return; } if (search_strbuf(lineBuffer, "Transfer-Encoding:") == 0 && search_strbuf(lineBuffer, "chunked") != -1) { response.isChunked = true; chunkState = WAITING_SIZE; log("Chunked encoding rilevato"); return; } if (search_strbuf(lineBuffer, "Location:") == 0 && (response.statusCode == 301 || response.statusCode == 302 || response.statusCode == 307)) { trimmer(lineBuffer, 9); //log("Redirect verso: " + String(lineBuffer)); if(lineBuffer[0] == '/'){ // PATHBUFFER relativo snprintf(PATHBUFFER, sizeof(PATHBUFFER),"%s", lineBuffer); }else{ parseURL(lineBuffer); } headersParsed = true; // interrompo parseheaders e faccio subito redirect return; } if (search_strbuf(lineBuffer, "Content-Type:") == 0) { trimmer(lineBuffer, 14); snprintf(response.contentType, sizeof(response.contentType),"%s", lineBuffer ); } else if (search_strbuf(lineBuffer, "Content-Length:") == 0) { trimmer(lineBuffer, 16); response.contentLength = atoi(lineBuffer); response.isStream = true; } } //------------------------------------ int AsyncHTTPClientLight::readUntilTerminator(Stream* client, char* buffer, size_t maxLen, char terminator, unsigned long timeoutMs, bool delCR) { size_t index = 0; unsigned long start = millis(); bool isdati = false; while (millis() - start < timeoutMs && index < maxLen - 1) { if (client->available()) { char c = client->read(); isdati = true; if (c == '\r'){ if (delCR)continue; // ignora il carriage return } if (c == terminator) break; buffer[index++] = c; } } buffer[index] = '\0'; if (index == 0 && millis() - start >= timeoutMs && !isdati) { return -1; // timeout senza dati } return index; // numero di caratteri letti } //----------------------------------- void AsyncHTTPClientLight::releasePayload() { if(!payloadAllocated) return; if (payloadAllocated && ptr_Inpayload) { free(ptr_Inpayload); ptr_Inpayload = nullptr; payloadAllocated = false; log("Payload liberato"); } } //------------------------------------------------- // FUNZIONE TRIMMER toglie spazi iniziali e finali da un buffer int AsyncHTTPClientLight::trimmer(char* buftrim, int dadove) { int s = dadove; int e = 0; int _last_ch = 0; if (buftrim[0] == '\0') return _last_ch; do { if (buftrim[s] == ' ' && _last_ch == 0) { //tolgo spazi iniziali s++; } else { buftrim[e] = buftrim[s]; if (buftrim[e] != ' ') _last_ch = e; // ultima lettera valida s++; e++; } } while (buftrim[s] != '\0'); buftrim[_last_ch + 1] = '\0'; return _last_ch; } // cerca una stringa nel buffer e ritorna la posizione.. -1 se non trova int AsyncHTTPClientLight::search_strbuf(const char* buffer, char* str_cmp, int fromwhere) { if (str_cmp[0] == '\0') return fromwhere; int t = fromwhere; int x = 0; int pos = -1; while (buffer[t] != '\0') { if (buffer[t] != str_cmp[x]) { if(pos != -1) t = pos; t++; pos = -1; x = 0; } else { if (pos == -1) pos = t; x++; t++; if (str_cmp[x] == '\0') return pos; } } return -1; }