http_client 0.5.0-universal-java-1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +79 -0
- data/http_client-0.5.0-universal-java-1.7.gem +0 -0
- data/http_client-0.5.0.gem +0 -0
- data/http_client.gemspec +15 -0
- data/lib/http_client.rb +223 -0
- data/test/http_client_test.rb +95 -0
- data/vendor/commons-logging-1.1.3.jar +0 -0
- data/vendor/httpclient-4.3.1.jar +0 -0
- data/vendor/httpcore-4.3.jar +0 -0
- data/vendor/httpmime-4.3.1.jar +0 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a120508893a08fdf41e1cd67421138ae3e043088
|
4
|
+
data.tar.gz: eca3be3cb0a0f5c12f112ceb8a384a99269b6081
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3d4477baa911fbfdd8750816571d862f4a55e892e7e90fd39e9483668f9e634ff62970f1d722f6651fe9ea5b27ae9867556bfb8a4e23760d59e49b7384343eb4
|
7
|
+
data.tar.gz: b1dd2383ee8362db2199e942cf297872315e3e90f3d0cdadac17602a1e8810b2bf090ac34984537f4ad5f8496dd499038fab013cfaa35eb0d512e63d1c45379e
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# A simple yet powerful HTTP client for JRuby
|
2
|
+
|
3
|
+
This library wraps the Apache HTTPClient (4.3) in a simple fashion.
|
4
|
+
It currently implements all common HTTP verbs, connection pooling, retries and transparent gzip response handling. All common exceptions from Javaland are wrapped as Ruby exceptions. The library is intended to be used in a multithreaded environment.
|
5
|
+
|
6
|
+
## Examples
|
7
|
+
|
8
|
+
#### A simple GET request
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
require "http_client"
|
12
|
+
|
13
|
+
client = HTTPClient.new
|
14
|
+
response = client.get("http://www.google.com/robots.txt")
|
15
|
+
|
16
|
+
response.status
|
17
|
+
# => 200
|
18
|
+
|
19
|
+
response.body
|
20
|
+
# =>
|
21
|
+
# User-agent: *
|
22
|
+
# ...
|
23
|
+
```
|
24
|
+
|
25
|
+
#### POST a form
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
response = client.post("http://geocities.com/darrensden/guestbook",
|
29
|
+
:form => {
|
30
|
+
:name => "Joe Stub",
|
31
|
+
:email => "joey@ymail.com",
|
32
|
+
:comment => "Hey, I really like your site! Awesome stuff"
|
33
|
+
}
|
34
|
+
)
|
35
|
+
```
|
36
|
+
|
37
|
+
#### POST JSON data
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
response = client.post("http://webtwoopointo.com/v1/api/guestbooks/123/comments",
|
41
|
+
:json => {
|
42
|
+
:name => "Jason",
|
43
|
+
:email => "jaz0r@gmail.com",
|
44
|
+
:comment => "Yo dawg, luv ur site!"
|
45
|
+
}
|
46
|
+
)
|
47
|
+
```
|
48
|
+
|
49
|
+
#### Provide request headers
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
response = client.get("http://secretservice.com/users/123",
|
53
|
+
:headers => { "X-Auth": "deadbeef23" }
|
54
|
+
)
|
55
|
+
```
|
56
|
+
|
57
|
+
#### Using a connection pool
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
$client = HTTPClient.new(
|
61
|
+
:use_connection_pool => true,
|
62
|
+
:max_connections => 10,
|
63
|
+
)
|
64
|
+
|
65
|
+
%[www.google.de www.yahoo.com www.altavista.com].each do |host|
|
66
|
+
Thread.new do
|
67
|
+
response = $client.get("http://#{host}/robots.txt")
|
68
|
+
puts response.body
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
## Contribute
|
74
|
+
|
75
|
+
This library covers just what I need. I wanted to have a thread safe HTTP client that has a fixed connection pool with fine grained timeout configurations.
|
76
|
+
|
77
|
+
Before you start hacking away, have a look at the issues. There might be stuff that is already in the making. If so, there will be a published branch you can contribute to.
|
78
|
+
|
79
|
+
Just create a fork and send me a pull request. I would be honored to look at your input.
|
Binary file
|
Binary file
|
data/http_client.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "http_client"
|
3
|
+
s.version = "0.5.0"
|
4
|
+
s.date = "2013-11-07"
|
5
|
+
s.summary = "HTTP client for JRuby"
|
6
|
+
s.description = "This library wraps the Apache HTTPClient (4.3) in a simple fashion. The library is intended to be used in a multithreaded environment."
|
7
|
+
s.platform = Gem::Platform::CURRENT
|
8
|
+
s.requirements << "JRuby"
|
9
|
+
s.requirements << "Java, since this library wraps a Java library"
|
10
|
+
s.authors = ["Lukas Rieder"]
|
11
|
+
s.email = "l.rieder@gmail.com"
|
12
|
+
s.files = Dir["**/*"]
|
13
|
+
s.homepage = "https://github.com/Overbryd/http_client"
|
14
|
+
s.license = "MIT"
|
15
|
+
end
|
data/lib/http_client.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "java"
|
5
|
+
%w[httpcore-4.3 httpclient-4.3.1 httpmime-4.3.1 commons-logging-1.1.3].each do |jar|
|
6
|
+
require_relative "../vendor/#{jar}.jar"
|
7
|
+
end
|
8
|
+
|
9
|
+
class HttpClient
|
10
|
+
import org.apache.http.impl.client.HttpClients
|
11
|
+
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
12
|
+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager
|
13
|
+
import org.apache.http.client.methods.HttpGet
|
14
|
+
import org.apache.http.client.methods.HttpPost
|
15
|
+
import org.apache.http.client.methods.HttpPut
|
16
|
+
import org.apache.http.client.methods.HttpPatch
|
17
|
+
import org.apache.http.client.methods.HttpDelete
|
18
|
+
import org.apache.http.client.methods.HttpHead
|
19
|
+
import org.apache.http.client.methods.HttpOptions
|
20
|
+
import org.apache.http.client.config.RequestConfig
|
21
|
+
import org.apache.http.entity.StringEntity
|
22
|
+
import org.apache.http.client.entity.UrlEncodedFormEntity
|
23
|
+
import org.apache.http.client.entity.GzipDecompressingEntity
|
24
|
+
import org.apache.http.client.entity.DeflateDecompressingEntity
|
25
|
+
import org.apache.http.message.BasicNameValuePair
|
26
|
+
import org.apache.http.entity.ContentType
|
27
|
+
import org.apache.http.util.EntityUtils
|
28
|
+
import org.apache.http.HttpException
|
29
|
+
import org.apache.http.conn.ConnectTimeoutException
|
30
|
+
import java.io.IOException
|
31
|
+
import java.net.SocketTimeoutException
|
32
|
+
import java.util.concurrent.TimeUnit
|
33
|
+
|
34
|
+
class Error < StandardError; end
|
35
|
+
class Timeout < Error; end
|
36
|
+
class IOError < Error; end
|
37
|
+
|
38
|
+
class Response
|
39
|
+
attr_reader :status, :body, :headers
|
40
|
+
|
41
|
+
def initialize(closeable_response)
|
42
|
+
@status = closeable_response.status_line.status_code
|
43
|
+
@headers = closeable_response.get_all_headers.inject({}) do |headers, header|
|
44
|
+
headers[header.name] = header.value
|
45
|
+
headers
|
46
|
+
end
|
47
|
+
@body = read_body(closeable_response)
|
48
|
+
end
|
49
|
+
|
50
|
+
def success?
|
51
|
+
@status >= 200 && @status <= 206
|
52
|
+
end
|
53
|
+
|
54
|
+
def json_body(options = {})
|
55
|
+
@json_body ||= JSON.parse(body, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def read_body(closeable_response)
|
61
|
+
return "" unless entity = closeable_response.entity
|
62
|
+
return "" unless entity.content_length > 0
|
63
|
+
if content_encoding = entity.content_encoding
|
64
|
+
entity = case content_encoding.value
|
65
|
+
when "gzip", "x-gzip" then
|
66
|
+
GzipDecompressingEntity.new(entity)
|
67
|
+
when "deflate" then
|
68
|
+
DeflateDecompressingEntity.new(entity)
|
69
|
+
else
|
70
|
+
entity
|
71
|
+
end
|
72
|
+
end
|
73
|
+
EntityUtils.to_string(entity, "UTF-8")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
attr_reader :client, :max_retries, :response_class, :default_request_options
|
78
|
+
|
79
|
+
def initialize(options = {})
|
80
|
+
options = {
|
81
|
+
:use_connection_pool => false,
|
82
|
+
:max_connections => 20,
|
83
|
+
:max_connections_per_route => nil,
|
84
|
+
:max_retries => 0,
|
85
|
+
:response_class => Response,
|
86
|
+
:connection_request_timeout => 100,
|
87
|
+
:connect_timeout => 1000,
|
88
|
+
:socket_timeout => 2000,
|
89
|
+
:default_request_options => {}
|
90
|
+
}.merge(options)
|
91
|
+
@request_config = create_request_config(options)
|
92
|
+
@connection_manager = create_connection_manager(options)
|
93
|
+
@client = HttpClients.create_minimal(@connection_manager)
|
94
|
+
@max_retries = options[:max_retries]
|
95
|
+
@response_class = options[:response_class]
|
96
|
+
@default_request_options = options[:default_request_options]
|
97
|
+
end
|
98
|
+
|
99
|
+
def get(uri, options = {})
|
100
|
+
uri = uri.sub(/\?.+$|$/, "?#{URI.encode_www_form(options[:params])}") if options[:params]
|
101
|
+
request = create_request(HttpGet, uri, options)
|
102
|
+
execute(request)
|
103
|
+
end
|
104
|
+
|
105
|
+
def post(uri, options = {})
|
106
|
+
request = create_request(HttpPost, uri, options)
|
107
|
+
entity = create_entity(options)
|
108
|
+
request.set_entity(entity) if entity
|
109
|
+
execute(request)
|
110
|
+
end
|
111
|
+
|
112
|
+
def put(uri, options = {})
|
113
|
+
request = create_request(HttpPut, uri, options)
|
114
|
+
entity = create_entity(options)
|
115
|
+
request.set_entity(entity) if entity
|
116
|
+
execute(request)
|
117
|
+
end
|
118
|
+
|
119
|
+
def patch(uri, options = {})
|
120
|
+
request = create_request(HttpPatch, uri, options)
|
121
|
+
entity = create_entity(options)
|
122
|
+
request.set_entity(entity) if entity
|
123
|
+
execute(request)
|
124
|
+
end
|
125
|
+
|
126
|
+
def delete(uri, options = {})
|
127
|
+
request = create_request(HttpDelete, uri, options)
|
128
|
+
execute(request)
|
129
|
+
end
|
130
|
+
|
131
|
+
def head(uri, options = {})
|
132
|
+
request = create_request(HttpHead, uri, options)
|
133
|
+
execute(request)
|
134
|
+
end
|
135
|
+
|
136
|
+
def options(uri, options = {})
|
137
|
+
request = create_request(HttpOptions, uri, options)
|
138
|
+
execute(request)
|
139
|
+
end
|
140
|
+
|
141
|
+
def pool_stats
|
142
|
+
raise "#{self.class.name}#pool_stats is supported only when using a connection pool" unless @connection_manager.is_a?(PoolingHttpClientConnectionManager)
|
143
|
+
total_stats = @connection_manager.total_stats
|
144
|
+
Hash(
|
145
|
+
:idle => total_stats.available,
|
146
|
+
:in_use => total_stats.leased,
|
147
|
+
:max => total_stats.max,
|
148
|
+
:waiting => total_stats.pending
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
def cleanup_connections(max_idle = 5)
|
153
|
+
@connection_manager.close_idle_connections(max_idle, TimeUnit::SECONDS)
|
154
|
+
end
|
155
|
+
|
156
|
+
def shutdown
|
157
|
+
@connection_manager.shutdown
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def execute(request)
|
163
|
+
retries = max_retries
|
164
|
+
begin
|
165
|
+
closeable_response = client.execute(request)
|
166
|
+
response_class.new(closeable_response)
|
167
|
+
rescue ConnectTimeoutException, SocketTimeoutException => e
|
168
|
+
retry if (retries -= 1) > 0
|
169
|
+
raise Timeout, "#{e.message}"
|
170
|
+
rescue IOException => e
|
171
|
+
retry if (retries -= 1) > 0
|
172
|
+
raise IOError, "#{e.message}"
|
173
|
+
rescue HttpException => e
|
174
|
+
raise Error, "#{e.message}"
|
175
|
+
ensure
|
176
|
+
closeable_response.close if closeable_response
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def create_request(method_class, uri, options)
|
181
|
+
request = method_class.new(uri)
|
182
|
+
request.config = @request_config
|
183
|
+
options = default_request_options.merge(options)
|
184
|
+
options[:headers].each do |name, value|
|
185
|
+
request.set_header(name.to_s.gsub("_", "-"), value)
|
186
|
+
end if options[:headers]
|
187
|
+
request
|
188
|
+
end
|
189
|
+
|
190
|
+
def create_entity(options)
|
191
|
+
if options[:body]
|
192
|
+
StringEntity.new(options[:body], ContentType.create(options[:content_type] || "text/plain", "UTF-8"))
|
193
|
+
elsif options[:json]
|
194
|
+
json = options[:json].to_json
|
195
|
+
StringEntity.new(json, ContentType.create("application/json", "UTF-8"))
|
196
|
+
elsif options[:form]
|
197
|
+
form = options[:form].map { |k, v| BasicNameValuePair.new(k.to_s, v.to_s) }
|
198
|
+
UrlEncodedFormEntity.new(form, "UTF-8")
|
199
|
+
else
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def create_request_config(options)
|
205
|
+
config = RequestConfig.custom
|
206
|
+
config.set_stale_connection_check_enabled(true)
|
207
|
+
config.set_connection_request_timeout(options[:connection_request_timeout])
|
208
|
+
config.set_connect_timeout(options[:connect_timeout])
|
209
|
+
config.set_socket_timeout(options[:socket_timeout])
|
210
|
+
config.build
|
211
|
+
end
|
212
|
+
|
213
|
+
def create_connection_manager(options)
|
214
|
+
options[:use_connection_pool] ? create_pooling_connection_manager(options) : BasicHttpClientConnectionManager.new
|
215
|
+
end
|
216
|
+
|
217
|
+
def create_pooling_connection_manager(options)
|
218
|
+
connection_manager = PoolingHttpClientConnectionManager.new
|
219
|
+
connection_manager.max_total = options[:max_connections]
|
220
|
+
connection_manager.default_max_per_route = options[:max_connections_per_route] || options[:max_connections]
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
gem "minitest"
|
5
|
+
require "minitest/pride"
|
6
|
+
require "minitest/autorun"
|
7
|
+
|
8
|
+
require "json"
|
9
|
+
require_relative "../lib/http_client"
|
10
|
+
|
11
|
+
module Minitest
|
12
|
+
class Test
|
13
|
+
def self.test(name, &block)
|
14
|
+
define_method("test_#{name.gsub(" ", " ")}", &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class HttpClientTest < Minitest::Test
|
20
|
+
attr_reader :client
|
21
|
+
|
22
|
+
def setup
|
23
|
+
@client = HttpClient.new
|
24
|
+
end
|
25
|
+
|
26
|
+
test "GET request with params in uri" do
|
27
|
+
response = client.get("http://httpbin.org/get?foo=bar")
|
28
|
+
assert_equal Hash("foo" => "bar"), response.json_body["args"]
|
29
|
+
end
|
30
|
+
|
31
|
+
test "GET request with params in options" do
|
32
|
+
response = client.get("http://httpbin.org/get", :params => { :foo => "bar" })
|
33
|
+
assert_equal Hash("foo" => "bar"), response.json_body["args"]
|
34
|
+
end
|
35
|
+
|
36
|
+
test "GET request with headers" do
|
37
|
+
response = client.get("http://httpbin.org/get", :headers => { "X-Send-By" => "foobar" })
|
38
|
+
assert_equal "foobar", response.json_body["headers"]["X-Send-By"]
|
39
|
+
end
|
40
|
+
|
41
|
+
test "GET request with gzipped response" do
|
42
|
+
response = client.get("http://httpbin.org/gzip")
|
43
|
+
assert_equal true, response.json_body["gzipped"]
|
44
|
+
assert_nil response.json_body["headers"]["Content-Encoding"]
|
45
|
+
end
|
46
|
+
|
47
|
+
test "POST request with string body" do
|
48
|
+
response = client.post("http://httpbin.org/post", :body => "foo:bar|zig:zag")
|
49
|
+
assert_equal "text/plain; charset=UTF-8", response.json_body["headers"]["Content-Type"]
|
50
|
+
assert_equal "foo:bar|zig:zag", response.json_body["data"]
|
51
|
+
end
|
52
|
+
|
53
|
+
test "POST request with form data" do
|
54
|
+
response = client.post("http://httpbin.org/post", :form => { :foo => "bar"})
|
55
|
+
assert_equal "application/x-www-form-urlencoded; charset=UTF-8", response.json_body["headers"]["Content-Type"]
|
56
|
+
assert_equal Hash("foo" => "bar"), response.json_body["form"]
|
57
|
+
end
|
58
|
+
|
59
|
+
test "POST request with json data" do
|
60
|
+
response = client.post("http://httpbin.org/post", :json => { :foo => "bar"})
|
61
|
+
assert_equal "application/json; charset=UTF-8", response.json_body["headers"]["Content-Type"]
|
62
|
+
assert_equal Hash("foo" => "bar"), response.json_body["json"]
|
63
|
+
end
|
64
|
+
|
65
|
+
test "POST request Content-Type in header takes precedence" do
|
66
|
+
response = client.post("http://httpbin.org/post",
|
67
|
+
:json => { "foo" => "bar" },
|
68
|
+
:headers => { "Content-Type" => "application/x-json" }
|
69
|
+
)
|
70
|
+
assert_equal "application/x-json", response.json_body["headers"]["Content-Type"]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# # Content-Type precedence
|
75
|
+
#
|
76
|
+
# client.post("http://httpbin.org/post",
|
77
|
+
# :headers => {
|
78
|
+
# :content_type => "application/xml"
|
79
|
+
# },
|
80
|
+
# :body => %Q'<?xml version="1.0" encoding="utf-8"?><foo>bar</foo>'
|
81
|
+
# )
|
82
|
+
# client.post("http://httpbin.org/post",
|
83
|
+
# :content_type => "application/xml+foo",
|
84
|
+
# :headers => {
|
85
|
+
# :content_type => "application/xml"
|
86
|
+
# },
|
87
|
+
# :body => %Q'<?xml version="1.0" encoding="utf-8"?><foo>bar</foo>'
|
88
|
+
# )
|
89
|
+
# client.post("http://httpbin.org/post",
|
90
|
+
# :content_type => "application/xml+foo",
|
91
|
+
# :headers => {
|
92
|
+
# :content_type => "application/xml"
|
93
|
+
# },
|
94
|
+
# :json => %Q'{"foo":"bar"}'
|
95
|
+
# )
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: http_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: universal-java-1.7
|
6
|
+
authors:
|
7
|
+
- Lukas Rieder
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-07 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This library wraps the Apache HTTPClient (4.3) in a simple fashion. The library is intended to be used in a multithreaded environment.
|
14
|
+
email: l.rieder@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- http_client-0.5.0-universal-java-1.7.gem
|
20
|
+
- http_client-0.5.0.gem
|
21
|
+
- http_client.gemspec
|
22
|
+
- README.md
|
23
|
+
- lib/http_client.rb
|
24
|
+
- test/http_client_test.rb
|
25
|
+
- vendor/commons-logging-1.1.3.jar
|
26
|
+
- vendor/httpclient-4.3.1.jar
|
27
|
+
- vendor/httpcore-4.3.jar
|
28
|
+
- vendor/httpmime-4.3.1.jar
|
29
|
+
homepage: https://github.com/Overbryd/http_client
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
metadata: {}
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements:
|
48
|
+
- JRuby
|
49
|
+
- Java, since this library wraps a Java library
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 2.1.9
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: HTTP client for JRuby
|
55
|
+
test_files: []
|