static int setup(CURL *hnd, const char *url, struct transfer *t,
int http_version, struct curl_slist *host,
- CURLSH *share, int use_earlydata)
+ CURLSH *share, int use_earlydata, int fresh_connect)
{
curl_easy_setopt(hnd, CURLOPT_SHARE, share);
curl_easy_setopt(hnd, CURLOPT_URL, url);
curl_easy_setopt(hnd, CURLOPT_FORBID_REUSE, 1L);
if(host)
curl_easy_setopt(hnd, CURLOPT_RESOLVE, host);
+ if(fresh_connect)
+ curl_easy_setopt(hnd, CURLOPT_FRESH_CONNECT, 1L);
/* please be verbose */
if(verbose) {
int http_version = CURL_HTTP_VERSION_2_0;
int ch;
struct curl_slist *host = NULL;
- const char *resolve = NULL;
+ char *resolve = NULL;
+ size_t max_host_conns = 0;
+ int fresh_connect = 0;
- while((ch = getopt(argc, argv, "aefhm:n:A:F:P:r:V:")) != -1) {
+ while((ch = getopt(argc, argv, "aefhm:n:xA:F:M:P:r:V:")) != -1) {
switch(ch) {
case 'h':
usage(NULL);
case 'n':
transfer_count = (size_t)strtol(optarg, NULL, 10);
break;
+ case 'x':
+ fresh_connect = 1;
+ break;
case 'A':
abort_offset = (size_t)strtol(optarg, NULL, 10);
break;
case 'F':
fail_offset = (size_t)strtol(optarg, NULL, 10);
break;
+ case 'M':
+ max_host_conns = (size_t)strtol(optarg, NULL, 10);
+ break;
case 'P':
pause_offset = (size_t)strtol(optarg, NULL, 10);
break;
case 'r':
- resolve = optarg;
+ resolve = strdup(optarg);
break;
case 'V': {
if(!strcmp("http/1.1", optarg))
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
- curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
+ /* curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); */
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
multi_handle = curl_multi_init();
curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
+ curl_multi_setopt(multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS,
+ (long)max_host_conns);
active_transfers = 0;
for(i = 0; i < transfer_count; ++i) {
t = &transfers[i];
t->easy = curl_easy_init();
if(!t->easy ||
- setup(t->easy, url, t, http_version, host, share, use_earlydata)) {
+ setup(t->easy, url, t, http_version, host, share, use_earlydata,
+ fresh_connect)) {
fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
return 1;
}
t->easy = curl_easy_init();
if(!t->easy ||
setup(t->easy, url, t, http_version, host, share,
- use_earlydata)) {
+ use_earlydata, fresh_connect)) {
fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
return 1;
}
free(transfers);
curl_share_cleanup(share);
+ curl_slist_free_all(host);
+ free(resolve);
return 0;
#else
elif proto == 'h3':
# not implemented
assert earlydata[1] == 0, f'{earlydata}'
+
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
+ @pytest.mark.parametrize("max_host_conns", [0, 1, 5])
+ def test_02_33_max_host_conns(self, env: Env, httpd, nghttpx, proto, max_host_conns):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ count = 100
+ max_parallel = 100
+ docname = 'data-10k'
+ port = env.port_for(proto)
+ url = f'https://{env.domain1}:{port}/{docname}'
+ client = LocalClient(name='hx-download', env=env)
+ if not client.exists():
+ pytest.skip(f'example client not built: {client.name}')
+ r = client.run(args=[
+ '-n', f'{count}',
+ '-m', f'{max_parallel}',
+ '-x', # always use a fresh connection
+ '-M', str(max_host_conns), # limit conns per host
+ '-r', f'{env.domain1}:{port}:127.0.0.1',
+ '-V', proto, url
+ ])
+ r.check_exit_code(0)
+ srcfile = os.path.join(httpd.docs_dir, docname)
+ self.check_downloads(client, srcfile, count)