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