aee2d89a29c6ef92fb18796434b029ddbd0ef2ea
[m6w6/ext-http] / http_request_pool_api.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
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-2007, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 /* $Id$ */
14
15 #define HTTP_WANT_CURL
16 #define HTTP_WANT_EVENT
17 #include "php_http.h"
18
19 #if defined(ZEND_ENGINE_2) && defined(HTTP_HAVE_CURL)
20
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"
28
29 #ifndef HTTP_DEBUG_REQPOOLS
30 # define HTTP_DEBUG_REQPOOLS 0
31 #endif
32
33 #ifdef HTTP_HAVE_EVENT
34 typedef struct _http_request_pool_event_t {
35 struct event evnt;
36 http_request_pool *pool;
37 } http_request_pool_event;
38
39 static int http_request_pool_socket_callback(CURL *easy, curl_socket_t s, int action, void *, void *);
40 #endif
41
42 static int http_request_pool_compare_handles(void *h1, void *h2);
43
44 PHP_MINIT_FUNCTION(http_request_pool)
45 {
46 if (SUCCESS != http_persistent_handle_provide("http_request_pool", curl_multi_init, (http_persistent_handle_dtor) curl_multi_cleanup, NULL)) {
47 return FAILURE;
48 }
49 return SUCCESS;
50 }
51
52 #ifdef HTTP_HAVE_EVENT
53 PHP_RINIT_FUNCTION(http_request_pool)
54 {
55 if (!HTTP_G->request.pool.event.base && !(HTTP_G->request.pool.event.base = event_init())) {
56 return FAILURE;
57 }
58
59 return SUCCESS;
60 }
61 #endif
62
63 /* {{{ http_request_pool *http_request_pool_init(http_request_pool *) */
64 PHP_HTTP_API http_request_pool *_http_request_pool_init(http_request_pool *pool TSRMLS_DC)
65 {
66 zend_bool free_pool;
67
68 #if HTTP_DEBUG_REQPOOLS
69 fprintf(stderr, "Initializing request pool %p\n", pool);
70 #endif
71
72 if ((free_pool = (!pool))) {
73 pool = emalloc(sizeof(http_request_pool));
74 pool->ch = NULL;
75 }
76
77 if (SUCCESS != http_persistent_handle_acquire("http_request_pool", &pool->ch)) {
78 if (free_pool) {
79 efree(pool);
80 }
81 return NULL;
82 }
83
84 TSRMLS_SET_CTX(pool->tsrm_ls);
85
86 #if HTTP_HAVE_EVENT
87 curl_multi_setopt(pool->ch, CURLMOPT_SOCKETDATA, pool);
88 curl_multi_setopt(pool->ch, CURLMOPT_SOCKETFUNCTION, http_request_pool_socket_callback);
89 #endif
90
91 pool->unfinished = 0;
92 zend_llist_init(&pool->finished, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
93 zend_llist_init(&pool->handles, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
94
95 #if HTTP_DEBUG_REQPOOLS
96 fprintf(stderr, "Initialized request pool %p\n", pool);
97 #endif
98
99 return pool;
100 }
101 /* }}} */
102
103 /* {{{ STATUS http_request_pool_attach(http_request_pool *, zval *) */
104 PHP_HTTP_API STATUS _http_request_pool_attach(http_request_pool *pool, zval *request)
105 {
106 TSRMLS_FETCH_FROM_CTX(pool->tsrm_ls);
107 getObjectEx(http_request_object, req, request);
108
109 #if HTTP_DEBUG_REQPOOLS
110 fprintf(stderr, "Attaching HttpRequest(#%d) %p to pool %p\n", Z_OBJ_HANDLE_P(request), req, pool);
111 #endif
112
113 if (req->pool) {
114 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");
115 } else if (SUCCESS != http_request_object_requesthandler(req, request)) {
116 http_error_ex(HE_WARNING, HTTP_E_REQUEST, "Could not initialize HttpRequest object(#%d) for attaching to the HttpRequestPool", Z_OBJ_HANDLE_P(request));
117 } else {
118 CURLMcode code = curl_multi_add_handle(pool->ch, req->request->ch);
119
120 if ((CURLM_OK != code) && (CURLM_CALL_MULTI_PERFORM != code)) {
121 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));
122 } else {
123 req->pool = pool;
124
125 ZVAL_ADDREF(request);
126 zend_llist_add_element(&pool->handles, &request);
127
128 #if HTTP_DEBUG_REQPOOLS
129 fprintf(stderr, "> %d HttpRequests attached to pool %p\n", zend_llist_count(&pool->handles), pool);
130 #endif
131 return SUCCESS;
132 }
133 }
134 return FAILURE;
135 }
136 /* }}} */
137
138 /* {{{ STATUS http_request_pool_detach(http_request_pool *, zval *) */
139 PHP_HTTP_API STATUS _http_request_pool_detach(http_request_pool *pool, zval *request)
140 {
141 CURLMcode code;
142 TSRMLS_FETCH_FROM_CTX(pool->tsrm_ls);
143 getObjectEx(http_request_object, req, request);
144
145 #if HTTP_DEBUG_REQPOOLS
146 fprintf(stderr, "Detaching HttpRequest(#%d) %p from pool %p\n", Z_OBJ_HANDLE_P(request), req, pool);
147 #endif
148
149 if (!req->pool) {
150 /* not attached to any pool */
151 #if HTTP_DEBUG_REQPOOLS
152 fprintf(stderr, "HttpRequest object(#%d) %p is not attached to any HttpRequestPool\n", Z_OBJ_HANDLE_P(request), req);
153 #endif
154 } else if (req->pool != pool) {
155 http_error_ex(HE_WARNING, HTTP_E_INVALID_PARAM, "HttpRequest object(#%d) is not attached to this HttpRequestPool", Z_OBJ_HANDLE_P(request));
156 } else if (req->request->_in_progress_cb) {
157 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));
158 } else if (CURLM_OK != (code = curl_multi_remove_handle(pool->ch, req->request->ch))) {
159 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));
160 } else {
161 req->pool = NULL;
162 zend_llist_del_element(&pool->finished, request, http_request_pool_compare_handles);
163 zend_llist_del_element(&pool->handles, request, http_request_pool_compare_handles);
164
165 #if HTTP_DEBUG_REQPOOLS
166 fprintf(stderr, "> %d HttpRequests remaining in pool %p\n", zend_llist_count(&pool->handles), pool);
167 #endif
168
169 return SUCCESS;
170 }
171 return FAILURE;
172 }
173 /* }}} */
174
175 /* {{{ void http_request_pool_apply(http_request_pool *, http_request_pool_apply_func) */
176 PHP_HTTP_API void _http_request_pool_apply(http_request_pool *pool, http_request_pool_apply_func cb)
177 {
178 int count = zend_llist_count(&pool->handles);
179
180 if (count) {
181 int i = 0;
182 zend_llist_position pos;
183 zval **handle, **handles = emalloc(count * sizeof(zval *));
184
185 for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) {
186 handles[i++] = *handle;
187 }
188
189 /* should never happen */
190 if (i != count) {
191 zend_error(E_ERROR, "number of fetched request handles do not match overall count");
192 count = i;
193 }
194
195 for (i = 0; i < count; ++i) {
196 if (cb(pool, handles[i])) {
197 break;
198 }
199 }
200 efree(handles);
201 }
202 }
203 /* }}} */
204
205 /* {{{ void http_request_pool_apply_with_arg(http_request_pool *, http_request_pool_apply_with_arg_func, void *) */
206 PHP_HTTP_API void _http_request_pool_apply_with_arg(http_request_pool *pool, http_request_pool_apply_with_arg_func cb, void *arg)
207 {
208 int count = zend_llist_count(&pool->handles);
209
210 if (count) {
211 int i = 0;
212 zend_llist_position pos;
213 zval **handle, **handles = emalloc(count * sizeof(zval *));
214
215 for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) {
216 handles[i++] = *handle;
217 }
218
219 /* should never happen */
220 if (i != count) {
221 zend_error(E_ERROR, "number of fetched request handles do not match overall count");
222 count = i;
223 }
224
225 for (i = 0; i < count; ++i) {
226 if (cb(pool, handles[i], arg)) {
227 break;
228 }
229 }
230 efree(handles);
231 }
232 }
233 /* }}} */
234
235 /* {{{ void http_request_pool_detach_all(http_request_pool *) */
236 PHP_HTTP_API void _http_request_pool_detach_all(http_request_pool *pool)
237 {
238 #if HTTP_DEBUG_REQPOOLS
239 fprintf(stderr, "Detaching %d requests from pool %p\n", zend_llist_count(&pool->handles), pool);
240 #endif
241 http_request_pool_apply(pool, _http_request_pool_detach);
242 }
243 /* }}} */
244
245 /* {{{ STATUS http_request_pool_send(http_request_pool *) */
246 PHP_HTTP_API STATUS _http_request_pool_send(http_request_pool *pool)
247 {
248 TSRMLS_FETCH_FROM_CTX(pool->tsrm_ls);
249 #ifdef HTTP_HAVE_EVENT
250 CURLMcode rc;
251
252 do {
253 rc = curl_multi_socket_all(pool->ch, &pool->unfinished);
254 } while (CURLM_CALL_MULTI_PERFORM == rc);
255
256 if (CURLM_OK != rc) {
257 http_error(HE_WARNING, HTTP_E_SOCKET, curl_multi_strerror(rc));
258 return FAILURE;
259 }
260
261 event_base_dispatch(HTTP_G->request.pool.event.base);
262
263 return SUCCESS;
264 #else
265
266 # if HTTP_DEBUG_REQPOOLS
267 fprintf(stderr, "Attempt to send %d requests of pool %p\n", zend_llist_count(&pool->handles), pool);
268 # endif
269
270 while (http_request_pool_perform(pool)) {
271 if (SUCCESS != http_request_pool_select(pool)) {
272 # ifdef PHP_WIN32
273 /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */
274 http_error_ex(HE_WARNING, HTTP_E_SOCKET, "WinSock error: %d", WSAGetLastError());
275 # else
276 http_error(HE_WARNING, HTTP_E_SOCKET, strerror(errno));
277 # endif
278 return FAILURE;
279 }
280 }
281
282 # if HTTP_DEBUG_REQPOOLS
283 fprintf(stderr, "Finished sending %d HttpRequests of pool %p (still unfinished: %d)\n", zend_llist_count(&pool->handles), pool, pool->unfinished);
284 # endif
285
286 return SUCCESS;
287 #endif
288 }
289 /* }}} */
290
291 /* {{{ void http_request_pool_dtor(http_request_pool *) */
292 PHP_HTTP_API void _http_request_pool_dtor(http_request_pool *pool)
293 {
294 TSRMLS_FETCH_FROM_CTX(pool->tsrm_ls);
295
296 #if HTTP_DEBUG_REQPOOLS
297 fprintf(stderr, "Destructing request pool %p\n", pool);
298 #endif
299
300 pool->unfinished = 0;
301 zend_llist_clean(&pool->finished);
302 zend_llist_clean(&pool->handles);
303 http_persistent_handle_release("http_request_pool", &pool->ch);
304 }
305 /* }}} */
306
307 #ifdef PHP_WIN32
308 # define SELECT_ERROR SOCKET_ERROR
309 #else
310 # define SELECT_ERROR -1
311 #endif
312
313 /* {{{ STATUS http_request_pool_select(http_request_pool *) */
314 PHP_HTTP_API STATUS _http_request_pool_select(http_request_pool *pool)
315 {
316 #ifdef HTTP_HAVE_EVENT
317 TSRMLS_FETCH_FROM_CTX(pool->tsrm_ls);
318 http_error(HE_WARNING, HTTP_E_RUNTIME, "not implemented");
319 return FAILURE;
320 #else
321 int MAX;
322 fd_set R, W, E;
323 struct timeval timeout = {1, 0};
324 # ifdef HAVE_CURL_MULTI_TIMEOUT
325 long max_tout = 1000;
326
327 if ((CURLM_OK == curl_multi_timeout(pool->ch, &max_tout)) && (max_tout != -1)) {
328 timeout.tv_sec = max_tout / 1000;
329 timeout.tv_usec = (max_tout % 1000) * 1000;
330 }
331 # endif
332
333 FD_ZERO(&R);
334 FD_ZERO(&W);
335 FD_ZERO(&E);
336
337 if (CURLM_OK == curl_multi_fdset(pool->ch, &R, &W, &E, &MAX)) {
338 if (MAX == -1) {
339 http_sleep((double) timeout.tv_sec + (double) (timeout.tv_usec / HTTP_MCROSEC));
340 return SUCCESS;
341 } else if (SELECT_ERROR != select(MAX + 1, &R, &W, &E, &timeout)) {
342 return SUCCESS;
343 }
344 }
345 return FAILURE;
346 #endif
347 }
348 /* }}} */
349
350 /* {{{ int http_request_pool_perform(http_request_pool *) */
351 PHP_HTTP_API int _http_request_pool_perform(http_request_pool *pool)
352 {
353 TSRMLS_FETCH_FROM_CTX(pool->tsrm_ls);
354 #ifdef HTTP_HAVE_EVENT
355 http_error(HE_WARNING, HTTP_E_RUNTIME, "not implemented");
356 return FAILURE;
357 #else
358 CURLMsg *msg;
359 int remaining = 0;
360
361 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(pool->ch, &pool->unfinished));
362
363 while ((msg = curl_multi_info_read(pool->ch, &remaining))) {
364 if (CURLMSG_DONE == msg->msg) {
365 if (CURLE_OK != msg->data.result) {
366 http_request *r = NULL;
367 curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &r);
368 http_error_ex(HE_WARNING, HTTP_E_REQUEST, "%s; %s (%s)", curl_easy_strerror(msg->data.result), r?r->_error:"", r?r->url:"");
369 }
370 http_request_pool_apply_with_arg(pool, _http_request_pool_responsehandler, msg->easy_handle);
371 }
372 }
373
374 return pool->unfinished;
375 #endif
376 }
377 /* }}} */
378
379 /* {{{ void http_request_pool_responsehandler(http_request_pool *, zval *, void *) */
380 int _http_request_pool_responsehandler(http_request_pool *pool, zval *req, void *ch)
381 {
382 TSRMLS_FETCH_FROM_CTX(pool->tsrm_ls);
383 getObjectEx(http_request_object, obj, req);
384
385 if ((!ch) || obj->request->ch == (CURL *) ch) {
386
387 #if HTTP_DEBUG_REQPOOLS
388 fprintf(stderr, "Fetching data from HttpRequest(#%d) %p of pool %p\n", Z_OBJ_HANDLE_P(req), obj, obj->pool);
389 #endif
390
391 ZVAL_ADDREF(req);
392 zend_llist_add_element(&obj->pool->finished, &req);
393 http_request_object_responsehandler(obj, req);
394 return 1;
395 }
396 return 0;
397 }
398 /* }}} */
399
400 /*#*/
401
402 /* {{{ static int http_request_pool_compare_handles(void *, void *) */
403 static int http_request_pool_compare_handles(void *h1, void *h2)
404 {
405 return (Z_OBJ_HANDLE_PP((zval **) h1) == Z_OBJ_HANDLE_P((zval *) h2));
406 }
407 /* }}} */
408
409 #ifdef HTTP_HAVE_EVENT
410 static void http_request_pool_event_callback(int socket, short action, void *event_data)
411 {
412 CURLMcode rc;
413 CURLMsg *msg;
414 int remaining;
415 http_request_pool_event *ev = event_data;
416 http_request_pool *pool = ev->pool;
417 TSRMLS_FETCH_FROM_CTX(ev->pool->tsrm_ls);
418
419 #if HTTP_DEBUG_REQPOOLS
420 {
421 static const char event_strings[][20] = {"TIMEOUT","READ","TIMEOUT|READ","WRITE","TIMEOUT|WRITE","READ|WRITE","TIMEOUT|READ|WRITE","SIGNAL"};
422 fprintf(stderr, "Event on socket %d (%s) event %p of pool %p\n", socket, event_strings[action], ev, pool);
423 }
424 #endif
425
426 do {
427 #ifdef HAVE_CURL_MULTI_SOCKET_ACTION
428 switch (action & (EV_READ|EV_WRITE)) {
429 case EV_READ:
430 rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_IN, &pool->unfinished);
431 break;
432 case EV_WRITE:
433 rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_OUT, &pool->unfinished);
434 break;
435 case EV_READ|EV_WRITE:
436 rc = curl_multi_socket_action(pool->chm socket, CURL_CSELECT_IN|CURL_CSELECT_OUT, &pool->unfinished);
437 break;
438 default:
439 http_error(HE_WARNING, HTTP_E_SOCKET, "Unknown event %d", (int) action);
440 return;
441 }
442 #else
443 rc = curl_multi_socket(pool->ch, socket, &pool->unfinished);
444 #endif
445 } while (CURLM_CALL_MULTI_PERFORM == rc);
446
447 /* don't use 'ev' below here, as it might 've been freed in the socket callback */
448
449 if (CURLM_OK != rc) {
450 http_error(HE_WARNING, HTTP_E_SOCKET, curl_multi_strerror(rc));
451 }
452
453 while ((msg = curl_multi_info_read(pool->ch, &remaining))) {
454 if (CURLMSG_DONE == msg->msg) {
455 if (CURLE_OK != msg->data.result) {
456 http_request *r = NULL;
457 curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &r);
458 http_error_ex(HE_WARNING, HTTP_E_REQUEST, "%s; %s (%s)", curl_easy_strerror(msg->data.result), r?r->_error:"", r?r->url:"");
459 }
460 http_request_pool_apply_with_arg(pool, _http_request_pool_responsehandler, msg->easy_handle);
461 }
462 }
463 }
464
465 static int http_request_pool_socket_callback(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data)
466 {
467 int events = EV_PERSIST;
468 http_request_pool *pool = socket_data;
469 http_request_pool_event *ev = assign_data;
470 TSRMLS_FETCH_FROM_CTX(pool->tsrm_ls);
471
472 if (!ev) {
473 ev = ecalloc(1, sizeof(http_request_pool_event));
474 ev->pool = pool;
475 curl_multi_assign(pool->ch, sock, ev);
476 } else {
477 event_del(&ev->evnt);
478 }
479
480 #if HTTP_DEBUG_REQPOOLS
481 {
482 static const char action_strings[][8] = {"NONE", "IN", "OUT", "INOUT", "REMOVE"};
483 fprintf(stderr, "Callback on socket %d (%s) event %p of pool %p\n", (int) sock, action_strings[action], ev, pool);
484 }
485 #endif
486
487 switch (action) {
488 case CURL_POLL_IN:
489 events |= EV_READ;
490 break;
491 case CURL_POLL_OUT:
492 events |= EV_WRITE;
493 break;
494 case CURL_POLL_INOUT:
495 events |= EV_READ|EV_WRITE;
496 break;
497
498 case CURL_POLL_REMOVE:
499 efree(ev);
500 case CURL_POLL_NONE:
501 return 0;
502
503 default:
504 http_error_ex(HE_WARNING, HTTP_E_SOCKET, "Unknown socket action %d", action);
505 return -1;
506 }
507
508 event_set(&ev->evnt, sock, events, http_request_pool_event_callback, ev);
509 event_base_set(HTTP_G->request.pool.event.base, &ev->evnt);
510 event_add(&ev->evnt, NULL);
511
512 return 0;
513 }
514 #endif /* HTTP_HAVE_EVENT */
515
516 #endif /* ZEND_ENGINE_2 && HTTP_HAVE_CURL */
517
518
519 /*
520 * Local variables:
521 * tab-width: 4
522 * c-basic-offset: 4
523 * End:
524 * vim600: noet sw=4 ts=4 fdm=marker
525 * vim<600: noet sw=4 ts=4
526 */
527