mirror of
https://github.com/curl/curl.git
synced 2024-12-09 06:30:06 +08:00
url: fix logic in connection reuse to deny reuse on "unclean" connections
- add parameter to `conn_is_alive()` cfilter method that returns if there is input data waiting on the connection - refrain from re-using connnection from the cache that have input pending - adapt http/2 and http/3 alive checks to digest pending input to check the connection state - remove check_cxn method from openssl as that was just doing what the socket filter now does. - add tests for connection reuse with special server configs Closes #10690
This commit is contained in:
parent
6466071e8e
commit
7c5637b8b4
@ -325,20 +325,6 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
|
||||
return socket_close(data, conn, FALSE, sock);
|
||||
}
|
||||
|
||||
bool Curl_socket_is_dead(curl_socket_t sock)
|
||||
{
|
||||
int sval;
|
||||
bool ret_val = TRUE;
|
||||
|
||||
sval = SOCKET_READABLE(sock, 0);
|
||||
if(sval == 0)
|
||||
/* timeout */
|
||||
ret_val = FALSE;
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
|
||||
#ifdef USE_WINSOCK
|
||||
/* When you run a program that uses the Windows Sockets API, you may
|
||||
experience slow performance when you copy data to a TCP server.
|
||||
@ -1449,12 +1435,14 @@ static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf,
|
||||
}
|
||||
|
||||
static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
struct cf_socket_ctx *ctx = cf->ctx;
|
||||
struct pollfd pfd[1];
|
||||
int r;
|
||||
|
||||
*input_pending = FALSE;
|
||||
(void)data;
|
||||
if(!ctx || ctx->sock == CURL_SOCKET_BAD)
|
||||
return FALSE;
|
||||
@ -1479,6 +1467,7 @@ static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf,
|
||||
}
|
||||
|
||||
DEBUGF(LOG_CF(data, cf, "is_alive: valid events, looks alive"));
|
||||
*input_pending = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -70,13 +70,6 @@ CURLcode Curl_socket_open(struct Curl_easy *data,
|
||||
int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
|
||||
curl_socket_t sock);
|
||||
|
||||
/*
|
||||
* This function should return TRUE if the socket is to be assumed to
|
||||
* be dead. Most commonly this happens when the server has closed the
|
||||
* connection due to inactivity.
|
||||
*/
|
||||
bool Curl_socket_is_dead(curl_socket_t sock);
|
||||
|
||||
/**
|
||||
* Determine the curl code for a socket connect() == -1 with errno.
|
||||
*/
|
||||
|
@ -124,10 +124,11 @@ ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
}
|
||||
|
||||
bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
return cf->next?
|
||||
cf->next->cft->is_alive(cf->next, data) :
|
||||
cf->next->cft->is_alive(cf->next, data, input_pending) :
|
||||
FALSE; /* pessimistic in absence of data */
|
||||
}
|
||||
|
||||
@ -631,10 +632,12 @@ void Curl_conn_report_connect_stats(struct Curl_easy *data,
|
||||
}
|
||||
}
|
||||
|
||||
bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn)
|
||||
bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn,
|
||||
bool *input_pending)
|
||||
{
|
||||
struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET];
|
||||
return cf && !cf->conn->bits.close && cf->cft->is_alive(cf, data);
|
||||
return cf && !cf->conn->bits.close &&
|
||||
cf->cft->is_alive(cf, data, input_pending);
|
||||
}
|
||||
|
||||
CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
|
||||
|
@ -85,7 +85,8 @@ typedef ssize_t Curl_cft_recv(struct Curl_cfilter *cf,
|
||||
CURLcode *err); /* error to return */
|
||||
|
||||
typedef bool Curl_cft_conn_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data);
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending);
|
||||
|
||||
typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data);
|
||||
@ -216,7 +217,8 @@ CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
int event, int arg1, void *arg2);
|
||||
bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data);
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending);
|
||||
CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data);
|
||||
CURLcode Curl_cf_def_query(struct Curl_cfilter *cf,
|
||||
@ -443,7 +445,8 @@ void Curl_conn_report_connect_stats(struct Curl_easy *data,
|
||||
/**
|
||||
* Check if FIRSTSOCKET's cfilter chain deems connection alive.
|
||||
*/
|
||||
bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn);
|
||||
bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn,
|
||||
bool *input_pending);
|
||||
|
||||
/**
|
||||
* Try to upkeep the connection filters at sockindex.
|
||||
|
61
lib/http2.c
61
lib/http2.c
@ -366,6 +366,15 @@ static void http2_stream_free(struct HTTP *stream)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns nonzero if current HTTP/2 session should be closed.
|
||||
*/
|
||||
static int should_close_session(struct cf_h2_ctx *ctx)
|
||||
{
|
||||
return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) &&
|
||||
!nghttp2_session_want_write(ctx->h2);
|
||||
}
|
||||
|
||||
/*
|
||||
* The server may send us data at any point (e.g. PING frames). Therefore,
|
||||
* we cannot assume that an HTTP/2 socket is dead just because it is readable.
|
||||
@ -373,35 +382,27 @@ static void http2_stream_free(struct HTTP *stream)
|
||||
* Check the lower filters first and, if successful, peek at the socket
|
||||
* and distinguish between closed and data.
|
||||
*/
|
||||
static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
struct cf_h2_ctx *ctx = cf->ctx;
|
||||
int sval;
|
||||
bool dead = TRUE;
|
||||
bool alive = TRUE;
|
||||
|
||||
if(!cf->next || !cf->next->cft->is_alive(cf->next, data))
|
||||
return TRUE;
|
||||
*input_pending = FALSE;
|
||||
if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
|
||||
return FALSE;
|
||||
|
||||
sval = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0);
|
||||
if(sval == 0) {
|
||||
/* timeout */
|
||||
dead = FALSE;
|
||||
}
|
||||
else if(sval & CURL_CSELECT_ERR) {
|
||||
/* socket is in an error state */
|
||||
dead = TRUE;
|
||||
}
|
||||
else if(sval & CURL_CSELECT_IN) {
|
||||
if(*input_pending) {
|
||||
/* This happens before we've sent off a request and the connection is
|
||||
not in use by any other transfer, there shouldn't be any data here,
|
||||
only "protocol frames" */
|
||||
CURLcode result;
|
||||
ssize_t nread = -1;
|
||||
|
||||
*input_pending = FALSE;
|
||||
Curl_attach_connection(data, cf->conn);
|
||||
nread = Curl_conn_cf_recv(cf->next, data,
|
||||
ctx->inbuf, H2_BUFSIZE, &result);
|
||||
dead = FALSE;
|
||||
if(nread != -1) {
|
||||
DEBUGF(LOG_CF(data, cf, "%d bytes stray data read before trying "
|
||||
"h2 connection", (int)nread));
|
||||
@ -409,15 +410,19 @@ static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
ctx->inbuflen = nread;
|
||||
if(h2_process_pending_input(cf, data, &result) < 0)
|
||||
/* immediate error, considered dead */
|
||||
dead = TRUE;
|
||||
alive = FALSE;
|
||||
else {
|
||||
alive = !should_close_session(ctx);
|
||||
}
|
||||
}
|
||||
else
|
||||
else {
|
||||
/* the read failed so let's say this is dead anyway */
|
||||
dead = TRUE;
|
||||
alive = FALSE;
|
||||
}
|
||||
Curl_detach_connection(data);
|
||||
}
|
||||
|
||||
return dead;
|
||||
return alive;
|
||||
}
|
||||
|
||||
static CURLcode http2_send_ping(struct Curl_cfilter *cf,
|
||||
@ -1451,15 +1456,6 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns nonzero if current HTTP/2 session should be closed.
|
||||
*/
|
||||
static int should_close_session(struct cf_h2_ctx *ctx)
|
||||
{
|
||||
return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) &&
|
||||
!nghttp2_session_want_write(ctx->h2);
|
||||
}
|
||||
|
||||
/*
|
||||
* h2_process_pending_input() processes pending input left in
|
||||
* httpc->inbuf. Then, call h2_session_send() to send pending data.
|
||||
@ -2359,14 +2355,17 @@ static bool cf_h2_data_pending(struct Curl_cfilter *cf,
|
||||
}
|
||||
|
||||
static bool cf_h2_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
struct cf_h2_ctx *ctx = cf->ctx;
|
||||
CURLcode result;
|
||||
struct cf_call_data save;
|
||||
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
result = (ctx && ctx->h2 && !http2_connisdead(cf, data));
|
||||
result = (ctx && ctx->h2 && http2_connisalive(cf, data, input_pending));
|
||||
DEBUGF(LOG_CF(data, cf, "conn alive -> %d, input_pending=%d",
|
||||
result, *input_pending));
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
return result;
|
||||
}
|
||||
|
@ -145,7 +145,8 @@ static unsigned int rtsp_conncheck(struct Curl_easy *data,
|
||||
(void)data;
|
||||
|
||||
if(checks_to_perform & CONNCHECK_ISDEAD) {
|
||||
if(!Curl_conn_is_alive(data, conn))
|
||||
bool input_pending;
|
||||
if(!Curl_conn_is_alive(data, conn, &input_pending))
|
||||
ret_val |= CONNRESULT_DEAD;
|
||||
}
|
||||
|
||||
|
15
lib/url.c
15
lib/url.c
@ -965,7 +965,20 @@ static bool extract_if_dead(struct connectdata *conn,
|
||||
|
||||
}
|
||||
else {
|
||||
dead = !Curl_conn_is_alive(data, conn);
|
||||
bool input_pending;
|
||||
|
||||
dead = !Curl_conn_is_alive(data, conn, &input_pending);
|
||||
if(input_pending) {
|
||||
/* For reuse, we want a "clean" connection state. The includes
|
||||
* that we expect - in general - no waiting input data. Input
|
||||
* waiting might be a TLS Notify Close, for example. We reject
|
||||
* that.
|
||||
* For protocols where data from other other end may arrive at
|
||||
* any time (HTTP/2 PING for example), the protocol handler needs
|
||||
* to install its own `connection_check` callback.
|
||||
*/
|
||||
dead = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if(dead) {
|
||||
|
@ -769,11 +769,13 @@ static CURLcode cf_msh3_query(struct Curl_cfilter *cf,
|
||||
}
|
||||
|
||||
static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
struct cf_msh3_ctx *ctx = cf->ctx;
|
||||
|
||||
(void)data;
|
||||
*input_pending = FALSE;
|
||||
return ctx && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD && ctx->qconn &&
|
||||
ctx->connected;
|
||||
}
|
||||
|
@ -2444,6 +2444,32 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf,
|
||||
CURLE_UNKNOWN_OPTION;
|
||||
}
|
||||
|
||||
static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
bool alive = TRUE;
|
||||
|
||||
*input_pending = FALSE;
|
||||
if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
|
||||
return FALSE;
|
||||
|
||||
if(*input_pending) {
|
||||
/* This happens before we've sent off a request and the connection is
|
||||
not in use by any other transfer, there shouldn't be any data here,
|
||||
only "protocol frames" */
|
||||
*input_pending = FALSE;
|
||||
Curl_attach_connection(data, cf->conn);
|
||||
if(cf_process_ingress(cf, data))
|
||||
alive = FALSE;
|
||||
else {
|
||||
alive = TRUE;
|
||||
}
|
||||
Curl_detach_connection(data);
|
||||
}
|
||||
|
||||
return alive;
|
||||
}
|
||||
|
||||
struct Curl_cftype Curl_cft_http3 = {
|
||||
"HTTP/3",
|
||||
@ -2458,7 +2484,7 @@ struct Curl_cftype Curl_cft_http3 = {
|
||||
cf_ngtcp2_send,
|
||||
cf_ngtcp2_recv,
|
||||
cf_ngtcp2_data_event,
|
||||
Curl_cf_def_conn_is_alive,
|
||||
cf_ngtcp2_conn_is_alive,
|
||||
Curl_cf_def_conn_keep_alive,
|
||||
cf_ngtcp2_query,
|
||||
};
|
||||
|
@ -1359,6 +1359,32 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
|
||||
CURLE_UNKNOWN_OPTION;
|
||||
}
|
||||
|
||||
static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
bool alive = TRUE;
|
||||
|
||||
*input_pending = FALSE;
|
||||
if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
|
||||
return FALSE;
|
||||
|
||||
if(*input_pending) {
|
||||
/* This happens before we've sent off a request and the connection is
|
||||
not in use by any other transfer, there shouldn't be any data here,
|
||||
only "protocol frames" */
|
||||
*input_pending = FALSE;
|
||||
Curl_attach_connection(data, cf->conn);
|
||||
if(cf_process_ingress(cf, data))
|
||||
alive = FALSE;
|
||||
else {
|
||||
alive = TRUE;
|
||||
}
|
||||
Curl_detach_connection(data);
|
||||
}
|
||||
|
||||
return alive;
|
||||
}
|
||||
|
||||
struct Curl_cftype Curl_cft_http3 = {
|
||||
"HTTP/3",
|
||||
@ -1373,7 +1399,7 @@ struct Curl_cftype Curl_cft_http3 = {
|
||||
cf_quiche_send,
|
||||
cf_quiche_recv,
|
||||
cf_quiche_data_event,
|
||||
Curl_cf_def_conn_is_alive,
|
||||
cf_quiche_conn_is_alive,
|
||||
Curl_cf_def_conn_keep_alive,
|
||||
cf_quiche_query,
|
||||
};
|
||||
|
@ -1536,36 +1536,6 @@ static void nss_cleanup(void)
|
||||
initialized = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function uses SSL_peek to determine connection status.
|
||||
*
|
||||
* Return codes:
|
||||
* 1 means the connection is still in place
|
||||
* 0 means the connection has been closed
|
||||
* -1 means the connection status is unknown
|
||||
*/
|
||||
static int nss_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
int rc;
|
||||
char buf;
|
||||
|
||||
(void)data;
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
rc =
|
||||
PR_Recv(backend->handle, (void *)&buf, 1, PR_MSG_PEEK,
|
||||
PR_SecondsToInterval(1));
|
||||
if(rc > 0)
|
||||
return 1; /* connection still in place */
|
||||
|
||||
if(rc == 0)
|
||||
return 0; /* connection has been closed */
|
||||
|
||||
return -1; /* connection status unknown */
|
||||
}
|
||||
|
||||
static void close_one(struct ssl_connect_data *connssl)
|
||||
{
|
||||
/* before the cleanup, check whether we are using a client certificate */
|
||||
@ -2524,7 +2494,7 @@ const struct Curl_ssl Curl_ssl_nss = {
|
||||
nss_init, /* init */
|
||||
nss_cleanup, /* cleanup */
|
||||
nss_version, /* version */
|
||||
nss_check_cxn, /* check_cxn */
|
||||
Curl_none_check_cxn, /* check_cxn */
|
||||
/* NSS has no shutdown function provided and thus always fail */
|
||||
Curl_none_shutdown, /* shutdown */
|
||||
nss_data_pending, /* data_pending */
|
||||
|
@ -1780,63 +1780,6 @@ static void ossl_cleanup(void)
|
||||
Curl_tls_keylog_close();
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is used to determine connection status.
|
||||
*
|
||||
* Return codes:
|
||||
* 1 means the connection is still in place
|
||||
* 0 means the connection has been closed
|
||||
* -1 means the connection status is unknown
|
||||
*/
|
||||
static int ossl_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
{
|
||||
/* SSL_peek takes data out of the raw recv buffer without peeking so we use
|
||||
recv MSG_PEEK instead. Bug #795 */
|
||||
#ifdef MSG_PEEK
|
||||
char buf;
|
||||
ssize_t nread;
|
||||
curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
|
||||
if(sock == CURL_SOCKET_BAD)
|
||||
return 0; /* no socket, consider closed */
|
||||
nread = recv((RECV_TYPE_ARG1)sock,
|
||||
(RECV_TYPE_ARG2)&buf, (RECV_TYPE_ARG3)1,
|
||||
(RECV_TYPE_ARG4)MSG_PEEK);
|
||||
if(nread == 0)
|
||||
return 0; /* connection has been closed */
|
||||
if(nread == 1)
|
||||
return 1; /* connection still in place */
|
||||
else if(nread == -1) {
|
||||
int err = SOCKERRNO;
|
||||
if(err == EINPROGRESS ||
|
||||
#if defined(EAGAIN) && (EAGAIN != EWOULDBLOCK)
|
||||
err == EAGAIN ||
|
||||
#endif
|
||||
err == EWOULDBLOCK)
|
||||
return 1; /* connection still in place */
|
||||
if(err == ECONNRESET ||
|
||||
#ifdef ECONNABORTED
|
||||
err == ECONNABORTED ||
|
||||
#endif
|
||||
#ifdef ENETDOWN
|
||||
err == ENETDOWN ||
|
||||
#endif
|
||||
#ifdef ENETRESET
|
||||
err == ENETRESET ||
|
||||
#endif
|
||||
#ifdef ESHUTDOWN
|
||||
err == ESHUTDOWN ||
|
||||
#endif
|
||||
#ifdef ETIMEDOUT
|
||||
err == ETIMEDOUT ||
|
||||
#endif
|
||||
err == ENOTCONN)
|
||||
return 0; /* connection has been closed */
|
||||
}
|
||||
#endif
|
||||
(void)data;
|
||||
return -1; /* connection status unknown */
|
||||
}
|
||||
|
||||
/* Selects an OpenSSL crypto engine
|
||||
*/
|
||||
static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine)
|
||||
@ -4820,7 +4763,7 @@ const struct Curl_ssl Curl_ssl_openssl = {
|
||||
ossl_init, /* init */
|
||||
ossl_cleanup, /* cleanup */
|
||||
ossl_version, /* version */
|
||||
ossl_check_cxn, /* check_cxn */
|
||||
Curl_none_check_cxn, /* check_cxn */
|
||||
ossl_shutdown, /* shutdown */
|
||||
ossl_data_pending, /* data_pending */
|
||||
ossl_random, /* random */
|
||||
|
@ -3233,35 +3233,6 @@ static size_t sectransp_version(char *buffer, size_t size)
|
||||
return msnprintf(buffer, size, "SecureTransport");
|
||||
}
|
||||
|
||||
/*
|
||||
* This function uses SSLGetSessionState to determine connection status.
|
||||
*
|
||||
* Return codes:
|
||||
* 1 means the connection is still in place
|
||||
* 0 means the connection has been closed
|
||||
* -1 means the connection status is unknown
|
||||
*/
|
||||
static int sectransp_check_cxn(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
OSStatus err;
|
||||
SSLSessionState state;
|
||||
|
||||
(void)data;
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
if(backend->ssl_ctx) {
|
||||
DEBUGF(LOG_CF(data, cf, "check connection"));
|
||||
err = SSLGetSessionState(backend->ssl_ctx, &state);
|
||||
if(err == noErr)
|
||||
return state == kSSLConnected || state == kSSLHandshake;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool sectransp_data_pending(struct Curl_cfilter *cf,
|
||||
const struct Curl_easy *data)
|
||||
{
|
||||
@ -3473,7 +3444,7 @@ const struct Curl_ssl Curl_ssl_sectransp = {
|
||||
Curl_none_init, /* init */
|
||||
Curl_none_cleanup, /* cleanup */
|
||||
sectransp_version, /* version */
|
||||
sectransp_check_cxn, /* check_cxn */
|
||||
Curl_none_check_cxn, /* check_cxn */
|
||||
sectransp_shutdown, /* shutdown */
|
||||
sectransp_data_pending, /* data_pending */
|
||||
sectransp_random, /* random */
|
||||
|
@ -1650,10 +1650,11 @@ static CURLcode ssl_cf_query(struct Curl_cfilter *cf,
|
||||
CURLE_UNKNOWN_OPTION;
|
||||
}
|
||||
|
||||
static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
struct cf_call_data save;
|
||||
bool result;
|
||||
int result;
|
||||
/*
|
||||
* This function tries to determine connection status.
|
||||
*
|
||||
@ -1663,15 +1664,19 @@ static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
* -1 means the connection status is unknown
|
||||
*/
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
result = Curl_ssl->check_cxn(cf, data) != 0;
|
||||
result = Curl_ssl->check_cxn(cf, data);
|
||||
CF_DATA_RESTORE(cf, save);
|
||||
if(result > 0)
|
||||
if(result > 0) {
|
||||
*input_pending = TRUE;
|
||||
return TRUE;
|
||||
if(result == 0)
|
||||
}
|
||||
if(result == 0) {
|
||||
*input_pending = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
/* ssl backend does not know */
|
||||
return cf->next?
|
||||
cf->next->cft->is_alive(cf->next, data) :
|
||||
cf->next->cft->is_alive(cf->next, data, input_pending) :
|
||||
FALSE; /* pessimistic in absence of data */
|
||||
}
|
||||
|
||||
|
@ -110,4 +110,36 @@ class TestGoAway:
|
||||
assert r.duration >= timedelta(seconds=count)
|
||||
r.check_stats(count=count, exp_status=200, exp_exitcode=0)
|
||||
|
||||
# download files sequentially with delay, reload server for GOAWAY
|
||||
def test_03_03_h1_goaway(self, env: Env, httpd, nghttpx, repeat):
|
||||
proto = 'http/1.1'
|
||||
count = 3
|
||||
self.r = None
|
||||
def long_run():
|
||||
curl = CurlClient(env=env)
|
||||
# send 10 chunks of 1024 bytes in a response body with 100ms delay in between
|
||||
urln = f'https://{env.authority_for(env.domain1, proto)}' \
|
||||
f'/curltest/tweak?id=[0-{count - 1}]'\
|
||||
'&chunks=10&chunk_size=1024&chunk_delay=100ms'
|
||||
self.r = curl.http_download(urls=[urln], alpn_proto=proto)
|
||||
|
||||
t = Thread(target=long_run)
|
||||
t.start()
|
||||
# each request will take a second, reload the server in the middle
|
||||
# of the first one.
|
||||
time.sleep(1.5)
|
||||
assert httpd.reload()
|
||||
t.join()
|
||||
r: ExecResult = self.r
|
||||
assert r.exit_code == 0, f'{r}'
|
||||
r.check_stats(count=count, exp_status=200)
|
||||
# reload will shut down the connection gracefully with GOAWAY
|
||||
# we expect to see a second connection opened afterwards
|
||||
assert r.total_connects == 2
|
||||
for idx, s in enumerate(r.stats):
|
||||
if s['num_connects'] > 0:
|
||||
log.debug(f'request {idx} connected')
|
||||
# this should take `count` seconds to retrieve
|
||||
assert r.duration >= timedelta(seconds=count)
|
||||
|
||||
|
||||
|
93
tests/http/test_12_reuse.py
Normal file
93
tests/http/test_12_reuse.py
Normal file
@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#***************************************************************************
|
||||
# _ _ ____ _
|
||||
# Project ___| | | | _ \| |
|
||||
# / __| | | | |_) | |
|
||||
# | (__| |_| | _ <| |___
|
||||
# \___|\___/|_| \_\_____|
|
||||
#
|
||||
# Copyright (C) 2008 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at https://curl.se/docs/copyright.html.
|
||||
#
|
||||
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
# copies of the Software, and permit persons to whom the Software is
|
||||
# furnished to do so, under the terms of the COPYING file.
|
||||
#
|
||||
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
# KIND, either express or implied.
|
||||
#
|
||||
# SPDX-License-Identifier: curl
|
||||
#
|
||||
###########################################################################
|
||||
#
|
||||
import difflib
|
||||
import filecmp
|
||||
import logging
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from testenv import Env, CurlClient
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.mark.skipif(condition=Env.setup_incomplete(),
|
||||
reason=f"missing: {Env.incomplete_reason()}")
|
||||
class TestReuse:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
def _class_scope(self, env, httpd, nghttpx):
|
||||
env.make_data_file(indir=httpd.docs_dir, fname="data-100k", fsize=100*1024)
|
||||
env.make_data_file(indir=httpd.docs_dir, fname="data-1m", fsize=1024*1024)
|
||||
env.make_data_file(indir=httpd.docs_dir, fname="data-10m", fsize=10*1024*1024)
|
||||
yield
|
||||
# restore default config
|
||||
httpd.clear_extra_configs()
|
||||
httpd.reload()
|
||||
|
||||
# check if HTTP/1.1 handles 'Connection: close' correctly
|
||||
@pytest.mark.parametrize("proto", ['http/1.1'])
|
||||
def test_12_01_h1_conn_close(self, env: Env,
|
||||
httpd, nghttpx, repeat, proto):
|
||||
httpd.clear_extra_configs()
|
||||
httpd.set_extra_config('base', [
|
||||
f'MaxKeepAliveRequests 1',
|
||||
])
|
||||
httpd.reload()
|
||||
count = 100
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]'
|
||||
r = curl.http_download(urls=[urln], alpn_proto=proto)
|
||||
assert r.exit_code == 0
|
||||
r.check_stats(count=count, exp_status=200)
|
||||
# Server sends `Connection: close` on every 2nd request, requiring
|
||||
# a new connection
|
||||
assert r.total_connects == count/2
|
||||
|
||||
@pytest.mark.parametrize("proto", ['http/1.1'])
|
||||
def test_12_02_h1_conn_timeout(self, env: Env,
|
||||
httpd, nghttpx, repeat, proto):
|
||||
httpd.clear_extra_configs()
|
||||
httpd.set_extra_config('base', [
|
||||
f'KeepAliveTimeout 1',
|
||||
])
|
||||
httpd.reload()
|
||||
count = 5
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]'
|
||||
r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
|
||||
'--rate', '30/m',
|
||||
])
|
||||
assert r.exit_code == 0
|
||||
r.check_stats(count=count, exp_status=200)
|
||||
# Connections time out on server before we send another request,
|
||||
assert r.total_connects == count
|
||||
# we do not see how often a request was retried in the stats, so
|
||||
# we cannot check that connection reuse attempted a connection that
|
||||
# was later detected to be "dead". We would like to
|
||||
# assert stat['retry_count'] == 0
|
@ -100,6 +100,9 @@ class Httpd:
|
||||
else:
|
||||
self._extra_configs[domain] = lines
|
||||
|
||||
def clear_extra_configs(self):
|
||||
self._extra_configs = {}
|
||||
|
||||
def _run(self, args, intext=''):
|
||||
env = {}
|
||||
for key, val in os.environ.items():
|
||||
@ -231,6 +234,8 @@ class Httpd:
|
||||
f'Listen {self.env.proxys_port}',
|
||||
f'TypesConfig "{self._conf_dir}/mime.types',
|
||||
]
|
||||
if 'base' in self._extra_configs:
|
||||
conf.extend(self._extra_configs['base'])
|
||||
conf.extend([ # plain http host for domain1
|
||||
f'<VirtualHost *:{self.env.http_port}>',
|
||||
f' ServerName {domain1}',
|
||||
|
Loading…
Reference in New Issue
Block a user