fishwife 1.9.0-java → 1.10.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
- 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.