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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a9e85f7c2e0d8098b655d39ebf44f4849178422
4
- data.tar.gz: 8502ab39da87286743f75c00a7d85bcebb109dc0
3
+ metadata.gz: c401c8d2d0ab0ad9a9fa4c2f3e7698f6efa66814
4
+ data.tar.gz: 00ec5e80418ebed7743eda5332c7bc594d0f58be
5
5
  SHA512:
6
- metadata.gz: bba4387c379a74a757f28c07a02300867493e17bb779e8813a9e9db25aa99ee82ba220bab7f6d4fadbc57d608a1cecd7ea5c16cf2e366b00bad531de9c207d02
7
- data.tar.gz: 64dc0c62257adffb15171095b52bf3e8e750e8a1e293ae66c322e56026a4815f743b39e2c140bc6599050d183ae2cfd2a9505de48f12e92f23042523921dace3
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
@@ -17,4 +17,4 @@ spec/spec_helper.rb
17
17
  spec/test_app.rb
18
18
  spec/data/hello.txt
19
19
  spec/data/reddit-icon.png
20
- lib/fishwife/fishwife-1.6.1.jar
20
+ lib/fishwife/fishwife-1.7.0.jar
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
@@ -15,6 +15,6 @@
15
15
  #++
16
16
 
17
17
  module Fishwife
18
- VERSION = '1.6.1'
18
+ VERSION = '1.7.0'
19
19
  LIB_DIR = File.dirname( __FILE__ )
20
20
  end
Binary file
@@ -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].each do |k|
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
- # Apply options as setters
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( '/', { '/*' => RackServlet.new( app ) } )
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
- # The input stream is a wrapper around the Java InputStream.
166
- input = request.getInputStream.to_io.binmode
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
- OutputUtil.write_file( path, output )
282
+ IOUtil.write_file( path, output )
232
283
  else
233
- OutputUtil.write_body( body, output )
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
@@ -4,7 +4,7 @@
4
4
  <groupId>fishwife</groupId>
5
5
  <artifactId>fishwife</artifactId>
6
6
  <packaging>jar</packaging>
7
- <version>1.6.1</version>
7
+ <version>1.7.0</version>
8
8
  <name>Fishwife Java Extension</name>
9
9
 
10
10
  <properties>
@@ -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
- request.body = body if body
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', :port => 9201 }
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.6.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-01-25 00:00:00.000000000 Z
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.1.9
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