diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89d225a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.development +examples/node_test_server/node_modules/ diff --git a/ArduinoHttpClient.h b/ArduinoHttpClient.h new file mode 100644 index 0000000..2064028 --- /dev/null +++ b/ArduinoHttpClient.h @@ -0,0 +1,10 @@ +// 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" + +#endif diff --git a/HttpClient.cpp b/HttpClient.cpp index 5a11a45..9176108 100644 --- a/HttpClient.cpp +++ b/HttpClient.cpp @@ -4,36 +4,29 @@ #include "HttpClient.h" #include "b64.h" -#ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet -#include -#endif // Initialize constants const char* HttpClient::kUserAgent = "Arduino/2.2.0"; const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; -#ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet -HttpClient::HttpClient(Client& aClient, const char* aProxy, uint16_t aProxyPort) - : iClient(&aClient), iProxyPort(aProxyPort) +HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort) + : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort), + iConnectionClose(true), iSendDefaultRequestHeaders(true) { resetState(); - if (aProxy) - { - // Resolve the IP address for the proxy - DNSClient dns; - dns.begin(Ethernet.dnsServerIP()); - // Not ideal that we discard any errors here, but not a lot we can do in the ctor - // and we'll get a connect error later anyway - (void)dns.getHostByName(aProxy, iProxyAddress); - } } -#else -HttpClient::HttpClient(Client& aClient) - : iClient(&aClient), iProxyPort(0) + +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(); } -#endif void HttpClient::resetState() { @@ -51,87 +44,66 @@ void HttpClient::stop() resetState(); } +void HttpClient::connectionKeepAlive() +{ + iConnectionClose = false; +} + +void HttpClient::noDefaultRequestHeaders() +{ + iSendDefaultRequestHeaders = false; +} + void HttpClient::beginRequest() { iState = eRequestStarted; } -int HttpClient::startRequest(const char* aServerName, uint16_t aServerPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent) +int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod) { tHttpState initialState = iState; - if ((eIdle != iState) && (eRequestStarted != iState)) - { - return HTTP_ERROR_API; - } -#ifdef PROXY_ENABLED - if (iProxyPort) + if (!iConnectionClose) { - if (!iClient->connect(iProxyAddress, iProxyPort) > 0) - { -#ifdef LOGGING - Serial.println("Proxy connection failed"); -#endif - return HTTP_ERROR_CONNECTION_FAILED; - } - } - else -#endif - { - if (!iClient->connect(aServerName, aServerPort) > 0) - { -#ifdef LOGGING - Serial.println("Connection failed"); -#endif - return HTTP_ERROR_CONNECTION_FAILED; - } - } + flushClientRx(); - // Now we're connected, send the first part of the request - int ret = sendInitialHeaders(aServerName, IPAddress(0,0,0,0), aServerPort, aURLPath, aHttpMethod, aUserAgent); - if ((initialState == eIdle) && (HTTP_SUCCESS == ret)) - { - // This was a simple version of the API, so terminate the headers now - finishHeaders(); + resetState(); } - // else we'll call it in endRequest or in the first call to print, etc. - - return ret; -} -int HttpClient::startRequest(const IPAddress& aServerAddress, const char* aServerName, uint16_t aServerPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent) -{ - tHttpState initialState = iState; if ((eIdle != iState) && (eRequestStarted != iState)) { return HTTP_ERROR_API; } -#ifdef PROXY_ENABLED - if (iProxyPort) + if (iConnectionClose || !iClient->connected()) { - if (!iClient->connect(iProxyAddress, iProxyPort) > 0) - { + if (iServerName) { + if (!iClient->connect(iServerName, iServerPort) > 0) + { #ifdef LOGGING - Serial.println("Proxy connection failed"); + Serial.println("Connection failed"); #endif - return HTTP_ERROR_CONNECTION_FAILED; + 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 -#endif { - if (!iClient->connect(aServerAddress, aServerPort) > 0) - { #ifdef LOGGING - Serial.println("Connection failed"); + Serial.println("Connection already open"); #endif - return HTTP_ERROR_CONNECTION_FAILED; - } } // Now we're connected, send the first part of the request - int ret = sendInitialHeaders(aServerName, aServerAddress, aServerPort, aURLPath, aHttpMethod, aUserAgent); + int ret = sendInitialHeaders(aURLPath, aHttpMethod); if ((initialState == eIdle) && (HTTP_SUCCESS == ret)) { // This was a simple version of the API, so terminate the headers now @@ -142,7 +114,7 @@ int HttpClient::startRequest(const IPAddress& aServerAddress, const char* aServe return ret; } -int HttpClient::sendInitialHeaders(const char* aServerName, IPAddress aServerIP, uint16_t aPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent) +int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod) { #ifdef LOGGING Serial.println("Connected"); @@ -150,54 +122,33 @@ int HttpClient::sendInitialHeaders(const char* aServerName, IPAddress aServerIP, // Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0" iClient->print(aHttpMethod); iClient->print(" "); -#ifdef PROXY_ENABLED - if (iProxyPort) - { - // We're going through a proxy, send a full URL - iClient->print("http://"); - if (aServerName) - { - // We've got a server name, so use it - iClient->print(aServerName); - } - else - { - // We'll have to use the IP address - iClient->print(aServerIP); - } - if (aPort != kHttpPort) - { - iClient->print(":"); - iClient->print(aPort); - } - } -#endif + iClient->print(aURLPath); iClient->println(" HTTP/1.1"); - // The host header, if required - if (aServerName) + if (iSendDefaultRequestHeaders) { - iClient->print("Host: "); - iClient->print(aServerName); - if (aPort != kHttpPort) + // The host header, if required + if (iServerName) { - iClient->print(":"); - iClient->print(aPort); + iClient->print("Host: "); + iClient->print(iServerName); + if (iServerPort != kHttpPort) + { + iClient->print(":"); + iClient->print(iServerPort); + } + iClient->println(); } - iClient->println(); - } - // And user-agent string - if (aUserAgent) - { - sendHeader(HTTP_HEADER_USER_AGENT, aUserAgent); + // And user-agent string + sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); } - else + + if (iConnectionClose) { - sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent); + // Tell the server to + // close this connection after we're done + sendHeader(HTTP_HEADER_CONNECTION, "close"); } - // We don't support persistent connections, so tell the server to - // close this connection after we're done - sendHeader(HTTP_HEADER_CONNECTION, "close"); // Everything has gone well iState = eRequestStarted; @@ -278,6 +229,17 @@ void HttpClient::finishHeaders() iState = eRequestSent; } +void HttpClient::flushClientRx() +{ + if (iClient->connected()) + { + while (iClient->available()) + { + iClient->read(); + } + } +} + void HttpClient::endRequest() { if (iState < eRequestSent) @@ -299,7 +261,7 @@ int HttpClient::responseStatusCode() // Where HTTP-Version is of the form: // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT - char c = '\0'; + int c = '\0'; do { // Make sure the status code is reset, and likewise the state. This @@ -358,6 +320,9 @@ int HttpClient::responseStatusCode() 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(); @@ -431,6 +396,17 @@ int HttpClient::skipResponseHeaders() } } +int HttpClient::contentLength() +{ + // skip the response headers, if they haven't been read already + if (!endOfHeadersReached()) + { + skipResponseHeaders(); + } + + return iContentLength; +} + bool HttpClient::endOfBodyReached() { if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader)) @@ -443,30 +419,79 @@ bool HttpClient::endOfBodyReached() int HttpClient::read() { -#if 0 // Fails on WiFi because multi-byte read seems to be broken - uint8_t b[1]; - int ret = read(b, 1); - if (ret == 1) - { - return b[0]; - } - else - { - return -1; - } -#else 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++; - } + } } return ret; -#endif +} + +bool HttpClient::headerAvailable() +{ + // clear the currently store 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) @@ -477,9 +502,9 @@ int HttpClient::read(uint8_t *buf, size_t size) // 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; } diff --git a/HttpClient.h b/HttpClient.h index b4c3974..4b51ca3 100644 --- a/HttpClient.h +++ b/HttpClient.h @@ -44,11 +44,9 @@ class HttpClient : public Client // FIXME Write longer API request, using port and user-agent, example // FIXME Update tempToPachube example to calculate Content-Length correctly -#ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet - HttpClient(Client& aClient, const char* aProxy =NULL, uint16_t aProxyPort =0); -#else - HttpClient(Client& aClient); -#endif + 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, @@ -63,219 +61,42 @@ class HttpClient : public Client void endRequest(); /** Connect to the server and start to send a GET request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int get(const char* aServerName, uint16_t aServerPort, const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, aServerPort, aURLPath, HTTP_METHOD_GET, aUserAgent); } + int get(const char* aURLPath) + { return startRequest(aURLPath, HTTP_METHOD_GET); } - /** Connect to the server and start to send a GET request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int get(const char* aServerName, const char* aURLPath, const char* aUserAgent =NULL) - { return startRequest(aServerName, kHttpPort, aURLPath, HTTP_METHOD_GET, aUserAgent); } - - /** Connect to the server and start to send a GET request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int get(const IPAddress& aServerAddress, - const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, HTTP_METHOD_GET, aUserAgent); } - - /** Connect to the server and start to send a GET request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int get(const IPAddress& aServerAddress, - const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, HTTP_METHOD_GET, aUserAgent); } + int get(const String& aURLPath) + { return get(aURLPath.c_str()); } /** Connect to the server and start to send a POST request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int post(const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, aServerPort, aURLPath, HTTP_METHOD_POST, aUserAgent); } + int post(const char* aURLPath) + { return startRequest(aURLPath, HTTP_METHOD_POST); } - /** Connect to the server and start to send a POST request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int post(const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, kHttpPort, aURLPath, HTTP_METHOD_POST, aUserAgent); } - - /** Connect to the server and start to send a POST request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int post(const IPAddress& aServerAddress, - const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, HTTP_METHOD_POST, aUserAgent); } - - /** Connect to the server and start to send a POST request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int post(const IPAddress& aServerAddress, - const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, HTTP_METHOD_POST, aUserAgent); } + int post(const String& aURLPath) + { return post(aURLPath.c_str()); } /** Connect to the server and start to send a PUT request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int put(const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, aServerPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); } + int put(const char* aURLPath) + { return startRequest(aURLPath, HTTP_METHOD_PUT); } - /** Connect to the server and start to send a PUT request. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int put(const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerName, kHttpPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); } - - /** Connect to the server and start to send a PUT request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int put(const IPAddress& aServerAddress, - const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); } - - /** Connect to the server and start to send a PUT request. This version connects - doesn't perform a DNS lookup and just connects to the given IP address. - @param aServerAddress IP address of the server to connect to - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aURLPath Url to request - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int put(const IPAddress& aServerAddress, - const char* aServerName, - const char* aURLPath, - const char* aUserAgent =NULL) - { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, HTTP_METHOD_PUT, aUserAgent); } + int put(const String& aURLPath) + { return put(aURLPath.c_str()); } /** Connect to the server and start to send the request. - @param aServerName Name of the server being connected to. - @param aServerPort Port to connect to on the server @param aURLPath Url to request @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int startRequest(const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aHttpMethod, - const char* aUserAgent); - - /** Connect to the server and start to send the request. - @param aServerAddress IP address of the server to connect to. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerPort Port to connect to on the server - @param aURLPath Url to request - @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent - @return 0 if successful, else error - */ - int startRequest(const IPAddress& aServerAddress, - const char* aServerName, - uint16_t aServerPort, - const char* aURLPath, - const char* aHttpMethod, - const char* aUserAgent); + int startRequest(const char* aURLPath, + const char* aHttpMethod); /** Send an additional header line. This can only be called in between the calls to startRequest and finishRequest. @@ -284,6 +105,9 @@ class HttpClient : public Client */ 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 @@ -293,6 +117,9 @@ class HttpClient : public Client */ 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. @@ -303,6 +130,9 @@ class HttpClient : public Client */ 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. @@ -311,21 +141,37 @@ class HttpClient : public Client */ void sendBasicAuth(const char* aUser, const char* aPassword); - /** Finish sending the HTTP request. This basically just sends the blank - line to signify the end of the request - */ - void finishRequest(); + 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 vallue 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(); @@ -335,6 +181,7 @@ class HttpClient : public Client 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(); @@ -353,10 +200,20 @@ class HttpClient : public Client 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() { return iContentLength; }; + int contentLength(); + + /** 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 @@ -387,28 +244,21 @@ class HttpClient : public Client void resetState(); /** Send the first part of the request and the initial headers. - @param aServerName Name of the server being connected to. If NULL, the - "Host" header line won't be sent - @param aServerIP IP address of the server (only used if we're going through a - proxy and aServerName is NULL - @param aServerPort Port of the server being connected to. @param aURLPath Url to request @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc. - @param aUserAgent User-Agent string to send. If NULL the default - user-agent kUserAgent will be sent @return 0 if successful, else error */ - int sendInitialHeaders(const char* aServerName, - IPAddress aServerIP, - uint16_t aPort, - const char* aURLPath, - const char* aHttpMethod, - const char* aUserAgent); + 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; @@ -428,8 +278,13 @@ class HttpClient : public Client eLineStartingCRFound, eReadingBody } tHttpState; - // Ethernet client we're using + // 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 @@ -440,10 +295,10 @@ class HttpClient : public Client int iBodyLengthConsumed; // How far through a Content-Length header prefix we are const char* iContentLengthPtr; - // Address of the proxy to use, if we're using one - IPAddress iProxyAddress; - uint16_t iProxyPort; uint32_t iHttpResponseTimeout; + bool iConnectionClose; + bool iSendDefaultRequestHeaders; + String iHeaderLine; }; #endif diff --git a/README.md b/README.md index d55dfa4..655b618 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,22 @@ -# HttpClient +# ArduinoHttpClient -HttpClient is a library to make it easier to interact with web servers from Arduino. +ArduinoHttpClient is a library to make it easier to interact with web servers from Arduino. -## Dependencies - -- Requires the new Ethernet library API (with DHCP and DNS) which is in Arduino 1.0 and later +Derived from [Adrian McEwen's HttpClient library](https://github.com/amcewen/HttpClient) -## Installation +## Dependencies -1. Download the latest version of the library from https://github.com/amcewen/HttpClient/releases and save the file somewhere -1. In the Arduino IDE, go to the Sketch -> Import Library -> Add Library... menu option -1. Find the zip file that you saved in the first step, and choose that -1. Check that it has been successfully added by opening the Sketch -> Import Library menu. You should now see HttpClient listed among the available libraries. +- Requires a networking hardware and a library that provides transport specific `Client` instance, such as: + - [WiFi101](https://github.com/arduino-libraries/WiFi101) + - [Ethernet](https://github.com/arduino-libraries/Ethernet) + - [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 EthernetClient, WiFiClient and GSMClient. +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. diff --git a/b64.cpp b/b64.cpp index b926cad..683d60a 100644 --- a/b64.cpp +++ b/b64.cpp @@ -66,5 +66,7 @@ int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutpu b64_encode(&aInput[i*3], aInputLen % 3, &aOutput[i*4], aOutputLen - (i*4)); } } + + return ((aInputLen+2)/3)*4; } diff --git a/examples/DweetGet/DweetGet.ino b/examples/DweetGet/DweetGet.ino new file mode 100644 index 0000000..11d3daa --- /dev/null +++ b/examples/DweetGet/DweetGet.ino @@ -0,0 +1,121 @@ +/* + Dweet.io GET client for ArduinoHttpClient library + Connects to dweet.io once every ten seconds, + sends a GET request and a request body. Uses SSL + + Shows how to use Strings to assemble path and parse content + from response. dweet.io expects: + https://dweet.io/get/latest/dweet/for/thingName + + For more on dweet.io, see https://dweet.io/play/ + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 15 Feb 2016 + updated 16 Feb 2016 + by Tom Igoe + + this example is in the public domain +*/ +#include +#include +#include "config.h" + +const char serverAddress[] = "dweet.io"; // server address +int port = 80; +String dweetName = "scandalous-cheese-hoarder"; // use your own thing name here + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +int statusCode = 0; +int contentLength = 0; +String response; + +void setup() { + Serial.begin(9600); + while (!Serial); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + // assemble the path for the GET message: + String path = "/get/latest/dweet/for/" + dweetName; + + // send the GET request + Serial.println("making GET request"); + client.beginRequest(); + client.get(path); + client.endRequest(); + + // read the status code of the response + statusCode = client.responseStatusCode(); + Serial.print("Status code: "); + Serial.println(statusCode); + + // read the content length of the response + contentLength = client.contentLength(); + Serial.print("Content Length: "); + Serial.println(contentLength); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Response: "); + Serial.println(response); + + /* + Typical response is: + {"this":"succeeded", + "by":"getting", + "the":"dweets", + "with":[{"thing":"my-thing-name", + "created":"2016-02-16T05:10:36.589Z", + "content":{"sensorValue":456}}]} + + You want "content": numberValue + */ + // now parse the response looking for "content": + int labelStart = response.indexOf("content\":"); + // find the first { after "content": + int contentStart = response.indexOf("{", labelStart); + // find the following } and get what's between the braces: + int contentEnd = response.indexOf("}", labelStart); + String content = response.substring(contentStart + 1, contentEnd); + Serial.println(content); + + // now get the value after the colon, and convert to an int: + int valueStart = content.indexOf(":"); + String valueString = content.substring(valueStart + 1); + int number = valueString.toInt(); + Serial.print("Value string: "); + Serial.println(valueString); + Serial.print("Actual value: "); + Serial.println(number); + + Serial.println("Wait ten seconds\n"); + delay(10000); +} diff --git a/examples/DweetGet/config.h b/examples/DweetGet/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/DweetGet/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/DweetPost/DweetPost.ino b/examples/DweetPost/DweetPost.ino new file mode 100644 index 0000000..04b5f3e --- /dev/null +++ b/examples/DweetPost/DweetPost.ino @@ -0,0 +1,93 @@ +/* + Dweet.io POST client for ArduinoHttpClient library + Connects to dweet.io once every ten seconds, + sends a POST request and a request body. + + Shows how to use Strings to assemble path and body + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 15 Feb 2016 + by Tom Igoe + + this example is in the public domain +*/ +#include +#include +#include "config.h" + +const char serverAddress[] = "dweet.io"; // server address +int port = 80; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +int statusCode = 0; +int contentLength = 0; +String response; + +void setup() { + Serial.begin(9600); + while(!Serial); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + // assemble the path for the POST message: + String dweetName = "scandalous-cheese-hoarder"; + String path = "/dweet/for/" + dweetName; + + // assemble the body of the POST message: + int sensorValue = analogRead(A0); + String postData = "{\"sensorValue\":\""; + postData += sensorValue; + postData += "\"}"; + + Serial.println("making POST request"); + + // send the POST request + client.beginRequest(); + client.post(path); + client.sendHeader("Content-Type", "application/json"); + client.sendHeader("Content-Length", postData.length()); + client.endRequest(); + client.write((const byte*)postData.c_str(), postData.length()); + + // read the status code and content length of the response + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait ten seconds\n"); + delay(10000); +} diff --git a/examples/DweetPost/config.h b/examples/DweetPost/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/DweetPost/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/HueBlink/HueBlink.ino b/examples/HueBlink/HueBlink.ino new file mode 100644 index 0000000..3d6a028 --- /dev/null +++ b/examples/HueBlink/HueBlink.ino @@ -0,0 +1,110 @@ +/* HueBlink example for ArduinoHttpClient library + + Uses ArduinoHttpClient library to control Philips Hue + For more on Hue developer API see http://developer.meethue.com + + To control a light, the Hue expects a HTTP PUT request to: + + http://hue.hub.address/api/hueUserName/lights/lightNumber/state + + The body of the PUT request looks like this: + {"on": true} or {"on":false} + + This example shows how to concatenate Strings to assemble the + PUT request and the body of the request. + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + modified 15 Feb 2016 + by Tom Igoe (tigoe) to match new API +*/ + +#include +#include +#include +#include "config.h" + +int status = WL_IDLE_STATUS; // the Wifi radio's status +char hueHubIP[] = "192.168.0.3"; // IP address of the HUE bridge +String hueUserName = "huebridgeusername"; // hue bridge username + +// make a wifi instance and a HttpClient instance: +WiFiClient wifi; +HttpClient httpClient = HttpClient(wifi, hueHubIP); + + +void setup() { + //Initialize serial and wait for port to open: + Serial.begin(9600); + while (!Serial); // wait for serial port to connect. + + // attempt to connect to Wifi network: + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to WPA SSID: "); + Serial.println(ssid); + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // you're connected now, so print out the data: + Serial.print("You're connected to the network IP = "); + IPAddress ip = WiFi.localIP(); + Serial.println(ip); +} + +void loop() { + sendRequest(3, "on", "true"); // turn light on + delay(2000); // wait 2 seconds + sendRequest(3, "on", "false"); // turn light off + delay(2000); // wait 2 seconds +} + +void sendRequest(int light, String cmd, String value) { + // make a String for the HTTP request path: + String request = "/api/" + hueUserName; + request += "/lights/"; + request += light; + request += "/state/"; + + // make a string for the JSON command: + String hueCmd = "{\"" + cmd; + hueCmd += "\":"; + hueCmd += value; + hueCmd += "}"; + // see what you assembled to send: + Serial.print("PUT request to server: "); + Serial.println(request); + Serial.print("JSON command to server: "); + + // make the PUT request to the hub: + httpClient.beginRequest(); + httpClient.put(request); + httpClient.sendHeader("Content-Type", "application/json"); + httpClient.sendHeader("Content-Length", hueCmd.length()); + httpClient.endRequest(); + httpClient.write((const byte*)hueCmd.c_str(), hueCmd.length()); + + // read the status code and content length of the response + int statusCode = httpClient.responseStatusCode(); + int contentLength = httpClient.contentLength(); + + // read the response body + String response = ""; + response.reserve(contentLength); + while (httpClient.available()) { + response += (char)httpClient.read(); + } + + Serial.println(hueCmd); + Serial.print("Status code from server: "); + Serial.println(statusCode); + Serial.print("Server response: "); + Serial.println(response); + Serial.println(); +} + + diff --git a/examples/HueBlink/config.h b/examples/HueBlink/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/HueBlink/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/SimpleDelete/SimpleDelete.ino b/examples/SimpleDelete/SimpleDelete.ino new file mode 100644 index 0000000..591f4de --- /dev/null +++ b/examples/SimpleDelete/SimpleDelete.ino @@ -0,0 +1,80 @@ +/* + Simple DELETE client for ArduinoHttpClient library + Connects to server once every five seconds, sends a DELETE request + and a request body + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; +int contentLength = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making DELETE request"); + String delData = "name=light&age=46"; + + client.beginRequest(); + client.startRequest("/", HTTP_METHOD_DELETE); + client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); + client.sendHeader("Content-Length", delData.length()); + client.endRequest(); + client.write((const byte*)delData.c_str(), delData.length()); + + // read the status code and content length of the response + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/SimpleDelete/config.h b/examples/SimpleDelete/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/SimpleDelete/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/SimpleGet/SimpleGet.ino b/examples/SimpleGet/SimpleGet.ino new file mode 100644 index 0000000..86cdb9d --- /dev/null +++ b/examples/SimpleGet/SimpleGet.ino @@ -0,0 +1,74 @@ +/* + Simple GET client for ArduinoHttpClient library + Connects to server once every five seconds, sends a GET request + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; +int contentLength = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making GET request"); + + // read the status code and content length of the response + client.beginRequest(); + client.get("/"); + client.endRequest(); + + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/SimpleGet/config.h b/examples/SimpleGet/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/SimpleGet/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/SimpleHttpExample/SimpleHttpExample.ino b/examples/SimpleHttpExample/SimpleHttpExample.ino index f12f987..a6aa6b6 100644 --- a/examples/SimpleHttpExample/SimpleHttpExample.ino +++ b/examples/SimpleHttpExample/SimpleHttpExample.ino @@ -6,45 +6,54 @@ // outputs the content to the serial port #include -#include -#include -#include +#include +#include // This example downloads the URL "http://arduino.cc/" +char ssid[] = "yourNetwork"; // your network SSID (name) +char pass[] = "secretPassword"; // your network password + // Name of the server we want to connect to const char kHostname[] = "arduino.cc"; // Path to download (this is the bit after the hostname in the URL // that you want to download const char kPath[] = "/"; -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; - // Number of milliseconds to wait without receiving any data before we give up const int kNetworkTimeout = 30*1000; // Number of milliseconds to wait if no data is available before trying again const int kNetworkDelay = 1000; +WiFiClient c; +HttpClient http(c, kHostname); + void setup() { - // initialize serial communications at 9600 bps: - Serial.begin(9600); + //Initialize serial and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. Needed for native USB port only + } - while (Ethernet.begin(mac) != 1) - { - Serial.println("Error getting IP address via DHCP, trying again..."); - delay(15000); - } + // attempt to connect to Wifi network: + Serial.print("Attempting to connect to WPA SSID: "); + Serial.println(ssid); + while (WiFi.begin(ssid, pass) != WL_CONNECTED) { + // unsuccessful, retry in 4 seconds + Serial.print("failed ... "); + delay(4000); + Serial.print("retrying ... "); + } + + Serial.println("connected"); } void loop() { int err =0; - EthernetClient c; - HttpClient http(c); - - err = http.get(kHostname, kPath); + err = http.get(kPath); if (err == 0) { Serial.println("startedRequest ok"); @@ -59,44 +68,43 @@ void loop() // similar "success" code (200-299) before carrying on, // but we'll print out whatever response we get - err = http.skipResponseHeaders(); - if (err >= 0) - { - int bodyLen = http.contentLength(); - Serial.print("Content length is: "); - Serial.println(bodyLen); - Serial.println(); - Serial.println("Body returned follows:"); - - // Now we've got to the body, so we can print it out - unsigned long timeoutStart = millis(); - char c; - // Whilst we haven't timed out & haven't reached the end of the body - while ( (http.connected() || http.available()) && - ((millis() - timeoutStart) < kNetworkTimeout) ) - { - if (http.available()) - { - c = http.read(); - // Print out this character - Serial.print(c); - - bodyLen--; - // 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(kNetworkDelay); - } - } - } - else + // If you are interesting in the response headers, you + // can read them here: + //while(http.headerAvailable()) + //{ + // String headerName = http.readHeaderName(); + // String headerValue = http.readHeaderValue(); + //} + + int bodyLen = http.contentLength(); + Serial.print("Content length is: "); + Serial.println(bodyLen); + Serial.println(); + Serial.println("Body returned follows:"); + + // Now we've got to the body, so we can print it out + unsigned long timeoutStart = millis(); + char c; + // Whilst we haven't timed out & haven't reached the end of the body + while ( (http.connected() || http.available()) && + (!http.endOfBodyReached()) && + ((millis() - timeoutStart) < kNetworkTimeout) ) { - Serial.print("Failed to skip response headers: "); - Serial.println(err); + if (http.available()) + { + c = http.read(); + // Print out this character + Serial.print(c); + + // 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(kNetworkDelay); + } } } else diff --git a/examples/SimplePost/SimplePost.ino b/examples/SimplePost/SimplePost.ino new file mode 100644 index 0000000..46982ca --- /dev/null +++ b/examples/SimplePost/SimplePost.ino @@ -0,0 +1,80 @@ +/* + Simple POST client for ArduinoHttpClient library + Connects to server once every five seconds, sends a POST request + and a request body + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; +int contentLength = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making POST request"); + String postData = "name=Alice&age=12"; + + client.beginRequest(); + client.post("/"); + client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); + client.sendHeader("Content-Length", postData.length()); + client.endRequest(); + client.write((const byte*)postData.c_str(), postData.length()); + + // read the status code and content length of the response + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/SimplePost/config.h b/examples/SimplePost/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/SimplePost/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/SimplePut/SimplePut.ino b/examples/SimplePut/SimplePut.ino new file mode 100644 index 0000000..611f55c --- /dev/null +++ b/examples/SimplePut/SimplePut.ino @@ -0,0 +1,80 @@ +/* + Simple PUT client for ArduinoHttpClient library + Connects to server once every five seconds, sends a PUT request + and a request body + + note: WiFi SSID and password are stored in config.h file. + If it is not present, add a new tab, call it "config.h" + and add the following variables: + char ssid[] = "ssid"; // your network SSID (name) + char pass[] = "password"; // your network password + + created 14 Feb 2016 + by Tom Igoe + + this example is in the public domain + */ +#include +#include +#include "config.h" + +char serverAddress[] = "192.168.0.3"; // server address +int port = 8080; + +WiFiClient wifi; +HttpClient client = HttpClient(wifi, serverAddress, port); +int status = WL_IDLE_STATUS; +String response; +int statusCode = 0; +int contentLength = 0; + +void setup() { + Serial.begin(9600); + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to Network named: "); + Serial.println(ssid); // print the network name (SSID); + + // Connect to WPA/WPA2 network: + status = WiFi.begin(ssid, pass); + } + + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); +} + +void loop() { + Serial.println("making PUT request"); + String putData = "name=light&age=46"; + + client.beginRequest(); + client.put("/"); + client.sendHeader("Content-Type", "application/x-www-form-urlencoded"); + client.sendHeader("Content-Length", putData.length()); + client.endRequest(); + client.write((const byte*)putData.c_str(), putData.length()); + + // read the status code and content length of the response + statusCode = client.responseStatusCode(); + contentLength = client.contentLength(); + + // read the response body + response = ""; + response.reserve(contentLength); + while (client.available()) { + response += (char)client.read(); + } + + Serial.print("Status code: "); + Serial.println(statusCode); + Serial.print("Response: "); + Serial.println(response); + + Serial.println("Wait five seconds"); + delay(5000); +} diff --git a/examples/SimplePut/config.h b/examples/SimplePut/config.h new file mode 100644 index 0000000..c263766 --- /dev/null +++ b/examples/SimplePut/config.h @@ -0,0 +1,2 @@ +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password diff --git a/examples/node_test_server/getPostPutDelete.js b/examples/node_test_server/getPostPutDelete.js new file mode 100644 index 0000000..ec58f82 --- /dev/null +++ b/examples/node_test_server/getPostPutDelete.js @@ -0,0 +1,42 @@ +/* + Express.js GET/POST example + Shows how handle GET, POST, PUT, DELETE + in Express.js 4.0 + + created 14 Feb 2016 + by Tom Igoe +*/ + +var express = require('express'); // include express.js +var app = express(); // a local instance of it +var bodyParser = require('body-parser'); // include body-parser + +// you need a body parser: +app.use(bodyParser.urlencoded({extended: false})); // for application/x-www-form-urlencoded + +// this runs after the server successfully starts: +function serverStart() { + var port = server.address().port; + console.log('Server listening on port '+ port); +} + +// this is the POST handler: +app.all('/*', function (request, response) { + console.log('Got a ' + request.method + ' request'); + // the parameters of a GET request are passed in + // request.body. Pass that to formatResponse() + // for formatting: + console.log(request.headers); + if (request.method == 'GET') { + console.log(request.query); + } else { + console.log(request.body); + } + + // send the response: + response.send('OK'); + response.end(); +}); + +// start the server: +var server = app.listen(8080, serverStart); diff --git a/examples/node_test_server/package.json b/examples/node_test_server/package.json new file mode 100644 index 0000000..d6fb7cc --- /dev/null +++ b/examples/node_test_server/package.json @@ -0,0 +1,16 @@ +{ + "name": "node_test_server", + "version": "0.0.1", + "author": { + "name":"Tom Igoe" + }, + "dependencies": { + "express": ">=4.0.0", + "body-parser" : ">=1.11.0", + "multer" : "*" + }, + "engines": { + "node": "0.10.x", + "npm": "1.3.x" + } +} diff --git a/keywords.txt b/keywords.txt index cdefda4..dc1140e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -6,6 +6,7 @@ # Datatypes (KEYWORD1) ####################################### +ArduinoHttpClient KEYWORD1 HttpClient KEYWORD1 ####################################### @@ -27,6 +28,11 @@ endOfHeadersReached KEYWORD2 endOfBodyReached KEYWORD2 completed KEYWORD2 contentLength KEYWORD2 +connectionKeepAlive KEYWORD2 +noDefaultRequestHeaders KEYWORD2 +headerAvailable KEYWORD2 +readHeaderName KEYWORD2 +readHeaderValue KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/library.properties b/library.properties index 577c5d9..35c75cc 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ -name=HttpClient +name=ArduinoHttpClient version=2.2.0 author=Adrian McEwen -maintainer=Adrian McEwen +maintainer=Arduino sentence=Library to easily make HTTP GET, POST and PUT requests to a web server. paragraph=Works with any class derived from Client - so switching between Ethernet, WiFi and GSMClient requires minimal code changes. category=Communication -url=http://github.com/amcewen/HttpClient +url=https://github.com/arduino-libraries/ArduinoHttpClient architectures=*