source: branches/1.5x/libtransmission/web.c @ 7906

Last change on this file since 7906 was 7906, checked in by charles, 13 years ago

(1.5x libT) #1844: Prefer curl_multi_socket_action() to curl_multi_perform() in modern versions of libcurl

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