9b1808b92f43be348222de8fc481a770c4ae139f
[m6w6/ext-http] / src / php_http_client_curl_event.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-2014, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14 #include "php_http_client.h"
15 #include "php_http_client_curl.h"
16
17 #if PHP_HTTP_HAVE_CURL
18 #if PHP_HTTP_HAVE_EVENT
19 # if !PHP_HTTP_HAVE_EVENT2 && /* just be really sure */ !(LIBEVENT_VERSION_NUMBER >= 0x02000000)
20 # include <event.h>
21 # define event_base_new event_init
22 # define event_assign(e, b, s, a, cb, d) do {\
23 event_set(e, s, a, cb, d); \
24 event_base_set(b, e); \
25 } while(0)
26 # else
27 # if PHP_HTTP_HAVE_EVENT2
28 # include <event2/event.h>
29 # include <event2/event_struct.h>
30 # else
31 # error "libevent presence is unknown"
32 # endif
33 # endif
34 # ifndef DBG_EVENTS
35 # define DBG_EVENTS 0
36 # endif
37
38 typedef struct php_http_client_curl_event_context {
39 php_http_client_t *client;
40 struct event_base *evbase;
41 struct event *timeout;
42 } php_http_client_curl_event_context_t;
43
44 typedef struct php_http_client_curl_event_ev {
45 struct event evnt;
46 php_http_client_curl_event_context_t *context;
47 } php_http_client_curl_event_ev_t;
48
49 static inline int etoca(short action) {
50 switch (action & (EV_READ|EV_WRITE)) {
51 case EV_READ:
52 return CURL_CSELECT_IN;
53 break;
54 case EV_WRITE:
55 return CURL_CSELECT_OUT;
56 break;
57 case EV_READ|EV_WRITE:
58 return CURL_CSELECT_IN|CURL_CSELECT_OUT;
59 break;
60 default:
61 return 0;
62 }
63 }
64
65 static void php_http_client_curl_event_handler(void *context, curl_socket_t s, int curl_action)
66 {
67 CURLMcode rc;
68 php_http_client_curl_event_context_t *ctx = context;
69 php_http_client_curl_t *curl = ctx->client->ctx;
70 TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
71
72 #if DBG_EVENTS
73 fprintf(stderr, "H");
74 #endif
75
76 do {
77 rc = curl_multi_socket_action(curl->handle->multi, s, curl_action, &curl->unfinished);
78 } while (CURLM_CALL_MULTI_PERFORM == rc);
79
80 if (CURLM_OK != rc) {
81 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", curl_multi_strerror(rc));
82 }
83
84 php_http_client_curl_responsehandler(ctx->client);
85 }
86
87 static void php_http_client_curl_event_timeout_callback(int socket, short action, void *event_data)
88 {
89 #if DBG_EVENTS
90 fprintf(stderr, "T");
91 #endif
92
93 /* ignore and use -1,0 on timeout */
94 (void) socket;
95 (void) action;
96
97 php_http_client_curl_event_handler(event_data, CURL_SOCKET_TIMEOUT, 0);
98 }
99
100 static void php_http_client_curl_event_timer(CURLM *multi, long timeout_ms, void *timer_data)
101 {
102 php_http_client_curl_event_context_t *context = timer_data;
103
104 #if DBG_EVENTS
105 fprintf(stderr, "\ntimer <- timeout_ms: %ld\n", timeout_ms);
106 #endif
107
108 if (timeout_ms < 0) {
109 php_http_client_curl_event_handler(context, CURL_SOCKET_TIMEOUT, 0);
110 } else if (timeout_ms > 0 || !event_initialized(context->timeout) || !event_pending(context->timeout, EV_TIMEOUT, NULL)) {
111 struct timeval timeout;
112
113 if (!event_initialized(context->timeout)) {
114 event_assign(context->timeout, context->evbase, CURL_SOCKET_TIMEOUT, 0, php_http_client_curl_event_timeout_callback, context);
115 }
116
117 timeout.tv_sec = timeout_ms / 1000;
118 timeout.tv_usec = (timeout_ms % 1000) * 1000;
119
120 event_add(context->timeout, &timeout);
121 }
122 }
123
124 static void php_http_client_curl_event_callback(int socket, short action, void *event_data)
125 {
126 php_http_client_curl_event_context_t *ctx = event_data;
127 php_http_client_curl_t *curl = ctx->client->ctx;
128
129 #if DBG_EVENTS
130 fprintf(stderr, "E");
131 #endif
132
133 php_http_client_curl_event_handler(event_data, socket, etoca(action));
134
135 /* remove timeout if there are no transfers left */
136 if (!curl->unfinished && event_initialized(ctx->timeout) && event_pending(ctx->timeout, EV_TIMEOUT, NULL)) {
137 event_del(ctx->timeout);
138 }
139 }
140
141 static int php_http_client_curl_event_socket(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data)
142 {
143 php_http_client_curl_event_context_t *ctx = socket_data;
144 php_http_client_curl_t *curl = ctx->client->ctx;
145 int events = EV_PERSIST;
146 php_http_client_curl_event_ev_t *ev = assign_data;
147 TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
148
149 #if DBG_EVENTS
150 fprintf(stderr, "S");
151 #endif
152
153 if (!ev) {
154 ev = ecalloc(1, sizeof(*ev));
155 ev->context = ctx;
156 curl_multi_assign(curl->handle->multi, sock, ev);
157 } else {
158 event_del(&ev->evnt);
159 }
160
161 switch (action) {
162 case CURL_POLL_IN:
163 events |= EV_READ;
164 break;
165 case CURL_POLL_OUT:
166 events |= EV_WRITE;
167 break;
168 case CURL_POLL_INOUT:
169 events |= EV_READ|EV_WRITE;
170 break;
171
172 case CURL_POLL_REMOVE:
173 efree(ev);
174 /* no break */
175 case CURL_POLL_NONE:
176 return 0;
177
178 default:
179 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown socket action %d", action);
180 return -1;
181 }
182
183 event_assign(&ev->evnt, ctx->evbase, sock, events, php_http_client_curl_event_callback, ctx);
184 event_add(&ev->evnt, NULL);
185
186 return 0;
187 }
188
189 static ZEND_RESULT_CODE php_http_client_curl_event_once(void *context)
190 {
191 php_http_client_curl_event_context_t *ctx = context;
192
193 #if DBG_EVENTS
194 fprintf(stderr, "O");
195 #endif
196
197 if (0 > event_base_loop(ctx->evbase, EVLOOP_NONBLOCK)) {
198 return FAILURE;
199 }
200 return SUCCESS;
201 }
202
203 static ZEND_RESULT_CODE php_http_client_curl_event_wait(void *context, struct timeval *custom_timeout)
204 {
205 php_http_client_curl_event_context_t *ctx = context;
206 struct timeval timeout;
207
208 #if DBG_EVENTS
209 fprintf(stderr, "W");
210 #endif
211
212 if (!event_initialized(ctx->timeout)) {
213 if (0 > event_assign(ctx->timeout, ctx->evbase, CURL_SOCKET_TIMEOUT, 0, php_http_client_curl_event_timeout_callback, ctx)) {
214 return FAILURE;
215 }
216 } else if (custom_timeout && timerisset(custom_timeout)) {
217 if (0 > event_add(ctx->timeout, custom_timeout)) {
218 return FAILURE;
219 }
220 } else if (!event_pending(ctx->timeout, EV_TIMEOUT, NULL)) {
221 php_http_client_curl_get_timeout(ctx->client->ctx, 1000, &timeout);
222 if (0 > event_add(ctx->timeout, &timeout)) {
223 return FAILURE;
224 }
225 }
226
227 if (0 > event_base_loop(ctx->evbase, EVLOOP_ONCE)) {
228 return FAILURE;
229 }
230
231 return SUCCESS;
232 }
233
234 static ZEND_RESULT_CODE php_http_client_curl_event_exec(void *context)
235 {
236 php_http_client_curl_event_context_t *ctx = context;
237 php_http_client_curl_t *curl = ctx->client->ctx;
238 TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
239
240 #if DBG_EVENTS
241 fprintf(stderr, "E");
242 #endif
243
244 /* kickstart */
245 php_http_client_curl_event_handler(ctx, CURL_SOCKET_TIMEOUT, 0);
246
247 do {
248 if (0 > event_base_dispatch(ctx->evbase)) {
249 return FAILURE;
250 }
251 } while (curl->unfinished && !EG(exception));
252
253 return SUCCESS;
254 }
255
256 static void *php_http_client_curl_event_init(php_http_client_t *client)
257 {
258 php_http_client_curl_t *curl = client->ctx;
259 php_http_client_curl_event_context_t *ctx;
260 struct event_base *evb = event_base_new();
261
262 #if DBG_EVENTS
263 fprintf(stderr, "I");
264 #endif
265
266 if (!evb) {
267 return NULL;
268 }
269
270 ctx = ecalloc(1, sizeof(*ctx));
271 ctx->client = client;
272 ctx->evbase = evb;
273 ctx->timeout = ecalloc(1, sizeof(struct event));
274
275 curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, ctx);
276 curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, php_http_client_curl_event_socket);
277 curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, ctx);
278 curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, php_http_client_curl_event_timer);
279
280 return ctx;
281 }
282
283 static void php_http_client_curl_event_dtor(void **context)
284 {
285 php_http_client_curl_event_context_t *ctx = *context;
286 php_http_client_curl_t *curl;
287
288 #if DBG_EVENTS
289 fprintf(stderr, "D");
290 #endif
291
292 ZEND_ASSERT(ctx);
293
294 curl = ctx->client->ctx;
295
296 curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, NULL);
297 curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, NULL);
298 curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, NULL);
299 curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, NULL);
300
301 if (event_initialized(ctx->timeout) && event_pending(ctx->timeout, EV_TIMEOUT, NULL)) {
302 event_del(ctx->timeout);
303 }
304 efree(ctx->timeout);
305 event_base_free(ctx->evbase);
306
307 efree(ctx);
308 *context = NULL;
309 }
310
311 static php_http_client_curl_ops_t php_http_client_curl_event_ops = {
312 &php_http_client_curl_event_init,
313 &php_http_client_curl_event_dtor,
314 &php_http_client_curl_event_once,
315 &php_http_client_curl_event_wait,
316 &php_http_client_curl_event_exec,
317 };
318
319 php_http_client_curl_ops_t *php_http_client_curl_event_ops_get()
320 {
321 return &php_http_client_curl_event_ops;
322 }
323
324 #endif /* PHP_HTTP_HAVE_EVENT */
325 #endif /* PHP_HTTP_HAVE_CURL */
326
327 /*
328 * Local variables:
329 * tab-width: 4
330 * c-basic-offset: 4
331 * End:
332 * vim600: noet sw=4 ts=4 fdm=marker
333 * vim<600: noet sw=4 ts=4
334 */