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