diff --git a/Makefile.am b/Makefile.am index 30ca7ea5..92bb97d8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,6 +4,7 @@ AUTOMAKE_OPTIONS = foreign no-dependencies bin_SCRIPTS = event_rpcgen.py EXTRA_DIST = acconfig.h event.h event-internal.h log.h evsignal.h evdns.3 \ + evrpc.h evrpc-internal.h \ event.3 \ kqueue.c epoll_sub.c epoll.c select.c rtsig.c poll.c signal.c \ evport.c devpoll.c event_rpcgen.py \ @@ -39,7 +40,8 @@ SYS_INCLUDES = endif libevent_la_SOURCES = event.c buffer.c evbuffer.c log.c event_tagging.c \ - http.c evhttp.h http-internal.h evdns.c evdns.h $(SYS_SRC) + http.c evhttp.h http-internal.h evdns.c evdns.h evrpc.c \ + evrpc.h evrpc-internal.h $(SYS_SRC) libevent_la_LIBADD = @LTLIBOBJS@ $(SYS_LIBS) libevent_la_LDFLAGS = -release @VERSION@ -version-info 1:3:0 diff --git a/evhttp.h b/evhttp.h index cb3b2cf4..ed74d71d 100644 --- a/evhttp.h +++ b/evhttp.h @@ -53,6 +53,7 @@ typedef unsigned char u_char; #define HTTP_MOVEPERM 301 #define HTTP_MOVETEMP 302 #define HTTP_NOTFOUND 404 +#define HTTP_SERVUNAVAIL 503 struct evhttp; struct evhttp_request; @@ -82,6 +83,50 @@ void evhttp_send_reply(struct evhttp_request *, int, const char *, /* Interfaces for making requests */ enum evhttp_cmd_type { EVHTTP_REQ_GET, EVHTTP_REQ_POST, EVHTTP_REQ_HEAD }; +enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; + +/* + * the request structure that a server receives. + * WARNING: expect this structure to change. I will try to provide + * reasonable accessors. + */ +struct evhttp_request { + TAILQ_ENTRY(evhttp_request) next; + + /* the connection object that this request belongs to */ + struct evhttp_connection *evcon; + int flags; +#define EVHTTP_REQ_OWN_CONNECTION 0x0001 + + struct evkeyvalq *input_headers; + struct evkeyvalq *output_headers; + + /* xxx: do we still need these? */ + char *remote_host; + u_short remote_port; + + enum evhttp_request_kind kind; + enum evhttp_cmd_type type; + + char *uri; /* uri after HTTP request was parsed */ + + char major; /* HTTP Major number */ + char minor; /* HTTP Minor number */ + + int got_firstline; + int response_code; /* HTTP Response code */ + char *response_code_line; /* Readable response */ + + struct evbuffer *input_buffer; /* read data */ + int ntoread; + + struct evbuffer *output_buffer; /* outgoing post or data */ + + /* Callback */ + void (*cb)(struct evhttp_request *, void *); + void *cb_arg; +}; + /* * Creates a new request object that needs to be filled in with the request * parameters. The callback is executed when the request completed or an diff --git a/evrpc-internal.h b/evrpc-internal.h new file mode 100644 index 00000000..b5c4eaf8 --- /dev/null +++ b/evrpc-internal.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVRPC_INTERNAL_H_ +#define _EVRPC_INTERNAL_H_ + +struct evrpc; + +#define EVRPC_URI_PREFIX ".rpc." + +struct evrpc_base { + /* the HTTP server under which we register our RPC calls */ + struct evhttp* http_server; + + /* a list of all RPCs registered with us */ + TAILQ_HEAD(evrpc_list, evrpc) registered_rpcs; +}; + +struct evrpc_req_generic; +void evrpc_reqstate_free(struct evrpc_req_generic* rpc_state); + +#endif /* _EVRPC_INTERNAL_H_ */ diff --git a/evrpc.c b/evrpc.c new file mode 100644 index 00000000..7def2cfe --- /dev/null +++ b/evrpc.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#include "misc.h" +#endif +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#include +#include +#ifndef WIN32 +#include +#endif +#include +#include +#include +#include + +#include "event.h" +#include "evrpc.h" +#include "evrpc-internal.h" +#include "evhttp.h" +#include "log.h" + +struct evrpc_base * +evrpc_init(struct evhttp* http_server) +{ + struct evrpc_base* base = calloc(1, sizeof(struct evrpc_base)); + if (base == NULL) + return (NULL); + + TAILQ_INIT(&base->registered_rpcs); + base->http_server = http_server; + + return (base); +} + +void evrpc_request_cb(struct evhttp_request *, void *); +void evrpc_request_done(struct evrpc_req_generic*); + +/* + * Registers a new RPC with the HTTP server. The evrpc object is expected + * to have been filled in via the EVRPC_REGISTER_OBJECT macro which in turn + * calls this function. + */ + +int +evrpc_register_rpc(struct evrpc_base *base, struct evrpc *rpc, + void (*cb)(struct evrpc_req_generic *, void *), void *cb_arg) +{ + char *constructed_uri; + int constructed_uri_len; + + rpc->cb = cb; + rpc->cb_arg = cb_arg; + + constructed_uri_len = strlen(EVRPC_URI_PREFIX) + strlen(rpc->uri) + 1; + if ((constructed_uri = malloc(constructed_uri_len)) == NULL) + event_err(1, "%s: failed to register rpc at %s", + __func__, rpc->uri); + memcpy(constructed_uri, EVRPC_URI_PREFIX, strlen(EVRPC_URI_PREFIX)); + memcpy(constructed_uri + strlen(EVRPC_URI_PREFIX), + rpc->uri, strlen(rpc->uri)); + constructed_uri[constructed_uri_len - 1] = '\0'; + + TAILQ_INSERT_TAIL(&base->registered_rpcs, rpc, next); + + evhttp_set_cb(base->http_server, + constructed_uri, + evrpc_request_cb, + rpc); + + return (0); +} + +void +evrpc_request_cb(struct evhttp_request *req, void *arg) +{ + struct evrpc *rpc = arg; + struct evrpc_req_generic *rpc_state = NULL; + + /* let's verify the outside parameters */ + if (req->type != EVHTTP_REQ_POST || + EVBUFFER_LENGTH(req->input_buffer) <= 0) + goto error; + + rpc_state = calloc(1, sizeof(struct evrpc_req_generic)); + if (rpc_state == NULL) + goto error; + + /* let's check that we can parse the request */ + rpc_state->request = rpc->request_new(); + if (rpc_state->request == NULL) + goto error; + if (rpc->request_unmarshal( + rpc_state->request, req->input_buffer) == -1) { + /* we failed to parse the request; that's a bummer */ + goto error; + } + if (!rpc->request_complete(rpc_state->request)) { + /* + * we were able to parse the structure but not all required + * fields had been filled in. + */ + goto error; + } + + /* at this point, we have a well formed request, prepare the reply */ + + rpc_state->reply = rpc->reply_new(); + if (rpc_state->reply == NULL) + goto error; + + rpc_state->rpc = rpc; + rpc_state->http_req = req; + rpc_state->done = evrpc_request_done; + + /* give the rpc to the user; they can deal with it */ + rpc->cb(rpc_state, rpc->cb_arg); + + return; + +error: + evrpc_reqstate_free(rpc_state); + evhttp_send_error(req, HTTP_SERVUNAVAIL, "Service Error"); + return; +} + +void +evrpc_reqstate_free(struct evrpc_req_generic* rpc_state) +{ + struct evrpc *rpc = rpc_state->rpc; + + /* clean up all memory */ + if (rpc_state != NULL) { + if (rpc_state->request != NULL) + rpc->request_free(rpc_state); + if (rpc_state->reply != NULL) + rpc->reply_free(rpc_state->reply); + free(rpc_state); + } +} + +void +evrpc_request_done(struct evrpc_req_generic* rpc_state) +{ + struct evhttp_request *req = rpc_state->http_req; + struct evrpc *rpc = rpc_state->rpc; + struct evbuffer* data; + + if (!rpc->reply_complete(rpc_state->reply)) { + /* the reply was not completely filled in. error out */ + goto error; + } + + if ((data = evbuffer_new()) == NULL) { + /* out of memory */ + goto error; + } + + /* serialize the reply */ + rpc->reply_marshal(data, rpc_state->reply); + + evhttp_send_reply(req, HTTP_OK, "OK", data); + + evbuffer_free(data); + + evrpc_reqstate_free(rpc_state); + + return; + +error: + evrpc_reqstate_free(rpc_state); + evhttp_send_error(req, HTTP_SERVUNAVAIL, "Service Error"); + return; +} diff --git a/evrpc.h b/evrpc.h new file mode 100644 index 00000000..c46dcba9 --- /dev/null +++ b/evrpc.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVRPC_H_ +#define _EVRPC_H_ + +struct evbuffer; +struct evrpc_req_generic; + +/* Encapsulates a request */ +struct evrpc { + TAILQ_ENTRY(evrpc) next; + + /* the URI at which the request handler lives */ + const char* uri; + + /* creates a new request structure */ + void *(*request_new)(void); + + /* creates a new request structure */ + void (*request_free)(void *); + + /* unmarshals the buffer into the proper request structure */ + int (*request_unmarshal)(void *, struct evbuffer *); + + /* verifies that the unmarshaled buffer is complete */ + int (*request_complete)(void *); + + /* creates a new reply structure */ + void *(*reply_new)(void); + + /* creates a new reply structure */ + void (*reply_free)(void *); + + /* verifies that the reply is valid */ + int (*reply_complete)(void *); + + /* marshals the reply into a buffer */ + void (*reply_marshal)(struct evbuffer*, void *); + + /* the callback invoked for each received rpc */ + void (*cb)(struct evrpc_req_generic *, void *); + void *cb_arg; +}; + +#define EVRPC_STRUCT(rpcname) struct evrpc_req__##rpcname + +struct evhttp_request; + +/* We alias the RPC specific structs to this voided one */ +struct evrpc_req_generic { + /* the unmarshaled request object */ + void *request; + + /* the empty reply object that needs to be filled in */ + void *reply; + + /* + * the static structure for this rpc; that can be used to + * automatically unmarshal and marshal the http buffers. + */ + struct evrpc* rpc; + + /* + * the http request structure on which we need to answer. + */ + struct evhttp_request* http_req; + + /* + * callback to reply and finish answering this rpc + */ + void (*done)(struct evrpc_req_generic* rpc); +}; + +#define EVRPC_DEFINE(rpcname, reqstruct, rplystruct) \ +EVRPC_STRUCT(rpcname) { \ + struct reqstruct* request; \ + struct rplystruct* reply; \ + struct evrpc* rpc; \ + void (*done)(struct evrpc* rpc, void *request, void *reply); \ +} + +/* + * EVRPC_REQUEST_DONE is used to answer a request; the reply is expected + * to have been filled in. The request and reply pointers become invalid + * after this call has finished. + */ +#define EVRPC_REQUEST_DONE(rpc_req) do { \ + struct evrpc_req_generic *req = (struct evrpc_req_generic)(rpc_req); \ + req->done(req); \ +} + + +/* Takes a request object and fills it in with the right magic */ +#define EVRPC_REGISTER_OBJECT(rpc, name, request, reply) \ + do { \ + (rpc)->uri = strdup(name); \ + if ((rpc)->uri == NULL) \ + event_err(1, "failed to register object"); \ + (rpc)->request_new = (void *(*)(void))request##_new; \ + (rpc)->request_free = (void (*)(void *))request##_free; \ + (rpc)->request_unmarshal = (int (*)(void *, struct evbuffer *))request##_unmarshal; \ + (rpc)->request_complete = (int (*)(void *))request##_complete; \ + (rpc)->reply_new = (void *(*)(void))reply##_new; \ + (rpc)->reply_free = (void (*)(void *))reply##_free; \ + (rpc)->reply_complete = (int (*)(void *))reply##_complete; \ + (rpc)->reply_marshal = (void (*)(struct evbuffer*, void *))reply##_marshal; \ + } while(0) + +struct evrpc_base; +struct evhttp; + +/* functions to start up the rpc system */ +struct evrpc_base *evrpc_init(struct evhttp *server); + +/* this macro is used to register RPCs with the HTTP Server */ +#define EVRPC_REGISTER(base, name, request, reply, callback, cbarg) \ + do { \ + struct evrpc* rpc = calloc(1, sizeof(struct evrpc)); \ + EVRPC_REGISTER_OBJECT(rpc, name, request, reply); \ + evrpc_register_rpc(base, rpc, \ + (void (*)(struct evrpc_req_generic*, void *))callback, cbarg); \ + } while (0) + +int evrpc_register_rpc(struct evrpc_base *, struct evrpc *, + void (*)(struct evrpc_req_generic*, void *), void *); + +#endif /* _EVRPC_H_ */ diff --git a/http-internal.h b/http-internal.h index f5ad2861..5843b37c 100644 --- a/http-internal.h +++ b/http-internal.h @@ -50,45 +50,6 @@ struct evhttp_connection { void *cb_arg; }; -enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; - -struct evhttp_request { - TAILQ_ENTRY(evhttp_request) next; - - /* the connection object that this request belongs to */ - struct evhttp_connection *evcon; - int flags; -#define EVHTTP_REQ_OWN_CONNECTION 0x0001 - - struct evkeyvalq *input_headers; - struct evkeyvalq *output_headers; - - /* xxx: do we still need these? */ - char *remote_host; - u_short remote_port; - - enum evhttp_request_kind kind; - enum evhttp_cmd_type type; - - char *uri; /* uri after HTTP request was parsed */ - - char major; /* HTTP Major number */ - char minor; /* HTTP Minor number */ - - int got_firstline; - int response_code; /* HTTP Response code */ - char *response_code_line; /* Readable response */ - - struct evbuffer *input_buffer; /* read data */ - int ntoread; - - struct evbuffer *output_buffer; /* outgoing post or data */ - - /* Callback */ - void (*cb)(struct evhttp_request *, void *); - void *cb_arg; -}; - struct evhttp_cb { TAILQ_ENTRY(evhttp_cb) next; diff --git a/http.c b/http.c index e736f206..1e350220 100644 --- a/http.c +++ b/http.c @@ -1145,7 +1145,7 @@ evhttp_send(struct evhttp_request *req, struct evbuffer *databuf) assert(TAILQ_FIRST(&evcon->requests) == req); - /* xxx: not sure if we really should expost the data buffer this way */ + /* xxx: not sure if we really should expose the data buffer this way */ evbuffer_add_buffer(req->output_buffer, databuf); /* Adds headers to the response */ diff --git a/test/Makefile.am b/test/Makefile.am index 66f0e311..840e5f33 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -14,6 +14,7 @@ test_eof_SOURCES = test-eof.c test_weof_SOURCES = test-weof.c test_time_SOURCES = test-time.c regress_SOURCES = regress.c regress.h regress_http.c regress_dns.c \ + regress_rpc.c \ regress.gen.c regress.gen.h bench_SOURCES = bench.c diff --git a/test/regress.c b/test/regress.c index d0f135c0..580b3cab 100644 --- a/test/regress.c +++ b/test/regress.c @@ -884,6 +884,8 @@ main (int argc, char **argv) http_suite(); + rpc_suite(); + dns_suite(); test_simpleread(); diff --git a/test/regress.h b/test/regress.h index 296c93c5..4060ff5c 100644 --- a/test/regress.h +++ b/test/regress.h @@ -34,6 +34,8 @@ extern "C" { void http_suite(void); void http_basic_test(void); +void rpc_suite(void); + void dns_suite(void); #ifdef __cplusplus diff --git a/test/regress_http.c b/test/regress_http.c index a905b03e..b04a3639 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -65,7 +65,7 @@ static struct evhttp *http; void http_basic_cb(struct evhttp_request *req, void *arg); void http_post_cb(struct evhttp_request *req, void *arg); -struct evhttp * +static struct evhttp * http_setup(short *pport) { int i;