long_body 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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