summaryrefslogtreecommitdiff
path: root/lib/httpserver
diff options
context:
space:
mode:
Diffstat (limited to 'lib/httpserver')
-rw-r--r--lib/httpserver/HTTPException.txt29
-rw-r--r--lib/httpserver/HTTPRequest.cpp135
-rw-r--r--lib/httpserver/HTTPRequest.h11
-rw-r--r--lib/httpserver/HTTPResponse.cpp49
-rw-r--r--lib/httpserver/HTTPResponse.h10
-rw-r--r--lib/httpserver/HTTPServer.cpp31
-rw-r--r--lib/httpserver/HTTPServer.h5
-rw-r--r--lib/httpserver/S3Client.cpp83
-rw-r--r--lib/httpserver/S3Client.h10
-rw-r--r--lib/httpserver/S3Simulator.cpp37
-rw-r--r--lib/httpserver/S3Simulator.h9
-rw-r--r--lib/httpserver/cdecode.cpp2
12 files changed, 264 insertions, 147 deletions
diff --git a/lib/httpserver/HTTPException.txt b/lib/httpserver/HTTPException.txt
index 52630cda..c9b3f940 100644
--- a/lib/httpserver/HTTPException.txt
+++ b/lib/httpserver/HTTPException.txt
@@ -1,16 +1,17 @@
EXCEPTION HTTP 10
-Internal 0
-RequestReadFailed 1
-RequestAlreadyBeenRead 2
-BadRequest 3
-UnknownResponseCodeUsed 4
-NoContentTypeSet 5
-POSTContentTooLong 6
-CannotSetRedirectIfReponseHasData 7
-CannotSetNotFoundIfReponseHasData 8
-NotImplemented 9
-RequestNotInitialised 10
-BadResponse 11
-ResponseReadFailed 12
-NoStreamConfigured 13
+Internal 0
+RequestReadFailed 1
+RequestAlreadyBeenRead 2
+BadRequest 3
+UnknownResponseCodeUsed 4
+NoContentTypeSet 5
+POSTContentTooLong 6
+CannotSetRedirectIfReponseHasData 7
+CannotSetNotFoundIfReponseHasData 8
+NotImplemented 9
+RequestNotInitialised 10
+BadResponse 11
+ResponseReadFailed 12
+NoStreamConfigured 13
+RequestFailedUnexpectedly 14 The request was expected to succeed, but it failed.
diff --git a/lib/httpserver/HTTPRequest.cpp b/lib/httpserver/HTTPRequest.cpp
index 4c5dc149..a94d96b0 100644
--- a/lib/httpserver/HTTPRequest.cpp
+++ b/lib/httpserver/HTTPRequest.cpp
@@ -10,10 +10,11 @@
#include "Box.h"
#include <string.h>
-#include <strings.h>
#include <stdlib.h>
#include <stdio.h>
+#include <sstream>
+
#include "HTTPRequest.h"
#include "HTTPResponse.h"
#include "HTTPQueryDecoder.h"
@@ -94,6 +95,32 @@ HTTPRequest::~HTTPRequest()
}
}
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPRequest::GetMethodName()
+// Purpose: Returns the name of the request's HTTP method verb
+// as a string.
+// Created: 28/7/15
+//
+// --------------------------------------------------------------------------
+
+std::string HTTPRequest::GetMethodName() const
+{
+ switch(mMethod)
+ {
+ case Method_UNINITIALISED: return "uninitialised";
+ case Method_UNKNOWN: return "unknown";
+ case Method_GET: return "GET";
+ case Method_HEAD: return "HEAD";
+ case Method_POST: return "POST";
+ case Method_PUT: return "PUT";
+ default:
+ std::ostringstream oss;
+ oss << "unknown-" << mMethod;
+ return oss.str();
+ };
+}
// --------------------------------------------------------------------------
//
@@ -111,7 +138,7 @@ bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
// Check caller's logic
if(mMethod != Method_UNINITIALISED)
{
- THROW_EXCEPTION(HTTPException, RequestAlreadyBeenRead)
+ THROW_EXCEPTION(HTTPException, RequestAlreadyBeenRead);
}
// Read the first line, which is of a different format to the rest of the lines
@@ -126,8 +153,8 @@ bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
// Check the method
size_t p = 0; // current position in string
p = requestLine.find(' '); // end of first word
-
- if (p == std::string::npos)
+
+ if(p == std::string::npos)
{
// No terminating space, looks bad
p = requestLine.size();
@@ -163,14 +190,14 @@ bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
{
++p;
}
-
+
// Check there's a URI following...
if(requestLinePtr[p] == '\0')
{
// Didn't get the request line, probably end of connection which had been kept alive
return false;
}
-
+
// Read the URI, unescaping any %XX hex codes
while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0')
{
@@ -201,10 +228,10 @@ bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
{
code[1] = requestLinePtr[++p];
}
-
+
// Convert into a char code
long c = ::strtol(code, NULL, 16);
-
+
// Accept it?
if(c > 0 && c <= 255)
{
@@ -241,28 +268,32 @@ bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
int major, minor;
if(::sscanf(requestLinePtr + p + 5, "%d.%d", &major, &minor) != 2)
{
- THROW_EXCEPTION(HTTPException, BadRequest)
+ THROW_EXCEPTION_MESSAGE(HTTPException, BadRequest,
+ "Unable to parse HTTP version number: " <<
+ requestLinePtr);
}
-
+
// Store version
mHTTPVersion = (major * HTTPVersion__MajorMultiplier) + minor;
}
else
{
// Not good -- wrong string found
- THROW_EXCEPTION(HTTPException, BadRequest)
+ THROW_EXCEPTION_MESSAGE(HTTPException, BadRequest,
+ "Unable to parse HTTP request line: " <<
+ requestLinePtr);
}
}
-
+
BOX_TRACE("HTTPRequest: method=" << mMethod << ", uri=" <<
mRequestURI << ", version=" << mHTTPVersion);
-
+
// If HTTP 1.1 or greater, assume keep-alive
if(mHTTPVersion >= HTTPVersion_1_1)
{
mClientKeepAliveRequested = true;
}
-
+
// Decode query string?
if((mMethod == Method_GET || mMethod == Method_HEAD) && !mQueryString.empty())
{
@@ -270,28 +301,28 @@ bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
decoder.DecodeChunk(mQueryString.c_str(), mQueryString.size());
decoder.Finish();
}
-
+
// Now parse the headers
ParseHeaders(rGetLine, Timeout);
-
+
std::string expected;
- if (GetHeader("Expect", &expected))
+ if(GetHeader("Expect", &expected))
{
- if (expected == "100-continue")
+ if(expected == "100-continue")
{
mExpectContinue = true;
}
}
-
+
// Parse form data?
if(mMethod == Method_POST && mContentLength >= 0)
{
// Too long? Don't allow people to be nasty by sending lots of data
if(mContentLength > MAX_CONTENT_SIZE)
{
- THROW_EXCEPTION(HTTPException, POSTContentTooLong)
+ THROW_EXCEPTION(HTTPException, POSTContentTooLong);
}
-
+
// Some data in the request to follow, parsing it bit by bit
HTTPQueryDecoder decoder(mQuery);
// Don't forget any data left in the GetLine object
@@ -317,7 +348,8 @@ bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
if(r == 0)
{
// Timeout, just error
- THROW_EXCEPTION(HTTPException, RequestReadFailed)
+ THROW_EXCEPTION_MESSAGE(HTTPException, RequestReadFailed,
+ "Failed to read complete request with the timeout");
}
decoder.DecodeChunk(buf, r);
bytesToGo -= r;
@@ -336,7 +368,7 @@ bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
SetForReading();
mpStreamToReadFrom = &(rGetLine.GetUnderlyingStream());
}
-
+
return true;
}
@@ -346,7 +378,7 @@ void HTTPRequest::ReadContent(IOStream& rStreamToWriteTo)
CopyStreamTo(rStreamToWriteTo);
IOStream::pos_type bytesCopied = GetSize();
-
+
while (bytesCopied < mContentLength)
{
char buffer[1024];
@@ -397,7 +429,8 @@ bool HTTPRequest::Send(IOStream &rStream, int Timeout, bool ExpectContinue)
case HTTPVersion_1_0: rStream.Write("HTTP/1.0"); break;
case HTTPVersion_1_1: rStream.Write("HTTP/1.1"); break;
default:
- THROW_EXCEPTION(HTTPException, NotImplemented);
+ THROW_EXCEPTION_MESSAGE(HTTPException, NotImplemented,
+ "Unsupported HTTP version: " << mHTTPVersion);
}
rStream.Write("\n");
@@ -428,7 +461,8 @@ bool HTTPRequest::Send(IOStream &rStream, int Timeout, bool ExpectContinue)
if (mpCookies)
{
- THROW_EXCEPTION(HTTPException, NotImplemented);
+ THROW_EXCEPTION_MESSAGE(HTTPException, NotImplemented,
+ "Cookie support not implemented yet");
}
if (mClientKeepAliveRequested)
@@ -445,7 +479,7 @@ bool HTTPRequest::Send(IOStream &rStream, int Timeout, bool ExpectContinue)
{
oss << i->first << ": " << i->second << "\n";
}
-
+
if (ExpectContinue)
{
oss << "Expect: 100-continue\n";
@@ -461,23 +495,22 @@ void HTTPRequest::SendWithStream(IOStream &rStreamToSendTo, int Timeout,
IOStream* pStreamToSend, HTTPResponse& rResponse)
{
IOStream::pos_type size = pStreamToSend->BytesLeftToRead();
-
if (size != IOStream::SizeOfStreamUnknown)
{
mContentLength = size;
}
-
+
Send(rStreamToSendTo, Timeout, true);
-
+
rResponse.Receive(rStreamToSendTo, Timeout);
if (rResponse.GetResponseCode() != 100)
{
// bad response, abort now
return;
}
-
+
pStreamToSend->CopyStreamTo(rStreamToSendTo, Timeout);
-
+
// receive the final response
rResponse.Receive(rStreamToSendTo, Timeout);
}
@@ -502,13 +535,13 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
THROW_EXCEPTION(HTTPException, BadRequest)
}
- std::string currentLine;
+ std::string currentLine;
if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout))
{
// Timeout
THROW_EXCEPTION(HTTPException, RequestReadFailed)
}
-
+
// Is this a continuation of the previous line?
bool processHeader = haveHeader;
if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t'))
@@ -517,7 +550,7 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
processHeader = false;
}
//TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str());
-
+
// Parse the header -- this will actually process the header
// from the previous run around the loop.
if(processHeader)
@@ -538,7 +571,7 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
std::string header_name(ToLowerCase(std::string(h,
p)));
-
+
if (header_name == "content-length")
{
// Decode number
@@ -556,17 +589,17 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
{
// Store host header
mHostName = h + dataStart;
-
+
// Is there a port number to split off?
std::string::size_type colon = mHostName.find_first_of(':');
if(colon != std::string::npos)
{
// There's a port in the string... attempt to turn it into an int
mHostPort = ::strtol(mHostName.c_str() + colon + 1, 0, 10);
-
+
// Truncate the string to just the hostname
mHostName = mHostName.substr(0, colon);
-
+
BOX_TRACE("Host: header, hostname = " <<
"'" << mHostName << "', host "
"port = " << mHostPort);
@@ -596,7 +629,7 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
mExtraHeaders.push_back(Header(header_name,
h + dataStart));
}
-
+
// Unset have header flag, as it's now been processed
haveHeader = false;
}
@@ -617,7 +650,7 @@ void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
{
// All done!
break;
- }
+ }
}
}
@@ -636,13 +669,13 @@ void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts)
const char *pos = data;
const char *itemStart = pos;
std::string name;
-
+
enum
{
s_NAME, s_VALUE, s_VALUE_QUOTED, s_FIND_NEXT_NAME
} state = s_NAME;
- do
+ do
{
switch(state)
{
@@ -665,7 +698,7 @@ void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts)
}
}
break;
-
+
case s_VALUE:
{
if(*pos == ';' || *pos == ',' || *pos == '\0')
@@ -679,7 +712,7 @@ void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts)
}
}
break;
-
+
case s_VALUE_QUOTED:
{
if(*pos == '"')
@@ -693,7 +726,7 @@ void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts)
}
}
break;
-
+
case s_FIND_NEXT_NAME:
{
// Skip over terminators and white space to get to the next name
@@ -705,7 +738,7 @@ void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts)
}
}
break;
-
+
default:
// Ooops
THROW_EXCEPTION(HTTPException, Internal)
@@ -732,7 +765,7 @@ bool HTTPRequest::GetCookie(const char *CookieName, std::string &rValueOut) cons
{
return false;
}
-
+
// See if it's there
CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName)));
if(v != mpCookies->end())
@@ -741,7 +774,7 @@ bool HTTPRequest::GetCookie(const char *CookieName, std::string &rValueOut) cons
rValueOut = v->second;
return true;
}
-
+
return false;
}
@@ -764,7 +797,7 @@ const std::string &HTTPRequest::GetCookie(const char *CookieName) const
{
return noCookie;
}
-
+
// See if it's there
CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName)));
if(v != mpCookies->end())
@@ -772,7 +805,7 @@ const std::string &HTTPRequest::GetCookie(const char *CookieName) const
// Return the value
return v->second;
}
-
+
return noCookie;
}
diff --git a/lib/httpserver/HTTPRequest.h b/lib/httpserver/HTTPRequest.h
index 25effb70..16c4d16c 100644
--- a/lib/httpserver/HTTPRequest.h
+++ b/lib/httpserver/HTTPRequest.h
@@ -3,7 +3,7 @@
// File
// Name: HTTPRequest.h
// Purpose: Request object for HTTP connections
-// Created: 26/3/04
+// Created: 26/3/2004
//
// --------------------------------------------------------------------------
@@ -23,8 +23,12 @@ class IOStreamGetLine;
//
// Class
// Name: HTTPRequest
-// Purpose: Request object for HTTP connections
-// Created: 26/3/04
+// Purpose: Request object for HTTP connections. Although it
+// inherits from CollectInBufferStream, not all of the
+// request data is held in memory, only the beginning.
+// Use ReadContent() to write it all (including the
+// buffered beginning) to another stream, e.g. a file.
+// Created: 26/3/2004
//
// --------------------------------------------------------------------------
class HTTPRequest : public CollectInBufferStream
@@ -77,6 +81,7 @@ public:
//
// --------------------------------------------------------------------------
enum Method GetMethod() const {return mMethod;}
+ std::string GetMethodName() const;
const std::string &GetRequestURI() const {return mRequestURI;}
// Note: the HTTPRequest generates and parses the Host: header
diff --git a/lib/httpserver/HTTPResponse.cpp b/lib/httpserver/HTTPResponse.cpp
index 1a8c8447..c56f286f 100644
--- a/lib/httpserver/HTTPResponse.cpp
+++ b/lib/httpserver/HTTPResponse.cpp
@@ -138,8 +138,7 @@ void HTTPResponse::SetContentType(const char *ContentType)
// Function
// Name: HTTPResponse::Send(IOStream &, bool)
// Purpose: Build the response, and send via the stream.
-// Optionally omitting the content.
-// Created: 26/3/04
+// Created: 26/3/2004
//
// --------------------------------------------------------------------------
void HTTPResponse::Send(bool OmitContent)
@@ -148,7 +147,7 @@ void HTTPResponse::Send(bool OmitContent)
{
THROW_EXCEPTION(HTTPException, NoStreamConfigured);
}
-
+
if (GetSize() != 0 && mContentType.empty())
{
THROW_EXCEPTION(HTTPException, NoContentTypeSet);
@@ -184,6 +183,7 @@ void HTTPResponse::Send(bool OmitContent)
// static is allowed to be cached for a day
header += "\r\nCache-Control: max-age=86400";
}
+
if(mKeepAlive)
{
header += "\r\nConnection: keep-alive\r\n\r\n";
@@ -192,12 +192,13 @@ void HTTPResponse::Send(bool OmitContent)
{
header += "\r\nConnection: close\r\n\r\n";
}
+
// NOTE: header ends with blank line in all cases
-
+
// Write to stream
mpStreamToSendTo->Write(header.c_str(), header.size());
}
-
+
// Send content
if(!OmitContent)
{
@@ -227,16 +228,16 @@ void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
if(rGetLine.IsEOF())
{
// Header terminates unexpectedly
- THROW_EXCEPTION(HTTPException, BadRequest)
+ THROW_EXCEPTION(HTTPException, BadRequest)
}
- std::string currentLine;
+ std::string currentLine;
if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout))
{
// Timeout
THROW_EXCEPTION(HTTPException, RequestReadFailed)
}
-
+
// Is this a continuation of the previous line?
bool processHeader = haveHeader;
if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t'))
@@ -245,7 +246,7 @@ void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
processHeader = false;
}
//TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str());
-
+
// Parse the header -- this will actually process the header
// from the previous run around the loop.
if(processHeader)
@@ -263,7 +264,7 @@ void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
{
++dataStart;
}
-
+
if(p == sizeof("Content-Length")-1
&& ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0)
{
@@ -308,7 +309,7 @@ void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
std::string headerName = header.substr(0, p);
AddHeader(headerName, h + dataStart);
}
-
+
// Unset have header flag, as it's now been processed
haveHeader = false;
}
@@ -329,7 +330,7 @@ void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
{
// All done!
break;
- }
+ }
}
}
@@ -340,22 +341,22 @@ void HTTPResponse::Receive(IOStream& rStream, int Timeout)
if(rGetLine.IsEOF())
{
// Connection terminated unexpectedly
- THROW_EXCEPTION(HTTPException, BadResponse)
+ THROW_EXCEPTION_MESSAGE(HTTPException, BadResponse,
+ "HTTP server closed the connection without sending a response");
}
- std::string statusLine;
+ std::string statusLine;
if(!rGetLine.GetLine(statusLine, false /* no preprocess */, Timeout))
{
// Timeout
- THROW_EXCEPTION(HTTPException, ResponseReadFailed)
+ THROW_EXCEPTION_MESSAGE(HTTPException, ResponseReadFailed,
+ "Failed to get a response from the HTTP server within the timeout");
}
- if (statusLine.substr(0, 7) != "HTTP/1." ||
- statusLine[8] != ' ')
+ if (statusLine.substr(0, 7) != "HTTP/1." || statusLine[8] != ' ')
{
- // Status line terminated unexpectedly
- BOX_ERROR("Bad response status line: " << statusLine);
- THROW_EXCEPTION(HTTPException, BadResponse)
+ THROW_EXCEPTION_MESSAGE(HTTPException, BadResponse,
+ "HTTP server sent an invalid HTTP status line: " << statusLine);
}
if (statusLine[5] == '1' && statusLine[7] == '1')
@@ -363,7 +364,7 @@ void HTTPResponse::Receive(IOStream& rStream, int Timeout)
// HTTP/1.1 default is to keep alive
mKeepAlive = true;
}
-
+
// Decode the status code
long status = ::strtol(statusLine.substr(9, 3).c_str(), NULL, 10);
// returns zero in error case, this is OK
@@ -376,7 +377,7 @@ void HTTPResponse::Receive(IOStream& rStream, int Timeout)
{
return;
}
-
+
ParseHeaders(rGetLine, Timeout);
// push back whatever bytes we have left
@@ -549,7 +550,7 @@ void HTTPResponse::SetAsRedirect(const char *RedirectTo, bool IsLocalURI)
if(IsLocalURI) header += msDefaultURIPrefix;
header += RedirectTo;
mExtraHeaders.push_back(Header("Location", header));
-
+
// Set up some default content
mContentType = "text/html";
#define REDIRECT_HTML_1 "<html><head><title>Redirection</title></head>\n<body><p><a href=\""
@@ -622,7 +623,7 @@ void HTTPResponse::WriteStringDefang(const char *String, unsigned int StringLen)
StringLen -= toWrite;
String += toWrite;
}
-
+
// Is it a bad character next?
while(StringLen > 0)
{
diff --git a/lib/httpserver/HTTPResponse.h b/lib/httpserver/HTTPResponse.h
index 04051958..f39825d9 100644
--- a/lib/httpserver/HTTPResponse.h
+++ b/lib/httpserver/HTTPResponse.h
@@ -63,9 +63,10 @@ public:
typedef std::pair<std::string, std::string> Header;
void SetResponseCode(int Code);
- int GetResponseCode() { return mResponseCode; }
+ int GetResponseCode() const { return mResponseCode; }
void SetContentType(const char *ContentType);
const std::string& GetContentType() { return mContentType; }
+ int64_t GetContentLength() { return mContentLength; }
void SetAsRedirect(const char *RedirectTo, bool IsLocalURI = true);
void SetAsNotFound(const char *URI);
@@ -107,6 +108,7 @@ public:
void SetResponseIsDynamicContent(bool IsDynamic) {mResponseIsDynamicContent = IsDynamic;}
// Set keep alive control, default is to mark as to be closed
void SetKeepAlive(bool KeepAlive) {mKeepAlive = KeepAlive;}
+ bool IsKeepAlive() {return mKeepAlive;}
void SetCookie(const char *Name, const char *Value, const char *Path = "/", int ExpiresAt = 0);
@@ -127,7 +129,7 @@ public:
};
static const char *ResponseCodeToString(int ResponseCode);
-
+
void WriteStringDefang(const char *String, unsigned int StringLen);
void WriteStringDefang(const std::string &rString) {WriteStringDefang(rString.c_str(), rString.size());}
@@ -163,9 +165,9 @@ private:
bool mKeepAlive;
std::string mContentType;
std::vector<Header> mExtraHeaders;
- int mContentLength; // only used when reading response from stream
+ int64_t mContentLength; // only used when reading response from stream
IOStream* mpStreamToSendTo; // nonzero only when constructed with a stream
-
+
static std::string msDefaultURIPrefix;
void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout);
diff --git a/lib/httpserver/HTTPServer.cpp b/lib/httpserver/HTTPServer.cpp
index be1db687..a2daed99 100644
--- a/lib/httpserver/HTTPServer.cpp
+++ b/lib/httpserver/HTTPServer.cpp
@@ -27,8 +27,8 @@
// Created: 26/3/04
//
// --------------------------------------------------------------------------
-HTTPServer::HTTPServer()
- : mTimeout(20000) // default timeout leaves a little while for clients to get the second request in.
+HTTPServer::HTTPServer(int Timeout)
+: mTimeout(Timeout)
{
}
@@ -86,7 +86,7 @@ const ConfigurationVerify *HTTPServer::GetConfigVerify() const
0
}
};
-
+
static ConfigurationVerifyKey verifyrootkeys[] =
{
HTTPSERVER_VERIFY_ROOT_KEYS
@@ -132,10 +132,10 @@ void HTTPServer::Run()
// Created: 26/3/04
//
// --------------------------------------------------------------------------
-void HTTPServer::Connection(SocketStream &rStream)
+void HTTPServer::Connection(std::auto_ptr<SocketStream> apConn)
{
// Create a get line object to use
- IOStreamGetLine getLine(rStream);
+ IOStreamGetLine getLine(*apConn);
// Notify dervived claases
HTTPConnectionOpening();
@@ -150,10 +150,10 @@ void HTTPServer::Connection(SocketStream &rStream)
// Didn't get request, connection probably closed.
break;
}
-
+
// Generate a response
- HTTPResponse response(&rStream);
-
+ HTTPResponse response(apConn.get());
+
try
{
Handle(request, response);
@@ -169,9 +169,18 @@ void HTTPServer::Connection(SocketStream &rStream)
{
SendInternalErrorResponse("unknown", response);
}
-
- // Keep alive?
- if(request.GetClientKeepAliveRequested())
+
+ // Keep alive? response.GetSize() works for CollectInBufferStream, but
+ // when we make HTTPResponse stream the response instead, we'll need to
+ // figure out whether we can get the response length from the IOStream
+ // to be streamed, or not. Also, we don't currently support chunked
+ // encoding, and http://tools.ietf.org/html/rfc7230#section-3.3.1 says
+ // that "If any transfer coding other than chunked is applied to a
+ // response payload body, the sender MUST either apply chunked as the
+ // final transfer coding or terminate the message by closing the
+ // connection. So for now, keepalive stays off.
+ if(false && request.GetClientKeepAliveRequested() &&
+ response.GetSize() >= 0)
{
// Mark the response to the client as supporting keepalive
response.SetKeepAlive(true);
diff --git a/lib/httpserver/HTTPServer.h b/lib/httpserver/HTTPServer.h
index d9f74949..8ac1ff83 100644
--- a/lib/httpserver/HTTPServer.h
+++ b/lib/httpserver/HTTPServer.h
@@ -27,7 +27,8 @@ class HTTPResponse;
class HTTPServer : public ServerStream<SocketStream, 80>
{
public:
- HTTPServer();
+ HTTPServer(int Timeout = 60000);
+ // default timeout leaves 1 minute for clients to get a second request in.
~HTTPServer();
private:
// no copying
@@ -62,7 +63,7 @@ private:
const char *DaemonName() const;
const ConfigurationVerify *GetConfigVerify() const;
void Run();
- void Connection(SocketStream &rStream);
+ void Connection(std::auto_ptr<SocketStream> apStream);
};
// Root level
diff --git a/lib/httpserver/S3Client.cpp b/lib/httpserver/S3Client.cpp
index cd5988d5..21814066 100644
--- a/lib/httpserver/S3Client.cpp
+++ b/lib/httpserver/S3Client.cpp
@@ -34,7 +34,7 @@
// Name: S3Client::GetObject(const std::string& rObjectURI)
// Purpose: Retrieve the object with the specified URI (key)
// from your S3 bucket.
-// Created: 09/01/09
+// Created: 09/01/2009
//
// --------------------------------------------------------------------------
@@ -46,12 +46,29 @@ HTTPResponse S3Client::GetObject(const std::string& rObjectURI)
// --------------------------------------------------------------------------
//
// Function
+// Name: S3Client::HeadObject(const std::string& rObjectURI)
+// Purpose: Retrieve the metadata for the object with the
+// specified URI (key) from your S3 bucket.
+// Created: 03/08/2015
+//
+// --------------------------------------------------------------------------
+
+HTTPResponse S3Client::HeadObject(const std::string& rObjectURI)
+{
+ return FinishAndSendRequest(HTTPRequest::Method_HEAD, rObjectURI);
+}
+
+
+HTTPResponse HeadObject(const std::string& rObjectURI);
+// --------------------------------------------------------------------------
+//
+// Function
// Name: S3Client::PutObject(const std::string& rObjectURI,
// IOStream& rStreamToSend, const char* pContentType)
// Purpose: Upload the stream to S3, creating or overwriting the
// object with the specified URI (key) in your S3
// bucket.
-// Created: 09/01/09
+// Created: 09/01/2009
//
// --------------------------------------------------------------------------
@@ -77,7 +94,7 @@ HTTPResponse S3Client::PutObject(const std::string& rObjectURI,
// connection to the server if necessary, which may
// throw a ConnectionException. Returns the HTTP
// response returned by S3, which may be a 500 error.
-// Created: 09/01/09
+// Created: 09/01/2009
//
// --------------------------------------------------------------------------
@@ -113,7 +130,7 @@ HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method,
{
request.AddHeader("Content-Type", pStreamContentType);
}
-
+
std::string s3suffix = ".s3.amazonaws.com";
std::string bucket;
if (mHostName.size() > s3suffix.size())
@@ -126,18 +143,18 @@ HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method,
s3suffix.size());
}
}
-
+
std::ostringstream data;
data << request.GetVerb() << "\n";
data << "\n"; /* Content-MD5 */
data << request.GetContentType() << "\n";
data << date.str() << "\n";
-
+
if (! bucket.empty())
{
data << "/" << bucket;
}
-
+
data << request.GetRequestURI();
std::string data_string = data.str();
@@ -148,7 +165,7 @@ HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method,
(const unsigned char*)data_string.c_str(),
data_string.size(), digest_buffer, &digest_size);
std::string digest((const char *)digest_buffer, digest_size);
-
+
base64::encoder encoder;
std::string auth_code = "AWS " + mAccessKey + ":" +
encoder.encode(digest);
@@ -200,6 +217,7 @@ HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method,
}
else
{
+ BOX_TRACE("S3Client: " << mHostName << " ! " << ce.what());
throw;
}
}
@@ -218,26 +236,57 @@ HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method,
// necessary, which may throw a ConnectionException.
// Returns the HTTP response returned by S3, which may
// be a 500 error.
-// Created: 09/01/09
+// Created: 09/01/2009
//
// --------------------------------------------------------------------------
HTTPResponse S3Client::SendRequest(HTTPRequest& rRequest,
IOStream* pStreamToSend, const char* pStreamContentType)
-{
+{
HTTPResponse response;
-
+
if (pStreamToSend)
{
- rRequest.SendWithStream(*mapClientSocket,
- 30000 /* milliseconds */,
+ rRequest.SendWithStream(*mapClientSocket, mNetworkTimeout,
pStreamToSend, response);
}
else
{
- rRequest.Send(*mapClientSocket, 30000 /* milliseconds */);
- response.Receive(*mapClientSocket, 30000 /* milliseconds */);
+ rRequest.Send(*mapClientSocket, mNetworkTimeout);
+ response.Receive(*mapClientSocket, mNetworkTimeout);
+ }
+
+ if(!response.IsKeepAlive())
+ {
+ BOX_TRACE("Server will close the connection, closing our end too.");
+ mapClientSocket.reset();
+ }
+ else
+ {
+ BOX_TRACE("Server will keep the connection open for more requests.");
}
-
+
return response;
-}
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: S3Client::CheckResponse(HTTPResponse&,
+// std::string& message)
+// Purpose: Check the status code of an Amazon S3 response, and
+// throw an exception with a useful message (including
+// the supplied message) if it's not a 200 OK response.
+// Created: 26/07/2015
+//
+// --------------------------------------------------------------------------
+
+void S3Client::CheckResponse(const HTTPResponse& response, const std::string& message) const
+{
+ if(response.GetResponseCode() != HTTPResponse::Code_OK)
+ {
+ THROW_EXCEPTION_MESSAGE(HTTPException, RequestFailedUnexpectedly,
+ message);
+ }
+}
+
diff --git a/lib/httpserver/S3Client.h b/lib/httpserver/S3Client.h
index 3c4126ac..4cbb4b96 100644
--- a/lib/httpserver/S3Client.h
+++ b/lib/httpserver/S3Client.h
@@ -36,7 +36,8 @@ class S3Client
: mpSimulator(pSimulator),
mHostName(rHostName),
mAccessKey(rAccessKey),
- mSecretKey(rSecretKey)
+ mSecretKey(rSecretKey),
+ mNetworkTimeout(30000)
{ }
S3Client(std::string HostName, int Port, const std::string& rAccessKey,
@@ -45,12 +46,16 @@ class S3Client
mHostName(HostName),
mPort(Port),
mAccessKey(rAccessKey),
- mSecretKey(rSecretKey)
+ mSecretKey(rSecretKey),
+ mNetworkTimeout(30000)
{ }
HTTPResponse GetObject(const std::string& rObjectURI);
+ HTTPResponse HeadObject(const std::string& rObjectURI);
HTTPResponse PutObject(const std::string& rObjectURI,
IOStream& rStreamToSend, const char* pContentType = NULL);
+ void CheckResponse(const HTTPResponse& response, const std::string& message) const;
+ int GetNetworkTimeout() const { return mNetworkTimeout; }
private:
HTTPServer* mpSimulator;
@@ -58,6 +63,7 @@ class S3Client
int mPort;
std::auto_ptr<SocketStream> mapClientSocket;
std::string mAccessKey, mSecretKey;
+ int mNetworkTimeout; // milliseconds
HTTPResponse FinishAndSendRequest(HTTPRequest::Method Method,
const std::string& rRequestURI,
diff --git a/lib/httpserver/S3Simulator.cpp b/lib/httpserver/S3Simulator.cpp
index 4f6bb3e6..df8910d7 100644
--- a/lib/httpserver/S3Simulator.cpp
+++ b/lib/httpserver/S3Simulator.cpp
@@ -40,12 +40,12 @@
// --------------------------------------------------------------------------
const ConfigurationVerify* S3Simulator::GetConfigVerify() const
{
- static ConfigurationVerifyKey verifyserverkeys[] =
+ static ConfigurationVerifyKey verifyserverkeys[] =
{
HTTPSERVER_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default addresses
};
- static ConfigurationVerify verifyserver[] =
+ static ConfigurationVerify verifyserver[] =
{
{
"Server",
@@ -55,8 +55,8 @@ const ConfigurationVerify* S3Simulator::GetConfigVerify() const
0
}
};
-
- static ConfigurationVerifyKey verifyrootkeys[] =
+
+ static ConfigurationVerifyKey verifyrootkeys[] =
{
ConfigurationVerifyKey("AccessKey", ConfigTest_Exists),
ConfigurationVerifyKey("SecretKey", ConfigTest_Exists),
@@ -99,11 +99,11 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
const Configuration& rConfig(GetConfiguration());
std::string access_key = rConfig.GetKeyValue("AccessKey");
std::string secret_key = rConfig.GetKeyValue("SecretKey");
-
+
std::string md5, date, bucket;
rRequest.GetHeader("content-md5", &md5);
rRequest.GetHeader("date", &date);
-
+
std::string host = rRequest.GetHostName();
std::string s3suffix = ".s3.amazonaws.com";
if (host.size() > s3suffix.size())
@@ -116,7 +116,7 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
s3suffix.size());
}
}
-
+
std::ostringstream data;
data << rRequest.GetVerb() << "\n";
data << md5 << "\n";
@@ -127,7 +127,7 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
std::vector<HTTPRequest::Header> headers = rRequest.GetHeaders();
std::sort(headers.begin(), headers.end());
-
+
for (std::vector<HTTPRequest::Header>::iterator
i = headers.begin(); i != headers.end(); i++)
{
@@ -135,13 +135,13 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
{
data << i->first << ":" << i->second << "\n";
}
- }
-
+ }
+
if (! bucket.empty())
{
data << "/" << bucket;
}
-
+
data << rRequest.GetRequestURI();
std::string data_string = data.str();
@@ -152,17 +152,17 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
(const unsigned char*)data_string.c_str(),
data_string.size(), digest_buffer, &digest_size);
std::string digest((const char *)digest_buffer, digest_size);
-
+
base64::encoder encoder;
std::string expectedAuth = "AWS " + access_key + ":" +
encoder.encode(digest);
-
+
if (expectedAuth[expectedAuth.size() - 1] == '\n')
{
expectedAuth = expectedAuth.substr(0,
expectedAuth.size() - 1);
}
-
+
std::string actualAuth;
if (!rRequest.GetHeader("authorization", &actualAuth) ||
actualAuth != expectedAuth)
@@ -170,7 +170,7 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
rResponse.SetResponseCode(HTTPResponse::Code_Unauthorized);
SendInternalErrorResponse("Authentication Failed",
rResponse);
- }
+ }
else if (rRequest.GetMethod() == HTTPRequest::Method_GET)
{
HandleGet(rRequest, rResponse);
@@ -198,7 +198,7 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
{
SendInternalErrorResponse("Unknown exception", rResponse);
}
-
+
if (rResponse.GetResponseCode() != 200 &&
rResponse.GetSize() == 0)
{
@@ -207,7 +207,10 @@ void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
s << rResponse.GetResponseCode();
SendInternalErrorResponse(s.str().c_str(), rResponse);
}
-
+
+ BOX_NOTICE(rResponse.GetResponseCode() << " " << rRequest.GetMethodName() << " " <<
+ rRequest.GetRequestURI());
+
return;
}
diff --git a/lib/httpserver/S3Simulator.h b/lib/httpserver/S3Simulator.h
index f80770ee..eef4f400 100644
--- a/lib/httpserver/S3Simulator.h
+++ b/lib/httpserver/S3Simulator.h
@@ -27,13 +27,20 @@ class HTTPResponse;
class S3Simulator : public HTTPServer
{
public:
- S3Simulator() { }
+ // Increase timeout to 5 minutes, from HTTPServer default of 1 minute,
+ // to help with debugging.
+ S3Simulator() : HTTPServer(300000) { }
~S3Simulator() { }
const ConfigurationVerify* GetConfigVerify() const;
virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse);
virtual void HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse);
virtual void HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse);
+
+ virtual const char *DaemonName() const
+ {
+ return "s3simulator";
+ }
};
#endif // S3SIMULATOR__H
diff --git a/lib/httpserver/cdecode.cpp b/lib/httpserver/cdecode.cpp
index e632f182..11c59d62 100644
--- a/lib/httpserver/cdecode.cpp
+++ b/lib/httpserver/cdecode.cpp
@@ -12,7 +12,7 @@ extern "C"
int base64_decode_value(char value_in)
{
- static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
+ static signed const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
static const char decoding_size = sizeof(decoding);
value_in -= 43;
if (value_in < 0 || value_in > decoding_size) return -1;