-
Notifications
You must be signed in to change notification settings - Fork 492
Refactored Firebase library #42
Changes from 12 commits
0a613cd
d805dc6
217d58f
b2438cf
d5dd303
23fbbed
2bf3a4f
2df5bd6
077e060
6f65250
33f81ef
2ac7ab3
357a1d0
c65fef7
b286b19
34357a9
f38602f
e8ac1e9
caa70e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FirebaseStreamResult? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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_; | ||
} | ||
|
There was a problem hiding this comment.
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).