fetch-api 0.1.0 → 0.3.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 +4 -4
- data/README.md +37 -10
- data/lib/fetch/client.rb +47 -10
- data/lib/fetch/form_data.rb +60 -0
- data/lib/fetch/headers.rb +50 -0
- data/lib/fetch/response.rb +18 -0
- data/lib/fetch/url_search_params.rb +71 -0
- data/lib/fetch/version.rb +1 -1
- data/lib/fetch.rb +0 -1
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc8cbe5804a5286df86b5840189dbbe3c1cfc77e68bfd33b0ba3244be99af5c2
|
4
|
+
data.tar.gz: f72c409553a6b531803d084d8e862c77329ef785579e9a11b5a461fadd481ca3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6051e1551078686b6624604b23966804684f7a764ffb76c467e79cf5bfd09e0025c8dd315803b0e7c8b1fe83874e349984c1a3fa325306fb4c09d1f855524e0c
|
7
|
+
data.tar.gz: 6340aa6ca403e46a023753fd08144081009512a3b97c12a3b035ac58aa951e49e1218e7d543a52151d9600022ed40773f00c655926eaa5d59b7fc9c3f9fc8ca3
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Fetch API
|
1
|
+
# Fetch API for Ruby
|
2
2
|
|
3
3
|
Ruby's Net::HTTP is very powerful, but has a complicated API. OpenURI is easy to use, but has limited functionality. Third-party HTTP clients each have different APIs, and it can sometimes be difficult to learn how to use them.
|
4
4
|
|
@@ -29,10 +29,12 @@ require 'fetch-api'
|
|
29
29
|
|
30
30
|
res = Fetch::API.fetch('https://example.com')
|
31
31
|
|
32
|
-
# res is a
|
32
|
+
# res is a Fetch::Response object
|
33
33
|
puts res.body
|
34
34
|
```
|
35
35
|
|
36
|
+
or
|
37
|
+
|
36
38
|
``` ruby
|
37
39
|
require 'fetch-api'
|
38
40
|
|
@@ -41,6 +43,24 @@ include Fetch::API
|
|
41
43
|
res = fetch('https://example.com')
|
42
44
|
```
|
43
45
|
|
46
|
+
Options for `fetch` method:
|
47
|
+
|
48
|
+
- `method`: HTTP method (default: `'GET'`)
|
49
|
+
- `headers`: Request headers (default: `{}`)
|
50
|
+
- `body`: Request body (default: `nil`)
|
51
|
+
- `redirect`: Follow redirects (one of `follow`, `error`, `manual`, default: `follow`)
|
52
|
+
|
53
|
+
Methods of `Fetch::Response` object:
|
54
|
+
|
55
|
+
- `body`: Response body (String)
|
56
|
+
- `headers`: Response headers
|
57
|
+
- `ok`: Whether the response was successful or not (status code is in the range 200-299)
|
58
|
+
- `redirected`: Whether the response is the result of a redirect
|
59
|
+
- `status`: Status code (e.g. `200`, `404`)
|
60
|
+
- `status_text`: Status text (e.g. `'OK'`, `'Not Found'`)
|
61
|
+
- `url`: Response URL
|
62
|
+
- `json(**args)`: An object that parses the response body as JSON. The arguments are passed to `JSON.parse`
|
63
|
+
|
44
64
|
### Post JSON
|
45
65
|
|
46
66
|
``` ruby
|
@@ -57,23 +77,30 @@ res = fetch('http://example.com', **{
|
|
57
77
|
})
|
58
78
|
```
|
59
79
|
|
60
|
-
### Post
|
80
|
+
### Post application/x-www-form-urlencoded
|
61
81
|
|
62
82
|
``` ruby
|
63
83
|
res = fetch('http://example.com', **{
|
64
84
|
method: 'POST',
|
65
85
|
|
66
|
-
|
67
|
-
|
68
|
-
},
|
69
|
-
|
70
|
-
body: Rack::Multipart.build_multipart(
|
71
|
-
file: Rack::Multipart::UploadedFile.new(io: StringIO.new('foo'), filename: 'foo.txt')
|
86
|
+
body: Fetch::URLSearchParams.new(
|
87
|
+
name: 'Alice'
|
72
88
|
)
|
73
89
|
})
|
74
90
|
```
|
75
91
|
|
76
|
-
|
92
|
+
### Post multipart/form-data
|
93
|
+
|
94
|
+
``` ruby
|
95
|
+
res = fetch('http://example.com', **{
|
96
|
+
method: 'POST',
|
97
|
+
|
98
|
+
body: Fetch::FormData.build(
|
99
|
+
name: 'Alice',
|
100
|
+
file: File.open('path/to/file.txt')
|
101
|
+
)
|
102
|
+
})
|
103
|
+
```
|
77
104
|
|
78
105
|
## Development
|
79
106
|
|
data/lib/fetch/client.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require_relative '../fetch'
|
2
|
+
require_relative 'form_data'
|
3
|
+
require_relative 'headers'
|
4
|
+
require_relative 'response'
|
5
|
+
require_relative 'url_search_params'
|
2
6
|
|
7
|
+
require 'marcel'
|
3
8
|
require 'net/http'
|
4
|
-
require 'net/https'
|
5
|
-
require 'rack/response'
|
6
9
|
require 'singleton'
|
7
10
|
require 'uri'
|
8
11
|
|
@@ -10,15 +13,35 @@ module Fetch
|
|
10
13
|
class Client
|
11
14
|
include Singleton
|
12
15
|
|
13
|
-
def fetch(resource, method: 'GET', headers:
|
16
|
+
def fetch(resource, method: 'GET', headers: [], body: nil, redirect: 'follow', _redirected: false)
|
14
17
|
uri = URI.parse(resource)
|
15
18
|
req = Net::HTTP.const_get(method.capitalize).new(uri)
|
16
19
|
|
20
|
+
headers = Headers.new(headers) unless headers.is_a?(Headers)
|
21
|
+
|
17
22
|
headers.each do |k, v|
|
18
23
|
req[k] = v
|
19
24
|
end
|
20
25
|
|
21
|
-
|
26
|
+
case body
|
27
|
+
when FormData
|
28
|
+
req.set_form body.entries.map {|k, v|
|
29
|
+
if v.is_a?(File)
|
30
|
+
[k, v, {
|
31
|
+
filename: File.basename(v.path),
|
32
|
+
content_type: Marcel::MimeType.for(v) || 'application/octet-stream'
|
33
|
+
}]
|
34
|
+
else
|
35
|
+
[k, v]
|
36
|
+
end
|
37
|
+
}, 'multipart/form-data'
|
38
|
+
when URLSearchParams
|
39
|
+
req['Content-Type'] ||= 'application/x-www-form-urlencoded'
|
40
|
+
|
41
|
+
req.body = body.to_s
|
42
|
+
else
|
43
|
+
req.body = body
|
44
|
+
end
|
22
45
|
|
23
46
|
http = Net::HTTP.new(uri.hostname, uri.port)
|
24
47
|
http.use_ssl = uri.scheme == 'https'
|
@@ -29,23 +52,37 @@ module Fetch
|
|
29
52
|
when Net::HTTPRedirection
|
30
53
|
case redirect
|
31
54
|
when 'follow'
|
32
|
-
fetch(res['
|
55
|
+
fetch(res['Location'], method:, headers:, body:, redirect:, _redirected: true)
|
33
56
|
when 'error'
|
34
|
-
raise RedirectError, "redirected to #{res['
|
57
|
+
raise RedirectError, "redirected to #{res['Location']}"
|
35
58
|
when 'manual'
|
36
|
-
|
59
|
+
to_response(resource, res, _redirected)
|
37
60
|
else
|
38
61
|
raise ArgumentError, "invalid redirect option: #{redirect}"
|
39
62
|
end
|
40
63
|
else
|
41
|
-
|
64
|
+
to_response(resource, res, _redirected)
|
42
65
|
end
|
43
66
|
end
|
44
67
|
|
45
68
|
private
|
46
69
|
|
47
|
-
def
|
48
|
-
|
70
|
+
def to_response(url, res, redirected)
|
71
|
+
headers = Headers.new
|
72
|
+
|
73
|
+
res.each do |k, vs|
|
74
|
+
vs.split(', ').each do |v|
|
75
|
+
headers.append k, v
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
Response.new(
|
80
|
+
url: ,
|
81
|
+
status: res.code.to_i,
|
82
|
+
headers: ,
|
83
|
+
body: res.body,
|
84
|
+
redirected:
|
85
|
+
)
|
49
86
|
end
|
50
87
|
end
|
51
88
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Fetch
|
2
|
+
class FormData
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def self.build(enumerable)
|
6
|
+
data = FormData.new
|
7
|
+
|
8
|
+
enumerable.each do |k, v|
|
9
|
+
data.append k, v
|
10
|
+
end
|
11
|
+
|
12
|
+
data
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@entries = []
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :entries
|
20
|
+
|
21
|
+
def append(key, value)
|
22
|
+
@entries.push [key.to_s, value]
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(key)
|
26
|
+
@entries.reject! {|k,| k == key.to_s }
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(key)
|
30
|
+
@entries.assoc(key.to_s)&.last
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_all(key)
|
34
|
+
@entries.select {|k,| k == key.to_s }.map(&:last)
|
35
|
+
end
|
36
|
+
|
37
|
+
def has(key)
|
38
|
+
@entries.any? {|k,| k == key.to_s }
|
39
|
+
end
|
40
|
+
|
41
|
+
def keys
|
42
|
+
@entries.map(&:first)
|
43
|
+
end
|
44
|
+
|
45
|
+
def set(key, value)
|
46
|
+
delete key
|
47
|
+
append key, value
|
48
|
+
end
|
49
|
+
|
50
|
+
def values
|
51
|
+
@entries.map(&:last)
|
52
|
+
end
|
53
|
+
|
54
|
+
def each(&block)
|
55
|
+
return enum_for(:each) unless block_given?
|
56
|
+
|
57
|
+
@entries.each(&block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Fetch
|
2
|
+
class Headers
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(init = [])
|
6
|
+
@entries = []
|
7
|
+
|
8
|
+
init.each do |k, v|
|
9
|
+
append k, v
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :entries
|
14
|
+
|
15
|
+
def append(key, value)
|
16
|
+
@entries << [key.to_s.downcase, value]
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(key)
|
20
|
+
@entries.delete_if {|k,| k == key.to_s.downcase }
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(key)
|
24
|
+
@entries.select {|k,| k == key.to_s.downcase }.map(&:last).join(', ')
|
25
|
+
end
|
26
|
+
|
27
|
+
def has(key)
|
28
|
+
@entries.any? {|k,| k == key.to_s.downcase }
|
29
|
+
end
|
30
|
+
|
31
|
+
def keys
|
32
|
+
@entries.map(&:first)
|
33
|
+
end
|
34
|
+
|
35
|
+
def set(key, value)
|
36
|
+
delete key
|
37
|
+
append key, value
|
38
|
+
end
|
39
|
+
|
40
|
+
def values
|
41
|
+
@entries.map(&:last)
|
42
|
+
end
|
43
|
+
|
44
|
+
def each(&block)
|
45
|
+
return enum_for(:each) unless block_given?
|
46
|
+
|
47
|
+
@entries.each(&block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rack/utils'
|
3
|
+
|
4
|
+
module Fetch
|
5
|
+
Response = Data.define(:url, :status, :headers, :body, :redirected) {
|
6
|
+
def ok
|
7
|
+
status.between?(200, 299)
|
8
|
+
end
|
9
|
+
|
10
|
+
def status_text
|
11
|
+
Rack::Utils::HTTP_STATUS_CODES[status]
|
12
|
+
end
|
13
|
+
|
14
|
+
def json(**opts)
|
15
|
+
JSON.parse(body, **opts)
|
16
|
+
end
|
17
|
+
}
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Fetch
|
4
|
+
class URLSearchParams
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(options = [])
|
8
|
+
@entries = []
|
9
|
+
|
10
|
+
case options
|
11
|
+
when String
|
12
|
+
initialize(URI.decode_www_form(options.delete_prefix('?')))
|
13
|
+
when Enumerable
|
14
|
+
options.each do |k, v|
|
15
|
+
append k, v
|
16
|
+
end
|
17
|
+
else
|
18
|
+
raise ArgumentError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :entries
|
23
|
+
|
24
|
+
def append(key, value)
|
25
|
+
@entries.push [key.to_s, value]
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(key)
|
29
|
+
@entries.reject! {|k,| k == key.to_s }
|
30
|
+
end
|
31
|
+
|
32
|
+
def get(key)
|
33
|
+
@entries.assoc(key.to_s)&.last
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_all(key)
|
37
|
+
@entries.select {|k,| k == key.to_s }.map(&:last)
|
38
|
+
end
|
39
|
+
|
40
|
+
def has(key)
|
41
|
+
@entries.any? {|k,| k == key.to_s }
|
42
|
+
end
|
43
|
+
|
44
|
+
def keys
|
45
|
+
@entries.map(&:first)
|
46
|
+
end
|
47
|
+
|
48
|
+
def set(key, value)
|
49
|
+
delete key
|
50
|
+
append key, value
|
51
|
+
end
|
52
|
+
|
53
|
+
def sort
|
54
|
+
@entries.sort_by!(&:first)
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
URI.encode_www_form(@entries)
|
59
|
+
end
|
60
|
+
|
61
|
+
def values
|
62
|
+
@entries.map(&:last)
|
63
|
+
end
|
64
|
+
|
65
|
+
def each(&block)
|
66
|
+
return enum_for(:each) unless block_given?
|
67
|
+
|
68
|
+
@entries.each(&block)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/fetch/version.rb
CHANGED
data/lib/fetch.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fetch-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keita Urashima
|
@@ -10,6 +10,20 @@ bindir: exe
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2024-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: marcel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: net-http
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -79,6 +93,10 @@ files:
|
|
79
93
|
- lib/fetch.rb
|
80
94
|
- lib/fetch/api.rb
|
81
95
|
- lib/fetch/client.rb
|
96
|
+
- lib/fetch/form_data.rb
|
97
|
+
- lib/fetch/headers.rb
|
98
|
+
- lib/fetch/response.rb
|
99
|
+
- lib/fetch/url_search_params.rb
|
82
100
|
- lib/fetch/version.rb
|
83
101
|
- sig/fetch.rbs
|
84
102
|
homepage: https://github.com/ursm/fetch-api
|
@@ -95,7 +113,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
113
|
requirements:
|
96
114
|
- - ">="
|
97
115
|
- !ruby/object:Gem::Version
|
98
|
-
version: 3.
|
116
|
+
version: 3.2.0
|
99
117
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
118
|
requirements:
|
101
119
|
- - ">="
|