fishwife 1.6.1-java → 1.7.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.rdoc +7 -0
- data/Manifest.txt +1 -1
- data/Rakefile +7 -0
- data/lib/fishwife/base.rb +1 -1
- data/lib/fishwife/fishwife-1.7.0.jar +0 -0
- data/lib/fishwife/http_server.rb +25 -3
- data/lib/fishwife/rack_servlet.rb +67 -16
- data/pom.xml +1 -1
- data/spec/fishwife_spec.rb +44 -2
- data/spec/test_app.rb +8 -0
- metadata +8 -8
- data/lib/fishwife/fishwife-1.6.1.jar +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c401c8d2d0ab0ad9a9fa4c2f3e7698f6efa66814
|
4
|
+
data.tar.gz: 00ec5e80418ebed7743eda5332c7bc594d0f58be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05ac32f4acf00afbbb79932a6573b1c19be2e903a7c3375af31e7e092896569b3cfde56add2b2ab132781a2cae89b1a6aa92a1562b88dec2872ae89e0b21201f
|
7
|
+
data.tar.gz: af7f17f2c9f6031edb27977068e5b744f984cf833b2e1406490c124925538ebdf6ee9b2d996cd4e39ec525471a79c9ed6229cc744acaf7218985419a7720dccf
|
data/History.rdoc
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
=== 1.7.0 (2015-2-12)
|
2
|
+
* Added a complete implementation of input stream rewind for request
|
3
|
+
bodies, per Rack requirements. This uses an in-memory StringIO
|
4
|
+
buffer up to a configurable threshold size before buffering to a
|
5
|
+
TempFile. See the associated request_body_* options of
|
6
|
+
Fishwife::HttpServer::new. This resolves #4.
|
7
|
+
|
1
8
|
=== 1.6.1 (2015-1-24)
|
2
9
|
* Upgrade/broaden to rack [1.5.2, 1.7). Tested with rack 1.6.0.
|
3
10
|
|
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
@@ -5,3 +5,10 @@ require 'bundler/setup'
|
|
5
5
|
require 'rjack-tarpit'
|
6
6
|
|
7
7
|
RJack::TarPit.new( 'fishwife' ).define_tasks
|
8
|
+
|
9
|
+
desc "Upload RDOC to Amazon S3 (rdoc.gravitext.com/syncwrap, Oregon)"
|
10
|
+
task :publish_rdoc => [ :clean, :rerdoc ] do
|
11
|
+
sh <<-SH
|
12
|
+
aws s3 sync --acl public-read doc/ s3://rdoc.gravitext.com/fishwife/
|
13
|
+
SH
|
14
|
+
end
|
data/lib/fishwife/base.rb
CHANGED
Binary file
|
data/lib/fishwife/http_server.rb
CHANGED
@@ -40,6 +40,20 @@ module Fishwife
|
|
40
40
|
#
|
41
41
|
# :request_log_file::
|
42
42
|
# Request log to file name or :stderr (default: nil, no log)
|
43
|
+
#
|
44
|
+
# :request_body_ram::
|
45
|
+
# Maximum size of request body (i.e POST) to keep in memory
|
46
|
+
# before resorting to a temporary file (default: 256 KiB)
|
47
|
+
#
|
48
|
+
# :request_body_tmpdir::
|
49
|
+
# Path to where request body temporary files should be created
|
50
|
+
# (when request_body_ram is exceeded.) (default: Dir.tmpdir)
|
51
|
+
#
|
52
|
+
# :request_body_max::
|
53
|
+
# Maximum total size of a request body, after which the
|
54
|
+
# request will be rejected with status 413. This limit is
|
55
|
+
# provided to avoid pathologic resource exhaustion. (default: 8 MiB)
|
56
|
+
#
|
43
57
|
def initialize( options = {} )
|
44
58
|
super()
|
45
59
|
|
@@ -53,7 +67,8 @@ module Fishwife
|
|
53
67
|
options = Hash[ options.map { |o| [ o[0].to_s.downcase.to_sym, o[1] ] } ]
|
54
68
|
|
55
69
|
# Translate option values from possible Strings
|
56
|
-
[:port, :min_threads, :max_threads, :max_idle_time_ms
|
70
|
+
[ :port, :min_threads, :max_threads, :max_idle_time_ms,
|
71
|
+
:request_body_ram, :request_body_max ].each do |k|
|
57
72
|
v = options[k]
|
58
73
|
options[k] = v.to_i if v
|
59
74
|
end
|
@@ -61,7 +76,13 @@ module Fishwife
|
|
61
76
|
v = options[ :request_log_file ]
|
62
77
|
options[ :request_log_file ] = v.to_sym if v == 'stderr'
|
63
78
|
|
64
|
-
#
|
79
|
+
# Split out servlet options.
|
80
|
+
@servlet_options = {}
|
81
|
+
[ :request_body_ram, :request_body_tmpdir, :request_body_max ].each do |k|
|
82
|
+
@servlet_options[k] = options.delete(k)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Apply remaining options as setters
|
65
86
|
options.each do |k,v|
|
66
87
|
setter = "#{k}=".to_sym
|
67
88
|
send( setter, v ) if respond_to?( setter )
|
@@ -70,7 +91,8 @@ module Fishwife
|
|
70
91
|
|
71
92
|
# Start the server, given rack app to run
|
72
93
|
def start( app )
|
73
|
-
set_context_servlets( '/',
|
94
|
+
set_context_servlets( '/',
|
95
|
+
{'/*' => RackServlet.new(app, @servlet_options)} )
|
74
96
|
|
75
97
|
@server = create
|
76
98
|
@server.start
|
@@ -15,31 +15,39 @@
|
|
15
15
|
# permissions and limitations under the License.
|
16
16
|
#++
|
17
17
|
|
18
|
+
require 'tempfile'
|
19
|
+
|
18
20
|
# Magic loader hook -> JRubyService
|
19
21
|
require 'fishwife/JRuby'
|
20
22
|
|
21
|
-
#
|
22
|
-
# Wraps a Rack application in a Java servlet.
|
23
|
-
#
|
24
|
-
# Relevant documentation:
|
25
|
-
#
|
26
|
-
# http://rack.rubyforge.org/doc/SPEC.html
|
27
|
-
# http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax
|
28
|
-
# /servlet/http/HttpServlet.html
|
29
|
-
#
|
30
23
|
module Fishwife
|
31
24
|
java_import 'javax.servlet.http.HttpServlet'
|
32
25
|
|
26
|
+
# Error raised if a request body is larger than the
|
27
|
+
# :request_body_max option.
|
28
|
+
class RequestBodyTooLarge < RuntimeError
|
29
|
+
end
|
30
|
+
|
31
|
+
# Wraps a Rack application in a Java servlet.
|
32
|
+
#
|
33
|
+
# Relevant documentation:
|
34
|
+
#
|
35
|
+
# * {Rack Specification}[http://www.rubydoc.info/github/rack/rack/file/SPEC]
|
36
|
+
# * {Java HttpServlet}[http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServlet.html]
|
37
|
+
#
|
33
38
|
class RackServlet < HttpServlet
|
34
39
|
java_import 'java.io.FileInputStream'
|
35
40
|
java_import 'org.eclipse.jetty.continuation.ContinuationSupport'
|
36
41
|
|
37
42
|
ASCII_8BIT = Encoding.find( "ASCII-8BIT" ) if defined?( Encoding )
|
38
43
|
|
39
|
-
def initialize( app )
|
44
|
+
def initialize( app, opts = {} )
|
40
45
|
super()
|
41
46
|
@log = RJack::SLF4J[ self.class ]
|
42
47
|
@app = app
|
48
|
+
@request_body_ram = opts[:request_body_ram] || 256 * 1024
|
49
|
+
@request_body_tmpdir = opts[:request_body_tmpdir] || Dir.tmpdir
|
50
|
+
@request_body_max = opts[:request_body_max] || 8 * 1024 * 1024
|
43
51
|
end
|
44
52
|
|
45
53
|
# Takes an incoming request (as a Java Servlet) and dispatches it
|
@@ -98,12 +106,18 @@ module Fishwife
|
|
98
106
|
# If we got here, this is a continuation.
|
99
107
|
continuation.suspend(response)
|
100
108
|
|
109
|
+
rescue RequestBodyTooLarge => e
|
110
|
+
@log.warn( "On service: #{e.class.name}: #{e.message}" )
|
111
|
+
response.sendError( 413 )
|
101
112
|
rescue NativeException => n
|
102
113
|
@log.warn( "On service (native): #{n.cause.to_string}" )
|
103
114
|
raise n.cause
|
104
115
|
rescue Exception => e
|
105
116
|
@log.error( "On service: #{e}" )
|
106
117
|
raise e
|
118
|
+
ensure
|
119
|
+
fin = env && env['fishwife.input']
|
120
|
+
fin.close if fin
|
107
121
|
end
|
108
122
|
|
109
123
|
private
|
@@ -162,10 +176,8 @@ module Fishwife
|
|
162
176
|
@log.warn( "Weird headers: [#{ hn.to_s }]" )
|
163
177
|
end
|
164
178
|
|
165
|
-
|
166
|
-
|
167
|
-
input.set_encoding( ASCII_8BIT ) if input.respond_to?( :set_encoding )
|
168
|
-
env['rack.input'] = input
|
179
|
+
env['rack.input'] = env['fishwife.input'] =
|
180
|
+
convert_input( request.input_stream, clength )
|
169
181
|
|
170
182
|
# The output stream defaults to stderr.
|
171
183
|
env['rack.errors'] ||= $stderr
|
@@ -174,6 +186,45 @@ module Fishwife
|
|
174
186
|
env
|
175
187
|
end
|
176
188
|
|
189
|
+
def convert_input( in_stream, clength )
|
190
|
+
io = StringIO.new
|
191
|
+
io.set_encoding( ASCII_8BIT )
|
192
|
+
blen = if clength > 0
|
193
|
+
if clength > @request_body_max
|
194
|
+
raise( RequestBodyTooLarge,
|
195
|
+
"Request body (Content-Length): " +
|
196
|
+
"#{clength} > #{@request_body_max}" )
|
197
|
+
end
|
198
|
+
if clength < 16*1024
|
199
|
+
clength
|
200
|
+
else
|
201
|
+
16*1024
|
202
|
+
end
|
203
|
+
else
|
204
|
+
0 #default/unspecified
|
205
|
+
end
|
206
|
+
|
207
|
+
IOUtil.read_input_stream( blen, in_stream ) do |sbuf|
|
208
|
+
fsize = io.pos + sbuf.bytesize
|
209
|
+
if fsize > @request_body_max
|
210
|
+
raise( RequestBodyTooLarge,
|
211
|
+
"Request body (read): #{fsize} > #{@request_body_max}" )
|
212
|
+
end
|
213
|
+
if io.is_a?( StringIO ) && fsize > @request_body_ram
|
214
|
+
tmp = Tempfile.new( 'fishwife_req_body', @request_body_tmpdir )
|
215
|
+
tmp.unlink
|
216
|
+
tmp.binmode
|
217
|
+
tmp.set_encoding( ASCII_8BIT )
|
218
|
+
tmp.write( io.string )
|
219
|
+
io = tmp
|
220
|
+
end
|
221
|
+
io.write( sbuf )
|
222
|
+
end
|
223
|
+
|
224
|
+
io.rewind
|
225
|
+
io
|
226
|
+
end
|
227
|
+
|
177
228
|
# Turns a Rack response into a Servlet response; can be called
|
178
229
|
# multiple times. Returns true if this is the full request (either
|
179
230
|
# a synchronous request or the last part of an async request),
|
@@ -228,9 +279,9 @@ module Fishwife
|
|
228
279
|
|
229
280
|
# FIXME: Support ranges?
|
230
281
|
|
231
|
-
|
282
|
+
IOUtil.write_file( path, output )
|
232
283
|
else
|
233
|
-
|
284
|
+
IOUtil.write_body( body, output )
|
234
285
|
end
|
235
286
|
|
236
287
|
# Close the body if we're supposed to.
|
data/pom.xml
CHANGED
data/spec/fishwife_spec.rb
CHANGED
@@ -34,7 +34,13 @@ describe Fishwife do
|
|
34
34
|
Net::HTTP.start(@options[:host], @options[:port]) do |http|
|
35
35
|
request = Net::HTTP::Post.new(path, headers)
|
36
36
|
request.form_data = params if params
|
37
|
-
|
37
|
+
if body
|
38
|
+
if body.respond_to?( :read )
|
39
|
+
request.body_stream = body
|
40
|
+
else
|
41
|
+
request.body = body
|
42
|
+
end
|
43
|
+
end
|
38
44
|
http.request(request)
|
39
45
|
end
|
40
46
|
end
|
@@ -42,7 +48,11 @@ describe Fishwife do
|
|
42
48
|
before(:all) do
|
43
49
|
@lock = Mutex.new
|
44
50
|
@app = Rack::Lint.new(TestApp.new)
|
45
|
-
@options = { :host => '127.0.0.1',
|
51
|
+
@options = { :host => '127.0.0.1',
|
52
|
+
:port => 9201,
|
53
|
+
:request_body_ram => 256,
|
54
|
+
:request_body_max => 96 * 1024,
|
55
|
+
:request_body_tmpdir => File.dirname( __FILE__ ) }
|
46
56
|
Net::HTTP.version_1_2
|
47
57
|
@server = Fishwife::HttpServer.new(@options)
|
48
58
|
@server.start(@app)
|
@@ -92,6 +102,38 @@ describe Fishwife do
|
|
92
102
|
content['request.params']['question'].should == question
|
93
103
|
end
|
94
104
|
|
105
|
+
it "Passes along larger non-form POST body" do
|
106
|
+
body = '<' + "f" * (93*1024) + '>'
|
107
|
+
headers = { "Content-Type" => "text/plain" }
|
108
|
+
response = post("/dcount", nil, headers, body)
|
109
|
+
response.code.should == "200"
|
110
|
+
response.body.to_i.should == body.size * 2
|
111
|
+
end
|
112
|
+
|
113
|
+
it "Passes along larger non-form POST body when chunked" do
|
114
|
+
body = '<' + "f" * (93*1024) + '>'
|
115
|
+
headers = { "Content-Type" => "text/plain",
|
116
|
+
"Transfer-Encoding" => "chunked" }
|
117
|
+
response = post("/dcount", nil, headers, StringIO.new( body ) )
|
118
|
+
response.code.should == "200"
|
119
|
+
response.body.to_i.should == body.size * 2
|
120
|
+
end
|
121
|
+
|
122
|
+
it "Rejects request body larger than maximum" do
|
123
|
+
body = '<' + "f" * (100*1024) + '>'
|
124
|
+
headers = { "Content-Type" => "text/plain" }
|
125
|
+
response = post("/count", nil, headers, body)
|
126
|
+
response.code.should == "413"
|
127
|
+
end
|
128
|
+
|
129
|
+
it "Rejects request body larger than maximum in chunked request" do
|
130
|
+
body = '<' + "f" * (100*1024) + '>'
|
131
|
+
headers = { "Content-Type" => "text/plain",
|
132
|
+
"Transfer-Encoding" => "chunked" }
|
133
|
+
response = post("/count", nil, headers, StringIO.new( body ) )
|
134
|
+
response.code.should == "413"
|
135
|
+
end
|
136
|
+
|
95
137
|
it "passes custom headers" do
|
96
138
|
response = get("/echo", "X-My-Header" => "Pancakes")
|
97
139
|
response.code.should == "200"
|
data/spec/test_app.rb
CHANGED
@@ -72,6 +72,14 @@ class TestApp
|
|
72
72
|
response.finish
|
73
73
|
end
|
74
74
|
|
75
|
+
def dcount(request)
|
76
|
+
inp = request.env['rack.input']
|
77
|
+
dc = inp.read.length
|
78
|
+
inp.rewind
|
79
|
+
dc += inp.read.length
|
80
|
+
[ 200, {}, [ dc.to_s ] ]
|
81
|
+
end
|
82
|
+
|
75
83
|
def multi_headers(request)
|
76
84
|
[ 204, { "Warning" => %w[ warn-1 warn-2 ].join( "\n" ) }, [] ]
|
77
85
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fishwife
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- David Kellum
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -135,21 +135,21 @@ files:
|
|
135
135
|
- Manifest.txt
|
136
136
|
- README.rdoc
|
137
137
|
- Rakefile
|
138
|
-
- pom.xml
|
139
138
|
- bin/fishwife
|
140
139
|
- example/config.ru
|
141
140
|
- example/giant.ru
|
142
|
-
- lib/fishwife/base.rb
|
143
141
|
- lib/fishwife.rb
|
142
|
+
- lib/fishwife/base.rb
|
143
|
+
- lib/fishwife/fishwife-1.7.0.jar
|
144
144
|
- lib/fishwife/http_server.rb
|
145
145
|
- lib/fishwife/rack_servlet.rb
|
146
146
|
- lib/rack/handler/fishwife.rb
|
147
|
+
- pom.xml
|
148
|
+
- spec/data/hello.txt
|
149
|
+
- spec/data/reddit-icon.png
|
147
150
|
- spec/fishwife_spec.rb
|
148
151
|
- spec/spec_helper.rb
|
149
152
|
- spec/test_app.rb
|
150
|
-
- spec/data/hello.txt
|
151
|
-
- spec/data/reddit-icon.png
|
152
|
-
- lib/fishwife/fishwife-1.6.1.jar
|
153
153
|
homepage: http://github.com/dekellum/fishwife
|
154
154
|
licenses: []
|
155
155
|
metadata: {}
|
@@ -171,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
171
|
version: '0'
|
172
172
|
requirements: []
|
173
173
|
rubyforge_project:
|
174
|
-
rubygems_version: 2.
|
174
|
+
rubygems_version: 2.4.5
|
175
175
|
signing_key:
|
176
176
|
specification_version: 4
|
177
177
|
summary: A Jetty based Rack HTTP 1.1 server.
|
Binary file
|