A simple HTTPFetcher implementation using happyhttp.
Change-Id: I4f64d46ca4873711300ae175c5f5646ca1a7c366
This commit is contained in:
parent
79bd9eb51d
commit
dccb069ffc
|
@ -0,0 +1,184 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/base/httpfetcher.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#endif // WIN32
|
||||
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "third_party/happyhttp/src/happyhttp.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct HTTPResult {
|
||||
int status_code;
|
||||
std::string status_message;
|
||||
std::string response;
|
||||
};
|
||||
|
||||
bool ExtractUrlParams(const std::string& url, std::string* host,
|
||||
std::string* path, int* port) {
|
||||
DCHECK(host && path && port);
|
||||
|
||||
static const char kHttp[] = "http://";
|
||||
// arraysize counts the last null character, which needs to be removed.
|
||||
const char kHttpSize = arraysize(kHttp) - 1;
|
||||
static const char kHttps[] = "https://";
|
||||
const char kHttpsSize = arraysize(kHttps) - 1;
|
||||
size_t host_start_pos;
|
||||
if (StartsWithASCII(url, kHttp, false)) {
|
||||
host_start_pos = kHttpSize;
|
||||
} else if (StartsWithASCII(url, kHttps, false)) {
|
||||
host_start_pos = kHttpsSize;
|
||||
NOTIMPLEMENTED() << "Secure HTTP is not implemented yet.";
|
||||
return false;
|
||||
} else {
|
||||
host_start_pos = 0;
|
||||
}
|
||||
|
||||
const size_t npos = std::string::npos;
|
||||
const size_t port_start_pos = url.find(':', host_start_pos);
|
||||
const size_t path_start_pos = url.find('/', host_start_pos);
|
||||
|
||||
size_t host_size;
|
||||
if (port_start_pos == npos) {
|
||||
const int kStandardHttpPort = 80;
|
||||
*port = kStandardHttpPort;
|
||||
|
||||
host_size = path_start_pos == npos ? npos : path_start_pos - host_start_pos;
|
||||
} else {
|
||||
if (port_start_pos >= path_start_pos)
|
||||
return false;
|
||||
const size_t port_size =
|
||||
path_start_pos == npos ? npos : path_start_pos - port_start_pos - 1;
|
||||
if (!base::StringToInt(url.substr(port_start_pos + 1, port_size), port))
|
||||
return false;
|
||||
|
||||
host_size = port_start_pos - host_start_pos;
|
||||
}
|
||||
|
||||
*host = url.substr(host_start_pos, host_size);
|
||||
*path = path_start_pos == npos ? "/" : url.substr(path_start_pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
// happyhttp event callbacks.
|
||||
void OnBegin(const happyhttp::Response* response, void* userdata) {
|
||||
DCHECK(response && userdata);
|
||||
DLOG(INFO) << "BEGIN (" << response->getstatus() << ", "
|
||||
<< response->getreason() << ").";
|
||||
|
||||
HTTPResult* result = static_cast<HTTPResult*>(userdata);
|
||||
result->status_code = response->getstatus();
|
||||
result->status_message = response->getreason();
|
||||
result->response.clear();
|
||||
}
|
||||
|
||||
void OnData(const happyhttp::Response* response,
|
||||
void* userdata,
|
||||
const unsigned char* data,
|
||||
int num_bytes) {
|
||||
DCHECK(response && userdata && data);
|
||||
HTTPResult* result = static_cast<HTTPResult*>(userdata);
|
||||
result->response.append(reinterpret_cast<const char*>(data), num_bytes);
|
||||
}
|
||||
|
||||
void OnComplete(const happyhttp::Response* response, void* userdata) {
|
||||
DCHECK(response && userdata);
|
||||
HTTPResult* result = static_cast<HTTPResult*>(userdata);
|
||||
DLOG(INFO) << "COMPLETE (" << result->response.size() << " bytes).";
|
||||
}
|
||||
|
||||
const int kHttpOK = 200;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace media {
|
||||
|
||||
HTTPFetcher::HTTPFetcher() {
|
||||
#ifdef WIN32
|
||||
WSAData wsa_data;
|
||||
int code = WSAStartup(MAKEWORD(1, 1), &wsa_data);
|
||||
wsa_startup_succeeded_ = (code == 0);
|
||||
if (!wsa_startup_succeeded_)
|
||||
LOG(ERROR) << "WSAStartup failed with code " << code;
|
||||
#endif // WIN32
|
||||
}
|
||||
|
||||
HTTPFetcher::~HTTPFetcher() {
|
||||
#ifdef WIN32
|
||||
if (wsa_startup_succeeded_)
|
||||
WSACleanup();
|
||||
#endif // WIN32
|
||||
}
|
||||
|
||||
Status HTTPFetcher::Get(const std::string& path, std::string* response) {
|
||||
return FetchInternal("GET", path, "", response);
|
||||
}
|
||||
|
||||
Status HTTPFetcher::Post(const std::string& path, const std::string& data,
|
||||
std::string* response) {
|
||||
return FetchInternal("POST", path, data, response);
|
||||
}
|
||||
|
||||
Status HTTPFetcher::FetchInternal(const std::string& method,
|
||||
const std::string& url,
|
||||
const std::string& data,
|
||||
std::string* response) {
|
||||
DCHECK(response);
|
||||
|
||||
int status_code = 0;
|
||||
|
||||
std::string host;
|
||||
std::string path;
|
||||
int port = 0;
|
||||
if (!ExtractUrlParams(url, &host, &path, &port)) {
|
||||
std::string error_message = "Cannot extract url parameters from " + url;
|
||||
LOG(ERROR) << error_message;
|
||||
return Status(error::INVALID_ARGUMENT, error_message);
|
||||
}
|
||||
|
||||
try {
|
||||
HTTPResult result;
|
||||
happyhttp::Connection connection(host.data(), port);
|
||||
connection.setcallbacks(OnBegin, OnData, OnComplete, &result);
|
||||
|
||||
VLOG(1) << "Send " << method << " request to " << url << ": " << data;
|
||||
|
||||
static const char* kHeaders[] = {
|
||||
"Connection", "close",
|
||||
"Content-type", "application/x-www-form-urlencoded",
|
||||
"Accept", "text/plain",
|
||||
0};
|
||||
connection.request(
|
||||
method.data(), path.data(), kHeaders,
|
||||
data.empty() ? NULL : reinterpret_cast<const uint8*>(data.data()),
|
||||
data.size());
|
||||
|
||||
while (connection.outstanding())
|
||||
connection.pump();
|
||||
|
||||
status_code = result.status_code;
|
||||
*response = result.response;
|
||||
|
||||
VLOG(1) << "Response: " << result.response;
|
||||
} catch (happyhttp::Wobbly& exception) {
|
||||
std::string error_message =
|
||||
std::string("HTTP fetcher failed: ") + exception.what();
|
||||
LOG(ERROR) << error_message;
|
||||
return Status(error::HTTP_FAILURE, error_message);
|
||||
}
|
||||
|
||||
if (status_code != kHttpOK) {
|
||||
std::string error_message = "HTTP returns status " + base::IntToString(status_code);
|
||||
LOG(ERROR) << error_message;
|
||||
return Status(error::HTTP_FAILURE, error_message);
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace media
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef MEDIA_BASE_HTTPFETCHER_H_
|
||||
#define MEDIA_BASE_HTTPFETCHER_H_
|
||||
|
||||
#include "media/base/status.h"
|
||||
|
||||
namespace media {
|
||||
|
||||
// A simple HTTP fetcher implementation using happyhttp.
|
||||
class HTTPFetcher {
|
||||
public:
|
||||
// TODO(kqyang): Add timeout support.
|
||||
HTTPFetcher();
|
||||
~HTTPFetcher();
|
||||
|
||||
// Fetch |response| from |url| using HTTP GET.
|
||||
// |response| should not be NULL, will contain the body of the http response
|
||||
// on success.
|
||||
// Return OK on success.
|
||||
Status Get(const std::string& url, std::string* response);
|
||||
|
||||
// Fetch |response| from |url| using HTTP POST.
|
||||
// |response| should not be NULL, will contain the body of the http response
|
||||
// on success.
|
||||
// Return OK on success.
|
||||
Status Post(const std::string& url, const std::string& data,
|
||||
std::string* response);
|
||||
|
||||
private:
|
||||
// Internal implementation of HTTP functions, e.g. Get and Post.
|
||||
Status FetchInternal(const std::string& method, const std::string& url,
|
||||
const std::string& data, std::string* response);
|
||||
|
||||
#ifdef WIN32
|
||||
// Track whether WSAStartup executes successfully.
|
||||
bool wsa_startup_succeeded_;
|
||||
#endif
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(HTTPFetcher);
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
||||
#endif // MEDIA_BASE_HTTPFETCHER_H_
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2013 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "media/base/httpfetcher.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "media/base/status_test_util.h"
|
||||
|
||||
namespace {
|
||||
const int kHttpOK = 200;
|
||||
const int kHttpNotFound = 404;
|
||||
|
||||
const char kTestUrl[] = "http://packager-test.appspot.com/http_test";
|
||||
const char kTestUrlWithPort[] = "http://packager-test.appspot.com:80/http_test";
|
||||
const char kExpectedGetResponse[] =
|
||||
"<html><head><title>http_test</title></head><body><pre>"
|
||||
"Arguments()</pre></body></html>";
|
||||
const char kPostData[] = "foo=62&type=mp4";
|
||||
const char kExpectedPostResponse[] =
|
||||
"<html><head><title>http_test</title></head><body><pre>"
|
||||
"Arguments([foo]=>62[type]=>mp4)</pre></body></html>";
|
||||
} // namespace
|
||||
|
||||
namespace media {
|
||||
|
||||
static void CheckHttpGet(const std::string& url,
|
||||
const std::string& expected_response) {
|
||||
HTTPFetcher fetcher;
|
||||
std::string response;
|
||||
ASSERT_OK(fetcher.Get(url, &response));
|
||||
RemoveChars(response, "\r\n\t ", &response);
|
||||
EXPECT_EQ(expected_response, response);
|
||||
}
|
||||
|
||||
static void CheckHttpPost(const std::string& url, const std::string& data,
|
||||
const std::string& expected_response) {
|
||||
HTTPFetcher fetcher;
|
||||
std::string response;
|
||||
ASSERT_OK(fetcher.Post(url, data, &response));
|
||||
RemoveChars(response, "\r\n\t ", &response);
|
||||
EXPECT_EQ(expected_response, response);
|
||||
}
|
||||
|
||||
|
||||
TEST(HTTPFetcherTest, HttpGet) {
|
||||
CheckHttpGet(kTestUrl, kExpectedGetResponse);
|
||||
}
|
||||
|
||||
TEST(HTTPFetcherTest, HttpPost) {
|
||||
CheckHttpPost(kTestUrl, kPostData, kExpectedPostResponse);
|
||||
}
|
||||
|
||||
TEST(HTTPFetcherTest, InvalidUrl) {
|
||||
HTTPFetcher fetcher;
|
||||
std::string response;
|
||||
const std::string invalid_url(kTestUrl, sizeof(kTestUrl) - 2);
|
||||
Status status = fetcher.Get(invalid_url, &response);
|
||||
EXPECT_EQ(error::HTTP_FAILURE, status.error_code());
|
||||
EXPECT_TRUE(
|
||||
EndsWith(status.error_message(), base::IntToString(kHttpNotFound), true));
|
||||
}
|
||||
|
||||
TEST(HTTPFetcherTest, UrlWithPort) {
|
||||
CheckHttpGet(kTestUrlWithPort, kExpectedGetResponse);
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
|
|
@ -40,6 +40,9 @@ enum Code {
|
|||
// End of stream.
|
||||
END_OF_STREAM,
|
||||
|
||||
// Failure to get HTTP response successfully,
|
||||
HTTP_FAILURE,
|
||||
|
||||
// Unable to parse the media file.
|
||||
PARSER_FAILURE,
|
||||
|
||||
|
|
|
@ -11,6 +11,15 @@
|
|||
'src/happyhttp.cpp',
|
||||
'src/happyhttp.h',
|
||||
],
|
||||
'cflags!': [ '-fno-exceptions' ],
|
||||
'cflags_cc!': [ '-fno-exceptions' ],
|
||||
'conditions': [
|
||||
['OS=="mac"', {
|
||||
'xcode_settings': {
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES'
|
||||
}
|
||||
}]
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'happyhttp_lib_test',
|
||||
|
|
Loading…
Reference in New Issue