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