mirror of
https://github.com/curl/curl.git
synced 2026-07-23 11:57:18 +03:00
tests-httpd: basic infra to run curl against an apache httpd plus nghttpx for h3
- adding '--with-test-httpd=<path>' to configure non-standard apache2
install
- python env and base classes for running httpd
- basic tests for connectivity with h1/h2/h3
- adding test cases for truncated responses in http versions.
- adding goaway test for HTTP/3.
- adding "stuttering" tests with parallel downloads in chunks with
varying delays between chunks.
- adding a curltest module to the httpd server, adding GOAWAY test.
- mod_curltest now installs 2 handlers
- 'echo': writing as response body what came as request body
- 'tweak': with query parameters to tweak response behaviour
- marked known fails as skip for now
Closes #10175
This commit is contained in:
parent
1c5d8acf79
commit
33ac97e1cb
22 changed files with 3016 additions and 46 deletions
59
lib/http2.c
59
lib/http2.c
|
|
@ -772,48 +772,60 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
|
||||
if(!stream_id) {
|
||||
/* stream ID zero is for connection-oriented stuff */
|
||||
if(frame->hd.type == NGHTTP2_SETTINGS) {
|
||||
DEBUGASSERT(data);
|
||||
switch(frame->hd.type) {
|
||||
case NGHTTP2_SETTINGS: {
|
||||
uint32_t max_conn = ctx->max_concurrent_streams;
|
||||
H2BUGF(infof(data, "Got SETTINGS"));
|
||||
H2BUGF(infof(data, CFMSG(cf, "recv frame SETTINGS")));
|
||||
ctx->max_concurrent_streams = nghttp2_session_get_remote_settings(
|
||||
session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
|
||||
ctx->enable_push = nghttp2_session_get_remote_settings(
|
||||
session, NGHTTP2_SETTINGS_ENABLE_PUSH);
|
||||
H2BUGF(infof(data, "MAX_CONCURRENT_STREAMS == %d",
|
||||
H2BUGF(infof(data, CFMSG(cf, "MAX_CONCURRENT_STREAMS == %d"),
|
||||
ctx->max_concurrent_streams));
|
||||
H2BUGF(infof(data, "ENABLE_PUSH == %s",
|
||||
H2BUGF(infof(data, CFMSG(cf, "ENABLE_PUSH == %s"),
|
||||
ctx->enable_push?"TRUE":"false"));
|
||||
if(max_conn != ctx->max_concurrent_streams) {
|
||||
if(data && max_conn != ctx->max_concurrent_streams) {
|
||||
/* only signal change if the value actually changed */
|
||||
infof(data,
|
||||
"Connection state changed (MAX_CONCURRENT_STREAMS == %u)!",
|
||||
infof(data, CFMSG(cf, "MAX_CONCURRENT_STREAMS now %u"),
|
||||
ctx->max_concurrent_streams);
|
||||
multi_connchanged(data->multi);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NGHTTP2_GOAWAY:
|
||||
if(data) {
|
||||
infof(data, "recveived GOAWAY, error=%d, last_stream=%u",
|
||||
frame->goaway.error_code, frame->goaway.last_stream_id);
|
||||
multi_connchanged(data->multi);
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_WINDOW_UPDATE:
|
||||
H2BUGF(infof(data, CFMSG(cf, "recv frame WINDOW_UPDATE")));
|
||||
break;
|
||||
default:
|
||||
H2BUGF(infof(data, CFMSG(cf, "recv frame %x on 0"), frame->hd.type));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
data_s = nghttp2_session_get_stream_user_data(session, stream_id);
|
||||
if(!data_s) {
|
||||
H2BUGF(infof(data,
|
||||
"No Curl_easy associated with stream: %u",
|
||||
H2BUGF(infof(data, CFMSG(cf, "No Curl_easy associated with stream: %u"),
|
||||
stream_id));
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream = data_s->req.p.http;
|
||||
if(!stream) {
|
||||
H2BUGF(infof(data_s, "No proto pointer for stream: %u",
|
||||
H2BUGF(infof(data_s, CFMSG(cf, "No proto pointer for stream: %u"),
|
||||
stream_id));
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
H2BUGF(infof(data_s, "on_frame_recv() header %x stream %u",
|
||||
frame->hd.type, stream_id));
|
||||
|
||||
switch(frame->hd.type) {
|
||||
case NGHTTP2_DATA:
|
||||
/* If body started on this stream, then receiving DATA is illegal. */
|
||||
H2BUGF(infof(data_s, CFMSG(cf, "recv frame DATA stream %u"), stream_id));
|
||||
if(!stream->bodystarted) {
|
||||
rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||
stream_id, NGHTTP2_PROTOCOL_ERROR);
|
||||
|
|
@ -824,6 +836,8 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
}
|
||||
break;
|
||||
case NGHTTP2_HEADERS:
|
||||
H2BUGF(infof(data_s, CFMSG(cf, "recv frame HEADERS stream %u"),
|
||||
stream_id));
|
||||
if(stream->bodystarted) {
|
||||
/* Only valid HEADERS after body started is trailer HEADERS. We
|
||||
buffer them in on_header callback. */
|
||||
|
|
@ -857,7 +871,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
stream->nread_header_recvbuf += ncopy;
|
||||
|
||||
DEBUGASSERT(stream->mem);
|
||||
H2BUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p",
|
||||
H2BUGF(infof(data_s, CFMSG(cf, "%zu header bytes, stream %u at %p"),
|
||||
ncopy, stream_id, stream->mem));
|
||||
|
||||
stream->len -= ncopy;
|
||||
|
|
@ -869,6 +883,8 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
|
||||
break;
|
||||
case NGHTTP2_PUSH_PROMISE:
|
||||
H2BUGF(infof(data_s, CFMSG(cf, "recv frame PUSH_PROMISE stream %u"),
|
||||
stream_id));
|
||||
rv = push_promise(cf, data_s, &frame->push_promise);
|
||||
if(rv) { /* deny! */
|
||||
int h2;
|
||||
|
|
@ -879,13 +895,13 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
if(nghttp2_is_fatal(h2))
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
else if(rv == CURL_PUSH_ERROROUT) {
|
||||
DEBUGF(infof(data_s, "Fail the parent stream (too)"));
|
||||
DEBUGF(infof(data_s, CFMSG(cf, "Fail the parent stream (too)")));
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
H2BUGF(infof(data_s, "Got frame type %x for stream %u",
|
||||
H2BUGF(infof(data_s, CFMSG(cf, "recv frame %x for stream %u"),
|
||||
frame->hd.type, stream_id));
|
||||
break;
|
||||
}
|
||||
|
|
@ -2210,12 +2226,19 @@ static CURLcode h2_cf_query(struct Curl_cfilter *cf,
|
|||
int query, int *pres1, void **pres2)
|
||||
{
|
||||
struct h2_cf_ctx *ctx = cf->ctx;
|
||||
size_t effective_max;
|
||||
|
||||
switch(query) {
|
||||
case CF_QUERY_MAX_CONCURRENT:
|
||||
DEBUGASSERT(pres1);
|
||||
*pres1 = (ctx->max_concurrent_streams > INT_MAX)?
|
||||
INT_MAX : (int)ctx->max_concurrent_streams;
|
||||
if(nghttp2_session_check_request_allowed(ctx->h2) == 0) {
|
||||
/* the limit is what we have in use right now */
|
||||
effective_max = CONN_INUSE(cf->conn);
|
||||
}
|
||||
else {
|
||||
effective_max = ctx->max_concurrent_streams;
|
||||
}
|
||||
*pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max;
|
||||
return CURLE_OK;
|
||||
default:
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -67,15 +67,24 @@
|
|||
#include "memdebug.h"
|
||||
|
||||
/* #define DEBUG_NGTCP2 */
|
||||
#ifdef CURLDEBUG
|
||||
#define DEBUG_HTTP3
|
||||
#endif
|
||||
#ifdef DEBUG_HTTP3
|
||||
#define H3BUGF(x) x
|
||||
#define DEBUG_CF 0
|
||||
|
||||
#if DEBUG_CF
|
||||
#define CF_DEBUGF(x) x
|
||||
#define H3STRM(cf,sid,msg) \
|
||||
"[CONN-%ld-%d][CF-%s][h3sid=%" PRIx64 "] "msg, (cf)->conn->connection_id, \
|
||||
(cf)->sockindex, (cf)->cft->name, sid
|
||||
#define QSTRM(cf,sid,msg) \
|
||||
"[CONN-%ld-%d][CF-%s][qsid=%" PRIx64 "] "msg, (cf)->conn->connection_id, \
|
||||
(cf)->sockindex, (cf)->cft->name, sid
|
||||
|
||||
#else
|
||||
#define H3BUGF(x) do { } while(0)
|
||||
#define H3STRM(cf,sid,msg)
|
||||
#define QSTRM(cf,sid,msg)
|
||||
#define CF_DEBUGF(x) do { } while(0)
|
||||
#endif
|
||||
|
||||
|
||||
#define H3_ALPN_H3_29 "\x5h3-29"
|
||||
#define H3_ALPN_H3 "\x2h3"
|
||||
|
||||
|
|
@ -198,8 +207,11 @@ static ngtcp2_tstamp timestamp(void)
|
|||
#ifdef DEBUG_NGTCP2
|
||||
static void quic_printf(void *user_data, const char *fmt, ...)
|
||||
{
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct cf_ngtcp2_ctx *ctx = cf->ctx;
|
||||
|
||||
(void)ctx; /* TODO: need an easy handle to infof() message */
|
||||
va_list ap;
|
||||
(void)user_data; /* TODO, use this to do infof() instead long-term */
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
|
@ -430,15 +442,15 @@ static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
|
|||
gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref);
|
||||
|
||||
if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) {
|
||||
H3BUGF(fprintf(stderr,
|
||||
"ngtcp2_crypto_gnutls_configure_client_session failed\n"));
|
||||
CF_DEBUGF(fprintf(stderr,
|
||||
"ngtcp2_crypto_gnutls_configure_client_session failed\n"));
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL);
|
||||
if(rc < 0) {
|
||||
H3BUGF(fprintf(stderr, "gnutls_priority_set_direct failed: %s\n",
|
||||
gnutls_strerror(rc)));
|
||||
CF_DEBUGF(fprintf(stderr, "gnutls_priority_set_direct failed: %s\n",
|
||||
gnutls_strerror(rc)));
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
|
|
@ -637,9 +649,11 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
|
|||
{
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct cf_ngtcp2_ctx *ctx = cf->ctx;
|
||||
struct Curl_easy *data = stream_user_data;
|
||||
int rv;
|
||||
|
||||
(void)data;
|
||||
(void)tconn;
|
||||
(void)stream_user_data;
|
||||
/* stream is closed... */
|
||||
|
||||
if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
|
||||
|
|
@ -648,6 +662,7 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
|
|||
|
||||
rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id,
|
||||
app_error_code);
|
||||
CF_DEBUGF(infof(data, QSTRM(cf, stream_id, "close -> %d"), rv));
|
||||
if(rv) {
|
||||
ngtcp2_connection_close_error_set_application_error(
|
||||
&ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0);
|
||||
|
|
@ -663,13 +678,15 @@ static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id,
|
|||
{
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct cf_ngtcp2_ctx *ctx = cf->ctx;
|
||||
struct Curl_easy *data = stream_user_data;
|
||||
int rv;
|
||||
(void)tconn;
|
||||
(void)final_size;
|
||||
(void)app_error_code;
|
||||
(void)stream_user_data;
|
||||
(void)data;
|
||||
|
||||
rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id);
|
||||
CF_DEBUGF(infof(data, QSTRM(cf, stream_id, "reset -> %d"), rv));
|
||||
if(rv) {
|
||||
return NGTCP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
|
|
@ -850,14 +867,15 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
|
|||
uint64_t app_error_code, void *user_data,
|
||||
void *stream_user_data)
|
||||
{
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct Curl_easy *data = stream_user_data;
|
||||
struct HTTP *stream = data->req.p.http;
|
||||
(void)conn;
|
||||
(void)stream_id;
|
||||
(void)app_error_code;
|
||||
(void)user_data;
|
||||
H3BUGF(infof(data, "cb_h3_stream_close CALLED"));
|
||||
(void)cf;
|
||||
|
||||
CF_DEBUGF(infof(data, H3STRM(cf, stream_id, "close")));
|
||||
stream->closed = TRUE;
|
||||
stream->error3 = app_error_code;
|
||||
Curl_expire(data, 0, EXPIRE_QUIC);
|
||||
|
|
@ -897,12 +915,16 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream_id,
|
|||
const uint8_t *buf, size_t buflen,
|
||||
void *user_data, void *stream_user_data)
|
||||
{
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct Curl_easy *data = stream_user_data;
|
||||
struct HTTP *stream = data->req.p.http;
|
||||
CURLcode result = CURLE_OK;
|
||||
(void)conn;
|
||||
(void)cf;
|
||||
|
||||
result = write_data(stream, buf, buflen);
|
||||
CF_DEBUGF(infof(data, H3STRM(cf, stream_id, "recv_data(len=%zu) -> %d"),
|
||||
buflen, result));
|
||||
if(result) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -957,13 +979,14 @@ static int decode_status_code(const uint8_t *value, size_t len)
|
|||
static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
|
||||
int fin, void *user_data, void *stream_user_data)
|
||||
{
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct Curl_easy *data = stream_user_data;
|
||||
struct HTTP *stream = data->req.p.http;
|
||||
CURLcode result = CURLE_OK;
|
||||
(void)conn;
|
||||
(void)stream_id;
|
||||
(void)user_data;
|
||||
(void)fin;
|
||||
(void)cf;
|
||||
|
||||
/* add a CRLF only if we've received some headers */
|
||||
if(stream->firstheader) {
|
||||
|
|
@ -973,6 +996,8 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
|
|||
}
|
||||
}
|
||||
|
||||
CF_DEBUGF(infof(data, H3STRM(cf, stream_id, "end_headers(status_code=%d"),
|
||||
stream->status_code));
|
||||
if(stream->status_code / 100 != 1) {
|
||||
stream->bodystarted = TRUE;
|
||||
}
|
||||
|
|
@ -1054,12 +1079,14 @@ static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id,
|
|||
void *stream_user_data) {
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct cf_ngtcp2_ctx *ctx = cf->ctx;
|
||||
struct Curl_easy *data = stream_user_data;
|
||||
int rv;
|
||||
(void)conn;
|
||||
(void)stream_user_data;
|
||||
(void)data;
|
||||
|
||||
rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, stream_id,
|
||||
app_error_code);
|
||||
CF_DEBUGF(infof(data, H3STRM(cf, stream_id, "reset -> %d"), rv));
|
||||
if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
|
||||
return NGTCP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
|
|
@ -1251,9 +1278,9 @@ static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
|
|||
|
||||
if(!data->set.postfields) {
|
||||
stream->h3out->used -= datalen;
|
||||
H3BUGF(infof(data,
|
||||
"cb_h3_acked_stream_data, %zd bytes, %zd left unacked",
|
||||
datalen, stream->h3out->used));
|
||||
CF_DEBUGF(infof(data,
|
||||
"cb_h3_acked_stream_data, %zd bytes, %zd left unacked",
|
||||
datalen, stream->h3out->used));
|
||||
DEBUGASSERT(stream->h3out->used < H3_SEND_SIZE);
|
||||
|
||||
if(stream->h3out->used == 0) {
|
||||
|
|
@ -1318,13 +1345,13 @@ static nghttp3_ssize cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id,
|
|||
if(!stream->upload_left)
|
||||
*pflags = NGHTTP3_DATA_FLAG_EOF;
|
||||
}
|
||||
H3BUGF(infof(data, "cb_h3_readfunction %zd bytes%s (at %zd unacked)",
|
||||
nread, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"",
|
||||
out->used));
|
||||
CF_DEBUGF(infof(data, "cb_h3_readfunction %zd bytes%s (at %zd unacked)",
|
||||
nread, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"",
|
||||
out->used));
|
||||
}
|
||||
if(stream->upload_done && !stream->upload_len &&
|
||||
(stream->upload_left <= 0)) {
|
||||
H3BUGF(infof(data, "cb_h3_readfunction sets EOF"));
|
||||
CF_DEBUGF(infof(data, "cb_h3_readfunction sets EOF"));
|
||||
*pflags = NGHTTP3_DATA_FLAG_EOF;
|
||||
return nread ? 1 : 0;
|
||||
}
|
||||
|
|
@ -1427,7 +1454,7 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf,
|
|||
|
||||
Curl_safefree(nva);
|
||||
|
||||
infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)",
|
||||
infof(data, "Using HTTP/3 Stream ID: %" PRIx64 " (easy handle %p)",
|
||||
stream3_id, (void *)data);
|
||||
|
||||
Curl_pseudo_free(hreq);
|
||||
|
|
@ -1469,8 +1496,8 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|||
sent = len;
|
||||
}
|
||||
else {
|
||||
H3BUGF(infof(data, "ngh3_stream_send() wants to send %zd bytes",
|
||||
len));
|
||||
CF_DEBUGF(infof(data, "ngh3_stream_send() wants to send %zd bytes",
|
||||
len));
|
||||
if(!stream->upload_len) {
|
||||
stream->upload_mem = buf;
|
||||
stream->upload_len = len;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue