1 : /*
2 : * Copyright (c) 2000-2004 Niels Provos <provos@citi.umich.edu>
3 : * All rights reserved.
4 : *
5 : * Redistribution and use in source and binary forms, with or without
6 : * modification, are permitted provided that the following conditions
7 : * are met:
8 : * 1. Redistributions of source code must retain the above copyright
9 : * notice, this list of conditions and the following disclaimer.
10 : * 2. Redistributions in binary form must reproduce the above copyright
11 : * notice, this list of conditions and the following disclaimer in the
12 : * documentation and/or other materials provided with the distribution.
13 : * 3. The name of the author may not be used to endorse or promote products
14 : * derived from this software without specific prior written permission.
15 : *
16 : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 : * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 : * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 : * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 : * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 : * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 : */
27 : #ifdef HAVE_CONFIG_H
28 : #include "config.h"
29 : #endif
30 :
31 : #ifdef WIN32
32 : #define WIN32_LEAN_AND_MEAN
33 : #include <winsock2.h>
34 : #include <windows.h>
35 : #undef WIN32_LEAN_AND_MEAN
36 : #endif
37 :
38 : #include <sys/types.h>
39 : #ifndef WIN32
40 : #include <sys/socket.h>
41 : #endif
42 : #ifdef HAVE_SYS_TIME_H
43 : #include <sys/time.h>
44 : #endif
45 : #include <sys/queue.h>
46 : #include <stdio.h>
47 : #include <stdlib.h>
48 : #ifndef WIN32
49 : #include <unistd.h>
50 : #endif
51 : #include <errno.h>
52 : #include <signal.h>
53 : #include <string.h>
54 : #include <assert.h>
55 :
56 : #include "event.h"
57 : #include "evrpc.h"
58 : #include "event-internal.h"
59 : #include "evrpc-internal.h"
60 : #include "evhttp.h"
61 : #include "evutil.h"
62 : #include "log.h"
63 :
64 : struct evrpc_base *
65 0 : evrpc_init(struct evhttp *http_server)
66 : {
67 0 : struct evrpc_base* base = calloc(1, sizeof(struct evrpc_base));
68 0 : if (base == NULL)
69 0 : return (NULL);
70 :
71 : /* we rely on the tagging sub system */
72 0 : evtag_init();
73 :
74 0 : TAILQ_INIT(&base->registered_rpcs);
75 0 : TAILQ_INIT(&base->input_hooks);
76 0 : TAILQ_INIT(&base->output_hooks);
77 0 : base->http_server = http_server;
78 :
79 0 : return (base);
80 : }
81 :
82 : void
83 0 : evrpc_free(struct evrpc_base *base)
84 : {
85 : struct evrpc *rpc;
86 : struct evrpc_hook *hook;
87 :
88 0 : while ((rpc = TAILQ_FIRST(&base->registered_rpcs)) != NULL) {
89 0 : assert(evrpc_unregister_rpc(base, rpc->uri));
90 : }
91 0 : while ((hook = TAILQ_FIRST(&base->input_hooks)) != NULL) {
92 0 : assert(evrpc_remove_hook(base, EVRPC_INPUT, hook));
93 : }
94 0 : while ((hook = TAILQ_FIRST(&base->output_hooks)) != NULL) {
95 0 : assert(evrpc_remove_hook(base, EVRPC_OUTPUT, hook));
96 : }
97 0 : free(base);
98 0 : }
99 :
100 : void *
101 0 : evrpc_add_hook(void *vbase,
102 : enum EVRPC_HOOK_TYPE hook_type,
103 : int (*cb)(struct evhttp_request *, struct evbuffer *, void *),
104 : void *cb_arg)
105 : {
106 0 : struct _evrpc_hooks *base = vbase;
107 0 : struct evrpc_hook_list *head = NULL;
108 0 : struct evrpc_hook *hook = NULL;
109 0 : switch (hook_type) {
110 : case EVRPC_INPUT:
111 0 : head = &base->in_hooks;
112 0 : break;
113 : case EVRPC_OUTPUT:
114 0 : head = &base->out_hooks;
115 0 : break;
116 : default:
117 0 : assert(hook_type == EVRPC_INPUT || hook_type == EVRPC_OUTPUT);
118 : }
119 :
120 0 : hook = calloc(1, sizeof(struct evrpc_hook));
121 0 : assert(hook != NULL);
122 :
123 0 : hook->process = cb;
124 0 : hook->process_arg = cb_arg;
125 0 : TAILQ_INSERT_TAIL(head, hook, next);
126 :
127 0 : return (hook);
128 : }
129 :
130 : static int
131 0 : evrpc_remove_hook_internal(struct evrpc_hook_list *head, void *handle)
132 : {
133 0 : struct evrpc_hook *hook = NULL;
134 0 : TAILQ_FOREACH(hook, head, next) {
135 0 : if (hook == handle) {
136 0 : TAILQ_REMOVE(head, hook, next);
137 0 : free(hook);
138 0 : return (1);
139 : }
140 : }
141 :
142 0 : return (0);
143 : }
144 :
145 : /*
146 : * remove the hook specified by the handle
147 : */
148 :
149 : int
150 0 : evrpc_remove_hook(void *vbase, enum EVRPC_HOOK_TYPE hook_type, void *handle)
151 : {
152 0 : struct _evrpc_hooks *base = vbase;
153 0 : struct evrpc_hook_list *head = NULL;
154 0 : switch (hook_type) {
155 : case EVRPC_INPUT:
156 0 : head = &base->in_hooks;
157 0 : break;
158 : case EVRPC_OUTPUT:
159 0 : head = &base->out_hooks;
160 0 : break;
161 : default:
162 0 : assert(hook_type == EVRPC_INPUT || hook_type == EVRPC_OUTPUT);
163 : }
164 :
165 0 : return (evrpc_remove_hook_internal(head, handle));
166 : }
167 :
168 : static int
169 0 : evrpc_process_hooks(struct evrpc_hook_list *head,
170 : struct evhttp_request *req, struct evbuffer *evbuf)
171 : {
172 : struct evrpc_hook *hook;
173 0 : TAILQ_FOREACH(hook, head, next) {
174 0 : if (hook->process(req, evbuf, hook->process_arg) == -1)
175 0 : return (-1);
176 : }
177 :
178 0 : return (0);
179 : }
180 :
181 : static void evrpc_pool_schedule(struct evrpc_pool *pool);
182 : static void evrpc_request_cb(struct evhttp_request *, void *);
183 : void evrpc_request_done(struct evrpc_req_generic*);
184 :
185 : /*
186 : * Registers a new RPC with the HTTP server. The evrpc object is expected
187 : * to have been filled in via the EVRPC_REGISTER_OBJECT macro which in turn
188 : * calls this function.
189 : */
190 :
191 : static char *
192 0 : evrpc_construct_uri(const char *uri)
193 : {
194 : char *constructed_uri;
195 : int constructed_uri_len;
196 :
197 0 : constructed_uri_len = strlen(EVRPC_URI_PREFIX) + strlen(uri) + 1;
198 0 : if ((constructed_uri = malloc(constructed_uri_len)) == NULL)
199 0 : event_err(1, "%s: failed to register rpc at %s",
200 : __func__, uri);
201 0 : memcpy(constructed_uri, EVRPC_URI_PREFIX, strlen(EVRPC_URI_PREFIX));
202 0 : memcpy(constructed_uri + strlen(EVRPC_URI_PREFIX), uri, strlen(uri));
203 0 : constructed_uri[constructed_uri_len - 1] = '\0';
204 :
205 0 : return (constructed_uri);
206 : }
207 :
208 : int
209 0 : evrpc_register_rpc(struct evrpc_base *base, struct evrpc *rpc,
210 : void (*cb)(struct evrpc_req_generic *, void *), void *cb_arg)
211 : {
212 0 : char *constructed_uri = evrpc_construct_uri(rpc->uri);
213 :
214 0 : rpc->base = base;
215 0 : rpc->cb = cb;
216 0 : rpc->cb_arg = cb_arg;
217 :
218 0 : TAILQ_INSERT_TAIL(&base->registered_rpcs, rpc, next);
219 :
220 0 : evhttp_set_cb(base->http_server,
221 : constructed_uri,
222 : evrpc_request_cb,
223 : rpc);
224 :
225 0 : free(constructed_uri);
226 :
227 0 : return (0);
228 : }
229 :
230 : int
231 0 : evrpc_unregister_rpc(struct evrpc_base *base, const char *name)
232 : {
233 0 : char *registered_uri = NULL;
234 : struct evrpc *rpc;
235 :
236 : /* find the right rpc; linear search might be slow */
237 0 : TAILQ_FOREACH(rpc, &base->registered_rpcs, next) {
238 0 : if (strcmp(rpc->uri, name) == 0)
239 0 : break;
240 : }
241 0 : if (rpc == NULL) {
242 : /* We did not find an RPC with this name */
243 0 : return (-1);
244 : }
245 0 : TAILQ_REMOVE(&base->registered_rpcs, rpc, next);
246 :
247 0 : free((char *)rpc->uri);
248 0 : free(rpc);
249 :
250 0 : registered_uri = evrpc_construct_uri(name);
251 :
252 : /* remove the http server callback */
253 0 : assert(evhttp_del_cb(base->http_server, registered_uri) == 0);
254 :
255 0 : free(registered_uri);
256 0 : return (0);
257 : }
258 :
259 : static void
260 0 : evrpc_request_cb(struct evhttp_request *req, void *arg)
261 : {
262 0 : struct evrpc *rpc = arg;
263 0 : struct evrpc_req_generic *rpc_state = NULL;
264 :
265 : /* let's verify the outside parameters */
266 0 : if (req->type != EVHTTP_REQ_POST ||
267 0 : EVBUFFER_LENGTH(req->input_buffer) <= 0)
268 : goto error;
269 :
270 : /*
271 : * we might want to allow hooks to suspend the processing,
272 : * but at the moment, we assume that they just act as simple
273 : * filters.
274 : */
275 0 : if (evrpc_process_hooks(&rpc->base->input_hooks,
276 : req, req->input_buffer) == -1)
277 0 : goto error;
278 :
279 0 : rpc_state = calloc(1, sizeof(struct evrpc_req_generic));
280 0 : if (rpc_state == NULL)
281 0 : goto error;
282 :
283 : /* let's check that we can parse the request */
284 0 : rpc_state->request = rpc->request_new();
285 0 : if (rpc_state->request == NULL)
286 0 : goto error;
287 :
288 0 : rpc_state->rpc = rpc;
289 :
290 0 : if (rpc->request_unmarshal(
291 : rpc_state->request, req->input_buffer) == -1) {
292 : /* we failed to parse the request; that's a bummer */
293 0 : goto error;
294 : }
295 :
296 : /* at this point, we have a well formed request, prepare the reply */
297 :
298 0 : rpc_state->reply = rpc->reply_new();
299 0 : if (rpc_state->reply == NULL)
300 0 : goto error;
301 :
302 0 : rpc_state->http_req = req;
303 0 : rpc_state->done = evrpc_request_done;
304 :
305 : /* give the rpc to the user; they can deal with it */
306 0 : rpc->cb(rpc_state, rpc->cb_arg);
307 :
308 0 : return;
309 :
310 : error:
311 0 : evrpc_reqstate_free(rpc_state);
312 0 : evhttp_send_error(req, HTTP_SERVUNAVAIL, "Service Error");
313 0 : return;
314 : }
315 :
316 : void
317 0 : evrpc_reqstate_free(struct evrpc_req_generic* rpc_state)
318 : {
319 : /* clean up all memory */
320 0 : if (rpc_state != NULL) {
321 0 : struct evrpc *rpc = rpc_state->rpc;
322 :
323 0 : if (rpc_state->request != NULL)
324 0 : rpc->request_free(rpc_state->request);
325 0 : if (rpc_state->reply != NULL)
326 0 : rpc->reply_free(rpc_state->reply);
327 0 : free(rpc_state);
328 : }
329 0 : }
330 :
331 : void
332 0 : evrpc_request_done(struct evrpc_req_generic* rpc_state)
333 : {
334 0 : struct evhttp_request *req = rpc_state->http_req;
335 0 : struct evrpc *rpc = rpc_state->rpc;
336 0 : struct evbuffer* data = NULL;
337 :
338 0 : if (rpc->reply_complete(rpc_state->reply) == -1) {
339 : /* the reply was not completely filled in. error out */
340 0 : goto error;
341 : }
342 :
343 0 : if ((data = evbuffer_new()) == NULL) {
344 : /* out of memory */
345 0 : goto error;
346 : }
347 :
348 : /* serialize the reply */
349 0 : rpc->reply_marshal(data, rpc_state->reply);
350 :
351 : /* do hook based tweaks to the request */
352 0 : if (evrpc_process_hooks(&rpc->base->output_hooks,
353 : req, data) == -1)
354 0 : goto error;
355 :
356 : /* on success, we are going to transmit marshaled binary data */
357 0 : if (evhttp_find_header(req->output_headers, "Content-Type") == NULL) {
358 0 : evhttp_add_header(req->output_headers,
359 : "Content-Type", "application/octet-stream");
360 : }
361 :
362 0 : evhttp_send_reply(req, HTTP_OK, "OK", data);
363 :
364 0 : evbuffer_free(data);
365 :
366 0 : evrpc_reqstate_free(rpc_state);
367 :
368 0 : return;
369 :
370 : error:
371 0 : if (data != NULL)
372 0 : evbuffer_free(data);
373 0 : evrpc_reqstate_free(rpc_state);
374 0 : evhttp_send_error(req, HTTP_SERVUNAVAIL, "Service Error");
375 0 : return;
376 : }
377 :
378 : /* Client implementation of RPC site */
379 :
380 : static int evrpc_schedule_request(struct evhttp_connection *connection,
381 : struct evrpc_request_wrapper *ctx);
382 :
383 : struct evrpc_pool *
384 0 : evrpc_pool_new(struct event_base *base)
385 : {
386 0 : struct evrpc_pool *pool = calloc(1, sizeof(struct evrpc_pool));
387 0 : if (pool == NULL)
388 0 : return (NULL);
389 :
390 0 : TAILQ_INIT(&pool->connections);
391 0 : TAILQ_INIT(&pool->requests);
392 :
393 0 : TAILQ_INIT(&pool->input_hooks);
394 0 : TAILQ_INIT(&pool->output_hooks);
395 :
396 0 : pool->base = base;
397 0 : pool->timeout = -1;
398 :
399 0 : return (pool);
400 : }
401 :
402 : static void
403 0 : evrpc_request_wrapper_free(struct evrpc_request_wrapper *request)
404 : {
405 0 : free(request->name);
406 0 : free(request);
407 0 : }
408 :
409 : void
410 0 : evrpc_pool_free(struct evrpc_pool *pool)
411 : {
412 : struct evhttp_connection *connection;
413 : struct evrpc_request_wrapper *request;
414 : struct evrpc_hook *hook;
415 :
416 0 : while ((request = TAILQ_FIRST(&pool->requests)) != NULL) {
417 0 : TAILQ_REMOVE(&pool->requests, request, next);
418 : /* if this gets more complicated we need our own function */
419 0 : evrpc_request_wrapper_free(request);
420 : }
421 :
422 0 : while ((connection = TAILQ_FIRST(&pool->connections)) != NULL) {
423 0 : TAILQ_REMOVE(&pool->connections, connection, next);
424 0 : evhttp_connection_free(connection);
425 : }
426 :
427 0 : while ((hook = TAILQ_FIRST(&pool->input_hooks)) != NULL) {
428 0 : assert(evrpc_remove_hook(pool, EVRPC_INPUT, hook));
429 : }
430 :
431 0 : while ((hook = TAILQ_FIRST(&pool->output_hooks)) != NULL) {
432 0 : assert(evrpc_remove_hook(pool, EVRPC_OUTPUT, hook));
433 : }
434 :
435 0 : free(pool);
436 0 : }
437 :
438 : /*
439 : * Add a connection to the RPC pool. A request scheduled on the pool
440 : * may use any available connection.
441 : */
442 :
443 : void
444 0 : evrpc_pool_add_connection(struct evrpc_pool *pool,
445 : struct evhttp_connection *connection) {
446 0 : assert(connection->http_server == NULL);
447 0 : TAILQ_INSERT_TAIL(&pool->connections, connection, next);
448 :
449 : /*
450 : * associate an event base with this connection
451 : */
452 0 : if (pool->base != NULL)
453 0 : evhttp_connection_set_base(connection, pool->base);
454 :
455 : /*
456 : * unless a timeout was specifically set for a connection,
457 : * the connection inherits the timeout from the pool.
458 : */
459 0 : if (connection->timeout == -1)
460 0 : connection->timeout = pool->timeout;
461 :
462 : /*
463 : * if we have any requests pending, schedule them with the new
464 : * connections.
465 : */
466 :
467 0 : if (TAILQ_FIRST(&pool->requests) != NULL) {
468 0 : struct evrpc_request_wrapper *request =
469 : TAILQ_FIRST(&pool->requests);
470 0 : TAILQ_REMOVE(&pool->requests, request, next);
471 0 : evrpc_schedule_request(connection, request);
472 : }
473 0 : }
474 :
475 : void
476 0 : evrpc_pool_set_timeout(struct evrpc_pool *pool, int timeout_in_secs)
477 : {
478 : struct evhttp_connection *evcon;
479 0 : TAILQ_FOREACH(evcon, &pool->connections, next) {
480 0 : evcon->timeout = timeout_in_secs;
481 : }
482 0 : pool->timeout = timeout_in_secs;
483 0 : }
484 :
485 :
486 : static void evrpc_reply_done(struct evhttp_request *, void *);
487 : static void evrpc_request_timeout(int, short, void *);
488 :
489 : /*
490 : * Finds a connection object associated with the pool that is currently
491 : * idle and can be used to make a request.
492 : */
493 : static struct evhttp_connection *
494 0 : evrpc_pool_find_connection(struct evrpc_pool *pool)
495 : {
496 : struct evhttp_connection *connection;
497 0 : TAILQ_FOREACH(connection, &pool->connections, next) {
498 0 : if (TAILQ_FIRST(&connection->requests) == NULL)
499 0 : return (connection);
500 : }
501 :
502 0 : return (NULL);
503 : }
504 :
505 : /*
506 : * We assume that the ctx is no longer queued on the pool.
507 : */
508 : static int
509 0 : evrpc_schedule_request(struct evhttp_connection *connection,
510 : struct evrpc_request_wrapper *ctx)
511 : {
512 0 : struct evhttp_request *req = NULL;
513 0 : struct evrpc_pool *pool = ctx->pool;
514 : struct evrpc_status status;
515 0 : char *uri = NULL;
516 0 : int res = 0;
517 :
518 0 : if ((req = evhttp_request_new(evrpc_reply_done, ctx)) == NULL)
519 0 : goto error;
520 :
521 : /* serialize the request data into the output buffer */
522 0 : ctx->request_marshal(req->output_buffer, ctx->request);
523 :
524 0 : uri = evrpc_construct_uri(ctx->name);
525 0 : if (uri == NULL)
526 0 : goto error;
527 :
528 : /* we need to know the connection that we might have to abort */
529 0 : ctx->evcon = connection;
530 :
531 : /* apply hooks to the outgoing request */
532 0 : if (evrpc_process_hooks(&pool->output_hooks,
533 : req, req->output_buffer) == -1)
534 0 : goto error;
535 :
536 0 : if (pool->timeout > 0) {
537 : /*
538 : * a timeout after which the whole rpc is going to be aborted.
539 : */
540 : struct timeval tv;
541 0 : evutil_timerclear(&tv);
542 0 : tv.tv_sec = pool->timeout;
543 0 : evtimer_add(&ctx->ev_timeout, &tv);
544 : }
545 :
546 : /* start the request over the connection */
547 0 : res = evhttp_make_request(connection, req, EVHTTP_REQ_POST, uri);
548 0 : free(uri);
549 :
550 0 : if (res == -1)
551 0 : goto error;
552 :
553 0 : return (0);
554 :
555 : error:
556 0 : memset(&status, 0, sizeof(status));
557 0 : status.error = EVRPC_STATUS_ERR_UNSTARTED;
558 0 : (*ctx->cb)(&status, ctx->request, ctx->reply, ctx->cb_arg);
559 0 : evrpc_request_wrapper_free(ctx);
560 0 : return (-1);
561 : }
562 :
563 : int
564 0 : evrpc_make_request(struct evrpc_request_wrapper *ctx)
565 : {
566 0 : struct evrpc_pool *pool = ctx->pool;
567 :
568 : /* initialize the event structure for this rpc */
569 0 : evtimer_set(&ctx->ev_timeout, evrpc_request_timeout, ctx);
570 0 : if (pool->base != NULL)
571 0 : event_base_set(pool->base, &ctx->ev_timeout);
572 :
573 : /* we better have some available connections on the pool */
574 0 : assert(TAILQ_FIRST(&pool->connections) != NULL);
575 :
576 : /*
577 : * if no connection is available, we queue the request on the pool,
578 : * the next time a connection is empty, the rpc will be send on that.
579 : */
580 0 : TAILQ_INSERT_TAIL(&pool->requests, ctx, next);
581 :
582 0 : evrpc_pool_schedule(pool);
583 :
584 0 : return (0);
585 : }
586 :
587 : static void
588 0 : evrpc_reply_done(struct evhttp_request *req, void *arg)
589 : {
590 0 : struct evrpc_request_wrapper *ctx = arg;
591 0 : struct evrpc_pool *pool = ctx->pool;
592 : struct evrpc_status status;
593 0 : int res = -1;
594 :
595 : /* cancel any timeout we might have scheduled */
596 0 : event_del(&ctx->ev_timeout);
597 :
598 0 : memset(&status, 0, sizeof(status));
599 0 : status.http_req = req;
600 :
601 : /* we need to get the reply now */
602 0 : if (req != NULL) {
603 : /* apply hooks to the incoming request */
604 0 : if (evrpc_process_hooks(&pool->input_hooks,
605 : req, req->input_buffer) == -1) {
606 0 : status.error = EVRPC_STATUS_ERR_HOOKABORTED;
607 0 : res = -1;
608 : } else {
609 0 : res = ctx->reply_unmarshal(ctx->reply,
610 : req->input_buffer);
611 0 : if (res == -1) {
612 0 : status.error = EVRPC_STATUS_ERR_BADPAYLOAD;
613 : }
614 : }
615 : } else {
616 0 : status.error = EVRPC_STATUS_ERR_TIMEOUT;
617 : }
618 :
619 0 : if (res == -1) {
620 : /* clear everything that we might have written previously */
621 0 : ctx->reply_clear(ctx->reply);
622 : }
623 :
624 0 : (*ctx->cb)(&status, ctx->request, ctx->reply, ctx->cb_arg);
625 :
626 0 : evrpc_request_wrapper_free(ctx);
627 :
628 : /* the http layer owns the request structure */
629 :
630 : /* see if we can schedule another request */
631 0 : evrpc_pool_schedule(pool);
632 0 : }
633 :
634 : static void
635 0 : evrpc_pool_schedule(struct evrpc_pool *pool)
636 : {
637 0 : struct evrpc_request_wrapper *ctx = TAILQ_FIRST(&pool->requests);
638 : struct evhttp_connection *evcon;
639 :
640 : /* if no requests are pending, we have no work */
641 0 : if (ctx == NULL)
642 0 : return;
643 :
644 0 : if ((evcon = evrpc_pool_find_connection(pool)) != NULL) {
645 0 : TAILQ_REMOVE(&pool->requests, ctx, next);
646 0 : evrpc_schedule_request(evcon, ctx);
647 : }
648 : }
649 :
650 : static void
651 0 : evrpc_request_timeout(int fd, short what, void *arg)
652 : {
653 0 : struct evrpc_request_wrapper *ctx = arg;
654 0 : struct evhttp_connection *evcon = ctx->evcon;
655 0 : assert(evcon != NULL);
656 :
657 0 : evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT);
658 0 : }
|