source: trunk/libtransmission/web.c @ 7738

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

(trunk libT) minor cleanup

  • Property svn:keywords set to Date Rev Author Id
File size: 14.8 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 7738 2009-01-17 22:49:15Z 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, 360L );
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 )
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, 0, &g->still_running );
299        dbgmsg( "event_cb(): fd %d, still_running is %d",
300                fd, 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 UNUSED, void * g )
326{
327    tr_multi_socket_action( g, fd );
328}
329
330/* libevent says that timer_ms have passed, so wake up libcurl */
331static void
332timer_cb( int socket UNUSED, short action UNUSED, void * g )
333{
334    dbgmsg( "libevent timer is done" );
335    tr_multi_socket_action( g, CURL_SOCKET_TIMEOUT );
336}
337
338static void
339remsock( struct tr_web_sockinfo * f )
340{
341    if( f ) {
342        dbgmsg( "deleting sockinfo %p", f );
343        if( f->evset )
344            event_del( &f->ev );
345        tr_free( f );
346    }
347}
348
349static void
350setsock( curl_socket_t            sockfd,
351         int                      action,
352         struct tr_web          * g,
353         struct tr_web_sockinfo * f )
354{
355    const int kind = EV_PERSIST
356                   | (( action & CURL_POLL_IN ) ? EV_READ : 0 )
357                   | (( action & CURL_POLL_OUT ) ? EV_WRITE : 0 );
358    dbgmsg( "setsock: fd is %d, curl action is %d, libevent action is %d",
359            sockfd, action, kind );
360    if( f->evset )
361        event_del( &f->ev );
362    event_set( &f->ev, sockfd, kind, event_cb, g );
363    f->evset = 1;
364    event_add( &f->ev, NULL );
365}
366
367static void
368addsock( curl_socket_t    sockfd,
369         int              action,
370         struct tr_web  * g )
371{
372    struct tr_web_sockinfo * f = tr_new0( struct tr_web_sockinfo, 1 );
373    dbgmsg( "creating a sockinfo %p for fd %d", f, sockfd );
374    setsock( sockfd, action, g, f );
375    curl_multi_assign( g->multi, sockfd, f );
376}
377
378/* CURLMOPT_SOCKETFUNCTION */
379static int
380sock_cb( CURL            * e UNUSED,
381         curl_socket_t     s,
382         int               what,
383         void            * vg,
384         void            * vf)
385{
386    struct tr_web * g = vg;
387    struct tr_web_sockinfo * f = vf;
388    dbgmsg( "sock_cb: what is %d, sockinfo is %p", what, f );
389
390    if( what == CURL_POLL_REMOVE )
391        remsock( f );
392    else if( !f )
393        addsock( s, what, g );
394    else
395        setsock( s, what, g, f );
396
397    return 0;
398}
399
400
401/* libcurl documentation: "If 0, it means you should proceed immediately
402 * without waiting for anything. If it returns -1, there's no timeout at all
403 * set ... (but) you must not wait too long (more than a few seconds perhaps)
404 * before you call curl_multi_perform() again."  */
405static void
406multi_timer_cb( CURLM *multi UNUSED, long timer_ms, void * vg )
407{
408    tr_web * g = vg;
409
410    if( timer_ms < 1 ) {
411        if( timer_ms == 0 ) /* call it immediately */
412            timer_cb( 0, 0, g );
413        timer_ms = DEFAULT_TIMER_MSEC;
414    }
415
416    g->timer_ms = timer_ms;
417    restart_timer( g );
418}
419
420/****
421*****
422****/
423
424void
425tr_webRun( tr_session         * session,
426           const char         * url,
427           const char         * range,
428           tr_web_done_func     done_func,
429           void               * done_func_user_data )
430{
431    if( session->web )
432    {
433        static unsigned long tag = 0;
434        struct tr_web_task * task;
435
436        task = tr_new0( struct tr_web_task, 1 );
437        task->session = session;
438        task->url = tr_strdup( url );
439        task->range = tr_strdup( range );
440        task->done_func = done_func;
441        task->done_func_user_data = done_func_user_data;
442        task->tag = ++tag;
443        task->response = evbuffer_new( );
444
445        tr_runInEventThread( session, addTask, task );
446    }
447}
448
449tr_web*
450tr_webInit( tr_session * session )
451{
452    static int curlInited = FALSE;
453    tr_web * web;
454
455    /* call curl_global_init if we haven't done it already.
456     * try to enable ssl for https support; but if that fails,
457     * try a plain vanilla init */ 
458    if( curlInited == FALSE ) {
459        curlInited = TRUE;
460        if( curl_global_init( CURL_GLOBAL_SSL ) )
461            curl_global_init( 0 );
462    }
463   
464    web = tr_new0( struct tr_web, 1 );
465    web->multi = curl_multi_init( );
466    web->session = session;
467    web->timer_ms = DEFAULT_TIMER_MSEC; /* overwritten by multi_timer_cb() */
468
469    evtimer_set( &web->timer_event, timer_cb, web );
470    curl_multi_setopt( web->multi, CURLMOPT_SOCKETDATA, web );
471    curl_multi_setopt( web->multi, CURLMOPT_SOCKETFUNCTION, sock_cb );
472    curl_multi_setopt( web->multi, CURLMOPT_TIMERDATA, web );
473    curl_multi_setopt( web->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb );
474
475    return web;
476}
477
478void
479tr_webClose( tr_web ** web_in )
480{
481    tr_web * web = *web_in;
482    *web_in = NULL;
483    if( web->still_running < 1 )
484        web_close( web );
485    else
486        web->closing = 1;
487}
488
489/*****
490******
491******
492*****/
493
494static struct http_msg {
495    long code;
496    const char * text;
497} http_msg[] = {
498    {   0, "No Response" },
499    { 101, "Switching Protocols" },
500    { 200, "OK" },
501    { 201, "Created" },
502    { 202, "Accepted" },
503    { 203, "Non-Authoritative Information" },
504    { 204, "No Content" },
505    { 205, "Reset Content" },
506    { 206, "Partial Content" },
507    { 300, "Multiple Choices" },
508    { 301, "Moved Permanently" },
509    { 302, "Found" },
510    { 303, "See Other" },
511    { 304, "Not Modified" },
512    { 305, "Use Proxy" },
513    { 306, "(Unused)" },
514    { 307, "Temporary Redirect" },
515    { 400, "Bad Request" },
516    { 401, "Unauthorized" },
517    { 402, "Payment Required" },
518    { 403, "Forbidden" },
519    { 404, "Not Found" },
520    { 405, "Method Not Allowed" },
521    { 406, "Not Acceptable" },
522    { 407, "Proxy Authentication Required" },
523    { 408, "Request Timeout" },
524    { 409, "Conflict" },
525    { 410, "Gone" },
526    { 411, "Length Required" },
527    { 412, "Precondition Failed" },
528    { 413, "Request Entity Too Large" },
529    { 414, "Request-URI Too Long" },
530    { 415, "Unsupported Media Type" },
531    { 416, "Requested Range Not Satisfiable" },
532    { 417, "Expectation Failed" },
533    { 500, "Internal Server Error" },
534    { 501, "Not Implemented" },
535    { 502, "Bad Gateway" },
536    { 503, "Service Unavailable" },
537    { 504, "Gateway Timeout" },
538    { 505, "HTTP Version Not Supported" }
539};
540
541static int
542compareResponseCodes( const void * va, const void * vb )
543{
544    const long a = *(const long*) va;
545    const struct http_msg * b = vb;
546    return a - b->code;
547}
548
549const char *
550tr_webGetResponseStr( long code )
551{
552    struct http_msg * msg = bsearch( &code,
553                                     http_msg, 
554                                     sizeof( http_msg ) / sizeof( http_msg[0] ),
555                                     sizeof( http_msg[0] ),
556                                     compareResponseCodes );
557    return msg ? msg->text : "Unknown Error";
558}
Note: See TracBrowser for help on using the repository browser.