httpray 1.0.2
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 +7 -0
- data/.gitignore +54 -0
- data/.tool-versions +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +18 -0
- data/LICENSE +21 -0
- data/README.md +60 -0
- data/httpray.gemspec +21 -0
- data/lib/httpray/version.rb +3 -0
- data/lib/httpray.rb +76 -0
- data/test/httpray_test.rb +56 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 40bd4048bb72f0169b178246b13b20f86504eba4
|
4
|
+
data.tar.gz: 28642af4d6aeb959c948d24e6fb122e17cccb197
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5ea297d70ed2aaf3acf4dda9ad65248a0158a49340d509394c3ce7631497486ad930a035ad43a9b0686e26a6edae6886a20b36fa0828cbfdf7c37682bbb3bc3f
|
7
|
+
data.tar.gz: bb5d0f9279feeb73b69d2a13eb54ad0516ef7ff8bef36437a701761176cd66dec66c286daac11184e795bb747a0534e001a13335446f7dab6a1495cfd2b2d522
|
data/.gitignore
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
# Gemfile.lock
|
46
|
+
# .ruby-version
|
47
|
+
# .ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
51
|
+
|
52
|
+
# vim temp files
|
53
|
+
*.swp
|
54
|
+
*.un~
|
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 2.1.5
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 G Gordon Worley III
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# httpray
|
2
|
+
Non-blocking HTTP library for Ruby
|
3
|
+
|
4
|
+
[](https://badge.fury.io/rb/HTTPray)
|
5
|
+
|
6
|
+
Started out the same as the [fire-and-forget](https://github.com/mattetti/fire-and-forget) gem but with a more exposed interface, TLS support, and a better name. Added ideas from [tcp_timeout](https://github.com/lann/tcp-timeout-ruby) and accidentally ended up creating a light-weight, non-blocking HTTP client.
|
7
|
+
|
8
|
+
It differs from other Ruby HTTP libraries that support async because it doesn't use Threads, making HTTPray much less resource intensive to use since it instead directly implements HTTP/HTTPS 1.0 using `Socket` and `IO#select` for timeouts. You can optionally ask to be handed back the socket before it is closed in case you want to listen for a response, but that's not really what you're here for, and it creates a Fiber.
|
9
|
+
|
10
|
+
Great for use with sending data to HTTP endpoints for which you are willing to accept a UDP-style best-effort approach, but with the added guarantee of TCP that the packets made it to the server. Only the server will know what it did with the data, though!
|
11
|
+
|
12
|
+
## Install
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem "httpray"
|
16
|
+
```
|
17
|
+
|
18
|
+
## Use
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
require 'httpray'
|
22
|
+
|
23
|
+
# def HTTParty.request!(method, uri, headers = {}, body = "", timeout = 1, ssl_context = nil)
|
24
|
+
|
25
|
+
# send an HTTP request and don't listen for the response
|
26
|
+
HTTPray.request(
|
27
|
+
"POST",
|
28
|
+
"https://your.diety/prayers",
|
29
|
+
{"Content-Type" => "application/prayer"},
|
30
|
+
"It's me, Margret",
|
31
|
+
1) # timeout in seconds
|
32
|
+
|
33
|
+
# party with a response
|
34
|
+
HTTPray.request("GET", "https://your.diety/answered_prayers") do |socket|
|
35
|
+
socket.gets
|
36
|
+
end
|
37
|
+
|
38
|
+
# party dangerously (you have to close your own socket!)
|
39
|
+
socket = HTTPray.request!("GET", "https://your.diety/answered_prayers")
|
40
|
+
puts socket.gets
|
41
|
+
socket.close
|
42
|
+
```
|
43
|
+
|
44
|
+
## Help
|
45
|
+
|
46
|
+
HTTPray has minimal convenience and sanitization features because I didn't need them. All that it does is fill in the Host, User-Agent, Accept, and Content-Length headers for you. The body must be a string, so convert it yourself first. The URI can be a `URI` or a `String` that will go through `URI.parse`. You're welcome. You can also pass an `OpenSSL::SSL::SSLContext` if you want more control over how TLS is used, but if you don't provide one it will be created for you if needed.
|
47
|
+
|
48
|
+
Timeout support does not extend to the response since you just get back a `Socket`. You're own your own for how you want to handle that.
|
49
|
+
|
50
|
+
If you want it to be easier to use, feel free to submit pull requests. As long as you don't break existing functionality I will probably accept them.
|
51
|
+
|
52
|
+
## Tests
|
53
|
+
|
54
|
+
There are some tests that exercise the code paths. You can run them with:
|
55
|
+
|
56
|
+
```bash
|
57
|
+
ruby -I . test/httparty_test.rb
|
58
|
+
```
|
59
|
+
|
60
|
+
Unfortunately they have to hit real network endpoints, so they won't work without a network.
|
data/httpray.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'httpray/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "httpray"
|
7
|
+
spec.version = HTTPray::VERSION
|
8
|
+
spec.authors = ["G Gordon Worley III"]
|
9
|
+
spec.email = ["gworley3@gmail.com"]
|
10
|
+
spec.description = %q{Fire-and-forget HTTP requests for Ruby}
|
11
|
+
spec.summary = %q{Like UDP but for HTTP over TCP}
|
12
|
+
spec.homepage = "https://github.com/gworley3/httpray"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.post_install_message = "HTT🙏 for mercy"
|
21
|
+
end
|
data/lib/httpray.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'openssl'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
require_relative 'httpray/version'
|
6
|
+
|
7
|
+
module HTTPray
|
8
|
+
class Timeout < StandardError; end
|
9
|
+
|
10
|
+
DEFAULT_HEADERS = {
|
11
|
+
"User-Agent" => "HTTPray #{VERSION}",
|
12
|
+
"Accept" => "*/*"
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def self.request2!(method, uri, headers = {}, body = "", 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
|
+
begin
|
24
|
+
socket.connect_nonblock(socket_address)
|
25
|
+
rescue Errno::EINPROGRESS
|
26
|
+
if IO.select(nil, [socket], [socket], timeout)
|
27
|
+
begin
|
28
|
+
socket.connect_nonblock(socket_address)
|
29
|
+
rescue Errno::EISCONN
|
30
|
+
# connected
|
31
|
+
end
|
32
|
+
else
|
33
|
+
raise Timeout
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
original_socket = socket
|
38
|
+
if uri.scheme == "https"
|
39
|
+
ssl_context ||= OpenSSL::SSL::SSLContext.new
|
40
|
+
socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
|
41
|
+
socket.hostname = uri.host
|
42
|
+
socket.sync_close = true
|
43
|
+
socket.connect
|
44
|
+
end
|
45
|
+
|
46
|
+
headers = DEFAULT_HEADERS.merge(headers).merge(
|
47
|
+
"Host" => uri.host,
|
48
|
+
"Content-Length" => body.bytesize)
|
49
|
+
|
50
|
+
if IO.select(nil, [socket], [socket], 1)
|
51
|
+
socket.puts "#{method} #{uri.request_uri} HTTP/1.0\r\n"
|
52
|
+
headers.each do |header, value|
|
53
|
+
socket.puts "#{header}: #{value}\r\n"
|
54
|
+
end
|
55
|
+
socket.puts "\r\n"
|
56
|
+
socket.puts body
|
57
|
+
|
58
|
+
yield(socket) if block_given?
|
59
|
+
else
|
60
|
+
raise Timeout
|
61
|
+
end
|
62
|
+
return socket, original_socket
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.request!(*args)
|
66
|
+
socket, _ = request2!(*args)
|
67
|
+
socket
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.request(*args)
|
71
|
+
socket = request!(*args)
|
72
|
+
yield(socket) if block_given?
|
73
|
+
ensure
|
74
|
+
socket.close if socket && !socket.closed?
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'lib/httpray'
|
3
|
+
|
4
|
+
class HTTPrayTest < MiniTest::Unit::TestCase
|
5
|
+
def test_request_timesout_with_bad_address
|
6
|
+
assert_raises HTTPray::Timeout do
|
7
|
+
HTTPray.request("GET", "httppppp://httpbin.org/status/200")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
def test_request_sends
|
11
|
+
HTTPray.request("GET", "http://httpbin.org/get")
|
12
|
+
assert true
|
13
|
+
end
|
14
|
+
def test_request_receives_response
|
15
|
+
HTTPray.request("GET", "http://httpbin.org/status/200") do |socket|
|
16
|
+
assert_equal "HTTP/1.1 200 OK\r\n", socket.gets
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def test_secure_request_sends
|
20
|
+
HTTPray.request("GET", "https://httpbin.org/get")
|
21
|
+
assert true
|
22
|
+
end
|
23
|
+
def test_all_options_accepted
|
24
|
+
HTTPray.request(
|
25
|
+
"POST",
|
26
|
+
"https://httpbin.org/post",
|
27
|
+
{"Content-Type" => "application/x-www-form-urlencoded"},
|
28
|
+
"q=httpray",
|
29
|
+
5,
|
30
|
+
OpenSSL::SSL::SSLContext.new) do |socket|
|
31
|
+
assert_equal "HTTP/1.1 200 OK\r\n", socket.gets
|
32
|
+
end
|
33
|
+
end
|
34
|
+
def test_original_socket_closed_with_ssl
|
35
|
+
socket, original_socket = HTTPray.request2!(
|
36
|
+
"GET",
|
37
|
+
"https://httpbin.org/delay/10")
|
38
|
+
refute_same socket, original_socket
|
39
|
+
refute socket.closed?
|
40
|
+
refute original_socket.closed?
|
41
|
+
socket.close
|
42
|
+
assert socket.closed?
|
43
|
+
assert original_socket.closed?
|
44
|
+
end
|
45
|
+
def test_original_socket_closed_without_ssl
|
46
|
+
socket, original_socket = HTTPray.request2!(
|
47
|
+
"GET",
|
48
|
+
"http://httpbin.org/delay/10")
|
49
|
+
assert_same socket, original_socket
|
50
|
+
refute socket.closed?
|
51
|
+
refute original_socket.closed?
|
52
|
+
socket.close
|
53
|
+
assert socket.closed?
|
54
|
+
assert original_socket.closed?
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: httpray
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- G Gordon Worley III
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-14 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Fire-and-forget HTTP requests for Ruby
|
14
|
+
email:
|
15
|
+
- gworley3@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".gitignore"
|
21
|
+
- ".tool-versions"
|
22
|
+
- Gemfile
|
23
|
+
- Gemfile.lock
|
24
|
+
- LICENSE
|
25
|
+
- README.md
|
26
|
+
- httpray.gemspec
|
27
|
+
- lib/httpray.rb
|
28
|
+
- lib/httpray/version.rb
|
29
|
+
- test/httpray_test.rb
|
30
|
+
homepage: https://github.com/gworley3/httpray
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
metadata: {}
|
34
|
+
post_install_message: "HTT\U0001F64F for mercy"
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubyforge_project:
|
50
|
+
rubygems_version: 2.2.2
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: Like UDP but for HTTP over TCP
|
54
|
+
test_files:
|
55
|
+
- test/httpray_test.rb
|