source: trunk/libtransmission/web.c @ 7532

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

(trunk libT) simplify web.c's remove_finished_tasks() a bit

  • 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 7532 2008-12-29 19:02:12Z 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    for( ;; )
204    {
205        int ignored;
206        CURLMsg * msg = curl_multi_info_read( g->multi, &ignored );
207
208        if( msg == NULL )
209        {
210            break;
211        }
212        else if( ( msg->msg == CURLMSG_DONE ) && ( msg->easy_handle != NULL ) )
213        {
214            CURL * easy = msg->easy_handle;
215            long code;
216            struct tr_web_task * task;
217            curl_easy_getinfo( easy, CURLINFO_PRIVATE, (void*)&task );
218            curl_easy_getinfo( easy, CURLINFO_RESPONSE_CODE, &code );
219            task_finish( task, code );
220            curl_multi_remove_handle( g->multi, easy );
221            curl_easy_cleanup( easy );
222        }
223    }
224
225    g->prev_running = g->still_running;
226}
227
228static void
229stop_timer( tr_web* g )
230{
231    if( evtimer_pending( &g->timer_event, NULL ) )
232    {
233        dbgmsg( "deleting the pending global timer" );
234        evtimer_del( &g->timer_event );
235    }
236}
237
238static void
239restart_timer( tr_web * g )
240{
241    struct timeval interval;
242    stop_timer( g );
243    dbgmsg( "adding a timeout for %ld seconds from now", g->timer_ms/1000L );
244    tr_timevalMsec( g->timer_ms, &interval );
245    evtimer_add( &g->timer_event, &interval );
246}
247
248#if 0
249static void
250add_tasks_from_queue( tr_web * g )
251{
252    while( ( g->still_running < MAX_CONCURRENT_TASKS )
253        && ( tr_list_size( g->easy_queue ) > 0 ) )
254    {
255        CURL * easy = tr_list_pop_front( &g->easy_queue );
256        if( easy )
257        {
258            const CURLMcode rc = curl_multi_add_handle( g->multi, easy );
259            if( rc != CURLM_OK )
260                tr_err( "%s", curl_multi_strerror( rc ) );
261            else {
262                dbgmsg( "pumped the task queue, %d remain",
263                        tr_list_size( g->easy_queue ) );
264                ++g->still_running;
265            }
266        }
267    }
268}
269#endif
270
271static void
272web_close( tr_web * g )
273{
274    stop_timer( g );
275    curl_multi_cleanup( g->multi );
276    tr_free( g );
277}
278
279/* note: this function can free the tr_web if its 'closing' flag is set
280   and no tasks remain.  callers must not reference their g pointer
281   after calling this function */
282static void
283tr_multi_socket_action( tr_web * g, int fd, int mask )
284{
285    int closed = FALSE;
286    CURLMcode rc;
287
288    dbgmsg( "check_run_count: prev_running %d, still_running %d",
289            g->prev_running, g->still_running );
290
291    /* invoke libcurl's processing */
292    do {
293        rc = curl_multi_socket_action( g->multi, fd, mask, &g->still_running );
294        dbgmsg( "event_cb(): fd %d, mask %d, still_running is %d",
295                fd, mask, g->still_running );
296    } while( rc == CURLM_CALL_MULTI_PERFORM );
297    if( rc != CURLM_OK )
298        tr_err( "%s", curl_multi_strerror( rc ) );
299
300    remove_finished_tasks( g );
301
302#if 0
303    add_tasks_from_queue( g );
304#endif
305
306    if( !g->still_running ) {
307        stop_timer( g );
308        if( g->closing ) {
309            web_close( g );
310            closed = TRUE;
311        }
312    }
313
314    if( !closed )
315        restart_timer( g );
316}
317
318/* libevent says that sock is ready to be processed, so wake up libcurl */
319static void
320event_cb( int fd, short kind, void * g )
321{
322    int error;
323    int mask;
324    socklen_t errsz;
325
326    error = 0;
327    errsz = sizeof( error );
328    getsockopt( fd, SOL_SOCKET, SO_ERROR, &error, &errsz );
329    if( error )
330        mask = CURL_CSELECT_ERR;
331    else {
332        mask = 0;
333        if( kind & EV_READ  ) mask |= CURL_CSELECT_IN;
334        if( kind & EV_WRITE ) mask |= CURL_CSELECT_OUT;
335    }
336
337    tr_multi_socket_action( g, fd, mask );
338}
339
340/* libevent says that timer_ms have passed, so wake up libcurl */
341static void
342timer_cb( int socket UNUSED, short action UNUSED, void * g )
343{
344    dbgmsg( "libevent timer is done" );
345    tr_multi_socket_action( g, CURL_SOCKET_TIMEOUT, 0 );
346}
347
348static void
349remsock( struct tr_web_sockinfo * f )
350{
351    if( f ) {
352        dbgmsg( "deleting sockinfo %p", f );
353        if( f->evset )
354            event_del( &f->ev );
355        tr_free( f );
356    }
357}
358
359static void
360setsock( curl_socket_t            sockfd,
361         int                      action,
362         struct tr_web          * g,
363         struct tr_web_sockinfo * f )
364{
365    const int kind = EV_PERSIST
366                   | (( action & CURL_POLL_IN ) ? EV_READ : 0 )
367                   | (( action & CURL_POLL_OUT ) ? EV_WRITE : 0 );
368    dbgmsg( "setsock: fd is %d, curl action is %d, libevent action is %d",
369            sockfd, action, kind );
370    if( f->evset )
371        event_del( &f->ev );
372    event_set( &f->ev, sockfd, kind, event_cb, g );
373    f->evset = 1;
374    event_add( &f->ev, NULL );
375}
376
377static void
378addsock( curl_socket_t    sockfd,
379         int              action,
380         struct tr_web  * g )
381{
382    struct tr_web_sockinfo * f = tr_new0( struct tr_web_sockinfo, 1 );
383    dbgmsg( "creating a sockinfo %p for fd %d", f, sockfd );
384    setsock( sockfd, action, g, f );
385    curl_multi_assign( g->multi, sockfd, f );
386}
387
388/* CURLMOPT_SOCKETFUNCTION */
389static int
390sock_cb( CURL            * e UNUSED,
391         curl_socket_t     s,
392         int               what,
393         void            * vg,
394         void            * vf)
395{
396    struct tr_web * g = vg;
397    struct tr_web_sockinfo * f = vf;
398    dbgmsg( "sock_cb: what is %d, sockinfo is %p", what, f );
399
400    if( what == CURL_POLL_REMOVE )
401        remsock( f );
402    else if( !f )
403        addsock( s, what, g );
404    else
405        setsock( s, what, g, f );
406
407    return 0;
408}
409
410
411/* libcurl documentation: "If 0, it means you should proceed immediately
412 * without waiting for anything. If it returns -1, there's no timeout at all
413 * set ... (but) you must not wait too long (more than a few seconds perhaps)
414 * before you call curl_multi_perform() again."  */
415static void
416multi_timer_cb( CURLM *multi UNUSED, long timer_ms, void * vg )
417{
418    tr_web * g = vg;
419
420    if( timer_ms < 1 ) {
421        if( timer_ms == 0 ) /* call it immediately */
422            timer_cb( 0, 0, g );
423        timer_ms = DEFAULT_TIMER_MSEC;
424    }
425
426    g->timer_ms = timer_ms;
427    restart_timer( g );
428}
429
430/****
431*****
432****/
433
434void
435tr_webRun( tr_session         * session,
436           const char         * url,
437           const char         * range,
438           tr_web_done_func     done_func,
439           void               * done_func_user_data )
440{
441    if( session->web )
442    {
443        static unsigned long tag = 0;
444        struct tr_web_task * task;
445
446        task = tr_new0( struct tr_web_task, 1 );
447        task->session = session;
448        task->url = tr_strdup( url );
449        task->range = tr_strdup( range );
450        task->done_func = done_func;
451        task->done_func_user_data = done_func_user_data;
452        task->tag = ++tag;
453        task->response = evbuffer_new( );
454
455        tr_runInEventThread( session, addTask, task );
456    }
457}
458
459tr_web*
460tr_webInit( tr_session * session )
461{
462    static int curlInited = FALSE;
463    tr_web * web;
464
465    /* call curl_global_init if we haven't done it already.
466     * try to enable ssl for https support; but if that fails,
467     * try a plain vanilla init */ 
468    if( curlInited == FALSE ) {
469        curlInited = TRUE;
470        if( curl_global_init( CURL_GLOBAL_SSL ) )
471            curl_global_init( 0 );
472    }
473   
474    web = tr_new0( struct tr_web, 1 );
475    web->multi = curl_multi_init( );
476    web->session = session;
477    web->timer_ms = DEFAULT_TIMER_MSEC; /* overwritten by multi_timer_cb() */
478
479    evtimer_set( &web->timer_event, timer_cb, web );
480    curl_multi_setopt( web->multi, CURLMOPT_SOCKETDATA, web );
481    curl_multi_setopt( web->multi, CURLMOPT_SOCKETFUNCTION, sock_cb );
482    curl_multi_setopt( web->multi, CURLMOPT_TIMERDATA, web );
483    curl_multi_setopt( web->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb );
484
485    return web;
486}
487
488void
489tr_webClose( tr_web ** web_in )
490{
491    tr_web * web = *web_in;
492    *web_in = NULL;
493    if( web->still_running < 1 )
494        web_close( web );
495    else
496        web->closing = 1;
497}
498
499/*****
500******
501******
502*****/
503
504static struct http_msg {
505    long code;
506    const char * text;
507} http_msg[] = {
508    {   0, "No Response" },
509    { 101, "Switching Protocols" },
510    { 200, "OK" },
511    { 201, "Created" },
512    { 202, "Accepted" },
513    { 203, "Non-Authoritative Information" },
514    { 204, "No Content" },
515    { 205, "Reset Content" },
516    { 206, "Partial Content" },
517    { 300, "Multiple Choices" },
518    { 301, "Moved Permanently" },
519    { 302, "Found" },
520    { 303, "See Other" },
521    { 304, "Not Modified" },
522    { 305, "Use Proxy" },
523    { 306, "(Unused)" },
524    { 307, "Temporary Redirect" },
525    { 400, "Bad Request" },
526    { 401, "Unauthorized" },
527    { 402, "Payment Required" },
528    { 403, "Forbidden" },
529    { 404, "Not Found" },
530    { 405, "Method Not Allowed" },
531    { 406, "Not Acceptable" },
532    { 407, "Proxy Authentication Required" },
533    { 408, "Request Timeout" },
534    { 409, "Conflict" },
535    { 410, "Gone" },
536    { 411, "Length Required" },
537    { 412, "Precondition Failed" },
538    { 413, "Request Entity Too Large" },
539    { 414, "Request-URI Too Long" },
540    { 415, "Unsupported Media Type" },
541    { 416, "Requested Range Not Satisfiable" },
542    { 417, "Expectation Failed" },
543    { 500, "Internal Server Error" },
544    { 501, "Not Implemented" },
545    { 502, "Bad Gateway" },
546    { 503, "Service Unavailable" },
547    { 504, "Gateway Timeout" },
548    { 505, "HTTP Version Not Supported" }
549};
550
551static int
552compareResponseCodes( const void * va, const void * vb )
553{
554    const long a = *(const long*) va;
555    const struct http_msg * b = vb;
556    return a - b->code;
557}
558
559const char *
560tr_webGetResponseStr( long code )
561{
562    struct http_msg * msg = bsearch( &code,
563                                     http_msg, 
564                                     sizeof( http_msg ) / sizeof( http_msg[0] ),
565                                     sizeof( http_msg[0] ),
566                                     compareResponseCodes );
567    return msg ? msg->text : "Unknown Error";
568}
Note: See TracBrowser for help on using the repository browser.