]> granicus.if.org Git - libevent/commitdiff
http: Merge branch 'http-max_connections-pr-592'
authorAzat Khuzhin <azat@libevent.org>
Mon, 25 May 2020 08:25:18 +0000 (11:25 +0300)
committerAzat Khuzhin <azat@libevent.org>
Mon, 25 May 2020 08:25:18 +0000 (11:25 +0300)
@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

1  2 
http-internal.h
http.c
include/event2/http.h
test/regress_http.c

diff --cc http-internal.h
Simple merge
diff --cc http.c
index 142c2e73ee54e76cf23546e9d58e8253eced0b07,65530f0c9286ee27bb167cc6c2d0ee41436a8b6b..e8f78826bb6d62ad8ad129cebaf3f7fde122991a
--- 1/http.c
--- 2/http.c
+++ b/http.c
@@@ -4584,10 -4306,31 +4600,32 @@@ evhttp_get_request(struct evhttp *http
         * we need to know which http server it belongs to.
         */
        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);
  }
  
Simple merge
index 7748e0fe936f9d95426b0ac013e0a3cee158a3c4,dd9910e65c342e9cb22ed92cce54adeb40e89971..7f43ebf5ee6b15e2f98ad78940853612c02a4922
@@@ -5050,275 -4726,77 +5050,345 @@@ http_newreqcb_test(void *arg
  
  }
  
 +static void
 +http_timeout_read_client_test(void *arg)
 +{
 +      struct basic_test_data *data = arg;
 +      ev_uint16_t port = 0;
 +      struct evhttp_connection *evcon = NULL;
 +      struct evhttp_request *req = NULL;
 +      struct timeval tv;
 +      struct evhttp *http = http_setup(&port, data->base, 0);
 +
 +      test_ok = 0;
 +      exit_base = data->base;
 +
 +      evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port);
 +      tt_assert(evcon);
 +
 +      tv.tv_sec = 0;
 +      tv.tv_usec = 100000;
 +      evhttp_connection_set_connect_timeout_tv(evcon, &tv);
 +      evhttp_connection_set_write_timeout_tv(evcon, &tv);
 +      tv.tv_usec = 500000;
 +      evhttp_connection_set_read_timeout_tv(evcon, &tv);
 +
 +      req = evhttp_request_new(http_request_done, (void*)"");
 +      tt_assert(req);
 +      evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
 +      tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/delay"), ==, 0);
 +      event_base_dispatch(data->base);
 +      tt_int_op(test_ok, ==, 1);
 +
 + end:
 +      if (evcon)
 +              evhttp_connection_free(evcon);
 +      if (http)
 +              evhttp_free(http);
 +}
 +
 +/*
 + * Error callback tests
 + */
 +
 +#define ERRCBFLAG_GENCB  (0x01)
 +#define ERRCBFLAG_ERRCB  (0x02)
 +#define ERRCBFLAG_BOTHCB (ERRCBFLAG_GENCB | ERRCBFLAG_ERRCB)
 +
 +struct error_callback_test {
 +      unsigned char flags;
 +      enum evhttp_cmd_type type;
 +      int response_code;
 +      int length;
 +} error_callback_tests[] = {
 +      {0                , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 152} , /* 0 */
 +      {0                , EVHTTP_REQ_POST , HTTP_NOTIMPLEMENTED , 101} , /* 1 */
 +      {ERRCBFLAG_GENCB  , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 89}  , /* 2 */
 +      {ERRCBFLAG_GENCB  , EVHTTP_REQ_POST , HTTP_NOTIMPLEMENTED , 101} , /* 3 */
 +      {ERRCBFLAG_ERRCB  , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 3}   , /* 4 */
 +      {ERRCBFLAG_ERRCB  , EVHTTP_REQ_POST , HTTP_NOTIMPLEMENTED , 101} , /* 5 */
 +      {ERRCBFLAG_BOTHCB , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 3}   , /* 6 */
 +      {ERRCBFLAG_BOTHCB , EVHTTP_REQ_POST , HTTP_NOTIMPLEMENTED , 3}   , /* 7 */
 +      {ERRCBFLAG_ERRCB  , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 0}   , /* 8 */
 +      {ERRCBFLAG_ERRCB  , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 152} , /* 9 */
 +};
 +
 +struct error_callback_state
 +{
 +      struct basic_test_data *data;
 +      unsigned test_index;
 +      struct error_callback_test *test;
 +      int test_failed;
 +};
 +
 +static void
 +http_error_callback_gencb(struct evhttp_request *req, void *arg)
 +{
 +      struct error_callback_state *state_info = arg;
 +
 +      switch (state_info->test_index) {
 +      case 2:
 +      case 6:
 +              evhttp_send_error(req, HTTP_NOTFOUND, NULL);
 +              break;
 +      default:
 +              evhttp_send_error(req, HTTP_INTERNAL, NULL);
 +              break;
 +      }
 +}
 +
 +static int
 +http_error_callback_errorcb(struct evhttp_request *req, struct evbuffer *buf,
 +    int error, const char *reason, void *arg)
 +{
 +      struct error_callback_state *state_info = arg;
 +      int return_code = -1;
 +
 +      switch (state_info->test_index) {
 +      case 4:
 +      case 6:
 +      case 7:
 +              evbuffer_add_printf(buf, "%d", error);
 +              return_code = 0;
 +              break;
 +      case 8: /* Add nothing to buffer and then return 0 to trigger default */
 +              return_code = 0;
 +              break;
 +      case 9: /* Add text to buffer but then return -1 to trigger default */
 +              evbuffer_add_printf(buf, "%d", error);
 +              break;
 +      default:
 +              /* Do nothing */
 +              break;
 +      }
 +
 +      return return_code;
 +}
 +
 +static void
 +http_error_callback_test_done(struct evhttp_request *req, void *arg)
 +{
 +      struct evkeyvalq *headers;
 +      const char *header_text;
 +      struct error_callback_state *state_info = arg;
 +      struct error_callback_test *test_info;
 +
 +      test_info = state_info->test;
 +
 +      tt_assert(req);
 +      tt_int_op(evhttp_request_get_command(req), ==,
 +          test_info->type);
 +      tt_int_op(evhttp_request_get_response_code(req), ==,
 +          test_info->response_code);
 +
 +      headers = evhttp_request_get_input_headers(req);
 +      tt_assert(headers);
 +      header_text  = evhttp_find_header(headers, "Content-Type");
 +      tt_assert_msg(header_text, "Missing Content-Type");
 +      tt_str_op(header_text, ==, "text/html");
 +      tt_int_op(evbuffer_get_length(evhttp_request_get_input_buffer(req)),
 +          ==, test_info->length);
 +
 +      event_base_loopexit(state_info->data->base, NULL);
 +      return;
 +
 +end:
 +      state_info->test_failed = 1;
 +      event_base_loopexit(state_info->data->base, NULL);
 +}
 +
 +static void
 +http_error_callback_test(void *arg)
 +{
 +      struct basic_test_data *data = arg;
 +      struct evhttp *http = NULL;
 +      ev_uint16_t port = 0;
 +      struct evhttp_connection *evcon = NULL;
 +      struct evhttp_request *req = NULL;
 +      struct error_callback_state state_info;
 +
 +      test_ok = 0;
 +
 +      http = http_setup(&port, data->base, 0);
 +      evhttp_set_allowed_methods(http,
 +          EVHTTP_REQ_GET |
 +          EVHTTP_REQ_HEAD |
 +          EVHTTP_REQ_PUT |
 +          EVHTTP_REQ_DELETE);
 +
 +      evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port);
 +      tt_assert(evcon);
 +
 +      /* also bind to local host */
 +      evhttp_connection_set_local_address(evcon, "127.0.0.1");
 +
 +      /* Initialise the state info */
 +      state_info.data         = data;
 +      state_info.test         = error_callback_tests;
 +      state_info.test_failed  = 0;
 +
 +      /* Perform all the tests */
 +      for (state_info.test_index = 0;
 +           (state_info.test_index < ARRAY_SIZE(error_callback_tests)) &&
 +           (!state_info.test_failed);
 +           state_info.test_index++)
 +      {
 +              evhttp_set_gencb(http,
 +                  ((state_info.test->flags & ERRCBFLAG_GENCB) != 0 ?
 +                  http_error_callback_gencb : NULL),
 +                  &state_info);
 +              evhttp_set_errorcb(http,
 +                  ((state_info.test->flags & ERRCBFLAG_ERRCB) != 0 ?
 +                  http_error_callback_errorcb : NULL),
 +                  &state_info);
 +
 +              req = evhttp_request_new(http_error_callback_test_done,
 +                  &state_info);
 +              tt_assert(req);
 +              if (evhttp_make_request(evcon, req, state_info.test->type,
 +                      "/missing") == -1) {
 +                      tt_abort_msg("Couldn't make request");
 +                      exit(1);
 +              }
 +              event_base_dispatch(data->base);
 +
 +              if (!state_info.test_failed)
 +                      test_ok++;
 +              else
 +                      tt_abort_printf(("Sub-test %d failed",
 +                          state_info.test_index));
 +              state_info.test++;
 +      }
 +
 +end:
 +      if (evcon)
 +              evhttp_connection_free(evcon);
 +      if (http)
 +              evhttp_free(http);
 +}
 +
 +static void http_add_output_buffer(int fd, short events, void *arg)
 +{
 +      evbuffer_add(arg, POST_DATA, strlen(POST_DATA));
 +}
 +static void
 +http_timeout_read_server_test(void *arg)
 +{
 +      struct basic_test_data *data = arg;
 +      struct timeval tv;
 +      struct bufferevent *bev;
 +      struct evbuffer *out;
 +      int fd = -1;
 +      ev_uint16_t port = 0;
 +      struct evhttp *http = http_setup(&port, data->base, 0);
 +
 +      test_ok = 0;
 +
 +      tv.tv_sec = 0;
 +      tv.tv_usec = 100000;
 +      evhttp_set_write_timeout_tv(http, &tv);
 +      tv.tv_usec = 500000;
 +      evhttp_set_read_timeout_tv(http, &tv);
 +
 +      fd = http_connect("127.0.0.1", port);
 +      bev = create_bev(data->base, fd, 0);
 +      bufferevent_setcb(bev, http_readcb, http_writecb, http_errorcb, data->base);
 +      out = bufferevent_get_output(bev);
 +
 +      evbuffer_add_printf(out,
 +          "POST /postit HTTP/1.1\r\n"
 +          "Host: somehost\r\n"
 +          "Content-Length: " EV_SIZE_FMT "\r\n"
 +          "\r\n", strlen(POST_DATA));
 +
 +      tv.tv_usec = 200000;
 +      event_base_once(data->base, -1, EV_TIMEOUT, http_add_output_buffer, out, &tv);
 +
 +      event_base_dispatch(data->base);
 +      tt_int_op(test_ok, ==, 3);
 +
 + end:
 +      if (bev)
 +              bufferevent_free(bev);
 +      if (fd != -1)
 +              evutil_closesocket(fd);
 +      if (http)
 +              evhttp_free(http);
 +}
 +
 +
 +
  
+ #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 }
@@@ -5456,10 -4919,8 +5526,11 @@@ struct testcase_t http_testcases[] = 
        HTTP(request_extra_body),
  
        HTTP(newreqcb),
+       HTTP(max_connections),
  
 +      HTTP(timeout_read_client),
 +      HTTP(timeout_read_server),
 +
  #ifdef EVENT__HAVE_OPENSSL
        HTTPS(basic),
        HTTPS(filter_basic),