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