Add HTTP proxy support.

* Move default context management to BUrlRequest since some code
(including the testsuite) bypass the BUrlProtocolRoster.
* Introduce proxy host and port in BUrlContext
* Have BHttpRequest use the proxy when making requests
This commit is contained in:
Adrien Destugues 2014-09-15 14:24:37 +02:00
parent cee64ce507
commit c98378e51a
10 changed files with 124 additions and 32 deletions

View File

@ -26,7 +26,7 @@ public:
virtual status_t Stop();
protected:
bool _ResolveHostName(uint16_t port);
bool _ResolveHostName(BString host, uint16_t port);
void _ProtocolSetup();
status_t _GetLine(BString& destString);

View File

@ -2,6 +2,8 @@
* Copyright 2010-2014 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _B_URL_CONTEXT_H_
#define _B_URL_CONTEXT_H_
@ -27,16 +29,24 @@ public:
const BNetworkCookieJar& cookieJar);
void AddAuthentication(const BUrl& url,
const BHttpAuthentication& authentication);
void SetProxy(BString host, uint16 port);
// Context accessors
BNetworkCookieJar& GetCookieJar();
BHttpAuthentication& GetAuthentication(const BUrl& url);
bool UseProxy();
BString GetProxyHost();
uint16 GetProxyPort();
private:
BNetworkCookieJar fCookieJar;
typedef BPrivate::SynchronizedHashMap<BPrivate::HashString,
BHttpAuthentication*> BHttpAuthenticationMap;
BHttpAuthenticationMap* fAuthenticationMap;
BString fProxyHost;
uint16 fProxyPort;
};
#endif // _B_URL_CONTEXT_H_

View File

@ -235,7 +235,7 @@ BGopherRequest::_ProtocolLoop()
if (fSocket == NULL)
return B_NO_MEMORY;
if (!_ResolveHostName(70)) {
if (!_ResolveHostName(fUrl.Host(), fUrl.HasPort() ? fUrl.Port() : 70)) {
_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR,
"Unable to resolve hostname (%s), aborting.",
fUrl.Host().String());

View File

@ -319,7 +319,18 @@ BHttpRequest::_ProtocolLoop()
fHeaders.Clear();
_ResultHeaders().Clear();
if (!_ResolveHostName(fSSL ? 443 : 80)) {
BString host = fUrl.Host();
int port = fSSL ? 443 : 80;
if (fUrl.HasPort())
port = fUrl.Port();
if (fContext->UseProxy()) {
host = fContext->GetProxyHost();
port = fContext->GetProxyPort();
}
if (!_ResolveHostName(host, port)) {
_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR,
"Unable to resolve hostname (%s), aborting.",
fUrl.Host().String());
@ -733,11 +744,20 @@ void
BHttpRequest::_SendRequest()
{
BString request(fRequestMethod);
request << ' ';
if (fContext->UseProxy()) {
// When there is a proxy, the request must include the host and port so
// the proxy knows where to send the request.
request << Url().Protocol() << "://" << Url().Host();
if (Url().HasPort())
request << ':' << Url().Port();
}
if (Url().HasPath())
request << ' ' << Url().Path();
request << Url().Path();
else
request << " /";
request << '/';
if (Url().HasRequest())
request << '?' << Url().Request();

View File

@ -36,17 +36,14 @@ BNetworkRequest::Stop()
bool
BNetworkRequest::_ResolveHostName(uint16_t port)
BNetworkRequest::_ResolveHostName(BString host, uint16_t port)
{
_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Resolving %s",
fUrl.UrlString().String());
if (fUrl.HasPort())
port = fUrl.Port();
// FIXME stop forcing AF_INET, when BNetworkAddress stops giving IPv6
// addresses when there isn't an IPv6 link available.
fRemoteAddr = BNetworkAddress(AF_INET, fUrl.Host(), port);
fRemoteAddr = BNetworkAddress(AF_INET, host, port);
if (fRemoteAddr.InitCheck() != B_OK)
return false;

View File

@ -18,7 +18,9 @@
BUrlContext::BUrlContext()
:
fCookieJar(),
fAuthenticationMap(NULL)
fAuthenticationMap(NULL),
fProxyHost(),
fProxyPort(0)
{
fAuthenticationMap = new(std::nothrow) BHttpAuthenticationMap();
@ -31,8 +33,8 @@ BUrlContext::BUrlContext()
BUrlContext::~BUrlContext()
{
BHttpAuthenticationMap::Iterator iterator =
fAuthenticationMap->GetIterator();
BHttpAuthenticationMap::Iterator iterator
= fAuthenticationMap->GetIterator();
while (iterator.HasNext())
delete *iterator.NextValue();
@ -74,6 +76,14 @@ BUrlContext::AddAuthentication(const BUrl& url,
}
void
BUrlContext::SetProxy(BString host, uint16 port)
{
fProxyHost = host;
fProxyPort = port;
}
// #pragma mark Context accessors
@ -102,3 +112,24 @@ BUrlContext::GetAuthentication(const BUrl& url)
return *authentication;
}
bool
BUrlContext::UseProxy()
{
return !fProxyHost.IsEmpty();
}
BString
BUrlContext::GetProxyHost()
{
return fProxyHost;
}
uint16
BUrlContext::GetProxyPort()
{
return fProxyPort;
}

View File

@ -19,22 +19,10 @@
#include <UrlRequest.h>
static BReference<BUrlContext> gDefaultContext = new(std::nothrow) BUrlContext();
/* static */ BUrlRequest*
BUrlProtocolRoster::MakeRequest(const BUrl& url,
BUrlProtocolListener* listener, BUrlContext* context)
{
if (context == NULL)
context = gDefaultContext;
if (context == NULL) {
// Allocation of the gDefaultContext failed. Don't allow creating
// requests without a context.
return NULL;
}
// TODO: instanciate the correct BUrlProtocol using add-on interface
if (url.Protocol() == "http") {
return new(std::nothrow) BHttpRequest(url, false, "HTTP", listener,

View File

@ -12,6 +12,9 @@
#include <stdio.h>
static BReference<BUrlContext> gDefaultContext = new(std::nothrow) BUrlContext();
BUrlRequest::BUrlRequest(const BUrl& url, BUrlProtocolListener* listener,
BUrlContext* context, const char* threadName, const char* protocolName)
:
@ -25,6 +28,8 @@ BUrlRequest::BUrlRequest(const BUrl& url, BUrlProtocolListener* listener,
fThreadName(threadName),
fProtocol(protocolName)
{
if (fContext == NULL)
fContext = gDefaultContext;
}

View File

@ -45,7 +45,7 @@ HttpTest::GetTest()
CPPUNIT_ASSERT(t.Run());
while(t.IsRunning())
while (t.IsRunning())
snooze(1000);
CPPUNIT_ASSERT_EQUAL(B_OK, t.Status());
@ -59,7 +59,44 @@ HttpTest::GetTest()
// Fixed size as we know the response format.
CPPUNIT_ASSERT(!c->GetCookieJar().GetIterator().HasNext());
// This page should not set cookies
c->ReleaseReference();
}
void
HttpTest::ProxyTest()
{
BUrl testUrl(fBaseUrl, "/user-agent");
BUrlContext* c = new BUrlContext();
c->AcquireReference();
c->SetProxy("120.203.214.182", 83);
BHttpRequest t(testUrl);
t.SetContext(c);
BUrlProtocolListener l;
t.SetListener(&l);
CPPUNIT_ASSERT(t.Run());
while (t.IsRunning())
snooze(1000);
CPPUNIT_ASSERT_EQUAL(B_OK, t.Status());
const BHttpResult& r = dynamic_cast<const BHttpResult&>(t.Result());
printf("%s\n", r.StatusText().String());
CPPUNIT_ASSERT_EQUAL(200, r.StatusCode());
CPPUNIT_ASSERT_EQUAL(BString("OK"), r.StatusText());
CPPUNIT_ASSERT_EQUAL(42, r.Length());
// Fixed size as we know the response format.
CPPUNIT_ASSERT(!c->GetCookieJar().GetIterator().HasNext());
// This page should not set cookies
c->ReleaseReference();
}
@ -93,7 +130,7 @@ HttpTest::PortTest()
CPPUNIT_ASSERT(t.Run());
while(t.IsRunning())
while (t.IsRunning())
snooze(1000);
CPPUNIT_ASSERT_EQUAL(B_OK, t.Status());
@ -123,7 +160,7 @@ HttpTest::UploadTest()
CPPUNIT_ASSERT(t.Run());
while(t.IsRunning())
while (t.IsRunning())
snooze(1000);
CPPUNIT_ASSERT_EQUAL(B_OK, t.Status());
@ -131,7 +168,7 @@ HttpTest::UploadTest()
const BHttpResult& r = dynamic_cast<const BHttpResult&>(t.Result());
CPPUNIT_ASSERT_EQUAL(200, r.StatusCode());
CPPUNIT_ASSERT_EQUAL(BString("OK"), r.StatusText());
CPPUNIT_ASSERT_EQUAL(474, r.Length());
CPPUNIT_ASSERT_EQUAL(466, r.Length());
// Fixed size as we know the response format.
}
@ -165,7 +202,7 @@ HttpTest::_AuthTest(BUrl& testUrl)
CPPUNIT_ASSERT(t.Run());
while(t.IsRunning())
while (t.IsRunning())
snooze(1000);
CPPUNIT_ASSERT_EQUAL(B_OK, t.Status());
@ -216,6 +253,9 @@ HttpTest::AddTests(BTestSuite& parent)
suite.addTest(new CppUnit::TestCaller<HttpTest>(
"HttpTest::PortTest", &HttpTest::PortTest));
suite.addTest(new CppUnit::TestCaller<HttpTest>("HttpTest::ProxyTest",
&HttpTest::ProxyTest));
parent.addTest("HttpTest", &suite);
}

View File

@ -24,6 +24,7 @@ public:
void UploadTest();
void AuthBasicTest();
void AuthDigestTest();
void ProxyTest();
static void AddTests(BTestSuite& suite);