diff options
Diffstat (limited to 'lib/httpserver')
-rw-r--r-- | lib/httpserver/HTTPException.txt | 29 | ||||
-rw-r--r-- | lib/httpserver/HTTPRequest.cpp | 135 | ||||
-rw-r--r-- | lib/httpserver/HTTPRequest.h | 11 | ||||
-rw-r--r-- | lib/httpserver/HTTPResponse.cpp | 49 | ||||
-rw-r--r-- | lib/httpserver/HTTPResponse.h | 10 | ||||
-rw-r--r-- | lib/httpserver/HTTPServer.cpp | 31 | ||||
-rw-r--r-- | lib/httpserver/HTTPServer.h | 5 | ||||
-rw-r--r-- | lib/httpserver/S3Client.cpp | 83 | ||||
-rw-r--r-- | lib/httpserver/S3Client.h | 10 | ||||
-rw-r--r-- | lib/httpserver/S3Simulator.cpp | 37 | ||||
-rw-r--r-- | lib/httpserver/S3Simulator.h | 9 | ||||
-rw-r--r-- | lib/httpserver/cdecode.cpp | 2 |
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; |