From f69852b0a6097a4b1d43ee5612b73ee4b1e7d182 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Tue, 22 Sep 2015 09:05:00 +0200 Subject: [PATCH 1/3] Initial feature enhancement commit of my "MultiClient-HTTPClient" see README.md for more details --- HTTPClient.cpp | 80 ++++++++++++++++++++++++++++++++++++++------------ HTTPClient.h | 57 +++++++++++++++++++++++++++++++---- README.md | 27 ++++++++++++++++- 3 files changed, 138 insertions(+), 26 deletions(-) diff --git a/HTTPClient.cpp b/HTTPClient.cpp index faf1a70..4611867 100755 --- a/HTTPClient.cpp +++ b/HTTPClient.cpp @@ -27,6 +27,7 @@ #include #include #include "HTTPClient.h" +#include "EthernetClient.h" //a struct to store the uriEncoder & the handle to the http client typedef struct @@ -41,18 +42,58 @@ typedef struct #define URI_ALLOWED(byte) ((byte>='A' && byte<='Z') || (byte>='a' && byte<='z') || (byte>='0' && byte<='9') || byte == '-' || byte == '_' || byte == '.' || byte == '~') #define URI_RESERVED(byte) (byte == '!' || byte == '*' || byte == '\'' || byte == '(' || byte == ')' || byte == ';' || byte == ':' || byte == '&' || byte == '=' || byte == '+' || byte == '$' || byte == ',' || byte == '/' || byte == '?' || byte == '#' || byte == '[' || byte == ']') +HTTPClient::HTTPClient(char* host) : + _theClient(new EthernetClient()), _selfInitializedTheClient(true), hostName(host), debugCommunication(0), _useIp(false), port(80) +{ + //nothing else to do +} + +HTTPClient::HTTPClient(char* host, uint16_t port) : + _theClient(new EthernetClient()), _selfInitializedTheClient(true), hostName(host), debugCommunication(0), _useIp(false), port(port) +{ + //nothing else to do +} + HTTPClient::HTTPClient(char* host, uint8_t* ip) : - EthernetClient(), hostName(host), debugCommunication(0), ip(ip), port(80) + _theClient(new EthernetClient()), _selfInitializedTheClient(true), hostName(host), debugCommunication(0), ip(ip), _useIp(true), port(80) { //nothing else to do } HTTPClient::HTTPClient(char* host, uint8_t* ip, uint16_t port) : - EthernetClient(), hostName(host), debugCommunication(0), ip(ip), port(port) + _theClient(new EthernetClient()), _selfInitializedTheClient(true), hostName(host), debugCommunication(0), ip(ip), _useIp(true), port(port) { //nothing else to do } +HTTPClient::HTTPClient(Client& client, char* host) : + _theClient(&client), hostName(host), debugCommunication(0), _useIp(false), port(80) +{ + //nothing else to do +} + +HTTPClient::HTTPClient(Client& client, char* host, uint16_t port) : + _theClient(&client), hostName(host), debugCommunication(0), _useIp(false), port(port) +{ + //nothing else to do +} + +HTTPClient::HTTPClient(Client& client, char* host, uint8_t* ip) : + _theClient(&client), hostName(host), debugCommunication(0), ip(ip), _useIp(true), port(80) +{ + //nothing else to do +} + +HTTPClient::HTTPClient(Client& client, char* host, uint8_t* ip, uint16_t port) : + _theClient(&client), hostName(host), debugCommunication(0), ip(ip), _useIp(true), port(port) +{ + //nothing else to do +} + +HTTPClient::~HTTPClient() { + if (_selfInitializedTheClient) delete _theClient; +} + FILE* HTTPClient::getURI(char* uri) { @@ -184,11 +225,12 @@ HTTPClient::openClientFile() fdev_set_udata(result,udata); udata->client = this; udata->encode = 0; - if (connected()) - { - stop(); - } - if (connect(ip,port)) + _theClient->stop(); + + int connectRes; + if (_useIp) connectRes=_theClient->connect(ip,port); + else connectRes=_theClient->connect(hostName,port); + if (connectRes) { return result; } @@ -283,14 +325,14 @@ HTTPClient::clientWrite(char byte, FILE* stream) } http_stream_udata* udata = (http_stream_udata*) fdev_get_udata(stream); HTTPClient* client = udata->client; - if (client->connected() == 0) + if (client->_theClient->connected() == 0) { closeStream(stream); return EOF; } if (udata->encode == 0) { - client->write(byte); + client->_theClient->write(byte); if (client->debugCommunication) { Serial.print(byte); @@ -301,7 +343,7 @@ HTTPClient::clientWrite(char byte, FILE* stream) if (URI_ALLOWED(byte) || ((URI_RESERVED(byte) && (udata->encode & URI_ENCODE_RESERVED) == 0))) { - client->write(byte); + client->_theClient->write(byte); if (client->debugCommunication) { Serial.print(byte); @@ -315,7 +357,7 @@ HTTPClient::clientWrite(char byte, FILE* stream) // Write only the first three bytes, not the trailing null for (char i = 0; i < 3; i++) { - client->write(encoded[i]); + client->_theClient->write(encoded[i]); if (client->debugCommunication) { Serial.print(encoded[i]); @@ -335,19 +377,19 @@ HTTPClient::clientRead(FILE* stream) } http_stream_udata* udata = (http_stream_udata*) fdev_get_udata(stream); HTTPClient* client = udata->client; - if (!client->connected()) + if (!client->_theClient->connected()) { return EOF; } //block until we got a byte - while (client->available() == 0) + while (client->_theClient->available() == 0) { - if (client->connected() == 0) + if (client->_theClient->connected() == 0) { return EOF; } }; - int result = client->read(); + int result = client->_theClient->read(); if (result == EOF) { return EOF; @@ -364,9 +406,9 @@ HTTPClient::clientRead(FILE* stream) else { //block until we got the needed bytes - while (client->available() >= 2) + while (client->_theClient->available() >= 2) { - if (client->connected() == 0) + if (client->_theClient->connected() == 0) { return EOF; } @@ -374,7 +416,7 @@ HTTPClient::clientRead(FILE* stream) char return_value = 0; for (char i = 0; i < 2; i++) { - result = client->read(); + result = client->_theClient->read(); if (result == EOF) { return EOF; @@ -431,7 +473,7 @@ HTTPClient::closeStream(FILE* stream) if (stream != NULL) { http_stream_udata* udata = (http_stream_udata*) fdev_get_udata(stream); - udata->client->stop(); + udata->client->_theClient->stop(); free(udata); fclose(stream); } diff --git a/HTTPClient.h b/HTTPClient.h index 63bbe7c..a464516 100755 --- a/HTTPClient.h +++ b/HTTPClient.h @@ -26,7 +26,7 @@ #define HTTPCLIENT_H_ #include -#include "EthernetClient.h" +#include "Client.h" /* This struct is used to pass parameters as URI paramters and additional HTTP headers. * normally you pass this as a array. The last entry must have the NULL-Pointer as name. @@ -43,18 +43,56 @@ typedef struct * To construct a HTTP client you have to provide the IP AND the name of the server Рelse * the virtual host management of most internet servers will fail. Sorry for the inconvenience. */ -class HTTPClient : private EthernetClient +class HTTPClient { public: /* - * create a HTTP client that connects to the default port 80. + * create a HTTP client that connects to the default port 80 using hostname (and dhcp lookup). + * An internal EthernetClient instance is used for the communication. + */ + HTTPClient(char* host); + /* + * create a HTTP client that connects to another port using hostname (and dhcp lookup). HTTPS is not supported. + * An internal EthernetClient instance is used for the communication. + */ + HTTPClient(char* host, uint16_t port); + /* + * create a HTTP Ethernet client that connects to the default port 80 using provided ip. hostname is used in HTTP message. + * An internal EthernetClient instance is used for the communication. */ HTTPClient(char* host, uint8_t* ip ); /* - * create a HTTP client that connects to another port. HTTPS is not supported. + * create a HTTP Ethernet client that connects to another port using provided ip. hostname is used in HTTP message. HTTPS is not supported. + * An internal EthernetClient instance is used for the communication. */ HTTPClient(char* host, uint8_t* ip, uint16_t port); + /* + * create a HTTP client that connects to the default port 80 using hostname (and dhcp lookup). + * The given instance of a Client (e.g. EthernetClient, WifiClient or such) is used for communication. + */ + HTTPClient(Client& client, char* host); + /* + * create a HTTP client that connects to another port using hostname (and dhcp lookup). HTTPS is not supported. + * The given instance of a Client (e.g. EthernetClient, WifiClient or such) is used for communication. + */ + HTTPClient(Client& client, char* host, uint16_t port); + /* + * create a HTTP client that connects to the default port 80 using provided ip. hostname is used in HTTP message.. + * The given instance of a Client (e.g. EthernetClient, WifiClient or such) is used for communication. + */ + HTTPClient(Client& client, char* host, uint8_t* ip ); + /* + * create a HTTP client that connects to another port using provided ip. hostname is used in HTTP message. HTTPS is not supported. + * The given instance of a Client (e.g. EthernetClient, WifiClient or such) is used for communication. + */ + HTTPClient(Client& client, char* host, uint8_t* ip, uint16_t port); + + /* + * the destructor + */ + ~HTTPClient(); + /* * Post a GET request to the server. * The result is a file handle or null is an error occured. @@ -160,12 +198,19 @@ class HTTPClient : private EthernetClient * And that is the internal stuff of the HTTP client */ private: + //client-instance to use + Client* _theClient; + //flag if Client was self-generated by compatibility constructors and needs to be removed + bool _selfInitializedTheClient=false; + //the name of the host we are talking to char* hostName; //the ip of the host uint8_t* ip; - //the port we are talking to - uint16_t port; + //use ip instead of hostname + bool _useIp; + //the port we are talking to + uint16_t port; //the HTTP return code of the last request int lastReturnCode; //print to serial? diff --git a/README.md b/README.md index 74d12bc..a944bde 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,29 @@ -# HTTPClient for Arduino +# HTTPClient for Arduino (Enhanced) + +## Not about this version: + +This is an enhanced version of the HTTPClient library originally from Interactive-Matter +(https://github.com/interactive-matter/HTTPClient). Additionally changes from Salanki's +fork (https://github.com/salanki/HTTPClient) were incorporated. + +The main differences are: +* The used communication client instance can be specified with new constructors. With + this it can be used with nearly any kind of Client-inherited class (e.g. EthernetClient, + WifiClient, GSMCLient and many more). The old constructor methods without providing a + Client instance are still available and an EthernetClient instance is created then. So + this version should be a drop-in replacement for the original "HTTPClient" +* Additionally some new constructors were introduced that allow the connection to be + done by just providing the hostname and DHCP is used then. To prevent DNS lookup and + such simply use the original constructor versions where IP is provided additionally + to Hostname - then the IP is used for the connection without the need of a lookup. +* My optimizations that are in Pull-request #15 on original library are incorporated as + well: + * make sure connection is correctly closed at end of communication so that reuse of + one Client instance is possible + * check success of parsing for last HTTP returncode and only set the variable on success + +### Open Todos +* add examples for new Client-Feature ... ## Overview From d6b15e0977f235ebcd8778a9d1ba85cfcfd49d70 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Fri, 6 Nov 2015 14:11:19 +0100 Subject: [PATCH 2/3] Checks and delay before reading header infos - Added some checks that stream variable is not NULL before using it (just to make sure) - To avoid header-read errors that happened sometimes implement a check for available data with a maximum timeout of 1000ms (configurable via defines in header file). With my default settings we wait 20 ms and then check again if at least 17 characters are available to read (which should be a normal HTTP-header first line). This is done at max 50 times before the read is tried --- HTTPClient.cpp | 21 ++++++++++++++++++++- HTTPClient.h | 9 ++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/HTTPClient.cpp b/HTTPClient.cpp index 4611867..2cec1cb 100755 --- a/HTTPClient.cpp +++ b/HTTPClient.cpp @@ -245,6 +245,10 @@ char HTTPClient::sendUriAndHeaders(FILE* stream, char* hostName, const char* requestType, char* uri, http_client_parameter parameters[], http_client_parameter headers[]) { + if (stream == NULL) + { + return 0; + } fprintf_P(stream, requestType, uri); fprintf_P(stream, PSTR(" "), uri); //encode but use reserved characters @@ -298,6 +302,10 @@ HTTPClient::sendUriAndHeaders(FILE* stream, char* hostName, const char* requestT char HTTPClient::sendContentPayload(FILE* stream, char* data) { + if (stream == NULL) + { + return 0; + } //calculate the content length int content_length = 0; if (data != NULL) @@ -444,9 +452,20 @@ HTTPClient::skipHeader(FILE* stream) //skip over the header int httpReturnCode; lastReturnCode = NULL; + + if (stream == NULL) return NULL; + http_stream_udata* udata = (http_stream_udata*) fdev_get_udata(stream); + HTTPClient* client = udata->client; + + int delayReadHeaderCount=0; + // check if at least the first HTTP-response available, up to 1000ms=1s + while ((client->_theClient->available()<17) && (delayReadHeaderCount++ -#include "Client.h" +#include + +/* With this we try a delay of 20 ms before checking again if content is available + * and we try this up to 50 times. + * So max: 20x50ms=1000ms=1s + */ +#define HTTPCLIENT_HEADER_READ_DELAY_ONCE 20 +#define HTTPCLIENT_HEADER_READ_DELAY_MAXCOUNT 50 /* This struct is used to pass parameters as URI paramters and additional HTTP headers. * normally you pass this as a array. The last entry must have the NULL-Pointer as name. From 09800e86a8f18748067203684e3667a87f0f1f38 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Wed, 11 Nov 2015 09:32:23 +0100 Subject: [PATCH 3/3] changed includes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit changed some includes … --- HTTPClient.cpp | 3 +-- HTTPClient.h | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HTTPClient.cpp b/HTTPClient.cpp index 2cec1cb..2f5419c 100755 --- a/HTTPClient.cpp +++ b/HTTPClient.cpp @@ -26,8 +26,7 @@ #include #include #include -#include "HTTPClient.h" -#include "EthernetClient.h" +#include //a struct to store the uriEncoder & the handle to the http client typedef struct diff --git a/HTTPClient.h b/HTTPClient.h index 3146dcf..a977798 100755 --- a/HTTPClient.h +++ b/HTTPClient.h @@ -27,6 +27,7 @@ #include #include +#include /* With this we try a delay of 20 ms before checking again if content is available * and we try this up to 50 times.