PageRenderTime 46ms CodeModel.GetById 6ms RepoModel.GetById 0ms app.codeStats 1ms

/test/webrick/test_httpproxy.rb

http://github.com/ruby/ruby
Ruby | 466 lines | 392 code | 29 blank | 45 comment | 10 complexity | 42c350773f97acaa56297edb01850f2b MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, AGPL-3.0
  1. # frozen_string_literal: false
  2. require "test/unit"
  3. require "net/http"
  4. require "webrick"
  5. require "webrick/httpproxy"
  6. begin
  7. require "webrick/ssl"
  8. require "net/https"
  9. rescue LoadError
  10. # test_connect will be skipped
  11. end
  12. require File.expand_path("utils.rb", File.dirname(__FILE__))
  13. class TestWEBrickHTTPProxy < Test::Unit::TestCase
  14. def teardown
  15. WEBrick::Utils::TimeoutHandler.terminate
  16. super
  17. end
  18. def test_fake_proxy
  19. assert_nil(WEBrick::FakeProxyURI.scheme)
  20. assert_nil(WEBrick::FakeProxyURI.host)
  21. assert_nil(WEBrick::FakeProxyURI.port)
  22. assert_nil(WEBrick::FakeProxyURI.path)
  23. assert_nil(WEBrick::FakeProxyURI.userinfo)
  24. assert_raise(NoMethodError){ WEBrick::FakeProxyURI.foo }
  25. end
  26. def test_proxy
  27. # Testing GET or POST to the proxy server
  28. # Note that the proxy server works as the origin server.
  29. # +------+
  30. # V |
  31. # client -------> proxy ---+
  32. # GET / POST GET / POST
  33. #
  34. proxy_handler_called = request_handler_called = 0
  35. config = {
  36. :ServerName => "localhost.localdomain",
  37. :ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1 },
  38. :RequestCallback => Proc.new{|req, res| request_handler_called += 1 }
  39. }
  40. TestWEBrick.start_httpproxy(config){|server, addr, port, log|
  41. server.mount_proc("/"){|req, res|
  42. res.body = "#{req.request_method} #{req.path} #{req.body}"
  43. }
  44. http = Net::HTTP.new(addr, port, addr, port)
  45. req = Net::HTTP::Get.new("/")
  46. http.request(req){|res|
  47. assert_equal("1.1 localhost.localdomain:#{port}", res["via"], log.call)
  48. assert_equal("GET / ", res.body, log.call)
  49. }
  50. assert_equal(1, proxy_handler_called, log.call)
  51. assert_equal(2, request_handler_called, log.call)
  52. req = Net::HTTP::Head.new("/")
  53. http.request(req){|res|
  54. assert_equal("1.1 localhost.localdomain:#{port}", res["via"], log.call)
  55. assert_nil(res.body, log.call)
  56. }
  57. assert_equal(2, proxy_handler_called, log.call)
  58. assert_equal(4, request_handler_called, log.call)
  59. req = Net::HTTP::Post.new("/")
  60. req.body = "post-data"
  61. req.content_type = "application/x-www-form-urlencoded"
  62. http.request(req){|res|
  63. assert_equal("1.1 localhost.localdomain:#{port}", res["via"], log.call)
  64. assert_equal("POST / post-data", res.body, log.call)
  65. }
  66. assert_equal(3, proxy_handler_called, log.call)
  67. assert_equal(6, request_handler_called, log.call)
  68. }
  69. end
  70. def test_no_proxy
  71. # Testing GET or POST to the proxy server without proxy request.
  72. #
  73. # client -------> proxy
  74. # GET / POST
  75. #
  76. proxy_handler_called = request_handler_called = 0
  77. config = {
  78. :ServerName => "localhost.localdomain",
  79. :ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1 },
  80. :RequestCallback => Proc.new{|req, res| request_handler_called += 1 }
  81. }
  82. TestWEBrick.start_httpproxy(config){|server, addr, port, log|
  83. server.mount_proc("/"){|req, res|
  84. res.body = "#{req.request_method} #{req.path} #{req.body}"
  85. }
  86. http = Net::HTTP.new(addr, port)
  87. req = Net::HTTP::Get.new("/")
  88. http.request(req){|res|
  89. assert_nil(res["via"], log.call)
  90. assert_equal("GET / ", res.body, log.call)
  91. }
  92. assert_equal(0, proxy_handler_called, log.call)
  93. assert_equal(1, request_handler_called, log.call)
  94. req = Net::HTTP::Head.new("/")
  95. http.request(req){|res|
  96. assert_nil(res["via"], log.call)
  97. assert_nil(res.body, log.call)
  98. }
  99. assert_equal(0, proxy_handler_called, log.call)
  100. assert_equal(2, request_handler_called, log.call)
  101. req = Net::HTTP::Post.new("/")
  102. req.content_type = "application/x-www-form-urlencoded"
  103. req.body = "post-data"
  104. http.request(req){|res|
  105. assert_nil(res["via"], log.call)
  106. assert_equal("POST / post-data", res.body, log.call)
  107. }
  108. assert_equal(0, proxy_handler_called, log.call)
  109. assert_equal(3, request_handler_called, log.call)
  110. }
  111. end
  112. def test_big_bodies
  113. require 'digest/md5'
  114. rand_str = File.read(__FILE__)
  115. rand_str.freeze
  116. nr = 1024 ** 2 / rand_str.size # bigger works, too
  117. exp = Digest::MD5.new
  118. nr.times { exp.update(rand_str) }
  119. exp = exp.hexdigest
  120. TestWEBrick.start_httpserver do |o_server, o_addr, o_port, o_log|
  121. o_server.mount_proc('/') do |req, res|
  122. case req.request_method
  123. when 'GET'
  124. res['content-type'] = 'application/octet-stream'
  125. if req.path == '/length'
  126. res['content-length'] = (nr * rand_str.size).to_s
  127. else
  128. res.chunked = true
  129. end
  130. res.body = ->(socket) { nr.times { socket.write(rand_str) } }
  131. when 'POST'
  132. dig = Digest::MD5.new
  133. req.body { |buf| dig.update(buf); buf.clear }
  134. res['content-type'] = 'text/plain'
  135. res['content-length'] = '32'
  136. res.body = dig.hexdigest
  137. end
  138. end
  139. http = Net::HTTP.new(o_addr, o_port)
  140. IO.pipe do |rd, wr|
  141. headers = {
  142. 'Content-Type' => 'application/octet-stream',
  143. 'Transfer-Encoding' => 'chunked',
  144. }
  145. post = Net::HTTP::Post.new('/', headers)
  146. th = Thread.new { nr.times { wr.write(rand_str) }; wr.close }
  147. post.body_stream = rd
  148. http.request(post) do |res|
  149. assert_equal 'text/plain', res['content-type']
  150. assert_equal 32, res.content_length
  151. assert_equal exp, res.body
  152. end
  153. assert_nil th.value
  154. end
  155. TestWEBrick.start_httpproxy do |p_server, p_addr, p_port, p_log|
  156. http = Net::HTTP.new(o_addr, o_port, p_addr, p_port)
  157. http.request_get('/length') do |res|
  158. assert_equal(nr * rand_str.size, res.content_length)
  159. dig = Digest::MD5.new
  160. res.read_body { |buf| dig.update(buf); buf.clear }
  161. assert_equal exp, dig.hexdigest
  162. end
  163. http.request_get('/') do |res|
  164. assert_predicate res, :chunked?
  165. dig = Digest::MD5.new
  166. res.read_body { |buf| dig.update(buf); buf.clear }
  167. assert_equal exp, dig.hexdigest
  168. end
  169. IO.pipe do |rd, wr|
  170. headers = {
  171. 'Content-Type' => 'application/octet-stream',
  172. 'Content-Length' => (nr * rand_str.size).to_s,
  173. }
  174. post = Net::HTTP::Post.new('/', headers)
  175. th = Thread.new { nr.times { wr.write(rand_str) }; wr.close }
  176. post.body_stream = rd
  177. http.request(post) do |res|
  178. assert_equal 'text/plain', res['content-type']
  179. assert_equal 32, res.content_length
  180. assert_equal exp, res.body
  181. end
  182. assert_nil th.value
  183. end
  184. IO.pipe do |rd, wr|
  185. headers = {
  186. 'Content-Type' => 'application/octet-stream',
  187. 'Transfer-Encoding' => 'chunked',
  188. }
  189. post = Net::HTTP::Post.new('/', headers)
  190. th = Thread.new { nr.times { wr.write(rand_str) }; wr.close }
  191. post.body_stream = rd
  192. http.request(post) do |res|
  193. assert_equal 'text/plain', res['content-type']
  194. assert_equal 32, res.content_length
  195. assert_equal exp, res.body
  196. end
  197. assert_nil th.value
  198. end
  199. end
  200. end
  201. end
  202. def test_http10_proxy_chunked
  203. # Testing HTTP/1.0 client request and HTTP/1.1 chunked response
  204. # from origin server.
  205. # +------+
  206. # V |
  207. # client -------> proxy ---+
  208. # GET GET
  209. # HTTP/1.0 HTTP/1.1
  210. # non-chunked chunked
  211. #
  212. proxy_handler_called = request_handler_called = 0
  213. config = {
  214. :ServerName => "localhost.localdomain",
  215. :ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1 },
  216. :RequestCallback => Proc.new{|req, res| request_handler_called += 1 }
  217. }
  218. log_tester = lambda {|log, access_log|
  219. log.reject! {|str|
  220. %r{WARN chunked is set for an HTTP/1\.0 request\. \(ignored\)} =~ str
  221. }
  222. assert_equal([], log)
  223. }
  224. TestWEBrick.start_httpproxy(config, log_tester){|server, addr, port, log|
  225. body = nil
  226. server.mount_proc("/"){|req, res|
  227. body = "#{req.request_method} #{req.path} #{req.body}"
  228. res.chunked = true
  229. res.body = -> (socket) { body.each_char {|c| socket.write c } }
  230. }
  231. # Don't use Net::HTTP because it uses HTTP/1.1.
  232. TCPSocket.open(addr, port) {|s|
  233. s.write "GET / HTTP/1.0\r\nHost: localhost.localdomain\r\n\r\n"
  234. response = s.read
  235. assert_equal(body, response[/.*\z/])
  236. }
  237. }
  238. end
  239. def make_certificate(key, cn)
  240. subject = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=#{cn}")
  241. exts = [
  242. ["keyUsage", "keyEncipherment,digitalSignature", true],
  243. ]
  244. cert = OpenSSL::X509::Certificate.new
  245. cert.version = 2
  246. cert.serial = 1
  247. cert.subject = subject
  248. cert.issuer = subject
  249. cert.public_key = key
  250. cert.not_before = Time.now - 3600
  251. cert.not_after = Time.now + 3600
  252. ef = OpenSSL::X509::ExtensionFactory.new(cert, cert)
  253. exts.each {|args| cert.add_extension(ef.create_extension(*args)) }
  254. cert.sign(key, "sha256")
  255. return cert
  256. end if defined?(OpenSSL::SSL)
  257. def test_connect
  258. # Testing CONNECT to proxy server
  259. #
  260. # client -----------> proxy -----------> https
  261. # 1. CONNECT establish TCP
  262. # 2. ---- establish SSL session --->
  263. # 3. ------- GET or POST ---------->
  264. #
  265. key = TEST_KEY_RSA2048
  266. cert = make_certificate(key, "127.0.0.1")
  267. s_config = {
  268. :SSLEnable =>true,
  269. :ServerName => "localhost",
  270. :SSLCertificate => cert,
  271. :SSLPrivateKey => key,
  272. }
  273. config = {
  274. :ServerName => "localhost.localdomain",
  275. :RequestCallback => Proc.new{|req, res|
  276. assert_equal("CONNECT", req.request_method)
  277. },
  278. }
  279. TestWEBrick.start_httpserver(s_config){|s_server, s_addr, s_port, s_log|
  280. s_server.mount_proc("/"){|req, res|
  281. res.body = "SSL #{req.request_method} #{req.path} #{req.body}"
  282. }
  283. TestWEBrick.start_httpproxy(config){|server, addr, port, log|
  284. http = Net::HTTP.new("127.0.0.1", s_port, addr, port)
  285. http.use_ssl = true
  286. http.verify_callback = Proc.new do |preverify_ok, store_ctx|
  287. store_ctx.current_cert.to_der == cert.to_der
  288. end
  289. req = Net::HTTP::Get.new("/")
  290. req["Content-Type"] = "application/x-www-form-urlencoded"
  291. http.request(req){|res|
  292. assert_equal("SSL GET / ", res.body, s_log.call + log.call)
  293. }
  294. req = Net::HTTP::Post.new("/")
  295. req["Content-Type"] = "application/x-www-form-urlencoded"
  296. req.body = "post-data"
  297. http.request(req){|res|
  298. assert_equal("SSL POST / post-data", res.body, s_log.call + log.call)
  299. }
  300. }
  301. }
  302. end if defined?(OpenSSL::SSL)
  303. def test_upstream_proxy
  304. # Testing GET or POST through the upstream proxy server
  305. # Note that the upstream proxy server works as the origin server.
  306. # +------+
  307. # V |
  308. # client -------> proxy -------> proxy ---+
  309. # GET / POST GET / POST GET / POST
  310. #
  311. up_proxy_handler_called = up_request_handler_called = 0
  312. proxy_handler_called = request_handler_called = 0
  313. up_config = {
  314. :ServerName => "localhost.localdomain",
  315. :ProxyContentHandler => Proc.new{|req, res| up_proxy_handler_called += 1},
  316. :RequestCallback => Proc.new{|req, res| up_request_handler_called += 1}
  317. }
  318. TestWEBrick.start_httpproxy(up_config){|up_server, up_addr, up_port, up_log|
  319. up_server.mount_proc("/"){|req, res|
  320. res.body = "#{req.request_method} #{req.path} #{req.body}"
  321. }
  322. config = {
  323. :ServerName => "localhost.localdomain",
  324. :ProxyURI => URI.parse("http://localhost:#{up_port}"),
  325. :ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1},
  326. :RequestCallback => Proc.new{|req, res| request_handler_called += 1},
  327. }
  328. TestWEBrick.start_httpproxy(config){|server, addr, port, log|
  329. http = Net::HTTP.new(up_addr, up_port, addr, port)
  330. req = Net::HTTP::Get.new("/")
  331. http.request(req){|res|
  332. skip res.message unless res.code == '200'
  333. via = res["via"].split(/,\s+/)
  334. assert(via.include?("1.1 localhost.localdomain:#{up_port}"), up_log.call + log.call)
  335. assert(via.include?("1.1 localhost.localdomain:#{port}"), up_log.call + log.call)
  336. assert_equal("GET / ", res.body)
  337. }
  338. assert_equal(1, up_proxy_handler_called, up_log.call + log.call)
  339. assert_equal(2, up_request_handler_called, up_log.call + log.call)
  340. assert_equal(1, proxy_handler_called, up_log.call + log.call)
  341. assert_equal(1, request_handler_called, up_log.call + log.call)
  342. req = Net::HTTP::Head.new("/")
  343. http.request(req){|res|
  344. via = res["via"].split(/,\s+/)
  345. assert(via.include?("1.1 localhost.localdomain:#{up_port}"), up_log.call + log.call)
  346. assert(via.include?("1.1 localhost.localdomain:#{port}"), up_log.call + log.call)
  347. assert_nil(res.body, up_log.call + log.call)
  348. }
  349. assert_equal(2, up_proxy_handler_called, up_log.call + log.call)
  350. assert_equal(4, up_request_handler_called, up_log.call + log.call)
  351. assert_equal(2, proxy_handler_called, up_log.call + log.call)
  352. assert_equal(2, request_handler_called, up_log.call + log.call)
  353. req = Net::HTTP::Post.new("/")
  354. req.body = "post-data"
  355. req.content_type = "application/x-www-form-urlencoded"
  356. http.request(req){|res|
  357. via = res["via"].split(/,\s+/)
  358. assert(via.include?("1.1 localhost.localdomain:#{up_port}"), up_log.call + log.call)
  359. assert(via.include?("1.1 localhost.localdomain:#{port}"), up_log.call + log.call)
  360. assert_equal("POST / post-data", res.body, up_log.call + log.call)
  361. }
  362. assert_equal(3, up_proxy_handler_called, up_log.call + log.call)
  363. assert_equal(6, up_request_handler_called, up_log.call + log.call)
  364. assert_equal(3, proxy_handler_called, up_log.call + log.call)
  365. assert_equal(3, request_handler_called, up_log.call + log.call)
  366. if defined?(OpenSSL::SSL)
  367. # Testing CONNECT to the upstream proxy server
  368. #
  369. # client -------> proxy -------> proxy -------> https
  370. # 1. CONNECT CONNECT establish TCP
  371. # 2. -------- establish SSL session ------>
  372. # 3. ---------- GET or POST -------------->
  373. #
  374. key = TEST_KEY_RSA2048
  375. cert = make_certificate(key, "127.0.0.1")
  376. s_config = {
  377. :SSLEnable =>true,
  378. :ServerName => "localhost",
  379. :SSLCertificate => cert,
  380. :SSLPrivateKey => key,
  381. }
  382. TestWEBrick.start_httpserver(s_config){|s_server, s_addr, s_port, s_log|
  383. s_server.mount_proc("/"){|req2, res|
  384. res.body = "SSL #{req2.request_method} #{req2.path} #{req2.body}"
  385. }
  386. http = Net::HTTP.new("127.0.0.1", s_port, addr, port, up_log.call + log.call + s_log.call)
  387. http.use_ssl = true
  388. http.verify_callback = Proc.new do |preverify_ok, store_ctx|
  389. store_ctx.current_cert.to_der == cert.to_der
  390. end
  391. req2 = Net::HTTP::Get.new("/")
  392. http.request(req2){|res|
  393. assert_equal("SSL GET / ", res.body, up_log.call + log.call + s_log.call)
  394. }
  395. req2 = Net::HTTP::Post.new("/")
  396. req2.body = "post-data"
  397. req2.content_type = "application/x-www-form-urlencoded"
  398. http.request(req2){|res|
  399. assert_equal("SSL POST / post-data", res.body, up_log.call + log.call + s_log.call)
  400. }
  401. }
  402. end
  403. }
  404. }
  405. end
  406. if defined?(OpenSSL::SSL)
  407. TEST_KEY_RSA2048 = OpenSSL::PKey.read <<-_end_of_pem_
  408. -----BEGIN RSA PRIVATE KEY-----
  409. MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN
  410. s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign
  411. 4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D
  412. kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl
  413. NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J
  414. DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb
  415. I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq
  416. PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V
  417. seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0
  418. Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc
  419. VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW
  420. wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G
  421. 0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj
  422. XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb
  423. aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n
  424. h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw
  425. Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k
  426. IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb
  427. v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId
  428. U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr
  429. vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS
  430. Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC
  431. 9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41
  432. gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG
  433. 4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw==
  434. -----END RSA PRIVATE KEY-----
  435. _end_of_pem_
  436. end
  437. end