diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/README.md b/README.md index d280edd..550eb07 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,18 @@ HTTP Request library for Arduino and the ESP8266 WiFi SOC modules -This library now supports SSL! To use with SSL, you need to include the SHA1 fingerprint of the certificate of the site you are connecting to. You can get this by using a desktop browser and inspecting the SSL cert used at the site. Please note: this is FRAGILE, if the site updates their SSL, your code will break. But, there is not enough memory on the ESP8266 to store all the rool certs, so this is a working method. Se the example below. +This library supports SSL! +To use with SSL either include the SHA1 fingerprint of the certificate of the site you are connecting to, or force the library to use ssl insecurely (don't veryify the server is who it says it is). + +You can get the SHA1 fingerprint by using a desktop browser and inspecting the SSL cert used at the site. Please note: this is FRAGILE, if the site updates their SSL, your code will break. But, there is not enough memory on the ESP8266 to store all the rool certs, so this is a working method. Se the example below. This library is derived almost entirely from the great work done here: https://github.com/csquared/arduino-restclient +# Fork note + +API I used returns token inside response header, there was no way to access it, so I've managed to prepare two more functions. One for full access to raw response, and the second to fetch specific header value in simplest possible way. +I have no time to prepare full test suites, but it should work on any environments. + # Install Clone (or download and unzip) the repository to `~/Documents/Arduino/libraries` @@ -16,92 +24,124 @@ where `~/Documents/Arduino` is your sketchbook directory. > cd libraries > git clone https://github.com/dakaz/esp8266-restclient.git RestClient -# Usage - -### Include +# Dependencies -You need to have the `ESP8266` board support already included. +You need to have the `ESP8266` board support already installed. -### RestClient(host/ip, [port]) +# Usage Constructor to create an RestClient object to make requests against. +```c++ +RestClient(const char* host/ip, [int port], [bool force / const char* fingerprint]); +``` -Use domain name and default to port 80: +Use a domain name and default to port 80: ```c++ -RestClient client = RestClient("arduino-http-lib-test.herokuapp.com"); +RestClient client = RestClient("esp-rest-test.herokuapp.com"); ``` Use a local IP and an explicit port: ```c++ -RestClient client = RestClient("192.168.1.50",5000); +RestClient client = RestClient("192.168.1.50", 5000); ``` -Use a local IP, an explicit port to an SSL site and (must include the 1 to turn on SSL): +Use a domain name, an explicit port to an SSL site and verify the certificate with its fingerprint: ```c++ -RestClient client = RestClient("www.kudoso.com",443, 1); +RestClient client = RestClient("www.kudoso.com", 443, "EE 16 77 79 55 58 92 46 FB 18 40 99 2E 17 7E AB 32 0A 4A 88"); ``` -Use a local IP, an explicit port to an SSL site and verify the certificate with its fingerprint: +Use a domain name, an explicit port to an SSL site and force insecure SSL with no certificate verification: ```c++ -RestClient client = RestClient("www.kudoso.com",443, "EE 16 77 79 55 58 92 46 FB 18 40 99 2E 17 7E AB 32 0A 4A 88"); +RestClient client = RestClient("www.kudoso.com", 443, 1); ``` ### dhcp() Sets up `EthernetClient` with a mac address of `DEADBEEFFEED`. Returns `true` or `false` to indicate if setting up DHCP -was successful or not +was successful or not. Not used on the ESP. ## RESTful methods All methods return an HTTP status code or 0 if there was an error. -### `get(const char* path)` -### `get(const char* path, String* response)` - Start making requests! +## GET + ```c++ -int statusCode = client.get("/")); +get(const char* path, [String* response]); ``` -Pass in a string *by reference* for the response: +Examples: +```c++ +int statusCode = client.get("/"); ``` + +```c++ String response = ""; int statusCode = client.get("/", &response); ``` -### post(const char* path, const char* body) -### post(const char* path, String* response) -### post(const char* path, const char* body, String* response) +## POST +There are three different overloads of post: +```c++ +post(const char* path, const char* body); +post(const char* path, String* response); +post(const char* path, const char* body, String* response); ``` + +Examples: +```c++ String response = ""; int statusCode = client.post("/", &response); -statusCode = client.post("/", "foo=bar"); -response = ""; -statusCode = client.post("/", "foo=bar", &response); ``` -### put(const char* path, const char* body) -### put(const char* path, String* response) -### put(const char* path, const char* body, String* response) +```c++ +const char* post_body = "{foo: 'bar', bob: 'alice'}"; +int statusCode = client.post("/", post_body); ``` + +```c++ String response = ""; -int statusCode = client.put("/", &response); -statusCode = client.put("/", "foo=bar"); -response = ""; -statusCode = client.put("/", "foo=bar", &response); +const char* post_body = "{foo: 'bar', bob: 'alice'}"; + +int statusCode = client.post("/", post_body, &response); ``` -### del(const char* path) -### del(const char* path, const char* body) -### del(const char* path, String* response) -### del(const char* path, const char* body, String* response) +## PUT +```c++ +put(const char* path, const char* body); +put(const char* path, String* response); +put(const char* path, const char* body, String* response); ``` -String response = ""; -int statusCode = client.del("/", &response); + +## DEL + +```c++ +del(const char* path); +del(const char* path, const char* body); +del(const char* path, String* response); +del(const char* path, const char* body, String* response); +``` + +## Additional function implemented in this fork + +```c++ +// Get raw response (without body) +getRawLastResponse(); +// Get response header value by key +getResponseHeader(const char* key, String*); +``` + +Examples: + +```c++ +String rawResponse = client.getRawLastResponse(); +String headerValue; +client.getResponseHeader("Token", &headerValue); ``` ## Full Example diff --git a/RestClient.cpp b/RestClient.cpp index a289639..92305dd 100644 --- a/RestClient.cpp +++ b/RestClient.cpp @@ -2,32 +2,23 @@ #ifdef HTTP_DEBUG #define HTTP_DEBUG_PRINT(string) (Serial.print(string)) -#endif - -#ifndef HTTP_DEBUG +#else #define HTTP_DEBUG_PRINT(string) #endif RestClient::RestClient(const char* _host){ host = _host; port = 80; - ssl = 0; - fingerprint = NULL; - num_headers = 0; - if (!contentType) { - contentType = "application/x-www-form-urlencoded"; // default - } + ssl = false; + contentType = "application/x-www-form-urlencoded"; + rawLastResponse = ""; } RestClient::RestClient(const char* _host, int _port){ host = _host; port = _port; - ssl = 0; - fingerprint = NULL; - num_headers = 0; - if (!contentType) { - contentType = "application/x-www-form-urlencoded"; // default - } + ssl = false; + contentType = "application/x-www-form-urlencoded"; } bool RestClient::dhcp(){ @@ -45,23 +36,16 @@ bool RestClient::dhcp(){ RestClient::RestClient(const char* _host, int _port, const char* _fingerprint){ host = _host; port = _port; - ssl = 1; + ssl = true; fingerprint = _fingerprint; - num_headers = 0; - if (!contentType) { - contentType = "application/x-www-form-urlencoded"; // default - } + contentType = "application/x-www-form-urlencoded"; } -RestClient::RestClient(const char* _host, int _port, int _ssl) { +RestClient::RestClient(const char* _host, int _port, bool _ssl) { host = _host; port = _port; - ssl = (_ssl) ? 1 : 0; - fingerprint = NULL; - num_headers = 0; - if (!contentType) { - contentType = "application/x-www-form-urlencoded"; // default - } + ssl = _ssl; + contentType = "application/x-www-form-urlencoded"; } // GET path @@ -146,8 +130,12 @@ void RestClient::setContentType(const char* contentTypeValue){ contentType = contentTypeValue; } -void RestClient::setSSL(int _ssl){ - ssl = (_ssl) ? 1 : 0; +void RestClient::setSSL(bool _ssl){ + ssl = _ssl; +} + +void RestClient::setPort(int _port){ + port = _port; } // The mother- generic request method. @@ -158,19 +146,19 @@ int RestClient::request(const char* method, const char* path, HTTP_DEBUG_PRINT("HTTP: connect\n"); if (ssl) { + if (fingerprint) + { + sslClient.setFingerprint(fingerprint); + } + else + { + sslClient.setInsecure(); + } + if(!sslClient.connect(host, port)){ HTTP_DEBUG_PRINT("HTTPS Connection failed\n"); return 0; } - if (fingerprint) { - HTTP_DEBUG_PRINT("Verifiying SSL certificate\n"); - if (sslClient.verify(fingerprint, host)) { - HTTP_DEBUG_PRINT("SSL certificate matches\n"); - } else { - HTTP_DEBUG_PRINT("SSL certificate does not match\n"); - return 0; - } - } } else { if(!client.connect(host, port)){ HTTP_DEBUG_PRINT("HTTP Connection failed\n"); @@ -201,7 +189,7 @@ int RestClient::request(const char* method, const char* path, request += String(body); request += "\r\n\r\n"; } - + delay(0); write(request.c_str()); HTTP_DEBUG_PRINT("\nEND REQUEST\n"); @@ -238,22 +226,16 @@ int RestClient::readResponse(String* response) { int i = 0; int code = 0; - if(response == NULL){ - HTTP_DEBUG_PRINT("HTTP: NULL RESPONSE POINTER: \n"); - }else{ - HTTP_DEBUG_PRINT("HTTP: NON-NULL RESPONSE POINTER: \n"); - } - - HTTP_DEBUG_PRINT("HTTP: RESPONSE: \n"); void* http_client; if(ssl) { HTTP_DEBUG_PRINT("HTTP: Connect: " + String(sslClient.connected()) + " Available: " + String(sslClient.available()) + "\n"); while (sslClient.connected()) { - HTTP_DEBUG_PRINT("."); + // HTTP_DEBUG_PRINT("."); + delay(0); if (sslClient.available()) { - HTTP_DEBUG_PRINT(","); - + // HTTP_DEBUG_PRINT(","); + delay(0); char c = sslClient.read(); HTTP_DEBUG_PRINT(c); @@ -281,26 +263,26 @@ int RestClient::readResponse(String* response) { } if (c == '\n') { - // you're starting a new line + // your starting a new line currentLineIsBlank = true; } else if (c != '\r') { // you've gotten a character on the current line currentLineIsBlank = false; } + + rawLastResponse = rawLastResponse + c; } } } HTTP_DEBUG_PRINT("HTTPS client closed \n"); }else { - while (client.connected()) { - HTTP_DEBUG_PRINT("."); + while (client.connected() || client.available()) { + delay(0); if (client.available()) { - HTTP_DEBUG_PRINT(","); - + delay(0); char c = client.read(); - HTTP_DEBUG_PRINT(c); if(c == ' ' && !inStatus){ inStatus = true; @@ -316,7 +298,7 @@ int RestClient::readResponse(String* response) { } if(httpBody){ - //only write response if its not null + // only write response if its not null if(response != NULL) response->concat(c); } else @@ -326,18 +308,43 @@ int RestClient::readResponse(String* response) { } if (c == '\n') { - // you're starting a new line + // your starting a new line currentLineIsBlank = true; } else if (c != '\r') { // you've gotten a character on the current line currentLineIsBlank = false; } + + rawLastResponse = rawLastResponse + c; } } } + while (client.available()) { + char c = client.read(); + if (httpBody) + if (response) + response->concat(c); + } } HTTP_DEBUG_PRINT("HTTP: return readResponse3\n"); return code; } + +String RestClient::getRawLastResponse() { + return rawLastResponse; +} + +void RestClient::getResponseHeader(const char* key, String* value) { + HTTP_DEBUG_PRINT(rawLastResponse); + String fullLengthPrefix = (String) key + ": "; + HTTP_DEBUG_PRINT(fullLengthPrefix); + int start = rawLastResponse.indexOf(fullLengthPrefix); + int end = rawLastResponse.indexOf("\n", start); + HTTP_DEBUG_PRINT(start); + HTTP_DEBUG_PRINT(end); + if(start && end) { + value->concat(rawLastResponse.substring(start + fullLengthPrefix.length(), end-1)); + } +} diff --git a/RestClient.h b/RestClient.h index 176a13b..5c63c7b 100644 --- a/RestClient.h +++ b/RestClient.h @@ -2,32 +2,33 @@ #define RestClient_h #include -#include #include class RestClient { public: - RestClient(const char* host); - RestClient(const char* _host, int _port); + explicit RestClient(const char* host); + explicit RestClient(const char* _host, int _port); // set ssl to on but do not verify server identity with fingerprint - RestClient(const char* _host, int _port, int _ssl); + explicit RestClient(const char* _host, int _port, bool _ssl); // set fingerprint if using SSL, stores the SHA1 fingerprint of the remote site, implicity sets ssl to on - RestClient(const char* _host, int _port, const char* _fingerprint); + explicit RestClient(const char* _host, int _port, const char* _fingerprint); - //Client Setup + // Client Setup bool dhcp(); int begin(byte*); - //Generic HTTP Request + // Generic HTTP Request int request(const char* method, const char* path, const char* body, String* response); // Set a Request Header void setHeader(const char*); // Set Content-Type Header void setContentType(const char*); - // Set SSL support on(1) or off(0) - void setSSL(int); + // Set SSL support on or off + void setSSL(bool); + // Set port used + void setPort(int); // GET path int get(const char*); @@ -58,18 +59,24 @@ class RestClient { // DELETE path and body and response int del(const char*, const char*, String*); + // Get raw last response + String getRawLastResponse(); + // Get response header + void getResponseHeader(const char* key, String*); + private: WiFiClient client; WiFiClientSecure sslClient; + String rawLastResponse; int readResponse(String*); void write(const char*); - const char* host; - int port; - int num_headers; - const char* headers[10]; - const char* contentType; - const char* fingerprint; - int ssl; + const char* host = nullptr; + int port = 0; + int num_headers = 0; + const char* headers[10] = { nullptr }; + const char* contentType = nullptr; + const char* fingerprint = nullptr; + bool ssl = false; }; #endif diff --git a/examples/full_test_suite_esp/full_test_suite_esp.ino b/examples/full_test_suite_esp/full_test_suite_esp.ino new file mode 100644 index 0000000..c6cd1b5 --- /dev/null +++ b/examples/full_test_suite_esp/full_test_suite_esp.ino @@ -0,0 +1,271 @@ +/* RestClient full test suite + * + * Every REST method is called. + * + * by Mikkel Jeppesen (Duckle) + * + * Meant to call the API provided by: https://github.com/Hal9k-dk/REST-test + */ + +#include +#include "RestClient.h" + +int test_delay = 1000; //so we don't spam the API +boolean describe_tests = true; + +RestClient client = RestClient("esp-rest-test.herokuapp.com"); +//RestClient client = RestClient("10.0.1.47",5000); + + +const char ssid[] = "SSID"; +const char pass[] = "PASS"; + +//Setup +void setup() +{ + Serial.begin(115200); + delay(10); + + // Connect to Wi-Fi network + Serial.println(); + Serial.println(); + Serial.print("Connecting to..."); + Serial.println(ssid); + + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print("."); + } + Serial.println("\nWi-Fi connected successfully\n"); +} + +void test_status(int statusCode) +{ + delay(test_delay); + if(statusCode == 200) + { + Serial.print(" OK ("); + Serial.print(statusCode); + Serial.println(")"); + } + else + { + Serial.print(" FAIL ("); + Serial.print(statusCode); + Serial.println(")"); + } +} + +String response; +void test_response(bool has_body=false, int headers=0) +{ + String body, header1, header2; + bool ok = response.startsWith(String("OK")); + + if(ok) + { + response.remove(0,2); + + if(has_body) + { + body = response.substring(0,4); + response.remove(0,4); + } + + if(headers > 0) + { + header1 = response.substring(0,2); + response.remove(0,2); + } + + if(headers > 1) + { + header2 = response.substring(0,2); + response.remove(0,2); + } + + if (has_body) + { + String out = (body == "test") ? " Body(OK): " : " Body(FAIL): "; + Serial.print(out); Serial.println(body); + } + + if(headers) + { + String out = (header1 == "h1") ? " H1(OK): " : " H1(FAIL): "; + Serial.print(out); Serial.println(header1); + if(headers > 1) + { + String out = (header2 == "h2") ? " H2(OK): " : " H2(FAIL): "; + Serial.print(out); Serial.println(header2); + } + } + } + else + { + Serial.print(' '); Serial.println(response); + } + response = ""; +} + +void describe(char * description) +{ + if(describe_tests) + { + Serial.println(description); + } +} + +//reusable test variables +char* post_body = "{\"test\":\"test\"}"; + +void GET_tests() +{ + describe("GET"); + test_status(client.get("/")); + + describe("GET w. response"); + test_status(client.get("/", &response)); + test_response(); + + describe("GET w. header"); + client.setHeader("X-Test-Header1: one"); + test_status(client.get("/")); + + describe("GET w. header and response"); + client.setHeader("X-Test-Header1: one"); + test_status(client.get("/", &response)); + test_response(false, 1); + + describe("GET w. 2 headers and response"); + client.setHeader("X-Test-Header1: one"); + client.setHeader("X-Test-Header2: two"); + test_status(client.get("/", &response)); + test_response(false, 2); +} + +void POST_tests() +{ + // POST TESTS + describe("POST w. body"); + client.setContentType("application/json"); + test_status(client.post("/", post_body)); + + describe("POST w. body and response"); + client.setContentType("application/json"); + test_status(client.post("/", post_body, &response)); + test_response(true); + + describe("POST w. body and header"); + client.setContentType("application/json"); + client.setHeader("X-Test-Header1: one"); + test_status(client.post("/", post_body)); + + describe("POST w. body, header and response"); + client.setContentType("application/json"); + client.setHeader("X-Test-Header1: one"); + test_status(client.post("/", post_body, &response)); + test_response(true, 1); + + describe("POST w. body, 2 headers and response"); + client.setContentType("application/json"); + client.setHeader("X-Test-Header1: one"); + client.setHeader("X-Test-Header2: two"); + test_status(client.post("/", post_body, &response)); + test_response(true, 2); +} + +void PUT_tests() +{ + describe("PUT w. body"); + client.setContentType("application/json"); + test_status(client.put("/", post_body)); + + describe("PUT w. body and response"); + client.setContentType("application/json"); + test_status(client.put("/", post_body, &response)); + test_response(true); + + describe("PUT w. body and header"); + client.setHeader("X-Test-Header1: one"); + test_status(client.put("/", post_body)); + + describe("PUT w. body, header and response"); + client.setHeader("X-Test-Header1: one"); + test_status(client.put("/", post_body, &response)); + test_response(true, 1); + + describe("PUT w. body, 2 headers and response"); + client.setHeader("X-Test-Header1: one"); + client.setHeader("X-Test-Header2: two"); + test_status(client.put("/", post_body, &response)); + test_response(true, 2); +} + +void DELETE_tests() +{ + describe("DELETE"); + test_status(client.del("/")); + + describe("DELETE w. body"); + client.setContentType("application/json"); + test_status(client.del("/", post_body)); + + describe("DELETE w. body and response"); + client.setContentType("application/json"); + test_status(client.del("/", post_body, &response)); + test_response(true); + + describe("DELETE w. body and header"); + client.setContentType("application/json"); + client.setHeader("X-Test-Header1: one"); + test_status(client.del("/", post_body)); + + describe("DELETE w. body, header and response"); + client.setContentType("application/json"); + client.setHeader("X-Test-Header1: one"); + test_status(client.del("/", post_body, &response)); + test_response(true, 1); + + describe("DELETE w. body, 2 headers and response"); + client.setContentType("application/json"); + client.setHeader("X-Test-Header1: one"); + client.setHeader("X-Test-Header2: two"); + test_status(client.del("/", post_body, &response)); + test_response(true, 2); +} + + +// Run the tests! +void loop() +{ + Serial.println("\nplain tests:"); + client.setSSL(0); + client.setPort(80); + + GET_tests(); + POST_tests(); + PUT_tests(); + DELETE_tests(); + + + Serial.println("\nSSL tests:"); + client.setSSL(1); + client.setPort(443); + + GET_tests(); + POST_tests(); + PUT_tests(); + DELETE_tests(); + + Serial.println("\n send any-key to repeat tests"); + while(Serial.available() == 0) + {;} + while(Serial.available()) + { + Serial.read(); + } +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..90e00c6 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=ESP8266 REST client SSL +version=2.1.0 +author=Hal9k,DaKaz,csquared,ricardochimal,theycallmeswift +maintainer=Duckle29 +sentence=A restclient for the esp8266 that supports SSL +paragraph=A restclient for the esp8266 that supports SSL +category=Communication +url=https://github.com/Hal9k-dk/esp8266-restclient +architectures=esp8266