From dac9d4c7c45206669e084e371f12fe79c120826f Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Sat, 16 Jun 2018 04:13:07 +0900 Subject: [PATCH] FirebaseArduino: switch to ArduinoHttpClient This allow the library to be portable across arduino core implementing the Client Interface. Fixes #344 --- README.md | 7 +- library.properties | 2 +- src/FirebaseArduino.cpp | 157 ++++++++++++++++++++++++++++++---------- src/FirebaseArduino.h | 36 +++++---- src/FirebaseObject.cpp | 2 + src/FirebaseObject.h | 6 ++ 6 files changed, 155 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index dc9420f8..316dcf0c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,12 @@ The Arduino library is [under heavy development](https://github.com/googlesample - [FirebaseArduino API Reference](http://firebase-arduino.readthedocs.io/) ## Dependencies -- FirebaseArduino now depends on [ArduinoJson library](https://github.com/bblanchon/ArduinoJson) instead of containing it's own version of it. Please either use Library Manager or download specific version of the library from github. + +FirebaseArduino depends on the following libraries: +- [ArduinoJson library](https://github.com/bblanchon/ArduinoJson) +- [ArduinoHttpClient library](https://github.com/arduino-libraries/ArduinoHttpClient/) + +Please either use Library Manager or download specific version of the library from github. ## Disclaimer diff --git a/library.properties b/library.properties index 37247e39..1574bac7 100644 --- a/library.properties +++ b/library.properties @@ -6,4 +6,4 @@ sentence=Library for communicating with Firebase. paragraph=This library simplifies the process of communicating with Firebase. It hides the complexity of authentication and json parsing. category=Communication url=https://github.com/googlesamples/firebase-arduino -architectures=esp8266 +architectures=esp8266,esp32 diff --git a/src/FirebaseArduino.cpp b/src/FirebaseArduino.cpp index 4ab93f0e..2558a66d 100644 --- a/src/FirebaseArduino.cpp +++ b/src/FirebaseArduino.cpp @@ -16,8 +16,26 @@ #include "FirebaseArduino.h" -// This is needed to compile std::string on esp8266. -template class std::basic_string; +const char* kApplicationType = "application/json"; +const uint16_t kFirebasePort = 443; +const int kStatusOK = 200; +const int kStatusTemporaryRedirect = 307; + +String makeFirebaseURI(const String& path, const String& auth) { + String uri; + if (path[0] != '/') { + uri = "/"; + } + uri += path; + uri += ".json"; + if (auth.length() > 0) { + uri += "?auth="; + uri += auth; + } + return uri; +} + +FirebaseArduino::FirebaseArduino(Client& client) : client_(client) {} void FirebaseArduino::begin(const String& host, const String& auth) { host_ = host.c_str(); @@ -26,17 +44,15 @@ void FirebaseArduino::begin(const String& host, const String& auth) { void FirebaseArduino::initStream() { if (stream_http_.get() == nullptr) { - stream_http_.reset(FirebaseHttpClient::create()); - stream_http_->setReuseConnection(true); - stream_.reset(new FirebaseStream(stream_http_)); + stream_http_.reset(new HttpClient(client_, host_, kFirebasePort)); + stream_http_->connectionKeepAlive(); } } void FirebaseArduino::initRequest() { if (req_http_.get() == nullptr) { - req_http_.reset(FirebaseHttpClient::create()); - req_http_->setReuseConnection(true); - req_.reset(new FirebaseRequest(req_http_)); + req_http_.reset(new HttpClient(client_, host_, kFirebasePort)); + req_http_->connectionKeepAlive(); } } @@ -62,11 +78,20 @@ String FirebaseArduino::push(const String& path, const JsonVariant& value) { char * buf = new char[size]; value.printTo(buf, size); initRequest(); - int status = req_.get()->sendRequest(host_, auth_, "POST", path.c_str(), buf); - error_ = req_.get()->error(); - const char* name = req_.get()->json()["name"].as(); + String uri = makeFirebaseURI(path, auth_); + int err = req_http_->post(uri.c_str(), kApplicationType, buf); + if (err != 0) { + error_ = FirebaseError(err, "HTTP request failed"); + return ""; + } + int statusCode = req_http_->responseStatusCode(); + if (statusCode != kStatusOK) { + error_ = FirebaseError(statusCode, "PUT request failed"); + return ""; + } delete buf; - return name; + StaticJsonBuffer jsonBuffer; + return jsonBuffer.parseObject(req_http_->responseBody())["name"]; } void FirebaseArduino::setInt(const String& path, int value) { @@ -91,15 +116,34 @@ void FirebaseArduino::set(const String& path, const JsonVariant& value) { char* buf= new char[size]; value.printTo(buf, size); initRequest(); - req_.get()->sendRequest(host_, auth_, "PUT", path.c_str(), buf); - error_ = req_.get()->error(); + String uri = makeFirebaseURI(path, auth_); + int err = req_http_->put(uri.c_str(), kApplicationType, buf); + if (err != 0) { + error_ = FirebaseError(err, "HTTP request failed"); + return; + } + int statusCode = req_http_->responseStatusCode(); + if (statusCode != kStatusOK) { + error_ = FirebaseError(statusCode, "POST request failed"); + return; + } + req_http_->responseBody(); // consume body; delete buf; } void FirebaseArduino::getRequest(const String& path) { initRequest(); - req_.get()->sendRequest(host_, auth_, "GET", path.c_str()); - error_ = req_.get()->error(); + String uri = makeFirebaseURI(path, auth_); + int err = req_http_->get(uri.c_str()); + if (err != 0) { + error_ = FirebaseError(err, "HTTP request failed"); + return; + } + int statusCode = req_http_->responseStatusCode(); + if (statusCode != kStatusOK) { + error_ = FirebaseError(err, "GET request failed"); + return; + } } FirebaseObject FirebaseArduino::get(const String& path) { @@ -107,7 +151,7 @@ FirebaseObject FirebaseArduino::get(const String& path) { if (failed()) { return FirebaseObject{""}; } - return FirebaseObject(req_.get()->response().c_str()); + return FirebaseObject(req_http_->responseBody()); } int FirebaseArduino::getInt(const String& path) { @@ -115,7 +159,7 @@ int FirebaseArduino::getInt(const String& path) { if (failed()) { return 0; } - return FirebaseObject(req_.get()->response().c_str()).getInt(); + return FirebaseObject(req_http_->responseBody()).getInt(); } @@ -124,7 +168,7 @@ float FirebaseArduino::getFloat(const String& path) { if (failed()) { return 0.0f; } - return FirebaseObject(req_.get()->response().c_str()).getFloat(); + return FirebaseObject(req_http_->responseBody()).getFloat(); } String FirebaseArduino::getString(const String& path) { @@ -132,7 +176,7 @@ String FirebaseArduino::getString(const String& path) { if (failed()) { return ""; } - return FirebaseObject(req_.get()->response().c_str()).getString(); + return FirebaseObject(req_http_->responseBody()).getString(); } bool FirebaseArduino::getBool(const String& path) { @@ -140,40 +184,72 @@ bool FirebaseArduino::getBool(const String& path) { if (failed()) { return ""; } - return FirebaseObject(req_.get()->response().c_str()).getBool(); + return FirebaseObject(req_http_->responseBody()).getBool(); } + void FirebaseArduino::remove(const String& path) { initRequest(); - req_.get()->sendRequest(host_, auth_, "DELETE", path.c_str()); - error_ = req_.get()->error(); + String uri = makeFirebaseURI(path, auth_); + int err = req_http_->del(uri.c_str()); + if (err != 0) { + error_ = FirebaseError(err, "HTTP request failed"); + return; + } + int statusCode = req_http_->responseStatusCode(); + if (statusCode != kStatusOK) { + error_ = FirebaseError(statusCode, "PUT request failed"); + return; + } + req_http_->responseBody(); // consume body; } void FirebaseArduino::stream(const String& path) { initStream(); - stream_.get()->startStreaming(host_, auth_, path.c_str()); - error_ = stream_.get()->error(); + String uri = makeFirebaseURI(path, auth_); + stream_http_->beginRequest(); + stream_http_->get(uri.c_str()); + stream_http_->sendHeader("Accept", "text/event-stream"); + stream_http_->endRequest(); + + int statusCode = stream_http_->responseStatusCode(); + if (statusCode != kStatusOK) { + error_ = FirebaseError(statusCode, "STREAM request failed"); + return; + } + + if (statusCode == kStatusTemporaryRedirect) { + while(stream_http_->headerAvailable()) { + if (stream_http_->readHeaderName() == "Location") { + String location = stream_http_->readHeaderValue(); + int hostnameStart = location.indexOf(':')+2; + int hostnameEnd = location.indexOf('/', hostnameStart); + String hostname = location.substring(hostnameStart, hostnameEnd); + String path = location.substring(hostnameEnd); + client_.stop(); + stream_http_.reset(new HttpClient(client_, hostname, kFirebasePort)); + stream_http_->connectionKeepAlive(); + stream(path); + return; + } + } + } } bool FirebaseArduino::available() { if (stream_http_.get() == nullptr) { - return 0; + return false; } - auto client = stream_http_.get()->getStreamPtr(); - return (client == nullptr) ? false : client->available(); + return stream_http_->available(); } FirebaseObject FirebaseArduino::readEvent() { if (stream_http_.get() == nullptr) { return FirebaseObject(""); } - auto client = stream_http_.get()->getStreamPtr(); - if (client == nullptr) { - return FirebaseObject(""); - } - String type = client->readStringUntil('\n').substring(7);; - String event = client->readStringUntil('\n').substring(6); - client->readStringUntil('\n'); // consume separator - FirebaseObject obj = FirebaseObject(event.c_str()); + String type = stream_http_->readStringUntil('\n').substring(7);; + String event = stream_http_->readStringUntil('\n').substring(6); + stream_http_->readStringUntil('\n'); // consume separator + FirebaseObject obj = FirebaseObject(event); obj.getJsonVariant().asObject()["type"] = type.c_str(); return obj; } @@ -190,4 +266,11 @@ const String& FirebaseArduino::error() { return error_.message().c_str(); } -FirebaseArduino Firebase; + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + +#include +WiFiClientSecure client; +FirebaseArduino Firebase(client); + +#endif diff --git a/src/FirebaseArduino.h b/src/FirebaseArduino.h index e2f264e1..1db03d90 100644 --- a/src/FirebaseArduino.h +++ b/src/FirebaseArduino.h @@ -19,7 +19,9 @@ #include -#include "Firebase.h" +#include + +#include "FirebaseError.h" #include "FirebaseObject.h" /** @@ -31,13 +33,15 @@ */ class FirebaseArduino { public: + FirebaseArduino(Client& client); + /** * Must be called first. This initialize the client with the given * firebase host and credentials. * \param host Your firebase db host, usually X.firebaseio.com. * \param auth Optional credentials for the db, a secret or token. */ - virtual void begin(const String& host, const String& auth = ""); + void begin(const String& host, const String& auth = ""); /** * Appends the integer value to the node at path. @@ -77,7 +81,7 @@ class FirebaseArduino { * \param value String value that you wish to append to the node. * \return The unique key of the new child node. */ - virtual String pushString(const String& path, const String& value); + String pushString(const String& path, const String& value); /** * Appends the JSON data to the node at path. @@ -123,7 +127,7 @@ class FirebaseArduino { * \param path The path inside of your db to the node you wish to update. * \param value String value that you wish to write. */ - virtual void setString(const String& path, const String& value); + void setString(const String& path, const String& value); /** * Writes the JSON data to the node located at path. @@ -157,7 +161,7 @@ class FirebaseArduino { * \param path The path to the node you wish to retrieve. * \return The string value located at that path. Will only be populated if success() is true. */ - virtual String getString(const String& path); + String getString(const String& path); /** * Gets the boolean value located at path. @@ -181,7 +185,7 @@ class FirebaseArduino { * \param path The path to the node you wish to remove, * including all of its children. */ - virtual void remove(const String& path); + void remove(const String& path); /** * Starts streaming any changes made to the node located at path, including @@ -191,14 +195,14 @@ class FirebaseArduino { * monitoring available() and calling readEvent() to get new events. * \param path The path inside of your db to the node you wish to monitor. */ - virtual void stream(const String& path); + void stream(const String& path); /** * Checks if there are new events available. This is only meaningful once * stream() has been called. * \return If a new event is ready. */ - virtual bool available(); + bool available(); /** * Reads the next event in a stream. This is only meaningful once stream() has @@ -206,7 +210,7 @@ class FirebaseArduino { * \return FirebaseObject will have ["type"] that describes the event type, ["path"] * that describes the effected path and ["data"] that was updated. */ - virtual FirebaseObject readEvent(); + FirebaseObject readEvent(); /** * \return Whether the last command was successful. @@ -221,15 +225,15 @@ class FirebaseArduino { /** * \return Error message from last command if failed() is true. */ - virtual const String& error(); + const String& error(); private: - std::string host_; - std::string auth_; + Client& client_; + + String host_; + String auth_; FirebaseError error_; - std::shared_ptr req_http_; - std::shared_ptr req_; - std::shared_ptr stream_http_; - std::shared_ptr stream_; + std::shared_ptr req_http_; + std::shared_ptr stream_http_; void initStream(); void initRequest(); diff --git a/src/FirebaseObject.cpp b/src/FirebaseObject.cpp index 44fa5c37..35891dfe 100644 --- a/src/FirebaseObject.cpp +++ b/src/FirebaseObject.cpp @@ -26,6 +26,8 @@ FirebaseObject::FirebaseObject(const char* data) : data_{data} { // See: https://github.com/bblanchon/ArduinoJson/issues/279 } +FirebaseObject::FirebaseObject(const String& data) : FirebaseObject(data.c_str()) {} + bool FirebaseObject::getBool(const String& path) const { JsonVariant variant = getJsonVariant(path); if (!variant.is()) { diff --git a/src/FirebaseObject.h b/src/FirebaseObject.h index 01caeb5c..611695f2 100644 --- a/src/FirebaseObject.h +++ b/src/FirebaseObject.h @@ -42,6 +42,12 @@ class FirebaseObject { */ FirebaseObject(const char* data); + /** + * Construct from json. + * \param data JSON formatted string. + */ + FirebaseObject(const String& data); + /** * Return the value as a boolean. * \param optional path in the JSON object.