goliath-rack_proxy 0.1.0
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +108 -0
- data/goliath-rack_proxy.gemspec +23 -0
- data/lib/goliath/rack_proxy.rb +240 -0
- data/lib/goliath/rack_proxy/rack_2_compatibility.rb +19 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fe3f590845b04b15e9d4c493890f3abb0128dbd4
|
4
|
+
data.tar.gz: 732ca21c6467a237ff05d39fb22e9fced483b051
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4dd788993d89ccf180919686374de9436c2623a2638def3b132bbdf7e885b7c79d261afb5ef7f9f2e90d25937289fc6a11b12f5eae1f0e6b0b74565408996ced
|
7
|
+
data.tar.gz: b48dd539d8afb09d0ff4e3966e5e73eac27f29d952408fc9429b1abb3d748c260352c32f8825010a16a75e11a357a8bb732c1ebc16bec9cc2177edc979aeec76
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Janko Marohnić
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# Goliath::RackProxy
|
2
|
+
|
3
|
+
Allows you to use [Goliath] as a web server for your Rack app, giving you streaming requests and responses.
|
4
|
+
|
5
|
+
## Motivation
|
6
|
+
|
7
|
+
While developing [tus-ruby-server], a Rack application that handles large
|
8
|
+
uploads and large downloads, I wanted to find an appropriate web server to
|
9
|
+
recommend. I needed a web server that supports **streaming uploads**, allowing
|
10
|
+
the Rack application to start processing the request while the request body is
|
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.
|
14
|
+
|
15
|
+
The only web server I found that supported all of this was [Unicorn]. However,
|
16
|
+
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.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
```rb
|
31
|
+
gem "goliath-rack_proxy"
|
32
|
+
```
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
Create a file where you will initialize the Goliath Rack proxy:
|
37
|
+
|
38
|
+
```rb
|
39
|
+
# app.rb
|
40
|
+
require "goliath/rack_proxy"
|
41
|
+
|
42
|
+
class MyGoliathApp < Goliath::RackProxy
|
43
|
+
rack_app MyRackApp # provide your #call-able Rack application
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
You can then run the server by running that Ruby file:
|
48
|
+
|
49
|
+
```sh
|
50
|
+
$ ruby app.rb
|
51
|
+
```
|
52
|
+
|
53
|
+
Any command-line arguments passed after the file will be forwarded to the
|
54
|
+
Goliath server (see [list of available options][goliath server options]):
|
55
|
+
|
56
|
+
```sh
|
57
|
+
$ ruby app.rb --port 3000 --stdout
|
58
|
+
```
|
59
|
+
|
60
|
+
This will run a single EventMachine process, which by default uses a pool of
|
61
|
+
20 threads. You can increase the number of threads EventMachine uses:
|
62
|
+
|
63
|
+
```rb
|
64
|
+
EventMachine.threadpool_size = 100
|
65
|
+
```
|
66
|
+
|
67
|
+
You can also spawn multiple EventMachine processes using [Einhorn]:
|
68
|
+
|
69
|
+
```sh
|
70
|
+
$ einhorn -n COUNT -b 127.0.0.1:3000 ruby app.rb --einhorn
|
71
|
+
```
|
72
|
+
|
73
|
+
By default `Goliath::RackProxy` will use a rewindable `rack.input`, which means
|
74
|
+
the data received from the client will be cached onto disk for the duration of
|
75
|
+
the request. If you don't need the `rack.input` to be rewindable and want to
|
76
|
+
save on disk I/O, you can disable caching:
|
77
|
+
|
78
|
+
```rb
|
79
|
+
class MyGoliathApp < Goliath::RackProxy
|
80
|
+
rack_app MyRackApp
|
81
|
+
rewindable_input false
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
If you want to report any exceptions that might occur with the Rack app, you can
|
86
|
+
override `Goliath::RackProxy#log_exception`:
|
87
|
+
|
88
|
+
```rb
|
89
|
+
class MyGoliathApp < Goliath::RackProxy
|
90
|
+
rack_app MyRackApp
|
91
|
+
|
92
|
+
def log_exception(exception, env)
|
93
|
+
super
|
94
|
+
Airbrake.notify(exception)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
## License
|
100
|
+
|
101
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
102
|
+
|
103
|
+
[Goliath]: https://github.com/postrank-labs/goliath
|
104
|
+
[EventMachine]: https://github.com/eventmachine/eventmachine
|
105
|
+
[tus-ruby-server]: https://github.com/janko-m/tus-ruby-server
|
106
|
+
[Unicorn]: https://github.com/defunkt/unicorn
|
107
|
+
[goliath server options]: https://github.com/postrank-labs/goliath/wiki/Server
|
108
|
+
[Einhorn]: https://github.com/stripe/einhorn
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "goliath-rack_proxy"
|
3
|
+
gem.version = "0.1.0"
|
4
|
+
|
5
|
+
gem.required_ruby_version = ">= 2.1"
|
6
|
+
|
7
|
+
gem.summary = "Allows you to use Goliath as a web server for your Rack app, giving you streaming requests and responses."
|
8
|
+
|
9
|
+
gem.homepage = "https://github.com/janko-m/goliath-rack_proxy"
|
10
|
+
gem.authors = ["Janko Marohnić"]
|
11
|
+
gem.email = ["janko.marohnic@gmail.com"]
|
12
|
+
gem.license = "MIT"
|
13
|
+
|
14
|
+
gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "*.gemspec"]
|
15
|
+
gem.require_path = "lib"
|
16
|
+
|
17
|
+
gem.add_dependency "goliath", ">= 1.0.5", "< 2"
|
18
|
+
|
19
|
+
gem.add_development_dependency "rake", "~> 11.1"
|
20
|
+
gem.add_development_dependency "minitest", "~> 5.8"
|
21
|
+
gem.add_development_dependency "http"
|
22
|
+
gem.add_development_dependency "rack", "~> 2.0"
|
23
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
require "goliath/rack_proxy/rack_2_compatibility"
|
3
|
+
require "goliath"
|
4
|
+
require "tempfile"
|
5
|
+
|
6
|
+
module Goliath
|
7
|
+
class RackProxy < Goliath::API
|
8
|
+
# Rack app to proxy the incoming requests to.
|
9
|
+
def self.rack_app(app)
|
10
|
+
rack_proxy_options[:rack_app] = app
|
11
|
+
end
|
12
|
+
|
13
|
+
# Whether the input should be rewindable, i.e. cached onto disk.
|
14
|
+
def self.rewindable_input(value)
|
15
|
+
rack_proxy_options[:rewindable_input] = value
|
16
|
+
end
|
17
|
+
|
18
|
+
# Custom user-defined options.
|
19
|
+
def self.rack_proxy_options
|
20
|
+
@rack_proxy_options ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Called when request headers were parsed.
|
24
|
+
def on_headers(env, headers)
|
25
|
+
# assign a streaming input that acts as a bidirectional pipe
|
26
|
+
env["rack_proxy.input"] = RackInput.new(rewindable: self.class.rack_proxy_options.fetch(:rewindable_input, true))
|
27
|
+
rack_app = self.class.rack_proxy_options.fetch(:rack_app)
|
28
|
+
|
29
|
+
# start the rack request asynchronously with the created rack input
|
30
|
+
async_rack_call rack_app, env.merge("rack.input" => env["rack_proxy.input"])
|
31
|
+
end
|
32
|
+
|
33
|
+
# Called on each request body chunk received from the client.
|
34
|
+
def on_body(env, data)
|
35
|
+
# write data to the input, which will be read by the Rack app
|
36
|
+
env["rack_proxy.input"].write(data)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Called at the end of the request (after #response) or on client disconnect.
|
40
|
+
def on_close(env)
|
41
|
+
# reading the request body has finished, so we close write end of the input
|
42
|
+
env["rack_proxy.input"].close_write
|
43
|
+
end
|
44
|
+
|
45
|
+
# Called after all the data has been received from the client.
|
46
|
+
def response(env)
|
47
|
+
# reading the request body has finished, so we close write end of the input
|
48
|
+
env["rack_proxy.input"].close_write
|
49
|
+
|
50
|
+
# prevent Goliath from sending a response, we will send it once the
|
51
|
+
# asynchronous request to the rack app finishes
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Spawns a thread and initiates the call to the Rack application, which
|
58
|
+
# will be reading from Rack input that is being written to in #on_body. Once
|
59
|
+
# the request has finished, we stream the response back to the client.
|
60
|
+
def async_rack_call(rack_app, env)
|
61
|
+
env["goliath.request"] = env["stream.start"].binding.receiver # https://github.com/postrank-labs/goliath/pull/341
|
62
|
+
|
63
|
+
# spawn a thread for the request
|
64
|
+
EM.defer do
|
65
|
+
rack_response = make_request(rack_app, env)
|
66
|
+
|
67
|
+
# wait for client to stop sending data before sending the response
|
68
|
+
env["goliath.request"].callback do
|
69
|
+
# spawn a thread for the response
|
70
|
+
EM.defer { send_response(rack_response, env) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def make_request(rack_app, env)
|
76
|
+
# call the rack app with some patches
|
77
|
+
rack_app.call env.merge(
|
78
|
+
"rack.url_scheme" => env["options"][:ssl] ? "https" : "http", # https://github.com/postrank-labs/goliath/issues/210
|
79
|
+
"async.callback " => nil, # prevent Roda/Sinatra from calling EventMachine while streaming the response
|
80
|
+
)
|
81
|
+
rescue Exception => exception
|
82
|
+
# log the exception that occurred
|
83
|
+
log_exception(exception, env)
|
84
|
+
|
85
|
+
# return a generic error message on production, or a more detailed one otherwise
|
86
|
+
body = Goliath.env?(:production) ? ["An error occurred"] : [exception.inspect]
|
87
|
+
headers = {"Content-Length" => body[0].bytesize.to_s}
|
88
|
+
|
89
|
+
[500, headers, body]
|
90
|
+
ensure
|
91
|
+
# request has finished, so we close the read end of the rack input
|
92
|
+
env["rack.input"].close_read
|
93
|
+
end
|
94
|
+
|
95
|
+
# Streams the response to the client.
|
96
|
+
def send_response(rack_response, env)
|
97
|
+
request = env["goliath.request"]
|
98
|
+
connection = request.conn
|
99
|
+
response = request.response
|
100
|
+
|
101
|
+
response.status, response.headers, response.body = rack_response
|
102
|
+
response.each { |data| connection.send_data(data) }
|
103
|
+
|
104
|
+
connection.terminate_request(keep_alive?(env))
|
105
|
+
rescue Exception => exception
|
106
|
+
# log the exception that occurred
|
107
|
+
log_exception(exception, env)
|
108
|
+
|
109
|
+
# communicate that sending response failed and close the connection
|
110
|
+
connection.send_data("HTTP/1.1 500 Internal Server Error\r\n\r\n")
|
111
|
+
connection.terminate_request(false)
|
112
|
+
ensure
|
113
|
+
# log the response information
|
114
|
+
log_response(response, env)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns whether the TCP connection should be kept alive.
|
118
|
+
def keep_alive?(env)
|
119
|
+
if env["HTTP_VERSION"] >= "1.1"
|
120
|
+
# HTTP 1.1: all requests are persistent requests, client must
|
121
|
+
# send a "Connection: close" header to indicate otherwise
|
122
|
+
env["HTTP_CONNECTION"].to_s.downcase != "close"
|
123
|
+
elsif env["HTTP_VERSION"] == "1.0"
|
124
|
+
# HTTP 1.0: all requests are non keep-alive, client must
|
125
|
+
# send a "Connection: Keep-Alive" to indicate otherwise
|
126
|
+
env["HTTP_CONNECTION"].to_s.downcase == "keep-alive"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Logs the response in the Rack::CommonLogger format.
|
131
|
+
def log_response(response, env)
|
132
|
+
length = response.headers["Content-Length"]
|
133
|
+
length = nil if length.to_s == "0"
|
134
|
+
|
135
|
+
# log the response as Goliath would log it
|
136
|
+
env.logger.info '%s - %s [%s] "%s %s%s %s" %d %s %0.4f' % [
|
137
|
+
env["HTTP_X_FORWARDED_FOR"] || env["REMOTE_ADDR"] || "-",
|
138
|
+
env["REMOTE_USER"] || "-",
|
139
|
+
Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
140
|
+
env["REQUEST_METHOD"],
|
141
|
+
env["PATH_INFO"],
|
142
|
+
env["QUERY_STRING"].empty? ? "" : "?#{env["QUERY_STRING"]}",
|
143
|
+
env["HTTP_VERSION"],
|
144
|
+
response.status,
|
145
|
+
length || "-",
|
146
|
+
Time.now.to_f - env[:start_time],
|
147
|
+
]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Logs the exception and adds it to the env hash.
|
151
|
+
def log_exception(exception, env)
|
152
|
+
# mimic how Ruby would display the error
|
153
|
+
stderr = "#{exception.backtrace[0]}: #{exception.message} (#{exception.class})\n".dup
|
154
|
+
exception.backtrace[1..-1].each do |line|
|
155
|
+
stderr << " from #{line}\n"
|
156
|
+
end
|
157
|
+
env.logger.error(stderr)
|
158
|
+
|
159
|
+
# save the exception in the env hash
|
160
|
+
env["rack.exception"] = exception
|
161
|
+
end
|
162
|
+
|
163
|
+
# IO-like object that acts as a bidirectional pipe, which returns the data
|
164
|
+
# that has been written to it.
|
165
|
+
class RackInput
|
166
|
+
def initialize(rewindable: true)
|
167
|
+
@data_queue = Queue.new
|
168
|
+
@cache = Tempfile.new("goliath-rack_input") if rewindable
|
169
|
+
@buffer = nil
|
170
|
+
end
|
171
|
+
|
172
|
+
# Pops chunks of data from the queue and implements
|
173
|
+
# `IO#read(length = nil, outbuf = nil)` semantics.
|
174
|
+
def read(length = nil, outbuf = nil)
|
175
|
+
data = outbuf.clear if outbuf
|
176
|
+
data = @cache.read(length, outbuf) if @cache && !@cache.eof?
|
177
|
+
|
178
|
+
loop do
|
179
|
+
remaining_length = length - data.bytesize if data && length
|
180
|
+
|
181
|
+
break if remaining_length == 0
|
182
|
+
|
183
|
+
@buffer = @data_queue.pop or break if @buffer.nil?
|
184
|
+
|
185
|
+
buffered_data = if remaining_length && remaining_length < @buffer.bytesize
|
186
|
+
@buffer.byteslice(0, remaining_length)
|
187
|
+
else
|
188
|
+
@buffer
|
189
|
+
end
|
190
|
+
|
191
|
+
if data
|
192
|
+
data << buffered_data
|
193
|
+
else
|
194
|
+
data = buffered_data
|
195
|
+
end
|
196
|
+
|
197
|
+
@cache.write(buffered_data) if @cache
|
198
|
+
|
199
|
+
if buffered_data.bytesize < @buffer.bytesize
|
200
|
+
@buffer = @buffer.byteslice(buffered_data.bytesize..-1)
|
201
|
+
else
|
202
|
+
@buffer = nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
data.to_s unless length && (data.nil? || data.empty?)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Pushes data to the queue, which is then popped in #read.
|
210
|
+
def write(data)
|
211
|
+
@data_queue.push(data) unless @data_queue.closed?
|
212
|
+
end
|
213
|
+
|
214
|
+
# Rewinds the cache IO if it's configured, otherwise raises Errno::ESPIPE
|
215
|
+
# exception, which mimics the behaviour of caling #rewind on
|
216
|
+
# non-rewindable IOs such as pipes, sockets, and ttys.
|
217
|
+
def rewind
|
218
|
+
raise Errno::ESPIPE if @cache.nil? # raised by other non-rewindable IOs
|
219
|
+
@cache.rewind
|
220
|
+
end
|
221
|
+
|
222
|
+
# Closes the queue and deletes the cache IO.
|
223
|
+
def close_read
|
224
|
+
@data_queue.close
|
225
|
+
@cache.close! if @cache
|
226
|
+
end
|
227
|
+
|
228
|
+
# Closes the queue, which prevents fruther pushing, but #read can still
|
229
|
+
# pop remaining chunks from it.
|
230
|
+
def close_write
|
231
|
+
@data_queue.close
|
232
|
+
end
|
233
|
+
|
234
|
+
# Conforming to the Rack specification.
|
235
|
+
def close
|
236
|
+
# no-op
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "rack"
|
2
|
+
|
3
|
+
# Async-rack attempts to require files that exist only in Rack 1.x even on Rack
|
4
|
+
# 2.x, so we patch that behaviour to allow users to use this gem with Rack 2.x apps.
|
5
|
+
module Kernel
|
6
|
+
if Rack.release >= "2.0.0"
|
7
|
+
alias original_rubygems_require require
|
8
|
+
|
9
|
+
def require(file)
|
10
|
+
case file
|
11
|
+
when "rack/commonlogger" then original_rubygems_require("rack/common_logger")
|
12
|
+
when "rack/conditionalget" then original_rubygems_require("rack/conditional_get")
|
13
|
+
when "rack/showstatus" then original_rubygems_require("rack/show_status")
|
14
|
+
else
|
15
|
+
original_rubygems_require(file)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: goliath-rack_proxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Janko Marohnić
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: goliath
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.5
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '2'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.5
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '11.1'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '11.1'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: minitest
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '5.8'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '5.8'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: http
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rack
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '2.0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '2.0'
|
89
|
+
description:
|
90
|
+
email:
|
91
|
+
- janko.marohnic@gmail.com
|
92
|
+
executables: []
|
93
|
+
extensions: []
|
94
|
+
extra_rdoc_files: []
|
95
|
+
files:
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- goliath-rack_proxy.gemspec
|
99
|
+
- lib/goliath/rack_proxy.rb
|
100
|
+
- lib/goliath/rack_proxy/rack_2_compatibility.rb
|
101
|
+
homepage: https://github.com/janko-m/goliath-rack_proxy
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '2.1'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.6.11
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Allows you to use Goliath as a web server for your Rack app, giving you streaming
|
125
|
+
requests and responses.
|
126
|
+
test_files: []
|