A simple HTTPFetcher implementation using happyhttp.

Change-Id: I4f64d46ca4873711300ae175c5f5646ca1a7c366
This commit is contained in:
Kongqun Yang 2013-12-16 09:13:29 -08:00 committed by KongQun Yang
parent 79bd9eb51d
commit dccb069ffc
5 changed files with 315 additions and 0 deletions

184
media/base/httpfetcher.cc Normal file
View File

@ -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

48
media/base/httpfetcher.h Normal file
View File

@ -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_

View File

@ -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

View File

@ -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,

View File

@ -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',