async-rest 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/Gemfile +1 -2
- data/async-rest.gemspec +2 -2
- data/lib/async/rest.rb +3 -1
- data/lib/async/rest/resource.rb +31 -73
- data/lib/async/rest/version.rb +1 -1
- data/lib/async/rest/{json_body.rb → wrapper/json.rb} +38 -20
- metadata +6 -7
- data/lib/async/rest/reference.rb +0 -139
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 839be8ad4eee0471cff7053c05692ae11178f8ab934c2485c61c09b67cca6cf0
|
4
|
+
data.tar.gz: af6e5721af9c1585fcd03c3e95d886ad639b71b789972ac630ed62c4c7c9c264
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32ff875407f491ddf920a8ebe437ca91d7fc2d6bdaa343c61b40aa292a0ca380c7414c146ec16ce3d4125627eb8f21cf21f920ab110d9c23a32fc7c4a9d5e4bf
|
7
|
+
data.tar.gz: 29a11597c7af0851afeb8eef0d053abe854451e4e0c9d3dc41c10f8ea1b36d20a22bcf7261d676d3a1d3a9ef24476133e46e153d8f826a3b7ef134896e0d5b80
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/async-rest.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.authors = ["Samuel Williams"]
|
8
8
|
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
9
9
|
|
10
|
-
spec.summary = "A library for RESTful clients and servers."
|
10
|
+
spec.summary = "A library for RESTful clients (and hopefully servers)."
|
11
11
|
spec.homepage = "https://github.com/socketry/async-rest"
|
12
12
|
|
13
13
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
|
-
spec.add_dependency
|
19
|
+
spec.add_dependency "async-http", "~> 0.27.12"
|
20
20
|
|
21
21
|
spec.add_development_dependency "async-rspec", "~> 1.1"
|
22
22
|
|
data/lib/async/rest.rb
CHANGED
data/lib/async/rest/resource.rb
CHANGED
@@ -18,37 +18,36 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require_relative '
|
22
|
-
require_relative 'json_body'
|
21
|
+
require_relative 'wrapper/json'
|
23
22
|
|
24
23
|
require 'async/http/client'
|
24
|
+
require 'async/http/accept_encoding'
|
25
|
+
require 'async/http/reference'
|
25
26
|
require 'async/http/url_endpoint'
|
26
27
|
|
27
28
|
module Async
|
28
29
|
module REST
|
29
|
-
class Resource
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
}
|
34
|
-
|
35
|
-
def initialize(client, reference = Reference.parse, headers = DEFAULT_HEADERS, max_redirects: 10)
|
36
|
-
@client = client
|
30
|
+
class Resource < HTTP::Middleware
|
31
|
+
def initialize(client, reference = HTTP::Reference.parse, headers = HTTP::Headers.new, wrapper = Wrapper::JSON.new)
|
32
|
+
super(client)
|
33
|
+
|
37
34
|
@reference = reference
|
38
35
|
@headers = headers
|
39
|
-
|
40
|
-
@max_redirects = max_redirects
|
36
|
+
@wrapper = wrapper
|
41
37
|
end
|
42
38
|
|
43
|
-
def
|
44
|
-
|
39
|
+
def self.connect(url)
|
40
|
+
endpoint = HTTP::URLEndpoint.parse(url)
|
41
|
+
|
42
|
+
reference = HTTP::Reference.parse(endpoint.url.request_uri)
|
43
|
+
|
44
|
+
return HTTP::AcceptEncoding.new(HTTP::Client.new(endpoint)), reference
|
45
45
|
end
|
46
46
|
|
47
|
-
def self.for(url,
|
48
|
-
|
49
|
-
client = HTTP::Client.new(endpoint)
|
47
|
+
def self.for(url, *args)
|
48
|
+
client, reference = connect(url)
|
50
49
|
|
51
|
-
resource = self.new(client,
|
50
|
+
resource = self.new(client, reference, *args)
|
52
51
|
|
53
52
|
return resource unless block_given?
|
54
53
|
|
@@ -63,76 +62,35 @@ module Async
|
|
63
62
|
attr :reference
|
64
63
|
attr :headers
|
65
64
|
|
66
|
-
|
67
|
-
|
68
|
-
def [] path
|
69
|
-
self.class.new(@client, @reference.nest(path), @headers, max_redirects: @max_redirects)
|
65
|
+
def self.nest(parent, path = nil, *args)
|
66
|
+
self.new(*args, parent.client, parent.reference.dup(path), parent.headers, parent.wrapper)
|
70
67
|
end
|
71
68
|
|
72
69
|
def with(**headers)
|
73
|
-
self.class.new(@client, @reference, @headers.merge(headers),
|
74
|
-
end
|
75
|
-
|
76
|
-
def wrapper_for(content_type)
|
77
|
-
if content_type == 'application/json'
|
78
|
-
return JSONBody
|
79
|
-
end
|
70
|
+
self.class.new(@client, @reference, @headers.merge(headers), @wrapper)
|
80
71
|
end
|
81
72
|
|
82
|
-
def
|
83
|
-
|
73
|
+
def prepare_request(verb, payload = nil, headers = nil, **parameters)
|
74
|
+
headers ||= @headers.dup
|
75
|
+
reference = @reference.dup(nil, parameters)
|
76
|
+
body = @wrapper.prepare_request(payload, headers)
|
84
77
|
|
85
|
-
|
86
|
-
|
87
|
-
if wrapper = wrapper_for(content_type)
|
88
|
-
return wrapper.dump(payload)
|
89
|
-
else
|
90
|
-
raise ArgumentError.new("Unsure how to convert payload to #{content_type}!")
|
91
|
-
end
|
78
|
+
return HTTP::Request[verb, reference, headers, body]
|
92
79
|
end
|
93
80
|
|
94
81
|
def process_response(response)
|
95
|
-
|
96
|
-
|
97
|
-
content_type = response.headers['content-type']
|
98
|
-
|
99
|
-
if wrapper = wrapper_for(content_type)
|
100
|
-
response.body = wrapper.new(response.body)
|
101
|
-
end
|
102
|
-
|
103
|
-
return response
|
82
|
+
@wrapper.process_response(response)
|
104
83
|
end
|
105
84
|
|
106
|
-
HTTP::
|
107
|
-
define_method(verb.downcase) do |
|
108
|
-
|
85
|
+
HTTP::VERBS.each do |verb|
|
86
|
+
define_method(verb.downcase) do |*args|
|
87
|
+
request = prepare_request(verb, *args)
|
109
88
|
|
110
|
-
|
111
|
-
body = HTTP::DeflateBody.for_request(@headers, body)
|
112
|
-
end
|
89
|
+
response = self.call(request)
|
113
90
|
|
114
|
-
response
|
115
|
-
process_response(response)
|
116
|
-
end
|
117
|
-
|
118
|
-
return response
|
91
|
+
process_response(response)
|
119
92
|
end
|
120
93
|
end
|
121
|
-
|
122
|
-
def request(verb, location, *args)
|
123
|
-
@max_redirects.times do
|
124
|
-
@client.request(verb, location, *args) do |response|
|
125
|
-
if response.redirection?
|
126
|
-
verb = 'GET' unless response.preserve_method?
|
127
|
-
location = response.headers['location']
|
128
|
-
else
|
129
|
-
return yield response
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
raise ArgumentError.new("Too many redirections!")
|
135
|
-
end
|
136
94
|
end
|
137
95
|
end
|
138
96
|
end
|
data/lib/async/rest/version.rb
CHANGED
@@ -22,27 +22,45 @@ require 'json'
|
|
22
22
|
|
23
23
|
module Async
|
24
24
|
module REST
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def close
|
31
|
-
@body = @body.close
|
25
|
+
module Wrapper
|
26
|
+
class JSON
|
27
|
+
CONTENT_TYPE = "application/json".freeze
|
32
28
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
29
|
+
def initialize(content_type = CONTENT_TYPE)
|
30
|
+
@content_type = content_type
|
31
|
+
end
|
32
|
+
|
33
|
+
def split(*args)
|
34
|
+
@content_type.split
|
35
|
+
end
|
36
|
+
|
37
|
+
def prepare_request(payload, headers)
|
38
|
+
headers['accept'] ||= @content_type
|
39
|
+
|
40
|
+
if payload
|
41
|
+
headers['content-type'] = @content_type
|
42
|
+
|
43
|
+
HTTP::Body::Buffered.new([
|
44
|
+
::JSON.dump(payload)
|
45
|
+
])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_response(response)
|
50
|
+
# We expect all responses to be valid JSON.
|
51
|
+
# I tried inspecting content-type, but too many servers do the wrong thing.
|
52
|
+
if body = response.body
|
53
|
+
response.body = Parser.new(body)
|
54
|
+
end
|
55
|
+
|
56
|
+
return response
|
57
|
+
end
|
58
|
+
|
59
|
+
class Parser < HTTP::Body::Wrapper
|
60
|
+
def join
|
61
|
+
::JSON.parse(super, symbolize_names: true)
|
62
|
+
end
|
63
|
+
end
|
46
64
|
end
|
47
65
|
end
|
48
66
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-rest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async-http
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.27.12
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.27.12
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: async-rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,10 +95,9 @@ files:
|
|
95
95
|
- Rakefile
|
96
96
|
- async-rest.gemspec
|
97
97
|
- lib/async/rest.rb
|
98
|
-
- lib/async/rest/json_body.rb
|
99
|
-
- lib/async/rest/reference.rb
|
100
98
|
- lib/async/rest/resource.rb
|
101
99
|
- lib/async/rest/version.rb
|
100
|
+
- lib/async/rest/wrapper/json.rb
|
102
101
|
homepage: https://github.com/socketry/async-rest
|
103
102
|
licenses: []
|
104
103
|
metadata: {}
|
@@ -121,5 +120,5 @@ rubyforge_project:
|
|
121
120
|
rubygems_version: 2.7.6
|
122
121
|
signing_key:
|
123
122
|
specification_version: 4
|
124
|
-
summary: A library for RESTful clients and servers.
|
123
|
+
summary: A library for RESTful clients (and hopefully servers).
|
125
124
|
test_files: []
|
data/lib/async/rest/reference.rb
DELETED
@@ -1,139 +0,0 @@
|
|
1
|
-
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
20
|
-
|
21
|
-
module Async
|
22
|
-
module REST
|
23
|
-
# A relative reference, excluding any authority.
|
24
|
-
class Reference
|
25
|
-
def initialize(path, query_string, fragment, parameters)
|
26
|
-
@path = path
|
27
|
-
@query_string = query_string
|
28
|
-
@fragment = fragment
|
29
|
-
@parameters = parameters
|
30
|
-
end
|
31
|
-
|
32
|
-
# Generate a reference from a path and user parameters. The path may contain a `#fragment` or `?query=parameters`.
|
33
|
-
def self.parse(path = '/', parameters = nil)
|
34
|
-
base, fragment = path.split('#', 2)
|
35
|
-
path, query_string = base.split('?', 2)
|
36
|
-
|
37
|
-
self.new(path, query_string, fragment, parameters)
|
38
|
-
end
|
39
|
-
|
40
|
-
# The path component, e.g. /foo/bar/index.html
|
41
|
-
attr :path
|
42
|
-
|
43
|
-
# The un-parsed query string, e.g. 'x=10&y=20'
|
44
|
-
attr :query_string
|
45
|
-
|
46
|
-
# A fragment, the part after the '#'
|
47
|
-
attr :fragment
|
48
|
-
|
49
|
-
# User supplied parameters that will be appended to the query part.
|
50
|
-
attr :parameters
|
51
|
-
|
52
|
-
def append(buffer)
|
53
|
-
if @query_string
|
54
|
-
buffer << escape_path(@path) << '?' << query_string
|
55
|
-
buffer << '&' << encode(@parameters) if @parameters
|
56
|
-
else
|
57
|
-
buffer << escape_path(@path)
|
58
|
-
buffer << '?' << encode(@parameters) if @parameters
|
59
|
-
end
|
60
|
-
|
61
|
-
if @fragment
|
62
|
-
buffer << '#' << escape(@fragment)
|
63
|
-
end
|
64
|
-
|
65
|
-
return buffer
|
66
|
-
end
|
67
|
-
|
68
|
-
def to_str
|
69
|
-
append(String.new)
|
70
|
-
end
|
71
|
-
|
72
|
-
alias to_s to_str
|
73
|
-
|
74
|
-
def + path
|
75
|
-
self.dup(path)
|
76
|
-
end
|
77
|
-
|
78
|
-
def [] parameters
|
79
|
-
self.dup(nil, parameters)
|
80
|
-
end
|
81
|
-
|
82
|
-
def dup(path = nil, parameters = nil)
|
83
|
-
if parameters and @parameters
|
84
|
-
parameters = @parameters.merge(parameters)
|
85
|
-
else
|
86
|
-
parameters = @parameters
|
87
|
-
end
|
88
|
-
|
89
|
-
if path
|
90
|
-
path = @path + '/' + path
|
91
|
-
else
|
92
|
-
path = @path
|
93
|
-
end
|
94
|
-
|
95
|
-
self.class.new(path, @query_string, @fragment, parameters)
|
96
|
-
end
|
97
|
-
|
98
|
-
private
|
99
|
-
|
100
|
-
# Escapes a generic string, using percent encoding.
|
101
|
-
def escape(string)
|
102
|
-
encoding = string.encoding
|
103
|
-
string.b.gsub(/([^a-zA-Z0-9_.\-]+)/) do |m|
|
104
|
-
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
|
105
|
-
end.force_encoding(encoding)
|
106
|
-
end
|
107
|
-
|
108
|
-
# According to https://tools.ietf.org/html/rfc3986#section-3.3, we escape non-pchar.
|
109
|
-
NON_PCHAR = /([^a-zA-Z0-9_\-\.~!$&'()*+,;=:@\/]+)/.freeze
|
110
|
-
|
111
|
-
# Escapes a path
|
112
|
-
def escape_path(path)
|
113
|
-
encoding = path.encoding
|
114
|
-
path.b.gsub(NON_PCHAR) do |m|
|
115
|
-
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
|
116
|
-
end.force_encoding(encoding)
|
117
|
-
end
|
118
|
-
|
119
|
-
# Encodes a hash or array into a query string
|
120
|
-
def encode(value, prefix = nil)
|
121
|
-
case value
|
122
|
-
when Array
|
123
|
-
value.map { |v|
|
124
|
-
encode(v, "#{prefix}[]")
|
125
|
-
}.join("&")
|
126
|
-
when Hash
|
127
|
-
value.map { |k, v|
|
128
|
-
encode(v, prefix ? "#{prefix}[#{escape(k.to_s)}]" : escape(k.to_s))
|
129
|
-
}.reject(&:empty?).join('&')
|
130
|
-
when nil
|
131
|
-
prefix
|
132
|
-
else
|
133
|
-
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
134
|
-
"#{prefix}=#{escape(value.to_s)}"
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|