sinew 2.0.5 → 3.0.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/.github/workflows/test.yml +26 -0
- data/.rubocop.yml +9 -6
- data/.vscode/settings.json +0 -10
- data/Gemfile +9 -0
- data/README.md +13 -17
- data/Rakefile +33 -18
- data/bin/sinew +2 -0
- data/lib/sinew.rb +0 -1
- data/lib/sinew/connection.rb +52 -0
- data/lib/sinew/connection/log_formatter.rb +22 -0
- data/lib/sinew/connection/rate_limit.rb +29 -0
- data/lib/sinew/core_ext.rb +1 -1
- data/lib/sinew/dsl.rb +2 -1
- data/lib/sinew/main.rb +7 -55
- data/lib/sinew/output.rb +7 -23
- data/lib/sinew/request.rb +20 -71
- data/lib/sinew/response.rb +8 -57
- data/lib/sinew/runtime_options.rb +4 -4
- data/lib/sinew/version.rb +1 -1
- data/sample.sinew +2 -2
- data/sinew.gemspec +16 -17
- metadata +41 -99
- data/.travis.yml +0 -4
- data/lib/sinew/cache.rb +0 -79
- data/test/legacy/eu.httpbin.org/head/redirect,3 +0 -51
- data/test/legacy/eu.httpbin.org/head/status,500 +0 -1
- data/test/legacy/eu.httpbin.org/redirect,3 +0 -11
- data/test/legacy/eu.httpbin.org/status,500 +0 -1
- data/test/legacy/legacy.sinew +0 -2
- data/test/recipes/array_header.sinew +0 -6
- data/test/recipes/basic.sinew +0 -8
- data/test/recipes/dups.sinew +0 -7
- data/test/recipes/implicit_header.sinew +0 -5
- data/test/recipes/limit.sinew +0 -11
- data/test/recipes/noko.sinew +0 -9
- data/test/recipes/uri.sinew +0 -11
- data/test/recipes/xml.sinew +0 -8
- data/test/test.html +0 -45
- data/test/test_cache.rb +0 -69
- data/test/test_helper.rb +0 -126
- data/test/test_legacy.rb +0 -23
- data/test/test_main.rb +0 -34
- data/test/test_nokogiri_ext.rb +0 -18
- data/test/test_output.rb +0 -56
- data/test/test_recipes.rb +0 -60
- data/test/test_requests.rb +0 -164
- data/test/test_utf8.rb +0 -39
data/.travis.yml
DELETED
data/lib/sinew/cache.rb
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'tempfile'
|
3
|
-
|
4
|
-
#
|
5
|
-
# This class handles the caching of http responses on disk.
|
6
|
-
#
|
7
|
-
|
8
|
-
module Sinew
|
9
|
-
class Cache
|
10
|
-
attr_reader :sinew
|
11
|
-
|
12
|
-
def initialize(sinew)
|
13
|
-
@sinew = sinew
|
14
|
-
end
|
15
|
-
|
16
|
-
def get(request)
|
17
|
-
body = read_if_exist(body_path(request))
|
18
|
-
return nil if !body
|
19
|
-
|
20
|
-
head = read_if_exist(head_path(request))
|
21
|
-
Response.from_cache(request, body, head)
|
22
|
-
end
|
23
|
-
|
24
|
-
def set(response)
|
25
|
-
body_path = body_path(response.request)
|
26
|
-
head_path = head_path(response.request)
|
27
|
-
|
28
|
-
FileUtils.mkdir_p(File.dirname(body_path))
|
29
|
-
FileUtils.mkdir_p(File.dirname(head_path))
|
30
|
-
|
31
|
-
# write body, and head if necessary
|
32
|
-
atomic_write(body_path, response.body)
|
33
|
-
if head_necessary?(response)
|
34
|
-
head = JSON.pretty_generate(response.head_as_json)
|
35
|
-
atomic_write(head_path, head)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def root_dir
|
40
|
-
sinew.options[:cache]
|
41
|
-
end
|
42
|
-
protected :root_dir
|
43
|
-
|
44
|
-
def head_necessary?(response)
|
45
|
-
response.error? || response.redirected?
|
46
|
-
end
|
47
|
-
protected :head_necessary?
|
48
|
-
|
49
|
-
def body_path(request)
|
50
|
-
"#{root_dir}/#{request.cache_key}"
|
51
|
-
end
|
52
|
-
protected :body_path
|
53
|
-
|
54
|
-
def head_path(request)
|
55
|
-
body_path = body_path(request)
|
56
|
-
dir, base = File.dirname(body_path), File.basename(body_path)
|
57
|
-
"#{dir}/head/#{base}"
|
58
|
-
end
|
59
|
-
protected :head_path
|
60
|
-
|
61
|
-
def read_if_exist(path)
|
62
|
-
if File.exist?(path)
|
63
|
-
IO.read(path, mode: 'r:UTF-8')
|
64
|
-
end
|
65
|
-
end
|
66
|
-
protected :read_if_exist
|
67
|
-
|
68
|
-
def atomic_write(path, data)
|
69
|
-
tmp = Tempfile.new('sinew', encoding: 'UTF-8')
|
70
|
-
tmp.write(data)
|
71
|
-
tmp.close
|
72
|
-
FileUtils.chmod(0o644, tmp.path)
|
73
|
-
FileUtils.mv(tmp.path, path)
|
74
|
-
ensure
|
75
|
-
FileUtils.rm(tmp.path, force: true)
|
76
|
-
end
|
77
|
-
protected :atomic_write
|
78
|
-
end
|
79
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
HTTP/1.1 302 FOUND
|
2
|
-
Connection: keep-alive
|
3
|
-
Server: gunicorn/19.7.1
|
4
|
-
Date: Wed, 02 May 2018 20:55:20 GMT
|
5
|
-
Content-Type: text/html; charset=utf-8
|
6
|
-
Content-Length: 247
|
7
|
-
Location: /relative-redirect/2
|
8
|
-
Access-Control-Allow-Origin: *
|
9
|
-
Access-Control-Allow-Credentials: true
|
10
|
-
X-Powered-By: Flask
|
11
|
-
X-Processed-Time: 0
|
12
|
-
Via: 1.1 vegur
|
13
|
-
|
14
|
-
HTTP/1.1 302 FOUND
|
15
|
-
Connection: keep-alive
|
16
|
-
Server: gunicorn/19.7.1
|
17
|
-
Date: Wed, 02 May 2018 20:55:20 GMT
|
18
|
-
Content-Type: text/html; charset=utf-8
|
19
|
-
Content-Length: 0
|
20
|
-
Location: /relative-redirect/1
|
21
|
-
Access-Control-Allow-Origin: *
|
22
|
-
Access-Control-Allow-Credentials: true
|
23
|
-
X-Powered-By: Flask
|
24
|
-
X-Processed-Time: 0
|
25
|
-
Via: 1.1 vegur
|
26
|
-
|
27
|
-
HTTP/1.1 302 FOUND
|
28
|
-
Connection: keep-alive
|
29
|
-
Server: gunicorn/19.7.1
|
30
|
-
Date: Wed, 02 May 2018 20:55:20 GMT
|
31
|
-
Content-Type: text/html; charset=utf-8
|
32
|
-
Content-Length: 0
|
33
|
-
Location: /get
|
34
|
-
Access-Control-Allow-Origin: *
|
35
|
-
Access-Control-Allow-Credentials: true
|
36
|
-
X-Powered-By: Flask
|
37
|
-
X-Processed-Time: 0
|
38
|
-
Via: 1.1 vegur
|
39
|
-
|
40
|
-
HTTP/1.1 200 OK
|
41
|
-
Connection: keep-alive
|
42
|
-
Server: gunicorn/19.7.1
|
43
|
-
Date: Wed, 02 May 2018 20:55:20 GMT
|
44
|
-
Content-Type: application/json
|
45
|
-
Access-Control-Allow-Origin: *
|
46
|
-
Access-Control-Allow-Credentials: true
|
47
|
-
X-Powered-By: Flask
|
48
|
-
X-Processed-Time: 0
|
49
|
-
Content-Length: 220
|
50
|
-
Via: 1.1 vegur
|
51
|
-
|
@@ -1 +0,0 @@
|
|
1
|
-
CURLER_ERROR curl error (22)
|
@@ -1 +0,0 @@
|
|
1
|
-
|
data/test/legacy/legacy.sinew
DELETED
data/test/recipes/basic.sinew
DELETED
data/test/recipes/dups.sinew
DELETED
data/test/recipes/limit.sinew
DELETED
data/test/recipes/noko.sinew
DELETED
data/test/recipes/uri.sinew
DELETED
data/test/recipes/xml.sinew
DELETED
data/test/test.html
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
<html>
|
2
|
-
|
3
|
-
<head>
|
4
|
-
<title>Title</title>
|
5
|
-
<script>
|
6
|
-
alert("alert 1");
|
7
|
-
alert("alert 2");
|
8
|
-
</script>
|
9
|
-
</head>
|
10
|
-
|
11
|
-
<body>
|
12
|
-
<div id="main">
|
13
|
-
<span class="class1"> text1 </span>
|
14
|
-
<span class="class2"> text2 </span>
|
15
|
-
|
16
|
-
<!-- for test_normalize -->
|
17
|
-
<div id="element"> text </div>
|
18
|
-
<div class="e"> text1 </div>
|
19
|
-
<div class="e"> text2 </div>
|
20
|
-
</div>
|
21
|
-
|
22
|
-
<div id="nokogiri_ext">
|
23
|
-
<ul>
|
24
|
-
<li>hello</li>
|
25
|
-
<li>world</li>
|
26
|
-
</ul>
|
27
|
-
<div>
|
28
|
-
a
|
29
|
-
<p>b
|
30
|
-
<span>c</span>
|
31
|
-
</p>
|
32
|
-
<p>b
|
33
|
-
<span>c</span>
|
34
|
-
</p>
|
35
|
-
</div>
|
36
|
-
</div>
|
37
|
-
|
38
|
-
<div id="text_util">
|
39
|
-
<!-- a comment that should be removed -->
|
40
|
-
<div class="will_be_removed" />
|
41
|
-
<a class="will_be_preserved" />
|
42
|
-
</div>
|
43
|
-
</body>
|
44
|
-
|
45
|
-
</html>
|
data/test/test_cache.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
|
3
|
-
class TestCache < MiniTest::Test
|
4
|
-
def test_get
|
5
|
-
2.times do
|
6
|
-
sinew.dsl.get('http://httpbin.org/get', c: 3, d: 4)
|
7
|
-
end
|
8
|
-
if !test_network?
|
9
|
-
assert_requested :get, 'http://httpbin.org/get?c=3&d=4', times: 1
|
10
|
-
end
|
11
|
-
assert_equal 1, sinew.request_count
|
12
|
-
assert_equal({ c: '3', d: '4' }, sinew.dsl.json[:args])
|
13
|
-
assert File.exist?("#{TMP}/httpbin.org/get,c=3,d=4")
|
14
|
-
assert !File.exist?("#{TMP}/httpbin.org/head/get,c=3,d=4")
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_post
|
18
|
-
2.times do
|
19
|
-
sinew.dsl.post('http://httpbin.org/post', c: 5, d: 6)
|
20
|
-
end
|
21
|
-
if !test_network?
|
22
|
-
assert_requested :post, 'http://httpbin.org/post', times: 1
|
23
|
-
end
|
24
|
-
assert_equal 1, sinew.request_count
|
25
|
-
assert_equal({ c: '5', d: '6' }, sinew.dsl.json[:form])
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_redirect
|
29
|
-
2.times do
|
30
|
-
sinew.dsl.get('http://httpbin.org/redirect/2')
|
31
|
-
end
|
32
|
-
if !test_network?
|
33
|
-
assert_requested :get, 'http://httpbin.org/redirect/2', times: 1
|
34
|
-
assert_requested :get, 'http://httpbin.org/redirect/1', times: 1
|
35
|
-
assert_requested :get, 'http://httpbin.org/get', times: 1
|
36
|
-
end
|
37
|
-
assert_equal 1, sinew.request_count
|
38
|
-
assert_equal 'http://httpbin.org/get', sinew.dsl.url
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_error
|
42
|
-
# gotta set this or the retries mess up our request counts
|
43
|
-
sinew.runtime_options.retries = 0
|
44
|
-
assert_output(/failed with 500/) do
|
45
|
-
2.times do
|
46
|
-
sinew.dsl.get('http://httpbin.org/status/500')
|
47
|
-
end
|
48
|
-
end
|
49
|
-
if !test_network?
|
50
|
-
assert_requested :get, 'http://httpbin.org/status/500', times: 1
|
51
|
-
assert_equal '500', sinew.dsl.raw
|
52
|
-
end
|
53
|
-
assert_equal 1, sinew.request_count
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_timeout
|
57
|
-
return if test_network?
|
58
|
-
|
59
|
-
# gotta set this or the retries mess up our request counts
|
60
|
-
sinew.runtime_options.retries = 0
|
61
|
-
assert_output(/failed with 999/) do
|
62
|
-
2.times do
|
63
|
-
sinew.dsl.get('http://httpbin.org/delay/1')
|
64
|
-
end
|
65
|
-
end
|
66
|
-
assert_requested :get, 'http://httpbin.org/delay/1', times: 1
|
67
|
-
assert_equal 'execution expired', sinew.dsl.raw
|
68
|
-
end
|
69
|
-
end
|
data/test/test_helper.rb
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'minitest/pride'
|
3
|
-
require 'webmock/minitest' unless ENV['SINEW_TEST_NETWORK']
|
4
|
-
|
5
|
-
# to run one test, do this:
|
6
|
-
# TEST=test/unit/query_test.rb TESTOPTS="--name=test_parse" rake
|
7
|
-
|
8
|
-
# a hint to sinew, so that it'll do things like set rate limit to zero
|
9
|
-
ENV['SINEW_TEST'] = '1'
|
10
|
-
|
11
|
-
# Normally the Rakefile takes care of this, but it's handy to have it here when
|
12
|
-
# running tests individually.
|
13
|
-
$LOAD_PATH.unshift("#{__dir__}/../lib")
|
14
|
-
require 'sinew'
|
15
|
-
|
16
|
-
class MiniTest::Test
|
17
|
-
TMP = '/tmp/_test_sinew'.freeze
|
18
|
-
HTML = File.read("#{__dir__}/test.html")
|
19
|
-
|
20
|
-
def setup
|
21
|
-
super
|
22
|
-
|
23
|
-
# prepare TMP
|
24
|
-
FileUtils.rm_rf(TMP)
|
25
|
-
FileUtils.mkdir_p(TMP)
|
26
|
-
|
27
|
-
stub_network unless test_network?
|
28
|
-
end
|
29
|
-
|
30
|
-
def sinew
|
31
|
-
@sinew ||= Sinew::Main.new(cache: TMP, quiet: true, recipe: "#{TMP}/ignore.sinew")
|
32
|
-
end
|
33
|
-
protected :sinew
|
34
|
-
|
35
|
-
def test_network?
|
36
|
-
!!ENV['SINEW_TEST_NETWORK']
|
37
|
-
end
|
38
|
-
protected :test_network?
|
39
|
-
|
40
|
-
# mock requests, patterned on httpbin
|
41
|
-
def stub_network
|
42
|
-
stub_request(:get, %r{http://[^/]+/html}).to_return(method(:respond_html))
|
43
|
-
stub_request(:get, %r{http://[^/]+/get\b}).to_return(method(:respond_echo))
|
44
|
-
stub_request(:post, %r{http://[^/]+/post\b}).to_return(method(:respond_echo))
|
45
|
-
stub_request(:get, %r{http://[^/]+/status/\d+}).to_return(method(:respond_status))
|
46
|
-
stub_request(:get, %r{http://[^/]+/(relative-)?redirect/\d+}).to_return(method(:respond_redirect))
|
47
|
-
stub_request(:get, %r{http://[^/]+/delay/\d+}).to_timeout
|
48
|
-
stub_request(:get, %r{http://[^/]+/xml}).to_return(method(:respond_xml))
|
49
|
-
end
|
50
|
-
protected :stub_network
|
51
|
-
|
52
|
-
#
|
53
|
-
# respond_xxx helpers
|
54
|
-
#
|
55
|
-
|
56
|
-
def respond_html(_request)
|
57
|
-
# this html was carefully chosen to somewhat match httpbin.org/html
|
58
|
-
html = <<~EOF
|
59
|
-
<body>
|
60
|
-
<h1>Herman Melville - Moby-Dick</h1>
|
61
|
-
</body>
|
62
|
-
EOF
|
63
|
-
{ body: html }
|
64
|
-
end
|
65
|
-
protected :respond_html
|
66
|
-
|
67
|
-
def respond_xml(_request)
|
68
|
-
# this xml was carefully chosen to somewhat match httpbin.org/xml
|
69
|
-
xml = <<~EOF
|
70
|
-
<!-- A SAMPLE set of slides -->
|
71
|
-
<slideshow>
|
72
|
-
<slide type="all">
|
73
|
-
<title>Wake up to WonderWidgets!</title>
|
74
|
-
</slide>
|
75
|
-
<slide type="all">
|
76
|
-
<title>Overview</title>
|
77
|
-
</slide>
|
78
|
-
</slideshow>
|
79
|
-
EOF
|
80
|
-
{ body: xml }
|
81
|
-
end
|
82
|
-
protected :respond_xml
|
83
|
-
|
84
|
-
def respond_echo(request)
|
85
|
-
response = {}
|
86
|
-
response[:headers] = request.headers
|
87
|
-
|
88
|
-
# args
|
89
|
-
response[:args] = if request.uri.query
|
90
|
-
CGI.parse(request.uri.query).map { |k, v| [ k, v.first ] }.to_h
|
91
|
-
else
|
92
|
-
{}
|
93
|
-
end
|
94
|
-
|
95
|
-
# form
|
96
|
-
if request.headers['Content-Type'] == 'application/x-www-form-urlencoded'
|
97
|
-
response[:form] = CGI.parse(request.body).map { |k, v| [ k, v.first ] }.to_h
|
98
|
-
end
|
99
|
-
|
100
|
-
# json
|
101
|
-
if request.headers['Content-Type'] == 'application/json'
|
102
|
-
response[:json] = JSON.parse(request.body)
|
103
|
-
end
|
104
|
-
|
105
|
-
{
|
106
|
-
headers: { 'Content-Type' => 'application/json' },
|
107
|
-
body: response.to_json,
|
108
|
-
}
|
109
|
-
end
|
110
|
-
protected :respond_echo
|
111
|
-
|
112
|
-
def respond_status(request)
|
113
|
-
status = request.uri.to_s.split('/').last.to_i
|
114
|
-
{ body: status.to_s, status: status }
|
115
|
-
end
|
116
|
-
protected :respond_status
|
117
|
-
|
118
|
-
def respond_redirect(request)
|
119
|
-
parts = request.uri.to_s.split('/')
|
120
|
-
path, count = parts[-2], parts[-1].to_i
|
121
|
-
url = count == 1 ? '/get' : "/#{path}/#{count - 1}"
|
122
|
-
url = "http://example#{url}" if path =~ /absolute/
|
123
|
-
{ status: 302, headers: { 'Location' => url } }
|
124
|
-
end
|
125
|
-
protected :respond_redirect
|
126
|
-
end
|