source: trunk/libtransmission/web.c @ 7645

Last change on this file since 7645 was 7645, checked in by charles, 12 years ago

(trunk libT) clear out the experimental code that accumulated while trying to find the fix to the tracker-announces-never-finish bug. This revision is 1.42's version plus bugfixes.

  • Property svn:keywords set to Date Rev Author Id
File size: 14.6 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@transmissionbt.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: web.c 7645 2009-01-08 20:35:10Z charles $
11 */
12
13#include <assert.h>
14#include <stdlib.h> /* bsearch */
15
16#include <event.h>
17#include <curl/curl.h>
18
19#include "transmission.h"
20#include "session.h"
21#include "list.h"
22#include "net.h" /* socklen_t */
23#include "trevent.h"
24#include "utils.h"
25#include "web.h"
26
27enum 
28{
29    /* arbitrary number */
30    MAX_CONCURRENT_TASKS = 24,
31
32    /* arbitrary number */
33    DEFAULT_TIMER_MSEC = 2000
34};
35
36#if 0
37#define dbgmsg(...) \
38    do { \
39        fprintf( stderr, __VA_ARGS__ ); \
40        fprintf( stderr, "\n" ); \
41    } while( 0 )
42#else
43#define dbgmsg( ... ) \
44    do { \
45        if( tr_deepLoggingIsActive( ) ) \
46            tr_deepLog( __FILE__, __LINE__, "web", __VA_ARGS__ ); \
47    } while( 0 )
48#endif
49
50struct tr_web
51{
52    tr_bool isClosing;
53    int prev_running;
54    int still_running;
55    long timer_ms;
56    CURLM * multi;
57    tr_session * session;
58    tr_list * easy_queue;
59    struct event timer_event;
60};
61
62/***
63****
64***/
65
66struct tr_web_task
67{
68    unsigned long tag;
69    struct evbuffer * response;
70    char * url;
71    char * range;
72    tr_session * session;
73    tr_web_done_func * done_func;
74    void * done_func_user_data;
75};
76
77static size_t
78writeFunc( void * ptr, size_t size, size_t nmemb, void * task )
79{
80    const size_t byteCount = size * nmemb;
81    evbuffer_add( ((struct tr_web_task*)task)->response, ptr, byteCount );
82    dbgmsg( "wrote %zu bytes to task %p's buffer", byteCount, task );
83    return byteCount;
84}
85
86static int
87getCurlProxyType( tr_proxy_type t )
88{
89    switch( t )
90    {
91        case TR_PROXY_SOCKS4: return CURLPROXY_SOCKS4;
92        case TR_PROXY_SOCKS5: return CURLPROXY_SOCKS5;
93        default:              return CURLPROXY_HTTP;
94    }
95}
96
97static void
98addTask( void * vtask )
99{
100    struct tr_web_task * task = vtask;
101    const tr_session * session = task->session;
102
103    if( session && session->web )
104    {
105        struct tr_web * web = session->web;
106        CURL * easy;
107
108        dbgmsg( "adding task #%lu [%s]", task->tag, task->url );
109
110        easy = curl_easy_init( );
111
112        if( !task->range && session->isProxyEnabled ) {
113            curl_easy_setopt( easy, CURLOPT_PROXY, session->proxy );
114            curl_easy_setopt( easy, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
115            curl_easy_setopt( easy, CURLOPT_PROXYPORT, session->proxyPort );
116            curl_easy_setopt( easy, CURLOPT_PROXYTYPE,
117                                      getCurlProxyType( session->proxyType ) );
118        }
119        if( !task->range && session->isProxyAuthEnabled ) {
120            char * str = tr_strdup_printf( "%s:%s", session->proxyUsername,
121                                                    session->proxyPassword );
122            curl_easy_setopt( easy, CURLOPT_PROXYUSERPWD, str );
123            tr_free( str );
124        }
125
126        curl_easy_setopt( easy, CURLOPT_DNS_CACHE_TIMEOUT, 360L );
127        curl_easy_setopt( easy, CURLOPT_CONNECTTIMEOUT, 60L );
128        curl_easy_setopt( easy, CURLOPT_FOLLOWLOCATION, 1L );
129        curl_easy_setopt( easy, CURLOPT_MAXREDIRS, 16L );
130        curl_easy_setopt( easy, CURLOPT_NOSIGNAL, 1L );
131        curl_easy_setopt( easy, CURLOPT_PRIVATE, task );
132        curl_easy_setopt( easy, CURLOPT_SSL_VERIFYHOST, 0L );
133        curl_easy_setopt( easy, CURLOPT_SSL_VERIFYPEER, 0L );
134        curl_easy_setopt( easy, CURLOPT_URL, task->url );
135        curl_easy_setopt( easy, CURLOPT_USERAGENT,
136                                           TR_NAME "/" LONG_VERSION_STRING );
137        curl_easy_setopt( easy, CURLOPT_VERBOSE,
138                                       getenv( "TR_CURL_VERBOSE" ) != NULL );
139        curl_easy_setopt( easy, CURLOPT_WRITEDATA, task );
140        curl_easy_setopt( easy, CURLOPT_WRITEFUNCTION, writeFunc );
141        if( task->range )
142            curl_easy_setopt( easy, CURLOPT_RANGE, task->range );
143        else /* don't set encoding on webseeds; it messes up binary data */
144            curl_easy_setopt( easy, CURLOPT_ENCODING, "" );
145
146        if( web->still_running >= MAX_CONCURRENT_TASKS ) {
147            tr_list_append( &web->easy_queue, easy );
148            dbgmsg( " >> enqueueing a task ... size is now %d",
149                                           tr_list_size( web->easy_queue ) );
150        } else {
151            const CURLMcode rc = curl_multi_add_handle( web->multi, easy );
152            if( rc == CURLM_OK )
153                ++web->still_running;
154            else
155                tr_err( "%s", curl_multi_strerror( rc ) );
156        }
157    }
158}
159
160/***
161****
162***/
163
164struct tr_web_sockinfo
165{
166    struct event ev;
167    int evset;
168};
169
170static void
171task_free( struct tr_web_task * task )
172{
173    evbuffer_free( task->response );
174    tr_free( task->range );
175    tr_free( task->url );
176    tr_free( task );
177}
178
179static void
180task_finish( struct tr_web_task * task, long response_code )
181{
182    dbgmsg( "finished a web task... response code is %ld", response_code );
183    dbgmsg( "===================================================" );
184    task->done_func( task->session,
185                     response_code,
186                     EVBUFFER_DATA( task->response ),
187                     EVBUFFER_LENGTH( task->response ),
188                     task->done_func_user_data );
189    task_free( task );
190}
191
192static void
193remove_finished_tasks( tr_web * g )
194{
195    for( ;; )
196    {
197        int ignored;
198        CURLMsg * msg = curl_multi_info_read( g->multi, &ignored );
199
200        if( msg == NULL )
201        {
202            break;
203        }
204        else if( ( msg->msg == CURLMSG_DONE ) && ( msg->easy_handle != NULL ) )
205        {
206            CURL * easy = msg->easy_handle;
207            long code;
208            struct tr_web_task * task;
209            curl_easy_getinfo( easy, CURLINFO_PRIVATE, (void*)&task );
210            curl_easy_getinfo( easy, CURLINFO_RESPONSE_CODE, &code );
211            curl_multi_remove_handle( g->multi, easy );
212            curl_easy_cleanup( easy );
213            task_finish( task, code );
214        }
215    }
216
217    g->prev_running = g->still_running;
218}
219
220static void
221stop_timer( tr_web* g )
222{
223    if( evtimer_pending( &g->timer_event, NULL ) )
224    {
225        dbgmsg( "deleting the pending global timer" );
226        evtimer_del( &g->timer_event );
227    }
228}
229
230static void
231restart_timer( tr_web * g )
232{
233    struct timeval interval;
234    stop_timer( g );
235    dbgmsg( "adding a timeout for %ld seconds from now", g->timer_ms/1000L );
236    tr_timevalMsec( g->timer_ms, &interval );
237    evtimer_add( &g->timer_event, &interval );
238}
239
240static void
241add_tasks_from_queue( tr_web * g )
242{
243    while( ( g->still_running < MAX_CONCURRENT_TASKS ) 
244        && ( tr_list_size( g->easy_queue ) > 0 ) )
245    {
246        CURL * easy = tr_list_pop_front( &g->easy_queue );
247        if( easy )
248        {
249            const CURLMcode rc = curl_multi_add_handle( g->multi, easy );
250            if( rc != CURLM_OK )
251                tr_err( "%s", curl_multi_strerror( rc ) );
252            else {
253                dbgmsg( "pumped the task queue, %d remain",
254                        tr_list_size( g->easy_queue ) );
255                ++g->still_running;
256            }
257        }
258    }
259}
260
261static void
262web_close( tr_web * g )
263{
264    stop_timer( g );
265    curl_multi_cleanup( g->multi );
266    tr_free( g );
267}
268
269/* note: this function can free the tr_web if its 'isClosing' flag is set
270   and no tasks remain.  callers must not reference their g pointer
271   after calling this function */
272static void
273tr_multi_socket_action( tr_web * g, int fd )
274{
275    int closed = FALSE;
276    CURLMcode rc;
277
278    dbgmsg( "check_run_count: prev_running %d, still_running %d",
279            g->prev_running, g->still_running );
280
281    /* invoke libcurl's processing */
282    do {
283        rc = curl_multi_socket_action( g->multi, fd, 0, &g->still_running );
284        dbgmsg( "event_cb(): fd %d, still_running is %d",
285                fd, g->still_running );
286    } while( rc == CURLM_CALL_MULTI_PERFORM );
287    if( rc != CURLM_OK )
288        tr_err( "%s", curl_multi_strerror( rc ) );
289
290    remove_finished_tasks( g );
291
292    add_tasks_from_queue( g );
293
294    if( !g->still_running ) {
295        stop_timer( g );
296        if( g->isClosing ) {
297            web_close( g );
298            closed = TRUE;
299        }
300    }
301
302    if( !closed )
303        restart_timer( g );
304}
305
306/* libevent says that sock is ready to be processed, so wake up libcurl */
307static void
308event_cb( int fd, short kind UNUSED, void * g )
309{
310    tr_multi_socket_action( g, fd );
311}
312
313/* libevent says that timer_ms have passed, so wake up libcurl */
314static void
315timer_cb( int socket UNUSED, short action UNUSED, void * g )
316{
317    dbgmsg( "libevent timer is done" );
318    tr_multi_socket_action( g, CURL_SOCKET_TIMEOUT );
319}
320
321static void
322remsock( struct tr_web_sockinfo * f )
323{
324    if( f ) {
325        dbgmsg( "deleting sockinfo %p", f );
326        if( f->evset )
327            event_del( &f->ev );
328        tr_free( f );
329    }
330}
331
332static void
333setsock( curl_socket_t            sockfd,
334         int                      action,
335         struct tr_web          * g,
336         struct tr_web_sockinfo * f )
337{
338    const int kind = EV_PERSIST
339                   | (( action & CURL_POLL_IN ) ? EV_READ : 0 )
340                   | (( action & CURL_POLL_OUT ) ? EV_WRITE : 0 );
341    dbgmsg( "setsock: fd is %d, curl action is %d, libevent action is %d",
342            sockfd, action, kind );
343    if( f->evset )
344        event_del( &f->ev );
345    event_set( &f->ev, sockfd, kind, event_cb, g );
346    f->evset = 1;
347    event_add( &f->ev, NULL );
348}
349
350static void
351addsock( curl_socket_t    sockfd,
352         int              action,
353         struct tr_web  * g )
354{
355    struct tr_web_sockinfo * f = tr_new0( struct tr_web_sockinfo, 1 );
356    dbgmsg( "creating a sockinfo %p for fd %d", f, sockfd );
357    setsock( sockfd, action, g, f );
358    curl_multi_assign( g->multi, sockfd, f );
359}
360
361/* CURLMOPT_SOCKETFUNCTION */
362static int
363sock_cb( CURL            * e UNUSED,
364         curl_socket_t     s,
365         int               what,
366         void            * vg,
367         void            * vf)
368{
369    struct tr_web * g = vg;
370    struct tr_web_sockinfo * f = vf;
371    dbgmsg( "sock_cb: what is %d, sockinfo is %p", what, f );
372
373    if( what == CURL_POLL_REMOVE )
374        remsock( f );
375    else if( !f )
376        addsock( s, what, g );
377    else
378        setsock( s, what, g, f );
379
380    return 0;
381}
382
383
384/* libcurl documentation: "If 0, it means you should proceed immediately
385 * without waiting for anything. If it returns -1, there's no timeout at all
386 * set ... (but) you must not wait too long (more than a few seconds perhaps)
387 * before you call curl_multi_perform() again."  */
388static void
389multi_timer_cb( CURLM *multi UNUSED, long timer_ms, void * vg )
390{
391    tr_web * g = vg;
392
393    if( timer_ms < 1 ) {
394        if( timer_ms == 0 ) /* call it immediately */
395            timer_cb( 0, 0, g );
396        timer_ms = DEFAULT_TIMER_MSEC;
397    }
398
399    g->timer_ms = timer_ms;
400    restart_timer( g );
401}
402
403/****
404*****
405****/
406
407void
408tr_webRun( tr_session         * session,
409           const char         * url,
410           const char         * range,
411           tr_web_done_func     done_func,
412           void               * done_func_user_data )
413{
414    if( session->web )
415    {
416        static unsigned long tag = 0;
417        struct tr_web_task * task;
418
419        task = tr_new0( struct tr_web_task, 1 );
420        task->session = session;
421        task->url = tr_strdup( url );
422        task->range = tr_strdup( range );
423        task->done_func = done_func;
424        task->done_func_user_data = done_func_user_data;
425        task->tag = ++tag;
426        task->response = evbuffer_new( );
427
428        tr_runInEventThread( session, addTask, task );
429    }
430}
431
432tr_web*
433tr_webInit( tr_session * session )
434{
435    static int curlInited = FALSE;
436    tr_web * web;
437
438    /* call curl_global_init if we haven't done it already.
439     * try to enable ssl for https support; but if that fails,
440     * try a plain vanilla init */ 
441    if( curlInited == FALSE ) {
442        curlInited = TRUE;
443        if( curl_global_init( CURL_GLOBAL_SSL ) )
444            curl_global_init( 0 );
445    }
446   
447    web = tr_new0( struct tr_web, 1 );
448    web->multi = curl_multi_init( );
449    web->session = session;
450    web->timer_ms = DEFAULT_TIMER_MSEC; /* overwritten by multi_timer_cb() */
451
452    evtimer_set( &web->timer_event, timer_cb, web );
453    curl_multi_setopt( web->multi, CURLMOPT_SOCKETDATA, web );
454    curl_multi_setopt( web->multi, CURLMOPT_SOCKETFUNCTION, sock_cb );
455    curl_multi_setopt( web->multi, CURLMOPT_TIMERDATA, web );
456    curl_multi_setopt( web->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb );
457
458    return web;
459}
460
461void
462tr_webClose( tr_web ** web_in )
463{
464    tr_web * web = *web_in;
465    *web_in = NULL;
466    if( web->still_running < 1 )
467        web_close( web );
468    else
469        web->isClosing = 1;
470}
471
472/*****
473******
474******
475*****/
476
477static struct http_msg {
478    long code;
479    const char * text;
480} http_msg[] = {
481    {   0, "No Response" },
482    { 101, "Switching Protocols" },
483    { 200, "OK" },
484    { 201, "Created" },
485    { 202, "Accepted" },
486    { 203, "Non-Authoritative Information" },
487    { 204, "No Content" },
488    { 205, "Reset Content" },
489    { 206, "Partial Content" },
490    { 300, "Multiple Choices" },
491    { 301, "Moved Permanently" },
492    { 302, "Found" },
493    { 303, "See Other" },
494    { 304, "Not Modified" },
495    { 305, "Use Proxy" },
496    { 306, "(Unused)" },
497    { 307, "Temporary Redirect" },
498    { 400, "Bad Request" },
499    { 401, "Unauthorized" },
500    { 402, "Payment Required" },
501    { 403, "Forbidden" },
502    { 404, "Not Found" },
503    { 405, "Method Not Allowed" },
504    { 406, "Not Acceptable" },
505    { 407, "Proxy Authentication Required" },
506    { 408, "Request Timeout" },
507    { 409, "Conflict" },
508    { 410, "Gone" },
509    { 411, "Length Required" },
510    { 412, "Precondition Failed" },
511    { 413, "Request Entity Too Large" },
512    { 414, "Request-URI Too Long" },
513    { 415, "Unsupported Media Type" },
514    { 416, "Requested Range Not Satisfiable" },
515    { 417, "Expectation Failed" },
516    { 500, "Internal Server Error" },
517    { 501, "Not Implemented" },
518    { 502, "Bad Gateway" },
519    { 503, "Service Unavailable" },
520    { 504, "Gateway Timeout" },
521    { 505, "HTTP Version Not Supported" }
522};
523
524static int
525compareResponseCodes( const void * va, const void * vb )
526{
527    const long a = *(const long*) va;
528    const struct http_msg * b = vb;
529    return a - b->code;
530}
531
532const char *
533tr_webGetResponseStr( long code )
534{
535    struct http_msg * msg = bsearch( &code,
536                                     http_msg, 
537                                     sizeof( http_msg ) / sizeof( http_msg[0] ),
538                                     sizeof( http_msg[0] ),
539                                     compareResponseCodes );
540    return msg ? msg->text : "Unknown Error";
541}
Note: See TracBrowser for help on using the repository browser.