fishwife 1.6.1-java → 1.7.0-java
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 +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
|