micky 0.8.1 → 0.8.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 +4 -4
- data/lib/micky/version.rb +1 -1
- metadata +1 -8
- data/.github/workflows/ci.yml +0 -20
- data/Rakefile +0 -9
- data/test/max_response_size_test.rb +0 -89
- data/test/redirect_test.rb +0 -22
- data/test/test_helper.rb +0 -115
- data/test/total_timeout_test.rb +0 -44
- data/test/uri_test.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bfd554068deaa3274c2da419bde9eb75ab57a766990237c5ca80480acab41f14
|
|
4
|
+
data.tar.gz: 2457466811bcc288ea57b1cd3e90eb3e5425eefb3e5e409cae6eb7e51420723e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a134919cf3a80ac8014f62c0cf48fca4519d3a680af289c8ae06e01f11e39a0279e571c4d0ee9dd6d4e30008d7cdc4190fff4d45a8f88195fdd1a365ff04615
|
|
7
|
+
data.tar.gz: 866941a834365dcb0d3d3dff0f37228e52a7412299d13ee8867329aa688025b103fc625e0f12765ab3184b1e10f2bc9a79d24b9e664c4c2dd8dd459b6b1a6058
|
data/lib/micky/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: micky
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rafael Masson
|
|
@@ -88,21 +88,14 @@ executables: []
|
|
|
88
88
|
extensions: []
|
|
89
89
|
extra_rdoc_files: []
|
|
90
90
|
files:
|
|
91
|
-
- ".github/workflows/ci.yml"
|
|
92
91
|
- LICENSE.txt
|
|
93
92
|
- README.md
|
|
94
|
-
- Rakefile
|
|
95
93
|
- lib/micky.rb
|
|
96
94
|
- lib/micky/errors.rb
|
|
97
95
|
- lib/micky/request.rb
|
|
98
96
|
- lib/micky/response.rb
|
|
99
97
|
- lib/micky/uri.rb
|
|
100
98
|
- lib/micky/version.rb
|
|
101
|
-
- test/max_response_size_test.rb
|
|
102
|
-
- test/redirect_test.rb
|
|
103
|
-
- test/test_helper.rb
|
|
104
|
-
- test/total_timeout_test.rb
|
|
105
|
-
- test/uri_test.rb
|
|
106
99
|
homepage: https://github.com/rafbm/micky
|
|
107
100
|
licenses:
|
|
108
101
|
- MIT
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
pull_request:
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
test:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
strategy:
|
|
11
|
-
fail-fast: false
|
|
12
|
-
matrix:
|
|
13
|
-
ruby: ['3.3', '3.4', '4.0']
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@v4
|
|
16
|
-
- uses: ruby/setup-ruby@v1
|
|
17
|
-
with:
|
|
18
|
-
ruby-version: ${{ matrix.ruby }}
|
|
19
|
-
bundler-cache: true
|
|
20
|
-
- run: bundle exec rake test
|
data/Rakefile
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
require 'test_helper'
|
|
2
|
-
|
|
3
|
-
describe 'Micky max_response_size' do
|
|
4
|
-
it 'reads the whole body when the option is not set' do
|
|
5
|
-
response = Micky.get(server.url('/ok'))
|
|
6
|
-
assert_equal 1000, response.body.bytesize
|
|
7
|
-
refute response.truncated?
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
it 'leaves a body under the limit alone' do
|
|
11
|
-
response = Micky.get(server.url('/ok'), max_response_size: 100_000)
|
|
12
|
-
assert_equal 1000, response.body.bytesize
|
|
13
|
-
refute response.truncated?
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
it 'accepts a body exactly at the limit' do
|
|
17
|
-
response = Micky.get(server.url('/ok'), max_response_size: 1000)
|
|
18
|
-
assert_equal 1000, response.body.bytesize
|
|
19
|
-
refute response.truncated?
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
describe 'when the server sends more than the limit' do
|
|
23
|
-
let(:limit) { 50_000 }
|
|
24
|
-
|
|
25
|
-
it 'returns nil' do
|
|
26
|
-
assert_nil Micky.get(server.url('/flood'), max_response_size: limit)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it 'raises Micky::TooLargeResponse when asked to raise' do
|
|
30
|
-
error = assert_raises(Micky::TooLargeResponse) do
|
|
31
|
-
Micky.get(server.url('/flood'), max_response_size: limit, raise_errors: true)
|
|
32
|
-
end
|
|
33
|
-
assert_equal 'Response larger than 50000 bytes', error.message
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it 'gives up as soon as it has read the limit, rather than draining the server' do
|
|
37
|
-
# Unbounded, this reads a gigabyte
|
|
38
|
-
assert_operator elapsed { Micky.get(server.url('/flood'), max_response_size: limit) }, :<, 1.0
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
describe 'with truncate: true' do
|
|
42
|
-
let(:response) { Micky.get(server.url('/flood'), max_response_size: limit, truncate: true) }
|
|
43
|
-
|
|
44
|
-
it 'returns the first bytes up to the limit' do
|
|
45
|
-
assert_equal limit, response.body.bytesize
|
|
46
|
-
assert response.truncated?
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
it 'does not resume reading when the body is asked for again' do
|
|
50
|
-
response.body
|
|
51
|
-
assert_equal limit, response.body.bytesize
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it 'reports a content length that matches what it holds' do
|
|
55
|
-
assert_equal limit, response.content_length
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
it 'returns as soon as it has enough, rather than draining the server' do
|
|
59
|
-
assert_operator elapsed { response }, :<, 1.0
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
it 'counts what it keeps, not what came off the wire, when Net::HTTP decompresses' do
|
|
63
|
-
response = Micky.get(server.url('/gzipped-flood'), max_response_size: limit, truncate: true)
|
|
64
|
-
assert_equal limit, response.body.bytesize
|
|
65
|
-
assert response.body.start_with?('xxx')
|
|
66
|
-
assert response.truncated?
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
it 'caps a redirect body without treating it as too large' do
|
|
72
|
-
# The 302 itself carries a gigabyte; only the hop after it is a real response
|
|
73
|
-
time = elapsed {
|
|
74
|
-
response = Micky.get(server.url('/fat-redirect'), max_response_size: 50_000)
|
|
75
|
-
assert_equal 1000, response.body.bytesize
|
|
76
|
-
refute response.truncated?
|
|
77
|
-
}
|
|
78
|
-
assert_operator time, :<, 2.0
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
it 'keeps an empty body empty rather than nil' do
|
|
82
|
-
assert_equal '', Micky.get(server.url('/chunked-empty')).body
|
|
83
|
-
assert_equal '', Micky.get(server.url('/chunked-empty'), max_response_size: 100).body
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
it 'keeps a HEAD body nil' do
|
|
87
|
-
assert_nil Micky.head(server.url('/ok'), max_response_size: 100).body
|
|
88
|
-
end
|
|
89
|
-
end
|
data/test/redirect_test.rb
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
require 'test_helper'
|
|
2
|
-
|
|
3
|
-
describe 'Micky redirects' do
|
|
4
|
-
# The test server listens on a random port, so a redirect that loses the port
|
|
5
|
-
# goes to 127.0.0.1:80 and fails
|
|
6
|
-
it 'keeps the port when the Location is host-relative' do
|
|
7
|
-
response = Micky.get(server.url('/host-relative-redirect'))
|
|
8
|
-
assert_equal 1000, response.body.bytesize
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
it 'keeps the port when the Location is path-relative' do
|
|
12
|
-
response = Micky.get(server.url('/dir/path-relative-redirect'))
|
|
13
|
-
assert_equal 1000, response.body.bytesize
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
it 'redirects to the resolved URL' do
|
|
17
|
-
log = StringIO.new
|
|
18
|
-
Micky.logger = Logger.new(log, level: Logger::DEBUG)
|
|
19
|
-
Micky.get(server.url('/host-relative-redirect'))
|
|
20
|
-
assert_includes log.string, "302 redirect to #{server.url('/ok')}"
|
|
21
|
-
end
|
|
22
|
-
end
|
data/test/test_helper.rb
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
require 'minitest/autorun'
|
|
2
|
-
require 'micky'
|
|
3
|
-
require 'socket'
|
|
4
|
-
require 'logger'
|
|
5
|
-
|
|
6
|
-
# WebMock and friends intercept above Net::HTTP’s body reading, so they can’t
|
|
7
|
-
# test how many bytes come off the socket or how long we wait. This serves real
|
|
8
|
-
# HTTP over a real socket instead.
|
|
9
|
-
class TestServer
|
|
10
|
-
attr_reader :port
|
|
11
|
-
|
|
12
|
-
RESPONSES = {
|
|
13
|
-
# A small, ordinary response
|
|
14
|
-
'/ok' => ->(socket) {
|
|
15
|
-
body = 'hello' * 200
|
|
16
|
-
socket.write "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: #{body.bytesize}\r\n\r\n"
|
|
17
|
-
socket.write body
|
|
18
|
-
},
|
|
19
|
-
'/dir/ok' => ->(socket) { RESPONSES['/ok'].call(socket) },
|
|
20
|
-
# A 200 whose chunked body is empty; Net::HTTP yields no chunk for it
|
|
21
|
-
'/chunked-empty' => ->(socket) {
|
|
22
|
-
socket.write "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n"
|
|
23
|
-
},
|
|
24
|
-
# Redirects with a host-relative Location
|
|
25
|
-
'/host-relative-redirect' => ->(socket) {
|
|
26
|
-
socket.write "HTTP/1.1 302 Found\r\nLocation: /ok\r\nContent-Length: 0\r\n\r\n"
|
|
27
|
-
},
|
|
28
|
-
# Redirects with a path-relative Location, to /dir/ok
|
|
29
|
-
'/dir/path-relative-redirect' => ->(socket) {
|
|
30
|
-
socket.write "HTTP/1.1 302 Found\r\nLocation: ok\r\nContent-Length: 0\r\n\r\n"
|
|
31
|
-
},
|
|
32
|
-
# Streams for as long as anyone reads
|
|
33
|
-
'/flood' => ->(socket) {
|
|
34
|
-
socket.write "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 1000000000\r\n\r\n"
|
|
35
|
-
loop { socket.write('x' * 8192) }
|
|
36
|
-
},
|
|
37
|
-
# One byte at a time, never idle long enough to trip a per-read timeout
|
|
38
|
-
'/drip' => ->(socket) {
|
|
39
|
-
socket.write "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 100000\r\n\r\n"
|
|
40
|
-
loop { socket.write('x'); socket.flush; sleep 0.2 }
|
|
41
|
-
},
|
|
42
|
-
# A redirect that itself carries a huge body
|
|
43
|
-
'/fat-redirect' => ->(socket) {
|
|
44
|
-
socket.write "HTTP/1.1 302 Found\r\nLocation: /ok\r\nContent-Length: 1000000000\r\n\r\n"
|
|
45
|
-
loop { socket.write('x' * 8192) }
|
|
46
|
-
},
|
|
47
|
-
# gzip, so Net::HTTP’s transparent decoding is in the path
|
|
48
|
-
'/gzipped-flood' => ->(socket) {
|
|
49
|
-
require 'zlib'
|
|
50
|
-
socket.write "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\n\r\n"
|
|
51
|
-
writer = Zlib::GzipWriter.new(socket)
|
|
52
|
-
loop { writer.write('x' * 8192) }
|
|
53
|
-
},
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
def initialize
|
|
57
|
-
@server = TCPServer.new('127.0.0.1', 0)
|
|
58
|
-
@port = @server.addr[1]
|
|
59
|
-
@thread = Thread.new { accept_loop }
|
|
60
|
-
@thread.abort_on_exception = false
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def url(path) = "http://127.0.0.1:#{@port}#{path}"
|
|
64
|
-
|
|
65
|
-
def stop
|
|
66
|
-
@thread.kill
|
|
67
|
-
@server.close
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
private
|
|
71
|
-
|
|
72
|
-
def accept_loop
|
|
73
|
-
loop do
|
|
74
|
-
socket = @server.accept
|
|
75
|
-
Thread.start(socket) do |s|
|
|
76
|
-
begin
|
|
77
|
-
path = s.gets.to_s.split(' ')[1]
|
|
78
|
-
while (line = s.gets) && line != "\r\n"; end
|
|
79
|
-
RESPONSES.fetch(path, ->(so) { so.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n") }).call(s)
|
|
80
|
-
rescue StandardError, Timeout::Error
|
|
81
|
-
# The client hung up, as most of these specs expect
|
|
82
|
-
ensure
|
|
83
|
-
s.close rescue nil
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
class MickyTest < Minitest::Spec
|
|
91
|
-
def self.server
|
|
92
|
-
@server ||= begin
|
|
93
|
-
server = TestServer.new
|
|
94
|
-
Minitest.after_run { server.stop }
|
|
95
|
-
server
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def server = self.class.server
|
|
100
|
-
|
|
101
|
-
def setup
|
|
102
|
-
Micky::DEFAULTS.each { |key, value| Micky.public_send("#{key}=", value) }
|
|
103
|
-
Micky.logger = Logger.new(nil)
|
|
104
|
-
Micky.skip_resolve = true
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def elapsed
|
|
108
|
-
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
109
|
-
yield
|
|
110
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Every `describe` block inherits the server and the defaults reset above
|
|
115
|
-
Minitest::Spec.register_spec_type(//, MickyTest)
|
data/test/total_timeout_test.rb
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
require 'test_helper'
|
|
2
|
-
|
|
3
|
-
describe 'Micky total_timeout' do
|
|
4
|
-
it 'defaults to 20 seconds' do
|
|
5
|
-
assert_equal 20, Micky.total_timeout
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
it 'leaves a fast response alone' do
|
|
9
|
-
assert_equal 1000, Micky.get(server.url('/ok'), total_timeout: 5).body.bytesize
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
describe 'against a server that drips bytes slower than it is read' do
|
|
13
|
-
# Every read succeeds, so `timeout` never fires however low it is set
|
|
14
|
-
let(:opts) { { timeout: 5, total_timeout: 1 } }
|
|
15
|
-
|
|
16
|
-
it 'gives up at the deadline' do
|
|
17
|
-
assert_in_delta 1, elapsed { Micky.get(server.url('/drip'), **opts) }, 0.5
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
it 'returns nil by default' do
|
|
21
|
-
assert_nil Micky.get(server.url('/drip'), **opts)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
it 'raises Micky::TotalTimeout when asked to raise' do
|
|
25
|
-
assert_raises(Micky::TotalTimeout) do
|
|
26
|
-
Micky.get(server.url('/drip'), raise_errors: true, **opts)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it 'never returns without the deadline, which is what makes it necessary' do
|
|
31
|
-
assert_raises(Timeout::Error) do
|
|
32
|
-
Timeout.timeout(2) { Micky.get(server.url('/drip'), timeout: 5, total_timeout: nil) }
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
it 'does not relabel an enclosing block’s expiry as its own' do
|
|
38
|
-
assert_raises(ArgumentError) do
|
|
39
|
-
Timeout.timeout(0.2, ArgumentError) do
|
|
40
|
-
Micky.get(server.url('/drip'), timeout: 5, total_timeout: 30)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
data/test/uri_test.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
require 'test_helper'
|
|
2
|
-
|
|
3
|
-
describe 'Micky.URI' do
|
|
4
|
-
# Ruby only prints the deprecation under $VERBOSE, so turn it on to see it
|
|
5
|
-
def without_warnings
|
|
6
|
-
verbose, $VERBOSE = $VERBOSE, true
|
|
7
|
-
_, err = capture_io { yield }
|
|
8
|
-
err
|
|
9
|
-
ensure
|
|
10
|
-
$VERBOSE = verbose
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it 'escapes a URL that ::URI.parse rejects' do
|
|
14
|
-
uri = nil
|
|
15
|
-
err = without_warnings { uri = Micky.URI('http://example.com/a b?x=1|2') }
|
|
16
|
-
assert_equal 'http://example.com/a%20b?x=1%7C2', uri.to_s
|
|
17
|
-
assert_empty err
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
it 'extracts http URLs from text' do
|
|
21
|
-
urls = nil
|
|
22
|
-
err = without_warnings { urls = Micky::URI.extract('see http://a.com and ftp://c.com') }
|
|
23
|
-
assert_equal ['http://a.com'], urls
|
|
24
|
-
assert_empty err
|
|
25
|
-
end
|
|
26
|
-
end
|