3 Copyright (c) 2007, Arvid Norberg
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in
14 the documentation and/or other materials provided with the distribution.
15 * Neither the name of the author nor the names of its
16 contributors may be used to endorse or promote products derived
17 from this software without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
33 #include "libtorrent/http_connection.hpp"
35 #include <boost/bind.hpp>
36 #include <boost/lexical_cast.hpp>
37 #include <asio/ip/tcp.hpp>
45 void http_connection::get(std::string
const& url
, time_duration timeout
46 , bool handle_redirect
)
48 m_redirect
= handle_redirect
;
54 boost::tie(protocol
, auth
, hostname
, port
, path
) = parse_url_components(url
);
55 std::stringstream headers
;
56 headers
<< "GET " << path
<< " HTTP/1.0\r\n"
57 "Host:" << hostname
<<
58 "\r\nConnection: close\r\n";
60 headers
<< "Authorization: Basic " << base64encode(auth
) << "\r\n";
62 sendbuffer
= headers
.str();
63 start(hostname
, boost::lexical_cast
<std::string
>(port
), timeout
);
66 void http_connection::start(std::string
const& hostname
, std::string
const& port
67 , time_duration timeout
, bool handle_redirect
)
69 m_redirect
= handle_redirect
;
71 m_timer
.expires_from_now(m_timeout
);
72 m_timer
.async_wait(bind(&http_connection::on_timeout
73 , boost::weak_ptr
<http_connection
>(shared_from_this()), _1
));
75 if (m_sock
.is_open() && m_hostname
== hostname
&& m_port
== port
)
78 asio::async_write(m_sock
, asio::buffer(sendbuffer
)
79 , bind(&http_connection::on_write
, shared_from_this(), _1
));
84 tcp::resolver::query
query(hostname
, port
);
85 m_resolver
.async_resolve(query
, bind(&http_connection::on_resolve
86 , shared_from_this(), _1
, _2
));
87 m_hostname
= hostname
;
92 void http_connection::on_connect_timeout()
94 if (m_connection_ticket
> -1) m_cc
.done(m_connection_ticket
);
95 m_connection_ticket
= -1;
97 if (m_bottled
&& m_called
) return;
99 m_handler(asio::error::timed_out
, m_parser
, 0, 0);
103 void http_connection::on_timeout(boost::weak_ptr
<http_connection
> p
104 , asio::error_code
const& e
)
106 boost::shared_ptr
<http_connection
> c
= p
.lock();
108 if (c
->m_connection_ticket
> -1) c
->m_cc
.done(c
->m_connection_ticket
);
109 c
->m_connection_ticket
= -1;
111 if (e
== asio::error::operation_aborted
) return;
113 if (c
->m_bottled
&& c
->m_called
) return;
115 if (c
->m_last_receive
+ c
->m_timeout
< time_now())
118 c
->m_handler(asio::error::timed_out
, c
->m_parser
, 0, 0);
122 c
->m_timer
.expires_at(c
->m_last_receive
+ c
->m_timeout
);
123 c
->m_timer
.async_wait(bind(&http_connection::on_timeout
, p
, _1
));
126 void http_connection::close()
129 m_limiter_timer
.cancel();
134 if (m_connection_ticket
> -1) m_cc
.done(m_connection_ticket
);
135 m_connection_ticket
= -1;
138 void http_connection::on_resolve(asio::error_code
const& e
139 , tcp::resolver::iterator i
)
144 if (m_bottled
&& m_called
) return;
146 m_handler(e
, m_parser
, 0, 0);
149 assert(i
!= tcp::resolver::iterator());
150 m_cc
.enqueue(bind(&http_connection::connect
, shared_from_this(), _1
, *i
)
151 , bind(&http_connection::on_connect_timeout
, shared_from_this())
155 void http_connection::connect(int ticket
, tcp::endpoint target_address
)
157 m_connection_ticket
= ticket
;
158 m_sock
.async_connect(target_address
, boost::bind(&http_connection::on_connect
159 , shared_from_this(), _1
/*, ++i*/));
162 void http_connection::on_connect(asio::error_code
const& e
163 /*, tcp::resolver::iterator i*/)
167 m_last_receive
= time_now();
168 asio::async_write(m_sock
, asio::buffer(sendbuffer
)
169 , bind(&http_connection::on_write
, shared_from_this(), _1
));
171 /* else if (i != tcp::resolver::iterator())
173 // The connection failed. Try the next endpoint in the list.
175 m_cc.enqueue(bind(&http_connection::connect, shared_from_this(), _1, *i)
176 , bind(&http_connection::on_connect_timeout, shared_from_this())
182 if (m_bottled
&& m_called
) return;
184 m_handler(e
, m_parser
, 0, 0);
188 void http_connection::on_write(asio::error_code
const& e
)
193 if (m_bottled
&& m_called
) return;
195 m_handler(e
, m_parser
, 0, 0);
199 std::string().swap(sendbuffer
);
200 m_recvbuffer
.resize(4096);
202 int amount_to_read
= m_recvbuffer
.size() - m_read_pos
;
203 if (m_rate_limit
> 0 && amount_to_read
> m_download_quota
)
205 amount_to_read
= m_download_quota
;
206 if (m_download_quota
== 0)
208 if (!m_limiter_timer_active
)
209 on_assign_bandwidth(asio::error_code());
213 m_sock
.async_read_some(asio::buffer(&m_recvbuffer
[0] + m_read_pos
215 , bind(&http_connection::on_read
216 , shared_from_this(), _1
, _2
));
219 void http_connection::on_read(asio::error_code
const& e
220 , std::size_t bytes_transferred
)
224 m_download_quota
-= bytes_transferred
;
225 assert(m_download_quota
>= 0);
228 if (e
== asio::error::eof
)
231 if (m_bottled
&& m_called
) return;
233 char const* data
= 0;
234 std::size_t size
= 0;
237 data
= m_parser
.get_body().begin
;
238 size
= m_parser
.get_body().left();
240 m_handler(asio::error_code(), m_parser
, data
, size
);
247 if (m_bottled
&& m_called
) return;
249 m_handler(e
, m_parser
, 0, 0);
253 m_read_pos
+= bytes_transferred
;
254 assert(m_read_pos
<= int(m_recvbuffer
.size()));
256 // having a nonempty path means we should handle redirects
257 if (m_redirect
&& m_parser
.header_finished())
259 int code
= m_parser
.status_code();
260 if (code
>= 300 && code
< 400)
262 // attempt a redirect
263 std::string url
= m_parser
.header
<std::string
>("location");
266 // missing location header
267 if (m_bottled
&& m_called
) return;
269 m_handler(e
, m_parser
, 0, 0);
273 m_limiter_timer_active
= false;
283 if (m_bottled
|| !m_parser
.header_finished())
285 libtorrent::buffer::const_interval
rcv_buf(&m_recvbuffer
[0]
286 , &m_recvbuffer
[0] + m_read_pos
);
287 m_parser
.incoming(rcv_buf
);
288 if (!m_bottled
&& m_parser
.header_finished())
290 if (m_read_pos
> m_parser
.body_start())
291 m_handler(e
, m_parser
, &m_recvbuffer
[0] + m_parser
.body_start()
292 , m_read_pos
- m_parser
.body_start());
294 m_last_receive
= time_now();
296 else if (m_bottled
&& m_parser
.finished())
299 if (m_bottled
&& m_called
) return;
301 m_handler(e
, m_parser
, m_parser
.get_body().begin
, m_parser
.get_body().left());
307 m_handler(e
, m_parser
, &m_recvbuffer
[0], m_read_pos
);
309 m_last_receive
= time_now();
312 if (int(m_recvbuffer
.size()) == m_read_pos
)
313 m_recvbuffer
.resize((std::min
)(m_read_pos
+ 2048, 1024*500));
314 if (m_read_pos
== 1024 * 500)
317 if (m_bottled
&& m_called
) return;
319 m_handler(asio::error::eof
, m_parser
, 0, 0);
322 int amount_to_read
= m_recvbuffer
.size() - m_read_pos
;
323 if (m_rate_limit
> 0 && amount_to_read
> m_download_quota
)
325 amount_to_read
= m_download_quota
;
326 if (m_download_quota
== 0)
328 if (!m_limiter_timer_active
)
329 on_assign_bandwidth(asio::error_code());
333 m_sock
.async_read_some(asio::buffer(&m_recvbuffer
[0] + m_read_pos
335 , bind(&http_connection::on_read
336 , shared_from_this(), _1
, _2
));
339 void http_connection::on_assign_bandwidth(asio::error_code
const& e
)
341 if ((e
== asio::error::operation_aborted
342 && m_limiter_timer_active
)
343 || !m_sock
.is_open())
345 if (!m_bottled
|| !m_called
)
346 m_handler(e
, m_parser
, 0, 0);
349 m_limiter_timer_active
= false;
352 if (m_download_quota
> 0) return;
354 m_download_quota
= m_rate_limit
/ 4;
356 int amount_to_read
= m_recvbuffer
.size() - m_read_pos
;
357 if (amount_to_read
> m_download_quota
)
358 amount_to_read
= m_download_quota
;
360 m_sock
.async_read_some(asio::buffer(&m_recvbuffer
[0] + m_read_pos
362 , bind(&http_connection::on_read
363 , shared_from_this(), _1
, _2
));
365 m_limiter_timer_active
= true;
366 m_limiter_timer
.expires_from_now(milliseconds(250));
367 m_limiter_timer
.async_wait(bind(&http_connection::on_assign_bandwidth
368 , shared_from_this(), _1
));
371 void http_connection::rate_limit(int limit
)
373 if (!m_limiter_timer_active
)
375 m_limiter_timer_active
= true;
376 m_limiter_timer
.expires_from_now(milliseconds(250));
377 m_limiter_timer
.async_wait(bind(&http_connection::on_assign_bandwidth
378 , shared_from_this(), _1
));
380 m_rate_limit
= limit
;