httpray 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 46b15f44920b575fdc88fd97c64ddb14d670539a
4
- data.tar.gz: 84676027d29c606d4fba8b9e28c32408899621f8
3
+ metadata.gz: 9797174918b5b0dc9fc4ed93712eb645aea8b623
4
+ data.tar.gz: e45eb180606696eed1a43c08de52aac61ce6f4bd
5
5
  SHA512:
6
- metadata.gz: 9b9b2ec5dea26db03552cefbc7b8449f88534f63ff13d2b734656fbb1403cb5819264326d7b7c117a2d993148bb8517bd370eb2c652ce5b653954e176ef8e438
7
- data.tar.gz: 84d44dfb708a8c4c3c05b46dc74ec0cca0bab673c64c17d021b41c66f0da8de07356dd4a85fa623f635a00c3c63bd80778d8f558bd137384903266f0b8222055
6
+ metadata.gz: 82e916c0ef86af848840745586b7d2083776cf060559a98e3b4acb883f40417d74e43ec72a12d6e2eccf71f13ded205cbc03f5607f9c5e598f6049398a002f29
7
+ data.tar.gz: 0d41608e36c89f91999c1e5f252173e0aaeb555f6c0bc5a4805b0cf2b170d2ff28233f4e25b0cb642e2ea449d213677c02719956de35b917a8c40191d04c275f
@@ -1,3 +1,3 @@
1
1
  module HTTPray
2
- VERSION = "1.0.4".freeze
2
+ VERSION = "1.1.0".freeze
3
3
  end
data/lib/httpray.rb CHANGED
@@ -12,57 +12,96 @@ module HTTPray
12
12
  "Accept" => "*/*"
13
13
  }.freeze
14
14
 
15
- def self.request2!(method, uri, headers = {}, body = nil, timeout = 1, ssl_context = nil)
16
- uri = URI.parse(uri) unless URI === uri
17
- address = Socket.getaddrinfo(uri.host, nil, Socket::AF_INET).first[3]
18
- socket_address = Socket.pack_sockaddr_in(uri.port, address)
19
-
20
- socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
21
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
22
-
23
- expire_time = Time.now + timeout
24
- begin
25
- raise Timeout if Time.now > expire_time
26
- socket.connect_nonblock(socket_address)
27
- rescue IO::WaitReadable, IO::WaitWritable
28
- select_timeout = expire_time - Time.now
29
- select_timeout = 0 if select_timeout < 0
30
- IO.select([socket], [socket], [socket], select_timeout)
31
- retry
15
+ class Connection
16
+ def initialize(host, port, timeout = 1, ssl_context = nil)
17
+ @host = host
18
+ @port = port
19
+ @timeout = timeout
20
+ @ssl_context = ssl_context
21
+ @socket = connect
32
22
  end
33
23
 
34
- original_socket = socket
35
- if uri.scheme == "https"
36
- ssl_context ||= OpenSSL::SSL::SSLContext.new
37
- socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
38
- socket.hostname = uri.host
39
- socket.sync_close = true
24
+ # public
25
+
26
+ def socket
27
+ @socket
28
+ end
29
+
30
+ def request!(method, uri, headers = {}, body = nil)
31
+ begin
32
+ IO.select([@socket], [@socket], [@socket], @timeout) if @socket
33
+ rescue; end
34
+ @socket = connect unless @socket && !@socket.closed?
35
+ socket = @socket
36
+ uri = URI.parse(uri) unless URI === uri
37
+
38
+ headers = DEFAULT_HEADERS.merge(headers).merge("Host" => uri.host)
39
+ headers["Content-Length"] = body.bytesize if body
40
+
41
+ socket.write_nonblock "#{method} #{uri.request_uri} HTTP/1.0\r\n"
42
+ headers.each do |header, value|
43
+ socket.write_nonblock "#{header}: #{value}\r\n"
44
+ end
45
+ socket.write_nonblock "\r\n"
46
+ socket.write_nonblock body if body
47
+ socket
48
+ end
49
+
50
+ def request(*args)
51
+ socket = request!(*args)
52
+ yield(socket) if block_given?
53
+ end
54
+
55
+ # private
56
+
57
+ def connect2
58
+ address = Socket.getaddrinfo(@host, nil, Socket::AF_INET).first[3]
59
+ socket_address = Socket.pack_sockaddr_in(@port, address)
60
+
61
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
62
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
63
+
64
+ expire_time = Time.now + @timeout
40
65
  begin
41
66
  raise Timeout if Time.now > expire_time
42
- socket.connect_nonblock
67
+ socket.connect_nonblock(socket_address)
43
68
  rescue IO::WaitReadable, IO::WaitWritable
44
69
  select_timeout = expire_time - Time.now
45
70
  select_timeout = 0 if select_timeout < 0
46
- IO.select([socket.io], [socket.io], [socket.io], select_timeout)
71
+ IO.select([socket], [socket], [socket], select_timeout)
47
72
  retry
48
73
  end
49
- end
50
74
 
51
- headers = DEFAULT_HEADERS.merge(headers).merge("Host" => uri.host)
52
- headers["Content-Length"] = body.bytesize if body
75
+ original_socket = socket
76
+ if @ssl_context
77
+ socket = OpenSSL::SSL::SSLSocket.new(socket, @ssl_context)
78
+ socket.hostname = @host
79
+ socket.sync_close = true
80
+ begin
81
+ raise Timeout if Time.now > expire_time
82
+ socket.connect_nonblock
83
+ rescue IO::WaitReadable, IO::WaitWritable
84
+ select_timeout = expire_time - Time.now
85
+ select_timeout = 0 if select_timeout < 0
86
+ IO.select([socket.io], [socket.io], [socket.io], select_timeout)
87
+ retry
88
+ end
89
+ end
90
+ return socket, original_socket
91
+ end
53
92
 
54
- socket.write_nonblock "#{method} #{uri.request_uri} HTTP/1.0\r\n"
55
- headers.each do |header, value|
56
- socket.write_nonblock "#{header}: #{value}\r\n"
93
+ def connect
94
+ socket, _ = connect2
95
+ socket
57
96
  end
58
- socket.write_nonblock "\r\n"
59
- socket.write_nonblock body if body
60
- return socket, original_socket
61
97
  end
62
98
 
63
- def self.request!(*args)
64
- socket, _ = request2!(*args)
65
- socket
99
+ def self.request!(method, uri, headers = {}, body = nil, timeout = 1, ssl_context = nil)
100
+ uri = URI.parse(uri) unless URI === uri
101
+ ssl_context = nil
102
+ ssl_context = OpenSSL::SSL::SSLContext.new if uri.scheme == "https"
103
+ ark = Connection.new(uri.host, uri.port, timeout, ssl_context)
104
+ ark.request!(method, uri, headers, body)
66
105
  end
67
106
 
68
107
  def self.request(*args)
data/test/httpray_test.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'minitest/autorun'
2
2
  require 'lib/httpray'
3
+ require 'benchmark'
4
+ require 'net/http'
3
5
 
4
- class HTTPrayTest < MiniTest::Unit::TestCase
6
+ class HTTPrayTest < MiniTest::Test
5
7
  def test_request_timesout_with_short_timeout
6
8
  assert_raises HTTPray::Timeout do
7
9
  HTTPray.request("GET", "httppppp://httpbin.org/status/200", {}, nil, 0)
@@ -42,9 +44,10 @@ class HTTPrayTest < MiniTest::Unit::TestCase
42
44
  end
43
45
  end
44
46
  def test_original_socket_closed_with_ssl
45
- socket, original_socket = HTTPray.request2!(
46
- "GET",
47
- "https://httpbin.org/delay/10")
47
+ uri = URI.parse("https://httpbin.org/get")
48
+ ark = HTTPray::Connection.new(uri.host, uri.port, 1, OpenSSL::SSL::SSLContext.new)
49
+ ark.socket.close
50
+ socket, original_socket = ark.connect2
48
51
  refute_same socket, original_socket
49
52
  refute socket.closed?
50
53
  refute original_socket.closed?
@@ -53,9 +56,10 @@ class HTTPrayTest < MiniTest::Unit::TestCase
53
56
  assert original_socket.closed?
54
57
  end
55
58
  def test_original_socket_closed_without_ssl
56
- socket, original_socket = HTTPray.request2!(
57
- "GET",
58
- "http://httpbin.org/delay/10")
59
+ uri = URI.parse("http://httpbin.org/get")
60
+ ark = HTTPray::Connection.new(uri.host, uri.port, 1, nil)
61
+ ark.socket.close
62
+ socket, original_socket = ark.connect2
59
63
  assert_same socket, original_socket
60
64
  refute socket.closed?
61
65
  refute original_socket.closed?
@@ -63,4 +67,36 @@ class HTTPrayTest < MiniTest::Unit::TestCase
63
67
  assert socket.closed?
64
68
  assert original_socket.closed?
65
69
  end
70
+ def test_faster_than_net_http
71
+ uri = URI.parse("http://httpbin.org/delay/1")
72
+ net_http_time = Benchmark.realtime do
73
+ 2.times { Net::HTTP.get(uri) }
74
+ end
75
+ httpray_time = Benchmark.realtime do
76
+ 2.times { HTTPray.request("GET", uri) }
77
+ end
78
+ assert httpray_time < net_http_time
79
+ end
80
+ def test_persistent_connection_faster_than_ephemeral
81
+ uri = URI.parse("http://httpbin.org/delay/1")
82
+ persistent_time = Benchmark.realtime do
83
+ ark = HTTPray::Connection.new(uri.host, uri.port)
84
+ 3.times { ark.request("GET", uri) }
85
+ end
86
+ ephemeral_time = Benchmark.realtime do
87
+ 3.times { HTTPray.request("GET", uri) }
88
+ end
89
+ assert persistent_time < ephemeral_time
90
+ end
91
+ def test_reconnects_on_request_if_necessary
92
+ uri = URI.parse("http://httpbin.org/get")
93
+ ark = HTTPray::Connection.new(uri.host, uri.port, 1, nil)
94
+ original_socket = ark.socket
95
+ ark.request("GET", uri)
96
+ assert_same original_socket, ark.socket
97
+ ark.socket.close
98
+ assert original_socket.closed?
99
+ ark.request("GET", uri)
100
+ refute_same original_socket, ark.socket
101
+ end
66
102
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpray
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - G Gordon Worley III
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-14 00:00:00.000000000 Z
11
+ date: 2017-07-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Fire-and-forget HTTP requests for Ruby
14
14
  email: