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