Skip to content

Commit

Permalink
http: Merge branch 'http-max_connections-pr-592'
Browse files Browse the repository at this point in the history
@jcoffland:

  "When the max connection limit is enabled and the limit is reached, the
   server will respond immediately with 503 Service Unavailable. This can
   be used to prevent servers from running out of file descriptors. This is
   better than request limiting because clients may make more than one
   request over a single connection. Blocking a request does not
   necessarily close the connection and free up a socket."

* http-max_connections-pr-592:
  test: cover evhttp max connections
  Added evhttp max simultaneous connection limiting
  • Loading branch information
azat committed May 25, 2020
2 parents f10aaea + 95c1c20 commit 7426a56
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 1 deletion.
2 changes: 2 additions & 0 deletions http-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ struct evhttp {

/* All live connections on this host. */
struct evconq connections;
int connection_max;
int connection_cnt;

TAILQ_HEAD(vhostsq, evhttp) virtualhosts;

Expand Down
40 changes: 39 additions & 1 deletion http.c
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,7 @@ evhttp_connection_free(struct evhttp_connection *evcon)
if (evcon->http_server != NULL) {
struct evhttp *http = evcon->http_server;
TAILQ_REMOVE(&http->connections, evcon, next);
http->connection_cnt--;
}

if (event_initialized(&evcon->retry_ev)) {
Expand Down Expand Up @@ -4115,6 +4116,21 @@ evhttp_set_max_body_size(struct evhttp* http, ev_ssize_t max_body_size)
http->default_max_body_size = max_body_size;
}

void
evhttp_set_max_connections(struct evhttp* http, int max_connections)
{
if (max_connections < 0)
http->connection_max = 0;
else
http->connection_max = max_connections;
}

int
evhttp_get_connection_count(struct evhttp* http)
{
return http->connection_cnt;
}

void
evhttp_set_default_content_type(struct evhttp *http,
const char *content_type) {
Expand Down Expand Up @@ -4586,8 +4602,30 @@ evhttp_get_request(struct evhttp *http, evutil_socket_t fd,
evcon->http_server = http;
evcon->ext_method_cmp = http->ext_method_cmp;
TAILQ_INSERT_TAIL(&http->connections, evcon, next);
http->connection_cnt++;

/* send "service unavailable" if we've reached the connection limit */
if (http->connection_max && http->connection_max < http->connection_cnt) {
struct evhttp_request *req;

if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL) {
evhttp_connection_free(evcon);
return;
}

req->evcon = evcon; /* the request owns the connection */
req->flags |= EVHTTP_REQ_OWN_CONNECTION;
req->kind = EVHTTP_REQUEST;
/* note, req->remote_host not needed since we don't read */

TAILQ_INSERT_TAIL(&evcon->requests, req, next);

/* send error to client */
evcon->state = EVCON_WRITING;
bufferevent_enable(evcon->bufev, EV_READ); /* enable close events */
evhttp_send_error(req, HTTP_SERVUNAVAIL, NULL);

if (evhttp_associate_new_request_with_connection(evcon) == -1)
} else if (evhttp_associate_new_request_with_connection(evcon) == -1)
evhttp_connection_free(evcon);
}

Expand Down
18 changes: 18 additions & 0 deletions include/event2/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,24 @@ void evhttp_set_max_headers_size(struct evhttp* http, ev_ssize_t max_headers_siz
EVENT2_EXPORT_SYMBOL
void evhttp_set_max_body_size(struct evhttp* http, ev_ssize_t max_body_size);

/**
* Set the maximum number of simultaneous connections for this server.
* A value of zero or less disables the limit.
*
* @param http the http server on which to set the max connection limit
* @param max_connections the maximum number of simultaneous connections or 0
*/
EVENT2_EXPORT_SYMBOL
void evhttp_set_max_connections(struct evhttp* http, int max_connections);

/**
* Get the current number of connections.
*
* @return The current number of connections for this server.
*/
EVENT2_EXPORT_SYMBOL
int evhttp_get_connection_count(struct evhttp* http);

/**
Set the value to use for the Content-Type header when none was provided. If
the content type string is NULL, the Content-Type header will not be
Expand Down
71 changes: 71 additions & 0 deletions test/regress_http.c
Original file line number Diff line number Diff line change
Expand Up @@ -5319,6 +5319,76 @@ http_timeout_read_server_test(void *arg)



#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
static void
http_max_connections_test(void *arg)
{
struct basic_test_data *data = arg;
ev_uint16_t port = 0;
struct evhttp *http = http_setup(&port, data->base, 0);
struct evhttp_connection *evcons[2];
struct http_newreqcb_test_state newreqcb_test_state;
unsigned n;

exit_base = data->base;
test_ok = 0;

memset(&newreqcb_test_state, 0, sizeof(newreqcb_test_state));
memset(evcons, 0, sizeof(evcons));

evhttp_set_max_connections(http, ARRAY_SIZE(evcons)-1);

for (n = 0; n < sizeof(evcons)/sizeof(evcons[0]); ++n) {
struct evhttp_connection* evcon = NULL;
struct evhttp_request *req = NULL;
evcons[n] = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port);
evcon = evcons[n];
evhttp_connection_set_retries(evcon, 0);

tt_assert(evcon);

req = evhttp_request_new(http_request_done_newreqcb, &newreqcb_test_state);
evhttp_add_header(evhttp_request_get_output_headers(req), "Connection", "close");
evhttp_request_set_error_cb(req, http_request_error_newreqcb);

/* We give ownership of the request to the connection */
if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) {
tt_abort_msg("Couldn't make request");
}

++newreqcb_test_state.connections_started;
http_newreqcb_test_state_check(&newreqcb_test_state);
}

/* XXX: http_newreqcb_test_state_check will not stop the base, since:
* - connections_done == 2
* - connections_good == 1
*
* hence timeout
*/
{
struct timeval tv = { 0, 300e3 };
event_base_loopexit(data->base, &tv);
}

event_base_dispatch(data->base);

http_newreqcb_test_state_check(&newreqcb_test_state);
tt_int_op(newreqcb_test_state.connections_error, ==, 0);
tt_int_op(newreqcb_test_state.connections_done, ==, 2);
tt_int_op(newreqcb_test_state.connections_good, ==, 1);

end:
evhttp_free(http);

for (n = 0; n < ARRAY_SIZE(evcons); ++n) {
if (evcons[n]) {
evhttp_connection_free(evcons[n]);
}
}
}


#define HTTP_LEGACY(name) \
{ #name, run_legacy_test_fn, TT_ISOLATED|TT_LEGACY, &legacy_setup, \
http_##name##_test }
Expand Down Expand Up @@ -5456,6 +5526,7 @@ struct testcase_t http_testcases[] = {
HTTP(request_extra_body),

HTTP(newreqcb),
HTTP(max_connections),

HTTP(timeout_read_client),
HTTP(timeout_read_server),
Expand Down

0 comments on commit 7426a56

Please sign in to comment.