source: box/trunk/lib/httpserver/S3Client.cpp @ 2447

Revision 2447, 6.6 KB checked in by chris, 3 years ago (diff)

Move S3Client class into its own files for public access.

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-c++src
Line 
1// --------------------------------------------------------------------------
2//
3// File
4//              Name:    S3Client.cpp
5//              Purpose: Amazon S3 client helper implementation class
6//              Created: 09/01/2009
7//
8// --------------------------------------------------------------------------
9
10#include "Box.h"
11
12#include <cstring>
13
14// #include <cstdio>
15// #include <ctime>
16
17#include <openssl/hmac.h>
18
19#include "HTTPRequest.h"
20#include "HTTPResponse.h"
21#include "HTTPServer.h"
22#include "autogen_HTTPException.h"
23#include "IOStream.h"
24#include "Logging.h"
25#include "S3Client.h"
26#include "decode.h"
27#include "encode.h"
28
29#include "MemLeakFindOn.h"
30
31// --------------------------------------------------------------------------
32//
33// Function
34//              Name:    S3Client::GetObject(const std::string& rObjectURI)
35//              Purpose: Retrieve the object with the specified URI (key)
36//                       from your S3 bucket.
37//              Created: 09/01/09
38//
39// --------------------------------------------------------------------------
40
41HTTPResponse S3Client::GetObject(const std::string& rObjectURI)
42{
43        return FinishAndSendRequest(HTTPRequest::Method_GET, rObjectURI);
44}
45
46// --------------------------------------------------------------------------
47//
48// Function
49//              Name:    S3Client::PutObject(const std::string& rObjectURI,
50//                       IOStream& rStreamToSend, const char* pContentType)
51//              Purpose: Upload the stream to S3, creating or overwriting the
52//                       object with the specified URI (key) in your S3
53//                       bucket.
54//              Created: 09/01/09
55//
56// --------------------------------------------------------------------------
57
58HTTPResponse S3Client::PutObject(const std::string& rObjectURI,
59        IOStream& rStreamToSend, const char* pContentType)
60{
61        return FinishAndSendRequest(HTTPRequest::Method_PUT, rObjectURI,
62                &rStreamToSend, pContentType);
63}
64
65// --------------------------------------------------------------------------
66//
67// Function
68//              Name:    S3Client::FinishAndSendRequest(
69//                       HTTPRequest::Method Method,
70//                       const std::string& rRequestURI,
71//                       IOStream* pStreamToSend,
72//                       const char* pStreamContentType)
73//              Purpose: Internal method which creates an HTTP request to S3,
74//                       populates the date and authorization header fields,
75//                       and sends it to S3 (or the simulator), attaching
76//                       the specified stream if any to the request. Opens a
77//                       connection to the server if necessary, which may
78//                       throw a ConnectionException. Returns the HTTP
79//                       response returned by S3, which may be a 500 error.
80//              Created: 09/01/09
81//
82// --------------------------------------------------------------------------
83
84HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method,
85        const std::string& rRequestURI, IOStream* pStreamToSend,
86        const char* pStreamContentType)
87{
88        HTTPRequest request(Method, rRequestURI);
89        request.SetHostName(mHostName);
90       
91        std::ostringstream date;
92        time_t tt = time(NULL);
93        struct tm *tp = gmtime(&tt);
94        if (!tp)
95        {
96                BOX_ERROR("Failed to get current time");
97                THROW_EXCEPTION(HTTPException, Internal);
98        }
99        const char *dow[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
100        date << dow[tp->tm_wday] << ", ";
101        const char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
102                "Jul","Aug","Sep","Oct","Nov","Dec"};
103        date << std::internal << std::setfill('0') <<
104                std::setw(2) << tp->tm_mday << " " <<
105                month[tp->tm_mon] << " " <<
106                (tp->tm_year + 1900) << " ";
107        date << std::setw(2) << tp->tm_hour << ":" <<
108                std::setw(2) << tp->tm_min  << ":" <<
109                std::setw(2) << tp->tm_sec  << " GMT";
110        request.AddHeader("Date", date.str());
111
112        if (pStreamContentType)
113        {
114                request.AddHeader("Content-Type", pStreamContentType);
115        }
116       
117        std::string s3suffix = ".s3.amazonaws.com";
118        std::string bucket;
119        if (mHostName.size() > s3suffix.size())
120        {
121                std::string suffix = mHostName.substr(mHostName.size() -
122                        s3suffix.size(), s3suffix.size());
123                if (suffix == s3suffix)
124                {
125                        bucket = mHostName.substr(0, mHostName.size() -
126                                s3suffix.size());
127                }
128        }
129       
130        std::ostringstream data;
131        data << request.GetVerb() << "\n";
132        data << "\n"; /* Content-MD5 */
133        data << request.GetContentType() << "\n";
134        data << date.str() << "\n";
135               
136        if (! bucket.empty())
137        {
138                data << "/" << bucket;
139        }
140       
141        data << request.GetRequestURI();
142        std::string data_string = data.str();
143
144        unsigned char digest_buffer[EVP_MAX_MD_SIZE];
145        unsigned int digest_size = sizeof(digest_buffer);
146        /* unsigned char* mac = */ HMAC(EVP_sha1(),
147                mSecretKey.c_str(), mSecretKey.size(),
148                (const unsigned char*)data_string.c_str(),
149                data_string.size(), digest_buffer, &digest_size);
150        std::string digest((const char *)digest_buffer, digest_size);
151       
152        base64::encoder encoder;
153        std::string auth_code = "AWS " + mAccessKey + ":" +
154                encoder.encode(digest);
155
156        if (auth_code[auth_code.size() - 1] == '\n')
157        {
158                auth_code = auth_code.substr(0, auth_code.size() - 1);
159        }
160
161        request.AddHeader("Authorization", auth_code);
162       
163        if (mpSimulator)
164        {
165                if (pStreamToSend)
166                {
167                        pStreamToSend->CopyStreamTo(request);
168                }
169
170                request.SetForReading();
171                CollectInBufferStream response_buffer;
172                HTTPResponse response(&response_buffer);
173       
174                mpSimulator->Handle(request, response);
175                return response;
176        }
177        else
178        {
179                try
180                {
181                        if (!mapClientSocket.get())
182                        {
183                                mapClientSocket.reset(new SocketStream());
184                                mapClientSocket->Open(Socket::TypeINET,
185                                        mHostName, mPort);
186                        }
187                        return SendRequest(request, pStreamToSend,
188                                pStreamContentType);
189                }
190                catch (ConnectionException &ce)
191                {
192                        if (ce.GetType() == ConnectionException::SocketWriteError)
193                        {
194                                // server may have disconnected us,
195                                // try to reconnect, just once
196                                mapClientSocket->Open(Socket::TypeINET,
197                                        mHostName, mPort);
198                                return SendRequest(request, pStreamToSend,
199                                        pStreamContentType);
200                        }
201                        else
202                        {
203                                throw;
204                        }
205                }
206        }
207}
208
209// --------------------------------------------------------------------------
210//
211// Function
212//              Name:    S3Client::SendRequest(HTTPRequest& rRequest,
213//                       IOStream* pStreamToSend,
214//                       const char* pStreamContentType)
215//              Purpose: Internal method which sends a pre-existing HTTP
216//                       request to S3. Attaches the specified stream if any
217//                       to the request. Opens a connection to the server if
218//                       necessary, which may throw a ConnectionException.
219//                       Returns the HTTP response returned by S3, which may
220//                       be a 500 error.
221//              Created: 09/01/09
222//
223// --------------------------------------------------------------------------
224
225HTTPResponse S3Client::SendRequest(HTTPRequest& rRequest,
226        IOStream* pStreamToSend, const char* pStreamContentType)
227{               
228        HTTPResponse response;
229       
230        if (pStreamToSend)
231        {
232                rRequest.SendWithStream(*mapClientSocket,
233                        30000 /* milliseconds */,
234                        pStreamToSend, response);
235        }
236        else
237        {
238                rRequest.Send(*mapClientSocket, 30000 /* milliseconds */);
239                response.Receive(*mapClientSocket, 30000 /* milliseconds */);
240        }
241               
242        return response;
243}       
Note: See TracBrowser for help on using the repository browser.