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 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