Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Refactored Firebase library #42

Merged
merged 19 commits into from
Jan 29, 2016
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 200 additions & 75 deletions Firebase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,116 +15,241 @@
//
#include "Firebase.h"

const char* firebaseFingerprint = "7A 54 06 9B DC 7A 25 B3 86 8D 66 53 48 2C 0B 96 42 C7 B3 0A";
const uint16_t firebasePort = 443;
namespace {
const char* kFirebaseFingerprint = "7A 54 06 9B DC 7A 25 B3 86 8D 66 53 48 2C 0B 96 42 C7 B3 0A";
const uint16_t kFirebasePort = 443;

Firebase::Firebase(const String& host) : _host(host) {
_http.setReuse(true);
String makeUrl(const String& path, const String& auth) {
String url;
if (path[0] != '/') {
url = "/";
}
url += path + ".json";
if (auth.length() > 0) {
url += "?auth=" + auth;
}
return url;
}

Firebase& Firebase::auth(const String& auth) {
_auth = auth;
return *this;
class FirebaseCall {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should rename that into HTTPRequest since there is not much specific to firebase in this helper (basically just the port and the fingerprint).

public:
FirebaseCall(const String& host, const String& auth,
const char* method, const String& path, const String& value,
HTTPClient* http);
FirebaseCall(const String& host, const String& auth,
const char* method, const String& path,
HTTPClient* http);


// True if there was an error completing call.
bool isError() const;
String errorMessage() const;

// True if http status code is 200(OK).
bool isOk() const;

// Message sent back from Firebase backend. This pulls value to local memory,
// be careful if value can be large.
String rawResponse();

int httpStatus() const {
return status_;
}

private:
FirebaseCall(HTTPClient* http);

HTTPClient* http_;

int status_;
String error_message_;
};

} // namespace

Firebase::Firebase(const String& host) : host_(host) {
http_.setReuse(true);
}

String Firebase::get(const String& path) {
return sendRequestGetBody("GET", path);
Firebase& Firebase::auth(const String& auth) {
auth_ = auth;
return *this;
}

String Firebase::push(const String& path, const String& value) {
return sendRequestGetBody("POST", path, value);
FirebaseGetResult Firebase::get(const String& path) {
FirebaseCall call(host_, auth_, "GET", path, &http_);
return call.isError() ? FirebaseGetResult::FromError(call.errorMessage())
: FirebaseGetResult::FromResponse(call.rawResponse());
}

bool Firebase::remove(const String& path) {
int status = sendRequest("DELETE", path);
return status == HTTP_CODE_OK;
FirebasePushResult Firebase::push(const String& path, const String& value) {
FirebaseCall call(host_, auth_, "POST", path, value, &http_);
return call.isError() ? FirebasePushResult::FromError(call.errorMessage())
: FirebasePushResult::FromResponse(call.rawResponse());
}

Firebase& Firebase::stream(const String& path) {
_error.reset();
String url = makeURL(path);
const char* headers[] = {"Location"};
_http.setReuse(true);
_http.begin(_host.c_str(), firebasePort, url.c_str(), true, firebaseFingerprint);
_http.collectHeaders(headers, 1);
_http.addHeader("Accept", "text/event-stream");
int statusCode = _http.sendRequest("GET", (uint8_t*)NULL, 0);
String location;
// TODO(proppy): Add a max redirect check
while (statusCode == 307) {
location = _http.header("Location");
_http.setReuse(false);
_http.end();
_http.setReuse(true);
_http.begin(location, firebaseFingerprint);
statusCode = _http.sendRequest("GET", (uint8_t*)NULL, 0);
FirebaseRemoveResult Firebase::remove(const String& path) {
FirebaseCall call(host_, auth_, "DELETE", path, &http_);
if (call.isError()) {
return FirebaseRemoveResult::FromError(call.errorMessage());
}
if (statusCode != 200) {
_error.set(statusCode,
"stream " + location + ": "
+ HTTPClient::errorToString(statusCode));
// Remove is only complete if returned status is OK(200).
if (!call.isOk()) {
return FirebaseRemoveResult::FromError(
"Remove " + path + " returned with status " + call.httpStatus());
}
return *this;
return FirebaseRemoveResult::Ok();
}

String Firebase::makeURL(const String& path) {
String url;
if (path[0] != '/') {
url = "/";
}
url += path + ".json";
if (_auth.length() > 0) {
url += "?auth=" + _auth;
FirebaseEventStream Firebase::stream(const String& path) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FirebaseStreamResult?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well since it is an interactive object it seemed like less of a "result" but this may be to your point of not naming any of them "result" because they may be interactive I. the future with laziness.

return FirebaseEventStream(host_, auth_, path);
}

/* FirebaseCall */
FirebaseCall::FirebaseCall(const String& host, const String& auth,
const char* method, const String& path, const String& value,
HTTPClient* http) : http_(http) {
const String url = makeUrl(path, auth);
http_->begin(host.c_str(), kFirebasePort, url.c_str(), true, kFirebaseFingerprint);
status_ = http_->sendRequest(method, (uint8_t*)value.c_str(), value.length());
if (isError()) {
error_message_ = String(method) + " " + url + ": " + HTTPClient::errorToString(status_);
}
return url;
}

int Firebase::sendRequest(const char* method, const String& path, const String& value) {
String url = makeURL(path);
_http.begin(_host.c_str(), firebasePort, url.c_str(), true, firebaseFingerprint);
int statusCode = _http.sendRequest(method, (uint8_t*)value.c_str(), value.length());
setError(method, url, statusCode);
return statusCode;
FirebaseCall::FirebaseCall(const String& host, const String& auth,
const char* method, const String& path,
HTTPClient* http) : FirebaseCall(host, auth, method, path, "", http) {
}

String Firebase::sendRequestGetBody(const char* method, const String& path, const String& value) {
sendRequest(method, path, value);
if (_error.code() != 0) {
return "";
}
// no _http.end() because of connection reuse.
return _http.getString();
bool FirebaseCall::isOk() const {
return status_ == HTTP_CODE_OK;
}

bool FirebaseCall::isError() const {
return status_ < 0;
}

String FirebaseCall::errorMessage() const {
return error_message_;
}

void Firebase::setError(const char* method, const String& url, int statusCode) {
_error.reset();
if (statusCode < 0) {
_error.set(statusCode,
String(method) + " " + url + ": "
+ HTTPClient::errorToString(statusCode));
String FirebaseCall::rawResponse() {
return http_->getString();
}

/* FirebaseEventStream */

FirebaseEventStream::FirebaseEventStream(const String& host, const String& auth,
const String& path) {
const String url = makeUrl(path, auth);
http_.setReuse(true);
http_.begin(host.c_str(), kFirebasePort, url.c_str(), true,
kFirebaseFingerprint);
const char* headers[] = {"Location"};
http_.collectHeaders(headers, 1);
http_.addHeader("Accept", "text/event-stream");
status_ = http_.sendRequest("GET", (uint8_t*)NULL, 0);

String location;
// TODO(proppy): Add a max redirect check
while (status_ == HTTP_CODE_TEMPORARY_REDIRECT) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should do this in the stream() call so we can get some early failure, and keep the dedicated client only for the request after the last redirect.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems essentially the same. You will get the failure at the same time, when you check this object.

location = http_.header("Location");
http_.setReuse(false);
http_.end();
http_.setReuse(true);
http_.begin(location, kFirebaseFingerprint);
status_ = http_.sendRequest("GET", (uint8_t*)NULL, 0);
}

if (status_ != 200) {
error_message_ = "stream " + location + ": "
+ HTTPClient::errorToString(status_);
}
}

bool Firebase::connected() {
return _http.connected();
bool FirebaseEventStream::connected() {
return http_.connected();
}

bool Firebase::available() {
return _http.getStreamPtr()->available();
bool FirebaseEventStream::available() {
return http_.getStreamPtr()->available();
}

Firebase::Event Firebase::read(String& event) {
auto client = _http.getStreamPtr();
Event type;;
FirebaseEventStream::Event FirebaseEventStream::read(String& event) {
auto client = http_.getStreamPtr();
Event type;
String typeStr = client->readStringUntil('\n').substring(7);
if (typeStr == "put") {
type = Firebase::Event::PUT;
type = FirebaseEventStream::Event::PUT;
} else if (typeStr == "patch") {
type = Firebase::Event::PATCH;
type = FirebaseEventStream::Event::PATCH;
} else {
type = Firebase::Event::UNKNOWN;
type = FirebaseEventStream::Event::UNKNOWN;
}
event = client->readStringUntil('\n').substring(6);
client->readStringUntil('\n'); // consume separator
return type;
}

bool FirebaseEventStream::isError() const {
return status_ < 0;
}

String FirebaseEventStream::errorMessage() const {
return error_message_;
}

FirebaseEventStream::operator bool() {
return !isError() && connected();
}

/* FirebaseResult */

FirebaseResult::FirebaseResult(const String& error_message)
: is_error_(true), error_message_(error_message) {}

FirebaseResult::FirebaseResult() {}

FirebaseResult::operator bool() const {
return !isError();
}

bool FirebaseResult::isError() const {
return is_error_;
}

const String& FirebaseResult::errorMessage() const {
return error_message_;
}

/* FirebaseRemoveResult */

FirebaseRemoveResult::FirebaseRemoveResult(const String& error_message)
: FirebaseResult(error_message) {}

FirebaseRemoveResult::FirebaseRemoveResult() {}


/* FirebasePushResult */

FirebasePushResult::FirebasePushResult(const String& error_message)
: FirebaseResult(error_message) {}

FirebasePushResult::FirebasePushResult() {}

const String& FirebasePushResult::name() const {
return name_;
}

/* FirebaseGetResult */

FirebaseGetResult::FirebaseGetResult(const String& error_message)
: FirebaseResult(error_message) {}

FirebaseGetResult::FirebaseGetResult() {}

const String& FirebaseGetResult::rawResponse() {
return response_;
}

Loading