~ubuntu-branches/ubuntu/maverick/libtorrent-rasterbar/maverick

« back to all changes in this revision

Viewing changes to src/http_connection.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Cristian Greco
  • Date: 2008-07-02 10:46:21 UTC
  • Revision ID: james.westby@ubuntu.com-20080702104621-jzx3pfke9lkcxfxn
Tags: upstream-0.13.1
ImportĀ upstreamĀ versionĀ 0.13.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 
 
3
Copyright (c) 2007, Arvid Norberg
 
4
All rights reserved.
 
5
 
 
6
Redistribution and use in source and binary forms, with or without
 
7
modification, are permitted provided that the following conditions
 
8
are met:
 
9
 
 
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.
 
18
 
 
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.
 
30
 
 
31
*/
 
32
 
 
33
#include "libtorrent/http_connection.hpp"
 
34
 
 
35
#include <boost/bind.hpp>
 
36
#include <boost/lexical_cast.hpp>
 
37
#include <asio/ip/tcp.hpp>
 
38
#include <string>
 
39
 
 
40
using boost::bind;
 
41
 
 
42
namespace libtorrent
 
43
{
 
44
 
 
45
        enum { max_bottled_buffer = 1024 * 1024 };
 
46
 
 
47
void http_connection::get(std::string const& url, time_duration timeout
 
48
        , int handle_redirects)
 
49
{
 
50
        std::string protocol;
 
51
        std::string auth;
 
52
        std::string hostname;
 
53
        std::string path;
 
54
        int port;
 
55
        boost::tie(protocol, auth, hostname, port, path) = parse_url_components(url);
 
56
        std::stringstream headers;
 
57
        headers << "GET " << path << " HTTP/1.0\r\n"
 
58
                "Host:" << hostname <<
 
59
                "\r\nConnection: close\r\n";
 
60
        if (!auth.empty())
 
61
                headers << "Authorization: Basic " << base64encode(auth) << "\r\n";
 
62
        headers << "\r\n";
 
63
        sendbuffer = headers.str();
 
64
        m_url = url;
 
65
        start(hostname, boost::lexical_cast<std::string>(port), timeout, handle_redirects);
 
66
}
 
67
 
 
68
void http_connection::start(std::string const& hostname, std::string const& port
 
69
        , time_duration timeout, int handle_redirects)
 
70
{
 
71
        m_redirects = handle_redirects;
 
72
        m_timeout = timeout;
 
73
        m_timer.expires_from_now(m_timeout);
 
74
        m_timer.async_wait(bind(&http_connection::on_timeout
 
75
                , boost::weak_ptr<http_connection>(shared_from_this()), _1));
 
76
        m_called = false;
 
77
        m_parser.reset();
 
78
        m_recvbuffer.clear();
 
79
        m_read_pos = 0;
 
80
        if (m_sock.is_open() && m_hostname == hostname && m_port == port)
 
81
        {
 
82
                asio::async_write(m_sock, asio::buffer(sendbuffer)
 
83
                        , bind(&http_connection::on_write, shared_from_this(), _1));
 
84
        }
 
85
        else
 
86
        {
 
87
                m_sock.close();
 
88
                tcp::resolver::query query(hostname, port);
 
89
                m_resolver.async_resolve(query, bind(&http_connection::on_resolve
 
90
                        , shared_from_this(), _1, _2));
 
91
                m_hostname = hostname;
 
92
                m_port = port;
 
93
        }
 
94
}
 
95
 
 
96
void http_connection::on_connect_timeout()
 
97
{
 
98
        if (m_connection_ticket > -1) m_cc.done(m_connection_ticket);
 
99
        m_connection_ticket = -1;
 
100
 
 
101
        callback(asio::error::timed_out);
 
102
        close();
 
103
}
 
104
 
 
105
void http_connection::on_timeout(boost::weak_ptr<http_connection> p
 
106
        , asio::error_code const& e)
 
107
{
 
108
        boost::shared_ptr<http_connection> c = p.lock();
 
109
        if (!c) return;
 
110
        if (c->m_connection_ticket > -1) c->m_cc.done(c->m_connection_ticket);
 
111
        c->m_connection_ticket = -1;
 
112
 
 
113
        if (e == asio::error::operation_aborted) return;
 
114
 
 
115
        if (c->m_last_receive + c->m_timeout < time_now())
 
116
        {
 
117
                c->callback(asio::error::timed_out);
 
118
                c->close();
 
119
                return;
 
120
        }
 
121
 
 
122
        if (!c->m_sock.is_open()) return;
 
123
 
 
124
        c->m_timer.expires_at(c->m_last_receive + c->m_timeout);
 
125
        c->m_timer.async_wait(bind(&http_connection::on_timeout, p, _1));
 
126
}
 
127
 
 
128
void http_connection::close()
 
129
{
 
130
        m_timer.cancel();
 
131
        m_limiter_timer.cancel();
 
132
        m_sock.close();
 
133
        m_hostname.clear();
 
134
        m_port.clear();
 
135
 
 
136
        if (m_connection_ticket > -1) m_cc.done(m_connection_ticket);
 
137
        m_connection_ticket = -1;
 
138
 
 
139
        m_handler.clear();
 
140
}
 
141
 
 
142
void http_connection::on_resolve(asio::error_code const& e
 
143
                , tcp::resolver::iterator i)
 
144
{
 
145
        if (e)
 
146
        {
 
147
                callback(e);
 
148
                close();
 
149
                return;
 
150
        }
 
151
        TORRENT_ASSERT(i != tcp::resolver::iterator());
 
152
        m_cc.enqueue(bind(&http_connection::connect, shared_from_this(), _1, *i)
 
153
                , bind(&http_connection::on_connect_timeout, shared_from_this())
 
154
                , m_timeout);
 
155
}
 
156
 
 
157
void http_connection::connect(int ticket, tcp::endpoint target_address)
 
158
{
 
159
        m_connection_ticket = ticket;
 
160
        m_sock.async_connect(target_address, boost::bind(&http_connection::on_connect
 
161
                , shared_from_this(), _1/*, ++i*/));
 
162
}
 
163
 
 
164
void http_connection::on_connect(asio::error_code const& e
 
165
        /*, tcp::resolver::iterator i*/)
 
166
{
 
167
        if (!e)
 
168
        { 
 
169
                m_last_receive = time_now();
 
170
                if (m_connect_handler) m_connect_handler(*this);
 
171
                asio::async_write(m_sock, asio::buffer(sendbuffer)
 
172
                        , bind(&http_connection::on_write, shared_from_this(), _1));
 
173
        }
 
174
/*      else if (i != tcp::resolver::iterator())
 
175
        {
 
176
                // The connection failed. Try the next endpoint in the list.
 
177
                m_sock.close();
 
178
                m_cc.enqueue(bind(&http_connection::connect, shared_from_this(), _1, *i)
 
179
                        , bind(&http_connection::on_connect_timeout, shared_from_this())
 
180
                        , m_timeout);
 
181
        } 
 
182
*/      else
 
183
        { 
 
184
                callback(e);
 
185
                close();
 
186
        }
 
187
}
 
188
 
 
189
void http_connection::callback(asio::error_code const& e, char const* data, int size)
 
190
{
 
191
        if (!m_bottled || !m_called)
 
192
        {
 
193
                m_called = true;
 
194
                if (m_handler) m_handler(e, m_parser, data, size, *this);
 
195
        }
 
196
}
 
197
 
 
198
void http_connection::on_write(asio::error_code const& e)
 
199
{
 
200
        if (e)
 
201
        {
 
202
                callback(e);
 
203
                close();
 
204
                return;
 
205
        }
 
206
 
 
207
        std::string().swap(sendbuffer);
 
208
        m_recvbuffer.resize(4096);
 
209
 
 
210
        int amount_to_read = m_recvbuffer.size() - m_read_pos;
 
211
        if (m_rate_limit > 0 && amount_to_read > m_download_quota)
 
212
        {
 
213
                amount_to_read = m_download_quota;
 
214
                if (m_download_quota == 0)
 
215
                {
 
216
                        if (!m_limiter_timer_active)
 
217
                                on_assign_bandwidth(asio::error_code());
 
218
                        return;
 
219
                }
 
220
        }
 
221
        m_sock.async_read_some(asio::buffer(&m_recvbuffer[0] + m_read_pos
 
222
                , amount_to_read)
 
223
                , bind(&http_connection::on_read
 
224
                , shared_from_this(), _1, _2));
 
225
}
 
226
 
 
227
void http_connection::on_read(asio::error_code const& e
 
228
        , std::size_t bytes_transferred)
 
229
{
 
230
        if (m_rate_limit)
 
231
        {
 
232
                m_download_quota -= bytes_transferred;
 
233
                TORRENT_ASSERT(m_download_quota >= 0);
 
234
        }
 
235
 
 
236
        if (e == asio::error::eof)
 
237
        {
 
238
                TORRENT_ASSERT(bytes_transferred == 0);
 
239
                char const* data = 0;
 
240
                std::size_t size = 0;
 
241
                if (m_bottled && m_parser.header_finished())
 
242
                {
 
243
                        data = m_parser.get_body().begin;
 
244
                        size = m_parser.get_body().left();
 
245
                }
 
246
                callback(e, data, size);
 
247
                close();
 
248
                return;
 
249
        }
 
250
 
 
251
        if (e)
 
252
        {
 
253
                TORRENT_ASSERT(bytes_transferred == 0);
 
254
                callback(e);
 
255
                close();
 
256
                return;
 
257
        }
 
258
 
 
259
        m_read_pos += bytes_transferred;
 
260
        TORRENT_ASSERT(m_read_pos <= int(m_recvbuffer.size()));
 
261
 
 
262
        if (m_bottled || !m_parser.header_finished())
 
263
        {
 
264
                libtorrent::buffer::const_interval rcv_buf(&m_recvbuffer[0]
 
265
                        , &m_recvbuffer[0] + m_read_pos);
 
266
                try
 
267
                {
 
268
                        m_parser.incoming(rcv_buf);
 
269
                }
 
270
                catch (std::exception&)
 
271
                {
 
272
                        m_timer.cancel();
 
273
                        callback(asio::error::fault, 0, 0);
 
274
                        close();
 
275
                        return;
 
276
                }
 
277
 
 
278
                // having a nonempty path means we should handle redirects
 
279
                if (m_redirects && m_parser.header_finished())
 
280
                {
 
281
                        int code = m_parser.status_code();
 
282
 
 
283
                        if (code >= 300 && code < 400)
 
284
                        {
 
285
                                // attempt a redirect
 
286
                                std::string const& location = m_parser.header("location");
 
287
                                if (location.empty())
 
288
                                {
 
289
                                        // missing location header
 
290
                                        callback(asio::error::fault);
 
291
                                        close();
 
292
                                        return;
 
293
                                }
 
294
 
 
295
                                try
 
296
                                {
 
297
                                        asio::error_code ec;
 
298
                                        m_sock.close(ec);
 
299
                                        get(location, m_timeout, m_redirects - 1);
 
300
                                        return;
 
301
                                }
 
302
                                catch (std::exception& e)
 
303
                                {
 
304
                                        // some broken web servers send out relative paths
 
305
                                        // in the location header.
 
306
                                        std::string url = m_url;
 
307
                                        // remove the leaf filename
 
308
                                        std::size_t i = url.find_last_of('/');
 
309
                                        if (i == std::string::npos)
 
310
                                        {
 
311
                                                url += '/';
 
312
                                        }
 
313
                                        else
 
314
                                        {
 
315
                                                url.resize(i + 1);
 
316
                                        }
 
317
                                        url += location;
 
318
 
 
319
                                        try
 
320
                                        {
 
321
                                                get(url, m_timeout, m_redirects - 1);
 
322
                                        }
 
323
                                        catch (std::exception& e)
 
324
                                        {
 
325
                                                // location header is invalid
 
326
                                                callback(asio::error::fault);
 
327
                                                close();
 
328
                                        }
 
329
                                }
 
330
                                return;
 
331
                        }
 
332
        
 
333
                        m_redirects = 0;
 
334
                }
 
335
 
 
336
                if (!m_bottled && m_parser.header_finished())
 
337
                {
 
338
                        if (m_read_pos > m_parser.body_start())
 
339
                                callback(e, &m_recvbuffer[0] + m_parser.body_start()
 
340
                                        , m_read_pos - m_parser.body_start());
 
341
                        m_read_pos = 0;
 
342
                        m_last_receive = time_now();
 
343
                }
 
344
                else if (m_bottled && m_parser.finished())
 
345
                {
 
346
                        m_timer.cancel();
 
347
                        callback(e, m_parser.get_body().begin, m_parser.get_body().left());
 
348
                }
 
349
        }
 
350
        else
 
351
        {
 
352
                TORRENT_ASSERT(!m_bottled);
 
353
                callback(e, &m_recvbuffer[0], m_read_pos);
 
354
                m_read_pos = 0;
 
355
                m_last_receive = time_now();
 
356
        }
 
357
 
 
358
        if (int(m_recvbuffer.size()) == m_read_pos)
 
359
                m_recvbuffer.resize((std::min)(m_read_pos + 2048, int(max_bottled_buffer)));
 
360
        if (m_read_pos == max_bottled_buffer)
 
361
        {
 
362
                callback(asio::error::eof);
 
363
                close();
 
364
                return;
 
365
        }
 
366
        int amount_to_read = m_recvbuffer.size() - m_read_pos;
 
367
        if (m_rate_limit > 0 && amount_to_read > m_download_quota)
 
368
        {
 
369
                amount_to_read = m_download_quota;
 
370
                if (m_download_quota == 0)
 
371
                {
 
372
                        if (!m_limiter_timer_active)
 
373
                                on_assign_bandwidth(asio::error_code());
 
374
                        return;
 
375
                }
 
376
        }
 
377
        m_sock.async_read_some(asio::buffer(&m_recvbuffer[0] + m_read_pos
 
378
                , amount_to_read)
 
379
                , bind(&http_connection::on_read
 
380
                , shared_from_this(), _1, _2));
 
381
}
 
382
 
 
383
void http_connection::on_assign_bandwidth(asio::error_code const& e)
 
384
{
 
385
        if ((e == asio::error::operation_aborted
 
386
                && m_limiter_timer_active)
 
387
                || !m_sock.is_open())
 
388
        {
 
389
                callback(asio::error::eof);
 
390
                return;
 
391
        }
 
392
        m_limiter_timer_active = false;
 
393
        if (e) return;
 
394
 
 
395
        if (m_download_quota > 0) return;
 
396
 
 
397
        m_download_quota = m_rate_limit / 4;
 
398
 
 
399
        int amount_to_read = m_recvbuffer.size() - m_read_pos;
 
400
        if (amount_to_read > m_download_quota)
 
401
                amount_to_read = m_download_quota;
 
402
 
 
403
        if (!m_sock.is_open()) return;
 
404
 
 
405
        m_sock.async_read_some(asio::buffer(&m_recvbuffer[0] + m_read_pos
 
406
                , amount_to_read)
 
407
                , bind(&http_connection::on_read
 
408
                , shared_from_this(), _1, _2));
 
409
 
 
410
        m_limiter_timer_active = true;
 
411
        m_limiter_timer.expires_from_now(milliseconds(250));
 
412
        m_limiter_timer.async_wait(bind(&http_connection::on_assign_bandwidth
 
413
                , shared_from_this(), _1));
 
414
}
 
415
 
 
416
void http_connection::rate_limit(int limit)
 
417
{
 
418
        if (!m_sock.is_open()) return;
 
419
 
 
420
        if (!m_limiter_timer_active)
 
421
        {
 
422
                m_limiter_timer_active = true;
 
423
                m_limiter_timer.expires_from_now(milliseconds(250));
 
424
                m_limiter_timer.async_wait(bind(&http_connection::on_assign_bandwidth
 
425
                        , shared_from_this(), _1));
 
426
        }
 
427
        m_rate_limit = limit;
 
428
}
 
429
 
 
430
}
 
431