From 4c2316629d465b10aff903dccc3d150389ada3fe Mon Sep 17 00:00:00 2001 From: the-moog <3725802+the-moog@users.noreply.github.com> Date: Mon, 20 Mar 2023 15:56:41 +0000 Subject: [PATCH 1/2] Take current head and merge with forck in ArduinoWebsocketMQTTClient --- README.md | 57 +- library.properties | 20 +- src/ArduinoHttpClient.h | 24 +- src/HttpClient.cpp | 1724 +++++++++++++++++++-------------------- src/HttpClient.h | 808 +++++++++--------- src/URLEncoder.cpp | 110 +-- src/URLEncoder.h | 50 +- src/WebSocketClient.cpp | 759 ++++++++--------- src/WebSocketClient.h | 200 ++--- src/b64.cpp | 143 ++-- src/b64.h | 12 +- 11 files changed, 1975 insertions(+), 1932 deletions(-) mode change 100644 => 100755 README.md mode change 100644 => 100755 library.properties mode change 100644 => 100755 src/ArduinoHttpClient.h mode change 100644 => 100755 src/HttpClient.cpp mode change 100644 => 100755 src/HttpClient.h mode change 100644 => 100755 src/URLEncoder.cpp mode change 100644 => 100755 src/URLEncoder.h mode change 100644 => 100755 src/WebSocketClient.cpp mode change 100644 => 100755 src/WebSocketClient.h mode change 100644 => 100755 src/b64.cpp mode change 100644 => 100755 src/b64.h diff --git a/README.md b/README.md old mode 100644 new mode 100755 index e9c163b..2baa5fb --- a/README.md +++ b/README.md @@ -1,29 +1,28 @@ -# ArduinoHttpClient - -[![Check Arduino status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml) -[![Compile Examples status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/compile-examples.yml) -[![Spell Check status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml) - -ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino. - -Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/HttpClient) - -## Dependencies - -- Requires a networking hardware and a library that provides transport specific `Client` instance, such as: - - [WiFiNINA](https://github.com/arduino-libraries/WiFiNINA) - - [WiFi101](https://github.com/arduino-libraries/WiFi101) - - [Ethernet](https://github.com/arduino-libraries/Ethernet) - - [MKRGSM](https://github.com/arduino-libraries/MKRGSM) - - [MKRNB](https://github.com/arduino-libraries/MKRNB) - - [WiFi](https://github.com/arduino-libraries/WiFi) - - [GSM](https://github.com/arduino-libraries/GSM) - -## Usage - -In normal usage, handles the outgoing request and Host header. The returned status code is parsed for you, as is the Content-Length header (if present). - -Because it expects an object of type Client, you can use it with any of the networking classes that derive from that. Which means it will work with WiFiClient, EthernetClient and GSMClient. - -See the examples for more detail on how the library is used. - +# ArduinoHttpClient + +[![Check Arduino status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/check-arduino.yml) +[![Compile Examples status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/compile-examples.yml) +[![Spell Check status](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoHttpClient/actions/workflows/spell-check.yml) + +ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino. + +Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/HttpClient) + +## Dependencies + +- Requires a networking hardware and a library that provides transport specific `Client` instance, such as: + - [WiFiNINA](https://github.com/arduino-libraries/WiFiNINA) + - [WiFi101](https://github.com/arduino-libraries/WiFi101) + - [Ethernet](https://github.com/arduino-libraries/Ethernet) + - [MKRGSM](https://github.com/arduino-libraries/MKRGSM) + - [MKRNB](https://github.com/arduino-libraries/MKRNB) + - [WiFi](https://github.com/arduino-libraries/WiFi) + - [GSM](https://github.com/arduino-libraries/GSM) + +## Usage + +In normal usage, handles the outgoing request and Host header. The returned status code is parsed for you, as is the Content-Length header (if present). + +Because it expects an object of type Client, you can use it with any of the networking classes that derive from that. Which means it will work with WiFiClient, EthernetClient and GSMClient. + +See the examples for more detail on how the library is used. \ No newline at end of file diff --git a/library.properties b/library.properties old mode 100644 new mode 100755 index 2682599..6d04fd4 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ -name=ArduinoHttpClient -version=0.4.0 -author=Arduino -maintainer=Arduino -sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSockets. -paragraph=This library can be used for HTTP (GET, POST, PUT, DELETE) requests to a web server. It also supports exchanging messages with WebSocket servers. Based on Adrian McEwen's HttpClient library. -category=Communication -url=https://github.com/arduino-libraries/ArduinoHttpClient -architectures=* -includes=ArduinoHttpClient.h +name=ArduinoHttpClient +version=0.4.0 +author=Arduino +maintainer=Arduino +sentence=[EXPERIMENTAL] Easily interact with web servers from Arduino, using HTTP and WebSockets. +paragraph=This library can be used for HTTP (GET, POST, PUT, DELETE) requests to a web server. It also supports exchanging messages with WebSocket servers. Based on Adrian McEwen's HttpClient library. +category=Communication +url=https://github.com/arduino-libraries/ArduinoHttpClient +architectures=* +includes=ArduinoHttpClient.h \ No newline at end of file diff --git a/src/ArduinoHttpClient.h b/src/ArduinoHttpClient.h old mode 100644 new mode 100755 index abb8494..572a7fc --- a/src/ArduinoHttpClient.h +++ b/src/ArduinoHttpClient.h @@ -1,12 +1,12 @@ -// Library to simplify HTTP fetching on Arduino -// (c) Copyright Arduino. 2016 -// Released under Apache License, version 2.0 - -#ifndef ArduinoHttpClient_h -#define ArduinoHttpClient_h - -#include "HttpClient.h" -#include "WebSocketClient.h" -#include "URLEncoder.h" - -#endif +// Library to simplify HTTP fetching on Arduino +// (c) Copyright Arduino. 2016 +// Released under Apache License, version 2.0 + +#ifndef ArduinoHttpClient_h +#define ArduinoHttpClient_h + +#include "HttpClient.h" +#include "WebSocketClient.h" +#include "URLEncoder.h" + +#endif \ No newline at end of file diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp old mode 100644 new mode 100755 index 1c73464..459d724 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -1,862 +1,862 @@ -// Class to simplify HTTP fetching on Arduino -// (c) Copyright 2010-2011 MCQN Ltd -// Released under Apache License, version 2.0 - -#include "HttpClient.h" -#include "b64.h" - -// Initialize constants -const char* HttpClient::kUserAgent = "Arduino/2.2.0"; -const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; -const char* HttpClient::kTransferEncodingChunked = HTTP_HEADER_TRANSFER_ENCODING ": " HTTP_HEADER_VALUE_CHUNKED; - -HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort) - : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort), - iConnectionClose(true), iSendDefaultRequestHeaders(true) -{ - resetState(); -} - -HttpClient::HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort) - : HttpClient(aClient, aServerName.c_str(), aServerPort) -{ -} - -HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) - : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort), - iConnectionClose(true), iSendDefaultRequestHeaders(true) -{ - resetState(); -} - -void HttpClient::resetState() -{ - iState = eIdle; - iStatusCode = 0; - iContentLength = kNoContentLengthHeader; - iBodyLengthConsumed = 0; - iContentLengthPtr = kContentLengthPrefix; - iTransferEncodingChunkedPtr = kTransferEncodingChunked; - iIsChunked = false; - iChunkLength = 0; - iHttpResponseTimeout = kHttpResponseTimeout; -} - -void HttpClient::stop() -{ - iClient->stop(); - resetState(); -} - -void HttpClient::connectionKeepAlive() -{ - iConnectionClose = false; -} - -void HttpClient::noDefaultRequestHeaders() -{ - iSendDefaultRequestHeaders = false; -} - -void HttpClient::beginRequest() -{ - iState = eRequestStarted; -} - -int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, - const char* aContentType, int aContentLength, const byte aBody[]) -{ - if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk) - { - flushClientRx(); - - resetState(); - } - - tHttpState initialState = iState; - - if ((eIdle != iState) && (eRequestStarted != iState)) - { - return HTTP_ERROR_API; - } - - if (iConnectionClose || !iClient->connected()) - { - if (iServerName) - { - if (!(iClient->connect(iServerName, iServerPort) > 0)) - { -#ifdef LOGGING - Serial.println("Connection failed"); -#endif - return HTTP_ERROR_CONNECTION_FAILED; - } - } - else - { - if (!(iClient->connect(iServerAddress, iServerPort) > 0)) - { -#ifdef LOGGING - Serial.println("Connection failed"); -#endif - return HTTP_ERROR_CONNECTION_FAILED; - } - } - } - else - { -#ifdef LOGGING - Serial.println("Connection already open"); -#endif - } - - // Now we're connected, send the first part of the request - int ret = sendInitialHeaders(aURLPath, aHttpMethod); - - if (HTTP_SUCCESS == ret) - { - if (aContentType) - { - sendHeader(HTTP_HEADER_CONTENT_TYPE, aContentType); - } - - if (aContentLength > 0) - { - sendHeader(HTTP_HEADER_CONTENT_LENGTH, aContentLength); - } - - bool hasBody = (aBody && aContentLength > 0); - - if (initialState == eIdle || hasBody) - { - // This was a simple version of the API, so terminate the headers now - finishHeaders(); - } - // else we'll call it in endRequest or in the first call to print, etc. - - if (hasBody) - { - write(aBody, aContentLength); - } - } - - return ret; -} - -int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod) -{ -#ifdef LOGGING - Serial.println("Connected"); -#endif - // Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0" - iClient->print(aHttpMethod); - iClient->print(" "); - - iClient->print(aURLPath); - iClient->println(" HTTP/1.1"); - if (iSendDefaultRequestHeaders) - { - // The host header, if required - if (iServerName) - { - iClient->print("Host: "); - iClient->print(iServerName); - if (iServerPort != kHttpPort) - { - iClient->print(":"); - iClient->print(iServerPort); - } - iClient->println(); - } - // And user-agent string - sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); - } - - if (iConnectionClose) - { - // Tell the server to - // close this connection after we're done - sendHeader(HTTP_HEADER_CONNECTION, "close"); - } - - // Everything has gone well - iState = eRequestStarted; - return HTTP_SUCCESS; -} - -void HttpClient::sendHeader(const char* aHeader) -{ - iClient->println(aHeader); -} - -void HttpClient::sendHeader(const char* aHeaderName, const char* aHeaderValue) -{ - iClient->print(aHeaderName); - iClient->print(": "); - iClient->println(aHeaderValue); -} - -void HttpClient::sendHeader(const char* aHeaderName, const int aHeaderValue) -{ - iClient->print(aHeaderName); - iClient->print(": "); - iClient->println(aHeaderValue); -} - -void HttpClient::sendBasicAuth(const char* aUser, const char* aPassword) -{ - // Send the initial part of this header line - iClient->print("Authorization: Basic "); - // Now Base64 encode "aUser:aPassword" and send that - // This seems trickier than it should be but it's mostly to avoid either - // (a) some arbitrarily sized buffer which hopes to be big enough, or - // (b) allocating and freeing memory - // ...so we'll loop through 3 bytes at a time, outputting the results as we - // go. - // In Base64, each 3 bytes of unencoded data become 4 bytes of encoded data - unsigned char input[3]; - unsigned char output[5]; // Leave space for a '\0' terminator so we can easily print - int userLen = strlen(aUser); - int passwordLen = strlen(aPassword); - int inputOffset = 0; - for (int i = 0; i < (userLen+1+passwordLen); i++) - { - // Copy the relevant input byte into the input - if (i < userLen) - { - input[inputOffset++] = aUser[i]; - } - else if (i == userLen) - { - input[inputOffset++] = ':'; - } - else - { - input[inputOffset++] = aPassword[i-(userLen+1)]; - } - // See if we've got a chunk to encode - if ( (inputOffset == 3) || (i == userLen+passwordLen) ) - { - // We've either got to a 3-byte boundary, or we've reached then end - b64_encode(input, inputOffset, output, 4); - // NUL-terminate the output string - output[4] = '\0'; - // And write it out - iClient->print((char*)output); -// FIXME We might want to fill output with '=' characters if b64_encode doesn't -// FIXME do it for us when we're encoding the final chunk - inputOffset = 0; - } - } - // And end the header we've sent - iClient->println(); -} - -void HttpClient::finishHeaders() -{ - iClient->println(); - iState = eRequestSent; -} - -void HttpClient::flushClientRx() -{ - while (iClient->available()) - { - iClient->read(); - } -} - -void HttpClient::endRequest() -{ - beginBody(); -} - -void HttpClient::beginBody() -{ - if (iState < eRequestSent) - { - // We still need to finish off the headers - finishHeaders(); - } - // else the end of headers has already been sent, so nothing to do here -} - -int HttpClient::get(const char* aURLPath) -{ - return startRequest(aURLPath, HTTP_METHOD_GET); -} - -int HttpClient::get(const String& aURLPath) -{ - return get(aURLPath.c_str()); -} - -int HttpClient::post(const char* aURLPath) -{ - return startRequest(aURLPath, HTTP_METHOD_POST); -} - -int HttpClient::post(const String& aURLPath) -{ - return post(aURLPath.c_str()); -} - -int HttpClient::post(const char* aURLPath, const char* aContentType, const char* aBody) -{ - return post(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); -} - -int HttpClient::post(const String& aURLPath, const String& aContentType, const String& aBody) -{ - return post(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); -} - -int HttpClient::post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) -{ - return startRequest(aURLPath, HTTP_METHOD_POST, aContentType, aContentLength, aBody); -} - -int HttpClient::put(const char* aURLPath) -{ - return startRequest(aURLPath, HTTP_METHOD_PUT); -} - -int HttpClient::put(const String& aURLPath) -{ - return put(aURLPath.c_str()); -} - -int HttpClient::put(const char* aURLPath, const char* aContentType, const char* aBody) -{ - return put(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); -} - -int HttpClient::put(const String& aURLPath, const String& aContentType, const String& aBody) -{ - return put(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); -} - -int HttpClient::put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) -{ - return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody); -} - -int HttpClient::patch(const char* aURLPath) -{ - return startRequest(aURLPath, HTTP_METHOD_PATCH); -} - -int HttpClient::patch(const String& aURLPath) -{ - return patch(aURLPath.c_str()); -} - -int HttpClient::patch(const char* aURLPath, const char* aContentType, const char* aBody) -{ - return patch(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); -} - -int HttpClient::patch(const String& aURLPath, const String& aContentType, const String& aBody) -{ - return patch(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); -} - -int HttpClient::patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) -{ - return startRequest(aURLPath, HTTP_METHOD_PATCH, aContentType, aContentLength, aBody); -} - -int HttpClient::del(const char* aURLPath) -{ - return startRequest(aURLPath, HTTP_METHOD_DELETE); -} - -int HttpClient::del(const String& aURLPath) -{ - return del(aURLPath.c_str()); -} - -int HttpClient::del(const char* aURLPath, const char* aContentType, const char* aBody) -{ - return del(aURLPath, aContentType, strlen(aBody), (const byte*)aBody); -} - -int HttpClient::del(const String& aURLPath, const String& aContentType, const String& aBody) -{ - return del(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str()); -} - -int HttpClient::del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]) -{ - return startRequest(aURLPath, HTTP_METHOD_DELETE, aContentType, aContentLength, aBody); -} - -int HttpClient::responseStatusCode() -{ - if (iState < eRequestSent) - { - return HTTP_ERROR_API; - } - // The first line will be of the form Status-Line: - // HTTP-Version SP Status-Code SP Reason-Phrase CRLF - // Where HTTP-Version is of the form: - // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT - - int c = '\0'; - do - { - // Make sure the status code is reset, and likewise the state. This - // lets us easily cope with 1xx informational responses by just - // ignoring them really, and reading the next line for a proper response - iStatusCode = 0; - iState = eRequestSent; - - unsigned long timeoutStart = millis(); - // Psuedo-regexp we're expecting before the status-code - const char* statusPrefix = "HTTP/*.* "; - const char* statusPtr = statusPrefix; - // Whilst we haven't timed out & haven't reached the end of the headers - while ((c != '\n') && - ( (millis() - timeoutStart) < iHttpResponseTimeout )) - { - if (available()) - { - c = read(); - if (c != -1) - { - switch(iState) - { - case eRequestSent: - // We haven't reached the status code yet - if ( (*statusPtr == '*') || (*statusPtr == c) ) - { - // This character matches, just move along - statusPtr++; - if (*statusPtr == '\0') - { - // We've reached the end of the prefix - iState = eReadingStatusCode; - } - } - else - { - return HTTP_ERROR_INVALID_RESPONSE; - } - break; - case eReadingStatusCode: - if (isdigit(c)) - { - // This assumes we won't get more than the 3 digits we - // want - iStatusCode = iStatusCode*10 + (c - '0'); - } - else - { - // We've reached the end of the status code - // We could sanity check it here or double-check for ' ' - // rather than anything else, but let's be lenient - iState = eStatusCodeRead; - } - break; - case eStatusCodeRead: - // We're just waiting for the end of the line now - break; - - default: - break; - }; - // We read something, reset the timeout counter - timeoutStart = millis(); - } - } - else - { - // We haven't got any data, so let's pause to allow some to - // arrive - delay(kHttpWaitForDataDelay); - } - } - if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) ) - { - // We've reached the end of an informational status line - c = '\0'; // Clear c so we'll go back into the data reading loop - } - } - // If we've read a status code successfully but it's informational (1xx) - // loop back to the start - while ( (iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101) ); - - if ( (c == '\n') && (iState == eStatusCodeRead) ) - { - // We've read the status-line successfully - return iStatusCode; - } - else if (c != '\n') - { - // We must've timed out before we reached the end of the line - return HTTP_ERROR_TIMED_OUT; - } - else - { - // This wasn't a properly formed status line, or at least not one we - // could understand - return HTTP_ERROR_INVALID_RESPONSE; - } -} - -int HttpClient::skipResponseHeaders() -{ - // Just keep reading until we finish reading the headers or time out - unsigned long timeoutStart = millis(); - // Whilst we haven't timed out & haven't reached the end of the headers - while ((!endOfHeadersReached()) && - ( (millis() - timeoutStart) < iHttpResponseTimeout )) - { - if (available()) - { - (void)readHeader(); - // We read something, reset the timeout counter - timeoutStart = millis(); - } - else - { - // We haven't got any data, so let's pause to allow some to - // arrive - delay(kHttpWaitForDataDelay); - } - } - if (endOfHeadersReached()) - { - // Success - return HTTP_SUCCESS; - } - else - { - // We must've timed out - return HTTP_ERROR_TIMED_OUT; - } -} - -bool HttpClient::endOfHeadersReached() -{ - return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk); -}; - -int HttpClient::contentLength() -{ - // skip the response headers, if they haven't been read already - if (!endOfHeadersReached()) - { - skipResponseHeaders(); - } - - return iContentLength; -} - -String HttpClient::responseBody() -{ - int bodyLength = contentLength(); - String response; - - if (bodyLength > 0) - { - // try to reserve bodyLength bytes - if (response.reserve(bodyLength) == 0) { - // String reserve failed - return String((const char*)NULL); - } - } - - // keep on timedRead'ing, until: - // - we have a content length: body length equals consumed or no bytes - // available - // - no content length: no bytes are available - while (iBodyLengthConsumed != bodyLength) - { - int c = timedRead(); - - if (c == -1) { - // read timed out, done - break; - } - - if (!response.concat((char)c)) { - // adding char failed - return String((const char*)NULL); - } - } - - if (bodyLength > 0 && (unsigned int)bodyLength != response.length()) { - // failure, we did not read in response content length bytes - return String((const char*)NULL); - } - - return response; -} - -bool HttpClient::endOfBodyReached() -{ - if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader)) - { - // We've got to the body and we know how long it will be - return (iBodyLengthConsumed >= contentLength()); - } - return false; -} - -int HttpClient::available() -{ - if (iState == eReadingChunkLength) - { - while (iClient->available()) - { - char c = iClient->read(); - - if (c == '\n') - { - iState = eReadingBodyChunk; - break; - } - else if (c == '\r') - { - // no-op - } - else if (isHexadecimalDigit(c)) - { - char digit[2] = {c, '\0'}; - - iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16); - } - } - } - - if (iState == eReadingBodyChunk && iChunkLength == 0) - { - iState = eReadingChunkLength; - } - - if (iState == eReadingChunkLength) - { - return 0; - } - - int clientAvailable = iClient->available(); - - if (iState == eReadingBodyChunk) - { - return min(clientAvailable, iChunkLength); - } - else - { - return clientAvailable; - } -} - - -int HttpClient::read() -{ - if (iIsChunked && !available()) - { - return -1; - } - - int ret = iClient->read(); - if (ret >= 0) - { - if (endOfHeadersReached() && iContentLength > 0) - { - // We're outputting the body now and we've seen a Content-Length header - // So keep track of how many bytes are left - iBodyLengthConsumed++; - } - - if (iState == eReadingBodyChunk) - { - iChunkLength--; - - if (iChunkLength == 0) - { - iState = eReadingChunkLength; - } - } - } - return ret; -} - -bool HttpClient::headerAvailable() -{ - // clear the currently stored header line - iHeaderLine = ""; - - while (!endOfHeadersReached()) - { - // read a byte from the header - int c = readHeader(); - - if (c == '\r' || c == '\n') - { - if (iHeaderLine.length()) - { - // end of the line, all done - break; - } - else - { - // ignore any CR or LF characters - continue; - } - } - - // append byte to header line - iHeaderLine += (char)c; - } - - return (iHeaderLine.length() > 0); -} - -String HttpClient::readHeaderName() -{ - int colonIndex = iHeaderLine.indexOf(':'); - - if (colonIndex == -1) - { - return ""; - } - - return iHeaderLine.substring(0, colonIndex); -} - -String HttpClient::readHeaderValue() -{ - int colonIndex = iHeaderLine.indexOf(':'); - int startIndex = colonIndex + 1; - - if (colonIndex == -1) - { - return ""; - } - - // trim any leading whitespace - while (startIndex < (int)iHeaderLine.length() && isSpace(iHeaderLine[startIndex])) - { - startIndex++; - } - - return iHeaderLine.substring(startIndex); -} - -int HttpClient::read(uint8_t *buf, size_t size) -{ - int ret =iClient->read(buf, size); - if (endOfHeadersReached() && iContentLength > 0) - { - // We're outputting the body now and we've seen a Content-Length header - // So keep track of how many bytes are left - if (ret >= 0) - { - iBodyLengthConsumed += ret; - } - } - return ret; -} - -int HttpClient::readHeader() -{ - char c = read(); - - if (endOfHeadersReached()) - { - // We've passed the headers, but rather than return an error, we'll just - // act as a slightly less efficient version of read() - return c; - } - - // Whilst reading out the headers to whoever wants them, we'll keep an - // eye out for the "Content-Length" header - switch(iState) - { - case eStatusCodeRead: - // We're at the start of a line, or somewhere in the middle of reading - // the Content-Length prefix - if (*iContentLengthPtr == c) - { - // This character matches, just move along - iContentLengthPtr++; - if (*iContentLengthPtr == '\0') - { - // We've reached the end of the prefix - iState = eReadingContentLength; - // Just in case we get multiple Content-Length headers, this - // will ensure we just get the value of the last one - iContentLength = 0; - iBodyLengthConsumed = 0; - } - } - else if (*iTransferEncodingChunkedPtr == c) - { - // This character matches, just move along - iTransferEncodingChunkedPtr++; - if (*iTransferEncodingChunkedPtr == '\0') - { - // We've reached the end of the Transfer Encoding: chunked header - iIsChunked = true; - iState = eSkipToEndOfHeader; - } - } - else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r')) - { - // We've found a '\r' at the start of a line, so this is probably - // the end of the headers - iState = eLineStartingCRFound; - } - else - { - // This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line - iState = eSkipToEndOfHeader; - } - break; - case eReadingContentLength: - if (isdigit(c)) - { - iContentLength = iContentLength*10 + (c - '0'); - } - else - { - // We've reached the end of the content length - // We could sanity check it here or double-check for "\r\n" - // rather than anything else, but let's be lenient - iState = eSkipToEndOfHeader; - } - break; - case eLineStartingCRFound: - if (c == '\n') - { - if (iIsChunked) - { - iState = eReadingChunkLength; - iChunkLength = 0; - } - else - { - iState = eReadingBody; - } - } - break; - default: - // We're just waiting for the end of the line now - break; - }; - - if ( (c == '\n') && !endOfHeadersReached() ) - { - // We've got to the end of this line, start processing again - iState = eStatusCodeRead; - iContentLengthPtr = kContentLengthPrefix; - iTransferEncodingChunkedPtr = kTransferEncodingChunked; - } - // And return the character read to whoever wants it - return c; -} - - - +// Class to simplify HTTP fetching on Arduino +// (c) Copyright 2010-2011 MCQN Ltd +// Released under Apache License, version 2.0 + +#include "HttpClient.h" +#include "b64.h" + +// Initialize constants +const char *HttpClient::kUserAgent = "Arduino/2.2.0"; +const char *HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; +const char *HttpClient::kTransferEncodingChunked = HTTP_HEADER_TRANSFER_ENCODING ": " HTTP_HEADER_VALUE_CHUNKED; + +HttpClient::HttpClient(Client &aClient, const char *aServerName, uint16_t aServerPort) + : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort), + iConnectionClose(true), iSendDefaultRequestHeaders(true) +{ + resetState(); +} + +HttpClient::HttpClient(Client &aClient, const String &aServerName, uint16_t aServerPort) + : HttpClient(aClient, aServerName.c_str(), aServerPort) +{ +} + +HttpClient::HttpClient(Client &aClient, const IPAddress &aServerAddress, uint16_t aServerPort) + : iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort), + iConnectionClose(true), iSendDefaultRequestHeaders(true) +{ + resetState(); +} + +void HttpClient::resetState() +{ + iState = eIdle; + iStatusCode = 0; + iContentLength = kNoContentLengthHeader; + iBodyLengthConsumed = 0; + iContentLengthPtr = kContentLengthPrefix; + iTransferEncodingChunkedPtr = kTransferEncodingChunked; + iIsChunked = false; + iChunkLength = 0; + iHttpResponseTimeout = kHttpResponseTimeout; +} + +void HttpClient::stop() +{ + iClient->stop(); + resetState(); +} + +void HttpClient::connectionKeepAlive() +{ + iConnectionClose = false; +} + +void HttpClient::noDefaultRequestHeaders() +{ + iSendDefaultRequestHeaders = false; +} + +void HttpClient::beginRequest() +{ + iState = eRequestStarted; +} + +int HttpClient::startRequest(const char *aURLPath, const char *aHttpMethod, + const char *aContentType, int aContentLength, const byte aBody[]) +{ + if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk) + { + flushClientRx(); + + resetState(); + } + + tHttpState initialState = iState; + + if ((eIdle != iState) && (eRequestStarted != iState)) + { + return HTTP_ERROR_API; + } + + if (iConnectionClose || !iClient->connected()) + { + if (iServerName) + { + if (!(iClient->connect(iServerName, iServerPort) > 0)) + { +#ifdef LOGGING + Serial.println("Connection failed"); +#endif + return HTTP_ERROR_CONNECTION_FAILED; + } + } + else + { + if (!(iClient->connect(iServerAddress, iServerPort) > 0)) + { +#ifdef LOGGING + Serial.println("Connection failed"); +#endif + return HTTP_ERROR_CONNECTION_FAILED; + } + } + } + else + { +#ifdef LOGGING + Serial.println("Connection already open"); +#endif + } + + // Now we're connected, send the first part of the request + int ret = sendInitialHeaders(aURLPath, aHttpMethod); + + if (HTTP_SUCCESS == ret) + { + if (aContentType) + { + sendHeader(HTTP_HEADER_CONTENT_TYPE, aContentType); + } + + if (aContentLength > 0) + { + sendHeader(HTTP_HEADER_CONTENT_LENGTH, aContentLength); + } + + bool hasBody = (aBody && aContentLength > 0); + + if (initialState == eIdle || hasBody) + { + // This was a simple version of the API, so terminate the headers now + finishHeaders(); + } + // else we'll call it in endRequest or in the first call to print, etc. + + if (hasBody) + { + write(aBody, aContentLength); + } + } + + return ret; +} + +int HttpClient::sendInitialHeaders(const char *aURLPath, const char *aHttpMethod) +{ +#ifdef LOGGING + Serial.println("Connected"); +#endif + // Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0" + iClient->print(aHttpMethod); + iClient->print(" "); + + iClient->print(aURLPath); + iClient->println(" HTTP/1.1"); + if (iSendDefaultRequestHeaders) + { + // The host header, if required + if (iServerName) + { + iClient->print("Host: "); + iClient->print(iServerName); + if (iServerPort != kHttpPort) + { + iClient->print(":"); + iClient->print(iServerPort); + } + iClient->println(); + } + // And user-agent string + sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); + } + + if (iConnectionClose) + { + // Tell the server to + // close this connection after we're done + sendHeader(HTTP_HEADER_CONNECTION, "close"); + } + + // Everything has gone well + iState = eRequestStarted; + return HTTP_SUCCESS; +} + +void HttpClient::sendHeader(const char *aHeader) +{ + iClient->println(aHeader); +} + +void HttpClient::sendHeader(const char *aHeaderName, const char *aHeaderValue) +{ + iClient->print(aHeaderName); + iClient->print(": "); + iClient->println(aHeaderValue); +} + +void HttpClient::sendHeader(const char *aHeaderName, const int aHeaderValue) +{ + iClient->print(aHeaderName); + iClient->print(": "); + iClient->println(aHeaderValue); +} + +void HttpClient::sendBasicAuth(const char *aUser, const char *aPassword) +{ + // Send the initial part of this header line + iClient->print("Authorization: Basic "); + // Now Base64 encode "aUser:aPassword" and send that + // This seems trickier than it should be but it's mostly to avoid either + // (a) some arbitrarily sized buffer which hopes to be big enough, or + // (b) allocating and freeing memory + // ...so we'll loop through 3 bytes at a time, outputting the results as we + // go. + // In Base64, each 3 bytes of unencoded data become 4 bytes of encoded data + unsigned char input[3]; + unsigned char output[5]; // Leave space for a '\0' terminator so we can easily print + int userLen = strlen(aUser); + int passwordLen = strlen(aPassword); + int inputOffset = 0; + for (int i = 0; i < (userLen + 1 + passwordLen); i++) + { + // Copy the relevant input byte into the input + if (i < userLen) + { + input[inputOffset++] = aUser[i]; + } + else if (i == userLen) + { + input[inputOffset++] = ':'; + } + else + { + input[inputOffset++] = aPassword[i - (userLen + 1)]; + } + // See if we've got a chunk to encode + if ((inputOffset == 3) || (i == userLen + passwordLen)) + { + // We've either got to a 3-byte boundary, or we've reached then end + b64_encode(input, inputOffset, output, 4); + // NUL-terminate the output string + output[4] = '\0'; + // And write it out + iClient->print((char *)output); + // FIXME We might want to fill output with '=' characters if b64_encode doesn't + // FIXME do it for us when we're encoding the final chunk + inputOffset = 0; + } + } + // And end the header we've sent + iClient->println(); +} + +void HttpClient::finishHeaders() +{ + iClient->println(); + iState = eRequestSent; +} + +void HttpClient::flushClientRx() +{ + while (iClient->available()) + { + iClient->read(); + } +} + +void HttpClient::endRequest() +{ + beginBody(); +} + +void HttpClient::beginBody() +{ + if (iState < eRequestSent) + { + // We still need to finish off the headers + finishHeaders(); + } + // else the end of headers has already been sent, so nothing to do here +} + +int HttpClient::get(const char *aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_GET); +} + +int HttpClient::get(const String &aURLPath) +{ + return get(aURLPath.c_str()); +} + +int HttpClient::post(const char *aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_POST); +} + +int HttpClient::post(const String &aURLPath) +{ + return post(aURLPath.c_str()); +} + +int HttpClient::post(const char *aURLPath, const char *aContentType, const char *aBody) +{ + return post(aURLPath, aContentType, strlen(aBody), (const byte *)aBody); +} + +int HttpClient::post(const String &aURLPath, const String &aContentType, const String &aBody) +{ + return post(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte *)aBody.c_str()); +} + +int HttpClient::post(const char *aURLPath, const char *aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_POST, aContentType, aContentLength, aBody); +} + +int HttpClient::put(const char *aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_PUT); +} + +int HttpClient::put(const String &aURLPath) +{ + return put(aURLPath.c_str()); +} + +int HttpClient::put(const char *aURLPath, const char *aContentType, const char *aBody) +{ + return put(aURLPath, aContentType, strlen(aBody), (const byte *)aBody); +} + +int HttpClient::put(const String &aURLPath, const String &aContentType, const String &aBody) +{ + return put(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte *)aBody.c_str()); +} + +int HttpClient::put(const char *aURLPath, const char *aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody); +} + +int HttpClient::patch(const char *aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_PATCH); +} + +int HttpClient::patch(const String &aURLPath) +{ + return patch(aURLPath.c_str()); +} + +int HttpClient::patch(const char *aURLPath, const char *aContentType, const char *aBody) +{ + return patch(aURLPath, aContentType, strlen(aBody), (const byte *)aBody); +} + +int HttpClient::patch(const String &aURLPath, const String &aContentType, const String &aBody) +{ + return patch(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte *)aBody.c_str()); +} + +int HttpClient::patch(const char *aURLPath, const char *aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_PATCH, aContentType, aContentLength, aBody); +} + +int HttpClient::del(const char *aURLPath) +{ + return startRequest(aURLPath, HTTP_METHOD_DELETE); +} + +int HttpClient::del(const String &aURLPath) +{ + return del(aURLPath.c_str()); +} + +int HttpClient::del(const char *aURLPath, const char *aContentType, const char *aBody) +{ + return del(aURLPath, aContentType, strlen(aBody), (const byte *)aBody); +} + +int HttpClient::del(const String &aURLPath, const String &aContentType, const String &aBody) +{ + return del(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte *)aBody.c_str()); +} + +int HttpClient::del(const char *aURLPath, const char *aContentType, int aContentLength, const byte aBody[]) +{ + return startRequest(aURLPath, HTTP_METHOD_DELETE, aContentType, aContentLength, aBody); +} + +int HttpClient::responseStatusCode() +{ + if (iState < eRequestSent) + { + return HTTP_ERROR_API; + } + // The first line will be of the form Status-Line: + // HTTP-Version SP Status-Code SP Reason-Phrase CRLF + // Where HTTP-Version is of the form: + // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT + + int c = '\0'; + do + { + // Make sure the status code is reset, and likewise the state. This + // lets us easily cope with 1xx informational responses by just + // ignoring them really, and reading the next line for a proper response + iStatusCode = 0; + iState = eRequestSent; + + unsigned long timeoutStart = millis(); + // Psuedo-regexp we're expecting before the status-code + const char *statusPrefix = "HTTP/*.* "; + const char *statusPtr = statusPrefix; + // Whilst we haven't timed out & haven't reached the end of the headers + while ((c != '\n') && + ((millis() - timeoutStart) < iHttpResponseTimeout)) + { + if (available()) + { + c = read(); + if (c != -1) + { + switch (iState) + { + case eRequestSent: + // We haven't reached the status code yet + if ((*statusPtr == '*') || (*statusPtr == c)) + { + // This character matches, just move along + statusPtr++; + if (*statusPtr == '\0') + { + // We've reached the end of the prefix + iState = eReadingStatusCode; + } + } + else + { + return HTTP_ERROR_INVALID_RESPONSE; + } + break; + case eReadingStatusCode: + if (isdigit(c)) + { + // This assumes we won't get more than the 3 digits we + // want + iStatusCode = iStatusCode * 10 + (c - '0'); + } + else + { + // We've reached the end of the status code + // We could sanity check it here or double-check for ' ' + // rather than anything else, but let's be lenient + iState = eStatusCodeRead; + } + break; + case eStatusCodeRead: + // We're just waiting for the end of the line now + break; + + default: + break; + }; + // We read something, reset the timeout counter + timeoutStart = millis(); + } + } + else + { + // We haven't got any data, so let's pause to allow some to + // arrive + delay(kHttpWaitForDataDelay); + } + } + if ((c == '\n') && (iStatusCode < 200 && iStatusCode != 101)) + { + // We've reached the end of an informational status line + c = '\0'; // Clear c so we'll go back into the data reading loop + } + } + // If we've read a status code successfully but it's informational (1xx) + // loop back to the start + while ((iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101)); + + if ((c == '\n') && (iState == eStatusCodeRead)) + { + // We've read the status-line successfully + return iStatusCode; + } + else if (c != '\n') + { + // We must've timed out before we reached the end of the line + return HTTP_ERROR_TIMED_OUT; + } + else + { + // This wasn't a properly formed status line, or at least not one we + // could understand + return HTTP_ERROR_INVALID_RESPONSE; + } +} + +int HttpClient::skipResponseHeaders() +{ + // Just keep reading until we finish reading the headers or time out + unsigned long timeoutStart = millis(); + // Whilst we haven't timed out & haven't reached the end of the headers + while ((!endOfHeadersReached()) && + ((millis() - timeoutStart) < iHttpResponseTimeout)) + { + if (available()) + { + (void)readHeader(); + // We read something, reset the timeout counter + timeoutStart = millis(); + } + else + { + // We haven't got any data, so let's pause to allow some to + // arrive + delay(kHttpWaitForDataDelay); + } + } + if (endOfHeadersReached()) + { + // Success + return HTTP_SUCCESS; + } + else + { + // We must've timed out + return HTTP_ERROR_TIMED_OUT; + } +} + +bool HttpClient::endOfHeadersReached() +{ + return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk); +}; + +int HttpClient::contentLength() +{ + // skip the response headers, if they haven't been read already + if (!endOfHeadersReached()) + { + skipResponseHeaders(); + } + + return iContentLength; +} + +String HttpClient::responseBody() +{ + int bodyLength = contentLength(); + String response; + + if (bodyLength > 0) + { + // try to reserve bodyLength bytes + if (response.reserve(bodyLength) == 0) + { + // String reserve failed + return String((const char *)NULL); + } + } + + // keep on timedRead'ing, until: + // - we have a content length: body length equals consumed or no bytes + // available + // - no content length: no bytes are available + while (iBodyLengthConsumed != bodyLength) + { + int c = timedRead(); + + if (c == -1) + { + // read timed out, done + break; + } + + if (!response.concat((char)c)) + { + // adding char failed + return String((const char *)NULL); + } + } + + if (bodyLength > 0 && (unsigned int)bodyLength != response.length()) + { + // failure, we did not read in response content length bytes + return String((const char *)NULL); + } + + return response; +} + +bool HttpClient::endOfBodyReached() +{ + if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader)) + { + // We've got to the body and we know how long it will be + return (iBodyLengthConsumed >= contentLength()); + } + return false; +} + +int HttpClient::available() +{ + if (iState == eReadingChunkLength) + { + while (iClient->available()) + { + char c = iClient->read(); + + if (c == '\n') + { + iState = eReadingBodyChunk; + break; + } + else if (c == '\r') + { + // no-op + } + else if (isHexadecimalDigit(c)) + { + char digit[2] = {c, '\0'}; + + iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16); + } + } + } + + if (iState == eReadingBodyChunk && iChunkLength == 0) + { + iState = eReadingChunkLength; + } + + if (iState == eReadingChunkLength) + { + return 0; + } + + int clientAvailable = iClient->available(); + + if (iState == eReadingBodyChunk) + { + return min(clientAvailable, iChunkLength); + } + else + { + return clientAvailable; + } +} + +int HttpClient::read() +{ + if (iIsChunked && !available()) + { + return -1; + } + + int ret = iClient->read(); + if (ret >= 0) + { + if (endOfHeadersReached() && iContentLength > 0) + { + // We're outputting the body now and we've seen a Content-Length header + // So keep track of how many bytes are left + iBodyLengthConsumed++; + } + + if (iState == eReadingBodyChunk) + { + iChunkLength--; + + if (iChunkLength == 0) + { + iState = eReadingChunkLength; + } + } + } + return ret; +} + +bool HttpClient::headerAvailable() +{ + // clear the currently stored header line + iHeaderLine = ""; + + while (!endOfHeadersReached()) + { + // read a byte from the header + int c = readHeader(); + + if (c == '\r' || c == '\n') + { + if (iHeaderLine.length()) + { + // end of the line, all done + break; + } + else + { + // ignore any CR or LF characters + continue; + } + } + + // append byte to header line + iHeaderLine += (char)c; + } + + return (iHeaderLine.length() > 0); +} + +String HttpClient::readHeaderName() +{ + int colonIndex = iHeaderLine.indexOf(':'); + + if (colonIndex == -1) + { + return ""; + } + + return iHeaderLine.substring(0, colonIndex); +} + +String HttpClient::readHeaderValue() +{ + int colonIndex = iHeaderLine.indexOf(':'); + int startIndex = colonIndex + 1; + + if (colonIndex == -1) + { + return ""; + } + + // trim any leading whitespace + while (startIndex < (int)iHeaderLine.length() && isSpace(iHeaderLine[startIndex])) + { + startIndex++; + } + + return iHeaderLine.substring(startIndex); +} + +int HttpClient::read(uint8_t *buf, size_t size) +{ + int ret = iClient->read(buf, size); + if (endOfHeadersReached() && iContentLength > 0) + { + // We're outputting the body now and we've seen a Content-Length header + // So keep track of how many bytes are left + if (ret >= 0) + { + iBodyLengthConsumed += ret; + } + } + return ret; +} + +int HttpClient::readHeader() +{ + char c = read(); + + if (endOfHeadersReached()) + { + // We've passed the headers, but rather than return an error, we'll just + // act as a slightly less efficient version of read() + return c; + } + + // Whilst reading out the headers to whoever wants them, we'll keep an + // eye out for the "Content-Length" header + switch (iState) + { + case eStatusCodeRead: + // We're at the start of a line, or somewhere in the middle of reading + // the Content-Length prefix + if (*iContentLengthPtr == c) + { + // This character matches, just move along + iContentLengthPtr++; + if (*iContentLengthPtr == '\0') + { + // We've reached the end of the prefix + iState = eReadingContentLength; + // Just in case we get multiple Content-Length headers, this + // will ensure we just get the value of the last one + iContentLength = 0; + iBodyLengthConsumed = 0; + } + } + else if (*iTransferEncodingChunkedPtr == c) + { + // This character matches, just move along + iTransferEncodingChunkedPtr++; + if (*iTransferEncodingChunkedPtr == '\0') + { + // We've reached the end of the Transfer Encoding: chunked header + iIsChunked = true; + iState = eSkipToEndOfHeader; + } + } + else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r')) + { + // We've found a '\r' at the start of a line, so this is probably + // the end of the headers + iState = eLineStartingCRFound; + } + else + { + // This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line + iState = eSkipToEndOfHeader; + } + break; + case eReadingContentLength: + if (isdigit(c)) + { + iContentLength = iContentLength * 10 + (c - '0'); + } + else + { + // We've reached the end of the content length + // We could sanity check it here or double-check for "\r\n" + // rather than anything else, but let's be lenient + iState = eSkipToEndOfHeader; + } + break; + case eLineStartingCRFound: + if (c == '\n') + { + if (iIsChunked) + { + iState = eReadingChunkLength; + iChunkLength = 0; + } + else + { + iState = eReadingBody; + } + } + break; + default: + // We're just waiting for the end of the line now + break; + }; + + if ((c == '\n') && !endOfHeadersReached()) + { + // We've got to the end of this line, start processing again + iState = eStatusCodeRead; + iContentLengthPtr = kContentLengthPrefix; + iTransferEncodingChunkedPtr = kTransferEncodingChunked; + } + // And return the character read to whoever wants it + return c; +} diff --git a/src/HttpClient.h b/src/HttpClient.h old mode 100644 new mode 100755 index 6a7aa1d..6b7428e --- a/src/HttpClient.h +++ b/src/HttpClient.h @@ -1,392 +1,416 @@ -// Class to simplify HTTP fetching on Arduino -// (c) Copyright MCQN Ltd. 2010-2012 -// Released under Apache License, version 2.0 - -#ifndef HttpClient_h -#define HttpClient_h - -#include -#include -#include "Client.h" - -static const int HTTP_SUCCESS =0; -// The end of the headers has been reached. This consumes the '\n' -// Could not connect to the server -static const int HTTP_ERROR_CONNECTION_FAILED =-1; -// This call was made when the HttpClient class wasn't expecting it -// to be called. Usually indicates your code is using the class -// incorrectly -static const int HTTP_ERROR_API =-2; -// Spent too long waiting for a reply -static const int HTTP_ERROR_TIMED_OUT =-3; -// The response from the server is invalid, is it definitely an HTTP -// server? -static const int HTTP_ERROR_INVALID_RESPONSE =-4; - -// Define some of the common methods and headers here -// That lets other code reuse them without having to declare another copy -// of them, so saves code space and RAM -#define HTTP_METHOD_GET "GET" -#define HTTP_METHOD_POST "POST" -#define HTTP_METHOD_PUT "PUT" -#define HTTP_METHOD_PATCH "PATCH" -#define HTTP_METHOD_DELETE "DELETE" -#define HTTP_HEADER_CONTENT_LENGTH "Content-Length" -#define HTTP_HEADER_CONTENT_TYPE "Content-Type" -#define HTTP_HEADER_CONNECTION "Connection" -#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding" -#define HTTP_HEADER_USER_AGENT "User-Agent" -#define HTTP_HEADER_VALUE_CHUNKED "chunked" - -class HttpClient : public Client -{ -public: - static const int kNoContentLengthHeader =-1; - static const int kHttpPort =80; - static const char* kUserAgent; - -// FIXME Write longer API request, using port and user-agent, example -// FIXME Update tempToPachube example to calculate Content-Length correctly - - HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort = kHttpPort); - HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort); - HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort); - - /** Start a more complex request. - Use this when you need to send additional headers in the request, - but you will also need to call endRequest() when you are finished. - */ - void beginRequest(); - - /** End a more complex request. - Use this when you need to have sent additional headers in the request, - but you will also need to call beginRequest() at the start. - */ - void endRequest(); - - /** Start the body of a more complex request. - Use this when you need to send the body after additional headers - in the request, but can optionally call endRequest() when - you are finished. - */ - void beginBody(); - - /** Connect to the server and start to send a GET request. - @param aURLPath Url to request - @return 0 if successful, else error - */ - int get(const char* aURLPath); - int get(const String& aURLPath); - - /** Connect to the server and start to send a POST request. - @param aURLPath Url to request - @return 0 if successful, else error - */ - int post(const char* aURLPath); - int post(const String& aURLPath); - - /** Connect to the server and send a POST request - with body and content type - @param aURLPath Url to request - @param aContentType Content type of request body - @param aBody Body of the request - @return 0 if successful, else error - */ - int post(const char* aURLPath, const char* aContentType, const char* aBody); - int post(const String& aURLPath, const String& aContentType, const String& aBody); - int post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); - - /** Connect to the server and start to send a PUT request. - @param aURLPath Url to request - @return 0 if successful, else error - */ - int put(const char* aURLPath); - int put(const String& aURLPath); - - /** Connect to the server and send a PUT request - with body and content type - @param aURLPath Url to request - @param aContentType Content type of request body - @param aBody Body of the request - @return 0 if successful, else error - */ - int put(const char* aURLPath, const char* aContentType, const char* aBody); - int put(const String& aURLPath, const String& aContentType, const String& aBody); - int put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); - - /** Connect to the server and start to send a PATCH request. - @param aURLPath Url to request - @return 0 if successful, else error - */ - int patch(const char* aURLPath); - int patch(const String& aURLPath); - - /** Connect to the server and send a PATCH request - with body and content type - @param aURLPath Url to request - @param aContentType Content type of request body - @param aBody Body of the request - @return 0 if successful, else error - */ - int patch(const char* aURLPath, const char* aContentType, const char* aBody); - int patch(const String& aURLPath, const String& aContentType, const String& aBody); - int patch(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); - - /** Connect to the server and start to send a DELETE request. - @param aURLPath Url to request - @return 0 if successful, else error - */ - int del(const char* aURLPath); - int del(const String& aURLPath); - - /** Connect to the server and send a DELETE request - with body and content type - @param aURLPath Url to request - @param aContentType Content type of request body - @param aBody Body of the request - @return 0 if successful, else error - */ - int del(const char* aURLPath, const char* aContentType, const char* aBody); - int del(const String& aURLPath, const String& aContentType, const String& aBody); - int del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]); - - /** Connect to the server and start to send the request. - If a body is provided, the entire request (including headers and body) will be sent - @param aURLPath Url to request - @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. - @param aContentType Content type of request body (optional) - @param aContentLength Length of request body (optional) - @param aBody Body of request (optional) - @return 0 if successful, else error - */ - int startRequest(const char* aURLPath, - const char* aHttpMethod, - const char* aContentType = NULL, - int aContentLength = -1, - const byte aBody[] = NULL); - - /** Send an additional header line. This can only be called in between the - calls to beginRequest and endRequest. - @param aHeader Header line to send, in its entirety (but without the - trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES" - */ - void sendHeader(const char* aHeader); - - void sendHeader(const String& aHeader) - { sendHeader(aHeader.c_str()); } - - /** Send an additional header line. This is an alternate form of - sendHeader() which takes the header name and content as separate strings. - The call will add the ": " to separate the header, so for example, to - send a XXXXXX header call sendHeader("XXXXX", "Something") - @param aHeaderName Type of header being sent - @param aHeaderValue Value for that header - */ - void sendHeader(const char* aHeaderName, const char* aHeaderValue); - - void sendHeader(const String& aHeaderName, const String& aHeaderValue) - { sendHeader(aHeaderName.c_str(), aHeaderValue.c_str()); } - - /** Send an additional header line. This is an alternate form of - sendHeader() which takes the header name and content separately but where - the value is provided as an integer. - The call will add the ": " to separate the header, so for example, to - send a XXXXXX header call sendHeader("XXXXX", 123) - @param aHeaderName Type of header being sent - @param aHeaderValue Value for that header - */ - void sendHeader(const char* aHeaderName, const int aHeaderValue); - - void sendHeader(const String& aHeaderName, const int aHeaderValue) - { sendHeader(aHeaderName.c_str(), aHeaderValue); } - - /** Send a basic authentication header. This will encode the given username - and password, and send them in suitable header line for doing Basic - Authentication. - @param aUser Username for the authorization - @param aPassword Password for the user aUser - */ - void sendBasicAuth(const char* aUser, const char* aPassword); - - void sendBasicAuth(const String& aUser, const String& aPassword) - { sendBasicAuth(aUser.c_str(), aPassword.c_str()); } - - /** Get the HTTP status code contained in the response. - For example, 200 for successful request, 404 for file not found, etc. - */ - int responseStatusCode(); - - /** Check if a header is available to be read. - Use readHeaderName() to read header name, and readHeaderValue() to - read the header value - MUST be called after responseStatusCode() and before contentLength() - */ - bool headerAvailable(); - - /** Read the name of the current response header. - Returns empty string if a header is not available. - */ - String readHeaderName(); - - /** Read the value of the current response header. - Returns empty string if a header is not available. - */ - String readHeaderValue(); - - /** Read the next character of the response headers. - This functions in the same way as read() but to be used when reading - through the headers. Check whether or not the end of the headers has - been reached by calling endOfHeadersReached(), although after that point - this will still return data as read() would, but slightly less efficiently - MUST be called after responseStatusCode() and before contentLength() - @return The next character of the response headers - */ - int readHeader(); - - /** Skip any response headers to get to the body. - Use this if you don't want to do any special processing of the headers - returned in the response. You can also use it after you've found all of - the headers you're interested in, and just want to get on with processing - the body. - MUST be called after responseStatusCode() - @return HTTP_SUCCESS if successful, else an error code - */ - int skipResponseHeaders(); - - /** Test whether all of the response headers have been consumed. - @return true if we are now processing the response body, else false - */ - bool endOfHeadersReached(); - - /** Test whether the end of the body has been reached. - Only works if the Content-Length header was returned by the server - @return true if we are now at the end of the body, else false - */ - bool endOfBodyReached(); - virtual bool endOfStream() { return endOfBodyReached(); }; - virtual bool completed() { return endOfBodyReached(); }; - - /** Return the length of the body. - Also skips response headers if they have not been read already - MUST be called after responseStatusCode() - @return Length of the body, in bytes, or kNoContentLengthHeader if no - Content-Length header was returned by the server - */ - int contentLength(); - - /** Returns if the response body is chunked - @return true if response body is chunked, false otherwise - */ - int isResponseChunked() { return iIsChunked; } - - /** Return the response body as a String - Also skips response headers if they have not been read already - MUST be called after responseStatusCode() - @return response body of request as a String - */ - String responseBody(); - - /** Enables connection keep-alive mode - */ - void connectionKeepAlive(); - - /** Disables sending the default request headers (Host and User Agent) - */ - void noDefaultRequestHeaders(); - - // Inherited from Print - // Note: 1st call to these indicates the user is sending the body, so if need - // Note: be we should finish the header first - virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); }; - virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); }; - // Inherited from Stream - virtual int available(); - /** Read the next byte from the server. - @return Byte read or -1 if there are no bytes available. - */ - virtual int read(); - virtual int read(uint8_t *buf, size_t size); - virtual int peek() { return iClient->peek(); }; - virtual void flush() { iClient->flush(); }; - - // Inherited from Client - virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); }; - virtual int connect(const char *host, uint16_t port) { return iClient->connect(host, port); }; - virtual void stop(); - virtual uint8_t connected() { return iClient->connected(); }; - virtual operator bool() { return bool(iClient); }; - virtual uint32_t httpResponseTimeout() { return iHttpResponseTimeout; }; - virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; }; -protected: - /** Reset internal state data back to the "just initialised" state - */ - void resetState(); - - /** Send the first part of the request and the initial headers. - @param aURLPath Url to request - @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. - @return 0 if successful, else error - */ - int sendInitialHeaders(const char* aURLPath, - const char* aHttpMethod); - - /* Let the server know that we've reached the end of the headers - */ - void finishHeaders(); - - /** Reading any pending data from the client (used in connection keep alive mode) - */ - void flushClientRx(); - - // Number of milliseconds that we wait each time there isn't any data - // available to be read (during status code and header processing) - static const int kHttpWaitForDataDelay = 1000; - // Number of milliseconds that we'll wait in total without receiving any - // data before returning HTTP_ERROR_TIMED_OUT (during status code and header - // processing) - static const int kHttpResponseTimeout = 30*1000; - static const char* kContentLengthPrefix; - static const char* kTransferEncodingChunked; - typedef enum { - eIdle, - eRequestStarted, - eRequestSent, - eReadingStatusCode, - eStatusCodeRead, - eReadingContentLength, - eSkipToEndOfHeader, - eLineStartingCRFound, - eReadingBody, - eReadingChunkLength, - eReadingBodyChunk - } tHttpState; - // Client we're using - Client* iClient; - // Server we are connecting to - const char* iServerName; - IPAddress iServerAddress; - // Port of server we are connecting to - uint16_t iServerPort; - // Current state of the finite-state-machine - tHttpState iState; - // Stores the status code for the response, once known - int iStatusCode; - // Stores the value of the Content-Length header, if present - int iContentLength; - // How many bytes of the response body have been read by the user - int iBodyLengthConsumed; - // How far through a Content-Length header prefix we are - const char* iContentLengthPtr; - // How far through a Transfer-Encoding chunked header we are - const char* iTransferEncodingChunkedPtr; - // Stores if the response body is chunked - bool iIsChunked; - // Stores the value of the current chunk length, if present - int iChunkLength; - uint32_t iHttpResponseTimeout; - bool iConnectionClose; - bool iSendDefaultRequestHeaders; - String iHeaderLine; -}; - -#endif +// Class to simplify HTTP fetching on Arduino +// (c) Copyright MCQN Ltd. 2010-2012 +// Released under Apache License, version 2.0 + +#ifndef HttpClient_h +#define HttpClient_h + +#include +#include +#include "Client.h" + +static const int HTTP_SUCCESS = 0; +// The end of the headers has been reached. This consumes the '\n' +// Could not connect to the server +static const int HTTP_ERROR_CONNECTION_FAILED = -1; +// This call was made when the HttpClient class wasn't expecting it +// to be called. Usually indicates your code is using the class +// incorrectly +static const int HTTP_ERROR_API = -2; +// Spent too long waiting for a reply +static const int HTTP_ERROR_TIMED_OUT = -3; +// The response from the server is invalid, is it definitely an HTTP +// server? +static const int HTTP_ERROR_INVALID_RESPONSE = -4; + +// Define some of the common methods and headers here +// That lets other code reuse them without having to declare another copy +// of them, so saves code space and RAM +#define HTTP_METHOD_GET "GET" +#define HTTP_METHOD_POST "POST" +#define HTTP_METHOD_PUT "PUT" +#define HTTP_METHOD_PATCH "PATCH" +#define HTTP_METHOD_DELETE "DELETE" +#define HTTP_HEADER_CONTENT_LENGTH "Content-Length" +#define HTTP_HEADER_CONTENT_TYPE "Content-Type" +#define HTTP_HEADER_CONNECTION "Connection" +#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding" +#define HTTP_HEADER_USER_AGENT "User-Agent" +#define HTTP_HEADER_VALUE_CHUNKED "chunked" + +class HttpClient : public Client +{ +public: + static const int kNoContentLengthHeader = -1; + static const int kHttpPort = 80; + static const char *kUserAgent; + + // FIXME Write longer API request, using port and user-agent, example + // FIXME Update tempToPachube example to calculate Content-Length correctly + + HttpClient(Client &aClient, const char *aServerName, uint16_t aServerPort = kHttpPort); + HttpClient(Client &aClient, const String &aServerName, uint16_t aServerPort = kHttpPort); + HttpClient(Client &aClient, const IPAddress &aServerAddress, uint16_t aServerPort = kHttpPort); + + /** Start a more complex request. + Use this when you need to send additional headers in the request, + but you will also need to call endRequest() when you are finished. + */ + void beginRequest(); + + /** End a more complex request. + Use this when you need to have sent additional headers in the request, + but you will also need to call beginRequest() at the start. + */ + void endRequest(); + + /** Start the body of a more complex request. + Use this when you need to send the body after additional headers + in the request, but can optionally call endRequest() when + you are finished. + */ + void beginBody(); + + /** Connect to the server and start to send a GET request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int get(const char *aURLPath); + int get(const String &aURLPath); + + /** Connect to the server and start to send a POST request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int post(const char *aURLPath); + int post(const String &aURLPath); + + /** Connect to the server and send a POST request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int post(const char *aURLPath, const char *aContentType, const char *aBody); + int post(const String &aURLPath, const String &aContentType, const String &aBody); + int post(const char *aURLPath, const char *aContentType, int aContentLength, const byte aBody[]); + + /** Connect to the server and start to send a PUT request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int put(const char *aURLPath); + int put(const String &aURLPath); + + /** Connect to the server and send a PUT request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int put(const char *aURLPath, const char *aContentType, const char *aBody); + int put(const String &aURLPath, const String &aContentType, const String &aBody); + int put(const char *aURLPath, const char *aContentType, int aContentLength, const byte aBody[]); + + /** Connect to the server and start to send a PATCH request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int patch(const char *aURLPath); + int patch(const String &aURLPath); + + /** Connect to the server and send a PATCH request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int patch(const char *aURLPath, const char *aContentType, const char *aBody); + int patch(const String &aURLPath, const String &aContentType, const String &aBody); + int patch(const char *aURLPath, const char *aContentType, int aContentLength, const byte aBody[]); + + /** Connect to the server and start to send a DELETE request. + @param aURLPath Url to request + @return 0 if successful, else error + */ + int del(const char *aURLPath); + int del(const String &aURLPath); + + /** Connect to the server and send a DELETE request + with body and content type + @param aURLPath Url to request + @param aContentType Content type of request body + @param aBody Body of the request + @return 0 if successful, else error + */ + int del(const char *aURLPath, const char *aContentType, const char *aBody); + int del(const String &aURLPath, const String &aContentType, const String &aBody); + int del(const char *aURLPath, const char *aContentType, int aContentLength, const byte aBody[]); + + /** Connect to the server and start to send the request. + If a body is provided, the entire request (including headers and body) will be sent + @param aURLPath Url to request + @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. + @param aContentType Content type of request body (optional) + @param aContentLength Length of request body (optional) + @param aBody Body of request (optional) + @return 0 if successful, else error + */ + int startRequest(const char *aURLPath, + const char *aHttpMethod, + const char *aContentType = NULL, + int aContentLength = -1, + const byte aBody[] = NULL); + + /** Send an additional header line. This can only be called in between the + calls to beginRequest and endRequest. + @param aHeader Header line to send, in its entirety (but without the + trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES" + */ + void sendHeader(const char *aHeader); + + void sendHeader(const String &aHeader) + { + sendHeader(aHeader.c_str()); + } + + /** Send an additional header line. This is an alternate form of + sendHeader() which takes the header name and content as separate strings. + The call will add the ": " to separate the header, so for example, to + send a XXXXXX header call sendHeader("XXXXX", "Something") + @param aHeaderName Type of header being sent + @param aHeaderValue Value for that header + */ + void sendHeader(const char *aHeaderName, const char *aHeaderValue); + + void sendHeader(const String &aHeaderName, const String &aHeaderValue) + { + sendHeader(aHeaderName.c_str(), aHeaderValue.c_str()); + } + + /** Send an additional header line. This is an alternate form of + sendHeader() which takes the header name and content separately but where + the value is provided as an integer. + The call will add the ": " to separate the header, so for example, to + send a XXXXXX header call sendHeader("XXXXX", 123) + @param aHeaderName Type of header being sent + @param aHeaderValue Value for that header + */ + void sendHeader(const char *aHeaderName, const int aHeaderValue); + + void sendHeader(const String &aHeaderName, const int aHeaderValue) + { + sendHeader(aHeaderName.c_str(), aHeaderValue); + } + + /** Send a basic authentication header. This will encode the given username + and password, and send them in suitable header line for doing Basic + Authentication. + @param aUser Username for the authorization + @param aPassword Password for the user aUser + */ + void sendBasicAuth(const char *aUser, const char *aPassword); + + void sendBasicAuth(const String &aUser, const String &aPassword) + { + sendBasicAuth(aUser.c_str(), aPassword.c_str()); + } + + /** Get the HTTP status code contained in the response. + For example, 200 for successful request, 404 for file not found, etc. + */ + int responseStatusCode(); + + /** Check if a header is available to be read. + Use readHeaderName() to read header name, and readHeaderValue() to + read the header value + MUST be called after responseStatusCode() and before contentLength() + */ + bool headerAvailable(); + + /** Read the name of the current response header. + Returns empty string if a header is not available. + */ + String readHeaderName(); + + /** Read the value of the current response header. + Returns empty string if a header is not available. + */ + String readHeaderValue(); + + /** Read the next character of the response headers. + This functions in the same way as read() but to be used when reading + through the headers. Check whether or not the end of the headers has + been reached by calling endOfHeadersReached(), although after that point + this will still return data as read() would, but slightly less efficiently + MUST be called after responseStatusCode() and before contentLength() + @return The next character of the response headers + */ + int readHeader(); + + /** Skip any response headers to get to the body. + Use this if you don't want to do any special processing of the headers + returned in the response. You can also use it after you've found all of + the headers you're interested in, and just want to get on with processing + the body. + MUST be called after responseStatusCode() + @return HTTP_SUCCESS if successful, else an error code + */ + int skipResponseHeaders(); + + /** Test whether all of the response headers have been consumed. + @return true if we are now processing the response body, else false + */ + bool endOfHeadersReached(); + + /** Test whether the end of the body has been reached. + Only works if the Content-Length header was returned by the server + @return true if we are now at the end of the body, else false + */ + bool endOfBodyReached(); + virtual bool endOfStream() { return endOfBodyReached(); }; + virtual bool completed() { return endOfBodyReached(); }; + + /** Return the length of the body. + Also skips response headers if they have not been read already + MUST be called after responseStatusCode() + @return Length of the body, in bytes, or kNoContentLengthHeader if no + Content-Length header was returned by the server + */ + int contentLength(); + + /** Returns if the response body is chunked + @return true if response body is chunked, false otherwise + */ + int isResponseChunked() { return iIsChunked; } + + /** Return the response body as a String + Also skips response headers if they have not been read already + MUST be called after responseStatusCode() + @return response body of request as a String + */ + String responseBody(); + + /** Enables connection keep-alive mode + */ + void connectionKeepAlive(); + + /** Disables sending the default request headers (Host and User Agent) + */ + void noDefaultRequestHeaders(); + + // Inherited from Print + // Note: 1st call to these indicates the user is sending the body, so if need + // Note: be we should finish the header first + virtual size_t write(uint8_t aByte) + { + if (iState < eRequestSent) + { + finishHeaders(); + }; + return iClient->write(aByte); + }; + virtual size_t write(const uint8_t *aBuffer, size_t aSize) + { + if (iState < eRequestSent) + { + finishHeaders(); + }; + return iClient->write(aBuffer, aSize); + }; + // Inherited from Stream + virtual int available(); + /** Read the next byte from the server. + @return Byte read or -1 if there are no bytes available. + */ + virtual int read(); + virtual int read(uint8_t *buf, size_t size); + virtual int peek() { return iClient->peek(); }; + virtual void flush() { iClient->flush(); }; + + // Inherited from Client + virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); }; + virtual int connect(const char *host, uint16_t port) { return iClient->connect(host, port); }; + virtual void stop(); + virtual uint8_t connected() { return iClient->connected(); }; + virtual operator bool() { return bool(iClient); }; + virtual uint32_t httpResponseTimeout() { return iHttpResponseTimeout; }; + virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; }; + +protected: + /** Reset internal state data back to the "just initialised" state + */ + void resetState(); + + /** Send the first part of the request and the initial headers. + @param aURLPath Url to request + @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. + @return 0 if successful, else error + */ + int sendInitialHeaders(const char *aURLPath, + const char *aHttpMethod); + + /* Let the server know that we've reached the end of the headers + */ + void finishHeaders(); + + /** Reading any pending data from the client (used in connection keep alive mode) + */ + void flushClientRx(); + + // Number of milliseconds that we wait each time there isn't any data + // available to be read (during status code and header processing) + static const int kHttpWaitForDataDelay = 1000; + // Number of milliseconds that we'll wait in total without receiving any + // data before returning HTTP_ERROR_TIMED_OUT (during status code and header + // processing) + static const int kHttpResponseTimeout = 30 * 1000; + static const char *kContentLengthPrefix; + static const char *kTransferEncodingChunked; + typedef enum + { + eIdle, + eRequestStarted, + eRequestSent, + eReadingStatusCode, + eStatusCodeRead, + eReadingContentLength, + eSkipToEndOfHeader, + eLineStartingCRFound, + eReadingBody, + eReadingChunkLength, + eReadingBodyChunk + } tHttpState; + // Client we're using + Client *iClient; + // Server we are connecting to + const char *iServerName; + IPAddress iServerAddress; + // Port of server we are connecting to + uint16_t iServerPort; + // Current state of the finite-state-machine + tHttpState iState; + // Stores the status code for the response, once known + int iStatusCode; + // Stores the value of the Content-Length header, if present + int iContentLength; + // How many bytes of the response body have been read by the user + int iBodyLengthConsumed; + // How far through a Content-Length header prefix we are + const char *iContentLengthPtr; + // How far through a Transfer-Encoding chunked header we are + const char *iTransferEncodingChunkedPtr; + // Stores if the response body is chunked + bool iIsChunked; + // Stores the value of the current chunk length, if present + int iChunkLength; + uint32_t iHttpResponseTimeout; + bool iConnectionClose; + bool iSendDefaultRequestHeaders; + String iHeaderLine; +}; + +#endif diff --git a/src/URLEncoder.cpp b/src/URLEncoder.cpp old mode 100644 new mode 100755 index 7baf5a9..1968d5d --- a/src/URLEncoder.cpp +++ b/src/URLEncoder.cpp @@ -1,53 +1,57 @@ -// Library to simplify HTTP fetching on Arduino -// (c) Copyright Arduino. 2019 -// Released under Apache License, version 2.0 - -#include "URLEncoder.h" - -URLEncoderClass::URLEncoderClass() -{ -} - -URLEncoderClass::~URLEncoderClass() -{ -} - -String URLEncoderClass::encode(const char* str) -{ - return encode(str, strlen(str)); -} - -String URLEncoderClass::encode(const String& str) -{ - return encode(str.c_str(), str.length()); -} - -String URLEncoderClass::encode(const char* str, int length) -{ - String encoded; - - encoded.reserve(length); - - for (int i = 0; i < length; i++) { - char c = str[i]; - - const char HEX_DIGIT_MAPPER[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - if (isAlphaNumeric(c) || (c == '-') || (c == '.') || (c == '_') || (c == '~')) { - encoded += c; - } else { - char s[4]; - - s[0] = '%'; - s[1] = HEX_DIGIT_MAPPER[(c >> 4) & 0xf]; - s[2] = HEX_DIGIT_MAPPER[(c & 0x0f)]; - s[3] = 0; - - encoded += s; - } - } - - return encoded; -} - -URLEncoderClass URLEncoder; +// Library to simplify HTTP fetching on Arduino +// (c) Copyright Arduino. 2019 +// Released under Apache License, version 2.0 + +#include "URLEncoder.h" + +URLEncoderClass::URLEncoderClass() +{ +} + +URLEncoderClass::~URLEncoderClass() +{ +} + +String URLEncoderClass::encode(const char *str) +{ + return encode(str, strlen(str)); +} + +String URLEncoderClass::encode(const String &str) +{ + return encode(str.c_str(), str.length()); +} + +String URLEncoderClass::encode(const char *str, int length) +{ + String encoded; + + encoded.reserve(length); + + for (int i = 0; i < length; i++) + { + char c = str[i]; + + const char HEX_DIGIT_MAPPER[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + if (isAlphaNumeric(c) || (c == '-') || (c == '.') || (c == '_') || (c == '~')) + { + encoded += c; + } + else + { + char s[4]; + + s[0] = '%'; + s[1] = HEX_DIGIT_MAPPER[(c >> 4) & 0xf]; + s[2] = HEX_DIGIT_MAPPER[(c & 0x0f)]; + s[3] = 0; + + encoded += s; + } + } + + return encoded; +} + +URLEncoderClass URLEncoder; diff --git a/src/URLEncoder.h b/src/URLEncoder.h old mode 100644 new mode 100755 index bce5f17..521f8dc --- a/src/URLEncoder.h +++ b/src/URLEncoder.h @@ -1,25 +1,25 @@ -// Library to simplify HTTP fetching on Arduino -// (c) Copyright Arduino. 2019 -// Released under Apache License, version 2.0 - -#ifndef URL_ENCODER_H -#define URL_ENCODER_H - -#include - -class URLEncoderClass -{ -public: - URLEncoderClass(); - virtual ~URLEncoderClass(); - - static String encode(const char* str); - static String encode(const String& str); - -private: - static String encode(const char* str, int length); -}; - -extern URLEncoderClass URLEncoder; - -#endif +// Library to simplify HTTP fetching on Arduino +// (c) Copyright Arduino. 2019 +// Released under Apache License, version 2.0 + +#ifndef URL_ENCODER_H +#define URL_ENCODER_H + +#include + +class URLEncoderClass +{ +public: + URLEncoderClass(); + virtual ~URLEncoderClass(); + + static String encode(const char *str); + static String encode(const String &str); + +private: + static String encode(const char *str, int length); +}; + +extern URLEncoderClass URLEncoder; + +#endif diff --git a/src/WebSocketClient.cpp b/src/WebSocketClient.cpp old mode 100644 new mode 100755 index ab41b0a..755a612 --- a/src/WebSocketClient.cpp +++ b/src/WebSocketClient.cpp @@ -1,372 +1,387 @@ -// (c) Copyright Arduino. 2016 -// Released under Apache License, version 2.0 - -#include "b64.h" - -#include "WebSocketClient.h" - -WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort) - : HttpClient(aClient, aServerName, aServerPort), - iTxStarted(false), - iRxSize(0) -{ -} - -WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort) - : HttpClient(aClient, aServerName, aServerPort), - iTxStarted(false), - iRxSize(0) -{ -} - -WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort) - : HttpClient(aClient, aServerAddress, aServerPort), - iTxStarted(false), - iRxSize(0) -{ -} - -int WebSocketClient::begin(const char* aPath) -{ - // start the GET request - beginRequest(); - connectionKeepAlive(); - int status = get(aPath); - - if (status == 0) - { - uint8_t randomKey[16]; - char base64RandomKey[25]; - - // create a random key for the connection upgrade - for (int i = 0; i < (int)sizeof(randomKey); i++) - { - randomKey[i] = random(0x01, 0xff); - } - memset(base64RandomKey, 0x00, sizeof(base64RandomKey)); - b64_encode(randomKey, sizeof(randomKey), (unsigned char*)base64RandomKey, sizeof(base64RandomKey)); - - // start the connection upgrade sequence - sendHeader("Upgrade", "websocket"); - sendHeader("Connection", "Upgrade"); - sendHeader("Sec-WebSocket-Key", base64RandomKey); - sendHeader("Sec-WebSocket-Version", "13"); - endRequest(); - - status = responseStatusCode(); - - if (status > 0) - { - skipResponseHeaders(); - } - } - - iRxSize = 0; - - // status code of 101 means success - return (status == 101) ? 0 : status; -} - -int WebSocketClient::begin(const String& aPath) -{ - return begin(aPath.c_str()); -} - -int WebSocketClient::beginMessage(int aType) -{ - if (iTxStarted) - { - // fail TX already started - return 1; - } - - iTxStarted = true; - iTxMessageType = (aType & 0xf); - iTxSize = 0; - - return 0; -} - -int WebSocketClient::endMessage() -{ - if (!iTxStarted) - { - // fail TX not started - return 1; - } - - // send FIN + the message type (opcode) - HttpClient::write(0x80 | iTxMessageType); - - // the message is masked (0x80) - // send the length - if (iTxSize < 126) - { - HttpClient::write(0x80 | (uint8_t)iTxSize); - } - else if (iTxSize < 0xffff) - { - HttpClient::write(0x80 | 126); - HttpClient::write((iTxSize >> 8) & 0xff); - HttpClient::write((iTxSize >> 0) & 0xff); - } - else - { - HttpClient::write(0x80 | 127); - HttpClient::write((iTxSize >> 56) & 0xff); - HttpClient::write((iTxSize >> 48) & 0xff); - HttpClient::write((iTxSize >> 40) & 0xff); - HttpClient::write((iTxSize >> 32) & 0xff); - HttpClient::write((iTxSize >> 24) & 0xff); - HttpClient::write((iTxSize >> 16) & 0xff); - HttpClient::write((iTxSize >> 8) & 0xff); - HttpClient::write((iTxSize >> 0) & 0xff); - } - - uint8_t maskKey[4]; - - // create a random mask for the data and send - for (int i = 0; i < (int)sizeof(maskKey); i++) - { - maskKey[i] = random(0xff); - } - HttpClient::write(maskKey, sizeof(maskKey)); - - // mask the data and send - for (int i = 0; i < (int)iTxSize; i++) { - iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)]; - } - - size_t txSize = iTxSize; - - iTxStarted = false; - iTxSize = 0; - - return (HttpClient::write(iTxBuffer, txSize) == txSize) ? 0 : 1; -} - -size_t WebSocketClient::write(uint8_t aByte) -{ - return write(&aByte, sizeof(aByte)); -} - -size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize) -{ - if (iState < eReadingBody) - { - // have not upgraded the connection yet - return HttpClient::write(aBuffer, aSize); - } - - if (!iTxStarted) - { - // fail TX not started - return 0; - } - - // check if the write size, fits in the buffer - if ((iTxSize + aSize) > sizeof(iTxBuffer)) - { - aSize = sizeof(iTxSize) - iTxSize; - } - - // copy data into the buffer - memcpy(iTxBuffer + iTxSize, aBuffer, aSize); - - iTxSize += aSize; - - return aSize; -} - -int WebSocketClient::parseMessage() -{ - flushRx(); - - // make sure 2 bytes (opcode + length) - // are available - if (HttpClient::available() < 2) - { - return 0; - } - - // read open code and length - uint8_t opcode = HttpClient::read(); - int length = HttpClient::read(); - - if ((opcode & 0x0f) == 0) - { - // continuation, use previous opcode and update flags - iRxOpCode |= opcode; - } - else - { - iRxOpCode = opcode; - } - - iRxMasked = (length & 0x80); - length &= 0x7f; - - // read the RX size - if (length < 126) - { - iRxSize = length; - } - else if (length == 126) - { - iRxSize = (HttpClient::read() << 8) | HttpClient::read(); - } - else - { - iRxSize = ((uint64_t)HttpClient::read() << 56) | - ((uint64_t)HttpClient::read() << 48) | - ((uint64_t)HttpClient::read() << 40) | - ((uint64_t)HttpClient::read() << 32) | - ((uint64_t)HttpClient::read() << 24) | - ((uint64_t)HttpClient::read() << 16) | - ((uint64_t)HttpClient::read() << 8) | - (uint64_t)HttpClient::read(); - } - - // read in the mask, if present - if (iRxMasked) - { - for (int i = 0; i < (int)sizeof(iRxMaskKey); i++) - { - iRxMaskKey[i] = HttpClient::read(); - } - } - - iRxMaskIndex = 0; - - if (TYPE_CONNECTION_CLOSE == messageType()) - { - flushRx(); - stop(); - iRxSize = 0; - } - else if (TYPE_PING == messageType()) - { - beginMessage(TYPE_PONG); - while(available()) - { - write(read()); - } - endMessage(); - - iRxSize = 0; - } - else if (TYPE_PONG == messageType()) - { - flushRx(); - iRxSize = 0; - } - - return iRxSize; -} - -int WebSocketClient::messageType() -{ - return (iRxOpCode & 0x0f); -} - -bool WebSocketClient::isFinal() -{ - return ((iRxOpCode & 0x80) != 0); -} - -String WebSocketClient::readString() -{ - int avail = available(); - String s; - - if (avail > 0) - { - s.reserve(avail); - - for (int i = 0; i < avail; i++) - { - s += (char)read(); - } - } - - return s; -} - -int WebSocketClient::ping() -{ - uint8_t pingData[16]; - - // create random data for the ping - for (int i = 0; i < (int)sizeof(pingData); i++) - { - pingData[i] = random(0xff); - } - - beginMessage(TYPE_PING); - write(pingData, sizeof(pingData)); - return endMessage(); -} - -int WebSocketClient::available() -{ - if (iState < eReadingBody) - { - return HttpClient::available(); - } - - return iRxSize; -} - -int WebSocketClient::read() -{ - byte b; - - if (read(&b, sizeof(b))) - { - return b; - } - - return -1; -} - -int WebSocketClient::read(uint8_t *aBuffer, size_t aSize) -{ - int readCount = HttpClient::read(aBuffer, aSize); - - if (readCount > 0) - { - iRxSize -= readCount; - - // unmask the RX data if needed - if (iRxMasked) - { - for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++) - { - aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; - } - } - } - - return readCount; -} - -int WebSocketClient::peek() -{ - int p = HttpClient::peek(); - - if (p != -1 && iRxMasked) - { - // unmask the RX data if needed - p = (uint8_t)p ^ iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; - } - - return p; -} - -void WebSocketClient::flushRx() -{ - while(available()) - { - read(); - } -} +// (c) Copyright Arduino. 2016 +// Released under Apache License, version 2.0 + +#include "b64.h" + +#include "WebSocketClient.h" + +WebSocketClient::WebSocketClient(Client &aClient, const char *aServerName, uint16_t aServerPort) + : HttpClient(aClient, aServerName, aServerPort), + iTxStarted(false), + iRxSize(0) +{ +} + +WebSocketClient::WebSocketClient(Client &aClient, const String &aServerName, uint16_t aServerPort) + : HttpClient(aClient, aServerName, aServerPort), + iTxStarted(false), + iRxSize(0) +{ +} + +WebSocketClient::WebSocketClient(Client &aClient, const IPAddress &aServerAddress, uint16_t aServerPort) + : HttpClient(aClient, aServerAddress, aServerPort), + iTxStarted(false), + iRxSize(0) +{ +} + +int WebSocketClient::begin(const char *aPath, const char *protocol = NULL) +{ + // start the GET request + beginRequest(); + connectionKeepAlive(); + int status = get(aPath); + + if (status == 0) + { + uint8_t randomKey[16]; + char base64RandomKey[25]; + + // create a random key for the connection upgrade + for (int i = 0; i < (int)sizeof(randomKey); i++) + { + randomKey[i] = random(0x01, 0xff); + } + memset(base64RandomKey, 0x00, sizeof(base64RandomKey)); + b64_encode(randomKey, sizeof(randomKey), (unsigned char *)base64RandomKey, sizeof(base64RandomKey)); + + // start the connection upgrade sequence + sendHeader("Upgrade", "websocket"); + sendHeader("Connection", "Upgrade"); + sendHeader("Sec-WebSocket-Key", base64RandomKey); + sendHeader("Sec-WebSocket-Version", "13"); + if (protocol) + { + sendHeader("Sec-WebSocket-Protocol", protocol); + } + endRequest(); + + status = responseStatusCode(); + + if (status > 0) + { + skipResponseHeaders(); + } + } + + iRxSize = 0; + + // status code of 101 means success + return (status == 101) ? 0 : status; +} + +int WebSocketClient::begin(const String &aPath, const String &protocol) +{ + return begin(aPath, protocol); +} + +int WebSocketClient::begin(const char *aPath) +{ + return begin(aPath, NULL); +} + +int WebSocketClient::begin(const String &aPath) +{ + return begin(aPath.c_str(), NULL); +} + +int WebSocketClient::beginMessage(int aType) +{ + if (iTxStarted) + { + // fail TX already started + return 1; + } + + iTxStarted = true; + iTxMessageType = (aType & 0xf); + iTxSize = 0; + + return 0; +} + +int WebSocketClient::endMessage() +{ + if (!iTxStarted) + { + // fail TX not started + return 1; + } + + // send FIN + the message type (opcode) + HttpClient::write(0x80 | iTxMessageType); + + // the message is masked (0x80) + // send the length + if (iTxSize < 126) + { + HttpClient::write(0x80 | (uint8_t)iTxSize); + } + else if (iTxSize < 0xffff) + { + HttpClient::write(0x80 | 126); + HttpClient::write((iTxSize >> 8) & 0xff); + HttpClient::write((iTxSize >> 0) & 0xff); + } + else + { + HttpClient::write(0x80 | 127); + HttpClient::write((iTxSize >> 56) & 0xff); + HttpClient::write((iTxSize >> 48) & 0xff); + HttpClient::write((iTxSize >> 40) & 0xff); + HttpClient::write((iTxSize >> 32) & 0xff); + HttpClient::write((iTxSize >> 24) & 0xff); + HttpClient::write((iTxSize >> 16) & 0xff); + HttpClient::write((iTxSize >> 8) & 0xff); + HttpClient::write((iTxSize >> 0) & 0xff); + } + + uint8_t maskKey[4]; + + // create a random mask for the data and send + for (int i = 0; i < (int)sizeof(maskKey); i++) + { + maskKey[i] = random(0xff); + } + HttpClient::write(maskKey, sizeof(maskKey)); + + // mask the data and send + for (int i = 0; i < (int)iTxSize; i++) + { + iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)]; + } + + size_t txSize = iTxSize; + + iTxStarted = false; + iTxSize = 0; + + return (HttpClient::write(iTxBuffer, txSize) == txSize) ? 0 : 1; +} + +size_t WebSocketClient::write(uint8_t aByte) +{ + return write(&aByte, sizeof(aByte)); +} + +size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize) +{ + if (iState < eReadingBody) + { + // have not upgraded the connection yet + return HttpClient::write(aBuffer, aSize); + } + + if (!iTxStarted) + { + // fail TX not started + return 0; + } + + // check if the write size, fits in the buffer + if ((iTxSize + aSize) > sizeof(iTxBuffer)) + { + aSize = sizeof(iTxSize) - iTxSize; + } + + // copy data into the buffer + memcpy(iTxBuffer + iTxSize, aBuffer, aSize); + + iTxSize += aSize; + + return aSize; +} + +int WebSocketClient::parseMessage() +{ + flushRx(); + + // make sure 2 bytes (opcode + length) + // are available + if (HttpClient::available() < 2) + { + return 0; + } + + // read open code and length + uint8_t opcode = HttpClient::read(); + int length = HttpClient::read(); + + if ((opcode & 0x0f) == 0) + { + // continuation, use previous opcode and update flags + iRxOpCode |= opcode; + } + else + { + iRxOpCode = opcode; + } + + iRxMasked = (length & 0x80); + length &= 0x7f; + + // read the RX size + if (length < 126) + { + iRxSize = length; + } + else if (length == 126) + { + iRxSize = (HttpClient::read() << 8) | HttpClient::read(); + } + else + { + iRxSize = ((uint64_t)HttpClient::read() << 56) | + ((uint64_t)HttpClient::read() << 48) | + ((uint64_t)HttpClient::read() << 40) | + ((uint64_t)HttpClient::read() << 32) | + ((uint64_t)HttpClient::read() << 24) | + ((uint64_t)HttpClient::read() << 16) | + ((uint64_t)HttpClient::read() << 8) | + (uint64_t)HttpClient::read(); + } + + // read in the mask, if present + if (iRxMasked) + { + for (int i = 0; i < (int)sizeof(iRxMaskKey); i++) + { + iRxMaskKey[i] = HttpClient::read(); + } + } + + iRxMaskIndex = 0; + + if (TYPE_CONNECTION_CLOSE == messageType()) + { + flushRx(); + stop(); + iRxSize = 0; + } + else if (TYPE_PING == messageType()) + { + beginMessage(TYPE_PONG); + while (available()) + { + write(read()); + } + endMessage(); + + iRxSize = 0; + } + else if (TYPE_PONG == messageType()) + { + flushRx(); + iRxSize = 0; + } + + return iRxSize; +} + +int WebSocketClient::messageType() +{ + return (iRxOpCode & 0x0f); +} + +bool WebSocketClient::isFinal() +{ + return ((iRxOpCode & 0x80) != 0); +} + +String WebSocketClient::readString() +{ + int avail = available(); + String s; + + if (avail > 0) + { + s.reserve(avail); + + for (int i = 0; i < avail; i++) + { + s += (char)read(); + } + } + + return s; +} + +int WebSocketClient::ping() +{ + uint8_t pingData[16]; + + // create random data for the ping + for (int i = 0; i < (int)sizeof(pingData); i++) + { + pingData[i] = random(0xff); + } + + beginMessage(TYPE_PING); + write(pingData, sizeof(pingData)); + return endMessage(); +} + +int WebSocketClient::available() +{ + if (iState < eReadingBody) + { + return HttpClient::available(); + } + + return iRxSize; +} + +int WebSocketClient::read() +{ + byte b; + + if (read(&b, sizeof(b))) + { + return b; + } + + return -1; +} + +int WebSocketClient::read(uint8_t *aBuffer, size_t aSize) +{ + int readCount = HttpClient::read(aBuffer, aSize); + + if (readCount > 0) + { + iRxSize -= readCount; + + // unmask the RX data if needed + if (iRxMasked) + { + for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++) + { + aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; + } + } + } + + return readCount; +} + +int WebSocketClient::peek() +{ + int p = HttpClient::peek(); + + if (p != -1 && iRxMasked) + { + // unmask the RX data if needed + p = (uint8_t)p ^ iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)]; + } + + return p; +} + +void WebSocketClient::flushRx() +{ + while (available()) + { + read(); + } +} diff --git a/src/WebSocketClient.h b/src/WebSocketClient.h old mode 100644 new mode 100755 index 4b009e6..c354bf9 --- a/src/WebSocketClient.h +++ b/src/WebSocketClient.h @@ -1,99 +1,101 @@ -// (c) Copyright Arduino. 2016 -// Released under Apache License, version 2.0 - -#ifndef WebSocketClient_h -#define WebSocketClient_h - -#include - -#include "HttpClient.h" - -static const int TYPE_CONTINUATION = 0x0; -static const int TYPE_TEXT = 0x1; -static const int TYPE_BINARY = 0x2; -static const int TYPE_CONNECTION_CLOSE = 0x8; -static const int TYPE_PING = 0x9; -static const int TYPE_PONG = 0xa; - -class WebSocketClient : public HttpClient -{ -public: - WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort = HttpClient::kHttpPort); - WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort = HttpClient::kHttpPort); - WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = HttpClient::kHttpPort); - - /** Start the Web Socket connection to the specified path - @param aURLPath Path to use in request (optional, "/" is used by default) - @return 0 if successful, else error - */ - int begin(const char* aPath = "/"); - int begin(const String& aPath); - - /** Begin to send a message of type (TYPE_TEXT or TYPE_BINARY) - Use the write or Stream API's to set message content, followed by endMessage - to complete the message. - @param aURLPath Path to use in request - @return 0 if successful, else error - */ - int beginMessage(int aType); - - /** Completes sending of a message started by beginMessage - @return 0 if successful, else error - */ - int endMessage(); - - /** Try to parse an incoming messages - @return 0 if no message available, else size of parsed message - */ - int parseMessage(); - - /** Returns type of current parsed message - @return type of current parsedMessage (TYPE_TEXT or TYPE_BINARY) - */ - int messageType(); - - /** Returns if the current message is the final chunk of a split - message - @return true for final message, false otherwise - */ - bool isFinal(); - - /** Read the current messages as a string - @return current message as a string - */ - String readString(); - - /** Send a ping - @return 0 if successful, else error - */ - int ping(); - - // Inherited from Print - virtual size_t write(uint8_t aByte); - virtual size_t write(const uint8_t *aBuffer, size_t aSize); - // Inherited from Stream - virtual int available(); - /** Read the next byte from the server. - @return Byte read or -1 if there are no bytes available. - */ - virtual int read(); - virtual int read(uint8_t *buf, size_t size); - virtual int peek(); - -private: - void flushRx(); - -private: - bool iTxStarted; - uint8_t iTxMessageType; - uint8_t iTxBuffer[128]; - uint64_t iTxSize; - - uint8_t iRxOpCode; - uint64_t iRxSize; - bool iRxMasked; - int iRxMaskIndex; - uint8_t iRxMaskKey[4]; -}; - -#endif +// (c) Copyright Arduino. 2016 +// Released under Apache License, version 2.0 + +#ifndef WebSocketClient_h +#define WebSocketClient_h + +#include + +#include "HttpClient.h" + +static const int TYPE_CONTINUATION = 0x0; +static const int TYPE_TEXT = 0x1; +static const int TYPE_BINARY = 0x2; +static const int TYPE_CONNECTION_CLOSE = 0x8; +static const int TYPE_PING = 0x9; +static const int TYPE_PONG = 0xa; + +class WebSocketClient : public HttpClient +{ +public: + WebSocketClient(Client &aClient, const char *aServerName, uint16_t aServerPort = HttpClient::kHttpPort); + WebSocketClient(Client &aClient, const String &aServerName, uint16_t aServerPort = HttpClient::kHttpPort); + WebSocketClient(Client &aClient, const IPAddress &aServerAddress, uint16_t aServerPort = HttpClient::kHttpPort); + + /** Start the Web Socket connection to the specified path + @param aURLPath Path to use in request (optional, "/" is used by default) + @return 0 if successful, else error + */ + int begin(const char *aPath = "/"); + int begin(const String &aPath); + int begin(const char *aPath, const char *protocol); + int begin(const String &aPath, const String &protocol); + + /** Begin to send a message of type (TYPE_TEXT or TYPE_BINARY) + Use the write or Stream API's to set message content, followed by endMessage + to complete the message. + @param aURLPath Path to use in request + @return 0 if successful, else error + */ + int beginMessage(int aType); + + /** Completes sending of a message started by beginMessage + @return 0 if successful, else error + */ + int endMessage(); + + /** Try to parse an incoming messages + @return 0 if no message available, else size of parsed message + */ + int parseMessage(); + + /** Returns type of current parsed message + @return type of current parsedMessage (TYPE_TEXT or TYPE_BINARY) + */ + int messageType(); + + /** Returns if the current message is the final chunk of a split + message + @return true for final message, false otherwise + */ + bool isFinal(); + + /** Read the current messages as a string + @return current message as a string + */ + String readString(); + + /** Send a ping + @return 0 if successful, else error + */ + int ping(); + + // Inherited from Print + virtual size_t write(uint8_t aByte); + virtual size_t write(const uint8_t *aBuffer, size_t aSize); + // Inherited from Stream + virtual int available(); + /** Read the next byte from the server. + @return Byte read or -1 if there are no bytes available. + */ + virtual int read(); + virtual int read(uint8_t *buf, size_t size); + virtual int peek(); + +private: + void flushRx(); + +private: + bool iTxStarted; + uint8_t iTxMessageType; + uint8_t iTxBuffer[128]; + uint64_t iTxSize; + + uint8_t iRxOpCode; + uint64_t iRxSize; + bool iRxMasked; + int iRxMaskIndex; + uint8_t iRxMaskKey[4]; +}; + +#endif diff --git a/src/b64.cpp b/src/b64.cpp old mode 100644 new mode 100755 index 683d60a..475250a --- a/src/b64.cpp +++ b/src/b64.cpp @@ -1,72 +1,71 @@ -// Simple Base64 code -// (c) Copyright 2010 MCQN Ltd. -// Released under Apache License, version 2.0 - -#include "b64.h" - -/* Simple test program -#include -void main() -{ - char* in = "amcewen"; - char out[22]; - - b64_encode(in, 15, out, 22); - out[21] = '\0'; - - printf(out); -} -*/ - -int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutput, int aOutputLen) -{ - // Work out if we've got enough space to encode the input - // Every 6 bits of input becomes a byte of output - if (aOutputLen < (aInputLen*8)/6) - { - // FIXME Should we return an error here, or just the length - return (aInputLen*8)/6; - } - - // If we get here we've got enough space to do the encoding - - const char* b64_dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - if (aInputLen == 3) - { - aOutput[0] = b64_dictionary[aInput[0] >> 2]; - aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)]; - aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2|(aInput[2]>>6)]; - aOutput[3] = b64_dictionary[aInput[2]&0x3F]; - } - else if (aInputLen == 2) - { - aOutput[0] = b64_dictionary[aInput[0] >> 2]; - aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)]; - aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2]; - aOutput[3] = '='; - } - else if (aInputLen == 1) - { - aOutput[0] = b64_dictionary[aInput[0] >> 2]; - aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4]; - aOutput[2] = '='; - aOutput[3] = '='; - } - else - { - // Break the input into 3-byte chunks and process each of them - int i; - for (i = 0; i < aInputLen/3; i++) - { - b64_encode(&aInput[i*3], 3, &aOutput[i*4], 4); - } - if (aInputLen % 3 > 0) - { - // It doesn't fit neatly into a 3-byte chunk, so process what's left - b64_encode(&aInput[i*3], aInputLen % 3, &aOutput[i*4], aOutputLen - (i*4)); - } - } - - return ((aInputLen+2)/3)*4; -} - +// Simple Base64 code +// (c) Copyright 2010 MCQN Ltd. +// Released under Apache License, version 2.0 + +#include "b64.h" + +/* Simple test program +#include +void main() +{ + char* in = "amcewen"; + char out[22]; + + b64_encode(in, 15, out, 22); + out[21] = '\0'; + + printf(out); +} +*/ + +int b64_encode(const unsigned char *aInput, int aInputLen, unsigned char *aOutput, int aOutputLen) +{ + // Work out if we've got enough space to encode the input + // Every 6 bits of input becomes a byte of output + if (aOutputLen < (aInputLen * 8) / 6) + { + // FIXME Should we return an error here, or just the length + return (aInputLen * 8) / 6; + } + + // If we get here we've got enough space to do the encoding + + const char *b64_dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (aInputLen == 3) + { + aOutput[0] = b64_dictionary[aInput[0] >> 2]; + aOutput[1] = b64_dictionary[(aInput[0] & 0x3) << 4 | (aInput[1] >> 4)]; + aOutput[2] = b64_dictionary[(aInput[1] & 0x0F) << 2 | (aInput[2] >> 6)]; + aOutput[3] = b64_dictionary[aInput[2] & 0x3F]; + } + else if (aInputLen == 2) + { + aOutput[0] = b64_dictionary[aInput[0] >> 2]; + aOutput[1] = b64_dictionary[(aInput[0] & 0x3) << 4 | (aInput[1] >> 4)]; + aOutput[2] = b64_dictionary[(aInput[1] & 0x0F) << 2]; + aOutput[3] = '='; + } + else if (aInputLen == 1) + { + aOutput[0] = b64_dictionary[aInput[0] >> 2]; + aOutput[1] = b64_dictionary[(aInput[0] & 0x3) << 4]; + aOutput[2] = '='; + aOutput[3] = '='; + } + else + { + // Break the input into 3-byte chunks and process each of them + int i; + for (i = 0; i < aInputLen / 3; i++) + { + b64_encode(&aInput[i * 3], 3, &aOutput[i * 4], 4); + } + if (aInputLen % 3 > 0) + { + // It doesn't fit neatly into a 3-byte chunk, so process what's left + b64_encode(&aInput[i * 3], aInputLen % 3, &aOutput[i * 4], aOutputLen - (i * 4)); + } + } + + return ((aInputLen + 2) / 3) * 4; +} diff --git a/src/b64.h b/src/b64.h old mode 100644 new mode 100755 index cdb1226..55f35e7 --- a/src/b64.h +++ b/src/b64.h @@ -1,6 +1,6 @@ -#ifndef b64_h -#define b64_h - -int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutput, int aOutputLen); - -#endif +#ifndef b64_h +#define b64_h + +int b64_encode(const unsigned char *aInput, int aInputLen, unsigned char *aOutput, int aOutputLen); + +#endif From 1bac571b2728e246fc2654190efc79b73cb769e3 Mon Sep 17 00:00:00 2001 From: jasomorg Date: Tue, 21 Mar 2023 00:17:36 +0000 Subject: [PATCH 2/2] It seems nothing changed --- README.md | 0 library.properties | 0 src/ArduinoHttpClient.h | 0 src/HttpClient.cpp | 0 src/HttpClient.h | 0 src/URLEncoder.cpp | 0 src/URLEncoder.h | 0 src/WebSocketClient.cpp | 0 src/WebSocketClient.h | 0 src/b64.cpp | 0 src/b64.h | 0 11 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 README.md mode change 100755 => 100644 library.properties mode change 100755 => 100644 src/ArduinoHttpClient.h mode change 100755 => 100644 src/HttpClient.cpp mode change 100755 => 100644 src/HttpClient.h mode change 100755 => 100644 src/URLEncoder.cpp mode change 100755 => 100644 src/URLEncoder.h mode change 100755 => 100644 src/WebSocketClient.cpp mode change 100755 => 100644 src/WebSocketClient.h mode change 100755 => 100644 src/b64.cpp mode change 100755 => 100644 src/b64.h diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/library.properties b/library.properties old mode 100755 new mode 100644 diff --git a/src/ArduinoHttpClient.h b/src/ArduinoHttpClient.h old mode 100755 new mode 100644 diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp old mode 100755 new mode 100644 diff --git a/src/HttpClient.h b/src/HttpClient.h old mode 100755 new mode 100644 diff --git a/src/URLEncoder.cpp b/src/URLEncoder.cpp old mode 100755 new mode 100644 diff --git a/src/URLEncoder.h b/src/URLEncoder.h old mode 100755 new mode 100644 diff --git a/src/WebSocketClient.cpp b/src/WebSocketClient.cpp old mode 100755 new mode 100644 diff --git a/src/WebSocketClient.h b/src/WebSocketClient.h old mode 100755 new mode 100644 diff --git a/src/b64.cpp b/src/b64.cpp old mode 100755 new mode 100644 diff --git a/src/b64.h b/src/b64.h old mode 100755 new mode 100644