Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
dist/*
source/objects*/
nbproject/*
.genio.yaml
.clang-format
298 changes: 238 additions & 60 deletions source/HttpUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

#include <DataIO.h>
#include <NetworkAddressResolver.h>
#include <private/shared/Json.h>
#include <Roster.h>
#include <AppFileInfo.h>
#include <Application.h>

#include "Debug.h"
#include "HttpUtils.h"
Expand Down Expand Up @@ -58,53 +62,8 @@ class DataLimit : public BUrlProtocolListener, public BDataIO {
};


/**
* Loops on the DNS responses looking for connectivity on specified port.
*/
status_t
HttpUtils::CheckPort(BUrl url, BUrl* newUrl, uint32 flags)
{
uint16 port;
if (url.HasPort())
port = url.Port();
else
port = 80;

BReference<const BNetworkAddressResolver> resolver
= BNetworkAddressResolver::Resolve(url.Host(), port, flags);
if (resolver.Get() == NULL)
return B_NO_MEMORY;
status_t status = resolver->InitCheck();
if (status != B_OK)
return status;

BString newUrlString;
BNetworkAddress ipAddress;
BSocket* socket;
uint32 cookie = 0;

status_t portStatus = B_ERROR;
while (portStatus != B_OK) {
status = resolver->GetNextAddress(AF_INET6, &cookie, ipAddress);
if (status != B_OK)
status = resolver->GetNextAddress(&cookie, ipAddress);
if (status != B_OK)
return status;
socket = new (std::nothrow) BSocket();
portStatus = socket->Connect(ipAddress);
delete socket;
}

// If port number is 80, do not add it to the final URL
// Then, prepend the appropiate protocol
newUrlString
= ipAddress.ToString(ipAddress.Port() != 80).Prepend("://").Prepend(url.Protocol());
if (url.HasPath())
newUrlString.Append(url.Path());
newUrl->SetUrlString(newUrlString.String(), true);

return B_OK;
}
BString
HttpUtils::jsonType("application/json");


/**
Expand All @@ -115,7 +74,7 @@ HttpUtils::CheckPort(BUrl url, BUrl* newUrl, uint32 flags)
* @return BMallocIO* filled with retrieved content
*/
BMallocIO*
HttpUtils::GetAll(BUrl url, BHttpHeaders* responseHeaders, bigtime_t timeOut, BString* contentType,
HttpUtils::GetAll(BUrl* url, BHttpHeaders* responseHeaders, bigtime_t timeOut, BString* contentType,
size_t sizeLimit)
{
BMallocIO* data = new BMallocIO();
Expand All @@ -126,41 +85,260 @@ HttpUtils::GetAll(BUrl url, BHttpHeaders* responseHeaders, bigtime_t timeOut, BS
BHttpRequest* request;
if (sizeLimit)
request = dynamic_cast<BHttpRequest*>(
BUrlProtocolRoster::MakeRequest(url.UrlString().String(), &reader, &reader, NULL));
BUrlProtocolRoster::MakeRequest(url->UrlString().String(), &reader, &reader, NULL));
else
request = dynamic_cast<BHttpRequest*>(
BUrlProtocolRoster::MakeRequest(url.UrlString().String(), data, NULL, NULL));
BUrlProtocolRoster::MakeRequest(url->UrlString().String(), data, NULL, NULL));

if (request == NULL)
if (request == NULL) {
delete data;
return NULL;
}

if (contentType && !contentType->IsEmpty()) {
BHttpHeaders* requestHeaders = new BHttpHeaders();
requestHeaders->AddHeader("accept", contentType->String());
request->AdoptHeaders(requestHeaders);
BHttpHeaders requestHeaders;
requestHeaders.AddHeader("accept", contentType->String());
request->SetHeaders(requestHeaders);
}

request->SetAutoReferrer(true);
request->SetFollowLocation(true);
request->SetTimeout(timeOut);
request->SetUserAgent(Utils::UserAgent());
request->SetUserAgent(UserAgent());

thread_id threadId = request->Run();
status_t status;
wait_for_thread(threadId, &status);

BHttpResult& result = (BHttpResult&)request->Result();
int32 statusCode = result.StatusCode();
size_t bufferLen = data->BufferLength();
const BHttpResult& result = dynamic_cast<const BHttpResult&>(request->Result());
int32 statusCode = result.StatusCode();
size_t bufferLen = data->BufferLength();
if (!(statusCode == 0 || request->IsSuccessStatusCode(statusCode)) || bufferLen == 0) {
delete data;
data = NULL;
} else if (contentType != NULL)
contentType->SetTo(result.ContentType());
} else {
data->Seek(0, SEEK_SET);
if (contentType != NULL)
contentType->SetTo(result.ContentType());
}

if (responseHeaders != NULL)
*responseHeaders = result.Headers();

delete request;
return data;
}


/**
* Helper class for scanning simple JSONs
*/
class JsonPathExtractor : public BJsonEventListener {
public:

/**
* Listens to JSON parsing events and extracts specific path
* @param path Dot-separated location of element in JSON hierarchy
* [].{}.address.{}.street will get street within an
* address object within the unnamed objects of the array
* @param result Initialized BStringList to capture all occurrences
*/
JsonPathExtractor(BString* path, BStringList* result) :
BJsonEventListener(),
pathElements(true)
{
path->Split(".", true, pathElements);
this->result = result;
}

~JsonPathExtractor() {
pathElements.MakeEmpty();
}

virtual bool Handle(const BJsonEvent& event) {
switch (event.EventType()) {
case B_JSON_NUMBER :
case B_JSON_TRUE :
case B_JSON_FALSE :
case B_JSON_NULL :
if (awaitValue)
awaitValue = false;
if (offIndex > 0)
offIndex--;
else
index--;
break;

case B_JSON_STRING :
if (awaitValue) {
result->Add(event.Content());
awaitValue = false;
}
if (offIndex > 0)
offIndex--;
else
index--;
break;

case B_JSON_OBJECT_START :
if (index < pathElements.CountStrings() &&
pathElements.StringAt(index) == "{}" &&
offIndex == 0)
index++;
else
offIndex++;
break;

case B_JSON_OBJECT_END :
if (offIndex > 0)
offIndex--;
else
index--;
break;

case B_JSON_OBJECT_NAME :
if (index < pathElements.CountStrings() &&
pathElements.StringAt(index) == event.Content() &&
offIndex == 0)
{
index++;
if (index == pathElements.CountStrings())
awaitValue = true;
}
else
offIndex++;
break;

case B_JSON_ARRAY_START :
if (index < pathElements.CountStrings() &&
pathElements.StringAt(index) == "[]" &&
offIndex == 0)
index++;
else
offIndex++;
break;

case B_JSON_ARRAY_END :
if (offIndex > 0)
offIndex--;
else {
index--;
}
break;
}
return true;
}

virtual void HandleError(status_t status, int32 line, const char* message) {
printf("JsonParser line %d: %s", line, message);
}
virtual void Complete() { }

private:
int index = 0;
int offIndex = 0;
bool awaitValue = false;
BString name;
BStringList* result = NULL;

// path like "[].{}.name" extracts all elements called "name" in root array of unnamed objects
BStringList pathElements;
};

/**
* Helper to make retrieve list of occurrences of specific element in JSON
* @param url REST-Url to request
* @param path Dot-separated location of element in JSON hierarchy
* [].{}.address.{}.street will get street within an
* address object within the unnamed objects of the array
* @param contentType In/Out: Received headers
* @param timeout Timeout of request in ms, default set to 3 seconds
* @return BStringList* filled with all occurrences of element described by path
*/
BMessage*
HttpUtils::GetMsgFromREST(BUrl* url, BHttpHeaders* responseHeaders, bigtime_t timeOut) {
BMallocIO* json = GetAll(url, responseHeaders, timeOut, &jsonType, 0);
if (json == NULL)
return NULL;

BMessage* data = new BMessage();

status_t status = BJson::Parse((const char*)json->Buffer(), json->BufferLength(), *data);
delete json;

if (status != B_OK) {
delete data;
data = NULL;
}

return data;
}

/**
* Helper to make retrieve list of occurrences of specific element in JSON
* @param url REST-Url to request
* @param path Dot-separated location of element in JSON hierarchy
* [].{}.address.{}.street will get street within an
* address object within the unnamed objects of the root array
* @param responseHeaders Unless NULL, populated with HTTP headers of response
* @param contentType In/Out: Received headers
* @param timeout Timeout of request in ms, default set to 3 seconds
* @return BStringList* filled with all occurrences of element described by path
*/
BStringList*
HttpUtils::GetStringsFromREST(BUrl* url, BString* path, BHttpHeaders* responseHeaders,
bigtime_t timeout) {
BMallocIO* json = GetAll(url, responseHeaders, timeout, &jsonType, 0);
if (json == NULL)
return NULL;

if (json->BufferLength() == 0) {
delete json;
return NULL;
}

BStringList* result = new BStringList();
JsonPathExtractor extractor(path, result);
BJson::Parse(json, &extractor);
delete json;

if (result->IsEmpty()) {
delete result;
result = NULL;
}

return result;
}

char HttpUtils::sUserAgent[50] = "\0";

const char*
HttpUtils::UserAgent()
{
if (sUserAgent[0] != 0)
return sUserAgent;

app_info appInfo;
be_app->GetAppInfo(&appInfo);
BFile file(&appInfo.ref, B_READ_ONLY);
if (file.InitCheck() == B_OK) {
BAppFileInfo appFileInfo(&file);
struct version_info version;
if (appFileInfo.GetVersionInfo(&version, B_APP_VERSION_KIND) == B_OK)
snprintf(sUserAgent, sizeof(sUserAgent),
"StreamRadio/%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32, version.major, version.middle,
version.minor);
}

if (sUserAgent[0] == 0) {
// There was an error trying to get the version info
sprintf(sUserAgent, "StreamRadio");
}

return sUserAgent;
}





18 changes: 14 additions & 4 deletions source/HttpUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,26 @@
#include <Socket.h>
#include <StringList.h>
#include <Url.h>
#include <ObjectList.h>

using namespace BPrivate::Network;

#define HTTP_DEFAULT_TIMEOUT 3000

class HttpUtils {
public:
static status_t CheckPort(BUrl url, BUrl* newUrl, uint32 flags = 0);

static BMallocIO* GetAll(BUrl url, BHttpHeaders* returnHeaders = NULL, bigtime_t timeOut = 3000,
BString* contentType = NULL, size_t sizeLimit = 0);
static BString jsonType;
static BMallocIO* GetAll(BUrl* url, BHttpHeaders* responseHeaders = NULL,
bigtime_t timeOut = HTTP_DEFAULT_TIMEOUT,
BString* contentType = NULL, size_t sizeLimit = 0);
static BMessage* GetMsgFromREST(BUrl* url, BHttpHeaders* responseHeaders = NULL,
bigtime_t timeOut = HTTP_DEFAULT_TIMEOUT);
static BStringList* GetStringsFromREST(BUrl* url, BString* path,
BHttpHeaders* responseHeaders = NULL,
bigtime_t timeout = HTTP_DEFAULT_TIMEOUT);
static const char* UserAgent();
private:
static char sUserAgent[50];
};


Expand Down
Loading
Loading