EVOG2-Spiffs-Avery/HTTPSHandler.ino

368 lines
9.7 KiB
C++

#include "HTTPSHandler.h"
HTTPSHandler::HTTPSHandler(const char *serverCert)
{
_client.setCACert(serverCert);
}
bool HTTPSHandler::connect(const char *host, int port, const char *serverCert)
{
_host = host;
_port = port;
if (serverCert != nullptr)
{
_client.setCACert(serverCert);
}
_currentState = HttpsState::CONNECTING;
// reset response
_response = "";
return _connect();
}
bool HTTPSHandler::_connect()
{
return _client.connect(_host, _port);
}
int HTTPSHandler::_getContentLength()
{
unsigned long startTime = millis();
String line;
int contentLength = -1;
if (!_client.connected())
{
Serial.print("Not connected when getting content length");
_currentState = HttpsState::ERROR;
return -1;
}
while (contentLength == -1)
{
while (_client.available())
{
line = _client.readStringUntil('\n');
line.trim(); // Remove any trailing whitespace
if (line.startsWith("Content-Length: "))
{
contentLength = line.substring(15).toInt();
}
// Empty line / end of headers
if (line.length() == 0)
{
break;
}
}
// Check for timeout
if (millis() - startTime > _responseTimeout * 1000)
{
Serial.println("Timeout while trying to get Content-Length!");
_currentState = HttpsState::ERROR;
return -1;
}
}
return contentLength;
}
void HTTPSHandler::getRequest(const char *path)
{
// Check if the client is connected before sending
if (!_client.connected())
{
Serial.println("Error: Client is not connected!");
_currentState = HttpsState::ERROR;
return;
}
String request = "GET " + String(path) + " HTTP/1.1\r\n";
request += "Host: " + String(_host) + "\r\n";
if (_customHeader.length() > 0)
{
request += _customHeader + "\r\n";
}
request += "Connection: keep-alive\r\n\r\n";
size_t bytesSent = _client.print(request);
// Check if all bytes of the request were sent
if (bytesSent != request.length())
{
Serial.println("Error: Not all bytes sent!");
_currentState = HttpsState::ERROR;
return;
}
_currentState = HttpsState::WAITING;
_requestType = RequestType::GET;
}
void HTTPSHandler::_processDownload(File &dest)
{
const uint32_t BUFFER_SIZE = 2048;
int contentLength = _getContentLength();
static int bytesRead = 0; // Number of bytes downloaded so far
byte buffer[BUFFER_SIZE];
unsigned long startMillis = millis();
if (contentLength <= 0)
{
_currentState = HttpsState::ERROR;
}
while (_currentState == HttpsState::WAITING)
{
// Then proceed to download content
while (_client.available())
{
// If no data is received for the duration of _timeout, it times out
if (millis() - startMillis > _downloadTimeout * 1000)
{
Serial.println("Data inactivity timeout!");
_currentState = HttpsState::ERROR;
dest.close();
close();
return;
}
uint32_t bytesAvailable = _client.available();
int bytesToRead = min(bytesAvailable, BUFFER_SIZE);
int bytesReadFromClient = _client.read(buffer, bytesToRead);
dest.write(buffer, bytesReadFromClient);
bytesRead += bytesReadFromClient;
}
if (_progressCallback)
{
_progressCallback(bytesRead, contentLength);
}
if (bytesRead >= contentLength)
{
dest.close();
_currentState = HttpsState::COMPLETE;
close();
return;
}
}
}
String HTTPSHandler::_extractFileName(const String &fullPath)
{
int lastSlashIndex = fullPath.lastIndexOf('/');
if (lastSlashIndex == -1)
{
// No slashes found, return the full path itself.
return fullPath;
}
// Return the substring from the last slash onwards.
return fullPath.substring(lastSlashIndex + 1);
}
String HTTPSHandler::_createBoundary(const String &fileNameToUpload)
{
// Build the multipart form data request
String boundary = "--" + _BOUNDARY + "\r\n";
boundary += "Content-Disposition: form-data; name=\"fileToUpload\"; filename=\"" + fileNameToUpload + "\"\r\n";
boundary += "Content-Type: application/json\r\n";
boundary += "\r\n";
return boundary;
}
bool HTTPSHandler::uploadFile(const char *destPath, File &source, const String &fileName)
{
String fileNameToUpload = fileName == "" ? _extractFileName(source.name()) : fileName;
Serial.println("Uploading file: " + fileNameToUpload);
// Needed for header
String boundary = _createBoundary(fileNameToUpload);
String endBoundary = "\r\n--" + _BOUNDARY + "--\r\n";
// Calculate content length
int contentLength = boundary.length() + endBoundary.length() + source.size();
// Start sending the request
_client.println("POST " + String(destPath) + " HTTP/1.1");
_client.println("Host: appdig.com");
if (_customHeader.length() > 0)
{
_client.println(_customHeader);
}
_client.print("Content-Length: " + String(contentLength) + "\r\n");
_client.println("Content-Type: multipart/form-data; boundary=" + _BOUNDARY);
_client.println();
_client.print(boundary);
const int CHUNK_SIZE = 1024;
const long startTime = millis();
// Start sending body in chunks
while (source.available())
{
// Check for timeout
if (millis() - startTime > _responseTimeout * 1000)
{
Serial.println("Timeout while trying to get Content-Length!");
_currentState = HttpsState::ERROR;
return 0;
}
byte buffer[CHUNK_SIZE];
int bytesRead = source.read(buffer, CHUNK_SIZE);
_client.write(buffer, bytesRead);
if (_progressCallback)
{
_progressCallback(bytesRead, contentLength);
}
}
_client.print(endBoundary);
_currentState = HttpsState::COMPLETE;
// give the server some time to process the request
// update in the future to handle response from server
delay(10);
close();
return 1;
}
bool HTTPSHandler::postRequest(const char *destPath, const String &postData)
{
_client.println("POST " + String(destPath) + " HTTP/1.1");
_client.println("Host: " + String(_host));
_client.println("Content-Length: " + String(postData.length()));
_client.println("Connection: close");
_client.println();
_client.println(postData);
_currentState = HttpsState::WAITING;
_requestType = RequestType::POST;
return 1;
}
void HTTPSHandler::_processResponse()
{
unsigned long startMillis = millis();
// Read available data from the client and append to _response
while (_client.available())
{
// If no data is received for the duration of _timeout, it times out
if (millis() - startMillis > _responseTimeout * 1000)
{
Serial.println("Data inactivity timeout");
_currentState = HttpsState::ERROR;
close();
return;
}
char c = _client.read();
Serial.print(c);
_response += c;
if (_response.endsWith("\r\n0\r\n"))
{
break;
}
}
if (!_client.connected() || _response.endsWith("\r\n0\r\n"))
{
_currentState = HttpsState::COMPLETE;
}
close();
}
void HTTPSHandler::setTarget(const char *sourcePath, File &destFile)
{
_path = sourcePath;
_destinationFile = destFile;
_currentState = HttpsState::WAITING;
getRequest(_path);
_requestType = RequestType::DOWNLOAD;
}
void HTTPSHandler::advanceStateMachine()
{
static int lastState = -1;
if (lastState != static_cast<int>(_currentState))
{
Serial.println("HTTPSHandler state changed to " + static_cast<int>(_currentState));
lastState = static_cast<int>(_currentState);
}
switch (_currentState)
{
case HttpsState::CONNECTING:
if (!_client.connected())
{
Serial.println("Could not connect to server");
_currentState = HttpsState::ERROR;
}
_currentState = HttpsState::WAITING;
break;
case HttpsState::WAITING:
switch (_requestType)
{
case RequestType::DOWNLOAD:
_processDownload(_destinationFile);
break;
default:
_processResponse();
break;
}
break;
case HttpsState::ERROR:
_currentState = HttpsState::IDLE;
break;
case HttpsState::COMPLETE:
_currentState = HttpsState::IDLE;
break;
case HttpsState::IDLE:
break;
default:
break;
}
}
// void HTTPSHandler::get(const String &url, Callback callback)
// {
// if (!_client.connect(_host, _port))
// {
// Serial.println("Could not connect to server");
// return;
// }
// _client.println("GET / HTTP/1.1");
// _client.println("HOST appdig.com");
// _client.println("Connection: close");
// _requestStarted = 1;
// _callback = callback;
// }
void HTTPSHandler::close()
{
if (_client.connected())
{
_client.stop();
}
}
HTTPSHandler::~HTTPSHandler()
{
close();
}