diff options
-rw-r--r-- | lib/httpserver/S3Client.cpp | 243 | ||||
-rw-r--r-- | lib/httpserver/S3Client.h | 72 | ||||
-rw-r--r-- | test/httpserver/testhttpserver.cpp | 198 |
3 files changed, 316 insertions, 197 deletions
diff --git a/lib/httpserver/S3Client.cpp b/lib/httpserver/S3Client.cpp new file mode 100644 index 00000000..cd5988d5 --- /dev/null +++ b/lib/httpserver/S3Client.cpp @@ -0,0 +1,243 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.cpp +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <cstring> + +// #include <cstdio> +// #include <ctime> + +#include <openssl/hmac.h> + +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "HTTPServer.h" +#include "autogen_HTTPException.h" +#include "IOStream.h" +#include "Logging.h" +#include "S3Client.h" +#include "decode.h" +#include "encode.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::GetObject(const std::string& rObjectURI) +// Purpose: Retrieve the object with the specified URI (key) +// from your S3 bucket. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::GetObject(const std::string& rObjectURI) +{ + return FinishAndSendRequest(HTTPRequest::Method_GET, 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 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::PutObject(const std::string& rObjectURI, + IOStream& rStreamToSend, const char* pContentType) +{ + return FinishAndSendRequest(HTTPRequest::Method_PUT, rObjectURI, + &rStreamToSend, pContentType); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::FinishAndSendRequest( +// HTTPRequest::Method Method, +// const std::string& rRequestURI, +// IOStream* pStreamToSend, +// const char* pStreamContentType) +// Purpose: Internal method which creates an HTTP request to S3, +// populates the date and authorization header fields, +// and sends it to S3 (or the simulator), attaching +// the specified stream if any to the request. Opens a +// 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 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method, + const std::string& rRequestURI, IOStream* pStreamToSend, + const char* pStreamContentType) +{ + HTTPRequest request(Method, rRequestURI); + request.SetHostName(mHostName); + + std::ostringstream date; + time_t tt = time(NULL); + struct tm *tp = gmtime(&tt); + if (!tp) + { + BOX_ERROR("Failed to get current time"); + THROW_EXCEPTION(HTTPException, Internal); + } + const char *dow[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; + date << dow[tp->tm_wday] << ", "; + const char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + date << std::internal << std::setfill('0') << + std::setw(2) << tp->tm_mday << " " << + month[tp->tm_mon] << " " << + (tp->tm_year + 1900) << " "; + date << std::setw(2) << tp->tm_hour << ":" << + std::setw(2) << tp->tm_min << ":" << + std::setw(2) << tp->tm_sec << " GMT"; + request.AddHeader("Date", date.str()); + + if (pStreamContentType) + { + request.AddHeader("Content-Type", pStreamContentType); + } + + std::string s3suffix = ".s3.amazonaws.com"; + std::string bucket; + if (mHostName.size() > s3suffix.size()) + { + std::string suffix = mHostName.substr(mHostName.size() - + s3suffix.size(), s3suffix.size()); + if (suffix == s3suffix) + { + bucket = mHostName.substr(0, mHostName.size() - + 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(); + + unsigned char digest_buffer[EVP_MAX_MD_SIZE]; + unsigned int digest_size = sizeof(digest_buffer); + /* unsigned char* mac = */ HMAC(EVP_sha1(), + mSecretKey.c_str(), mSecretKey.size(), + (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); + + if (auth_code[auth_code.size() - 1] == '\n') + { + auth_code = auth_code.substr(0, auth_code.size() - 1); + } + + request.AddHeader("Authorization", auth_code); + + if (mpSimulator) + { + if (pStreamToSend) + { + pStreamToSend->CopyStreamTo(request); + } + + request.SetForReading(); + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + mpSimulator->Handle(request, response); + return response; + } + else + { + try + { + if (!mapClientSocket.get()) + { + mapClientSocket.reset(new SocketStream()); + mapClientSocket->Open(Socket::TypeINET, + mHostName, mPort); + } + return SendRequest(request, pStreamToSend, + pStreamContentType); + } + catch (ConnectionException &ce) + { + if (ce.GetType() == ConnectionException::SocketWriteError) + { + // server may have disconnected us, + // try to reconnect, just once + mapClientSocket->Open(Socket::TypeINET, + mHostName, mPort); + return SendRequest(request, pStreamToSend, + pStreamContentType); + } + else + { + throw; + } + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::SendRequest(HTTPRequest& rRequest, +// IOStream* pStreamToSend, +// const char* pStreamContentType) +// Purpose: Internal method which sends a pre-existing HTTP +// request to S3. Attaches the specified stream if any +// to the request. Opens a 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 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::SendRequest(HTTPRequest& rRequest, + IOStream* pStreamToSend, const char* pStreamContentType) +{ + HTTPResponse response; + + if (pStreamToSend) + { + rRequest.SendWithStream(*mapClientSocket, + 30000 /* milliseconds */, + pStreamToSend, response); + } + else + { + rRequest.Send(*mapClientSocket, 30000 /* milliseconds */); + response.Receive(*mapClientSocket, 30000 /* milliseconds */); + } + + return response; +} diff --git a/lib/httpserver/S3Client.h b/lib/httpserver/S3Client.h new file mode 100644 index 00000000..3c4126ac --- /dev/null +++ b/lib/httpserver/S3Client.h @@ -0,0 +1,72 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.h +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#ifndef S3CLIENT__H +#define S3CLIENT__H + +#include <string> +#include <map> + +#include "HTTPRequest.h" +#include "SocketStream.h" + +class HTTPResponse; +class HTTPServer; +class IOStream; + +// -------------------------------------------------------------------------- +// +// Class +// Name: S3Client +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- +class S3Client +{ + public: + S3Client(HTTPServer* pSimulator, const std::string& rHostName, + const std::string& rAccessKey, const std::string& rSecretKey) + : mpSimulator(pSimulator), + mHostName(rHostName), + mAccessKey(rAccessKey), + mSecretKey(rSecretKey) + { } + + S3Client(std::string HostName, int Port, const std::string& rAccessKey, + const std::string& rSecretKey) + : mpSimulator(NULL), + mHostName(HostName), + mPort(Port), + mAccessKey(rAccessKey), + mSecretKey(rSecretKey) + { } + + HTTPResponse GetObject(const std::string& rObjectURI); + HTTPResponse PutObject(const std::string& rObjectURI, + IOStream& rStreamToSend, const char* pContentType = NULL); + + private: + HTTPServer* mpSimulator; + std::string mHostName; + int mPort; + std::auto_ptr<SocketStream> mapClientSocket; + std::string mAccessKey, mSecretKey; + + HTTPResponse FinishAndSendRequest(HTTPRequest::Method Method, + const std::string& rRequestURI, + IOStream* pStreamToSend = NULL, + const char* pStreamContentType = NULL); + HTTPResponse SendRequest(HTTPRequest& rRequest, + IOStream* pStreamToSend = NULL, + const char* pStreamContentType = NULL); +}; + +#endif // S3CLIENT__H + diff --git a/test/httpserver/testhttpserver.cpp b/test/httpserver/testhttpserver.cpp index 5b608591..421c9626 100644 --- a/test/httpserver/testhttpserver.cpp +++ b/test/httpserver/testhttpserver.cpp @@ -20,6 +20,7 @@ #include "HTTPResponse.h" #include "HTTPServer.h" #include "IOStreamGetLine.h" +#include "S3Client.h" #include "ServerControl.h" #include "Test.h" #include "decode.h" @@ -326,203 +327,6 @@ void S3Simulator::HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse) rResponse.SetResponseCode(HTTPResponse::Code_OK); } -class S3Client -{ - public: - S3Client(S3Simulator* pSimulator, const std::string& rHostName, - const std::string& rAccessKey, const std::string& rSecretKey) - : mpSimulator(pSimulator), - mHostName(rHostName), - mAccessKey(rAccessKey), - mSecretKey(rSecretKey) - { } - - S3Client(std::string HostName, int Port, const std::string& rAccessKey, - const std::string& rSecretKey) - : mpSimulator(NULL), - mHostName(HostName), - mPort(Port), - mAccessKey(rAccessKey), - mSecretKey(rSecretKey) - { } - - HTTPResponse GetObject(const std::string& rObjectURI); - HTTPResponse PutObject(const std::string& rObjectURI, - IOStream& rStreamToSend, const char* pContentType = NULL); - - private: - S3Simulator* mpSimulator; - std::string mHostName; - int mPort; - std::auto_ptr<SocketStream> mapClientSocket; - std::string mAccessKey, mSecretKey; - - HTTPResponse FinishAndSendRequest(HTTPRequest::Method Method, - const std::string& rRequestURI, - IOStream* pStreamToSend = NULL, - const char* pStreamContentType = NULL); - HTTPResponse SendRequest(HTTPRequest& rRequest, - IOStream* pStreamToSend = NULL, - const char* pStreamContentType = NULL); -}; - -HTTPResponse S3Client::GetObject(const std::string& rObjectURI) -{ - return FinishAndSendRequest(HTTPRequest::Method_GET, rObjectURI); -} - -HTTPResponse S3Client::PutObject(const std::string& rObjectURI, - IOStream& rStreamToSend, const char* pContentType) -{ - return FinishAndSendRequest(HTTPRequest::Method_PUT, rObjectURI, - &rStreamToSend, pContentType); -} - -HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method, - const std::string& rRequestURI, IOStream* pStreamToSend, - const char* pStreamContentType) -{ - HTTPRequest request(Method, rRequestURI); - request.SetHostName(mHostName); - - std::ostringstream date; - time_t tt = time(NULL); - struct tm *tp = gmtime(&tt); - if (!tp) - { - BOX_ERROR("Failed to get current time"); - THROW_EXCEPTION(HTTPException, Internal); - } - const char *dow[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; - date << dow[tp->tm_wday] << ", "; - const char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", - "Jul","Aug","Sep","Oct","Nov","Dec"}; - date << std::internal << std::setfill('0') << - std::setw(2) << tp->tm_mday << " " << - month[tp->tm_mon] << " " << - (tp->tm_year + 1900) << " "; - date << std::setw(2) << tp->tm_hour << ":" << - std::setw(2) << tp->tm_min << ":" << - std::setw(2) << tp->tm_sec << " GMT"; - request.AddHeader("Date", date.str()); - - if (pStreamContentType) - { - request.AddHeader("Content-Type", pStreamContentType); - } - - std::string s3suffix = ".s3.amazonaws.com"; - std::string bucket; - if (mHostName.size() > s3suffix.size()) - { - std::string suffix = mHostName.substr(mHostName.size() - - s3suffix.size(), s3suffix.size()); - if (suffix == s3suffix) - { - bucket = mHostName.substr(0, mHostName.size() - - 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(); - - unsigned char digest_buffer[EVP_MAX_MD_SIZE]; - unsigned int digest_size = sizeof(digest_buffer); - /* unsigned char* mac = */ HMAC(EVP_sha1(), - mSecretKey.c_str(), mSecretKey.size(), - (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); - - if (auth_code[auth_code.size() - 1] == '\n') - { - auth_code = auth_code.substr(0, auth_code.size() - 1); - } - - request.AddHeader("Authorization", auth_code); - - if (mpSimulator) - { - if (pStreamToSend) - { - pStreamToSend->CopyStreamTo(request); - } - - request.SetForReading(); - CollectInBufferStream response_buffer; - HTTPResponse response(&response_buffer); - - mpSimulator->Handle(request, response); - return response; - } - else - { - try - { - if (!mapClientSocket.get()) - { - mapClientSocket.reset(new SocketStream()); - mapClientSocket->Open(Socket::TypeINET, - mHostName, mPort); - } - return SendRequest(request, pStreamToSend, - pStreamContentType); - } - catch (ConnectionException &ce) - { - if (ce.GetType() == ConnectionException::SocketWriteError) - { - // server may have disconnected us, - // try to reconnect, just once - mapClientSocket->Open(Socket::TypeINET, - mHostName, mPort); - return SendRequest(request, pStreamToSend, - pStreamContentType); - } - else - { - throw; - } - } - } -} - -HTTPResponse S3Client::SendRequest(HTTPRequest& rRequest, - IOStream* pStreamToSend, const char* pStreamContentType) -{ - HTTPResponse response; - - if (pStreamToSend) - { - rRequest.SendWithStream(*mapClientSocket, - 30000 /* milliseconds */, - pStreamToSend, response); - } - else - { - rRequest.Send(*mapClientSocket, 30000 /* milliseconds */); - response.Receive(*mapClientSocket, 30000 /* milliseconds */); - } - - return response; -} - int test(int argc, const char *argv[]) { if(argc >= 2 && ::strcmp(argv[1], "server") == 0) |