- make ist behave like a stack, not like a queue
[m6w6/ext-http] / http_request_pool_api.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 "php_http_api.h"
21 #include "php_http_exception_object.h"
22 #include "php_http_request_api.h"
23 #include "php_http_request_object.h"
24 #include "php_http_request_pool_api.h"
25 #include "php_http_requestpool_object.h"
26 #include "php_http_persistent_handle_api.h"
27
28 #ifndef HTTP_DEBUG_REQPOOLS
29 # define HTTP_DEBUG_REQPOOLS 0
30 #endif
31
32 #ifndef HAVE_CURL_MULTI_STRERROR
33 # define curl_multi_strerror(dummy) "unknown error"
34 #endif
35
36 #ifdef HTTP_HAVE_PERSISTENT_HANDLES
37 # define HTTP_CURL_MULTI_CTOR(ch) (SUCCESS == http_persistent_handle_acquire("http_request_pool", &(ch)))
38 # define HTTP_CURL_MULTI_DTOR(chp) http_persistent_handle_release("http_request_pool", (chp))
39 #else
40 # define HTTP_CURL_MULTI_CTOR(ch) ((ch) = curl_multi_init())
41 # define HTTP_CURL_MULTI_DTOR(chp) curl_multi_cleanup(*(chp)); *(chp) = NULL
42 #endif
43
44 static int http_request_pool_compare_handles(void *h1, void *h2);
45
46 #ifdef HTTP_HAVE_PERSISTENT_HANDLES
47 PHP_MINIT_FUNCTION(http_request_pool)
48 {
49 if (SUCCESS != http_persistent_handle_provide("http_request_pool", curl_multi_init, (http_persistent_handle_dtor) curl_multi_cleanup)) {
50 return FAILURE;
51 }
52 return SUCCESS;
53 }
54 #endif
55
56 /* {{{ http_request_pool *http_request_pool_init(http_request_pool *) */
57 PHP_HTTP_API http_request_pool *_http_request_pool_init(http_request_pool *pool TSRMLS_DC)
58 {
59 zend_bool free_pool;
60
61 #if HTTP_DEBUG_REQPOOLS
62 fprintf(stderr, "Initializing request pool %p\n", pool);
63 #endif
64
65 if ((free_pool = (!pool))) {
66 pool = emalloc(sizeof(http_request_pool));
67 pool->ch = NULL;
68 }
69
70 if (!HTTP_CURL_MULTI_CTOR(pool->ch)) {
71 if (free_pool) {
72 efree(pool);
73 }
74 return NULL;
75 }
76
77 pool->unfinished = 0;
78 zend_llist_init(&pool->finished, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
79 zend_llist_init(&pool->handles, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
80
81 #if HTTP_DEBUG_REQPOOLS
82 fprintf(stderr, "Initialized request pool %p\n", pool);
83 #endif
84
85 return pool;
86 }
87 /* }}} */
88
89 /* {{{ STATUS http_request_pool_attach(http_request_pool *, zval *) */
90 PHP_HTTP_API STATUS _http_request_pool_attach(http_request_pool *pool, zval *request TSRMLS_DC)
91 {
92 getObjectEx(http_request_object, req, request);
93
94 #if HTTP_DEBUG_REQPOOLS
95 fprintf(stderr, "Attaching HttpRequest(#%d) %p to pool %p\n", Z_OBJ_HANDLE_P(request), req, pool);
96 #endif
97
98 if (req->pool) {
99 http_error_ex(HE_WARNING, HTTP_E_INVALID_PARAM, "HttpRequest object(#%d) is already member of %s HttpRequestPool", Z_OBJ_HANDLE_P(request), req->pool == pool ? "this" : "another");
100 } else if (SUCCESS != http_request_object_requesthandler(req, request)) {
101 http_error_ex(HE_WARNING, HTTP_E_REQUEST, "Could not initialize HttpRequest object(#%d) for attaching to the HttpRequestPool", Z_OBJ_HANDLE_P(request));
102 } else {
103 CURLMcode code = curl_multi_add_handle(pool->ch, req->request->ch);
104
105 if ((CURLM_OK != code) && (CURLM_CALL_MULTI_PERFORM != code)) {
106 http_error_ex(HE_WARNING, HTTP_E_REQUEST_POOL, "Could not attach HttpRequest object(#%d) to the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request), curl_multi_strerror(code));
107 } else {
108 req->pool = pool;
109
110 ZVAL_ADDREF(request);
111 zend_llist_add_element(&pool->handles, &request);
112
113 #if HTTP_DEBUG_REQPOOLS
114 fprintf(stderr, "> %d HttpRequests attached to pool %p\n", zend_llist_count(&pool->handles), pool);
115 #endif
116 return SUCCESS;
117 }
118 }
119 return FAILURE;
120 }
121 /* }}} */
122
123 /* {{{ STATUS http_request_pool_detach(http_request_pool *, zval *) */
124 PHP_HTTP_API STATUS _http_request_pool_detach(http_request_pool *pool, zval *request TSRMLS_DC)
125 {
126 CURLMcode code;
127 getObjectEx(http_request_object, req, request);
128
129 #if HTTP_DEBUG_REQPOOLS
130 fprintf(stderr, "Detaching HttpRequest(#%d) %p from pool %p\n", Z_OBJ_HANDLE_P(request), req, pool);
131 #endif
132
133 if (!req->pool) {
134 /* not attached to any pool */
135 #if HTTP_DEBUG_REQPOOLS
136 fprintf(stderr, "HttpRequest object(#%d) %p is not attached to any HttpRequestPool\n", Z_OBJ_HANDLE_P(request), req);
137 #endif
138 } else if (req->pool != pool) {
139 http_error_ex(HE_WARNING, HTTP_E_INVALID_PARAM, "HttpRequest object(#%d) is not attached to this HttpRequestPool", Z_OBJ_HANDLE_P(request));
140 } else if (req->request->_in_progress_cb) {
141 http_error_ex(HE_WARNING, HTTP_E_REQUEST_POOL, "HttpRequest object(#%d) cannot be detached from the HttpRequestPool while executing the progress callback", Z_OBJ_HANDLE_P(request));
142 } else if (CURLM_OK != (code = curl_multi_remove_handle(pool->ch, req->request->ch))) {
143 http_error_ex(HE_WARNING, HTTP_E_REQUEST_POOL, "Could not detach HttpRequest object(#%d) from the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request), curl_multi_strerror(code));
144 } else {
145 req->pool = NULL;
146 zend_llist_del_element(&pool->finished, request, http_request_pool_compare_handles);
147 zend_llist_del_element(&pool->handles, request, http_request_pool_compare_handles);
148
149 #if HTTP_DEBUG_REQPOOLS
150 fprintf(stderr, "> %d HttpRequests remaining in pool %p\n", zend_llist_count(&pool->handles), pool);
151 #endif
152
153 return SUCCESS;
154 }
155 return FAILURE;
156 }
157 /* }}} */
158
159 /* {{{ void http_request_pool_apply(http_request_pool *, http_request_pool_apply_func) */
160 PHP_HTTP_API void _http_request_pool_apply(http_request_pool *pool, http_request_pool_apply_func cb TSRMLS_DC)
161 {
162 int count = zend_llist_count(&pool->handles);
163
164 if (count) {
165 int i = 0;
166 zend_llist_position pos;
167 zval **handle, **handles = emalloc(count * sizeof(zval *));
168
169 for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) {
170 handles[i++] = *handle;
171 }
172
173 /* should never happen */
174 if (i != count) {
175 zend_error(E_ERROR, "number of fetched request handles do not match overall count");
176 count = i;
177 }
178
179 for (i = 0; i < count; ++i) {
180 if (cb(pool, handles[i] TSRMLS_CC)) {
181 break;
182 }
183 }
184 efree(handles);
185 }
186 }
187 /* }}} */
188
189 /* {{{ void http_request_pool_apply_with_arg(http_request_pool *, http_request_pool_apply_with_arg_func, void *) */
190 PHP_HTTP_API void _http_request_pool_apply_with_arg(http_request_pool *pool, http_request_pool_apply_with_arg_func cb, void *arg TSRMLS_DC)
191 {
192 int count = zend_llist_count(&pool->handles);
193
194 if (count) {
195 int i = 0;
196 zend_llist_position pos;
197 zval **handle, **handles = emalloc(count * sizeof(zval *));
198
199 for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) {
200 handles[i++] = *handle;
201 }
202
203 /* should never happen */
204 if (i != count) {
205 zend_error(E_ERROR, "number of fetched request handles do not match overall count");
206 count = i;
207 }
208
209 for (i = 0; i < count; ++i) {
210 if (cb(pool, handles[i], arg TSRMLS_CC)) {
211 break;
212 }
213 }
214 efree(handles);
215 }
216 }
217 /* }}} */
218
219 /* {{{ void http_request_pool_detach_all(http_request_pool *) */
220 PHP_HTTP_API void _http_request_pool_detach_all(http_request_pool *pool TSRMLS_DC)
221 {
222 #if HTTP_DEBUG_REQPOOLS
223 fprintf(stderr, "Detaching %d requests from pool %p\n", zend_llist_count(&pool->handles), pool);
224 #endif
225 http_request_pool_apply(pool, _http_request_pool_detach);
226 }
227 /* }}} */
228
229 /* {{{ STATUS http_request_pool_send(http_request_pool *) */
230 PHP_HTTP_API STATUS _http_request_pool_send(http_request_pool *pool TSRMLS_DC)
231 {
232 #if HTTP_DEBUG_REQPOOLS
233 fprintf(stderr, "Attempt to send %d requests of pool %p\n", zend_llist_count(&pool->handles), pool);
234 #endif
235
236 while (http_request_pool_perform(pool)) {
237 if (SUCCESS != http_request_pool_select(pool)) {
238 #ifdef PHP_WIN32
239 /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */
240 http_error_ex(HE_WARNING, HTTP_E_SOCKET, "WinSock error: %d", WSAGetLastError());
241 #else
242 http_error(HE_WARNING, HTTP_E_SOCKET, strerror(errno));
243 #endif
244 return FAILURE;
245 }
246 }
247
248 #if HTTP_DEBUG_REQPOOLS
249 fprintf(stderr, "Finished sending %d HttpRequests of pool %p (still unfinished: %d)\n", zend_llist_count(&pool->handles), pool, pool->unfinished);
250 #endif
251
252 return SUCCESS;
253 }
254 /* }}} */
255
256 /* {{{ void http_request_pool_dtor(http_request_pool *) */
257 PHP_HTTP_API void _http_request_pool_dtor(http_request_pool *pool TSRMLS_DC)
258 {
259 #if HTTP_DEBUG_REQPOOLS
260 fprintf(stderr, "Destructing request pool %p\n", pool);
261 #endif
262
263 pool->unfinished = 0;
264 zend_llist_clean(&pool->finished);
265 zend_llist_clean(&pool->handles);
266 HTTP_CURL_MULTI_DTOR(&pool->ch);
267 }
268 /* }}} */
269
270 #ifdef PHP_WIN32
271 # define SELECT_ERROR SOCKET_ERROR
272 #else
273 # define SELECT_ERROR -1
274 #endif
275
276 /* {{{ STATUS http_request_pool_select(http_request_pool *) */
277 PHP_HTTP_API STATUS _http_request_pool_select(http_request_pool *pool)
278 {
279 int MAX;
280 fd_set R, W, E;
281 struct timeval timeout = {1, 0};
282 #ifdef HAVE_CURL_MULTI_TIMEOUT
283 long max_tout = 1000;
284
285 if ((CURLM_OK == curl_multi_timeout(pool->ch, &max_tout)) && (max_tout != -1)) {
286 timeout.tv_sec = max_tout / 1000;
287 timeout.tv_usec = (max_tout % 1000) * 1000;
288 }
289 #endif
290
291 FD_ZERO(&R);
292 FD_ZERO(&W);
293 FD_ZERO(&E);
294
295 if (CURLM_OK == curl_multi_fdset(pool->ch, &R, &W, &E, &MAX)) {
296 if (MAX == -1) {
297 http_sleep((double) timeout.tv_sec + (double) (timeout.tv_usec / HTTP_MCROSEC));
298 return SUCCESS;
299 } else if (SELECT_ERROR != select(MAX + 1, &R, &W, &E, &timeout)) {
300 return SUCCESS;
301 }
302 }
303 return FAILURE;
304 }
305 /* }}} */
306
307 /* {{{ int http_request_pool_perform(http_request_pool *) */
308 PHP_HTTP_API int _http_request_pool_perform(http_request_pool *pool TSRMLS_DC)
309 {
310 CURLMsg *msg;
311 int remaining = 0;
312
313 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(pool->ch, &pool->unfinished));
314
315 while ((msg = curl_multi_info_read(pool->ch, &remaining))) {
316 if (CURLMSG_DONE == msg->msg) {
317 if (CURLE_OK != msg->data.result) {
318 http_request *r = NULL;
319 curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &r);
320 http_error_ex(HE_WARNING, HTTP_E_REQUEST, "%s; %s (%s)", curl_easy_strerror(msg->data.result), r?r->_error:"", r?r->url:"");
321 }
322 http_request_pool_apply_with_arg(pool, _http_request_pool_responsehandler, msg->easy_handle);
323 }
324 }
325
326 return pool->unfinished;
327 }
328 /* }}} */
329
330 /* {{{ void http_request_pool_responsehandler(http_request_pool *, zval *, void *) */
331 int _http_request_pool_responsehandler(http_request_pool *pool, zval *req, void *ch TSRMLS_DC)
332 {
333 getObjectEx(http_request_object, obj, req);
334
335 if ((!ch) || obj->request->ch == (CURL *) ch) {
336
337 #if HTTP_DEBUG_REQPOOLS
338 fprintf(stderr, "Fetching data from HttpRequest(#%d) %p of pool %p\n", Z_OBJ_HANDLE_P(req), obj, obj->pool);
339 #endif
340
341 ZVAL_ADDREF(req);
342 zend_llist_add_element(&obj->pool->finished, &req);
343 http_request_object_responsehandler(obj, req);
344 return 1;
345 }
346 return 0;
347 }
348 /* }}} */
349
350 /*#*/
351
352 /* {{{ static int http_request_pool_compare_handles(void *, void *) */
353 static int http_request_pool_compare_handles(void *h1, void *h2)
354 {
355 return (Z_OBJ_HANDLE_PP((zval **) h1) == Z_OBJ_HANDLE_P((zval *) h2));
356 }
357 /* }}} */
358
359 #endif /* ZEND_ENGINE_2 && HTTP_HAVE_CURL */
360
361
362 /*
363 * Local variables:
364 * tab-width: 4
365 * c-basic-offset: 4
366 * End:
367 * vim600: noet sw=4 ts=4 fdm=marker
368 * vim<600: noet sw=4 ts=4
369 */
370