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.
|
||||||
END_OF_STREAM,
|
END_OF_STREAM,
|
||||||
|
|
||||||
|
// Failure to get HTTP response successfully,
|
||||||
|
HTTP_FAILURE,
|
||||||
|
|
||||||
// Unable to parse the media file.
|
// Unable to parse the media file.
|
||||||
PARSER_FAILURE,
|
PARSER_FAILURE,
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,15 @@
|
||||||
'src/happyhttp.cpp',
|
'src/happyhttp.cpp',
|
||||||
'src/happyhttp.h',
|
'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',
|
'target_name': 'happyhttp_lib_test',
|
||||||
|
|
Loading…
Reference in New Issue