2 +--------------------------------------------------------------------+
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2010, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
15 #define HTTP_WANT_CURL
16 #define HTTP_WANT_EVENT
19 #if defined(ZEND_ENGINE_2) && defined(HTTP_HAVE_CURL)
21 #include "php_http_api.h"
22 #include "php_http_exception_object.h"
23 #include "php_http_persistent_handle_api.h"
24 #include "php_http_request_api.h"
25 #include "php_http_request_object.h"
26 #include "php_http_request_pool_api.h"
27 #include "php_http_requestpool_object.h"
29 #ifndef HTTP_DEBUG_REQPOOLS
30 # define HTTP_DEBUG_REQPOOLS 0
33 #ifdef HTTP_HAVE_EVENT
34 typedef struct _http_request_pool_event_t
{
36 http_request_pool
*pool
;
37 } http_request_pool_event
;
39 static void http_request_pool_timeout_callback(int socket
, short action
, void *event_data
);
40 static void http_request_pool_event_callback(int socket
, short action
, void *event_data
);
41 static int http_request_pool_socket_callback(CURL
*easy
, curl_socket_t s
, int action
, void *, void *);
42 static void http_request_pool_timer_callback(CURLM
*multi
, long timeout_ms
, void *timer_data
);
45 static int http_request_pool_compare_handles(void *h1
, void *h2
);
47 PHP_MINIT_FUNCTION(http_request_pool
)
49 if (SUCCESS
!= http_persistent_handle_provide("http_request_pool", curl_multi_init
, (http_persistent_handle_dtor
) curl_multi_cleanup
, NULL
)) {
55 #ifdef HTTP_HAVE_EVENT
56 PHP_RINIT_FUNCTION(http_request_pool
)
58 if (!HTTP_G
->request
.pool
.event
.base
&& !(HTTP_G
->request
.pool
.event
.base
= event_init())) {
66 /* {{{ http_request_pool *http_request_pool_init(http_request_pool *) */
67 PHP_HTTP_API http_request_pool
*_http_request_pool_init(http_request_pool
*pool TSRMLS_DC
)
71 #if HTTP_DEBUG_REQPOOLS
72 fprintf(stderr
, "Initializing request pool %p\n", pool
);
75 if ((free_pool
= (!pool
))) {
76 pool
= emalloc(sizeof(http_request_pool
));
80 if (SUCCESS
!= http_persistent_handle_acquire("http_request_pool", &pool
->ch
)) {
87 TSRMLS_SET_CTX(pool
->tsrm_ls
);
89 #ifdef HTTP_HAVE_EVENT
90 pool
->timeout
= ecalloc(1, sizeof(struct event
));
91 curl_multi_setopt(pool
->ch
, CURLMOPT_SOCKETDATA
, pool
);
92 curl_multi_setopt(pool
->ch
, CURLMOPT_SOCKETFUNCTION
, http_request_pool_socket_callback
);
93 curl_multi_setopt(pool
->ch
, CURLMOPT_TIMERDATA
, pool
);
94 curl_multi_setopt(pool
->ch
, CURLMOPT_TIMERFUNCTION
, http_request_pool_timer_callback
);
98 zend_llist_init(&pool
->finished
, sizeof(zval
*), (llist_dtor_func_t
) ZVAL_PTR_DTOR
, 0);
99 zend_llist_init(&pool
->handles
, sizeof(zval
*), (llist_dtor_func_t
) ZVAL_PTR_DTOR
, 0);
101 #if HTTP_DEBUG_REQPOOLS
102 fprintf(stderr
, "Initialized request pool %p\n", pool
);
109 /* {{{ STATUS http_request_pool_attach(http_request_pool *, zval *) */
110 PHP_HTTP_API STATUS
_http_request_pool_attach(http_request_pool
*pool
, zval
*request
)
113 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
115 getObjectEx(http_request_object
, req
, request
);
117 #if HTTP_DEBUG_REQPOOLS
118 fprintf(stderr
, "Attaching HttpRequest(#%d) %p to pool %p\n", Z_OBJ_HANDLE_P(request
), req
, pool
);
122 http_error_ex(HE_WARNING
, HTTP_E_INVALID_PARAM
, "HttpRequest object(#%d) is already member of %s HttpRequestPool", Z_OBJ_HANDLE_P(request
), req
->pool
== pool
? "this" : "another");
123 } else if (SUCCESS
!= http_request_object_requesthandler(req
, request
)) {
124 http_error_ex(HE_WARNING
, HTTP_E_REQUEST
, "Could not initialize HttpRequest object(#%d) for attaching to the HttpRequestPool", Z_OBJ_HANDLE_P(request
));
126 CURLMcode code
= curl_multi_add_handle(pool
->ch
, req
->request
->ch
);
128 if (CURLM_OK
!= code
) {
129 http_error_ex(HE_WARNING
, HTTP_E_REQUEST_POOL
, "Could not attach HttpRequest object(#%d) to the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request
), curl_multi_strerror(code
));
133 ZVAL_ADDREF(request
);
134 zend_llist_add_element(&pool
->handles
, &request
);
137 #if HTTP_DEBUG_REQPOOLS
138 fprintf(stderr
, "> %d HttpRequests attached to pool %p\n", zend_llist_count(&pool
->handles
), pool
);
147 /* {{{ STATUS http_request_pool_detach(http_request_pool *, zval *) */
148 PHP_HTTP_API STATUS
_http_request_pool_detach(http_request_pool
*pool
, zval
*request
)
152 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
154 getObjectEx(http_request_object
, req
, request
);
156 #if HTTP_DEBUG_REQPOOLS
157 fprintf(stderr
, "Detaching HttpRequest(#%d) %p from pool %p\n", Z_OBJ_HANDLE_P(request
), req
, pool
);
161 /* not attached to any pool */
162 #if HTTP_DEBUG_REQPOOLS
163 fprintf(stderr
, "HttpRequest object(#%d) %p is not attached to any HttpRequestPool\n", Z_OBJ_HANDLE_P(request
), req
);
165 } else if (req
->pool
!= pool
) {
166 http_error_ex(HE_WARNING
, HTTP_E_INVALID_PARAM
, "HttpRequest object(#%d) is not attached to this HttpRequestPool", Z_OBJ_HANDLE_P(request
));
167 } else if (req
->request
->_in_progress_cb
) {
168 http_error_ex(HE_WARNING
, HTTP_E_REQUEST_POOL
, "HttpRequest object(#%d) cannot be detached from the HttpRequestPool while executing the progress callback", Z_OBJ_HANDLE_P(request
));
169 } else if (CURLM_OK
!= (code
= curl_multi_remove_handle(pool
->ch
, req
->request
->ch
))) {
170 http_error_ex(HE_WARNING
, HTTP_E_REQUEST_POOL
, "Could not detach HttpRequest object(#%d) from the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request
), curl_multi_strerror(code
));
173 zend_llist_del_element(&pool
->finished
, request
, http_request_pool_compare_handles
);
174 zend_llist_del_element(&pool
->handles
, request
, http_request_pool_compare_handles
);
176 #if HTTP_DEBUG_REQPOOLS
177 fprintf(stderr
, "> %d HttpRequests remaining in pool %p\n", zend_llist_count(&pool
->handles
), pool
);
186 /* {{{ void http_request_pool_apply(http_request_pool *, http_request_pool_apply_func) */
187 PHP_HTTP_API
void _http_request_pool_apply(http_request_pool
*pool
, http_request_pool_apply_func cb
)
189 int count
= zend_llist_count(&pool
->handles
);
193 zend_llist_position pos
;
194 zval
**handle
, **handles
= emalloc(count
* sizeof(zval
*));
196 for (handle
= zend_llist_get_first_ex(&pool
->handles
, &pos
); handle
; handle
= zend_llist_get_next_ex(&pool
->handles
, &pos
)) {
197 handles
[i
++] = *handle
;
200 /* should never happen */
202 zend_error(E_ERROR
, "number of fetched request handles do not match overall count");
206 for (i
= 0; i
< count
; ++i
) {
207 if (cb(pool
, handles
[i
])) {
216 /* {{{ void http_request_pool_apply_with_arg(http_request_pool *, http_request_pool_apply_with_arg_func, void *) */
217 PHP_HTTP_API
void _http_request_pool_apply_with_arg(http_request_pool
*pool
, http_request_pool_apply_with_arg_func cb
, void *arg
)
219 int count
= zend_llist_count(&pool
->handles
);
223 zend_llist_position pos
;
224 zval
**handle
, **handles
= emalloc(count
* sizeof(zval
*));
226 for (handle
= zend_llist_get_first_ex(&pool
->handles
, &pos
); handle
; handle
= zend_llist_get_next_ex(&pool
->handles
, &pos
)) {
227 handles
[i
++] = *handle
;
230 /* should never happen */
232 zend_error(E_ERROR
, "number of fetched request handles do not match overall count");
236 for (i
= 0; i
< count
; ++i
) {
237 if (cb(pool
, handles
[i
], arg
)) {
246 /* {{{ void http_request_pool_detach_all(http_request_pool *) */
247 PHP_HTTP_API
void _http_request_pool_detach_all(http_request_pool
*pool
)
249 #if HTTP_DEBUG_REQPOOLS
250 fprintf(stderr
, "Detaching %d requests from pool %p\n", zend_llist_count(&pool
->handles
), pool
);
252 http_request_pool_apply(pool
, _http_request_pool_detach
);
256 /* {{{ STATUS http_request_pool_send(http_request_pool *) */
257 PHP_HTTP_API STATUS
_http_request_pool_send(http_request_pool
*pool
)
259 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
261 #if HTTP_DEBUG_REQPOOLS
262 fprintf(stderr
, "Attempt to send %d requests of pool %p\n", zend_llist_count(&pool
->handles
), pool
);
265 #ifdef HTTP_HAVE_EVENT
266 if (pool
->useevents
) {
268 #if HTTP_DEBUG_REQPOOLS
269 fprintf(stderr
, "& Starting event dispatcher of pool %p\n", pool
);
271 event_base_dispatch(HTTP_G
->request
.pool
.event
.base
);
272 } while (pool
->unfinished
);
276 while (http_request_pool_perform(pool
)) {
277 if (SUCCESS
!= http_request_pool_select(pool
)) {
279 /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */
280 http_error_ex(HE_WARNING
, HTTP_E_SOCKET
, "WinSock error: %d", WSAGetLastError());
282 http_error(HE_WARNING
, HTTP_E_SOCKET
, strerror(errno
));
289 #if HTTP_DEBUG_REQPOOLS
290 fprintf(stderr
, "Finished sending %d HttpRequests of pool %p (still unfinished: %d)\n", zend_llist_count(&pool
->handles
), pool
, pool
->unfinished
);
297 /* {{{ void http_request_pool_dtor(http_request_pool *) */
298 PHP_HTTP_API
void _http_request_pool_dtor(http_request_pool
*pool
)
300 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
302 #if HTTP_DEBUG_REQPOOLS
303 fprintf(stderr
, "Destructing request pool %p\n", pool
);
306 #ifdef HTTP_HAVE_EVENT
307 efree(pool
->timeout
);
310 http_request_pool_detach_all(pool
);
312 pool
->unfinished
= 0;
313 zend_llist_clean(&pool
->finished
);
314 zend_llist_clean(&pool
->handles
);
315 http_persistent_handle_release("http_request_pool", &pool
->ch
);
320 # define SELECT_ERROR SOCKET_ERROR
322 # define SELECT_ERROR -1
325 /* {{{ STATUS http_request_pool_select(http_request_pool *) */
326 PHP_HTTP_API STATUS
_http_request_pool_select(http_request_pool
*pool
)
328 return http_request_pool_select_ex(pool
, NULL
);
332 /* {{{ STATUS http_request_pool_select_ex(http_request_pool *, struct timeval *) */
333 PHP_HTTP_API STATUS
_http_request_pool_select_ex(http_request_pool
*pool
, struct timeval
*custom_timeout
)
337 struct timeval timeout
;
339 #ifdef HTTP_HAVE_EVENT
340 if (pool
->useevents
) {
341 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
342 http_error(HE_WARNING
, HTTP_E_RUNTIME
, "not implemented; use HttpRequest callbacks");
347 if (custom_timeout
&& timerisset(custom_timeout
)) {
348 timeout
= *custom_timeout
;
350 http_request_pool_timeout(pool
, &timeout
);
357 if (CURLM_OK
== curl_multi_fdset(pool
->ch
, &R
, &W
, &E
, &MAX
)) {
359 http_sleep((double) timeout
.tv_sec
+ (double) (timeout
.tv_usec
/ HTTP_MCROSEC
));
361 } else if (SELECT_ERROR
!= select(MAX
+ 1, &R
, &W
, &E
, &timeout
)) {
369 /* {{{ int http_request_pool_perform(http_request_pool *) */
370 PHP_HTTP_API
int _http_request_pool_perform(http_request_pool
*pool
)
372 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
374 #ifdef HTTP_HAVE_EVENT
375 if (pool
->useevents
) {
376 http_error(HE_WARNING
, HTTP_E_RUNTIME
, "not implemented; use HttpRequest callbacks");
381 while (CURLM_CALL_MULTI_PERFORM
== curl_multi_perform(pool
->ch
, &pool
->unfinished
));
383 #if HTTP_DEBUG_REQPOOLS
384 fprintf(stderr
, "%u unfinished requests of pool %p remaining\n", pool
->unfinished
, pool
);
387 http_request_pool_responsehandler(pool
);
389 return pool
->unfinished
;
393 /* {{{ void http_request_pool_responsehandler(http_request_pool *) */
394 void _http_request_pool_responsehandler(http_request_pool
*pool
)
398 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
401 msg
= curl_multi_info_read(pool
->ch
, &remaining
);
402 if (msg
&& CURLMSG_DONE
== msg
->msg
) {
403 if (CURLE_OK
!= msg
->data
.result
) {
404 http_request_storage
*st
= http_request_storage_get(msg
->easy_handle
);
405 http_error_ex(HE_WARNING
, HTTP_E_REQUEST
, "%s; %s (%s)", curl_easy_strerror(msg
->data
.result
), st
?st
->errorbuffer
:"", st
?st
->url
:"");
407 http_request_pool_apply_with_arg(pool
, _http_request_pool_apply_responsehandler
, msg
->easy_handle
);
413 /* {{{ int http_request_pool_apply_responsehandler(http_request_pool *, zval *, void *) */
414 int _http_request_pool_apply_responsehandler(http_request_pool
*pool
, zval
*req
, void *ch
)
417 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
419 getObjectEx(http_request_object
, obj
, req
);
421 if ((!ch
) || obj
->request
->ch
== (CURL
*) ch
) {
423 #if HTTP_DEBUG_REQPOOLS
424 fprintf(stderr
, "Fetching data from HttpRequest(#%d) %p of pool %p\n", Z_OBJ_HANDLE_P(req
), obj
, obj
->pool
);
428 zend_llist_add_element(&obj
->pool
->finished
, &req
);
429 http_request_object_responsehandler(obj
, req
);
436 /* {{{ struct timeval *_http_request_pool_timeout(http_request_pool *, struct timeval *) */
437 struct timeval
*_http_request_pool_timeout(http_request_pool
*pool
, struct timeval
*timeout
)
439 #ifdef HAVE_CURL_MULTI_TIMEOUT
440 long max_tout
= 1000;
442 if ((CURLM_OK
== curl_multi_timeout(pool
->ch
, &max_tout
)) && (max_tout
> 0)) {
443 timeout
->tv_sec
= max_tout
/ 1000;
444 timeout
->tv_usec
= (max_tout
% 1000) * 1000;
448 timeout
->tv_usec
= 1000;
449 #ifdef HAVE_CURL_MULTI_TIMEOUT
453 #if HTTP_DEBUG_REQPOOLS
454 fprintf(stderr
, "Calculating timeout (%lu, %lu) of pool %p\n", (ulong
) timeout
->tv_sec
, (ulong
) timeout
->tv_usec
, pool
);
463 /* {{{ static int http_request_pool_compare_handles(void *, void *) */
464 static int http_request_pool_compare_handles(void *h1
, void *h2
)
466 return (Z_OBJ_HANDLE_PP((zval
**) h1
) == Z_OBJ_HANDLE_P((zval
*) h2
));
470 #ifdef HTTP_HAVE_EVENT
471 /* {{{ static void http_request_pool_timeout_callback(int, short, void *) */
472 static void http_request_pool_timeout_callback(int socket
, short action
, void *event_data
)
474 http_request_pool
*pool
= event_data
;
476 if (pool
->useevents
) {
478 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
480 #if HTTP_DEBUG_REQPOOLS
481 fprintf(stderr
, "Timeout occurred of pool %p\n", pool
);
484 while (CURLM_CALL_MULTI_PERFORM
== (rc
= curl_multi_socket(pool
->ch
, CURL_SOCKET_TIMEOUT
, &pool
->unfinished
)));
486 if (CURLM_OK
!= rc
) {
487 http_error(HE_WARNING
, HTTP_E_SOCKET
, curl_multi_strerror(rc
));
490 http_request_pool_responsehandler(pool
);
495 /* {{{ static void http_request_pool_event_callback(int, short, void *) */
496 static void http_request_pool_event_callback(int socket
, short action
, void *event_data
)
498 http_request_pool_event
*ev
= event_data
;
499 http_request_pool
*pool
= ev
->pool
;
501 if (pool
->useevents
) {
502 CURLMcode rc
= CURLE_OK
;
503 TSRMLS_FETCH_FROM_CTX(ev
->pool
->tsrm_ls
);
505 #if HTTP_DEBUG_REQPOOLS
507 static const char event_strings
[][20] = {"NONE","TIMEOUT","READ","TIMEOUT|READ","WRITE","TIMEOUT|WRITE","READ|WRITE","TIMEOUT|READ|WRITE","SIGNAL"};
508 fprintf(stderr
, "Event on socket %d (%s) event %p of pool %p\n", socket
, event_strings
[action
], ev
, pool
);
512 /* don't use 'ev' below this loop as it might 've been freed in the socket callback */
514 #ifdef HAVE_CURL_MULTI_SOCKET_ACTION
515 switch (action
& (EV_READ
|EV_WRITE
)) {
517 rc
= curl_multi_socket_action(pool
->ch
, socket
, CURL_CSELECT_IN
, &pool
->unfinished
);
520 rc
= curl_multi_socket_action(pool
->ch
, socket
, CURL_CSELECT_OUT
, &pool
->unfinished
);
522 case EV_READ
|EV_WRITE
:
523 rc
= curl_multi_socket_action(pool
->ch
, socket
, CURL_CSELECT_IN
|CURL_CSELECT_OUT
, &pool
->unfinished
);
526 http_error_ex(HE_WARNING
, HTTP_E_SOCKET
, "Unknown event %d", (int) action
);
530 rc
= curl_multi_socket(pool
->ch
, socket
, &pool
->unfinished
);
532 } while (CURLM_CALL_MULTI_PERFORM
== rc
);
535 case CURLM_BAD_SOCKET
:
537 fprintf(stderr
, "!!! Bad socket: %d (%d)\n", socket
, (int) action
);
542 http_error(HE_WARNING
, HTTP_E_SOCKET
, curl_multi_strerror(rc
));
546 http_request_pool_responsehandler(pool
);
548 /* remove timeout if there are no transfers left */
549 if (!pool
->unfinished
&& event_initialized(pool
->timeout
) && event_pending(pool
->timeout
, EV_TIMEOUT
, NULL
)) {
550 event_del(pool
->timeout
);
551 #if HTTP_DEBUG_REQPOOLS
552 fprintf(stderr
, "Removed timeout of pool %p\n", pool
);
559 /* {{{ static int http_request_pool_socket_callback(CURL *, curl_socket_t, int, void *, void *) */
560 static int http_request_pool_socket_callback(CURL
*easy
, curl_socket_t sock
, int action
, void *socket_data
, void *assign_data
)
562 http_request_pool
*pool
= socket_data
;
564 if (pool
->useevents
) {
565 int events
= EV_PERSIST
;
566 http_request_pool_event
*ev
= assign_data
;
567 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
570 ev
= ecalloc(1, sizeof(http_request_pool_event
));
572 curl_multi_assign(pool
->ch
, sock
, ev
);
573 event_base_set(HTTP_G
->request
.pool
.event
.base
, &ev
->evnt
);
575 event_del(&ev
->evnt
);
578 #if HTTP_DEBUG_REQPOOLS
580 static const char action_strings
[][8] = {"NONE", "IN", "OUT", "INOUT", "REMOVE"};
582 curl_easy_getinfo(easy
, CURLINFO_PRIVATE
, &r
);
583 fprintf(stderr
, "Callback on socket %2d (%8s) event %p of pool %p (%d)\n", (int) sock
, action_strings
[action
], ev
, pool
, pool
->unfinished
);
594 case CURL_POLL_INOUT
:
595 events
|= EV_READ
|EV_WRITE
;
598 case CURL_POLL_REMOVE
:
604 http_error_ex(HE_WARNING
, HTTP_E_SOCKET
, "Unknown socket action %d", action
);
608 event_set(&ev
->evnt
, sock
, events
, http_request_pool_event_callback
, ev
);
609 event_add(&ev
->evnt
, NULL
);
616 /* {{{ static void http_request_pool_timer_callback(CURLM *, long, void*) */
617 static void http_request_pool_timer_callback(CURLM
*multi
, long timeout_ms
, void *timer_data
)
619 http_request_pool
*pool
= timer_data
;
621 if (pool
->useevents
) {
622 TSRMLS_FETCH_FROM_CTX(pool
->tsrm_ls
);
623 struct timeval timeout
;
625 if (!event_initialized(pool
->timeout
)) {
626 event_set(pool
->timeout
, -1, 0, http_request_pool_timeout_callback
, pool
);
627 event_base_set(HTTP_G
->request
.pool
.event
.base
, pool
->timeout
);
628 } else if (event_pending(pool
->timeout
, EV_TIMEOUT
, NULL
)) {
629 event_del(pool
->timeout
);
632 if (timeout_ms
> 0) {
633 timeout
.tv_sec
= timeout_ms
/ 1000;
634 timeout
.tv_usec
= (timeout_ms
% 1000) * 1000;
636 http_request_pool_timeout(pool
, &timeout
);
639 event_add(pool
->timeout
, &timeout
);
641 #if HTTP_DEBUG_REQPOOLS
642 fprintf(stderr
, "Updating timeout %lu (%lu, %lu) of pool %p\n", (ulong
) timeout_ms
, (ulong
) timeout
.tv_sec
, (ulong
) timeout
.tv_usec
, pool
);
647 #endif /* HTTP_HAVE_EVENT */
649 #endif /* ZEND_ENGINE_2 && HTTP_HAVE_CURL */
657 * vim600: noet sw=4 ts=4 fdm=marker
658 * vim<600: noet sw=4 ts=4