FireBreath  1.4.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Pages
SimpleStreamHelper.cpp
1 /**********************************************************\
2 Original Author: Richard Bateman
3 
4 Created: Jan 24, 2011
5 License: Dual license model; choose one of two:
6  New BSD License
7  http://www.opensource.org/licenses/bsd-license.php
8  - or -
9  GNU Lesser General Public License, version 2.1
10  http://www.gnu.org/licenses/lgpl-2.1.html
11 
12 Copyright 2011 Richard Bateman,
13  Firebreath development team
14 \**********************************************************/
15 
16 #include "BrowserHost.h"
17 #include <boost/algorithm/string.hpp>
18 #include <boost/bind.hpp>
19 #include "precompiled_headers.h" // On windows, everything above this line in PCH
20 
21 #include "BrowserStreamRequest.h"
22 #include "SimpleStreamHelper.h"
23 
24 static const int MEGABYTE = 1024 * 1024;
25 
26 FB::SimpleStreamHelperPtr FB::SimpleStreamHelper::AsyncGet( const FB::BrowserHostPtr& host, const FB::URI& uri,
27  const HttpCallback& callback, bool cache /*= true*/, size_t bufferSize /*= 256*1024*/ )
28 {
29  BrowserStreamRequest req(uri, "GET");
30  req.setCallback(callback);
31  req.setBufferSize(bufferSize);
32  req.setCacheable(cache);
33  return AsyncRequest(host, req);
34 }
35 
36 FB::SimpleStreamHelperPtr FB::SimpleStreamHelper::AsyncPost(const FB::BrowserHostPtr& host,
37  const FB::URI& uri,
38  const std::string& postdata,
39  const HttpCallback& callback,
40  bool cache /*= true*/,
41  size_t bufferSize /*= 256*1024*/ )
42 {
43  BrowserStreamRequest req(uri, "POST");
44  req.setPostData(postdata);
45  req.setCallback(callback);
46  req.setBufferSize(bufferSize);
47  req.setCacheable(cache);
48  return AsyncRequest(host, req);
49 }
50 
51 FB::SimpleStreamHelperPtr FB::SimpleStreamHelper::AsyncRequest( const FB::BrowserHostConstPtr& host,
52  const BrowserStreamRequest& req ) {
53  if (!req.getCallback()) {
54  throw std::runtime_error("Invalid callback");
55  }
56  if (!host->isMainThread()) {
57  // This must be run from the main thread
58  return host->CallOnMainThread(boost::bind(&AsyncRequest, host, req));
59  }
60  FB::BrowserStreamPtr stream(host->createStream(req, false));
61  return AsyncRequest(host, stream, req);
62 }
63 
64 FB::SimpleStreamHelperPtr FB::SimpleStreamHelper::AsyncRequest( const FB::BrowserHostConstPtr& host,
65  const FB::BrowserStreamPtr& stream,
66  const BrowserStreamRequest& req ) {
67  if (!host->isMainThread()) {
68  // This must be run from the main thread
69  return host->CallOnMainThread(boost::bind(&AsyncRequest, host, stream, req));
70  }
71  FB::SimpleStreamHelperPtr ptr(boost::make_shared<FB::SimpleStreamHelper>(req.getCallback(), req.internalBufferSize));
72  // This is kinda a weird trick; it's responsible for freeing itself, unless something decides
73  // to hold a reference to it.
74  ptr->keepReference(ptr);
75  stream->AttachObserver(ptr);
76  return ptr;
77 }
78 
79 struct SyncHTTPHelper
80 {
81 public:
82  SyncHTTPHelper()
83  : done(false) { }
84  void setPtr(const FB::SimpleStreamHelperPtr& inPtr) { ptr = inPtr; }
85 
86  void getURLCallback(bool success, const FB::HeaderMap& headers,
87  const boost::shared_array<uint8_t>& data, const size_t size)
88  {
89  boost::lock_guard<boost::mutex> lock(m_mutex);
90  m_response = boost::make_shared<FB::HttpStreamResponse>(success, headers, data, size);
91  done = true;
92  m_cond.notify_all();
93  }
94  void waitForDone() {
95  boost::unique_lock<boost::mutex> lock(m_mutex);
96  while (!done) {
97  m_cond.wait(lock);
98  }
99  }
100 
101  bool done;
102  FB::SimpleStreamHelperPtr ptr;
103  boost::condition_variable m_cond;
104  boost::mutex m_mutex;
105  FB::HttpStreamResponsePtr m_response;
106 };
107 
108 
109 FB::HttpStreamResponsePtr FB::SimpleStreamHelper::SynchronousRequest( const FB::BrowserHostPtr& host, const BrowserStreamRequest& req )
110 {
111  // We can't ever block on the main thread, so SynchronousGet can't be called from there.
112  // Also, if you could block the main thread, that still wouldn't work because the request
113  // is processed on the main thread!
114  assert(!host->isMainThread());
115  SyncHTTPHelper helper;
116  try {
117  FB::HttpCallback cb(boost::bind(&SyncHTTPHelper::getURLCallback, &helper, _1, _2, _3, _4));
118  FB::BrowserStreamRequest req2(req);
119  req2.setCallback(cb);
120  FB::SimpleStreamHelperPtr ptr = AsyncRequest(host, req2);
121  helper.setPtr(ptr);
122  helper.waitForDone();
123  } catch (const std::exception&) {
124  // If anything weird happens, just return NULL (to indicate failure)
125  return FB::HttpStreamResponsePtr();
126  }
127  return helper.m_response;
128 }
129 
130 FB::HttpStreamResponsePtr FB::SimpleStreamHelper::SynchronousGet( const FB::BrowserHostPtr& host,
131  const FB::URI& uri, const bool cache /*= true*/, const size_t bufferSize /*= 128*1024*/ )
132 {
133  FB::BrowserStreamRequest req(uri, "GET");
134  req.setCacheable(cache);
135  req.setBufferSize(bufferSize);
136  return SynchronousRequest(host, req);
137 }
138 
139 FB::HttpStreamResponsePtr FB::SimpleStreamHelper::SynchronousPost( const FB::BrowserHostPtr& host,
140  const FB::URI& uri, const std::string& postdata, const bool cache /*= true*/, const size_t bufferSize /*= 128*1024*/ )
141 {
142  FB::BrowserStreamRequest req(uri, "POST");
143  req.setCacheable(cache);
144  req.setBufferSize(bufferSize);
145  req.setPostData(postdata);
146  return SynchronousRequest(host, req);
147 }
148 
149 FB::SimpleStreamHelper::SimpleStreamHelper( const HttpCallback& callback, const size_t blockSize )
150  : blockSize(blockSize), received(0), callback(callback)
151 {
152 
153 }
154 
156 {
157  if (!evt->success) {
158  if (callback)
159  callback(false, FB::HeaderMap(), boost::shared_array<uint8_t>(), received);
160  callback.clear();
161  self.reset();
162  return false;
163  }
164  if (!data) {
165  data = boost::shared_array<uint8_t>(new uint8_t[received]);
166  int i = 0;
167  for (BlockList::const_iterator it = blocks.begin();
168  it != blocks.end(); ++it) {
169  size_t offset(i * blockSize);
170  size_t len(received - offset);
171  if (len > blockSize)
172  len = blockSize;
173 
174  std::copy(it->get(), it->get()+len, data.get()+offset);
175  ++i;
176  }
177  // Free all the old blocks
178  blocks.clear();
179  }
180  if (callback && stream) {
181  std::multimap<std::string, std::string> headers;
182  headers = parse_http_headers(stream->getHeaders());
183  callback(true, headers, data, received);
184  }
185  callback.clear();
186  self.reset();
187  return false; // Always return false to make sure the browserhost knows to let go of the object
188 }
189 
190 bool FB::SimpleStreamHelper::onStreamOpened( FB::StreamOpenedEvent *evt, FB::BrowserStream * )
191 {
192  // We can't reliably find the actual length, so we won't try
193  return false;
194 }
195 
196 bool FB::SimpleStreamHelper::onStreamDataArrived( FB::StreamDataArrivedEvent *evt, FB::BrowserStream * )
197 {
198  received += evt->getLength();
199  const uint8_t* buf = reinterpret_cast<const uint8_t*>(evt->getData());
200  const uint8_t* endbuf = buf + evt->getLength();
201 
202  int len = evt->getLength();
203  int offset = evt->getDataPosition();
204  while (buf < endbuf) {
205  size_t n = offset / blockSize;
206  size_t pos = offset % blockSize;
207  if (blocks.size() < n+1) {
208  blocks.push_back(boost::shared_array<uint8_t>(new uint8_t[blockSize]));
209  }
210  uint8_t *destBuf = blocks.back().get();
211  //if (pos + len > )
212  int curLen = len;
213  if (pos + len >= blockSize) {
214  // If there isn't room in the current block, copy what there is room for
215  // and loop
216  curLen = blockSize-pos;
217  }
218  // Copy the bytes that fit in this buffer
219  std::copy(buf, buf+curLen, destBuf+pos);
220  buf += curLen;
221  offset += curLen;
222  len -= curLen;
223  }
224  return false;
225 }
226 
227 FB::HeaderMap FB::SimpleStreamHelper::parse_http_headers(const std::string& headers )
228 {
229  FB::HeaderMap res;
230  std::vector<std::string> lines;
231  boost::split(lines, headers, boost::is_any_of("\r\n"));
232  for (std::vector<std::string>::const_iterator it = lines.begin(); it != lines.end(); ++it) {
233  std::string line = boost::trim_copy(*it);
234  if (line.empty()) continue;
235  size_t loc = line.find(':');
236  if (loc == std::string::npos) {
237  // Weird; bad header
238  continue;
239  }
240  res.insert(std::make_pair(boost::trim_copy(line.substr(0, loc)),
241  boost::trim_copy(line.substr(loc + 1))));
242  }
243  return res;
244 }
245 
246 void FB::SimpleStreamHelper::keepReference( const SimpleStreamHelperPtr& ptr )
247 {
248  self = ptr;
249 }
250 
static FB::SimpleStreamHelperPtr AsyncRequest(const BrowserHostConstPtr &host, const BrowserStreamRequest &req)
Creates an asynchronous HTTP request from the provided BrowserStreamRequest.
void setBufferSize(size_t size)
Call this to indicate the preferred internal buffer size of the BrowserStream object.
void setCallback(const HttpCallback &cb)
Call this method to provide a callback function that should be called when the request completes...
This is the abstract base class (interface class) for a browser stream.
Definition: BrowserStream.h:41
static FB::SimpleStreamHelperPtr AsyncPost(const FB::BrowserHostPtr &host, const FB::URI &uri, const std::string &postdata, const HttpCallback &callback, bool cache=true, size_t bufferSize=128 *1024)
Starts an asynchronous HTTP post request.
Information about an HTTP request to be made.
virtual bool onStreamDataArrived(FB::StreamDataArrivedEvent *evt, FB::BrowserStream *)
Called when data arrives.
Data structure for dealing with URI strings.
Definition: URI.h:42
static HttpStreamResponsePtr SynchronousRequest(const FB::BrowserHostPtr &host, const BrowserStreamRequest &req)
Do not call from the main thread! Starts a Synchronous HTTP request.
static FB::SimpleStreamHelperPtr AsyncGet(const FB::BrowserHostPtr &host, const FB::URI &uri, const HttpCallback &callback, bool cache=true, size_t bufferSize=128 *1024)
Starts an asynchronous HTTP get request.
boost::shared_ptr< FB::BrowserHost > BrowserHostPtr
Defines an alias representing a BrowserHost shared_ptr (you should never use a BrowserHost* directly)...
Definition: APITypes.h:117
virtual std::string getHeaders() const
Returns the http headers.
HttpCallback getCallback() const
Returns the HttpCallback functor assigned to the object, or a NULL HttpCallback if none...
static HttpStreamResponsePtr SynchronousGet(const FB::BrowserHostPtr &host, const FB::URI &uri, const bool cache=true, const size_t bufferSize=128 *1024)
Do not call from the main thread! Starts a Synchronous HTTP get request.
static HttpStreamResponsePtr SynchronousPost(const FB::BrowserHostPtr &host, const FB::URI &uri, const std::string &postdata, const bool cache=true, const size_t bufferSize=128 *1024)
Do not call from the main thread! Starts a Synchronous HTTP POST request.
virtual bool onStreamOpened(FB::StreamOpenedEvent *evt, FB::BrowserStream *)
Called when the stream was opened successfully.
This event is fired when a stream has completed downloading.
Definition: StreamEvents.h:148
void setCacheable(bool c)
Call with true to indicate that the browser's cache may be used for this request; default is false...
virtual bool onStreamCompleted(FB::StreamCompletedEvent *evt, FB::BrowserStream *)
Called when the stream finished downloading successfully.