d1873fb5b754e6ad717114c0ec31987c3db974ae
[m6w6/ext-http] / php_http_curl_client_pool.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-2011, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14
15 #if PHP_HTTP_HAVE_CURL
16
17 #if PHP_HTTP_HAVE_EVENT
18 # include <event.h>
19 #endif
20
21 typedef struct php_http_curl_client_pool {
22 CURLM *handle;
23
24 int unfinished; /* int because of curl_multi_perform() */
25
26 #if PHP_HTTP_HAVE_EVENT
27 struct event *timeout;
28 unsigned useevents:1;
29 unsigned runsocket:1;
30 #endif
31 } php_http_curl_client_pool_t;
32
33 static void *php_http_curlm_ctor(void *opaque TSRMLS_DC)
34 {
35 return curl_multi_init();
36 }
37
38 static void php_http_curlm_dtor(void *opaque, void *handle TSRMLS_DC)
39 {
40 curl_multi_cleanup(handle);
41 }
42
43
44 static void php_http_curl_client_pool_responsehandler(php_http_client_pool_t *pool)
45 {
46 int remaining = 0;
47 zval **requests;
48 php_http_curl_client_pool_t *curl = pool->ctx;
49 TSRMLS_FETCH_FROM_CTX(pool->ts);
50
51 do {
52 CURLMsg *msg = curl_multi_info_read(curl->handle, &remaining);
53
54 if (msg && CURLMSG_DONE == msg->msg) {
55 zval **request;
56
57 if (CURLE_OK != msg->data.result) {
58 php_http_curl_client_storage_t *st = get_storage(msg->easy_handle);
59 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT, "%s; %s (%s)", curl_easy_strerror(msg->data.result), STR_PTR(st->errorbuffer), STR_PTR(st->url));
60 }
61
62 php_http_client_pool_requests(pool, &requests, NULL);
63 for (request = requests; *request; ++request) {
64 php_http_client_object_t *obj = zend_object_store_get_object(*request TSRMLS_CC);
65
66 if (msg->easy_handle == ((php_http_curl_client_t *) (obj->client->ctx))->handle) {
67 Z_ADDREF_PP(request);
68 zend_llist_add_element(&pool->clients.finished, request);
69 php_http_client_object_handle_response(*request TSRMLS_CC);
70 }
71
72 zval_ptr_dtor(request);
73 }
74 efree(requests);
75 }
76 } while (remaining);
77 }
78
79
80 #if PHP_HTTP_HAVE_EVENT
81
82 typedef struct php_http_client_pool_event {
83 struct event evnt;
84 php_http_client_pool_t *pool;
85 } php_http_client_pool_event_t;
86
87 static inline int etoca(short action) {
88 switch (action & (EV_READ|EV_WRITE)) {
89 case EV_READ:
90 return CURL_CSELECT_IN;
91 break;
92 case EV_WRITE:
93 return CURL_CSELECT_OUT;
94 break;
95 case EV_READ|EV_WRITE:
96 return CURL_CSELECT_IN|CURL_CSELECT_OUT;
97 break;
98 default:
99 return 0;
100 }
101 }
102
103 static void php_http_curl_client_pool_timeout_callback(int socket, short action, void *event_data)
104 {
105 php_http_client_pool_t *pool = event_data;
106 php_http_curl_client_pool_t *curl = pool->ctx;
107
108 #if DBG_EVENTS
109 fprintf(stderr, "T");
110 #endif
111 if (curl->useevents) {
112 CURLMcode rc;
113 TSRMLS_FETCH_FROM_CTX(pool->ts);
114
115 while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle, socket, etoca(action), &curl->unfinished)));
116
117 if (CURLM_OK != rc) {
118 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "%s", curl_multi_strerror(rc));
119 }
120
121 php_http_curl_client_pool_responsehandler(pool);
122 }
123 }
124
125 static void php_http_curl_client_pool_event_callback(int socket, short action, void *event_data)
126 {
127 php_http_client_pool_t *pool = event_data;
128 php_http_curl_client_pool_t *curl = pool->ctx;
129
130 #if DBG_EVENTS
131 fprintf(stderr, "E");
132 #endif
133 if (curl->useevents) {
134 CURLMcode rc = CURLE_OK;
135 TSRMLS_FETCH_FROM_CTX(pool->ts);
136
137 while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle, socket, etoca(action), &curl->unfinished)));
138
139 if (CURLM_OK != rc) {
140 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "%s", curl_multi_strerror(rc));
141 }
142
143 php_http_curl_client_pool_responsehandler(pool);
144
145 /* remove timeout if there are no transfers left */
146 if (!curl->unfinished && event_initialized(curl->timeout) && event_pending(curl->timeout, EV_TIMEOUT, NULL)) {
147 event_del(curl->timeout);
148 }
149 }
150 }
151
152 static int php_http_curl_client_pool_socket_callback(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data)
153 {
154 php_http_client_pool_t *pool = socket_data;
155 php_http_curl_client_pool_t *curl = pool->ctx;
156
157 #if DBG_EVENTS
158 fprintf(stderr, "S");
159 #endif
160 if (curl->useevents) {
161 int events = EV_PERSIST;
162 php_http_client_pool_event_t *ev = assign_data;
163 TSRMLS_FETCH_FROM_CTX(pool->ts);
164
165 if (!ev) {
166 ev = ecalloc(1, sizeof(php_http_client_pool_event_t));
167 ev->pool = pool;
168 curl_multi_assign(curl->handle, sock, ev);
169 event_base_set(PHP_HTTP_G->curl.event_base, &ev->evnt);
170 } else {
171 event_del(&ev->evnt);
172 }
173
174 switch (action) {
175 case CURL_POLL_IN:
176 events |= EV_READ;
177 break;
178 case CURL_POLL_OUT:
179 events |= EV_WRITE;
180 break;
181 case CURL_POLL_INOUT:
182 events |= EV_READ|EV_WRITE;
183 break;
184
185 case CURL_POLL_REMOVE:
186 efree(ev);
187 /* no break */
188 case CURL_POLL_NONE:
189 return 0;
190
191 default:
192 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "Unknown socket action %d", action);
193 return -1;
194 }
195
196 event_set(&ev->evnt, sock, events, php_http_curl_client_pool_event_callback, pool);
197 event_add(&ev->evnt, NULL);
198 }
199
200 return 0;
201 }
202
203 static void php_http_curl_client_pool_timer_callback(CURLM *multi, long timeout_ms, void *timer_data)
204 {
205 php_http_client_pool_t *pool = timer_data;
206 php_http_curl_client_pool_t *curl = pool->ctx;
207
208 #if DBG_EVENTS
209 fprintf(stderr, "%ld", timeout_ms);
210 #endif
211 if (curl->useevents) {
212
213 if (timeout_ms < 0) {
214 php_http_curl_client_pool_timeout_callback(CURL_SOCKET_TIMEOUT, CURL_CSELECT_IN|CURL_CSELECT_OUT, pool);
215 } else if (timeout_ms > 0 || !event_initialized(curl->timeout) || !event_pending(curl->timeout, EV_TIMEOUT, NULL)) {
216 struct timeval timeout;
217 TSRMLS_FETCH_FROM_CTX(pool->ts);
218
219 if (!event_initialized(curl->timeout)) {
220 event_set(curl->timeout, -1, 0, php_http_curl_client_pool_timeout_callback, pool);
221 event_base_set(PHP_HTTP_G->curl.event_base, curl->timeout);
222 } else if (event_pending(curl->timeout, EV_TIMEOUT, NULL)) {
223 event_del(curl->timeout);
224 }
225
226 timeout.tv_sec = timeout_ms / 1000;
227 timeout.tv_usec = (timeout_ms % 1000) * 1000;
228
229 event_add(curl->timeout, &timeout);
230 }
231 }
232 }
233
234 #endif /* HAVE_EVENT */
235
236
237 /* pool handler ops */
238
239 static php_http_client_pool_t *php_http_curl_client_pool_init(php_http_client_pool_t *h, void *handle)
240 {
241 php_http_curl_client_pool_t *curl;
242 TSRMLS_FETCH_FROM_CTX(h->ts);
243
244 if (!handle && !(handle = php_http_resource_factory_handle_ctor(h->rf TSRMLS_CC))) {
245 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "could not initialize curl pool handle");
246 return NULL;
247 }
248
249 curl = ecalloc(1, sizeof(*curl));
250 curl->handle = handle;
251 curl->unfinished = 0;
252 h->ctx = curl;
253
254 return h;
255 }
256
257 static void php_http_curl_client_pool_dtor(php_http_client_pool_t *h)
258 {
259 php_http_curl_client_pool_t *curl = h->ctx;
260 TSRMLS_FETCH_FROM_CTX(h->ts);
261
262 #if PHP_HTTP_HAVE_EVENT
263 if (curl->timeout) {
264 efree(curl->timeout);
265 curl->timeout = NULL;
266 }
267 #endif
268 curl->unfinished = 0;
269 php_http_client_pool_reset(h);
270
271 php_http_resource_factory_handle_dtor(h->rf, curl->handle TSRMLS_CC);
272
273 efree(curl);
274 h->ctx = NULL;
275 }
276
277 static STATUS php_http_curl_client_pool_attach(php_http_client_pool_t *h, php_http_client_t *r, php_http_message_t *m)
278 {
279 php_http_curl_client_pool_t *curl = h->ctx;
280 php_http_curl_client_t *recurl = r->ctx;
281 CURLMcode rs;
282 TSRMLS_FETCH_FROM_CTX(h->ts);
283
284 if (r->ops != php_http_curl_client_get_ops()) {
285 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Cannot attach a non-curl client to this pool");
286 return FAILURE;
287 }
288
289 if (SUCCESS != php_http_curl_client_prepare(r, m)) {
290 return FAILURE;
291 }
292
293 if (CURLM_OK == (rs = curl_multi_add_handle(curl->handle, recurl->handle))) {
294 ++curl->unfinished;
295 return SUCCESS;
296 } else {
297 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Could not attach request to pool: %s", curl_multi_strerror(rs));
298 return FAILURE;
299 }
300 }
301
302 static STATUS php_http_curl_client_pool_detach(php_http_client_pool_t *h, php_http_client_t *r)
303 {
304 php_http_curl_client_pool_t *curl = h->ctx;
305 php_http_curl_client_t *recurl = r->ctx;
306 CURLMcode rs;
307 TSRMLS_FETCH_FROM_CTX(h->ts);
308
309 if (r->ops != php_http_curl_client_get_ops()) {
310 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Cannot attach a non-curl client to this pool");
311 return FAILURE;
312 }
313
314 if (CURLM_OK == (rs = curl_multi_remove_handle(curl->handle, recurl->handle))) {
315 return SUCCESS;
316 } else {
317 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Could not detach request from pool: %s", curl_multi_strerror(rs));
318 return FAILURE;
319 }
320 }
321
322 #ifdef PHP_WIN32
323 # define SELECT_ERROR SOCKET_ERROR
324 #else
325 # define SELECT_ERROR -1
326 #endif
327
328 static STATUS php_http_curl_client_pool_wait(php_http_client_pool_t *h, struct timeval *custom_timeout)
329 {
330 int MAX;
331 fd_set R, W, E;
332 struct timeval timeout;
333 php_http_curl_client_pool_t *curl = h->ctx;
334
335 #if PHP_HTTP_HAVE_EVENT
336 if (curl->useevents) {
337 TSRMLS_FETCH_FROM_CTX(h->ts);
338
339 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented");
340 return FAILURE;
341 }
342 #endif
343
344 if (custom_timeout && timerisset(custom_timeout)) {
345 timeout = *custom_timeout;
346 } else {
347 long max_tout = 1000;
348
349 if ((CURLM_OK == curl_multi_timeout(curl->handle, &max_tout)) && (max_tout > 0)) {
350 timeout.tv_sec = max_tout / 1000;
351 timeout.tv_usec = (max_tout % 1000) * 1000;
352 } else {
353 timeout.tv_sec = 0;
354 timeout.tv_usec = 1000;
355 }
356 }
357
358 FD_ZERO(&R);
359 FD_ZERO(&W);
360 FD_ZERO(&E);
361
362 if (CURLM_OK == curl_multi_fdset(curl->handle, &R, &W, &E, &MAX)) {
363 if (MAX == -1) {
364 php_http_sleep((double) timeout.tv_sec + (double) (timeout.tv_usec / PHP_HTTP_MCROSEC));
365 return SUCCESS;
366 } else if (SELECT_ERROR != select(MAX + 1, &R, &W, &E, &timeout)) {
367 return SUCCESS;
368 }
369 }
370 return FAILURE;
371 }
372
373 static int php_http_curl_client_pool_once(php_http_client_pool_t *h)
374 {
375 php_http_curl_client_pool_t *curl = h->ctx;
376
377 #if PHP_HTTP_HAVE_EVENT
378 if (curl->useevents) {
379 TSRMLS_FETCH_FROM_CTX(h->ts);
380 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented");
381 return FAILURE;
382 }
383 #endif
384
385 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl->handle, &curl->unfinished));
386
387 php_http_curl_client_pool_responsehandler(h);
388
389 return curl->unfinished;
390
391 }
392 #if PHP_HTTP_HAVE_EVENT
393 static void dolog(int i, const char *m) {
394 fprintf(stderr, "%d: %s\n", i, m);
395 }
396 #endif
397 static STATUS php_http_curl_client_pool_exec(php_http_client_pool_t *h)
398 {
399 TSRMLS_FETCH_FROM_CTX(h->ts);
400
401 #if PHP_HTTP_HAVE_EVENT
402 php_http_curl_client_pool_t *curl = h->ctx;
403
404 if (curl->useevents) {
405 event_set_log_callback(dolog);
406 do {
407 #if DBG_EVENTS
408 fprintf(stderr, "X");
409 #endif
410 event_base_dispatch(PHP_HTTP_G->curl.event_base);
411 } while (curl->unfinished);
412 } else
413 #endif
414 {
415 while (php_http_curl_client_pool_once(h)) {
416 if (SUCCESS != php_http_curl_client_pool_wait(h, NULL)) {
417 #ifdef PHP_WIN32
418 /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */
419 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "WinSock error: %d", WSAGetLastError());
420 #else
421 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, strerror(errno));
422 #endif
423 return FAILURE;
424 }
425 }
426 }
427
428 return SUCCESS;
429 }
430
431 static STATUS php_http_curl_client_pool_setopt(php_http_client_pool_t *h, php_http_client_pool_setopt_opt_t opt, void *arg)
432 {
433 php_http_curl_client_pool_t *curl = h->ctx;
434
435 switch (opt) {
436 case PHP_HTTP_CLIENT_POOL_OPT_ENABLE_PIPELINING:
437 if (CURLM_OK != curl_multi_setopt(curl->handle, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) {
438 return FAILURE;
439 }
440 break;
441
442 case PHP_HTTP_CLIENT_POOL_OPT_USE_EVENTS:
443 #if PHP_HTTP_HAVE_EVENT
444 if ((curl->useevents = *((zend_bool *) arg))) {
445 if (!curl->timeout) {
446 curl->timeout = ecalloc(1, sizeof(struct event));
447 }
448 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h);
449 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curl_client_pool_socket_callback);
450 curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h);
451 curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curl_client_pool_timer_callback);
452 } else {
453 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL);
454 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL);
455 curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL);
456 curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL);
457 }
458 break;
459 #endif
460
461 default:
462 return FAILURE;
463 }
464 return SUCCESS;
465 }
466
467 static php_http_resource_factory_ops_t php_http_curlm_resource_factory_ops = {
468 php_http_curlm_ctor,
469 NULL,
470 php_http_curlm_dtor
471 };
472
473 static php_http_client_pool_ops_t php_http_curl_client_pool_ops = {
474 &php_http_curlm_resource_factory_ops,
475 php_http_curl_client_pool_init,
476 NULL /* copy */,
477 php_http_curl_client_pool_dtor,
478 NULL /*reset */,
479 php_http_curl_client_pool_exec,
480 php_http_curl_client_pool_wait,
481 php_http_curl_client_pool_once,
482 php_http_curl_client_pool_attach,
483 php_http_curl_client_pool_detach,
484 php_http_curl_client_pool_setopt,
485 (php_http_new_t) php_http_curl_client_pool_object_new_ex,
486 php_http_curl_client_pool_get_class_entry
487 };
488
489 PHP_HTTP_API php_http_client_pool_ops_t *php_http_curl_client_pool_get_ops(void)
490 {
491 return &php_http_curl_client_pool_ops;
492 }
493
494 #define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpClientCURL, method, 0, req_args)
495 #define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpClientCURL, method, 0)
496 #define PHP_HTTP_CURL_ME(method, visibility) PHP_ME(HttpClientCURL, method, PHP_HTTP_ARGS(HttpClientCURL, method), visibility)
497 #define PHP_HTTP_CURL_ALIAS(method, func) PHP_HTTP_STATIC_ME_ALIAS(method, func, PHP_HTTP_ARGS(HttpClientCURL, method))
498 #define PHP_HTTP_CURL_MALIAS(me, al, vis) ZEND_FENTRY(me, ZEND_MN(HttpClientCURL_##al), PHP_HTTP_ARGS(HttpClientCURL, al), vis)
499
500 static zend_class_entry *php_http_curl_client_pool_class_entry;
501
502 zend_class_entry *php_http_curl_client_pool_get_class_entry(void)
503 {
504 return php_http_curl_client_pool_class_entry;
505 }
506
507 static zend_function_entry php_http_curl_client_pool_method_entry[] = {
508 EMPTY_FUNCTION_ENTRY
509 };
510
511 zend_object_value php_http_curl_client_pool_object_new(zend_class_entry *ce TSRMLS_DC)
512 {
513 return php_http_curl_client_pool_object_new_ex(ce, NULL, NULL TSRMLS_CC);
514 }
515
516 zend_object_value php_http_curl_client_pool_object_new_ex(zend_class_entry *ce, php_http_client_pool_t *p, php_http_client_pool_object_t **ptr TSRMLS_DC)
517 {
518 zend_object_value ov;
519 php_http_client_pool_object_t *o;
520
521 o = ecalloc(1, sizeof(php_http_client_pool_object_t));
522 zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
523 #if PHP_VERSION_ID < 50339
524 zend_hash_copy(((zend_object *) o)->properties, &(ce->default_properties), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*));
525 #else
526 object_properties_init((zend_object *) o, ce);
527 #endif
528
529 if (!(o->pool = p)) {
530 o->pool = php_http_client_pool_init(NULL, &php_http_curl_client_pool_ops, NULL, NULL TSRMLS_CC);
531 }
532
533 if (ptr) {
534 *ptr = o;
535 }
536
537 ov.handle = zend_objects_store_put(o, NULL, php_http_client_pool_object_free, NULL TSRMLS_CC);
538 ov.handlers = php_http_client_pool_get_object_handlers();
539
540 return ov;
541 }
542
543
544 PHP_MINIT_FUNCTION(http_curl_client_pool)
545 {
546 if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_client_pool.curl"), &php_http_curlm_resource_factory_ops, NULL, NULL)) {
547 return FAILURE;
548 }
549
550 PHP_HTTP_REGISTER_CLASS(http\\Curl\\Client, Pool, http_curl_client_pool, php_http_client_pool_get_class_entry(), 0);
551 php_http_curl_client_pool_class_entry->create_object = php_http_curl_client_pool_object_new;
552
553 return SUCCESS;
554 }
555
556 PHP_RINIT_FUNCTION(http_curl_client_pool)
557 {
558 #if PHP_HTTP_HAVE_EVENT
559 if (!PHP_HTTP_G->curl.event_base && !(PHP_HTTP_G->curl.event_base = event_init())) {
560 return FAILURE;
561 }
562 #endif
563
564 return SUCCESS;
565 }
566
567 #endif /* PHP_HTTP_HAVE_CURL */
568
569 /*
570 * Local variables:
571 * tab-width: 4
572 * c-basic-offset: 4
573 * End:
574 * vim600: noet sw=4 ts=4 fdm=marker
575 * vim<600: noet sw=4 ts=4
576 */
577