long_body 0.0.1 → 0.0.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
  SHA1:
3
- metadata.gz: 919ec90f6d67ef12a71c59222abc33cfb7c48256
4
- data.tar.gz: 3ba12c21e137a5ecf7351c36a2c03dfdac80b536
3
+ metadata.gz: a5900eac4ab0756b862af488203a3e5b431a2956
4
+ data.tar.gz: 2d2cd54282e2eff1bb687a7dcfa5e969f9acf505
5
5
  SHA512:
6
- metadata.gz: 9947da48ea520ee2a2f5d7d73e103f0264853ce10ae868624d835f61097a78979d6e7c5be1385dfe0a51554e4da22920944c98c6990fcd4f75417b193e37fefc
7
- data.tar.gz: 6fcb2f4830d5cc4be8ad2fce1c9c5f899a65423e30d37e3e24b1cfc87d8052bda8e9c24c507d411e7a6a4bb247478dd4a34fc58e6bc6fdf40239d24891edf669
6
+ metadata.gz: f9651bc656fdfdf9606adbb26f658befbfd5d7c89a1bc199f7d651ff0eff8b0fffcb66288470be42e79c176b3984cedcdcb3dfa4a9c829f131f1a80be3355d45
7
+ data.tar.gz: 90098d028bb0427d0818e658067a4c40f7dd184421e398db1fa4518fdd9beae1c590ddcb7097775a65f6ad7ae778a826a2653e938adde376a76613a93b0ef341
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - 2.1.5
5
+ - 2.2.2
6
+ cache: bundler
7
+ sudo: false
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # long_body
2
2
 
3
+ [![Build Status](https://travis-ci.org/julik/long_body.svg?branch=master)](https://travis-ci.org/julik/long_body)
4
+
3
5
  Universal Rack middleware for immediate (after-headers) streaming of long Rack response bodies.
4
6
  Normally most Rack handler webservers will buffer your entire response, or buffer your response
5
7
  without telling you for some time before sending it.
@@ -43,11 +45,18 @@ Streaming a large file, without buffering:
43
45
  [200, h, File.open(s, 'rb')]
44
46
  }
45
47
 
48
+ ## Selective bypass
49
+
50
+ Most requests in your application (assets, HTML pages and so on) probably do not need this and are better to be sent as-is.
51
+ Also, such processing will likely bypass all HTTP caching you set up. `long_body` is "always on" by default. To bypass it,
52
+ send `X-Rack-Long-Body-Skip` header with any truthy contents in your response headers (better use a string value so that
53
+ `Rack::Lint` does not complain).
54
+
46
55
  ## Compatibility
47
56
 
48
- This gem is tested on Ruby 2.2, and should run acceptably well on Ruby 2.+. If you are using Thin you have to
57
+ This gem is tested on Ruby 2.2, and should run acceptably well on Ruby 2.+. If you are using Thin it is recommended to
49
58
  use Ruby 2.+ because of the fiber stack size limitation. If you are using Puma, Rainbows or other threaded
50
- server running this gem on 1.9.2 should be possible as well.
59
+ server running this gem on 1.9.3+ should be possible as well.
51
60
 
52
61
  <table>
53
62
  <tr><th>Webserver</th><th>Version tested</th><th>Compatibility</th></tr>
@@ -46,6 +46,9 @@ class LongBody
46
46
  # Call the upstream first
47
47
  s, h, b = @app.call(env)
48
48
 
49
+ # Return as-is if the long body skip is requested
50
+ return [s, h,b] if h.delete('X-Rack-Long-Body-Skip')
51
+
49
52
  # If the response has nothing to do with the streaming response, just
50
53
  # let it go through as it is not big enough to bother. Also if there is no hijack
51
54
  # support there is no sense to bother at all.
@@ -7,12 +7,6 @@
7
7
  # For more on this:
8
8
  # http://apidock.com/ruby/IO/write_nonblock
9
9
  # http://old.blog.phusion.nl/2013/01/23/the-new-rack-socket-hijacking-api/
10
- #
11
- # By using this class as a middleware you will put a select() wait
12
- # spinlock on the output socket of the webserver.
13
- #
14
- # The middleware will only trigger for 200 and 206 responses, and only
15
- # if the Rack handler it is running on supports rack hijacking.
16
10
  module LongBody::HijackHandler
17
11
  extend self
18
12
  HIJACK_HEADER = 'rack.hijack'.freeze
@@ -1,3 +1,3 @@
1
1
  class LongBody
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -2,11 +2,11 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: long_body 0.0.1 ruby lib
5
+ # stub: long_body 0.0.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "long_body"
9
- s.version = "0.0.1"
9
+ s.version = "0.0.2"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.files = [
22
22
  ".document",
23
23
  ".rspec",
24
+ ".travis.yml",
24
25
  "Gemfile",
25
26
  "LICENSE.txt",
26
27
  "README.md",
@@ -6,6 +6,7 @@ require_relative 'shared_webserver_examples'
6
6
  describe "LongBody" do
7
7
  SERVERS.each do | server_engine |
8
8
  context "on #{server_engine.name}" do
9
+ let(:port) { server_engine.port }
9
10
  it_behaves_like "compliant"
10
11
  end
11
12
  end
@@ -1,6 +1,6 @@
1
1
  RSpec.shared_examples "compliant" do
2
2
  it "when the response is sent in full works with predefined Content-Length" do | example |
3
- parts = TestDownload.perform("http://0.0.0.0:9393/with-content-length")
3
+ parts = TestDownload.perform("http://0.0.0.0:#{port}/with-content-length")
4
4
  timings = parts.map(&:time_difference)
5
5
 
6
6
  # $postrun.puts ""
@@ -22,7 +22,7 @@ RSpec.shared_examples "compliant" do
22
22
  end
23
23
 
24
24
  it 'when the response is sent in full works with chunked encoding' do | example |
25
- parts = TestDownload.perform("http://0.0.0.0:9393/chunked")
25
+ parts = TestDownload.perform("http://0.0.0.0:#{port}/chunked")
26
26
  timings = parts.map(&:time_difference)
27
27
 
28
28
  # $postrun.puts example.full_description
@@ -40,18 +40,50 @@ RSpec.shared_examples "compliant" do
40
40
  expect(received_after_previous).to be_within(1).of(0.3)
41
41
  end
42
42
  end
43
-
43
+
44
+ it 'bypasses when X-Rack-Long-Body-Skip is set' do | example |
45
+ parts = TestDownload.perform("http://0.0.0.0:#{port}/explicitly-skipping-long-body")
46
+ timings = parts.map(&:time_difference)
47
+
48
+ # $postrun.puts example.full_description
49
+ # $postrun.puts "Part receive timings: #{timings.inspect}"
50
+ # $postrun.puts ""
51
+
52
+ expect(File).to exist('/tmp/streamer_close.mark')
53
+
54
+ (1..(parts.length-1)).each do | part_i|
55
+ this_part = parts[part_i]
56
+ previous_part = parts[part_i -1]
57
+ received_after_previous = this_part.time_difference - previous_part.time_difference
58
+
59
+ # Ensure there was some time before this chunk arrived. This is the most important test.
60
+ expect(received_after_previous).to be_within(1).of(0.3)
61
+ end
62
+ end
63
+
64
+
65
+ it 'raises an error if no Content-Length or chunked transfer encoding is set' do
66
+ parts = TestDownload.perform("http://0.0.0.0:#{port}/error-with-unclassified-body")
67
+ response_body = parts.join
68
+ expect(response_body).to include('If uncertain, insert Rack')
69
+ end
70
+
44
71
  it 'when the HTTP client process is killed midflight, does not read more chunks from the body object' do
45
72
  # This test checks whether the server makes the iterable body complete if the client closes the connection
46
73
  # prematurely. If you have the callbacks set up wrong on Thin, for instance, it will read the response
47
74
  # completely and potentially buffer it in memory, filling up your RAM. We need to ensure that the server
48
75
  # uses it's internal mechanics to stop reading the body once the client is dropped.
49
76
  pid = fork do
50
- TestDownload.perform("http://0.0.0.0:9393/with-content-length")
77
+ TestDownload.perform("http://0.0.0.0:#{port}/with-content-length")
51
78
  end
52
79
  sleep(1)
53
80
  Process.kill("KILL", pid)
54
81
 
82
+ # Wait for the webserver to terminate operations on that request (the close()
83
+ # call on the body is usually not immediate, but gets executed once the write
84
+ # to the client socket fails).
85
+ sleep 0.9
86
+
55
87
  written_parts_list = File.read("/tmp/streamer_messages.log").split("\n")
56
88
  expect(written_parts_list.length).to be < 6
57
89
 
@@ -34,12 +34,31 @@ class StreamerWithLength < Streamer
34
34
  end
35
35
  end
36
36
 
37
+ map '/error-with-unclassified-body' do
38
+ use Rack::ShowExceptions
39
+ use LongBody
40
+ run ->(env) { [200, {}, %w( one two three )]}
41
+ end
42
+
37
43
  map '/chunked' do
38
44
  use LongBody
39
45
  use Rack::Chunked
40
46
  run Streamer
41
47
  end
42
48
 
49
+ class Skipper < Struct.new(:app)
50
+ def call(env)
51
+ s, h, b = app.call(env)
52
+ [s, h.merge('X-Rack-Long-Body-Skip' => 'yes'), b]
53
+ end
54
+ end
55
+
56
+ map '/explicitly-skipping-long-body' do
57
+ use LongBody
58
+ use Skipper
59
+ run Streamer
60
+ end
61
+
43
62
  map '/with-content-length' do
44
63
  use LongBody
45
64
  run StreamerWithLength
@@ -18,25 +18,4 @@ module TestDownload
18
18
  end
19
19
  response_chunks
20
20
  end
21
-
22
- def perform_and_abort_after_3_chunks(uri)
23
- response_chunks = []
24
- catch :abort do
25
- uri = URI(uri.to_s)
26
- conn = Net::HTTP.new(uri.host, uri.port)
27
- conn.read_timeout = 120 # Might take LONG
28
- conn.start do |http|
29
- req = Net::HTTP::Get.new(uri.request_uri)
30
- before_first = Time.now.to_i
31
- http.request(req) do |res|
32
- res.read_body do |chunk|
33
- diff = Time.now.to_i - before_first
34
- response_chunks << Part.new(diff, chunk)
35
- throw :abort if response_chunks.length == 3
36
- end
37
- end
38
- end
39
- end
40
- response_chunks
41
- end
42
- end
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: long_body
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
@@ -186,6 +186,7 @@ extra_rdoc_files:
186
186
  files:
187
187
  - ".document"
188
188
  - ".rspec"
189
+ - ".travis.yml"
189
190
  - Gemfile
190
191
  - LICENSE.txt
191
192
  - README.md