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 +5 -5
- data/History.rdoc +9 -0
- data/Manifest.txt +3 -1
- data/README.rdoc +27 -29
- data/Rakefile +7 -0
- data/lib/fishwife.rb +1 -0
- data/lib/fishwife/base.rb +1 -1
- data/lib/fishwife/fishwife-1.10.0.jar +0 -0
- data/lib/fishwife/hijacked_io.rb +99 -0
- data/lib/fishwife/rack_servlet.rb +48 -88
- data/pom.xml +1 -1
- data/spec/fishwife/hijacked_io_spec.rb +105 -0
- data/spec/fishwife_spec.rb +33 -22
- data/spec/test_app.rb +55 -31
- metadata +11 -7
- data/lib/fishwife/fishwife-1.9.0.jar +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e03291bc98a777e051010275b8cd1f16bf5887d45d84a315cae65c1983e5f822
|
4
|
+
data.tar.gz: e12785a24dd82799a8df83d0c9d64bbe93d2f809e13144d26ad0705cfcc4996a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d881973e3c17742a28c7cf00315f2bf593be244b3d4d9eb6210674b1f0c8f5b8ca26b0a59a83d8336094fd1823b19177f689fd1ae9310680d54328f97ba6349c
|
7
|
+
data.tar.gz: 4b66135628a938cad6ddb9e3adc02a31588fe8c8424f58a94d1ae7511707cc3fc2828a04deea269707978d479e4dd0329f2997e4ff05cf927a5b337f7a61e4c5
|
data/History.rdoc
CHANGED
@@ -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
|
data/Manifest.txt
CHANGED
@@ -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
|
-
|
23
|
+
spec/fishwife/hijacked_io_spec.rb
|
24
|
+
lib/fishwife/fishwife-1.10.0.jar
|
data/README.rdoc
CHANGED
@@ -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/]
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
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 (
|
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
|
75
|
-
include any one-off
|
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
|
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
data/lib/fishwife.rb
CHANGED
data/lib/fishwife/base.rb
CHANGED
Binary file
|
@@ -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
|
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
|
-
#
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
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
|
-
|
231
|
+
if response_hijack_callback
|
232
|
+
response_hijack_callback.call( HijackedIO.new(request.start_async) )
|
233
|
+
else
|
234
|
+
output = response.getOutputStream
|
274
235
|
|
275
|
-
|
236
|
+
if body.respond_to?( :to_path )
|
276
237
|
|
277
|
-
|
238
|
+
path = body.to_path
|
278
239
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
end
|
240
|
+
unless headers['Content-Length']
|
241
|
+
response.setContentLength( File.size( path ) )
|
242
|
+
end
|
283
243
|
|
284
|
-
|
244
|
+
# FIXME: Support ranges?
|
285
245
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
246
|
+
IOUtil.write_file( path, output )
|
247
|
+
else
|
248
|
+
IOUtil.write_body( body, output )
|
249
|
+
end
|
290
250
|
|
291
|
-
|
292
|
-
|
251
|
+
# Close the body if we're supposed to.
|
252
|
+
body.close if body.respond_to?(:close)
|
293
253
|
|
294
|
-
|
295
|
-
|
296
|
-
|
254
|
+
# All done.
|
255
|
+
output.close
|
256
|
+
end
|
297
257
|
end
|
298
258
|
end
|
299
259
|
end
|
data/pom.xml
CHANGED
@@ -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
|
data/spec/fishwife_spec.rb
CHANGED
@@ -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
|
-
|
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
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
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 "
|
258
|
-
|
259
|
-
|
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
|
data/spec/test_app.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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.
|
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
|
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.
|
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.
|
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.
|
Binary file
|