fishwife 1.9.0-java → 1.10.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
- SHA1:
3
- metadata.gz: 601c734db28498a70a3c7f24d973b7b0e2c479a3
4
- data.tar.gz: 714d5e5fd5431590b2890b6565a4671626037847
2
+ SHA256:
3
+ metadata.gz: e03291bc98a777e051010275b8cd1f16bf5887d45d84a315cae65c1983e5f822
4
+ data.tar.gz: e12785a24dd82799a8df83d0c9d64bbe93d2f809e13144d26ad0705cfcc4996a
5
5
  SHA512:
6
- metadata.gz: a53a415a0fa11dd101fbeb4ac1ee3c9c46d7b23bdfe6a877e60c029933b7f69f11b8bcd81c8285519f5541fbbf3210f5adde15c20e560afba9874dd2aa9ca56e
7
- data.tar.gz: 354dcf246450f3b55faced05224d4f671ae0abcd394c5e98768eb3ca665781ca83470c1b5dd6cd57e9ac86bbfdb1aa1d73efdee9c5634255768cb856688a41e4
6
+ metadata.gz: d881973e3c17742a28c7cf00315f2bf593be244b3d4d9eb6210674b1f0c8f5b8ca26b0a59a83d8336094fd1823b19177f689fd1ae9310680d54328f97ba6349c
7
+ data.tar.gz: 4b66135628a938cad6ddb9e3adc02a31588fe8c8424f58a94d1ae7511707cc3fc2828a04deea269707978d479e4dd0329f2997e4ff05cf927a5b337f7a61e4c5
@@ -1,3 +1,12 @@
1
+ === 1.10.0 (2017-6-16)
2
+ * Broaden to rjack-jetty [9.2.11, 9.5) (tested with 9.4.6)
3
+
4
+ * Replace the vestigial/broken `async.callback` implementation
5
+ with \Rack 1.5+ compliant, response (after-headers) only
6
+ {Hijacking}[http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking]
7
+ support via `rack.hijack`, Fishwife::HijackedIO (with Theo
8
+ Hultberg, #15 #16 #17)
9
+
1
10
  === 1.9.0 (2017-2-13)
2
11
  * Upgrade/broaden to rack [1.6.4, 2.1). Note that the rack 2.0.1 gem
3
12
  lists ruby 2.2.2 as its minimum ruby version, thus you will need to
@@ -10,6 +10,7 @@ example/giant.ru
10
10
  example/ssl.ru
11
11
  lib/fishwife/base.rb
12
12
  lib/fishwife.rb
13
+ lib/fishwife/hijacked_io.rb
13
14
  lib/fishwife/http_server.rb
14
15
  lib/fishwife/rack_servlet.rb
15
16
  lib/rack/handler/fishwife.rb
@@ -19,4 +20,5 @@ spec/test_app.rb
19
20
  spec/data/hello.txt
20
21
  spec/data/localhost.keystore
21
22
  spec/data/reddit-icon.png
22
- lib/fishwife/fishwife-1.9.0.jar
23
+ spec/fishwife/hijacked_io_spec.rb
24
+ lib/fishwife/fishwife-1.10.0.jar
@@ -1,4 +1,4 @@
1
- = Fishwife
1
+ = \Fishwife
2
2
 
3
3
  * http://github.com/dekellum/fishwife
4
4
  * Travis CI: {<img src="https://travis-ci.org/dekellum/fishwife.svg?branch=fishwife-dev" />}[https://travis-ci.org/dekellum/fishwife]
@@ -6,38 +6,44 @@
6
6
  == Description
7
7
 
8
8
  A hard working threaded HTTP 1.1 Rack server for JRuby using
9
- {Jetty}[http://www.eclipse.org/jetty/] 7.x or 9.x. Fishwife deploys
9
+ {Jetty}[http://www.eclipse.org/jetty/] 9. \Fishwife deploys
10
10
  and operates like other Ruby HTTP servers/rack handlers such as
11
11
  Mongrel or Puma. No Java legacy war files (e.g. warbler) required.
12
12
 
13
13
  As compared to similar projects, most notably
14
14
  {Mizuno}[https://github.com/matadon/mizuno]
15
- from which it was originally forked, Fishwife offers the following
15
+ from which it was originally forked, \Fishwife offers the following
16
16
  unique features:
17
17
 
18
- * Depends on the {rjack-jetty}[http://rjack.gravitext.com/jetty]
19
- gem which closely tracks the upstream Jetty project, currently for
20
- versions 7.x and 9.x. You can test and control the version of Jetty
21
- you use, for example via Bundler, independent of the Fishwife release.
18
+ * Depends on the {rjack-jetty}[http://rjack.gravitext.com/jetty] gem
19
+ which closely tracks the upstream Jetty project. You can test and
20
+ control the version of Jetty you use, for example via Bundler,
21
+ independent of the \Fishwife release.
22
22
 
23
- * Fishwife may itself be embedded in your own custom server/launcher
23
+ * \Fishwife may itself be embedded in your own custom server/launcher
24
24
  where you have total control over JVM options like heap size, number
25
25
  of threads, and other initialization/configuration details. For an
26
26
  example, see:
27
27
  {boxed-geminabox}[https://github.com/dekellum/boxed-geminabox#readme]
28
28
  and notes in {#6}[https://github.com/dekellum/fishwife/issues/6].
29
29
 
30
- * Logging is unified across Jetty, Fishwife, and your application via
31
- rjack-slf4j and on to any supported output adapter. (See Logging below)
30
+ * Logging is unified across Jetty, \Fishwife, and your application via
31
+ rjack-slf4j and on to any supported output adapter
32
+ (see {Logging}[#label-Logging] below).
33
+
34
+ * Includes \Rack 1.5
35
+ {Hijacking}[http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking]
36
+ support for asynchronous response (after headers) streaming via
37
+ Jetty's (Servlets 3.1) asynchronous implementation.
32
38
 
33
39
  * Rack output streaming is optimized in a custom java extension.
34
40
 
35
41
  * Simple foreground server. Compatible with several real process
36
- monitors (See Process Monitoring below)
42
+ monitors (see {Process Monitoring}[#label-Process+Monitoring] below).
37
43
 
38
44
  == Synopsis
39
45
 
40
- To use Fishwife with your Rack app and config.ru:
46
+ To use \Fishwife with your \Rack app and config.ru:
41
47
 
42
48
  gem install fishwife
43
49
  cd /path/to/my/rack/app
@@ -58,41 +64,33 @@ See also:
58
64
 
59
65
  == Logging
60
66
 
61
- Fishwife requires
67
+ \Fishwife requires
62
68
  {rjack-slf4j}[http://rjack.gravitext.com/slf4j/RJack/SLF4J.html] which
63
69
  provides a unified interface for Java/Ruby logging. Jetty auto-detect
64
70
  SLF4J's presence and logs there. A logging output provider must also
65
71
  be loaded. The fishwife script will load
66
72
  {rjack-logback}[http://rjack.gravitext.com/logback/RJack/Logback.html]
67
73
  if found or otherwise fall back to `rjack-slf4j/simple`. If you are
68
- starting Fishwife through some other means than the fishwife script, you
74
+ starting \Fishwife through some other means than the fishwife script, you
69
75
  will need to load an output provider (see linked docs and notes in
70
76
  {#6}[https://github.com/dekellum/fishwife/issues/6]).
71
77
 
72
78
  == Process Monitoring
73
79
 
74
- We assume this isn't your only production ruby server and don't
75
- include any one-off, half-baked process control and monitoring
80
+ We assume this isn't your only production Ruby or Java server and
81
+ don't include any one-off or half-baked process control and monitoring
76
82
  features. Note that Unix-like fork(2) is not safe on the JVM and thus
77
83
  from JRuby so its not practical to support a fishwife --daemon
78
- flag. Fishwife simply logs to STDERR by default. Instead consider
84
+ flag. \Fishwife simply logs to STDERR by default. Instead consider
79
85
  using:
80
86
 
87
+ * Linux systemd, which supports foreground processes (Type=simple)
88
+
81
89
  * {Bluepill}[https://github.com/arya/bluepill#readme] or God (MRI
82
- Ruby) process monitors support self-forking of foreground processes,
83
- for example `rackup` or `fishwife`.
90
+ Ruby) process monitors support foreground processes.
84
91
 
85
92
  * The {Iyyov}[https://github.com/dekellum/iyyov#readme] (JRuby)
86
- process monitor supports self-daemonizing via the hashdot
87
- launcher. Your app can also be packaged/updated as a gem if
88
- desired. For an example see:
89
- {boxed-geminabox}[https://github.com/dekellum/boxed-geminabox#readme].
90
-
91
- * Linux systemd
92
-
93
- All options also support redirecting log output (including any
94
- JVM-level errors) to a file. Iyyov supports log rotation internally.
95
- Unix-like logrotate(8) may be used with the former process monitors.
93
+ process monitor supports self-daemonizing via the hashdot launcher.
96
94
 
97
95
  == Building from Source and Contributing
98
96
 
data/Rakefile CHANGED
@@ -12,3 +12,10 @@ task :publish_rdoc => [ :clean, :rerdoc ] do
12
12
  aws s3 sync --acl public-read doc/ s3://rdoc.gravitext.com/fishwife/
13
13
  SH
14
14
  end
15
+
16
+ task :rdoc do
17
+ sh <<-SH
18
+ rm -rf doc/fonts
19
+ cp rdoc_css/*.css doc/css/
20
+ SH
21
+ end
@@ -32,4 +32,5 @@ end
32
32
  require 'rack'
33
33
  require 'fishwife/rack_servlet'
34
34
  require 'fishwife/http_server'
35
+ require 'fishwife/hijacked_io'
35
36
  require 'rack/handler/fishwife'
@@ -15,6 +15,6 @@
15
15
  #++
16
16
 
17
17
  module Fishwife
18
- VERSION = '1.9.0'
18
+ VERSION = '1.10.0'
19
19
  LIB_DIR = File.dirname( __FILE__ )
20
20
  end
@@ -0,0 +1,99 @@
1
+ #--
2
+ # Copyright (c) 2017 Theo Hultberg, David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You may
6
+ # obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ module Fishwife
18
+
19
+ # Wraps a Java Servlets
20
+ # {AsyncContext}[http://docs.oracle.com/javaee/7/api/javax/servlet/AsyncContext.html]
21
+ # as a ruby ::IO-like class for use in \Rack response (after headers)
22
+ # {Hijacking}[http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking].
23
+ #
24
+ # This currently supports only (blocking) #write, #flush, and
25
+ # #close. Other methods raise NotImplementedError.
26
+ class HijackedIO
27
+ def initialize(async_context)
28
+ @async_context = async_context
29
+ @closed = false
30
+ end
31
+
32
+ # Writes the given str to the #output_stream as bytes. If str is
33
+ # not a ruby String, it will be converted using Object#to_s.
34
+ # Returns the bytesize of the String (Java's OutputStream#write is
35
+ # never partial).
36
+ def write(str)
37
+ str = str.to_s
38
+ IOUtil.write(str, out_stream)
39
+ str.bytesize
40
+ end
41
+
42
+ # Write the given Array of ruby ::String instances to the output
43
+ # stream as bytes. This is included here, because #write(ary.to_s)
44
+ # would otherwise result in inspect-styled output.
45
+ def write_array(ary)
46
+ IOUtil.write_body(ary, out_stream)
47
+ end
48
+
49
+ # Flushes the underlying #output_stream and returns self.
50
+ def flush
51
+ out_stream.flush
52
+ self
53
+ end
54
+
55
+ # Calls complete on the underlying AsyncContext. Subsequent calls
56
+ # after the first are ignored.
57
+ def close
58
+ if !@closed
59
+ @async_context.complete
60
+ @closed = true
61
+ end
62
+ nil
63
+ end
64
+
65
+ alias_method :close_write, :close
66
+
67
+ # Return true if #close has been called.
68
+ def closed?
69
+ @closed
70
+ end
71
+
72
+ # Raises NotImplementedError.
73
+ def read(*)
74
+ raise NotImplementedError, "##{__method__} on hijacked IO is not supported"
75
+ end
76
+
77
+ # Raises NotImplementedError.
78
+ def read_nonblock(*)
79
+ raise NotImplementedError, "##{__method__} on hijacked IO is not supported"
80
+ end
81
+
82
+ # Raises NotImplementedError.
83
+ def write_nonblock(*)
84
+ raise NotImplementedError, "##{__method__} on hijacked IO is not supported"
85
+ end
86
+
87
+ # Raises NotImplementedError.
88
+ def close_read
89
+ raise NotImplementedError, "##{__method__} on hijacked IO is not supported"
90
+ end
91
+
92
+ private
93
+
94
+ def out_stream
95
+ @out_stream ||= @async_context.response.output_stream
96
+ end
97
+
98
+ end
99
+ end
@@ -38,10 +38,14 @@ module Fishwife
38
38
  #
39
39
  class RackServlet < HttpServlet
40
40
  java_import 'java.io.FileInputStream'
41
- java_import 'org.eclipse.jetty.continuation.ContinuationSupport'
42
41
 
43
42
  ASCII_8BIT = Encoding.find( "ASCII-8BIT" ) if defined?( Encoding )
44
43
 
44
+ REQ_HIJACK_NOT_SUPPORTED = lambda do
45
+ raise( NotImplementedError,
46
+ 'Only response hijacking is supported, not request hijacking' )
47
+ end
48
+
45
49
  def initialize( app, opts = {} )
46
50
  super()
47
51
  @log = RJack::SLF4J[ self.class ]
@@ -56,57 +60,19 @@ module Fishwife
56
60
  # involves is translating the various bits of the Servlet API into
57
61
  # the Rack API on the way in, and translating the response back on
58
62
  # the way out.
59
- #
60
- # Also, we implement a common extension to the Rack api for
61
- # asynchronous request processing. We supply an 'async.callback'
62
- # parameter in env to the Rack application. If we catch an :async
63
- # symbol thrown by the app, we initiate a Jetty continuation.
64
- #
65
- # When 'async.callback' gets a response with empty headers and an
66
- # empty body, we declare the async response finished.
67
63
  def service(request, response)
68
64
  # Turn the ServletRequest into a Rack env hash
69
65
  env = servlet_to_rack(request)
70
66
 
71
- # Handle asynchronous responses via Servlet continuations.
72
- continuation = ContinuationSupport.getContinuation(request)
73
-
74
- # If this is an expired connection, do nothing.
75
- return if continuation.isExpired
76
-
77
- # We should never be re-dispatched.
78
- raise("Request re-dispatched.") unless continuation.isInitial
79
-
80
67
  # Add our own special bits to the rack environment so that Rack
81
68
  # middleware can have access to the Java internals.
82
69
  env['rack.java.servlet'] = true
83
70
  env['rack.java.servlet.request'] = request
84
71
  env['rack.java.servlet.response'] = response
85
- env['rack.java.servlet.continuation'] = continuation
86
-
87
- # Add an callback that can be used to add results to the
88
- # response asynchronously.
89
- env['async.callback'] = lambda do |rack_response|
90
- servlet_response = continuation.getServletResponse
91
- rack_to_servlet(rack_response, servlet_response) and
92
- continuation.complete
93
- end
94
-
95
- # Execute the Rack request.
96
- catch(:async) do
97
- rack_response = @app.call(env)
98
-
99
- # For apps that don't throw :async.
100
- unless(rack_response[0] == -1)
101
- # Nope, nothing asynchronous here.
102
- rack_to_servlet(rack_response, response)
103
- return
104
- end
105
- end
106
-
107
- # If we got here, this is a continuation.
108
- continuation.suspend(response)
109
72
 
73
+ rack_response = @app.call(env)
74
+ rack_to_servlet(rack_response, request, response)
75
+ nil
110
76
  rescue RequestBodyTooLarge => e
111
77
  @log.warn( "On service: #{e.class.name}: #{e.message}" )
112
78
  response.sendError( 413 )
@@ -183,6 +149,9 @@ module Fishwife
183
149
  # The output stream defaults to stderr.
184
150
  env['rack.errors'] ||= $stderr
185
151
 
152
+ env['rack.hijack?'] = true
153
+ env['rack.hijack'] = REQ_HIJACK_NOT_SUPPORTED
154
+
186
155
  # All done, hand back the Rack request.
187
156
  env
188
157
  end
@@ -226,10 +195,7 @@ module Fishwife
226
195
  io
227
196
  end
228
197
 
229
- # Turns a Rack response into a Servlet response; can be called
230
- # multiple times. Returns true if this is the full request (either
231
- # a synchronous request or the last part of an async request),
232
- # false otherwise.
198
+ # Turns a Rack response into a Servlet response.
233
199
  #
234
200
  # Note that keep-alive *only* happens if we get either a pathname
235
201
  # (because we can find the length ourselves), or if we get a
@@ -239,61 +205,55 @@ module Fishwife
239
205
  # something *huge*.
240
206
  #
241
207
  # http://docstore.mik.ua/orelly/java-ent/servlet/ch05_03.htm
242
- def rack_to_servlet(rack_response, response)
208
+ def rack_to_servlet(rack_response, request, response)
243
209
  # Split apart the Rack response.
244
210
  status, headers, body = rack_response
245
211
 
246
- # We assume the request is finished if we got empty headers,
247
- # an empty body, and we have a committed response.
248
- finished = ( headers.empty? and
249
- body.respond_to?(:empty?) and body.empty?)
250
- return(true) if (finished and response.isCommitted)
251
-
252
- # No need to send headers again if we've already shipped data
253
- # out on an async request.
254
- unless(response.isCommitted)
255
- # Set the HTTP status code.
256
- response.setStatus(status.to_i)
257
-
258
- # Add all the result headers.
259
- headers.each do |h, v|
260
- case h
261
- when 'Content-Length'
262
- # Did we get a Content-Length header?
263
- response.setContentLength(v.to_i) if v
264
- when 'Content-Type'
265
- # Did we get a Content-Type header?
266
- response.setContentType(v) if v
267
- else
268
- v.split("\n").each { |val| response.addHeader(h, val) }
269
- end
212
+ # Set the HTTP status code.
213
+ response.setStatus(status.to_i)
214
+
215
+ response_hijack_callback = nil
216
+
217
+ # Add all the result headers.
218
+ headers.each do |h, v|
219
+ case h
220
+ when 'Content-Length'
221
+ response.setContentLength(v.to_i) if v
222
+ when 'Content-Type'
223
+ response.setContentType(v) if v
224
+ when 'rack.hijack'
225
+ response_hijack_callback = v
226
+ else
227
+ v.split("\n").each { |val| response.addHeader(h, val) }
270
228
  end
271
229
  end
272
230
 
273
- output = response.getOutputStream
231
+ if response_hijack_callback
232
+ response_hijack_callback.call( HijackedIO.new(request.start_async) )
233
+ else
234
+ output = response.getOutputStream
274
235
 
275
- if body.respond_to?( :to_path )
236
+ if body.respond_to?( :to_path )
276
237
 
277
- path = body.to_path
238
+ path = body.to_path
278
239
 
279
- # Set Content-Length unless this is an async request.
280
- unless headers['Content-Length']
281
- response.setContentLength( File.size( path ) )
282
- end
240
+ unless headers['Content-Length']
241
+ response.setContentLength( File.size( path ) )
242
+ end
283
243
 
284
- # FIXME: Support ranges?
244
+ # FIXME: Support ranges?
285
245
 
286
- IOUtil.write_file( path, output )
287
- else
288
- IOUtil.write_body( body, output )
289
- end
246
+ IOUtil.write_file( path, output )
247
+ else
248
+ IOUtil.write_body( body, output )
249
+ end
290
250
 
291
- # Close the body if we're supposed to.
292
- body.close if body.respond_to?(:close)
251
+ # Close the body if we're supposed to.
252
+ body.close if body.respond_to?(:close)
293
253
 
294
- # All done.
295
- output.close
296
- false
254
+ # All done.
255
+ output.close
256
+ end
297
257
  end
298
258
  end
299
259
  end
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.9.0</version>
7
+ <version>1.10.0</version>
8
8
  <name>Fishwife Java Extension</name>
9
9
 
10
10
  <properties>
@@ -0,0 +1,105 @@
1
+ #--
2
+ # Copyright (c) 2017 Theo Hultberg, David Kellum
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
5
+ # may not use this file except in compliance with the License. You may
6
+ # obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
+ # implied. See the License for the specific language governing
14
+ # permissions and limitations under the License.
15
+ #++
16
+
17
+ require 'spec_helper'
18
+
19
+ describe Fishwife::HijackedIO do
20
+ subject :io do
21
+ described_class.new(async_context)
22
+ end
23
+
24
+ let :async_context do
25
+ double(:async_context)
26
+ end
27
+
28
+ let :buffer do
29
+ StringIO.new
30
+ end
31
+
32
+ let :output_stream do
33
+ buffer.to_outputstream
34
+ end
35
+
36
+ before do
37
+ response = double(:response)
38
+ async_context.stub(:response).and_return(response)
39
+ async_context.stub(:complete)
40
+ response.stub(:output_stream).and_return(output_stream)
41
+ end
42
+
43
+ describe '#write' do
44
+ it 'writes a string to the response output stream' do
45
+ io.write('hello')
46
+ buffer.string.should eq('hello')
47
+ end
48
+
49
+ it 'returns the number of bytes written' do
50
+ io.write('hello').should eq(5)
51
+ io.write("\u2603").should eq(3)
52
+ end
53
+ end
54
+
55
+ describe '#flush' do
56
+ let :output_stream do
57
+ double(:output_stream)
58
+ end
59
+
60
+ before do
61
+ output_stream.stub(:flush)
62
+ end
63
+
64
+ it 'flushes the response output stream' do
65
+ output_stream.should_receive(:flush)
66
+ io.flush
67
+ end
68
+
69
+ it 'returns self' do
70
+ io.flush.should equal(io)
71
+ end
72
+ end
73
+
74
+ [:close, :close_write].each do |method_name|
75
+ describe "##{method_name}" do
76
+ before do
77
+ async_context.stub(:complete)
78
+ end
79
+
80
+ it 'completes the async processing' do
81
+ async_context.should_receive(:complete)
82
+ io.send(method_name)
83
+ end
84
+
85
+ it 'returns nil' do
86
+ io.send(method_name).should be_nil
87
+ end
88
+ end
89
+ end
90
+
91
+ describe '#closed?' do
92
+ context 'before #close has been called' do
93
+ it 'returns false' do
94
+ io.should_not be_closed
95
+ end
96
+ end
97
+
98
+ context 'after #close has been called' do
99
+ it 'returns true' do
100
+ io.close
101
+ io.should be_closed
102
+ end
103
+ end
104
+ end
105
+ end
@@ -28,12 +28,16 @@ describe Fishwife do
28
28
 
29
29
  describe "for #{scheme.to_s} scheme" do
30
30
 
31
- def get(path, headers = {})
31
+ def get(path, headers = {}, &block )
32
32
  Net::HTTP.start(@options[:host], @options[:port],
33
33
  nil, nil, nil, nil, #Irrelevant http proxy parameters
34
34
  @https_client_opts) do |http|
35
35
  request = Net::HTTP::Get.new(path, headers)
36
- http.request(request)
36
+ if block
37
+ http.request(request, &block)
38
+ else
39
+ http.request(request)
40
+ end
37
41
  end
38
42
  end
39
43
 
@@ -232,31 +236,38 @@ describe Fishwife do
232
236
  response.body.should == '8da4b60a9bbe205d4d3699985470627e'
233
237
  end
234
238
 
235
- it "handles async requests" do
236
- pending "Causes intermittent 30s pauses, TestApp.push/pull is sketchy"
237
- lock = Mutex.new
238
- buffer = Array.new
239
-
240
- clients = 10.times.map do |index|
241
- Thread.new do
242
- Net::HTTP.start(@options[:host], @options[:port]) do |http|
243
- response = http.get("/pull")
244
- lock.synchronize {
245
- buffer << "#{index}: #{response.body}" }
246
- end
239
+ it "handles frozen Rack responses" do
240
+ response = get("/frozen_response")
241
+ response.code.should == "200"
242
+ end
243
+
244
+ it "handles response hijacking" do
245
+ chunks = []
246
+ get( "/hijack" ) do |resp|
247
+ resp.code.should == '200'
248
+ resp.read_body do |chunk|
249
+ chunks << chunk
247
250
  end
248
251
  end
252
+ chunks.should == [ "hello", " world\n" ]
253
+ end
249
254
 
250
- lock.synchronize { buffer.should be_empty }
251
- post("/push", 'message' => "one")
252
- clients.each { |c| c.join }
253
- lock.synchronize { buffer.should_not be_empty }
254
- lock.synchronize { buffer.count.should == 10 }
255
+ it "handles ugly response hijacking" do
256
+ chunks = []
257
+ get( "/hijack?ugly=t" ) do |resp|
258
+ resp.code.should == '200'
259
+ resp.read_body do |chunk|
260
+ chunks << chunk
261
+ end
262
+ end
263
+ chunks.should == [ "hello", " world\n" ]
255
264
  end
256
265
 
257
- it "handles frozen Rack responses" do
258
- response = get("/frozen_response")
259
- response.code.should == "200"
266
+ it "gracefully declines request hijacking" do
267
+ chunks = []
268
+ resp = get( "/request_hijack" )
269
+ resp.code.should == '200'
270
+ resp.body.should =~ /^Only response hijacking is supported/
260
271
  end
261
272
 
262
273
  end
@@ -28,10 +28,6 @@ require 'json'
28
28
  #
29
29
  # /file:: Returns a file for downloading.
30
30
  #
31
- # /push:: Publishes a message to async listeners.
32
- #
33
- # /pull:: Recieves messages sent via /push using async.
34
- #
35
31
  # A request to any endpoint not listed above will return a 404 error.
36
32
  class TestApp
37
33
  def initialize
@@ -84,33 +80,6 @@ class TestApp
84
80
  [ 204, { "Warning" => %w[ warn-1 warn-2 ].join( "\n" ) }, [] ]
85
81
  end
86
82
 
87
- def push(request)
88
- message = request.params['message']
89
-
90
- @subscribers.reject! do |subscriber|
91
- begin
92
- response = Rack::Response.new
93
- if(message.empty?)
94
- subscriber.call(response.finish)
95
- next(true)
96
- else
97
- response.write(message)
98
- subscriber.call(response.finish)
99
- next(false)
100
- end
101
- rescue java.io.IOException => error
102
- next(true)
103
- end
104
- end
105
-
106
- ping(request)
107
- end
108
-
109
- def pull(request)
110
- @subscribers << request.env['async.callback']
111
- throw(:async)
112
- end
113
-
114
83
  def download(request)
115
84
  file = File.new( File.dirname( __FILE__ ) + "/data/reddit-icon.png" )
116
85
  def file.to_path
@@ -130,4 +99,59 @@ class TestApp
130
99
  def frozen_response(request)
131
100
  [200, {}.freeze, [].freeze].freeze
132
101
  end
102
+
103
+ def hijack(request)
104
+ if request.env['rack.hijack?']
105
+ hm = request.params['ugly'] ? :ugly_response : :hijack_response
106
+ [200, {'rack.hijack' => method(hm)}, []]
107
+ else
108
+ [501, {}, ['Hijack not supported']]
109
+ end
110
+ end
111
+
112
+ def request_hijack(request)
113
+ if request.env['rack.hijack?']
114
+ hm = request.env['rack.hijack']
115
+ if hm
116
+ io = hm.call
117
+ [503, {}, ['Should not get IO: ', io.inspect]]
118
+ else
119
+ [502, {}, ['rack.hijack: ', hm.inspect]]
120
+ end
121
+ else
122
+ [501, {}, ['Hijack not supported']]
123
+ end
124
+ rescue NotImplementedError => e
125
+ [200, {}, [e.to_s]]
126
+ end
127
+
128
+ private
129
+
130
+ def hijack_response(io)
131
+ Thread.start do
132
+ io.write(:hello)
133
+ io.flush
134
+ io.write(" world\n")
135
+ io.close
136
+ end
137
+ end
138
+
139
+ def ugly_response(io)
140
+ Thread.start do
141
+ sleep( rand * 0.1 )
142
+ io.write('h')
143
+ io.write('ello')
144
+ io.write(nil)
145
+ io.flush
146
+ io.flush
147
+ io.write('')
148
+ io.flush
149
+ io.write(" world\n")
150
+ io.write(nil)
151
+ io.flush
152
+ io.close
153
+ io.close
154
+ end
155
+ sleep( rand * 0.1 )
156
+ end
133
157
  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.9.0
4
+ version: 1.10.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: 2017-02-13 00:00:00.000000000 Z
11
+ date: 2017-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -38,7 +38,7 @@ dependencies:
38
38
  version: 9.2.11
39
39
  - - "<"
40
40
  - !ruby/object:Gem::Version
41
- version: '9.4'
41
+ version: '9.5'
42
42
  name: rjack-jetty
43
43
  prerelease: false
44
44
  type: :runtime
@@ -49,7 +49,7 @@ dependencies:
49
49
  version: 9.2.11
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
- version: '9.4'
52
+ version: '9.5'
53
53
  - !ruby/object:Gem::Dependency
54
54
  requirement: !ruby/object:Gem::Requirement
55
55
  requirements:
@@ -120,7 +120,9 @@ dependencies:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
122
  version: '2.1'
123
- description: A hard working threaded HTTP 1.1 Rack server for JRuby using Jetty 7.x or 9.x. Fishwife deploys and operates like other Ruby HTTP servers/rack handlers such as Mongrel or Puma. No Java legacy war files (e.g. warbler) required.
123
+ description: A hard working threaded HTTP 1.1 Rack server for JRuby using Jetty 9.
124
+ \Fishwife deploys and operates like other Ruby HTTP servers/rack handlers such as
125
+ Mongrel or Puma. No Java legacy war files (e.g. warbler) required.
124
126
  email:
125
127
  - dek-oss@gravitext.com
126
128
  executables:
@@ -141,7 +143,8 @@ files:
141
143
  - example/ssl.ru
142
144
  - lib/fishwife.rb
143
145
  - lib/fishwife/base.rb
144
- - lib/fishwife/fishwife-1.9.0.jar
146
+ - lib/fishwife/fishwife-1.10.0.jar
147
+ - lib/fishwife/hijacked_io.rb
145
148
  - lib/fishwife/http_server.rb
146
149
  - lib/fishwife/rack_servlet.rb
147
150
  - lib/rack/handler/fishwife.rb
@@ -149,6 +152,7 @@ files:
149
152
  - spec/data/hello.txt
150
153
  - spec/data/localhost.keystore
151
154
  - spec/data/reddit-icon.png
155
+ - spec/fishwife/hijacked_io_spec.rb
152
156
  - spec/fishwife_spec.rb
153
157
  - spec/spec_helper.rb
154
158
  - spec/test_app.rb
@@ -173,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
177
  version: '0'
174
178
  requirements: []
175
179
  rubyforge_project:
176
- rubygems_version: 2.6.8
180
+ rubygems_version: 2.6.11
177
181
  signing_key:
178
182
  specification_version: 4
179
183
  summary: A Jetty based Rack HTTP 1.1 server.