test_10_proxy.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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. #
  27. import filecmp
  28. import logging
  29. import os
  30. import re
  31. import pytest
  32. from testenv import Env, CurlClient, ExecResult
  33. log = logging.getLogger(__name__)
  34. class TestProxy:
  35. @pytest.fixture(autouse=True, scope='class')
  36. def _class_scope(self, env, httpd, nghttpx_fwd):
  37. push_dir = os.path.join(httpd.docs_dir, 'push')
  38. if not os.path.exists(push_dir):
  39. os.makedirs(push_dir)
  40. if env.have_nghttpx():
  41. nghttpx_fwd.start_if_needed()
  42. env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100*1024)
  43. env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024)
  44. httpd.clear_extra_configs()
  45. httpd.reload()
  46. def get_tunnel_proto_used(self, r: ExecResult):
  47. for l in r.trace_lines:
  48. m = re.match(r'.* CONNECT tunnel: (\S+) negotiated$', l)
  49. if m:
  50. return m.group(1)
  51. assert False, f'tunnel protocol not found in:\n{"".join(r.trace_lines)}'
  52. return None
  53. # download via http: proxy (no tunnel)
  54. def test_10_01_proxy_http(self, env: Env, httpd, repeat):
  55. curl = CurlClient(env=env)
  56. url = f'http://localhost:{env.http_port}/data.json'
  57. r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  58. extra_args=curl.get_proxy_args(proxys=False))
  59. r.check_response(count=1, http_status=200)
  60. # download via https: proxy (no tunnel)
  61. @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
  62. reason='curl lacks HTTPS-proxy support')
  63. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  64. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  65. def test_10_02_proxys_down(self, env: Env, httpd, nghttpx_fwd, proto, repeat):
  66. if proto == 'h2' and not env.curl_uses_lib('nghttp2'):
  67. pytest.skip('only supported with nghttp2')
  68. curl = CurlClient(env=env)
  69. url = f'http://localhost:{env.http_port}/data.json'
  70. xargs = curl.get_proxy_args(proto=proto)
  71. r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  72. extra_args=xargs)
  73. r.check_response(count=1, http_status=200,
  74. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  75. # upload via https: with proto (no tunnel)
  76. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  77. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  78. @pytest.mark.parametrize("fname, fcount", [
  79. ['data.json', 5],
  80. ['data-100k', 5],
  81. ['data-1m', 2]
  82. ])
  83. @pytest.mark.skipif(condition=not Env.have_nghttpx(),
  84. reason="no nghttpx available")
  85. def test_10_02_proxys_up(self, env: Env, httpd, nghttpx, proto,
  86. fname, fcount, repeat):
  87. if proto == 'h2' and not env.curl_uses_lib('nghttp2'):
  88. pytest.skip('only supported with nghttp2')
  89. count = fcount
  90. srcfile = os.path.join(httpd.docs_dir, fname)
  91. curl = CurlClient(env=env)
  92. url = f'http://localhost:{env.http_port}/curltest/echo?id=[0-{count-1}]'
  93. xargs = curl.get_proxy_args(proto=proto)
  94. r = curl.http_upload(urls=[url], data=f'@{srcfile}', alpn_proto=proto,
  95. extra_args=xargs)
  96. r.check_response(count=count, http_status=200,
  97. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  98. indata = open(srcfile).readlines()
  99. for i in range(count):
  100. respdata = open(curl.response_file(i)).readlines()
  101. assert respdata == indata
  102. # download http: via http: proxytunnel
  103. def test_10_03_proxytunnel_http(self, env: Env, httpd, repeat):
  104. curl = CurlClient(env=env)
  105. url = f'http://localhost:{env.http_port}/data.json'
  106. xargs = curl.get_proxy_args(proxys=False, tunnel=True)
  107. r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  108. extra_args=xargs)
  109. r.check_response(count=1, http_status=200)
  110. # download http: via https: proxytunnel
  111. @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
  112. reason='curl lacks HTTPS-proxy support')
  113. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  114. def test_10_04_proxy_https(self, env: Env, httpd, nghttpx_fwd, repeat):
  115. curl = CurlClient(env=env)
  116. url = f'http://localhost:{env.http_port}/data.json'
  117. xargs = curl.get_proxy_args(tunnel=True)
  118. r = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
  119. extra_args=xargs)
  120. r.check_response(count=1, http_status=200)
  121. # download https: with proto via http: proxytunnel
  122. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  123. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  124. def test_10_05_proxytunnel_http(self, env: Env, httpd, proto, repeat):
  125. curl = CurlClient(env=env)
  126. url = f'https://localhost:{env.https_port}/data.json'
  127. xargs = curl.get_proxy_args(proxys=False, tunnel=True)
  128. r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
  129. with_headers=True,
  130. extra_args=xargs)
  131. r.check_response(count=1, http_status=200,
  132. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  133. # download https: with proto via https: proxytunnel
  134. @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
  135. reason='curl lacks HTTPS-proxy support')
  136. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  137. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  138. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  139. def test_10_06_proxytunnel_https(self, env: Env, httpd, nghttpx_fwd, proto, tunnel, repeat):
  140. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  141. pytest.skip('only supported with nghttp2')
  142. curl = CurlClient(env=env)
  143. url = f'https://localhost:{env.https_port}/data.json?[0-0]'
  144. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  145. r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
  146. with_headers=True, extra_args=xargs)
  147. r.check_response(count=1, http_status=200,
  148. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  149. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  150. if tunnel == 'h2' else 'HTTP/1.1'
  151. srcfile = os.path.join(httpd.docs_dir, 'data.json')
  152. dfile = curl.download_file(0)
  153. assert filecmp.cmp(srcfile, dfile, shallow=False)
  154. # download many https: with proto via https: proxytunnel
  155. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  156. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  157. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  158. @pytest.mark.parametrize("fname, fcount", [
  159. ['data.json', 100],
  160. ['data-100k', 20],
  161. ['data-1m', 5]
  162. ])
  163. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  164. def test_10_07_pts_down_small(self, env: Env, httpd, nghttpx_fwd, proto,
  165. tunnel, fname, fcount, repeat):
  166. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  167. pytest.skip('only supported with nghttp2')
  168. count = fcount
  169. curl = CurlClient(env=env)
  170. url = f'https://localhost:{env.https_port}/{fname}?[0-{count-1}]'
  171. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  172. r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
  173. with_headers=True, extra_args=xargs)
  174. r.check_response(count=count, http_status=200,
  175. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  176. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  177. if tunnel == 'h2' else 'HTTP/1.1'
  178. srcfile = os.path.join(httpd.docs_dir, fname)
  179. for i in range(count):
  180. dfile = curl.download_file(i)
  181. assert filecmp.cmp(srcfile, dfile, shallow=False)
  182. assert r.total_connects == 1, r.dump_logs()
  183. # upload many https: with proto via https: proxytunnel
  184. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  185. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  186. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  187. @pytest.mark.parametrize("fname, fcount", [
  188. ['data.json', 50],
  189. ['data-100k', 20],
  190. ['data-1m', 5]
  191. ])
  192. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  193. def test_10_08_upload_seq_large(self, env: Env, httpd, nghttpx, proto,
  194. tunnel, fname, fcount, repeat):
  195. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  196. pytest.skip('only supported with nghttp2')
  197. count = fcount
  198. srcfile = os.path.join(httpd.docs_dir, fname)
  199. curl = CurlClient(env=env)
  200. url = f'https://localhost:{env.https_port}/curltest/echo?id=[0-{count-1}]'
  201. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  202. r = curl.http_upload(urls=[url], data=f'@{srcfile}', alpn_proto=proto,
  203. extra_args=xargs)
  204. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  205. if tunnel == 'h2' else 'HTTP/1.1'
  206. r.check_response(count=count, http_status=200)
  207. indata = open(srcfile).readlines()
  208. for i in range(count):
  209. respdata = open(curl.response_file(i)).readlines()
  210. assert respdata == indata
  211. assert r.total_connects == 1, r.dump_logs()
  212. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  213. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  214. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  215. def test_10_09_reuse_ser(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat):
  216. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  217. pytest.skip('only supported with nghttp2')
  218. curl = CurlClient(env=env)
  219. url1 = f'https://localhost:{env.https_port}/data.json'
  220. url2 = f'http://localhost:{env.http_port}/data.json'
  221. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  222. r = curl.http_download(urls=[url1, url2], alpn_proto='http/1.1', with_stats=True,
  223. with_headers=True, extra_args=xargs)
  224. r.check_response(count=2, http_status=200)
  225. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  226. if tunnel == 'h2' else 'HTTP/1.1'
  227. if tunnel == 'h2':
  228. # TODO: we would like to reuse the first connection for the
  229. # second URL, but this is currently not possible
  230. # assert r.total_connects == 1
  231. assert r.total_connects == 2
  232. else:
  233. assert r.total_connects == 2