1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3#*************************************************************************** 4# _ _ ____ _ 5# Project ___| | | | _ \| | 6# / __| | | | |_) | | 7# | (__| |_| | _ <| |___ 8# \___|\___/|_| \_\_____| 9# 10# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 11# 12# This software is licensed as described in the file COPYING, which 13# you should have received as part of this distribution. The terms 14# are also available at https://curl.se/docs/copyright.html. 15# 16# You may opt to use, copy, modify, merge, publish, distribute and/or sell 17# copies of the Software, and permit persons to whom the Software is 18# furnished to do so, under the terms of the COPYING file. 19# 20# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21# KIND, either express or implied. 22# 23# SPDX-License-Identifier: curl 24# 25########################################################################### 26# 27import difflib 28import filecmp 29import logging 30import os 31import time 32import pytest 33 34from testenv import Env, CurlClient, LocalClient 35 36 37log = logging.getLogger(__name__) 38 39 40class TestUpload: 41 42 @pytest.fixture(autouse=True, scope='class') 43 def _class_scope(self, env, httpd, nghttpx): 44 if env.have_h3(): 45 nghttpx.start_if_needed() 46 env.make_data_file(indir=env.gen_dir, fname="data-63k", fsize=63*1024) 47 env.make_data_file(indir=env.gen_dir, fname="data-64k", fsize=64*1024) 48 env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100*1024) 49 env.make_data_file(indir=env.gen_dir, fname="data-1m+", fsize=(1024*1024)+1) 50 env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) 51 httpd.clear_extra_configs() 52 httpd.reload() 53 54 # upload small data, check that this is what was echoed 55 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 56 def test_07_01_upload_1_small(self, env: Env, httpd, nghttpx, repeat, proto): 57 if proto == 'h3' and not env.have_h3(): 58 pytest.skip("h3 not supported") 59 if proto == 'h3' and env.curl_uses_lib('msh3'): 60 pytest.skip("msh3 fails here") 61 data = '0123456789' 62 curl = CurlClient(env=env) 63 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' 64 r = curl.http_upload(urls=[url], data=data, alpn_proto=proto) 65 r.check_stats(count=1, http_status=200, exitcode=0) 66 respdata = open(curl.response_file(0)).readlines() 67 assert respdata == [data] 68 69 # upload large data, check that this is what was echoed 70 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 71 def test_07_02_upload_1_large(self, env: Env, httpd, nghttpx, repeat, proto): 72 if proto == 'h3' and not env.have_h3(): 73 pytest.skip("h3 not supported") 74 if proto == 'h3' and env.curl_uses_lib('msh3'): 75 pytest.skip("msh3 fails here") 76 fdata = os.path.join(env.gen_dir, 'data-100k') 77 curl = CurlClient(env=env) 78 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' 79 r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto) 80 r.check_stats(count=1, http_status=200, exitcode=0) 81 indata = open(fdata).readlines() 82 respdata = open(curl.response_file(0)).readlines() 83 assert respdata == indata 84 85 # upload data sequentially, check that they were echoed 86 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 87 def test_07_10_upload_sequential(self, env: Env, httpd, nghttpx, repeat, proto): 88 if proto == 'h3' and not env.have_h3(): 89 pytest.skip("h3 not supported") 90 if proto == 'h3' and env.curl_uses_lib('msh3'): 91 pytest.skip("msh3 stalls here") 92 count = 50 93 data = '0123456789' 94 curl = CurlClient(env=env) 95 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' 96 r = curl.http_upload(urls=[url], data=data, alpn_proto=proto) 97 r.check_stats(count=count, http_status=200, exitcode=0) 98 for i in range(count): 99 respdata = open(curl.response_file(i)).readlines() 100 assert respdata == [data] 101 102 # upload data parallel, check that they were echoed 103 @pytest.mark.parametrize("proto", ['h2', 'h3']) 104 def test_07_11_upload_parallel(self, env: Env, httpd, nghttpx, repeat, proto): 105 if proto == 'h3' and not env.have_h3(): 106 pytest.skip("h3 not supported") 107 if proto == 'h3' and env.curl_uses_lib('msh3'): 108 pytest.skip("msh3 stalls here") 109 # limit since we use a separate connection in h1 110 count = 50 111 data = '0123456789' 112 curl = CurlClient(env=env) 113 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' 114 r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, 115 extra_args=['--parallel']) 116 r.check_stats(count=count, http_status=200, exitcode=0) 117 for i in range(count): 118 respdata = open(curl.response_file(i)).readlines() 119 assert respdata == [data] 120 121 # upload large data sequentially, check that this is what was echoed 122 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 123 def test_07_12_upload_seq_large(self, env: Env, httpd, nghttpx, repeat, proto): 124 if proto == 'h3' and not env.have_h3(): 125 pytest.skip("h3 not supported") 126 if proto == 'h3' and env.curl_uses_lib('msh3'): 127 pytest.skip("msh3 stalls here") 128 fdata = os.path.join(env.gen_dir, 'data-100k') 129 count = 20 130 curl = CurlClient(env=env) 131 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' 132 r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto) 133 r.check_response(count=count, http_status=200) 134 indata = open(fdata).readlines() 135 r.check_stats(count=count, http_status=200, exitcode=0) 136 for i in range(count): 137 respdata = open(curl.response_file(i)).readlines() 138 assert respdata == indata 139 140 # upload very large data sequentially, check that this is what was echoed 141 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 142 def test_07_13_upload_seq_large(self, env: Env, httpd, nghttpx, repeat, proto): 143 if proto == 'h3' and not env.have_h3(): 144 pytest.skip("h3 not supported") 145 if proto == 'h3' and env.curl_uses_lib('msh3'): 146 pytest.skip("msh3 stalls here") 147 fdata = os.path.join(env.gen_dir, 'data-10m') 148 count = 2 149 curl = CurlClient(env=env) 150 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' 151 r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto) 152 r.check_stats(count=count, http_status=200, exitcode=0) 153 indata = open(fdata).readlines() 154 for i in range(count): 155 respdata = open(curl.response_file(i)).readlines() 156 assert respdata == indata 157 158 # upload data parallel, check that they were echoed 159 @pytest.mark.parametrize("proto", ['h2', 'h3']) 160 def test_07_20_upload_parallel(self, env: Env, httpd, nghttpx, repeat, proto): 161 if proto == 'h3' and not env.have_h3(): 162 pytest.skip("h3 not supported") 163 if proto == 'h3' and env.curl_uses_lib('msh3'): 164 pytest.skip("msh3 stalls here") 165 # limit since we use a separate connection in h1 166 count = 20 167 data = '0123456789' 168 curl = CurlClient(env=env) 169 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' 170 r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, 171 extra_args=['--parallel']) 172 r.check_stats(count=count, http_status=200, exitcode=0) 173 for i in range(count): 174 respdata = open(curl.response_file(i)).readlines() 175 assert respdata == [data] 176 177 # upload large data parallel, check that this is what was echoed 178 @pytest.mark.parametrize("proto", ['h2', 'h3']) 179 def test_07_21_upload_parallel_large(self, env: Env, httpd, nghttpx, repeat, proto): 180 if proto == 'h3' and not env.have_h3(): 181 pytest.skip("h3 not supported") 182 if proto == 'h3' and env.curl_uses_lib('msh3'): 183 pytest.skip("msh3 stalls here") 184 fdata = os.path.join(env.gen_dir, 'data-100k') 185 # limit since we use a separate connection in h1 186 count = 20 187 curl = CurlClient(env=env) 188 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]' 189 r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, 190 extra_args=['--parallel']) 191 r.check_response(count=count, http_status=200) 192 self.check_download(count, fdata, curl) 193 194 # upload large data parallel to a URL that denies uploads 195 @pytest.mark.parametrize("proto", ['h2', 'h3']) 196 def test_07_22_upload_parallel_fail(self, env: Env, httpd, nghttpx, repeat, proto): 197 if proto == 'h3' and not env.have_h3(): 198 pytest.skip("h3 not supported") 199 if proto == 'h3' and env.curl_uses_lib('msh3'): 200 pytest.skip("msh3 stalls here") 201 fdata = os.path.join(env.gen_dir, 'data-10m') 202 count = 100 203 curl = CurlClient(env=env) 204 url = f'https://{env.authority_for(env.domain1, proto)}'\ 205 f'/curltest/tweak?status=400&delay=5ms&chunks=1&body_error=reset&id=[0-{count-1}]' 206 r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, 207 extra_args=['--parallel']) 208 exp_exit = 92 if proto == 'h2' else 95 209 r.check_stats(count=count, exitcode=exp_exit) 210 211 # PUT 100k 212 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 213 def test_07_30_put_100k(self, env: Env, httpd, nghttpx, repeat, proto): 214 if proto == 'h3' and not env.have_h3(): 215 pytest.skip("h3 not supported") 216 if proto == 'h3' and env.curl_uses_lib('msh3'): 217 pytest.skip("msh3 fails here") 218 fdata = os.path.join(env.gen_dir, 'data-100k') 219 count = 1 220 curl = CurlClient(env=env) 221 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]' 222 r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, 223 extra_args=['--parallel']) 224 r.check_stats(count=count, http_status=200, exitcode=0) 225 exp_data = [f'{os.path.getsize(fdata)}'] 226 r.check_response(count=count, http_status=200) 227 for i in range(count): 228 respdata = open(curl.response_file(i)).readlines() 229 assert respdata == exp_data 230 231 # PUT 10m 232 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 233 def test_07_31_put_10m(self, env: Env, httpd, nghttpx, repeat, proto): 234 if proto == 'h3' and not env.have_h3(): 235 pytest.skip("h3 not supported") 236 if proto == 'h3' and env.curl_uses_lib('msh3'): 237 pytest.skip("msh3 fails here") 238 fdata = os.path.join(env.gen_dir, 'data-10m') 239 count = 1 240 curl = CurlClient(env=env) 241 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]&chunk_delay=2ms' 242 r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, 243 extra_args=['--parallel']) 244 r.check_stats(count=count, http_status=200, exitcode=0) 245 exp_data = [f'{os.path.getsize(fdata)}'] 246 r.check_response(count=count, http_status=200) 247 for i in range(count): 248 respdata = open(curl.response_file(i)).readlines() 249 assert respdata == exp_data 250 251 # issue #10591 252 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 253 def test_07_32_issue_10591(self, env: Env, httpd, nghttpx, repeat, proto): 254 if proto == 'h3' and not env.have_h3(): 255 pytest.skip("h3 not supported") 256 if proto == 'h3' and env.curl_uses_lib('msh3'): 257 pytest.skip("msh3 fails here") 258 fdata = os.path.join(env.gen_dir, 'data-10m') 259 count = 1 260 curl = CurlClient(env=env) 261 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]' 262 r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto) 263 r.check_stats(count=count, http_status=200, exitcode=0) 264 265 # issue #11157, upload that is 404'ed by server, needs to terminate 266 # correctly and not time out on sending 267 def test_07_33_issue_11157a(self, env: Env, httpd, nghttpx, repeat): 268 proto = 'h2' 269 fdata = os.path.join(env.gen_dir, 'data-10m') 270 # send a POST to our PUT handler which will send immediately a 404 back 271 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put' 272 curl = CurlClient(env=env) 273 r = curl.run_direct(with_stats=True, args=[ 274 '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1', 275 '--cacert', env.ca.cert_file, 276 '--request', 'POST', 277 '--max-time', '5', '-v', 278 '--url', url, 279 '--form', 'idList=12345678', 280 '--form', 'pos=top', 281 '--form', 'name=mr_test', 282 '--form', f'fileSource=@{fdata};type=application/pdf', 283 ]) 284 assert r.exit_code == 0, f'{r}' 285 r.check_stats(1, 404) 286 287 # issue #11157, send upload that is slowly read in 288 def test_07_33_issue_11157b(self, env: Env, httpd, nghttpx, repeat): 289 proto = 'h2' 290 fdata = os.path.join(env.gen_dir, 'data-10m') 291 # tell our test PUT handler to read the upload more slowly, so 292 # that the send buffering and transfer loop needs to wait 293 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?chunk_delay=2ms' 294 curl = CurlClient(env=env) 295 r = curl.run_direct(with_stats=True, args=[ 296 '--verbose', '--trace-config', 'ids,time', 297 '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1', 298 '--cacert', env.ca.cert_file, 299 '--request', 'PUT', 300 '--max-time', '10', '-v', 301 '--url', url, 302 '--form', 'idList=12345678', 303 '--form', 'pos=top', 304 '--form', 'name=mr_test', 305 '--form', f'fileSource=@{fdata};type=application/pdf', 306 ]) 307 assert r.exit_code == 0, r.dump_logs() 308 r.check_stats(1, 200) 309 310 def test_07_34_issue_11194(self, env: Env, httpd, nghttpx, repeat): 311 proto = 'h2' 312 # tell our test PUT handler to read the upload more slowly, so 313 # that the send buffering and transfer loop needs to wait 314 fdata = os.path.join(env.gen_dir, 'data-100k') 315 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put' 316 curl = CurlClient(env=env) 317 r = curl.run_direct(with_stats=True, args=[ 318 '--verbose', '--trace-config', 'ids,time', 319 '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1', 320 '--cacert', env.ca.cert_file, 321 '--request', 'PUT', 322 '--digest', '--user', 'test:test', 323 '--data-binary', f'@{fdata}', 324 '--url', url, 325 ]) 326 assert r.exit_code == 0, r.dump_logs() 327 r.check_stats(1, 200) 328 329 # upload large data on a h1 to h2 upgrade 330 def test_07_35_h1_h2_upgrade_upload(self, env: Env, httpd, nghttpx, repeat): 331 fdata = os.path.join(env.gen_dir, 'data-100k') 332 curl = CurlClient(env=env) 333 url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]' 334 r = curl.http_upload(urls=[url], data=f'@{fdata}', extra_args=[ 335 '--http2' 336 ]) 337 r.check_response(count=1, http_status=200) 338 # apache does not Upgrade on request with a body 339 assert r.stats[0]['http_version'] == '1.1', f'{r}' 340 indata = open(fdata).readlines() 341 respdata = open(curl.response_file(0)).readlines() 342 assert respdata == indata 343 344 # upload to a 301,302,303 response 345 @pytest.mark.parametrize("redir", ['301', '302', '303']) 346 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 347 def test_07_36_upload_30x(self, env: Env, httpd, nghttpx, repeat, redir, proto): 348 if proto == 'h3' and not env.have_h3(): 349 pytest.skip("h3 not supported") 350 if proto == 'h3' and env.curl_uses_lib('msh3'): 351 pytest.skip("msh3 fails here") 352 data = '0123456789' * 10 353 curl = CurlClient(env=env) 354 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo{redir}?id=[0-0]' 355 r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[ 356 '-L', '--trace-config', 'http/2,http/3' 357 ]) 358 r.check_response(count=1, http_status=200) 359 respdata = open(curl.response_file(0)).readlines() 360 assert respdata == [] # was transformed to a GET 361 362 # upload to a 307 response 363 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 364 def test_07_37_upload_307(self, env: Env, httpd, nghttpx, repeat, proto): 365 if proto == 'h3' and not env.have_h3(): 366 pytest.skip("h3 not supported") 367 if proto == 'h3' and env.curl_uses_lib('msh3'): 368 pytest.skip("msh3 fails here") 369 data = '0123456789' * 10 370 curl = CurlClient(env=env) 371 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo307?id=[0-0]' 372 r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[ 373 '-L', '--trace-config', 'http/2,http/3' 374 ]) 375 r.check_response(count=1, http_status=200) 376 respdata = open(curl.response_file(0)).readlines() 377 assert respdata == [data] # was POST again 378 379 # POST form data, yet another code path in transfer 380 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 381 def test_07_38_form_small(self, env: Env, httpd, nghttpx, repeat, proto): 382 if proto == 'h3' and not env.have_h3(): 383 pytest.skip("h3 not supported") 384 if proto == 'h3' and env.curl_uses_lib('msh3'): 385 pytest.skip("msh3 fails here") 386 curl = CurlClient(env=env) 387 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' 388 r = curl.http_form(urls=[url], alpn_proto=proto, form={ 389 'name1': 'value1', 390 }) 391 r.check_stats(count=1, http_status=200, exitcode=0) 392 393 # POST data urlencoded, small enough to be sent with request headers 394 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 395 def test_07_39_post_urlenc_small(self, env: Env, httpd, nghttpx, repeat, proto): 396 if proto == 'h3' and not env.have_h3(): 397 pytest.skip("h3 not supported") 398 if proto == 'h3' and env.curl_uses_lib('msh3'): 399 pytest.skip("msh3 fails here") 400 fdata = os.path.join(env.gen_dir, 'data-63k') 401 curl = CurlClient(env=env) 402 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' 403 r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=[ 404 '--trace-config', 'http/2,http/3' 405 ]) 406 r.check_stats(count=1, http_status=200, exitcode=0) 407 indata = open(fdata).readlines() 408 respdata = open(curl.response_file(0)).readlines() 409 assert respdata == indata 410 411 # POST data urlencoded, large enough to be sent separate from request headers 412 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 413 def test_07_40_post_urlenc_large(self, env: Env, httpd, nghttpx, repeat, proto): 414 if proto == 'h3' and not env.have_h3(): 415 pytest.skip("h3 not supported") 416 if proto == 'h3' and env.curl_uses_lib('msh3'): 417 pytest.skip("msh3 fails here") 418 fdata = os.path.join(env.gen_dir, 'data-64k') 419 curl = CurlClient(env=env) 420 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' 421 r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=[ 422 '--trace-config', 'http/2,http/3' 423 ]) 424 r.check_stats(count=1, http_status=200, exitcode=0) 425 indata = open(fdata).readlines() 426 respdata = open(curl.response_file(0)).readlines() 427 assert respdata == indata 428 429 # POST data urlencoded, small enough to be sent with request headers 430 # and request headers are so large that the first send is larger 431 # than our default upload buffer length (64KB). 432 # Unfixed, this will fail when run with CURL_DBG_SOCK_WBLOCK=80 most 433 # of the time 434 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 435 def test_07_41_post_urlenc_small(self, env: Env, httpd, nghttpx, repeat, proto): 436 if proto == 'h3' and not env.have_h3(): 437 pytest.skip("h3 not supported") 438 if proto == 'h3' and env.curl_uses_lib('msh3'): 439 pytest.skip("msh3 fails here") 440 if proto == 'h3' and env.curl_uses_lib('quiche'): 441 pytest.skip("quiche has CWND issues with large requests") 442 fdata = os.path.join(env.gen_dir, 'data-63k') 443 curl = CurlClient(env=env) 444 extra_args = ['--trace-config', 'http/2,http/3'] 445 # add enough headers so that the first send chunk is > 64KB 446 for i in range(63): 447 extra_args.extend(['-H', f'x{i:02d}: {"y"*1019}']) 448 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' 449 r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=extra_args) 450 r.check_stats(count=1, http_status=200, exitcode=0) 451 indata = open(fdata).readlines() 452 respdata = open(curl.response_file(0)).readlines() 453 assert respdata == indata 454 455 def check_download(self, count, srcfile, curl): 456 for i in range(count): 457 dfile = curl.download_file(i) 458 assert os.path.exists(dfile) 459 if not filecmp.cmp(srcfile, dfile, shallow=False): 460 diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), 461 b=open(dfile).readlines(), 462 fromfile=srcfile, 463 tofile=dfile, 464 n=1)) 465 assert False, f'download {dfile} differs:\n{diff}' 466 467 # upload data, pause, let connection die with an incomplete response 468 # issues #11769 #13260 469 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 470 def test_07_42a_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto): 471 if proto == 'h3' and not env.have_h3(): 472 pytest.skip("h3 not supported") 473 if proto == 'h3' and env.curl_uses_lib('msh3'): 474 pytest.skip("msh3 fails here") 475 client = LocalClient(name='upload-pausing', env=env, timeout=60) 476 if not client.exists(): 477 pytest.skip(f'example client not built: {client.name}') 478 url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after=0' 479 r = client.run([url]) 480 r.check_exit_code(18) # PARTIAL_FILE 481 482 # upload data, pause, let connection die without any response at all 483 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 484 def test_07_42b_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto): 485 if proto == 'h3' and not env.have_h3(): 486 pytest.skip("h3 not supported") 487 if proto == 'h3' and env.curl_uses_lib('msh3'): 488 pytest.skip("msh3 fails here") 489 client = LocalClient(name='upload-pausing', env=env, timeout=60) 490 if not client.exists(): 491 pytest.skip(f'example client not built: {client.name}') 492 url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&just_die=1' 493 r = client.run([url]) 494 r.check_exit_code(52) # GOT_NOTHING 495 496 # upload data, pause, let connection die after 100 continue 497 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 498 def test_07_42c_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto): 499 if proto == 'h3' and not env.have_h3(): 500 pytest.skip("h3 not supported") 501 if proto == 'h3' and env.curl_uses_lib('msh3'): 502 pytest.skip("msh3 fails here") 503 client = LocalClient(name='upload-pausing', env=env, timeout=60) 504 if not client.exists(): 505 pytest.skip(f'example client not built: {client.name}') 506 url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after_100=1' 507 r = client.run([url]) 508 r.check_exit_code(52) # GOT_NOTHING 509 510 # speed limited on put handler 511 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 512 def test_07_50_put_speed_limit(self, env: Env, httpd, nghttpx, proto, repeat): 513 if proto == 'h3' and not env.have_h3(): 514 pytest.skip("h3 not supported") 515 count = 1 516 fdata = os.path.join(env.gen_dir, 'data-100k') 517 up_len = 100 * 1024 518 speed_limit = 20 * 1024 519 curl = CurlClient(env=env) 520 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]' 521 r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, 522 with_headers=True, extra_args=[ 523 '--limit-rate', f'{speed_limit}' 524 ]) 525 r.check_response(count=count, http_status=200) 526 assert r.responses[0]['header']['received-length'] == f'{up_len}', f'{r.responses[0]}' 527 up_speed = r.stats[0]['speed_upload'] 528 assert (speed_limit * 0.5) <= up_speed <= (speed_limit * 1.5), f'{r.stats[0]}' 529 530 # speed limited on echo handler 531 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 532 def test_07_51_echo_speed_limit(self, env: Env, httpd, nghttpx, proto, repeat): 533 if proto == 'h3' and not env.have_h3(): 534 pytest.skip("h3 not supported") 535 count = 1 536 fdata = os.path.join(env.gen_dir, 'data-100k') 537 speed_limit = 20 * 1024 538 curl = CurlClient(env=env) 539 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]' 540 r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, 541 with_headers=True, extra_args=[ 542 '--limit-rate', f'{speed_limit}' 543 ]) 544 r.check_response(count=count, http_status=200) 545 up_speed = r.stats[0]['speed_upload'] 546 assert (speed_limit * 0.5) <= up_speed <= (speed_limit * 1.5), f'{r.stats[0]}' 547 548 # upload larger data, triggering "Expect: 100-continue" code paths 549 @pytest.mark.parametrize("proto", ['http/1.1']) 550 def test_07_60_upload_exp100(self, env: Env, httpd, nghttpx, repeat, proto): 551 fdata = os.path.join(env.gen_dir, 'data-1m+') 552 read_delay = 1 553 curl = CurlClient(env=env) 554 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]'\ 555 f'&read_delay={read_delay}s' 556 r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, extra_args=[ 557 '--expect100-timeout', f'{read_delay+1}' 558 ]) 559 r.check_stats(count=1, http_status=200, exitcode=0) 560 561 # upload larger data, triggering "Expect: 100-continue" code paths 562 @pytest.mark.parametrize("proto", ['http/1.1']) 563 def test_07_61_upload_exp100_timeout(self, env: Env, httpd, nghttpx, repeat, proto): 564 fdata = os.path.join(env.gen_dir, 'data-1m+') 565 read_delay = 2 566 curl = CurlClient(env=env) 567 url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]'\ 568 f'&read_delay={read_delay}s' 569 r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, extra_args=[ 570 '--expect100-timeout', f'{read_delay-1}' 571 ]) 572 r.check_stats(count=1, http_status=200, exitcode=0) 573