- disallow detaching requests while executing progress callbacks
[m6w6/ext-http] / http_requestpool_object.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-2006, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 /* $Id$ */
14
15 #define HTTP_WANT_CURL
16 #include "php_http.h"
17
18 #if defined(ZEND_ENGINE_2) && defined(HTTP_HAVE_CURL)
19
20 #include "zend_interfaces.h"
21
22 #include "php_http_api.h"
23 #include "php_http_exception_object.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 #if defined(HAVE_SPL) && !defined(WONKY)
30 /* SPL doesn't install its headers */
31 extern PHPAPI zend_class_entry *spl_ce_Countable;
32 #endif
33
34 #define HTTP_BEGIN_ARGS(method, req_args) HTTP_BEGIN_ARGS_EX(HttpRequestPool, method, 0, req_args)
35 #define HTTP_EMPTY_ARGS(method) HTTP_EMPTY_ARGS_EX(HttpRequestPool, method, 0)
36 #define HTTP_REQPOOL_ME(method, visibility) PHP_ME(HttpRequestPool, method, HTTP_ARGS(HttpRequestPool, method), visibility)
37
38 HTTP_BEGIN_ARGS(__construct, 0)
39 HTTP_ARG_OBJ(HttpRequest, request0, 0)
40 HTTP_ARG_OBJ(HttpRequest, request1, 0)
41 HTTP_ARG_OBJ(HttpRequest, requestN, 0)
42 HTTP_END_ARGS;
43
44 HTTP_EMPTY_ARGS(__destruct);
45 HTTP_EMPTY_ARGS(reset);
46
47 HTTP_BEGIN_ARGS(attach, 1)
48 HTTP_ARG_OBJ(HttpRequest, request, 0)
49 HTTP_END_ARGS;
50
51 HTTP_BEGIN_ARGS(detach, 1)
52 HTTP_ARG_OBJ(HttpRequest, request, 0)
53 HTTP_END_ARGS;
54
55 HTTP_EMPTY_ARGS(send);
56 HTTP_EMPTY_ARGS(socketPerform);
57 HTTP_EMPTY_ARGS(socketSelect);
58
59 HTTP_EMPTY_ARGS(valid);
60 HTTP_EMPTY_ARGS(current);
61 HTTP_EMPTY_ARGS(key);
62 HTTP_EMPTY_ARGS(next);
63 HTTP_EMPTY_ARGS(rewind);
64
65 HTTP_EMPTY_ARGS(count);
66
67 HTTP_EMPTY_ARGS(getAttachedRequests);
68 HTTP_EMPTY_ARGS(getFinishedRequests);
69
70 zend_class_entry *http_requestpool_object_ce;
71 zend_function_entry http_requestpool_object_fe[] = {
72 HTTP_REQPOOL_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
73 HTTP_REQPOOL_ME(__destruct, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)
74 HTTP_REQPOOL_ME(attach, ZEND_ACC_PUBLIC)
75 HTTP_REQPOOL_ME(detach, ZEND_ACC_PUBLIC)
76 HTTP_REQPOOL_ME(send, ZEND_ACC_PUBLIC)
77 HTTP_REQPOOL_ME(reset, ZEND_ACC_PUBLIC)
78
79 HTTP_REQPOOL_ME(socketPerform, ZEND_ACC_PROTECTED)
80 HTTP_REQPOOL_ME(socketSelect, ZEND_ACC_PROTECTED)
81
82 /* implements Iterator */
83 HTTP_REQPOOL_ME(valid, ZEND_ACC_PUBLIC)
84 HTTP_REQPOOL_ME(current, ZEND_ACC_PUBLIC)
85 HTTP_REQPOOL_ME(key, ZEND_ACC_PUBLIC)
86 HTTP_REQPOOL_ME(next, ZEND_ACC_PUBLIC)
87 HTTP_REQPOOL_ME(rewind, ZEND_ACC_PUBLIC)
88
89 /* implmenents Countable */
90 HTTP_REQPOOL_ME(count, ZEND_ACC_PUBLIC)
91
92 HTTP_REQPOOL_ME(getAttachedRequests, ZEND_ACC_PUBLIC)
93 HTTP_REQPOOL_ME(getFinishedRequests, ZEND_ACC_PUBLIC)
94
95 EMPTY_FUNCTION_ENTRY
96 };
97 static zend_object_handlers http_requestpool_object_handlers;
98
99 PHP_MINIT_FUNCTION(http_requestpool_object)
100 {
101 HTTP_REGISTER_CLASS_EX(HttpRequestPool, http_requestpool_object, NULL, 0);
102 http_requestpool_object_handlers.clone_obj = NULL;
103
104 #if defined(HAVE_SPL) && !defined(WONKY)
105 zend_class_implements(http_requestpool_object_ce TSRMLS_CC, 2, spl_ce_Countable, zend_ce_iterator);
106 #else
107 zend_class_implements(http_requestpool_object_ce TSRMLS_CC, 1, zend_ce_iterator);
108 #endif
109
110 return SUCCESS;
111 }
112
113 zend_object_value _http_requestpool_object_new(zend_class_entry *ce TSRMLS_DC)
114 {
115 zend_object_value ov;
116 http_requestpool_object *o;
117
118 o = ecalloc(1, sizeof(http_requestpool_object));
119 o->zo.ce = ce;
120
121 http_request_pool_init(&o->pool);
122
123 ALLOC_HASHTABLE(OBJ_PROP(o));
124 zend_hash_init(OBJ_PROP(o), zend_hash_num_elements(&ce->default_properties), NULL, ZVAL_PTR_DTOR, 0);
125 zend_hash_copy(OBJ_PROP(o), &ce->default_properties, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
126
127 ov.handle = putObject(http_requestpool_object, o);
128 ov.handlers = &http_requestpool_object_handlers;
129
130 return ov;
131 }
132
133 void _http_requestpool_object_free(zend_object *object TSRMLS_DC)
134 {
135 http_requestpool_object *o = (http_requestpool_object *) object;
136
137 http_request_pool_dtor(&o->pool);
138 freeObject(o);
139 }
140
141 #define http_requestpool_object_llist2array _http_requestpool_object_llist2array
142 static void _http_requestpool_object_llist2array(zval **req, zval *array TSRMLS_DC)
143 {
144 ZVAL_ADDREF(*req);
145 add_next_index_zval(array, *req);
146 }
147
148 /* ### USERLAND ### */
149
150 /* {{{ proto void HttpRequestPool::__construct([HttpRequest request[, ...]])
151 *
152 * Instantiate a new HttpRequestPool object. An HttpRequestPool is
153 * able to send several HttpRequests in parallel.
154 *
155 * WARNING: Don't attach/detach HttpRequest objects to the HttpRequestPool
156 * object while you're using the implemented Iterator interface.
157 *
158 * Accepts virtual infinite optional parameters each referencing an
159 * HttpRequest object.
160 *
161 * Throws HttpRequestPoolException (HttpRequestException, HttpInvalidParamException).
162 *
163 * Example:
164 * <pre>
165 * <?php
166 * try {
167 * $pool = new HttpRequestPool(
168 * new HttpRequest('http://www.google.com/', HttpRequest::METH_HEAD),
169 * new HttpRequest('http://www.php.net/', HttpRequest::METH_HEAD)
170 * );
171 * $pool->send();
172 * foreach($pool as $request) {
173 * printf("%s is %s (%d)\n",
174 * $request->getUrl(),
175 * $request->getResponseCode() ? 'alive' : 'not alive',
176 * $request->getResponseCode()
177 * );
178 * }
179 * } catch (HttpException $e) {
180 * echo $e;
181 * }
182 * ?>
183 * </pre>
184 */
185 PHP_METHOD(HttpRequestPool, __construct)
186 {
187 int argc = ZEND_NUM_ARGS();
188 zval ***argv = safe_emalloc(argc, sizeof(zval *), 0);
189 getObject(http_requestpool_object, obj);
190
191 SET_EH_THROW_HTTP();
192 if (SUCCESS == zend_get_parameters_array_ex(argc, argv)) {
193 int i;
194
195 for (i = 0; i < argc; ++i) {
196 if (Z_TYPE_PP(argv[i]) == IS_OBJECT && instanceof_function(Z_OBJCE_PP(argv[i]), http_request_object_ce TSRMLS_CC)) {
197 http_request_pool_attach(&obj->pool, *(argv[i]));
198 }
199 }
200 }
201 efree(argv);
202 SET_EH_NORMAL();
203 http_final(HTTP_EX_CE(request_pool));
204 }
205 /* }}} */
206
207 /* {{{ proto void HttpRequestPool::__destruct()
208 *
209 * Clean up HttpRequestPool object.
210 */
211 PHP_METHOD(HttpRequestPool, __destruct)
212 {
213 getObject(http_requestpool_object, obj);
214
215 NO_ARGS;
216
217 http_request_pool_detach_all(&obj->pool);
218 }
219 /* }}} */
220
221 /* {{{ proto void HttpRequestPool::reset()
222 *
223 * Detach all attached HttpRequest objects.
224 */
225 PHP_METHOD(HttpRequestPool, reset)
226 {
227 getObject(http_requestpool_object, obj);
228
229 NO_ARGS;
230
231 obj->iterator.pos = 0;
232 http_request_pool_detach_all(&obj->pool);
233 }
234
235 /* {{{ proto bool HttpRequestPool::attach(HttpRequest request)
236 *
237 * Attach an HttpRequest object to this HttpRequestPool.
238 * WARNING: set all options prior attaching!
239 *
240 * Expects the parameter to be an HttpRequest object not already attached to
241 * antother HttpRequestPool object.
242 *
243 * Returns TRUE on success, or FALSE on failure.
244 *
245 * Throws HttpInvalidParamException, HttpRequestException,
246 * HttpRequestPoolException, HttpEncodingException.
247 */
248 PHP_METHOD(HttpRequestPool, attach)
249 {
250 zval *request;
251 STATUS status = FAILURE;
252 getObject(http_requestpool_object, obj);
253
254 SET_EH_THROW_HTTP();
255 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, http_request_object_ce)) {
256 if (obj->iterator.pos > 0 && obj->iterator.pos < zend_llist_count(&obj->pool.handles)) {
257 http_error(HE_THROW, HTTP_E_REQUEST_POOL, "Cannot attach to the HttpRequestPool while the iterator is active");
258 } else {
259 status = http_request_pool_attach(&obj->pool, request);
260 }
261 }
262 SET_EH_NORMAL();
263 RETURN_SUCCESS(status);
264 }
265 /* }}} */
266
267 /* {{{ proto bool HttpRequestPool::detach(HttpRequest request)
268 *
269 * Detach an HttpRequest object from this HttpRequestPool.
270 *
271 * Expects the parameter to be an HttpRequest object attached to this
272 * HttpRequestPool object.
273 *
274 * Returns TRUE on success, or FALSE on failure.
275 *
276 * Throws HttpInvalidParamException, HttpRequestPoolException.
277 */
278 PHP_METHOD(HttpRequestPool, detach)
279 {
280 zval *request;
281 STATUS status = FAILURE;
282 getObject(http_requestpool_object, obj);
283
284 SET_EH_THROW_HTTP();
285 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, http_request_object_ce)) {
286 obj->iterator.pos = -1;
287 status = http_request_pool_detach(&obj->pool, request);
288 }
289 SET_EH_NORMAL();
290 RETURN_SUCCESS(status);
291 }
292 /* }}} */
293
294 /* {{{ proto bool HttpRequestPool::send()
295 *
296 * Send all attached HttpRequest objects in parallel.
297 *
298 * Returns TRUE on success, or FALSE on failure.
299 *
300 * Throws HttpRequestPoolException (HttpSocketException, HttpRequestException, HttpMalformedHeaderException).
301 */
302 PHP_METHOD(HttpRequestPool, send)
303 {
304 STATUS status;
305 getObject(http_requestpool_object, obj);
306
307 NO_ARGS;
308
309 SET_EH_THROW_HTTP();
310 status = http_request_pool_send(&obj->pool);
311 SET_EH_NORMAL();
312
313 /* rethrow as HttpRequestPoolException */
314 http_final(HTTP_EX_CE(request_pool));
315
316 RETURN_SUCCESS(status);
317 }
318 /* }}} */
319
320 /* {{{ proto protected bool HttpRequestPool::socketPerform()
321 *
322 * Returns TRUE until each request has finished its transaction.
323 *
324 * Usage:
325 * <pre>
326 * <?php
327 * class MyPool extends HttpRequestPool
328 * {
329 * public function send()
330 * {
331 * while ($this->socketPerform()) {
332 * if (!$this->socketSelect()) {
333 * throw new HttpSocketExcpetion;
334 * }
335 * }
336 * }
337 *
338 * protected final function socketPerform()
339 * {
340 * $result = parent::socketPerform();
341 * foreach ($this->getFinishedRequests() as $r) {
342 * $this->detach($r);
343 * // handle response of finished request
344 * }
345 * return $result;
346 * }
347 * }
348 * ?>
349 * </pre>
350 */
351 PHP_METHOD(HttpRequestPool, socketPerform)
352 {
353 getObject(http_requestpool_object, obj);
354
355 NO_ARGS;
356
357 if (0 < http_request_pool_perform(&obj->pool)) {
358 RETURN_TRUE;
359 } else {
360 RETURN_FALSE;
361 }
362 }
363 /* }}} */
364
365 /* {{{ proto protected bool HttpRequestPool::socketSelect()
366 *
367 * See HttpRequestPool::socketPerform().
368 *
369 * Returns TRUE on success, or FALSE on failure.
370 */
371 PHP_METHOD(HttpRequestPool, socketSelect)
372 {
373 getObject(http_requestpool_object, obj);
374
375 NO_ARGS;
376
377 RETURN_SUCCESS(http_request_pool_select(&obj->pool));
378 }
379 /* }}} */
380
381 /* implements Iterator */
382
383 /* {{{ proto bool HttpRequestPool::valid()
384 *
385 * Implements Iterator::valid().
386 */
387 PHP_METHOD(HttpRequestPool, valid)
388 {
389 NO_ARGS;
390
391 if (return_value_used) {
392 getObject(http_requestpool_object, obj);
393 RETURN_BOOL(obj->iterator.pos >= 0 && obj->iterator.pos < zend_llist_count(&obj->pool.handles));
394 }
395 }
396 /* }}} */
397
398 /* {{{ proto HttpRequest HttpRequestPool::current()
399 *
400 * Implements Iterator::current().
401 */
402 PHP_METHOD(HttpRequestPool, current)
403 {
404 NO_ARGS;
405
406 if (return_value_used) {
407 long pos = 0;
408 zval **current = NULL;
409 zend_llist_position lpos;
410 getObject(http_requestpool_object, obj);
411
412 if (obj->iterator.pos < zend_llist_count(&obj->pool.handles)) {
413 for ( current = zend_llist_get_first_ex(&obj->pool.handles, &lpos);
414 current && obj->iterator.pos != pos++;
415 current = zend_llist_get_next_ex(&obj->pool.handles, &lpos));
416 if (current) {
417 RETURN_OBJECT(*current, 1);
418 }
419 }
420 RETURN_NULL();
421 }
422 }
423 /* }}} */
424
425 /* {{{ proto int HttpRequestPool::key()
426 *
427 * Implements Iterator::key().
428 */
429 PHP_METHOD(HttpRequestPool, key)
430 {
431 NO_ARGS;
432
433 if (return_value_used) {
434 getObject(http_requestpool_object, obj);
435 RETURN_LONG(obj->iterator.pos);
436 }
437 }
438 /* }}} */
439
440 /* {{{ proto void HttpRequestPool::next()
441 *
442 * Implements Iterator::next().
443 */
444 PHP_METHOD(HttpRequestPool, next)
445 {
446 NO_ARGS {
447 getObject(http_requestpool_object, obj);
448 ++(obj->iterator.pos);
449 }
450 }
451 /* }}} */
452
453 /* {{{ proto void HttpRequestPool::rewind()
454 *
455 * Implements Iterator::rewind().
456 */
457 PHP_METHOD(HttpRequestPool, rewind)
458 {
459 NO_ARGS {
460 getObject(http_requestpool_object, obj);
461 obj->iterator.pos = 0;
462 }
463 }
464 /* }}} */
465
466 /* {{{ proto int HttpRequestPool::count()
467 *
468 * Implements Countable.
469 *
470 * Returns the number of attached HttpRequest objects.
471 */
472 PHP_METHOD(HttpRequestPool, count)
473 {
474 NO_ARGS {
475 getObject(http_requestpool_object, obj);
476 RETURN_LONG((long) zend_llist_count(&obj->pool.handles));
477 }
478 }
479 /* }}} */
480
481 /* {{{ proto array HttpRequestPool::getAttachedRequests()
482 *
483 * Get attached HttpRequest objects.
484 *
485 * Returns an array containing all currently attached HttpRequest objects.
486 */
487 PHP_METHOD(HttpRequestPool, getAttachedRequests)
488 {
489 getObject(http_requestpool_object, obj);
490
491 NO_ARGS;
492
493 array_init(return_value);
494 zend_llist_apply_with_argument(&obj->pool.handles,
495 (llist_apply_with_arg_func_t) http_requestpool_object_llist2array,
496 return_value TSRMLS_CC);
497 }
498 /* }}} */
499
500 /* {{{ proto array HttpRequestPool::getFinishedRequests()
501 *
502 * Get attached HttpRequest objects that already have finished their work.
503 *
504 * Returns an array containing all attached HttpRequest objects that
505 * already have finished their work.
506 */
507 PHP_METHOD(HttpRequestPool, getFinishedRequests)
508 {
509 getObject(http_requestpool_object, obj);
510
511 NO_ARGS;
512
513 array_init(return_value);
514 zend_llist_apply_with_argument(&obj->pool.finished,
515 (llist_apply_with_arg_func_t) http_requestpool_object_llist2array,
516 return_value TSRMLS_CC);
517 }
518 /* }}} */
519
520 #endif /* ZEND_ENGINE_2 && HTTP_HAVE_CURL */
521
522 /*
523 * Local variables:
524 * tab-width: 4
525 * c-basic-offset: 4
526 * End:
527 * vim600: noet sw=4 ts=4 fdm=marker
528 * vim<600: noet sw=4 ts=4
529 */
530