mirror of
https://github.com/cuberite/libevent.git
synced 2025-09-08 03:44:22 -04:00
support input/output filters for bufferevents
svn:r748
This commit is contained in:
parent
28add6b9e2
commit
682adc443b
@ -79,6 +79,7 @@ Changes in current version:
|
|||||||
o convert evhttp_connection to use bufferevents.
|
o convert evhttp_connection to use bufferevents.
|
||||||
o use libevent's internal timercmp on all platforms, to avoid bugs on old platforms where timercmp(a,b,<=) is buggy.
|
o use libevent's internal timercmp on all platforms, to avoid bugs on old platforms where timercmp(a,b,<=) is buggy.
|
||||||
o Remove the never-exported, never-used evhttp_hostportfile function.
|
o Remove the never-exported, never-used evhttp_hostportfile function.
|
||||||
|
o Support input/output filters for bufferevents; somewhat similar to libio's model. This will allow us to implement SSL, compression, etc, transparently to users of bufferevents such as the http layer.
|
||||||
|
|
||||||
Changes in 1.4.0:
|
Changes in 1.4.0:
|
||||||
o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr.
|
o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr.
|
||||||
|
@ -81,7 +81,9 @@ event-config.h: config.h
|
|||||||
-e 's/#ifndef /#ifndef _EVENT_/' < config.h >> $@
|
-e 's/#ifndef /#ifndef _EVENT_/' < config.h >> $@
|
||||||
echo "#endif" >> $@
|
echo "#endif" >> $@
|
||||||
|
|
||||||
CORE_SRC = event.c buffer.c evbuffer.c log.c evutil.c $(SYS_SRC)
|
CORE_SRC = event.c buffer.c evbuffer-internal.h evbuffer.c \
|
||||||
|
bufferevent-internal.h \
|
||||||
|
log.c evutil.c $(SYS_SRC)
|
||||||
EXTRA_SRC = event_tagging.c http.c evhttp.h http-internal.h evdns.c \
|
EXTRA_SRC = event_tagging.c http.c evhttp.h http-internal.h evdns.c \
|
||||||
evdns.h evrpc.c evrpc.h evrpc-internal.h mm-internal.h \
|
evdns.h evrpc.c evrpc.h evrpc-internal.h mm-internal.h \
|
||||||
strlcpy.c strlcpy-internal.h strlcpy-internal.h
|
strlcpy.c strlcpy-internal.h strlcpy-internal.h
|
||||||
|
61
bufferevent-internal.h
Normal file
61
bufferevent-internal.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2008 Niels Provos <provos@citi.umich.edu>
|
||||||
|
* 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 _BUFFEREVENT_INTERNAL_H_
|
||||||
|
#define _BUFFEREVENT_INTERNAL_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "evutil.h"
|
||||||
|
|
||||||
|
struct bufferevent_filter {
|
||||||
|
/** allows chaining of filters */
|
||||||
|
TAILQ_ENTRY(bufferevent_filter) (next);
|
||||||
|
|
||||||
|
/** used for intermediary state either on the input or output path */
|
||||||
|
struct evbuffer *buffer;
|
||||||
|
|
||||||
|
/** initializes the context provided to process */
|
||||||
|
void (*init_context)(void *);
|
||||||
|
|
||||||
|
/** frees any context related to ctx */
|
||||||
|
void (*free_context)(void *);
|
||||||
|
|
||||||
|
enum bufferevent_filter_result (*process)(
|
||||||
|
struct evbuffer *src, struct evbuffer *dst,
|
||||||
|
enum bufferevent_filter_state flags, void *ctx);
|
||||||
|
|
||||||
|
void *ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _BUFFEREVENT_INTERNAL_H_ */
|
341
evbuffer.c
341
evbuffer.c
@ -34,6 +34,7 @@
|
|||||||
#ifdef HAVE_SYS_TIME_H
|
#ifdef HAVE_SYS_TIME_H
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include <sys/queue.h>
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -52,11 +53,17 @@
|
|||||||
#include "event2/buffer.h"
|
#include "event2/buffer.h"
|
||||||
#include "event2/bufferevent_struct.h"
|
#include "event2/bufferevent_struct.h"
|
||||||
#include "event2/event.h"
|
#include "event2/event.h"
|
||||||
|
#include "log.h"
|
||||||
#include "mm-internal.h"
|
#include "mm-internal.h"
|
||||||
|
#include "bufferevent-internal.h"
|
||||||
|
|
||||||
/* prototypes */
|
/* prototypes */
|
||||||
|
|
||||||
void bufferevent_read_pressure_cb(struct evbuffer *, size_t, size_t, void *);
|
static void bufferevent_read_pressure_cb(
|
||||||
|
struct evbuffer *, size_t, size_t, void *);
|
||||||
|
static int bufferevent_process_filters(
|
||||||
|
struct bufferevent_filter *, struct evbuffer *,
|
||||||
|
enum bufferevent_filter_state);
|
||||||
|
|
||||||
static int
|
static int
|
||||||
bufferevent_add(struct event *ev, int timeout)
|
bufferevent_add(struct event *ev, int timeout)
|
||||||
@ -77,7 +84,7 @@ bufferevent_add(struct event *ev, int timeout)
|
|||||||
* We use it to apply back pressure on the reading side.
|
* We use it to apply back pressure on the reading side.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void
|
static void
|
||||||
bufferevent_read_pressure_cb(struct evbuffer *buf, size_t old, size_t now,
|
bufferevent_read_pressure_cb(struct evbuffer *buf, size_t old, size_t now,
|
||||||
void *arg) {
|
void *arg) {
|
||||||
struct bufferevent *bufev = arg;
|
struct bufferevent *bufev = arg;
|
||||||
@ -93,13 +100,47 @@ bufferevent_read_pressure_cb(struct evbuffer *buf, size_t old, size_t now,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
bufferevent_read_closure(struct bufferevent *bufev, int progress)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
bufferevent_add(&bufev->ev_read, bufev->timeout_read);
|
||||||
|
|
||||||
|
/* nothing user visible changed? */
|
||||||
|
if (!progress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* See if this callbacks meets the water marks */
|
||||||
|
len = EVBUFFER_LENGTH(bufev->input);
|
||||||
|
if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* For read pressure, we use the buffer exposed to the users.
|
||||||
|
* Filters can arbitrarily change the data that users get to see,
|
||||||
|
* in particular, a user might select a watermark that is smaller
|
||||||
|
* then what a filter needs to make progress.
|
||||||
|
*/
|
||||||
|
if (bufev->wm_read.high != 0 && len >= bufev->wm_read.high) {
|
||||||
|
event_del(&bufev->ev_read);
|
||||||
|
|
||||||
|
/* Now schedule a callback for us when the buffer changes */
|
||||||
|
evbuffer_setcb(bufev->input,
|
||||||
|
bufferevent_read_pressure_cb, bufev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Invoke the user callback - must always be called last */
|
||||||
|
if (bufev->readcb != NULL)
|
||||||
|
(*bufev->readcb)(bufev, bufev->cbarg);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
bufferevent_readcb(evutil_socket_t fd, short event, void *arg)
|
bufferevent_readcb(evutil_socket_t fd, short event, void *arg)
|
||||||
{
|
{
|
||||||
struct bufferevent *bufev = arg;
|
struct bufferevent *bufev = arg;
|
||||||
int res = 0;
|
struct evbuffer *input;
|
||||||
|
int res = 0, progress = 1;
|
||||||
short what = EVBUFFER_READ;
|
short what = EVBUFFER_READ;
|
||||||
size_t len;
|
|
||||||
int howmuch = -1;
|
int howmuch = -1;
|
||||||
|
|
||||||
if (event == EV_TIMEOUT) {
|
if (event == EV_TIMEOUT) {
|
||||||
@ -107,23 +148,28 @@ bufferevent_readcb(evutil_socket_t fd, short event, void *arg)
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TAILQ_FIRST(&bufev->input_filters) != NULL)
|
||||||
|
input = TAILQ_FIRST(&bufev->input_filters)->buffer;
|
||||||
|
else
|
||||||
|
input = bufev->input;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we have a high watermark configured then we don't want to
|
* If we have a high watermark configured then we don't want to
|
||||||
* read more data than would make us reach the watermark.
|
* read more data than would make us reach the watermark.
|
||||||
*/
|
*/
|
||||||
if (bufev->wm_read.high != 0) {
|
if (bufev->wm_read.high != 0) {
|
||||||
howmuch = bufev->wm_read.high - EVBUFFER_LENGTH(bufev->input);
|
howmuch = bufev->wm_read.high - EVBUFFER_LENGTH(input);
|
||||||
/* we might have lowered the watermark, stop reading */
|
/* we might have lowered the watermark, stop reading */
|
||||||
if (howmuch <= 0) {
|
if (howmuch <= 0) {
|
||||||
struct evbuffer *buf = bufev->input;
|
|
||||||
event_del(&bufev->ev_read);
|
event_del(&bufev->ev_read);
|
||||||
evbuffer_setcb(buf,
|
evbuffer_setcb(input,
|
||||||
bufferevent_read_pressure_cb, bufev);
|
bufferevent_read_pressure_cb, bufev);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res = evbuffer_read(bufev->input, fd, howmuch);
|
res = evbuffer_read(input, fd, howmuch);
|
||||||
|
|
||||||
if (res == -1) {
|
if (res == -1) {
|
||||||
if (errno == EAGAIN || errno == EINTR)
|
if (errno == EAGAIN || errno == EINTR)
|
||||||
goto reschedule;
|
goto reschedule;
|
||||||
@ -134,26 +180,27 @@ bufferevent_readcb(evutil_socket_t fd, short event, void *arg)
|
|||||||
what |= EVBUFFER_EOF;
|
what |= EVBUFFER_EOF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TAILQ_FIRST(&bufev->input_filters) != NULL) {
|
||||||
|
int state = BEV_NORMAL;
|
||||||
|
if (what & EVBUFFER_EOF)
|
||||||
|
state = BEV_FLUSH;
|
||||||
|
/* XXX(niels): what to do about EVBUFFER_ERROR? */
|
||||||
|
progress = bufferevent_process_filters(
|
||||||
|
TAILQ_FIRST(&bufev->input_filters),
|
||||||
|
bufev->input,
|
||||||
|
state);
|
||||||
|
|
||||||
|
/* propagate potential errors to the user */
|
||||||
|
if (progress == -1) {
|
||||||
|
res = -1;
|
||||||
|
what |= EVBUFFER_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (res <= 0)
|
if (res <= 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
bufferevent_add(&bufev->ev_read, bufev->timeout_read);
|
bufferevent_read_closure(bufev, progress);
|
||||||
|
|
||||||
/* See if this callbacks meets the water marks */
|
|
||||||
len = EVBUFFER_LENGTH(bufev->input);
|
|
||||||
if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
|
|
||||||
return;
|
|
||||||
if (bufev->wm_read.high != 0 && len >= bufev->wm_read.high) {
|
|
||||||
struct evbuffer *buf = bufev->input;
|
|
||||||
event_del(&bufev->ev_read);
|
|
||||||
|
|
||||||
/* Now schedule a callback for us when the buffer changes */
|
|
||||||
evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Invoke the user callback - must always be called last */
|
|
||||||
if (bufev->readcb != NULL)
|
|
||||||
(*bufev->readcb)(bufev, bufev->cbarg);
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
reschedule:
|
reschedule:
|
||||||
@ -266,6 +313,9 @@ bufferevent_new(evutil_socket_t fd, evbuffercb readcb, evbuffercb writecb,
|
|||||||
*/
|
*/
|
||||||
bufev->enabled = EV_WRITE;
|
bufev->enabled = EV_WRITE;
|
||||||
|
|
||||||
|
TAILQ_INIT(&bufev->input_filters);
|
||||||
|
TAILQ_INIT(&bufev->output_filters);
|
||||||
|
|
||||||
return (bufev);
|
return (bufev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,6 +333,8 @@ bufferevent_setcb(struct bufferevent *bufev,
|
|||||||
void
|
void
|
||||||
bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd)
|
bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd)
|
||||||
{
|
{
|
||||||
|
struct bufferevent_filter *filter;
|
||||||
|
|
||||||
event_del(&bufev->ev_read);
|
event_del(&bufev->ev_read);
|
||||||
event_del(&bufev->ev_write);
|
event_del(&bufev->ev_write);
|
||||||
|
|
||||||
@ -293,6 +345,21 @@ bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd)
|
|||||||
event_base_set(bufev->ev_base, &bufev->ev_write);
|
event_base_set(bufev->ev_base, &bufev->ev_write);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* we need to free all filter contexts and then init them again */
|
||||||
|
TAILQ_FOREACH(filter, &bufev->input_filters, next) {
|
||||||
|
if (filter->free_context)
|
||||||
|
filter->free_context(filter->ctx);
|
||||||
|
if (filter->init_context)
|
||||||
|
filter->init_context(filter->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
TAILQ_FOREACH(filter, &bufev->output_filters, next) {
|
||||||
|
if (filter->free_context)
|
||||||
|
filter->free_context(filter->ctx);
|
||||||
|
if (filter->init_context)
|
||||||
|
filter->init_context(filter->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
/* might have to manually trigger event registration */
|
/* might have to manually trigger event registration */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +372,9 @@ bufferevent_input(struct bufferevent *bufev)
|
|||||||
struct evbuffer *
|
struct evbuffer *
|
||||||
bufferevent_output(struct bufferevent *bufev)
|
bufferevent_output(struct bufferevent *bufev)
|
||||||
{
|
{
|
||||||
return (bufev->output);
|
return TAILQ_FIRST(&bufev->output_filters) != NULL ?
|
||||||
|
TAILQ_FIRST(&bufev->output_filters)->buffer :
|
||||||
|
bufev->output;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -324,21 +393,55 @@ bufferevent_priority_set(struct bufferevent *bufev, int priority)
|
|||||||
void
|
void
|
||||||
bufferevent_free(struct bufferevent *bufev)
|
bufferevent_free(struct bufferevent *bufev)
|
||||||
{
|
{
|
||||||
|
struct bufferevent_filter *filter;
|
||||||
|
|
||||||
event_del(&bufev->ev_read);
|
event_del(&bufev->ev_read);
|
||||||
event_del(&bufev->ev_write);
|
event_del(&bufev->ev_write);
|
||||||
|
|
||||||
evbuffer_free(bufev->input);
|
evbuffer_free(bufev->input);
|
||||||
evbuffer_free(bufev->output);
|
evbuffer_free(bufev->output);
|
||||||
|
|
||||||
|
/* free input and output filters */
|
||||||
|
while ((filter = TAILQ_FIRST(&bufev->input_filters)) != NULL) {
|
||||||
|
bufferevent_filter_remove(bufev, BEV_INPUT, filter);
|
||||||
|
|
||||||
|
bufferevent_filter_free(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((filter = TAILQ_FIRST(&bufev->output_filters)) != NULL) {
|
||||||
|
bufferevent_filter_remove(bufev, BEV_OUTPUT, filter);
|
||||||
|
|
||||||
|
bufferevent_filter_free(filter);
|
||||||
|
}
|
||||||
|
|
||||||
mm_free(bufev);
|
mm_free(bufev);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
static inline void
|
* Executes filters on the written data and schedules a network write if
|
||||||
|
* necessary.
|
||||||
|
*/
|
||||||
|
static inline int
|
||||||
bufferevent_write_closure(struct bufferevent *bufev, int progress)
|
bufferevent_write_closure(struct bufferevent *bufev, int progress)
|
||||||
{
|
{
|
||||||
|
/* if no data was written, we do not need to do anything */
|
||||||
|
if (!progress)
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
if (TAILQ_FIRST(&bufev->output_filters) != NULL) {
|
||||||
|
progress = bufferevent_process_filters(
|
||||||
|
TAILQ_FIRST(&bufev->output_filters),
|
||||||
|
bufev->output, BEV_NORMAL);
|
||||||
|
if (progress == -1) {
|
||||||
|
(*bufev->errorcb)(bufev, EVBUFFER_ERROR, bufev->cbarg);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* If everything is okay, we need to schedule a write */
|
/* If everything is okay, we need to schedule a write */
|
||||||
if (progress && (bufev->enabled & EV_WRITE))
|
if (bufev->enabled & EV_WRITE)
|
||||||
bufferevent_add(&bufev->ev_write, bufev->timeout_write);
|
bufferevent_add(&bufev->ev_write, bufev->timeout_write);
|
||||||
|
|
||||||
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -349,25 +452,34 @@ bufferevent_write_closure(struct bufferevent *bufev, int progress)
|
|||||||
int
|
int
|
||||||
bufferevent_write(struct bufferevent *bufev, const void *data, size_t size)
|
bufferevent_write(struct bufferevent *bufev, const void *data, size_t size)
|
||||||
{
|
{
|
||||||
if (evbuffer_add(bufev->output, data, size) == -1)
|
struct evbuffer *output;
|
||||||
|
|
||||||
|
if (TAILQ_FIRST(&bufev->output_filters) != NULL)
|
||||||
|
output = TAILQ_FIRST(&bufev->output_filters)->buffer;
|
||||||
|
else
|
||||||
|
output = bufev->output;
|
||||||
|
|
||||||
|
if (evbuffer_add(output, data, size) == -1)
|
||||||
return (-1);
|
return (-1);
|
||||||
|
|
||||||
bufferevent_write_closure(bufev, size > 0);
|
return (bufferevent_write_closure(bufev, size > 0));
|
||||||
|
|
||||||
return (0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf)
|
bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf)
|
||||||
{
|
{
|
||||||
int len = EVBUFFER_LENGTH(bufev->output);
|
int len = EVBUFFER_LENGTH(buf);
|
||||||
|
struct evbuffer *output;
|
||||||
|
|
||||||
if (evbuffer_add_buffer(bufev->output, buf) == -1)
|
if (TAILQ_FIRST(&bufev->output_filters) != NULL)
|
||||||
|
output = TAILQ_FIRST(&bufev->output_filters)->buffer;
|
||||||
|
else
|
||||||
|
output = bufev->output;
|
||||||
|
|
||||||
|
if (evbuffer_add_buffer(output, buf) == -1)
|
||||||
return (-1);
|
return (-1);
|
||||||
|
|
||||||
bufferevent_write_closure(bufev, len > 0);
|
|
||||||
|
|
||||||
return (0);
|
return (bufferevent_write_closure(bufev, len > 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
@ -462,3 +574,156 @@ bufferevent_base_set(struct event_base *base, struct bufferevent *bufev)
|
|||||||
res = event_base_set(base, &bufev->ev_write);
|
res = event_base_set(base, &bufev->ev_write);
|
||||||
return (res);
|
return (res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filtering stuff
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct bufferevent_filter *
|
||||||
|
bufferevent_filter_new(
|
||||||
|
void (*init_context)(void *ctx),
|
||||||
|
void (*free_context)(void *ctx),
|
||||||
|
enum bufferevent_filter_result (*process)(
|
||||||
|
struct evbuffer *src, struct evbuffer *dst,
|
||||||
|
enum bufferevent_filter_state flags, void *ctx), void *ctx)
|
||||||
|
{
|
||||||
|
struct bufferevent_filter *filter;
|
||||||
|
|
||||||
|
if ((filter = mm_malloc(sizeof(struct bufferevent_filter))) == NULL)
|
||||||
|
return (NULL);
|
||||||
|
|
||||||
|
if ((filter->buffer = evbuffer_new()) == NULL) {
|
||||||
|
mm_free(filter);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
filter->init_context = init_context;
|
||||||
|
filter->free_context = free_context;
|
||||||
|
filter->process = process;
|
||||||
|
filter->ctx = ctx;
|
||||||
|
|
||||||
|
return (filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
bufferevent_filter_free(struct bufferevent_filter *filter)
|
||||||
|
{
|
||||||
|
evbuffer_free(filter->buffer);
|
||||||
|
mm_free(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
bufferevent_filter_insert(struct bufferevent *bufev,
|
||||||
|
enum bufferevent_filter_type filter_type,
|
||||||
|
struct bufferevent_filter *filter)
|
||||||
|
{
|
||||||
|
switch (filter_type) {
|
||||||
|
case BEV_INPUT:
|
||||||
|
TAILQ_INSERT_TAIL(&bufev->input_filters, filter, next);
|
||||||
|
break;
|
||||||
|
case BEV_OUTPUT:
|
||||||
|
TAILQ_INSERT_HEAD(&bufev->output_filters, filter, next);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
event_errx(1, "illegal filter type %d", filter_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter->init_context)
|
||||||
|
filter->init_context(filter->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
bufferevent_filter_remove(struct bufferevent *bufev,
|
||||||
|
enum bufferevent_filter_type filter_type,
|
||||||
|
struct bufferevent_filter *filter)
|
||||||
|
{
|
||||||
|
switch (filter_type) {
|
||||||
|
case BEV_INPUT:
|
||||||
|
TAILQ_REMOVE(&bufev->input_filters, filter, next);
|
||||||
|
break;
|
||||||
|
case BEV_OUTPUT:
|
||||||
|
TAILQ_REMOVE(&bufev->output_filters, filter, next);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
event_errx(1, "illegal filter type %d", filter_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
evbuffer_drain(filter->buffer, -1);
|
||||||
|
|
||||||
|
if (filter->free_context)
|
||||||
|
filter->free_context(filter->ctx);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
bufferevent_process_filters(
|
||||||
|
struct bufferevent_filter *filter, struct evbuffer *final,
|
||||||
|
enum bufferevent_filter_state state)
|
||||||
|
{
|
||||||
|
struct evbuffer *src, *dst;
|
||||||
|
struct bufferevent_filter *next;
|
||||||
|
int len = EVBUFFER_LENGTH(final);
|
||||||
|
|
||||||
|
for (; filter != NULL; filter = next) {
|
||||||
|
int res;
|
||||||
|
|
||||||
|
next = TAILQ_NEXT(filter, next);
|
||||||
|
src = filter->buffer;
|
||||||
|
dst = next != NULL ? next->buffer : final;
|
||||||
|
|
||||||
|
res = (*filter->process)(src, dst, state, filter->ctx);
|
||||||
|
|
||||||
|
/* an error causes complete termination of the bufferevent */
|
||||||
|
if (res == BEV_ERROR)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
/* a read filter indicated that it cannot produce
|
||||||
|
* further data, we do not need to invoke any
|
||||||
|
* subsequent filters. Unless, a flush or something
|
||||||
|
* similar was specifically requested.
|
||||||
|
*/
|
||||||
|
if (res == BEV_NEED_MORE && state == BEV_NORMAL)
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we made user visible progress if the buffer size changed */
|
||||||
|
return (EVBUFFER_LENGTH(final) != len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
bufferevent_trigger_filter(struct bufferevent *bufev,
|
||||||
|
struct bufferevent_filter *filter, int iotype,
|
||||||
|
enum bufferevent_filter_state state)
|
||||||
|
{
|
||||||
|
struct evbuffer *dst = iotype == BEV_INPUT ?
|
||||||
|
bufev->input : bufev->output;
|
||||||
|
int progress;
|
||||||
|
|
||||||
|
/* trigger all filters if filter is not specified */
|
||||||
|
if (filter == NULL) {
|
||||||
|
struct bufferevent_filterq *head = BEV_INPUT ?
|
||||||
|
&bufev->input_filters : &bufev->output_filters;
|
||||||
|
filter = TAILQ_FIRST(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress = bufferevent_process_filters(filter, dst, state);
|
||||||
|
if (progress == -1) {
|
||||||
|
(*bufev->errorcb)(bufev, EVBUFFER_ERROR, bufev->cbarg);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (iotype) {
|
||||||
|
case BEV_INPUT:
|
||||||
|
bufferevent_read_closure(bufev, progress);
|
||||||
|
break;
|
||||||
|
case BEV_OUTPUT:
|
||||||
|
if (progress && (bufev->enabled & EV_WRITE))
|
||||||
|
bufferevent_add(
|
||||||
|
&bufev->ev_write, bufev->timeout_write);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
event_errx(1, "Illegal bufferevent iotype: %d", iotype);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
@ -228,6 +228,9 @@ struct evbuffer *bufferevent_input(struct bufferevent *bufev);
|
|||||||
/**
|
/**
|
||||||
Returns the outut buffer.
|
Returns the outut buffer.
|
||||||
|
|
||||||
|
When filters are being used, the filters need to be manually
|
||||||
|
triggered if the output buffer was manipulated.
|
||||||
|
|
||||||
@param bufev the buffervent from which to get the evbuffer
|
@param bufev the buffervent from which to get the evbuffer
|
||||||
@return the evbuffer object for the output buffer
|
@return the evbuffer object for the output buffer
|
||||||
*/
|
*/
|
||||||
@ -244,7 +247,6 @@ struct evbuffer *bufferevent_output(struct bufferevent *bufev);
|
|||||||
*/
|
*/
|
||||||
int bufferevent_enable(struct bufferevent *bufev, short event);
|
int bufferevent_enable(struct bufferevent *bufev, short event);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Disable a bufferevent.
|
Disable a bufferevent.
|
||||||
|
|
||||||
@ -288,6 +290,147 @@ void bufferevent_setwatermark(struct bufferevent *bufev, short events,
|
|||||||
#define EVBUFFER_INPUT(x) bufferevent_input(x)
|
#define EVBUFFER_INPUT(x) bufferevent_input(x)
|
||||||
#define EVBUFFER_OUTPUT(x) bufferevent_output(x)
|
#define EVBUFFER_OUTPUT(x) bufferevent_output(x)
|
||||||
|
|
||||||
|
/**
|
||||||
|
Support for filtering input and output of bufferevents.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
Flags that can be passed into filters to let them know how to
|
||||||
|
deal with the incoming data.
|
||||||
|
*/
|
||||||
|
enum bufferevent_filter_state {
|
||||||
|
/** usually set when processing data */
|
||||||
|
BEV_NORMAL = 0,
|
||||||
|
|
||||||
|
/** encountered EOF on read or done sending data */
|
||||||
|
BEV_FLUSH = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Values that filters can return.
|
||||||
|
*/
|
||||||
|
enum bufferevent_filter_result {
|
||||||
|
/** everything is okay */
|
||||||
|
BEV_OK = 0,
|
||||||
|
|
||||||
|
/** the filter needs to read more data before output */
|
||||||
|
BEV_NEED_MORE = 1,
|
||||||
|
|
||||||
|
/** the filter enountered a critical error, no further data
|
||||||
|
can be processed. */
|
||||||
|
BEV_ERROR = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bufferevent_filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Creates a new filtering object for a bufferevent.
|
||||||
|
|
||||||
|
Filters can be used to implement compression, authentication, rate limiting,
|
||||||
|
etc. for bufferevents. Filters can be associated with the input or output
|
||||||
|
path or both. Filters need to be inserted with bufferevent_filter_insert()
|
||||||
|
on either the input or output path.
|
||||||
|
|
||||||
|
For example, when implementing compression, both an input and an
|
||||||
|
output filters are required. The output filter compress all output
|
||||||
|
as it passes along whereas the input filter decompresses all input as
|
||||||
|
it is being read from the network.
|
||||||
|
|
||||||
|
Some filters may require specificaly behavior such as flushing their buffers
|
||||||
|
on EOF. To allom them to do that, a bufferevent will invoke the filter
|
||||||
|
with BEV_FLUSH to let it know that EOF has been reached.
|
||||||
|
|
||||||
|
When a filter needs more data before it can output any data, it may return
|
||||||
|
BEV_NEED_MORE in which case the filter chain is being interrupted until
|
||||||
|
more data arrives. A filter can indicate a fatal error by returning
|
||||||
|
BEV_ERROR. Otherwise, it should return BEV_OK.
|
||||||
|
|
||||||
|
@param init_context an optional function that initializes the ctx parameter.
|
||||||
|
@param free_context an optional function to free memory associated with the
|
||||||
|
ctx parameter.
|
||||||
|
@param process the filtering function that should be invokved either during
|
||||||
|
input or output depending on where the filter should be attached.
|
||||||
|
@param ctx additional context that can be passed to the process function
|
||||||
|
@return a bufferevent_filter object that can subsequently be installed
|
||||||
|
*/
|
||||||
|
struct bufferevent_filter *bufferevent_filter_new(
|
||||||
|
void (*init_context)(void *),
|
||||||
|
void (*free_context)(void *),
|
||||||
|
enum bufferevent_filter_result (*process)(
|
||||||
|
struct evbuffer *src, struct evbuffer *dst,
|
||||||
|
enum bufferevent_filter_state state, void *ctx), void *ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Frees the filter object.
|
||||||
|
|
||||||
|
It must have been removed from the bufferevent before it can be freed.
|
||||||
|
|
||||||
|
@param filter the filter to be freed
|
||||||
|
@see bufferevent_filter_remove()
|
||||||
|
*/
|
||||||
|
void bufferevent_filter_free(struct bufferevent_filter *filter);
|
||||||
|
|
||||||
|
/** Filter types for inserting or removing filters */
|
||||||
|
enum bufferevent_filter_type {
|
||||||
|
/** filter is being used for input */
|
||||||
|
BEV_INPUT = 0,
|
||||||
|
|
||||||
|
/** filter is being used for output */
|
||||||
|
BEV_OUTPUT = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Inserts a filter into the processing of data for bufferevent.
|
||||||
|
|
||||||
|
A filter can be inserted only once. It can not be used again for
|
||||||
|
another insert unless it have been removed via
|
||||||
|
bufferevent_filter_remove() first.
|
||||||
|
|
||||||
|
Input filters are inserted at the end, output filters at the
|
||||||
|
beginning of the queue.
|
||||||
|
|
||||||
|
@param bufev the bufferevent object into which to install the filter
|
||||||
|
@param filter_type either BEV_INPUT or BEV_OUTPUT
|
||||||
|
@param filter the filter object
|
||||||
|
@see bufferevent_filter_remove()
|
||||||
|
*/
|
||||||
|
void bufferevent_filter_insert(struct bufferevent *bufev,
|
||||||
|
enum bufferevent_filter_type filter_type,
|
||||||
|
struct bufferevent_filter *filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes a filter from the bufferevent.
|
||||||
|
|
||||||
|
A filter should be flushed via buffervent_trigger_filter before removing
|
||||||
|
it from a bufferevent. Any remaining intermediate buffer data is going
|
||||||
|
to be lost.
|
||||||
|
|
||||||
|
@param bufev the bufferevent object from which to remove the filter
|
||||||
|
@param filter_type either BEV_INPUT or BEV_OUTPUT
|
||||||
|
@param filter the filter object or NULL to trigger all filters
|
||||||
|
@see bufferevent_trigger_filter()
|
||||||
|
*/
|
||||||
|
void bufferevent_filter_remove(struct bufferevent *bufev,
|
||||||
|
enum bufferevent_filter_type filter_type,
|
||||||
|
struct bufferevent_filter *filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Triggers the filter chain the specified filter to produce more
|
||||||
|
data is possible. This is primarily for time-based filters such
|
||||||
|
as rate-limiting to produce more data as time passes.
|
||||||
|
|
||||||
|
@param bufev the bufferevent object to which the filter belongs
|
||||||
|
@param filter the bufferevent filter at which to start
|
||||||
|
@param iotype either BEV_INPUT or BEV_OUTPUT depending on where the filter
|
||||||
|
was installed
|
||||||
|
@param state either BEV_NORMAL or BEV_FLUSH
|
||||||
|
@return -1 on failure, 0 if no data was produces, 1 if data was produced
|
||||||
|
*/
|
||||||
|
|
||||||
|
int
|
||||||
|
bufferevent_trigger_filter(struct bufferevent *bufev,
|
||||||
|
struct bufferevent_filter *filter, int iotype,
|
||||||
|
enum bufferevent_filter_state state);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,25 @@ struct event_watermark {
|
|||||||
size_t high;
|
size_t high;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct bufferevent_filter;
|
||||||
|
|
||||||
|
/* Fix so that ppl dont have to run with <sys/queue.h> */
|
||||||
|
#ifndef TAILQ_HEAD
|
||||||
|
#define _EVENT_DEFINED_TQHEAD
|
||||||
|
#define TAILQ_HEAD(name, type) \
|
||||||
|
struct name { \
|
||||||
|
struct type *tqh_first; /* first element */ \
|
||||||
|
struct type **tqh_last; /* addr of last next element */ \
|
||||||
|
}
|
||||||
|
#endif /* !TAILQ_HEAD */
|
||||||
|
|
||||||
|
TAILQ_HEAD(bufferevent_filterq, bufferevent_filter);
|
||||||
|
|
||||||
|
#ifdef _EVENT_DEFINED_TQHEAD
|
||||||
|
#undef TAILQ_HEAD
|
||||||
|
#undef _EVENT_DEFINED_TQHEAD
|
||||||
|
#endif /* _EVENT_DEFINED_TQHEAD */
|
||||||
|
|
||||||
struct bufferevent {
|
struct bufferevent {
|
||||||
struct event_base *ev_base;
|
struct event_base *ev_base;
|
||||||
|
|
||||||
@ -85,8 +104,11 @@ struct bufferevent {
|
|||||||
int timeout_write; /* in seconds */
|
int timeout_write; /* in seconds */
|
||||||
|
|
||||||
short enabled; /* events that are currently enabled */
|
short enabled; /* events that are currently enabled */
|
||||||
};
|
|
||||||
|
|
||||||
|
/** the list of input and output filters */
|
||||||
|
struct bufferevent_filterq input_filters;
|
||||||
|
struct bufferevent_filterq output_filters;
|
||||||
|
};
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -1443,6 +1443,101 @@ test_bufferevent_watermarks(void)
|
|||||||
cleanup_test();
|
cleanup_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test bufferevent filters
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* strip an 'x' from each byte */
|
||||||
|
|
||||||
|
static enum bufferevent_filter_result
|
||||||
|
bufferevent_input_filter(struct evbuffer *src, struct evbuffer *dst,
|
||||||
|
enum bufferevent_filter_state state, void *ctx)
|
||||||
|
{
|
||||||
|
const unsigned char *buffer;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (state == BEV_FREE_DATA)
|
||||||
|
return (BEV_OK);
|
||||||
|
|
||||||
|
buffer = evbuffer_pullup(src, EVBUFFER_LENGTH(src));
|
||||||
|
for (i = 0; i < EVBUFFER_LENGTH(src); i += 2) {
|
||||||
|
assert(buffer[i] == 'x');
|
||||||
|
evbuffer_add(dst, buffer + i + 1, 1);
|
||||||
|
|
||||||
|
if (i + 2 > EVBUFFER_LENGTH(src))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
evbuffer_drain(src, i);
|
||||||
|
return (BEV_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add an 'x' before each byte */
|
||||||
|
|
||||||
|
static enum bufferevent_filter_result
|
||||||
|
bufferevent_output_filter(struct evbuffer *src, struct evbuffer *dst,
|
||||||
|
enum bufferevent_filter_state state, void *ctx)
|
||||||
|
{
|
||||||
|
const unsigned char *buffer;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (state == BEV_FREE_DATA)
|
||||||
|
return (BEV_OK);
|
||||||
|
|
||||||
|
buffer = evbuffer_pullup(src, EVBUFFER_LENGTH(src));
|
||||||
|
for (i = 0; i < EVBUFFER_LENGTH(src); ++i) {
|
||||||
|
evbuffer_add(dst, "x", 1);
|
||||||
|
evbuffer_add(dst, buffer + i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
evbuffer_drain(src, EVBUFFER_LENGTH(src));
|
||||||
|
return (BEV_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_bufferevent_filters(void)
|
||||||
|
{
|
||||||
|
struct bufferevent *bev1, *bev2;
|
||||||
|
struct bufferevent_filter *finput, *foutput;
|
||||||
|
char buffer[8333];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
setup_test("Bufferevent Filters: ");
|
||||||
|
|
||||||
|
bev1 = bufferevent_new(pair[0], NULL, writecb, errorcb, NULL);
|
||||||
|
bev2 = bufferevent_new(pair[1], readcb, NULL, errorcb, NULL);
|
||||||
|
|
||||||
|
bufferevent_disable(bev1, EV_READ);
|
||||||
|
bufferevent_enable(bev2, EV_READ);
|
||||||
|
|
||||||
|
for (i = 0; i < sizeof(buffer); i++)
|
||||||
|
buffer[i] = i;
|
||||||
|
|
||||||
|
/* insert some filters */
|
||||||
|
finput = bufferevent_filter_new(
|
||||||
|
NULL, NULL,bufferevent_input_filter, NULL);
|
||||||
|
foutput = bufferevent_filter_new(
|
||||||
|
NULL, NULL, bufferevent_output_filter, NULL);
|
||||||
|
|
||||||
|
bufferevent_filter_insert(bev1, BEV_OUTPUT, foutput);
|
||||||
|
bufferevent_filter_insert(bev2, BEV_INPUT, finput);
|
||||||
|
|
||||||
|
bufferevent_write(bev1, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
event_dispatch();
|
||||||
|
|
||||||
|
bufferevent_filter_remove(bev1, BEV_OUTPUT, foutput);
|
||||||
|
bufferevent_filter_free(foutput);
|
||||||
|
|
||||||
|
bufferevent_free(bev1);
|
||||||
|
bufferevent_free(bev2);
|
||||||
|
|
||||||
|
if (test_ok != 2)
|
||||||
|
test_ok = 0;
|
||||||
|
|
||||||
|
cleanup_test();
|
||||||
|
}
|
||||||
|
|
||||||
struct test_pri_event {
|
struct test_pri_event {
|
||||||
struct event ev;
|
struct event ev;
|
||||||
int count;
|
int count;
|
||||||
@ -1944,6 +2039,7 @@ main (int argc, char **argv)
|
|||||||
|
|
||||||
test_bufferevent();
|
test_bufferevent();
|
||||||
test_bufferevent_watermarks();
|
test_bufferevent_watermarks();
|
||||||
|
test_bufferevent_filters();
|
||||||
|
|
||||||
test_free_active_base();
|
test_free_active_base();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user