test_10_proxy.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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. extra_args=xargs)
  130. r.check_response(count=1, http_status=200,
  131. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  132. # download https: with proto via https: proxytunnel
  133. @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
  134. reason='curl lacks HTTPS-proxy support')
  135. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  136. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  137. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  138. def test_10_06_proxytunnel_https(self, env: Env, httpd, nghttpx_fwd, proto, tunnel, repeat):
  139. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  140. pytest.skip('only supported with nghttp2')
  141. curl = CurlClient(env=env)
  142. url = f'https://localhost:{env.https_port}/data.json?[0-0]'
  143. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  144. r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
  145. extra_args=xargs)
  146. r.check_response(count=1, http_status=200,
  147. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  148. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  149. if tunnel == 'h2' else 'HTTP/1.1'
  150. srcfile = os.path.join(httpd.docs_dir, 'data.json')
  151. dfile = curl.download_file(0)
  152. assert filecmp.cmp(srcfile, dfile, shallow=False)
  153. # download many https: with proto via https: proxytunnel
  154. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  155. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  156. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  157. @pytest.mark.parametrize("fname, fcount", [
  158. ['data.json', 100],
  159. ['data-100k', 20],
  160. ['data-1m', 5]
  161. ])
  162. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  163. def test_10_07_pts_down_small(self, env: Env, httpd, nghttpx_fwd, proto,
  164. tunnel, fname, fcount, repeat):
  165. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  166. pytest.skip('only supported with nghttp2')
  167. count = fcount
  168. curl = CurlClient(env=env)
  169. url = f'https://localhost:{env.https_port}/{fname}?[0-{count-1}]'
  170. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  171. r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
  172. extra_args=xargs)
  173. r.check_response(count=count, http_status=200,
  174. protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
  175. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  176. if tunnel == 'h2' else 'HTTP/1.1'
  177. srcfile = os.path.join(httpd.docs_dir, fname)
  178. for i in range(count):
  179. dfile = curl.download_file(i)
  180. assert filecmp.cmp(srcfile, dfile, shallow=False)
  181. assert r.total_connects == 1, r.dump_logs()
  182. # upload many https: with proto via https: proxytunnel
  183. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  184. @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
  185. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  186. @pytest.mark.parametrize("fname, fcount", [
  187. ['data.json', 50],
  188. ['data-100k', 20],
  189. ['data-1m', 5]
  190. ])
  191. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  192. def test_10_08_upload_seq_large(self, env: Env, httpd, nghttpx, proto,
  193. tunnel, fname, fcount, repeat):
  194. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  195. pytest.skip('only supported with nghttp2')
  196. count = fcount
  197. srcfile = os.path.join(httpd.docs_dir, fname)
  198. curl = CurlClient(env=env)
  199. url = f'https://localhost:{env.https_port}/curltest/echo?id=[0-{count-1}]'
  200. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  201. r = curl.http_upload(urls=[url], data=f'@{srcfile}', alpn_proto=proto,
  202. extra_args=xargs)
  203. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  204. if tunnel == 'h2' else 'HTTP/1.1'
  205. r.check_response(count=count, http_status=200)
  206. indata = open(srcfile).readlines()
  207. for i in range(count):
  208. respdata = open(curl.response_file(i)).readlines()
  209. assert respdata == indata
  210. assert r.total_connects == 1, r.dump_logs()
  211. @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
  212. @pytest.mark.parametrize("tunnel", ['http/1.1', 'h2'])
  213. @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
  214. def test_10_09_reuse_ser(self, env: Env, httpd, nghttpx_fwd, tunnel, repeat):
  215. if tunnel == 'h2' and not env.curl_uses_lib('nghttp2'):
  216. pytest.skip('only supported with nghttp2')
  217. curl = CurlClient(env=env)
  218. url1 = f'https://localhost:{env.https_port}/data.json'
  219. url2 = f'http://localhost:{env.http_port}/data.json'
  220. xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
  221. r = curl.http_download(urls=[url1, url2], alpn_proto='http/1.1', with_stats=True,
  222. extra_args=xargs)
  223. r.check_response(count=2, http_status=200)
  224. assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
  225. if tunnel == 'h2' else 'HTTP/1.1'
  226. if tunnel == 'h2':
  227. # TODO: we would like to reuse the first connection for the
  228. # second URL, but this is currently not possible
  229. # assert r.total_connects == 1
  230. assert r.total_connects == 2
  231. else:
  232. assert r.total_connects == 2