source: trunk/libtransmission/web.c @ 6414

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

#1122: faster http processing

  • Property svn:keywords set to Date Rev Author Id
File size: 11.5 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 6414 2008-07-29 00:51:07Z 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 "trevent.h"
21#include "utils.h"
22#include "web.h"
23
24#define dbgmsg(fmt...) tr_deepLog( __FILE__, __LINE__, "web", ##fmt )
25
26struct tr_web
27{
28    unsigned int dying     : 1;
29    unsigned int running   : 1;
30    int remain;
31    CURLM * cm;
32    tr_session * session;
33    struct event timer;
34};
35
36struct tr_web_task
37{
38    unsigned long tag;
39    struct evbuffer * response;
40    char * url;
41    char * range;
42    tr_session * session;
43    tr_web_done_func * done_func;
44    void * done_func_user_data;
45};
46
47static void
48processCompletedTasks( tr_web * web )
49{
50    CURL * easy;
51    CURLMsg * msg;
52
53    do {
54        /* this convoluted loop is from the "hiperinfo.c" sample which
55         * hints that removing an easy handle in curl_multi_info_read's
56         * loop may be unsafe */
57        int more;
58        easy = NULL;
59        while(( msg = curl_multi_info_read( web->cm, &more ))) {
60            if( msg->msg == CURLMSG_DONE ) {
61                easy = msg->easy_handle;
62                break;
63            }
64        }
65        if( easy ) {
66            struct tr_web_task * task;
67            long response_code;
68            curl_easy_getinfo( easy, CURLINFO_PRIVATE, &task );
69            curl_easy_getinfo( easy, CURLINFO_RESPONSE_CODE, &response_code );
70            --web->remain;
71            dbgmsg( "task #%lu done (%d remain)", task->tag, web->remain );
72            task->done_func( web->session,
73                             response_code,
74                             EVBUFFER_DATA(task->response),
75                             EVBUFFER_LENGTH(task->response),
76                             task->done_func_user_data );
77            curl_multi_remove_handle( web->cm, easy );
78            curl_easy_cleanup( easy );
79            evbuffer_free( task->response );
80            tr_free( task->range );
81            tr_free( task->url );
82            tr_free( task );
83        }
84    } while( easy );
85}
86
87static size_t
88writeFunc( void * ptr, size_t size, size_t nmemb, void * task )
89{
90    const size_t byteCount = size * nmemb;
91    evbuffer_add( ((struct tr_web_task*)task)->response, ptr, byteCount );
92    return byteCount;
93}
94
95static int
96getCurlProxyType( tr_proxy_type t )
97{
98    switch( t )
99    {
100        case TR_PROXY_SOCKS4: return CURLPROXY_SOCKS4;
101        case TR_PROXY_SOCKS5: return CURLPROXY_SOCKS5;
102        default:              return CURLPROXY_HTTP;
103    }
104}
105
106
107
108static void
109addTask( void * vtask )
110{
111    struct tr_web_task * task = vtask;
112    const tr_handle * session = task->session;
113
114    if( session && session->web )
115    {
116        struct tr_web * web = session->web;
117        CURL * ch;
118
119        ++web->remain;
120        dbgmsg( "adding task #%lu [%s] (%d remain)", task->tag, task->url, web->remain );
121
122        ch = curl_easy_init( );
123
124        if( getenv( "TRANSMISSION_LIBCURL_VERBOSE" ) != 0 )
125            curl_easy_setopt( ch, CURLOPT_VERBOSE, 1 );
126
127        if( !task->range && session->isProxyEnabled ) {
128            curl_easy_setopt( ch, CURLOPT_PROXY, session->proxy );
129            curl_easy_setopt( ch, CURLOPT_PROXYPORT, session->proxyPort );
130            curl_easy_setopt( ch, CURLOPT_PROXYTYPE, getCurlProxyType( session->proxyType ) );
131            curl_easy_setopt( ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
132        }
133        if( !task->range && session->isProxyAuthEnabled ) {
134            char * str = tr_strdup_printf( "%s:%s", session->proxyUsername, session->proxyPassword );
135            curl_easy_setopt( ch, CURLOPT_PROXYUSERPWD, str );
136            tr_free( str );
137        }
138
139        curl_easy_setopt( ch, CURLOPT_PRIVATE, task );
140        curl_easy_setopt( ch, CURLOPT_URL, task->url );
141        curl_easy_setopt( ch, CURLOPT_WRITEFUNCTION, writeFunc );
142        curl_easy_setopt( ch, CURLOPT_WRITEDATA, task );
143        curl_easy_setopt( ch, CURLOPT_USERAGENT, TR_NAME "/" LONG_VERSION_STRING );
144        curl_easy_setopt( ch, CURLOPT_SSL_VERIFYHOST, 0 );
145        curl_easy_setopt( ch, CURLOPT_SSL_VERIFYPEER, 0 );
146        curl_easy_setopt( ch, CURLOPT_FORBID_REUSE, 1 );
147        curl_easy_setopt( ch, CURLOPT_NOSIGNAL, 1 );
148        curl_easy_setopt( ch, CURLOPT_FOLLOWLOCATION, 1 );
149        curl_easy_setopt( ch, CURLOPT_MAXREDIRS, 5 );
150        curl_easy_setopt( ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
151        if( task->range )
152            curl_easy_setopt( ch, CURLOPT_RANGE, task->range );
153        else /* don't set encoding if range is sent; it messes up binary data */
154            curl_easy_setopt( ch, CURLOPT_ENCODING, "" );
155        curl_multi_add_handle( web->cm, ch );
156    }
157}
158
159void
160tr_webRun( tr_session         * session,
161           const char         * url,
162           const char         * range,
163           tr_web_done_func   * done_func,
164           void               * done_func_user_data )
165{
166    if( session->web )
167    {
168        static unsigned long tag = 0;
169        struct tr_web_task * task;
170
171        task = tr_new0( struct tr_web_task, 1 );
172        task->session = session;
173        task->url = tr_strdup( url );
174        task->range = tr_strdup( range );
175        task->done_func = done_func;
176        task->done_func_user_data = done_func_user_data;
177        task->tag = ++tag;
178        task->response = evbuffer_new( );
179
180        tr_runInEventThread( session, addTask, task );
181    }
182}
183
184static void
185webDestroy( tr_web * web )
186{
187    dbgmsg( "deleting web timer" );
188    assert( !web->running );
189    evtimer_del( &web->timer );
190    curl_multi_cleanup( web->cm );
191    tr_free( web );
192}
193
194/* libevent says that sock is ready to be processed, so wake up libcurl */
195static void
196event_callback( int sock, short action, void * vweb )
197{
198    tr_web * web = vweb;
199    CURLMcode rc;
200    int mask;
201
202#if 0
203    static const char *strings[] = {
204        "NONE","TIMEOUT","READ","TIMEOUT|READ","WRITE","TIMEOUT|WRITE",
205        "READ|WRITE","TIMEOUT|READ|WRITE","SIGNAL" };
206     fprintf( stderr, "%s:%d (%s) event on socket %d (%s)\n",
207              __FILE__, __LINE__, __FUNCTION__,
208              sock, strings[action] );
209#endif
210
211    mask = 0;
212    if( action & EV_READ  ) mask |= CURL_CSELECT_IN;
213    if( action & EV_WRITE ) mask |= CURL_CSELECT_OUT;
214
215    do
216        rc = curl_multi_socket_action( web->cm, sock, mask, &web->remain );
217    while( rc == CURLM_CALL_MULTI_PERFORM );
218
219    if ( rc != CURLM_OK  )
220        tr_err( "%s (%d)", curl_multi_strerror(rc), (int)sock );
221
222    processCompletedTasks( web );
223}
224
225/* libcurl wants us to tell it when sock is ready to be processed */
226static int
227socket_callback( CURL            * easy UNUSED,
228                 curl_socket_t     sock,
229                 int               action,
230                 void            * vweb,
231                 void            * assigndata )
232{
233    tr_web * web = vweb;
234    int events = EV_PERSIST;
235    struct event * ev = assigndata;
236
237    if( ev )
238        event_del( ev );
239    else {
240        ev = tr_new0( struct event, 1 );
241        curl_multi_assign( web->cm, sock, ev );
242    }
243
244#if 0
245    {
246        static const char *actions[] = {"NONE", "IN", "OUT", "INOUT", "REMOVE"};
247        fprintf( stderr, "%s:%d (%s) callback on socket %d (%s)\n",
248                 __FILE__, __LINE__, __FUNCTION__,
249                 (int)sock, actions[action]);
250    }
251#endif
252
253    switch (action) {
254        case CURL_POLL_IN: events |= EV_READ; break;
255        case CURL_POLL_OUT: events |= EV_WRITE; break;
256        case CURL_POLL_INOUT: events |= EV_READ|EV_WRITE; break;
257        case CURL_POLL_REMOVE: tr_free( ev ); /* fallthrough */
258        case CURL_POLL_NONE: return 0;
259        default: tr_err( "Unknown socket action %d", action ); return -1;
260    }
261
262    event_set( ev, sock, events, event_callback, web );
263    event_add( ev, NULL );
264    return 0;
265}
266
267/* libevent says that timeout_ms have passed, so wake up libcurl */
268static void
269timeout_callback( int socket UNUSED, short action UNUSED, void * vweb )
270{
271    CURLMcode rc;
272    tr_web * web = vweb;
273
274    do
275        rc = curl_multi_socket( web->cm, CURL_SOCKET_TIMEOUT, &web->remain );
276    while( rc == CURLM_CALL_MULTI_PERFORM );
277
278    if( rc != CURLM_OK )
279        tr_err( "%s", curl_multi_strerror( rc ) );
280
281    processCompletedTasks( web );
282}
283
284/* libcurl wants us to tell it when timeout_ms have passed */
285static void
286timer_callback( CURLM *multi UNUSED, long timeout_ms, void * vweb )
287{
288    tr_web * web = vweb;
289    struct timeval timeout = tr_timevalMsec( timeout_ms );
290    evtimer_add( &web->timer, &timeout );
291}
292
293tr_web*
294tr_webInit( tr_session * session )
295{
296    static int curlInited = FALSE;
297    tr_web * web;
298
299    /* call curl_global_init if we haven't done it already.
300     * try to enable ssl for https support; but if that fails,
301     * try a plain vanilla init */ 
302    if( curlInited == FALSE ) {
303        curlInited = TRUE;
304        if( curl_global_init( CURL_GLOBAL_SSL ) )
305            curl_global_init( 0 );
306    }
307   
308    web = tr_new0( struct tr_web, 1 );
309    web->cm = curl_multi_init( );
310    web->session = session;
311
312    evtimer_set( &web->timer, timeout_callback, web );
313    curl_multi_setopt( web->cm, CURLMOPT_SOCKETDATA, web );
314    curl_multi_setopt( web->cm, CURLMOPT_SOCKETFUNCTION, socket_callback );
315    curl_multi_setopt( web->cm, CURLMOPT_TIMERDATA, web );
316    curl_multi_setopt( web->cm, CURLMOPT_TIMERFUNCTION, timer_callback );
317
318    return web;
319}
320
321void
322tr_webClose( tr_web ** web_in )
323{
324    tr_web * web = *web_in;
325    *web_in = NULL;
326
327    if( !web->running )
328        webDestroy( web );
329    else
330        web->dying = 1;
331}
332
333/***
334****
335***/
336
337static struct http_msg {
338    long code;
339    const char * text;
340} http_msg[] = {
341    { 101, "Switching Protocols" },
342    { 200, "OK" },
343    { 201, "Created" },
344    { 202, "Accepted" },
345    { 203, "Non-Authoritative Information" },
346    { 204, "No Content" },
347    { 205, "Reset Content" },
348    { 206, "Partial Content" },
349    { 300, "Multiple Choices" },
350    { 301, "Moved Permanently" },
351    { 302, "Found" },
352    { 303, "See Other" },
353    { 304, "Not Modified" },
354    { 305, "Use Proxy" },
355    { 306, "(Unused)" },
356    { 307, "Temporary Redirect" },
357    { 400, "Bad Request" },
358    { 401, "Unauthorized" },
359    { 402, "Payment Required" },
360    { 403, "Forbidden" },
361    { 404, "Not Found" },
362    { 405, "Method Not Allowed" },
363    { 406, "Not Acceptable" },
364    { 407, "Proxy Authentication Required" },
365    { 408, "Request Timeout" },
366    { 409, "Conflict" },
367    { 410, "Gone" },
368    { 411, "Length Required" },
369    { 412, "Precondition Failed" },
370    { 413, "Request Entity Too Large" },
371    { 414, "Request-URI Too Long" },
372    { 415, "Unsupported Media Type" },
373    { 416, "Requested Range Not Satisfiable" },
374    { 417, "Expectation Failed" },
375    { 500, "Internal Server Error" },
376    { 501, "Not Implemented" },
377    { 502, "Bad Gateway" },
378    { 503, "Service Unavailable" },
379    { 504, "Gateway Timeout" },
380    { 505, "HTTP Version Not Supported" },
381    { 0, NULL }
382};
383
384static int
385compareResponseCodes( const void * va, const void * vb )
386{
387    const long a = *(const long*) va;
388    const struct http_msg * b = vb;
389    return a - b->code;
390}
391
392const char *
393tr_webGetResponseStr( long code )
394{
395    struct http_msg * msg = bsearch( &code,
396                                     http_msg, 
397                                     sizeof( http_msg ) / sizeof( http_msg[0] ),
398                                     sizeof( http_msg[0] ),
399                                     compareResponseCodes );
400    return msg ? msg->text : "Unknown Error";
401}
Note: See TracBrowser for help on using the repository browser.