Skip to content

Commit

Permalink
feat: shared HTTP code
Browse files Browse the repository at this point in the history
  • Loading branch information
vlastahajek committed Sep 3, 2021
1 parent bcf7b07 commit 9f7f857
Show file tree
Hide file tree
Showing 10 changed files with 480 additions and 330 deletions.
204 changes: 204 additions & 0 deletions src/HTTPService.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@

#include "HTTPService.h"
#include "Platform.h"
#include "Version.h"

// Uncomment bellow in case of a problem and rebuild sketch
//#define INFLUXDB_CLIENT_DEBUG_ENABLE
#include "util/debug.h"

static const char UserAgent[] PROGMEM = "influxdb-client-arduino/" INFLUXDB_CLIENT_VERSION " (" INFLUXDB_CLIENT_PLATFORM " " INFLUXDB_CLIENT_PLATFORM_VERSION ")";

#if defined(ESP8266)
bool checkMFLN(BearSSL::WiFiClientSecure *client, String url);
#endif

// This cannot be put to PROGMEM due to the way how it is used
static const char *RetryAfter = "Retry-After";
const char *TransferEncoding = "Transfer-Encoding";

HTTPService::HTTPService(const String &serverUrl, const String &authToken, const char *certInfo, bool insecure) {
_authToken = authToken;
_apiURL = serverUrl;
_apiURL += "/api/v2/";
bool https = serverUrl.startsWith("https");
if(https) {
#if defined(ESP8266)
BearSSL::WiFiClientSecure *wifiClientSec = new BearSSL::WiFiClientSecure;
if (insecure) {
wifiClientSec->setInsecure();
} else if(certInfo && strlen_P(certInfo) > 0) {
if(strlen_P(certInfo) > 60 ) { //differentiate fingerprint and cert
_cert = new BearSSL::X509List(certInfo);
wifiClientSec->setTrustAnchors(_cert);
} else {
wifiClientSec->setFingerprint(certInfo);
}
}
checkMFLN(wifiClientSec, serverUrl);
#elif defined(ESP32)
WiFiClientSecure *wifiClientSec = new WiFiClientSecure;
if (insecure) {
#ifndef ARDUINO_ESP32_RELEASE_1_0_4
// This works only in ESP32 SDK 1.0.5 and higher
wifiClientSec->setInsecure();
#endif
} else if(_certInfo && strlen_P(certInfo) > 0) {
wifiClientSec->setCACert(certInfo);
}
#endif
_wifiClient = wifiClientSec;
} else {
_wifiClient = new WiFiClient;
}
if(!_httpClient) {
_httpClient = new HTTPClient;
}
_httpClient->setReuse(_httpOptions._connectionReuse);

_httpClient->setUserAgent(FPSTR(UserAgent));
};

HTTPService::~HTTPService() {
if(_httpClient) {
delete _httpClient;
_httpClient = nullptr;
}
if(_wifiClient) {
delete _wifiClient;
_wifiClient = nullptr;
}
#if defined(ESP8266)
if(_cert) {
delete _cert;
_cert = nullptr;
}
#endif
_lastStatusCode = 0;
_lastErrorResponse = "";
}


void HTTPService::setHTTPOptions(const HTTPOptions & httpOptions) {
_httpOptions = httpOptions;
if(!_httpClient) {
_httpClient = new HTTPClient;
}
_httpClient->setReuse(_httpOptions._connectionReuse);
_httpClient->setTimeout(_httpOptions._httpReadTimeout);
#if defined(ESP32)
_httpClient->setConnectTimeout(_httpOptions._httpReadTimeout);
#endif
}

// parse URL for host and port and call probeMaxFragmentLength
#if defined(ESP8266)
bool checkMFLN(BearSSL::WiFiClientSecure *client, String url) {
int index = url.indexOf(':');
if(index < 0) {
return false;
}
String protocol = url.substring(0, index);
int port = -1;
url.remove(0, (index + 3)); // remove http:// or https://

if (protocol == "http") {
// set default port for 'http'
port = 80;
} else if (protocol == "https") {
// set default port for 'https'
port = 443;
} else {
return false;
}
index = url.indexOf('/');
String host = url.substring(0, index);
url.remove(0, index); // remove host
// check Authorization
index = host.indexOf('@');
if(index >= 0) {
host.remove(0, index + 1); // remove auth part including @
}
// get port
index = host.indexOf(':');
if(index >= 0) {
String portS = host;
host = host.substring(0, index); // hostname
portS.remove(0, (index + 1)); // remove hostname + :
port = portS.toInt(); // get port
}
INFLUXDB_CLIENT_DEBUG("[D] probeMaxFragmentLength to %s:%d\n", host.c_str(), port);
bool mfln = client->probeMaxFragmentLength(host, port, 1024);
INFLUXDB_CLIENT_DEBUG("[D] MFLN:%s\n", mfln ? "yes" : "no");
if (mfln) {
client->setBufferSizes(1024, 1024);
}
return mfln;
}
#endif //ESP8266

bool HTTPService::beforeRequest(const char *url) {
if(!_httpClient->begin(*_wifiClient, url)) {
_lastErrorResponse = F("begin failed");
return false;
}
if(_authToken.length() > 0) {
_httpClient->addHeader(F("Authorization"), "Token " + _authToken);
}
const char * headerKeys[] = {RetryAfter, TransferEncoding} ;
_httpClient->collectHeaders(headerKeys, 2);
return true;
}

bool HTTPService::doPOST(const char *url, const char *data, const char *contentType, int expectedCode, httpResponseCallback cb) {
INFLUXDB_CLIENT_DEBUG("[D] POST request - %s, data: %dbytes, type %s\n", url, strlen(data), contentType);
if(!beforeRequest(url)) {
return false;
}
if(contentType) {
_httpClient->addHeader(F("Content-Type"), FPSTR(contentType));
}
_lastStatusCode = _httpClient->POST((uint8_t *) data, strlen(data));
return afterRequest(expectedCode, cb);
}

bool HTTPService::doGET(const char *url, int expectedCode, httpResponseCallback cb) {
INFLUXDB_CLIENT_DEBUG("[D] GET request - %s\n", url);
if(!beforeRequest(url)) {
return false;
}
_lastStatusCode = _httpClient->GET();
return afterRequest(expectedCode, cb, false);
}

bool HTTPService::afterRequest(int expectedStatusCode, httpResponseCallback cb, bool modifyLastConnStatus) {
if(modifyLastConnStatus) {
_lastRequestTime = millis();
INFLUXDB_CLIENT_DEBUG("[D] HTTP status code - %d\n", _lastStatusCode);
_lastRetryAfter = 0;
if(_lastStatusCode >= 429) { //retryable server errors
if(_httpClient->hasHeader(RetryAfter)) {
_lastRetryAfter = _httpClient->header(RetryAfter).toInt();
INFLUXDB_CLIENT_DEBUG("[D] Reply after - %d\n", _lastRetryAfter);
}
}
}
_lastErrorResponse = "";
bool ret = _lastStatusCode == expectedStatusCode;
bool endConnection = true;
if(!ret) {
if(_lastStatusCode > 0) {
_lastErrorResponse = _httpClient->getString();
INFLUXDB_CLIENT_DEBUG("[D] Response:\n%s\n", _lastErrorResponse.c_str());
} else {
_lastErrorResponse = _httpClient->errorToString(_lastStatusCode);
INFLUXDB_CLIENT_DEBUG("[E] Error - %s\n", _lastErrorResponse.c_str());
}
} else if(cb){
endConnection = cb(_httpClient);
}
if(endConnection) {
_httpClient->end();
}
return ret;
}
73 changes: 73 additions & 0 deletions src/HTTPService.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#ifndef _HTTP_SERVICE_H_
#define _HTTP_SERVICE_H_

#include <Arduino.h>
#if defined(ESP8266)
# include <WiFiClientSecureBearSSL.h>
# include <ESP8266HTTPClient.h>
#elif defined(ESP32)
# include <HTTPClient.h>
#else
# error "This library currently supports only ESP8266 and ESP32."
#endif
#include "Options.h"

class Test;

typedef std::function<bool(HTTPClient *client)> httpResponseCallback;
extern const char *TransferEncoding;

class HTTPService {
friend class Test;
private:
// Server API URL
String _apiURL;
// authetication token
String _authToken;
// Last time in ms we made are a request to server
uint32_t _lastRequestTime = 0;
// HTTP status code of last request to server
int _lastStatusCode = 0;
// Server reponse or library error message for last failed request
String _lastErrorResponse;
// Underlying HTTPClient instance
HTTPClient *_httpClient = nullptr;
// Underlying connection object
WiFiClient *_wifiClient = nullptr;
// Certificate info
const char *_certInfo = nullptr;
#ifdef ESP8266
BearSSL::X509List *_cert = nullptr;
#endif
// Store retry timeout suggested by server after last request
int _lastRetryAfter = 0;
// HTTP options
HTTPOptions _httpOptions;
protected:
// Sets request params
bool beforeRequest(const char *url);
// Handles response
bool afterRequest(int expectedStatusCode, httpResponseCallback cb, bool modifyLastConnStatus = true);
public:
HTTPService(const String &serverUrl, const String &authToken, const char *certInfo, bool insecure);
~HTTPService();
// Sets custom HTTP options. See HTTPOptions doc for more info.
// Must be called before calling any method initiating a connection to server.
// Example:
// service.setHTTPOptions(HTTPOptions().httpReadTimeout(20000)).
void setHTTPOptions(const HTTPOptions &httpOptions);
HTTPOptions &getHTTPOptions() { return _httpOptions; }
// Performs HTTP POST by sending data. On success calls response call back
bool doPOST(const char *url, const char *data, const char *contentType, int expectedCode, httpResponseCallback cb);
// Performs HTTP GET. On success calls response call back
bool doGET(const char *url, int expectedCode, httpResponseCallback cb);

String getServerAPIURL() const { return _apiURL; }
int getLastRetryAfter() const { return _lastRetryAfter; }
int getLastStatusCode() const { return _lastStatusCode; }
uint32_t getLastRequestTime() const { return _lastRequestTime; }
// Returns last response when operation failed
String getLastErrorMessage() const { return _lastErrorResponse; }
};

#endif //_HTTP_SERVICE_H_
4 changes: 4 additions & 0 deletions src/InfluxDb.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _INFLUXDB_H_
#define _INFLUXDB_H

#include "InfluxData.h"

class Influxdb : public InfluxDBClient {
Expand Down Expand Up @@ -53,3 +56,4 @@ class Influxdb : public InfluxDBClient {

void begin();
};
#endif
Loading

0 comments on commit 9f7f857

Please sign in to comment.