http2: improved upload eos handling

- replace the counting of upload lengths with the new eos send flag
- improve frequency of stream draining to happen less on events where it
  is not needed
- this PR is based on #14220

http2, cf-h2-proxy: fix EAGAINed out buffer
- in adjust pollset and shutdown handling, a non-empty `ctx->outbufq`
  must trigger send polling, irregardless of http/2 flow control
- in http2, fix retry handling of blocked GOAWAY frame

test case improvement:
- let client 'upload-pausing' handle http versions

Closes #14253
This commit is contained in:
Stefan Eissing 2024-08-04 09:51:26 +02:00 committed by Daniel Stenberg
parent 344ba8c883
commit 35bf766280
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
10 changed files with 302 additions and 190 deletions

View file

@ -182,7 +182,16 @@ static int err(void)
exit(2);
}
static void usage(const char *msg)
{
if(msg)
fprintf(stderr, "%s\n", msg);
fprintf(stderr,
"usage: [options] url\n"
" upload and pause, options:\n"
" -V http_version (http/1.1, h2, h3) http version to use\n"
);
}
int main(int argc, char *argv[])
{
@ -192,12 +201,37 @@ int main(int argc, char *argv[])
struct curl_slist *resolve = NULL;
char resolve_buf[1024];
char *url, *host = NULL, *port = NULL;
int http_version = CURL_HTTP_VERSION_1_1;
int ch;
if(argc != 2) {
fprintf(stderr, "ERROR: need URL as argument\n");
while((ch = getopt(argc, argv, "V:")) != -1) {
switch(ch) {
case 'V': {
if(!strcmp("http/1.1", optarg))
http_version = CURL_HTTP_VERSION_1_1;
else if(!strcmp("h2", optarg))
http_version = CURL_HTTP_VERSION_2_0;
else if(!strcmp("h3", optarg))
http_version = CURL_HTTP_VERSION_3ONLY;
else {
usage("invalid http version");
return 1;
}
break;
}
default:
usage("invalid option");
return 1;
}
}
argc -= optind;
argv += optind;
if(argc != 1) {
usage("not enough arguments");
return 2;
}
url = argv[1];
url = argv[0];
curl_global_init(CURL_GLOBAL_DEFAULT);
curl_global_trace("ids,time");
@ -247,6 +281,9 @@ int main(int argc, char *argv[])
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
if(curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L) != CURLE_OK ||
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, debug_cb)
!= CURLE_OK ||
@ -254,6 +291,8 @@ int main(int argc, char *argv[])
err();
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, http_version);
rc = curl_easy_perform(curl);
if(curl) {

View file

@ -475,9 +475,14 @@ class TestUpload:
client = LocalClient(name='upload-pausing', env=env, timeout=60)
if not client.exists():
pytest.skip(f'example client not built: {client.name}')
url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after=0'
r = client.run([url])
r.check_exit_code(18) # PARTIAL_FILE
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]&die_after=0'
r = client.run(['-V', proto, url])
exp_code = 18 # PARTIAL_FILE
if proto == 'h2':
exp_code = 92 # CURLE_HTTP2_STREAM
elif proto == 'h3':
exp_code = 95 # CURLE_HTTP3
r.check_exit_code(exp_code)
# upload data, pause, let connection die without any response at all
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
@ -489,9 +494,12 @@ class TestUpload:
client = LocalClient(name='upload-pausing', env=env, timeout=60)
if not client.exists():
pytest.skip(f'example client not built: {client.name}')
url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&just_die=1'
r = client.run([url])
r.check_exit_code(52) # GOT_NOTHING
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]&just_die=1'
r = client.run(['-V', proto, url])
exp_code = 52 # GOT_NOTHING
if proto == 'h2' or proto == 'h3':
exp_code = 0 # we get a 500 from the server
r.check_exit_code(exp_code) # GOT_NOTHING
# upload data, pause, let connection die after 100 continue
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
@ -503,9 +511,12 @@ class TestUpload:
client = LocalClient(name='upload-pausing', env=env, timeout=60)
if not client.exists():
pytest.skip(f'example client not built: {client.name}')
url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after_100=1'
r = client.run([url])
r.check_exit_code(52) # GOT_NOTHING
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]&die_after_100=1'
r = client.run(['-V', proto, url])
exp_code = 52 # GOT_NOTHING
if proto == 'h2' or proto == 'h3':
exp_code = 0 # we get a 500 from the server
r.check_exit_code(exp_code) # GOT_NOTHING
# speed limited on put handler
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])

View file

@ -221,7 +221,7 @@ class TestProxy:
indata = open(srcfile).readlines()
for i in range(count):
respdata = open(curl.response_file(i)).readlines()
assert respdata == indata
assert respdata == indata, f'resonse {i} differs'
assert r.total_connects == 1, r.dump_logs()
@pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")