goliath-rack_proxy 1.0.0 → 1.0.1

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: ca389756f31e83a5660faf6628a02cdc94c42431
4
- data.tar.gz: 2b5c39cba40a5a989870b8e2acc2548b3e7c6bdc
3
+ metadata.gz: ab4ba13cd9def1d20578a29ffefabf79080598ff
4
+ data.tar.gz: 6ace1d22f5aba4ecd862dfa72ec959dd378b2cd4
5
5
  SHA512:
6
- metadata.gz: 51840456dab796caa37a5390b58e85cf5b5a4be37c40cae3caaa094aae32a2c9514046425f27400654c2933af01fd532784873259432bb11888ec413bd732c67
7
- data.tar.gz: 8c2fac7e1676038be1da9b58e38f5f27da879341324a6b5c810949ac8d18729d9c9f293b84cdba033b5ad61f472820fa3947c87bda1993372d91f0b552a170b9
6
+ metadata.gz: 0bacf146eea719d6e0e124cd99e1e1194a86d7d79a1d7931035c78bebcfca8e2a85be2a9693e51b0b304440c34d4eb90ecb5d4d26954569ece2719c5af2d2cec
7
+ data.tar.gz: 8e22fc46cf8edab53d430b017e8b6f3421ec7530a88d9963a83c5485f8e168cb9ee24102e3543707e655ce51128df65e1823dca39b74abfc0c8d2ce50bfa90a7
data/README.md CHANGED
@@ -9,21 +9,27 @@ uploads and large downloads, I wanted to find an appropriate web server to
9
9
  recommend. I needed a web server that supports **streaming uploads**, allowing
10
10
  the Rack application to start processing the request while the request body is
11
11
  still being received, and that way giving it the ability to save whatever data
12
- it received before possible request interruptions. I also needed support for
13
- **streaming downloads**, sending response body in chunks back to the client.
12
+ it received before possible potential request interruption. I also needed
13
+ support for **streaming downloads**, sending response body in chunks back to
14
+ the client.
14
15
 
15
16
  The only web server I found that supported all of this was [Unicorn]. However,
16
17
  Unicorn needs to spawn a whole process for serving each concurrent request,
17
- which isn't the most efficent use of server resources.
18
-
19
- Then I found [Goliath], which I found to provide the most flexibility in
20
- handling requests. It's built on top of [EventMachine], so it uses threads for
21
- serving requests, but it can also be run in hybrid mode. However, Goliath is
22
- more meant to be used standalone than in tandem with another Rack app.
23
-
24
- So I created a `Goliath::API` subclass that proxies incoming requests to the
25
- specified Rack app in a streaming fashion, making it act like a web server for
26
- the app.
18
+ which isn't the most efficent use of server resources. It's also difficult to
19
+ estimate how many workers you need, because once you disable request buffering
20
+ in the application server, you become vulnerable to slow clients.
21
+
22
+ Then I came across [Goliath], which gave me the control I needed for handling
23
+ requests. It's built on top of [EventMachine], which uses the reactor pattern
24
+ to schedule work efficiently. The most important feature is that long-running
25
+ requests won't impact request throughput, as there are no web workers that are
26
+ waiting for incoming data.
27
+
28
+ However, Goliath itself is designed to be used standalone, not in tandem with
29
+ another Rack app. So I created `Goliath::RackProxy`, which is a `Goliath::API`
30
+ subclass that proxies incoming/outgoing requests/responses to/from the
31
+ specified Rack app in a streaming fashion, essentially making it act like a web
32
+ server for the Rack app.
27
33
 
28
34
  ## Installation
29
35
 
@@ -75,20 +81,6 @@ class MyGoliathApp < Goliath::RackProxy
75
81
  end
76
82
  ```
77
83
 
78
- If you want to report any exceptions that might occur with the Rack app, you can
79
- override `Goliath::RackProxy#log_exception`:
80
-
81
- ```rb
82
- class MyGoliathApp < Goliath::RackProxy
83
- rack_app MyRackApp
84
-
85
- def log_exception(exception, env)
86
- super
87
- Airbrake.notify(exception)
88
- end
89
- end
90
- ```
91
-
92
84
  ## License
93
85
 
94
86
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "goliath-rack_proxy"
3
- gem.version = "1.0.0"
3
+ gem.version = "1.0.1"
4
4
 
5
5
  gem.required_ruby_version = ">= 2.1"
6
6
 
@@ -11,7 +11,7 @@ module Goliath
11
11
  rack_proxy_options[:rack_app] = app
12
12
  end
13
13
 
14
- # Whether the input should be rewindable, i.e. cached onto disk.
14
+ # Whether the request body should be rewindable.
15
15
  def self.rewindable_input(value)
16
16
  rack_proxy_options[:rewindable_input] = value
17
17
  end
@@ -21,7 +21,7 @@ module Goliath
21
21
  @rack_proxy_options ||= {}
22
22
  end
23
23
 
24
- # Called when request headers were parsed.
24
+ # Starts the request to the given Rack application.
25
25
  def on_headers(env, headers)
26
26
  rack_app = self.class.rack_proxy_options.fetch(:rack_app)
27
27
  rewindable_input = self.class.rack_proxy_options.fetch(:rewindable_input, true)
@@ -30,31 +30,48 @@ module Goliath
30
30
  env["rack_proxy.call"].resume
31
31
  end
32
32
 
33
- # Called on each request body chunk received from the client.
33
+ # Resumes the Rack request with the received request body data.
34
34
  def on_body(env, data)
35
- # write data to the input, which will be read by the Rack app
36
35
  env["rack_proxy.call"].resume(data)
37
36
  end
38
37
 
39
- # Called at the end of the request (after #response) or on client disconnect.
38
+ # Resumes the Rack request with no more data.
40
39
  def on_close(env)
41
- # reading the request body has finished, so we close write end of the input
42
40
  env["rack_proxy.call"].resume
43
41
  end
44
42
 
45
- # Called after all the data has been received from the client.
43
+ # Resumes the Rack request with no more data.
46
44
  def response(env)
47
45
  env["rack_proxy.call"].resume
48
46
  end
49
47
 
50
48
  private
51
49
 
50
+ # Allows "curry-calling" the Rack application, resuming the call as we're
51
+ # receiving more request body data.
52
52
  class RackCall
53
53
  def initialize(app, env, rewindable_input: true)
54
- @fiber = Fiber.new do
55
- rack_input = RackInput.new(rewindable: rewindable_input) { Fiber.yield }
54
+ @app = app
55
+ @env = env
56
+ @rewindable_input = rewindable_input
57
+ end
58
+
59
+ def resume(data = nil)
60
+ @result = fiber.resume(data) if fiber.alive?
61
+ @result
62
+ end
63
+
64
+ private
65
+
66
+ # Calls the Rack application inside a Fiber, using the RackInput object as
67
+ # the request body. When the Rack application wants to read request body
68
+ # data that hasn't been received yet, the execution is automatically
69
+ # paused so that the event loop can go on.
70
+ def fiber
71
+ @fiber ||= Fiber.new do
72
+ rack_input = RackInput.new(rewindable: @rewindable_input) { Fiber.yield }
56
73
 
57
- result = app.call env.merge(
74
+ result = @app.call @env.merge(
58
75
  "rack.input" => rack_input,
59
76
  "async.callback" => nil, # prevent Roda/Sinatra from calling EventMachine while streaming the response
60
77
  )
@@ -64,25 +81,23 @@ module Goliath
64
81
  result
65
82
  end
66
83
  end
67
-
68
- def resume(data = nil)
69
- @result = @fiber.resume(data) if @fiber.alive?
70
- @result
71
- end
72
84
  end
73
85
 
74
- # IO-like object that acts as a bidirectional pipe, which returns the data
75
- # that has been written to it.
86
+ # IO-like object that conforms to the Rack specification for the request
87
+ # body ("rack input"). It takes a block which produces chunks of data, and
88
+ # makes this data retrievable through the IO#read interface. When rewindable
89
+ # caches the retrieved content onto disk.
76
90
  class RackInput
77
91
  def initialize(rewindable: true, &next_chunk)
78
92
  @next_chunk = next_chunk
79
- @cache = Tempfile.new("goliath-rack_input") if rewindable
93
+ @cache = Tempfile.new("goliath-rack_input", binmode: true) if rewindable
80
94
  @buffer = nil
81
95
  @eof = false
82
96
  end
83
97
 
84
- # Pops chunks of data from the queue and implements
85
- # `IO#read(length = nil, outbuf = nil)` semantics.
98
+ # Retrieves data using the IO#read semantics. If rack input is declared
99
+ # rewindable, writes retrieved content into a Tempfile object so that
100
+ # it can later be re-read.
86
101
  def read(length = nil, outbuf = nil)
87
102
  data = outbuf.clear if outbuf
88
103
  data = @cache.read(length, outbuf) if @cache && !@cache.eof?
@@ -118,15 +133,15 @@ module Goliath
118
133
  data.to_s unless length && (data.nil? || data.empty?)
119
134
  end
120
135
 
121
- # Rewinds the cache IO if it's configured, otherwise raises Errno::ESPIPE
122
- # exception, which mimics the behaviour of caling #rewind on
123
- # non-rewindable IOs such as pipes, sockets, and ttys.
136
+ # Rewinds the tempfile if rewindable. Otherwise raises Errno::ESPIPE
137
+ # exception, which is what other non-rewindable Ruby IO objects raise.
124
138
  def rewind
125
- raise Errno::ESPIPE if @cache.nil? # raised by other non-rewindable IOs
139
+ raise Errno::ESPIPE if @cache.nil?
126
140
  @cache.rewind
127
141
  end
128
142
 
129
- # Conforming to the Rack specification.
143
+ # Deletes the tempfile. The #close method is also part of the Rack
144
+ # specification.
130
145
  def close
131
146
  @cache.close! if @cache
132
147
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: goliath-rack_proxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-15 00:00:00.000000000 Z
11
+ date: 2018-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: goliath