don't crash if user extends abstract classes
[m6w6/ext-http] / php_http_client_pool_curl.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_client_pool_curl {
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_client_pool_curl_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_client_pool_curl_responsehandler(php_http_client_pool_t *pool)
45 {
46 int remaining = 0;
47 zval **requests;
48 php_http_client_pool_curl_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_client_curl_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_client_curl_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_client_pool_curl_timeout_callback(int socket, short action, void *event_data)
104 {
105 php_http_client_pool_t *pool = event_data;
106 php_http_client_pool_curl_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_client_pool_curl_responsehandler(pool);
122 }
123 }
124
125 static void php_http_client_pool_curl_event_callback(int socket, short action, void *event_data)
126 {
127 php_http_client_pool_t *pool = event_data;
128 php_http_client_pool_curl_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_client_pool_curl_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_client_pool_curl_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_client_pool_curl_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_client_pool_curl_event_callback, pool);
197 event_add(&ev->evnt, NULL);
198 }
199
200 return 0;
201 }
202
203 static void php_http_client_pool_curl_timer_callback(CURLM *multi, long timeout_ms, void *timer_data)
204 {
205 php_http_client_pool_t *pool = timer_data;
206 php_http_client_pool_curl_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_client_pool_curl_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_client_pool_curl_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_client_pool_curl_init(php_http_client_pool_t *h, void *handle)
240 {
241 php_http_client_pool_curl_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_client_pool_curl_dtor(php_http_client_pool_t *h)
258 {
259 php_http_client_pool_curl_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_client_pool_curl_attach(php_http_client_pool_t *h, php_http_client_t *r, php_http_message_t *m)
278 {
279 php_http_client_pool_curl_t *curl = h->ctx;
280 php_http_client_curl_t *recurl = r->ctx;
281 CURLMcode rs;
282 TSRMLS_FETCH_FROM_CTX(h->ts);
283
284 if (SUCCESS != php_http_client_curl_prepare(r, m)) {
285 return FAILURE;
286 }
287
288 if (CURLM_OK == (rs = curl_multi_add_handle(curl->handle, recurl->handle))) {
289 ++curl->unfinished;
290 return SUCCESS;
291 } else {
292 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Could not attach request to pool: %s", curl_multi_strerror(rs));
293 return FAILURE;
294 }
295 }
296
297 static STATUS php_http_client_pool_curl_detach(php_http_client_pool_t *h, php_http_client_t *r)
298 {
299 php_http_client_pool_curl_t *curl = h->ctx;
300 php_http_client_curl_t *recurl = r->ctx;
301 CURLMcode rs = curl_multi_remove_handle(curl->handle, recurl->handle);
302 TSRMLS_FETCH_FROM_CTX(h->ts);
303
304 if (CURLM_OK == rs) {
305 return SUCCESS;
306 } else {
307 php_http_error(HE_WARNING, PHP_HTTP_E_CLIENT_POOL, "Could not detach request from pool: %s", curl_multi_strerror(rs));
308 return FAILURE;
309 }
310 }
311
312 #ifdef PHP_WIN32
313 # define SELECT_ERROR SOCKET_ERROR
314 #else
315 # define SELECT_ERROR -1
316 #endif
317
318 static STATUS php_http_client_pool_curl_wait(php_http_client_pool_t *h, struct timeval *custom_timeout)
319 {
320 int MAX;
321 fd_set R, W, E;
322 struct timeval timeout;
323 php_http_client_pool_curl_t *curl = h->ctx;
324
325 #if PHP_HTTP_HAVE_EVENT
326 if (curl->useevents) {
327 TSRMLS_FETCH_FROM_CTX(h->ts);
328
329 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented");
330 return FAILURE;
331 }
332 #endif
333
334 if (custom_timeout && timerisset(custom_timeout)) {
335 timeout = *custom_timeout;
336 } else {
337 long max_tout = 1000;
338
339 if ((CURLM_OK == curl_multi_timeout(curl->handle, &max_tout)) && (max_tout > 0)) {
340 timeout.tv_sec = max_tout / 1000;
341 timeout.tv_usec = (max_tout % 1000) * 1000;
342 } else {
343 timeout.tv_sec = 0;
344 timeout.tv_usec = 1000;
345 }
346 }
347
348 FD_ZERO(&R);
349 FD_ZERO(&W);
350 FD_ZERO(&E);
351
352 if (CURLM_OK == curl_multi_fdset(curl->handle, &R, &W, &E, &MAX)) {
353 if (MAX == -1) {
354 php_http_sleep((double) timeout.tv_sec + (double) (timeout.tv_usec / PHP_HTTP_MCROSEC));
355 return SUCCESS;
356 } else if (SELECT_ERROR != select(MAX + 1, &R, &W, &E, &timeout)) {
357 return SUCCESS;
358 }
359 }
360 return FAILURE;
361 }
362
363 static int php_http_client_pool_curl_once(php_http_client_pool_t *h)
364 {
365 php_http_client_pool_curl_t *curl = h->ctx;
366
367 #if PHP_HTTP_HAVE_EVENT
368 if (curl->useevents) {
369 TSRMLS_FETCH_FROM_CTX(h->ts);
370 php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented");
371 return FAILURE;
372 }
373 #endif
374
375 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl->handle, &curl->unfinished));
376
377 php_http_client_pool_curl_responsehandler(h);
378
379 return curl->unfinished;
380
381 }
382 #if PHP_HTTP_HAVE_EVENT
383 static void dolog(int i, const char *m) {
384 fprintf(stderr, "%d: %s\n", i, m);
385 }
386 #endif
387 static STATUS php_http_client_pool_curl_exec(php_http_client_pool_t *h)
388 {
389 TSRMLS_FETCH_FROM_CTX(h->ts);
390
391 #if PHP_HTTP_HAVE_EVENT
392 php_http_client_pool_curl_t *curl = h->ctx;
393
394 if (curl->useevents) {
395 event_set_log_callback(dolog);
396 do {
397 #if DBG_EVENTS
398 fprintf(stderr, "X");
399 #endif
400 event_base_dispatch(PHP_HTTP_G->curl.event_base);
401 } while (curl->unfinished);
402 } else
403 #endif
404 {
405 while (php_http_client_pool_curl_once(h)) {
406 if (SUCCESS != php_http_client_pool_curl_wait(h, NULL)) {
407 #ifdef PHP_WIN32
408 /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */
409 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "WinSock error: %d", WSAGetLastError());
410 #else
411 php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, strerror(errno));
412 #endif
413 return FAILURE;
414 }
415 }
416 }
417
418 return SUCCESS;
419 }
420
421 static STATUS php_http_client_pool_curl_setopt(php_http_client_pool_t *h, php_http_client_pool_setopt_opt_t opt, void *arg)
422 {
423 php_http_client_pool_curl_t *curl = h->ctx;
424
425 switch (opt) {
426 case PHP_HTTP_CLIENT_POOL_OPT_ENABLE_PIPELINING:
427 if (CURLM_OK != curl_multi_setopt(curl->handle, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) {
428 return FAILURE;
429 }
430 break;
431
432 case PHP_HTTP_CLIENT_POOL_OPT_USE_EVENTS:
433 #if PHP_HTTP_HAVE_EVENT
434 if ((curl->useevents = *((zend_bool *) arg))) {
435 if (!curl->timeout) {
436 curl->timeout = ecalloc(1, sizeof(struct event));
437 }
438 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h);
439 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_client_pool_curl_socket_callback);
440 curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h);
441 curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_client_pool_curl_timer_callback);
442 } else {
443 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL);
444 curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL);
445 curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL);
446 curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL);
447 }
448 break;
449 #endif
450
451 default:
452 return FAILURE;
453 }
454 return SUCCESS;
455 }
456
457 static php_http_resource_factory_ops_t php_http_curlm_resource_factory_ops = {
458 php_http_curlm_ctor,
459 NULL,
460 php_http_curlm_dtor
461 };
462
463 static zend_class_entry *get_class_entry(void)
464 {
465 return php_http_client_pool_curl_class_entry;
466 }
467
468 static php_http_client_pool_ops_t php_http_client_pool_curl_ops = {
469 &php_http_curlm_resource_factory_ops,
470 php_http_client_pool_curl_init,
471 NULL /* copy */,
472 php_http_client_pool_curl_dtor,
473 NULL /*reset */,
474 php_http_client_pool_curl_exec,
475 php_http_client_pool_curl_wait,
476 php_http_client_pool_curl_once,
477 php_http_client_pool_curl_attach,
478 php_http_client_pool_curl_detach,
479 php_http_client_pool_curl_setopt,
480 (php_http_new_t) php_http_client_pool_curl_object_new_ex,
481 get_class_entry
482 };
483
484 PHP_HTTP_API php_http_client_pool_ops_t *php_http_client_pool_curl_get_ops(void)
485 {
486 return &php_http_client_pool_curl_ops;
487 }
488
489 #define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpClientCURL, method, 0, req_args)
490 #define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpClientCURL, method, 0)
491 #define PHP_HTTP_CURL_ME(method, visibility) PHP_ME(HttpClientCURL, method, PHP_HTTP_ARGS(HttpClientCURL, method), visibility)
492 #define PHP_HTTP_CURL_ALIAS(method, func) PHP_HTTP_STATIC_ME_ALIAS(method, func, PHP_HTTP_ARGS(HttpClientCURL, method))
493 #define PHP_HTTP_CURL_MALIAS(me, al, vis) ZEND_FENTRY(me, ZEND_MN(HttpClientCURL_##al), PHP_HTTP_ARGS(HttpClientCURL, al), vis)
494
495 zend_class_entry *php_http_client_pool_curl_class_entry;
496 zend_function_entry php_http_client_pool_curl_method_entry[] = {
497 EMPTY_FUNCTION_ENTRY
498 };
499
500 zend_object_value php_http_client_pool_curl_object_new(zend_class_entry *ce TSRMLS_DC)
501 {
502 return php_http_client_pool_curl_object_new_ex(ce, NULL, NULL TSRMLS_CC);
503 }
504
505 zend_object_value php_http_client_pool_curl_object_new_ex(zend_class_entry *ce, php_http_client_pool_t *p, php_http_client_pool_object_t **ptr TSRMLS_DC)
506 {
507 zend_object_value ov;
508 php_http_client_pool_object_t *o;
509
510 o = ecalloc(1, sizeof(php_http_client_pool_object_t));
511 zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
512 object_properties_init((zend_object *) o, ce);
513
514 if (!(o->pool = p)) {
515 o->pool = php_http_client_pool_init(NULL, &php_http_client_pool_curl_ops, NULL, NULL TSRMLS_CC);
516 }
517
518 if (ptr) {
519 *ptr = o;
520 }
521
522 ov.handle = zend_objects_store_put(o, NULL, php_http_client_pool_object_free, NULL TSRMLS_CC);
523 ov.handlers = php_http_client_pool_get_object_handlers();
524
525 return ov;
526 }
527
528
529 PHP_MINIT_FUNCTION(http_client_pool_curl)
530 {
531 if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_client_pool.curl"), &php_http_curlm_resource_factory_ops, NULL, NULL)) {
532 return FAILURE;
533 }
534
535 PHP_HTTP_REGISTER_CLASS(http\\Client\\Pool, CURL, http_client_pool_curl, php_http_client_pool_get_class_entry(), 0);
536 php_http_client_pool_curl_class_entry->create_object = php_http_client_pool_curl_object_new;
537
538 return SUCCESS;
539 }
540
541 PHP_RINIT_FUNCTION(http_client_pool_curl)
542 {
543 #if PHP_HTTP_HAVE_EVENT
544 if (!PHP_HTTP_G->curl.event_base && !(PHP_HTTP_G->curl.event_base = event_init())) {
545 return FAILURE;
546 }
547 #endif
548
549 return SUCCESS;
550 }
551
552 #endif /* PHP_HTTP_HAVE_CURL */
553
554 /*
555 * Local variables:
556 * tab-width: 4
557 * c-basic-offset: 4
558 * End:
559 * vim600: noet sw=4 ts=4 fdm=marker
560 * vim<600: noet sw=4 ts=4
561 */
562