fix problem when finishing a chunked encoding stream
[m6w6/ext-http] / php_http_request_pool.c
1
2 #include "php_http.h"
3
4
5 #ifndef PHP_HTTP_DEBUG_REQPOOLS
6 # define PHP_HTTP_DEBUG_REQPOOLS 0
7 #endif
8
9 #ifdef PHP_HTTP_HAVE_EVENT
10 typedef struct php_http_request_pool_event {
11 struct event evnt;
12 php_http_request_pool_t *pool;
13 } php_http_request_pool_event_t;
14
15 static void php_http_request_pool_timeout_callback(int socket, short action, void *event_data);
16 static void php_http_request_pool_event_callback(int socket, short action, void *event_data);
17 static int php_http_request_pool_socket_callback(CURL *easy, curl_socket_t s, int action, void *, void *);
18 static void php_http_request_pool_timer_callback(CURLM *multi, long timeout_ms, void *timer_data);
19 #endif
20
21 static int php_http_request_pool_compare_handles(void *h1, void *h2);
22
23 PHP_HTTP_API php_http_request_pool_t *php_http_request_pool_init(php_http_request_pool_t *pool TSRMLS_DC)
24 {
25 zend_bool free_pool;
26
27 #if PHP_HTTP_DEBUG_REQPOOLS
28 fprintf(stderr, "Initializing request pool %p\n", pool);
29 #endif
30
31 if ((free_pool = (!pool))) {
32 pool = emalloc(sizeof(php_http_request_pool_t));
33 pool->ch = NULL;
34 }
35
36 if (SUCCESS != php_http_persistent_handle_acquire(ZEND_STRL("http_request_pool"), &pool->ch TSRMLS_CC)) {
37 if (free_pool) {
38 efree(pool);
39 }
40 return NULL;
41 }
42
43 TSRMLS_SET_CTX(pool->ts);
44
45 #ifdef PHP_HTTP_HAVE_EVENT
46 pool->timeout = ecalloc(1, sizeof(struct event));
47 curl_multi_setopt(pool->ch, CURLMOPT_SOCKETDATA, pool);
48 curl_multi_setopt(pool->ch, CURLMOPT_SOCKETFUNCTION, php_http_request_pool_socket_callback);
49 curl_multi_setopt(pool->ch, CURLMOPT_TIMERDATA, pool);
50 curl_multi_setopt(pool->ch, CURLMOPT_TIMERFUNCTION, php_http_request_pool_timer_callback);
51 #endif
52
53 pool->unfinished = 0;
54 zend_llist_init(&pool->finished, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
55 zend_llist_init(&pool->handles, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
56
57 #if PHP_HTTP_DEBUG_REQPOOLS
58 fprintf(stderr, "Initialized request pool %p\n", pool);
59 #endif
60
61 return pool;
62 }
63
64 PHP_HTTP_API STATUS php_http_request_pool_attach(php_http_request_pool_t *pool, zval *request)
65 {
66 #ifdef ZTS
67 TSRMLS_FETCH_FROM_CTX(pool->ts);
68 #endif
69 php_http_request_object_t *req = zend_object_store_get_object(request TSRMLS_CC);
70
71 #if PHP_HTTP_DEBUG_REQPOOLS
72 fprintf(stderr, "Attaching HttpRequest(#%d) %p to pool %p\n", Z_OBJ_HANDLE_P(request), req, pool);
73 #endif
74
75 if (req->pool) {
76 php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "HttpRequest object(#%d) is already member of %s HttpRequestPool", Z_OBJ_HANDLE_P(request), req->pool == pool ? "this" : "another");
77 } else if (SUCCESS != php_http_request_object_requesthandler(req, request)) {
78 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not initialize HttpRequest object(#%d) for attaching to the HttpRequestPool", Z_OBJ_HANDLE_P(request));
79 } else {
80 CURLMcode code = curl_multi_add_handle(pool->ch, req->request->ch);
81
82 if (CURLM_OK != code) {
83 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_POOL, "Could not attach HttpRequest object(#%d) to the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request), curl_multi_strerror(code));
84 } else {
85 req->pool = pool;
86
87 Z_ADDREF_P(request);
88 zend_llist_add_element(&pool->handles, &request);
89 ++pool->unfinished;
90
91 #if PHP_HTTP_DEBUG_REQPOOLS
92 fprintf(stderr, "> %d HttpRequests attached to pool %p\n", zend_llist_count(&pool->handles), pool);
93 #endif
94 return SUCCESS;
95 }
96 }
97 return FAILURE;
98 }
99
100 PHP_HTTP_API STATUS php_http_request_pool_detach(php_http_request_pool_t *pool, zval *request)
101 {
102 CURLMcode code;
103 #ifdef ZTS
104 TSRMLS_FETCH_FROM_CTX(pool->ts);
105 #endif
106 php_http_request_object_t *req = zend_object_store_get_object(request TSRMLS_CC);
107
108 #if PHP_HTTP_DEBUG_REQPOOLS
109 fprintf(stderr, "Detaching HttpRequest(#%d) %p from pool %p\n", Z_OBJ_HANDLE_P(request), req, pool);
110 #endif
111
112 if (!req->pool) {
113 /* not attached to any pool */
114 #if PHP_HTTP_DEBUG_REQPOOLS
115 fprintf(stderr, "HttpRequest object(#%d) %p is not attached to any HttpRequestPool\n", Z_OBJ_HANDLE_P(request), req);
116 #endif
117 } else if (req->pool != pool) {
118 php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "HttpRequest object(#%d) is not attached to this HttpRequestPool", Z_OBJ_HANDLE_P(request));
119 } else if (req->request->_progress.in_cb) {
120 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_POOL, "HttpRequest object(#%d) cannot be detached from the HttpRequestPool while executing the progress callback", Z_OBJ_HANDLE_P(request));
121 } else if (CURLM_OK != (code = curl_multi_remove_handle(pool->ch, req->request->ch))) {
122 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_POOL, "Could not detach HttpRequest object(#%d) from the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request), curl_multi_strerror(code));
123 } else {
124 req->pool = NULL;
125 zend_llist_del_element(&pool->finished, request, php_http_request_pool_compare_handles);
126 zend_llist_del_element(&pool->handles, request, php_http_request_pool_compare_handles);
127
128 #if PHP_HTTP_DEBUG_REQPOOLS
129 fprintf(stderr, "> %d HttpRequests remaining in pool %p\n", zend_llist_count(&pool->handles), pool);
130 #endif
131
132 return SUCCESS;
133 }
134 return FAILURE;
135 }
136
137 PHP_HTTP_API void php_http_request_pool_apply(php_http_request_pool_t *pool, php_http_request_pool_apply_func_t cb)
138 {
139 int count = zend_llist_count(&pool->handles);
140
141 if (count) {
142 int i = 0;
143 zend_llist_position pos;
144 zval **handle, **handles = emalloc(count * sizeof(zval *));
145
146 for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) {
147 handles[i++] = *handle;
148 }
149
150 /* should never happen */
151 if (i != count) {
152 zend_error(E_ERROR, "number of fetched request handles do not match overall count");
153 count = i;
154 }
155
156 for (i = 0; i < count; ++i) {
157 if (cb(pool, handles[i])) {
158 break;
159 }
160 }
161 efree(handles);
162 }
163 }
164
165 PHP_HTTP_API void php_http_request_pool_apply_with_arg(php_http_request_pool_t *pool, php_http_request_pool_apply_with_arg_func_t cb, void *arg)
166 {
167 int count = zend_llist_count(&pool->handles);
168
169 if (count) {
170 int i = 0;
171 zend_llist_position pos;
172 zval **handle, **handles = emalloc(count * sizeof(zval *));
173
174 for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) {
175 handles[i++] = *handle;
176 }
177
178 /* should never happen */
179 if (i != count) {
180 zend_error(E_ERROR, "number of fetched request handles do not match overall count");
181 count = i;
182 }
183
184 for (i = 0; i < count; ++i) {
185 if (cb(pool, handles[i], arg)) {
186 break;
187 }
188 }
189 efree(handles);
190 }
191 }
192
193 PHP_HTTP_API void php_http_request_pool_detach_all(php_http_request_pool_t *pool)
194 {
195 #if PHP_HTTP_DEBUG_REQPOOLS
196 fprintf(stderr, "Detaching %d requests from pool %p\n", zend_llist_count(&pool->handles), pool);
197 #endif
198 php_http_request_pool_apply(pool, php_http_request_pool_detach);
199 }
200
201 PHP_HTTP_API STATUS php_http_request_pool_send(php_http_request_pool_t *pool)
202 {
203 TSRMLS_FETCH_FROM_CTX(pool->ts);
204
205 #if PHP_HTTP_DEBUG_REQPOOLS
206 fprintf(stderr, "Attempt to send %d requests of pool %p\n", zend_llist_count(&pool->handles), pool);
207 #endif
208
209 #ifdef PHP_HTTP_HAVE_EVENT
210 if (pool->useevents) {
211 do {
212 #if PHP_HTTP_DEBUG_REQPOOLS
213 fprintf(stderr, "& Starting event dispatcher of pool %p\n", pool);
214 #endif
215 event_base_dispatch(PHP_HTTP_G->request_pool.event_base);
216 } while (pool->unfinished);
217 } else
218 #endif
219 {
220 while (php_http_request_pool_perform(pool)) {
221 if (SUCCESS != php_http_request_pool_select(pool, NULL)) {
222 #ifdef PHP_WIN32
223 /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */
224 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "WinSock error: %d", WSAGetLastError());
225 #else
226 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, strerror(errno));
227 #endif
228 return FAILURE;
229 }
230 }
231 }
232
233 #if PHP_HTTP_DEBUG_REQPOOLS
234 fprintf(stderr, "Finished sending %d HttpRequests of pool %p (still unfinished: %d)\n", zend_llist_count(&pool->handles), pool, pool->unfinished);
235 #endif
236
237 return SUCCESS;
238 }
239
240 PHP_HTTP_API void php_http_request_pool_dtor(php_http_request_pool_t *pool)
241 {
242 TSRMLS_FETCH_FROM_CTX(pool->ts);
243
244 #if PHP_HTTP_DEBUG_REQPOOLS
245 fprintf(stderr, "Destructing request pool %p\n", pool);
246 #endif
247
248 #ifdef PHP_HTTP_HAVE_EVENT
249 efree(pool->timeout);
250 #endif
251
252 php_http_request_pool_detach_all(pool);
253
254 pool->unfinished = 0;
255 zend_llist_clean(&pool->finished);
256 zend_llist_clean(&pool->handles);
257 php_http_persistent_handle_release(ZEND_STRL("php_http_request_pool_t"), &pool->ch TSRMLS_CC);
258 }
259
260 PHP_HTTP_API void php_http_request_pool_free(php_http_request_pool_t **pool) {
261 if (*pool) {
262 php_http_request_pool_dtor(*pool);
263 efree(*pool);
264 *pool = NULL;
265 }
266 }
267
268 #ifdef PHP_WIN32
269 # define SELECT_ERROR SOCKET_ERROR
270 #else
271 # define SELECT_ERROR -1
272 #endif
273
274 PHP_HTTP_API STATUS php_http_request_pool_select(php_http_request_pool_t *pool, struct timeval *custom_timeout)
275 {
276 int MAX;
277 fd_set R, W, E;
278 struct timeval timeout;
279
280 #ifdef PHP_HTTP_HAVE_EVENT
281 if (pool->useevents) {
282 TSRMLS_FETCH_FROM_CTX(pool->ts);
283 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented; use HttpRequest callbacks");
284 return FAILURE;
285 }
286 #endif
287
288 if (custom_timeout && timerisset(custom_timeout)) {
289 timeout = *custom_timeout;
290 } else {
291 php_http_request_pool_timeout(pool, &timeout);
292 }
293
294 FD_ZERO(&R);
295 FD_ZERO(&W);
296 FD_ZERO(&E);
297
298 if (CURLM_OK == curl_multi_fdset(pool->ch, &R, &W, &E, &MAX)) {
299 if (MAX == -1) {
300 php_http_sleep((double) timeout.tv_sec + (double) (timeout.tv_usec / PHP_HTTP_MCROSEC));
301 return SUCCESS;
302 } else if (SELECT_ERROR != select(MAX + 1, &R, &W, &E, &timeout)) {
303 return SUCCESS;
304 }
305 }
306 return FAILURE;
307 }
308
309 PHP_HTTP_API int php_http_request_pool_perform(php_http_request_pool_t *pool)
310 {
311 TSRMLS_FETCH_FROM_CTX(pool->ts);
312
313 #ifdef PHP_HTTP_HAVE_EVENT
314 if (pool->useevents) {
315 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented; use HttpRequest callbacks");
316 return FAILURE;
317 }
318 #endif
319
320 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(pool->ch, &pool->unfinished));
321
322 #if PHP_HTTP_DEBUG_REQPOOLS
323 fprintf(stderr, "%u unfinished requests of pool %p remaining\n", pool->unfinished, pool);
324 #endif
325
326 php_http_request_pool_responsehandler(pool);
327
328 return pool->unfinished;
329 }
330
331 void php_http_request_pool_responsehandler(php_http_request_pool_t *pool)
332 {
333 CURLMsg *msg;
334 int remaining = 0;
335 TSRMLS_FETCH_FROM_CTX(pool->ts);
336
337 do {
338 msg = curl_multi_info_read(pool->ch, &remaining);
339 if (msg && CURLMSG_DONE == msg->msg) {
340 if (CURLE_OK != msg->data.result) {
341 php_http_request_storage_t *st = php_http_request_storage_get(msg->easy_handle);
342 php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "%s; %s (%s)", curl_easy_strerror(msg->data.result), STR_PTR(st->errorbuffer), STR_PTR(st->url));
343 }
344 php_http_request_pool_apply_with_arg(pool, php_http_request_pool_apply_responsehandler, msg->easy_handle);
345 }
346 } while (remaining);
347 }
348
349 int php_http_request_pool_apply_responsehandler(php_http_request_pool_t *pool, zval *req, void *ch)
350 {
351 #ifdef ZTS
352 TSRMLS_FETCH_FROM_CTX(pool->ts);
353 #endif
354 php_http_request_object_t *obj = zend_object_store_get_object(req TSRMLS_CC);
355
356 if ((!ch) || obj->request->ch == (CURL *) ch) {
357
358 #if PHP_HTTP_DEBUG_REQPOOLS
359 fprintf(stderr, "Fetching data from HttpRequest(#%d) %p of pool %p\n", Z_OBJ_HANDLE_P(req), obj, obj->pool);
360 #endif
361
362 Z_ADDREF_P(req);
363 zend_llist_add_element(&obj->pool->finished, &req);
364 php_http_request_object_responsehandler(obj, req);
365 return 1;
366 }
367 return 0;
368 }
369
370 struct timeval *php_http_request_pool_timeout(php_http_request_pool_t *pool, struct timeval *timeout)
371 {
372 #ifdef HAVE_CURL_MULTI_TIMEOUT
373 long max_tout = 1000;
374
375 if ((CURLM_OK == curl_multi_timeout(pool->ch, &max_tout)) && (max_tout > 0)) {
376 timeout->tv_sec = max_tout / 1000;
377 timeout->tv_usec = (max_tout % 1000) * 1000;
378 } else {
379 #endif
380 timeout->tv_sec = 0;
381 timeout->tv_usec = 1000;
382 #ifdef HAVE_CURL_MULTI_TIMEOUT
383 }
384 #endif
385
386 #if PHP_HTTP_DEBUG_REQPOOLS
387 fprintf(stderr, "Calculating timeout (%lu, %lu) of pool %p\n", (ulong) timeout->tv_sec, (ulong) timeout->tv_usec, pool);
388 #endif
389
390 return timeout;
391 }
392
393 /*#*/
394
395 static int php_http_request_pool_compare_handles(void *h1, void *h2)
396 {
397 return (Z_OBJ_HANDLE_PP((zval **) h1) == Z_OBJ_HANDLE_P((zval *) h2));
398 }
399
400 #ifdef PHP_HTTP_HAVE_EVENT
401
402 static void php_http_request_pool_timeout_callback(int socket, short action, void *event_data)
403 {
404 php_http_request_pool_t *pool = event_data;
405
406 if (pool->useevents) {
407 CURLMcode rc;
408 TSRMLS_FETCH_FROM_CTX(pool->ts);
409
410 #if PHP_HTTP_DEBUG_REQPOOLS
411 fprintf(stderr, "Timeout occurred of pool %p\n", pool);
412 #endif
413
414 while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket(pool->ch, CURL_SOCKET_TIMEOUT, &pool->unfinished)));
415
416 if (CURLM_OK != rc) {
417 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, curl_multi_strerror(rc));
418 }
419
420 php_http_request_pool_responsehandler(pool);
421 }
422 }
423
424 static void php_http_request_pool_event_callback(int socket, short action, void *event_data)
425 {
426 php_http_request_pool_event_t *ev = event_data;
427 php_http_request_pool_t *pool = ev->pool;
428
429 if (pool->useevents) {
430 CURLMcode rc = CURLE_OK;
431 TSRMLS_FETCH_FROM_CTX(ev->pool->ts);
432
433 #if PHP_HTTP_DEBUG_REQPOOLS
434 {
435 static const char event_strings[][20] = {"NONE","TIMEOUT","READ","TIMEOUT|READ","WRITE","TIMEOUT|WRITE","READ|WRITE","TIMEOUT|READ|WRITE","SIGNAL"};
436 fprintf(stderr, "Event on socket %d (%s) event %p of pool %p\n", socket, event_strings[action], ev, pool);
437 }
438 #endif
439
440 /* don't use 'ev' below this loop as it might 've been freed in the socket callback */
441 do {
442 #ifdef HAVE_CURL_MULTI_SOCKET_ACTION
443 switch (action & (EV_READ|EV_WRITE)) {
444 case EV_READ:
445 rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_IN, &pool->unfinished);
446 break;
447 case EV_WRITE:
448 rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_OUT, &pool->unfinished);
449 break;
450 case EV_READ|EV_WRITE:
451 rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_IN|CURL_CSELECT_OUT, &pool->unfinished);
452 break;
453 default:
454 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "Unknown event %d", (int) action);
455 return;
456 }
457 #else
458 rc = curl_multi_socket(pool->ch, socket, &pool->unfinished);
459 #endif
460 } while (CURLM_CALL_MULTI_PERFORM == rc);
461
462 switch (rc) {
463 case CURLM_BAD_SOCKET:
464 #if 0
465 fprintf(stderr, "!!! Bad socket: %d (%d)\n", socket, (int) action);
466 #endif
467 case CURLM_OK:
468 break;
469 default:
470 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, curl_multi_strerror(rc));
471 break;
472 }
473
474 php_http_request_pool_responsehandler(pool);
475
476 /* remove timeout if there are no transfers left */
477 if (!pool->unfinished && event_initialized(pool->timeout) && event_pending(pool->timeout, EV_TIMEOUT, NULL)) {
478 event_del(pool->timeout);
479 #if PHP_HTTP_DEBUG_REQPOOLS
480 fprintf(stderr, "Removed timeout of pool %p\n", pool);
481 #endif
482 }
483 }
484 }
485
486 static int php_http_request_pool_socket_callback(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data)
487 {
488 php_http_request_pool_t *pool = socket_data;
489
490 if (pool->useevents) {
491 int events = EV_PERSIST;
492 php_http_request_pool_event_t *ev = assign_data;
493 TSRMLS_FETCH_FROM_CTX(pool->ts);
494
495 if (!ev) {
496 ev = ecalloc(1, sizeof(php_http_request_pool_event_t));
497 ev->pool = pool;
498 curl_multi_assign(pool->ch, sock, ev);
499 event_base_set(PHP_HTTP_G->request_pool.event_base, &ev->evnt);
500 } else {
501 event_del(&ev->evnt);
502 }
503
504 #if PHP_HTTP_DEBUG_REQPOOLS
505 {
506 static const char action_strings[][8] = {"NONE", "IN", "OUT", "INOUT", "REMOVE"};
507 php_http_request_t *r;
508 curl_easy_getinfo(easy, CURLINFO_PRIVATE, &r);
509 fprintf(stderr, "Callback on socket %2d (%8s) event %p of pool %p (%d)\n", (int) sock, action_strings[action], ev, pool, pool->unfinished);
510 }
511 #endif
512
513 switch (action) {
514 case CURL_POLL_IN:
515 events |= EV_READ;
516 break;
517 case CURL_POLL_OUT:
518 events |= EV_WRITE;
519 break;
520 case CURL_POLL_INOUT:
521 events |= EV_READ|EV_WRITE;
522 break;
523
524 case CURL_POLL_REMOVE:
525 efree(ev);
526 case CURL_POLL_NONE:
527 return 0;
528
529 default:
530 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "Unknown socket action %d", action);
531 return -1;
532 }
533
534 event_set(&ev->evnt, sock, events, php_http_request_pool_event_callback, ev);
535 event_add(&ev->evnt, NULL);
536 }
537
538 return 0;
539 }
540
541 static void php_http_request_pool_timer_callback(CURLM *multi, long timeout_ms, void *timer_data)
542 {
543 php_http_request_pool_t *pool = timer_data;
544
545 if (pool->useevents) {
546 TSRMLS_FETCH_FROM_CTX(pool->ts);
547 struct timeval timeout;
548
549 if (!event_initialized(pool->timeout)) {
550 event_set(pool->timeout, -1, 0, php_http_request_pool_timeout_callback, pool);
551 event_base_set(PHP_HTTP_G->request_pool.event_base, pool->timeout);
552 } else if (event_pending(pool->timeout, EV_TIMEOUT, NULL)) {
553 event_del(pool->timeout);
554 }
555
556 if (timeout_ms > 0) {
557 timeout.tv_sec = timeout_ms / 1000;
558 timeout.tv_usec = (timeout_ms % 1000) * 1000;
559 } else {
560 php_http_request_pool_timeout(pool, &timeout);
561 }
562
563 event_add(pool->timeout, &timeout);
564
565 #if PHP_HTTP_DEBUG_REQPOOLS
566 fprintf(stderr, "Updating timeout %lu (%lu, %lu) of pool %p\n", (ulong) timeout_ms, (ulong) timeout.tv_sec, (ulong) timeout.tv_usec, pool);
567 #endif
568 }
569 }
570 #endif /* HAVE_EVENT */
571
572 #define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpRequestPool, method, 0, req_args)
573 #define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpRequestPool, method, 0)
574 #define PHP_HTTP_REQPOOL_ME(method, visibility) PHP_ME(HttpRequestPool, method, PHP_HTTP_ARGS(HttpRequestPool, method), visibility)
575
576 PHP_HTTP_EMPTY_ARGS(__construct);
577
578 PHP_HTTP_EMPTY_ARGS(__destruct);
579 PHP_HTTP_EMPTY_ARGS(reset);
580
581 PHP_HTTP_BEGIN_ARGS(attach, 1)
582 PHP_HTTP_ARG_OBJ(http\\Request, request, 0)
583 PHP_HTTP_END_ARGS;
584
585 PHP_HTTP_BEGIN_ARGS(detach, 1)
586 PHP_HTTP_ARG_OBJ(http\\Request, request, 0)
587 PHP_HTTP_END_ARGS;
588
589 PHP_HTTP_EMPTY_ARGS(send);
590 PHP_HTTP_EMPTY_ARGS(socketPerform);
591 PHP_HTTP_BEGIN_ARGS(socketSelect, 0)
592 PHP_HTTP_ARG_VAL(timeout, 0)
593 PHP_HTTP_END_ARGS;
594
595 PHP_HTTP_EMPTY_ARGS(valid);
596 PHP_HTTP_EMPTY_ARGS(current);
597 PHP_HTTP_EMPTY_ARGS(key);
598 PHP_HTTP_EMPTY_ARGS(next);
599 PHP_HTTP_EMPTY_ARGS(rewind);
600
601 PHP_HTTP_EMPTY_ARGS(count);
602
603 PHP_HTTP_EMPTY_ARGS(getAttachedRequests);
604 PHP_HTTP_EMPTY_ARGS(getFinishedRequests);
605
606 PHP_HTTP_BEGIN_ARGS(enablePipelining, 0)
607 PHP_HTTP_ARG_VAL(enable, 0)
608 PHP_HTTP_END_ARGS;
609
610 PHP_HTTP_BEGIN_ARGS(enableEvents, 0)
611 PHP_HTTP_ARG_VAL(enable, 0)
612 PHP_HTTP_END_ARGS;
613
614 zend_class_entry *php_http_request_pool_class_entry;
615 zend_function_entry php_http_request_pool_method_entry[] = {
616 PHP_HTTP_REQPOOL_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
617 PHP_HTTP_REQPOOL_ME(__destruct, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)
618 PHP_HTTP_REQPOOL_ME(attach, ZEND_ACC_PUBLIC)
619 PHP_HTTP_REQPOOL_ME(detach, ZEND_ACC_PUBLIC)
620 PHP_HTTP_REQPOOL_ME(send, ZEND_ACC_PUBLIC)
621 PHP_HTTP_REQPOOL_ME(reset, ZEND_ACC_PUBLIC)
622
623 PHP_HTTP_REQPOOL_ME(socketPerform, ZEND_ACC_PROTECTED)
624 PHP_HTTP_REQPOOL_ME(socketSelect, ZEND_ACC_PROTECTED)
625
626 /* implements Iterator */
627 PHP_HTTP_REQPOOL_ME(valid, ZEND_ACC_PUBLIC)
628 PHP_HTTP_REQPOOL_ME(current, ZEND_ACC_PUBLIC)
629 PHP_HTTP_REQPOOL_ME(key, ZEND_ACC_PUBLIC)
630 PHP_HTTP_REQPOOL_ME(next, ZEND_ACC_PUBLIC)
631 PHP_HTTP_REQPOOL_ME(rewind, ZEND_ACC_PUBLIC)
632
633 /* implmenents Countable */
634 PHP_HTTP_REQPOOL_ME(count, ZEND_ACC_PUBLIC)
635
636 PHP_HTTP_REQPOOL_ME(getAttachedRequests, ZEND_ACC_PUBLIC)
637 PHP_HTTP_REQPOOL_ME(getFinishedRequests, ZEND_ACC_PUBLIC)
638
639 PHP_HTTP_REQPOOL_ME(enablePipelining, ZEND_ACC_PUBLIC)
640 PHP_HTTP_REQPOOL_ME(enableEvents, ZEND_ACC_PUBLIC)
641
642 EMPTY_FUNCTION_ENTRY
643 };
644 static zend_object_handlers php_http_request_pool_object_handlers;
645
646 zend_object_value php_http_request_pool_object_new(zend_class_entry *ce TSRMLS_DC)
647 {
648 zend_object_value ov;
649 php_http_request_pool_object_t *o;
650
651 o = ecalloc(1, sizeof(php_http_request_pool_object_t));
652 zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
653 object_properties_init((zend_object *) o, ce);
654
655 php_http_request_pool_init(&o->pool TSRMLS_CC);
656
657 ov.handle = zend_objects_store_put(o, NULL, php_http_request_pool_object_free, NULL TSRMLS_CC);
658 ov.handlers = &php_http_request_pool_object_handlers;
659
660 return ov;
661 }
662
663 void php_http_request_pool_object_free(void *object TSRMLS_DC)
664 {
665 php_http_request_pool_object_t *o = (php_http_request_pool_object_t *) object;
666
667 php_http_request_pool_dtor(&o->pool);
668 zend_object_std_dtor((zend_object *) o TSRMLS_CC);
669 efree(o);
670 }
671
672 static void php_http_request_pool_object_llist2array(zval **req, zval *array TSRMLS_DC)
673 {
674 Z_ADDREF_P(*req);
675 add_next_index_zval(array, *req);
676 }
677
678 /* ### USERLAND ### */
679
680 PHP_METHOD(HttpRequestPool, __construct)
681 {
682 with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
683 int argc;
684 zval ***argv;
685
686 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "*", &argv, &argc)) {
687 with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_pool)) {
688 int i;
689 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
690
691 for (i = 0; i < argc; ++i) {
692 if (Z_TYPE_PP(argv[i]) == IS_OBJECT && instanceof_function(Z_OBJCE_PP(argv[i]), php_http_request_class_entry TSRMLS_CC)) {
693 php_http_request_pool_attach(&obj->pool, *(argv[i]));
694 }
695 }
696 } end_error_handling();
697 }
698 } end_error_handling();
699 }
700
701 PHP_METHOD(HttpRequestPool, __destruct)
702 {
703 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
704
705 if (SUCCESS != zend_parse_parameters_none()) {
706 ; /* we always want to clean up */
707 }
708
709 php_http_request_pool_detach_all(&obj->pool);
710 }
711
712 PHP_METHOD(HttpRequestPool, reset)
713 {
714 if (SUCCESS == zend_parse_parameters_none()) {
715 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
716
717 obj->iterator.pos = 0;
718 php_http_request_pool_detach_all(&obj->pool);
719 RETURN_TRUE;
720 }
721 RETURN_FALSE;
722 }
723
724 PHP_METHOD(HttpRequestPool, attach)
725 {
726 RETVAL_FALSE;
727
728 with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
729 zval *request;
730
731 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) {
732 with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
733 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
734
735 if (obj->iterator.pos > 0 && obj->iterator.pos < zend_llist_count(&obj->pool.handles)) {
736 php_http_error(HE_THROW, PHP_HTTP_E_REQUEST_POOL, "Cannot attach to the HttpRequestPool while the iterator is active");
737 } else {
738 RETVAL_SUCCESS(php_http_request_pool_attach(&obj->pool, request));
739 }
740 } end_error_handling();
741 }
742 } end_error_handling();
743 }
744
745 PHP_METHOD(HttpRequestPool, detach)
746 {
747 RETVAL_FALSE;
748
749 with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
750 zval *request;
751
752 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) {
753 with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_pool)) {
754 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
755
756 obj->iterator.pos = -1;
757 RETVAL_SUCCESS(php_http_request_pool_detach(&obj->pool, request));
758 } end_error_handling();
759 }
760 } end_error_handling();
761 }
762
763 PHP_METHOD(HttpRequestPool, send)
764 {
765 RETVAL_FALSE;
766
767 with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
768 if (SUCCESS == zend_parse_parameters_none()) {
769 with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_pool)) {
770 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
771
772 RETVAL_SUCCESS(php_http_request_pool_send(&obj->pool));
773 } end_error_handling();
774 }
775 } end_error_handling();
776 }
777
778 PHP_METHOD(HttpRequestPool, socketPerform)
779 {
780 if (SUCCESS == zend_parse_parameters_none()) {
781 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
782
783 if (0 < php_http_request_pool_perform(&obj->pool)) {
784 RETURN_TRUE;
785 }
786 }
787 RETURN_FALSE;
788 }
789
790 PHP_METHOD(HttpRequestPool, socketSelect)
791 {
792 double timeout = 0;
793
794 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|d", &timeout)) {
795 struct timeval timeout_val;
796 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
797
798 timeout_val.tv_sec = (time_t) timeout;
799 timeout_val.tv_usec = PHP_HTTP_USEC(timeout) % PHP_HTTP_MCROSEC;
800
801 RETURN_SUCCESS(php_http_request_pool_select(&obj->pool, timeout ? &timeout_val : NULL));
802 }
803 RETURN_FALSE;
804 }
805
806 PHP_METHOD(HttpRequestPool, valid)
807 {
808 if (SUCCESS == zend_parse_parameters_none()) {
809 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
810
811 RETURN_BOOL(obj->iterator.pos >= 0 && obj->iterator.pos < zend_llist_count(&obj->pool.handles));
812 }
813 RETURN_FALSE;
814 }
815
816 PHP_METHOD(HttpRequestPool, current)
817 {
818 if (SUCCESS == zend_parse_parameters_none()) {
819 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
820
821 if (obj->iterator.pos < zend_llist_count(&obj->pool.handles)) {
822 long pos = 0;
823 zval **current = NULL;
824 zend_llist_position lpos;
825
826 for ( current = zend_llist_get_first_ex(&obj->pool.handles, &lpos);
827 current && obj->iterator.pos != pos++;
828 current = zend_llist_get_next_ex(&obj->pool.handles, &lpos));
829 if (current) {
830 RETURN_OBJECT(*current, 1);
831 }
832 }
833 }
834 RETURN_FALSE;
835 }
836
837 PHP_METHOD(HttpRequestPool, key)
838 {
839 if (SUCCESS == zend_parse_parameters_none()) {
840 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
841
842 RETURN_LONG(obj->iterator.pos);
843 }
844 RETURN_FALSE;
845 }
846
847 PHP_METHOD(HttpRequestPool, next)
848 {
849 if (SUCCESS == zend_parse_parameters_none()) {
850 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
851
852 ++obj->iterator.pos;
853 }
854 }
855
856 PHP_METHOD(HttpRequestPool, rewind)
857 {
858 if (SUCCESS == zend_parse_parameters_none()) {
859 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
860
861 obj->iterator.pos = 0;
862 }
863 }
864
865 PHP_METHOD(HttpRequestPool, count)
866 {
867 if (SUCCESS == zend_parse_parameters_none()) {
868 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
869
870 RETURN_LONG((long) zend_llist_count(&obj->pool.handles));
871 }
872 }
873
874 PHP_METHOD(HttpRequestPool, getAttachedRequests)
875 {
876 if (SUCCESS == zend_parse_parameters_none()) {
877 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
878
879 array_init(return_value);
880 zend_llist_apply_with_argument(&obj->pool.handles,
881 (llist_apply_with_arg_func_t) php_http_request_pool_object_llist2array,
882 return_value TSRMLS_CC);
883 return;
884 }
885 RETURN_FALSE;
886 }
887
888 PHP_METHOD(HttpRequestPool, getFinishedRequests)
889 {
890 if (SUCCESS == zend_parse_parameters_none()) {
891 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
892
893 array_init(return_value);
894 zend_llist_apply_with_argument(&obj->pool.finished,
895 (llist_apply_with_arg_func_t) php_http_request_pool_object_llist2array,
896 return_value TSRMLS_CC);
897 return;
898 }
899 RETURN_FALSE;
900 }
901
902 PHP_METHOD(HttpRequestPool, enablePipelining)
903 {
904 zend_bool enable = 1;
905
906 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &enable)) {
907 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
908
909 if (CURLM_OK == curl_multi_setopt(obj->pool.ch, CURLMOPT_PIPELINING, (long) enable)) {
910 RETURN_TRUE;
911 }
912 }
913 RETURN_FALSE;
914 }
915
916 PHP_METHOD(HttpRequestPool, enableEvents)
917 {
918 zend_bool enable = 1;
919
920 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &enable)) {
921 #if PHP_HTTP_HAVE_EVENT
922 php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
923
924 obj->pool.useevents = enable;
925 RETURN_TRUE;
926 #endif
927 }
928 RETURN_FALSE;
929 }
930
931 PHP_MINIT_FUNCTION(http_request_pool)
932 {
933 if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_request_pool"), curl_multi_init, (php_http_persistent_handle_dtor_t) curl_multi_cleanup, NULL TSRMLS_CC)) {
934 return FAILURE;
935 }
936
937 PHP_HTTP_REGISTER_CLASS(http\\request, Pool, http_request_pool, php_http_object_class_entry, 0);
938 php_http_request_pool_class_entry->create_object = php_http_request_pool_object_new;
939 memcpy(&php_http_request_pool_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
940 php_http_request_pool_object_handlers.clone_obj = NULL;
941
942 zend_class_implements(php_http_request_pool_class_entry TSRMLS_CC, 2, spl_ce_Countable, zend_ce_iterator);
943
944 return SUCCESS;
945 }
946
947 PHP_RINIT_FUNCTION(http_request_pool)
948 {
949 #ifdef PHP_HTTP_HAVE_EVENT
950 if (!PHP_HTTP_G->request_pool.event_base && !(PHP_HTTP_G->request_pool.event_base = event_init())) {
951 return FAILURE;
952 }
953 #endif
954
955 return SUCCESS;
956 }
957
958