From dccb069ffc961e0eabc29209ca032f3511556b76 Mon Sep 17 00:00:00 2001 From: Kongqun Yang Date: Mon, 16 Dec 2013 09:13:29 -0800 Subject: [PATCH] A simple HTTPFetcher implementation using happyhttp. Change-Id: I4f64d46ca4873711300ae175c5f5646ca1a7c366 --- media/base/httpfetcher.cc | 184 ++++++++++++++++++++++++++++ media/base/httpfetcher.h | 48 ++++++++ media/base/httpfetcher_unittest.cc | 71 +++++++++++ media/base/status.h | 3 + third_party/happyhttp/happyhttp.gyp | 9 ++ 5 files changed, 315 insertions(+) create mode 100644 media/base/httpfetcher.cc create mode 100644 media/base/httpfetcher.h create mode 100644 media/base/httpfetcher_unittest.cc diff --git a/media/base/httpfetcher.cc b/media/base/httpfetcher.cc new file mode 100644 index 0000000000..1b1d1b3862 --- /dev/null +++ b/media/base/httpfetcher.cc @@ -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 +#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(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(userdata); + result->response.append(reinterpret_cast(data), num_bytes); +} + +void OnComplete(const happyhttp::Response* response, void* userdata) { + DCHECK(response && userdata); + HTTPResult* result = static_cast(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(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 diff --git a/media/base/httpfetcher.h b/media/base/httpfetcher.h new file mode 100644 index 0000000000..820c20b92e --- /dev/null +++ b/media/base/httpfetcher.h @@ -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_ + diff --git a/media/base/httpfetcher_unittest.cc b/media/base/httpfetcher_unittest.cc new file mode 100644 index 0000000000..ada82a3e78 --- /dev/null +++ b/media/base/httpfetcher_unittest.cc @@ -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[] = + "http_test
"
+    "Arguments()
"; +const char kPostData[] = "foo=62&type=mp4"; +const char kExpectedPostResponse[] = + "http_test
"
+    "Arguments([foo]=>62[type]=>mp4)
"; +} // 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 + diff --git a/media/base/status.h b/media/base/status.h index 5c9872119c..ae9fe12f4d 100644 --- a/media/base/status.h +++ b/media/base/status.h @@ -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, diff --git a/third_party/happyhttp/happyhttp.gyp b/third_party/happyhttp/happyhttp.gyp index 7f8b93e917..0cc903abc2 100644 --- a/third_party/happyhttp/happyhttp.gyp +++ b/third_party/happyhttp/happyhttp.gyp @@ -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',