source: trunk/libtransmission/web.c @ 7173

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

use tr_bool instead of C bitfields. (http://blogs.msdn.com/oldnewthing/archive/2008/11/26/9143050.aspx)

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