368 lines
9.7 KiB
C++
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();
|
|
}
|