source: trunk/libtransmission/web.c @ 7529

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

(trunk libT) wrap tr_inf(), tr_msg(), tr_dbg() calls inside a check to see if that debugging level is active. That way that function calls in the vararg list won't be invoked unless that level of verbosity is actually turned on.

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