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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecea094f30e0551b84949260cd3c78b34673cb69bcefdcd746b506938adffeab
4
- data.tar.gz: 98874aec481b78fdb48fe0ae50248ec51683e299b3a6c6770706a706b6266894
3
+ metadata.gz: bfd554068deaa3274c2da419bde9eb75ab57a766990237c5ca80480acab41f14
4
+ data.tar.gz: 2457466811bcc288ea57b1cd3e90eb3e5425eefb3e5e409cae6eb7e51420723e
5
5
  SHA512:
6
- metadata.gz: 6a0f768ceb90965a4203f30c1d68de2a82f328f3fa5cbf18a68ebe94f54c0018cd2df1c2ef5399af64bae8221868368407bd98d60cbdf78beb59c31ee80a7ae7
7
- data.tar.gz: a277768598966377bd039e5a4df2340ac679d10728d9b913b45f3882159b1812d8eccf768424a43b6f4b4244a9b2e57c400a09b4dc685d2ab1eda56040a66f0f
6
+ metadata.gz: 8a134919cf3a80ac8014f62c0cf48fca4519d3a680af289c8ae06e01f11e39a0279e571c4d0ee9dd6d4e30008d7cdc4190fff4d45a8f88195fdd1a365ff04615
7
+ data.tar.gz: 866941a834365dcb0d3d3dff0f37228e52a7412299d13ee8867329aa688025b103fc625e0f12765ab3184b1e10f2bc9a79d24b9e664c4c2dd8dd459b6b1a6058
data/lib/micky/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Micky
2
- VERSION = '0.8.1'
2
+ VERSION = '0.8.2'
3
3
  end
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.1
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
@@ -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,9 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- require 'rake/testtask'
3
-
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << 'test'
6
- t.pattern = 'test/*_test.rb'
7
- end
8
-
9
- task default: :test
@@ -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
@@ -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)
@@ -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