trinidad_rack 0.1.0

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.
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ == Trinidad-Rack
2
+
3
+ Copyright (c) 2011 David Calavera
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ == Additional Bundled Software
25
+
26
+ lib/trinidad_rack/rack_servlet.rb is a work of Don Werve and it's licensed according to the terms of Apache License, Version 2.0 (current).
27
+ See http://www.apache.org/licenses/LICENSE-2.0 for details.
data/README ADDED
@@ -0,0 +1,30 @@
1
+ Trinidad-Rack
2
+ -------------
3
+
4
+ Library that replaces Trinidad's rack layer from JRuby-Rack to the Mizuno Rack Servlet.
5
+
6
+ It provides better integration with tools that relie on Rack like Capybara
7
+ or any other Rack-Test based tool.
8
+
9
+ RackServlet is a work of Don Werve and it's licensed under the Apache
10
+ License. This library modifies its code to remove the asynchronous layer
11
+ bounded to Jetty.
12
+
13
+ Installation
14
+ ============
15
+
16
+ $ jruby -S gem install trinidad_rack
17
+
18
+ Execution
19
+ =========
20
+
21
+ $ jruby -S trinidad_rack
22
+
23
+ or
24
+
25
+ $ rackup -s Trinidad
26
+
27
+ [IMPORTANT]
28
+
29
+ This is totally experimental and due to Mizuno and JRuby-Rack
30
+ work in a completely different way some features cannot work as expected.
@@ -0,0 +1,144 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/testtask'
49
+ Rake::TestTask.new(:test) do |test|
50
+ test.libs << 'lib' << 'test'
51
+ test.pattern = 'test/**/test_*.rb'
52
+ test.verbose = true
53
+ end
54
+
55
+ desc "Generate RCov test coverage and open in your browser"
56
+ task :coverage do
57
+ require 'rcov'
58
+ sh "rm -fr coverage"
59
+ sh "rcov test/test_*.rb"
60
+ sh "open coverage/index.html"
61
+ end
62
+
63
+ require 'rake/rdoctask'
64
+ Rake::RDocTask.new do |rdoc|
65
+ rdoc.rdoc_dir = 'rdoc'
66
+ rdoc.title = "#{name} #{version}"
67
+ rdoc.rdoc_files.include('README*')
68
+ rdoc.rdoc_files.include('lib/**/*.rb')
69
+ end
70
+
71
+ desc "Open an irb session preloaded with this library"
72
+ task :console do
73
+ sh "irb -rubygems -r ./lib/#{name}.rb"
74
+ end
75
+
76
+ #############################################################################
77
+ #
78
+ # Custom tasks (add your own tasks here)
79
+ #
80
+ #############################################################################
81
+
82
+ task :default => :spec
83
+
84
+ require 'rspec/core/rake_task'
85
+
86
+ RSpec::Core::RakeTask.new(:spec) do |spec|
87
+ spec.rspec_opts = ['--colour', "--format documentation"]
88
+ end
89
+
90
+ #############################################################################
91
+ #
92
+ # Packaging tasks
93
+ #
94
+ #############################################################################
95
+
96
+ task :release => :build do
97
+ unless `git branch` =~ /^\* master$/
98
+ puts "You must be on the master branch to release!"
99
+ exit!
100
+ end
101
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
102
+ sh "git tag v#{version}"
103
+ sh "git push origin master"
104
+ sh "git push --tags"
105
+ sh "gem push pkg/#{name}-#{version}.gem"
106
+ end
107
+
108
+ task :build => :gemspec do
109
+ sh "mkdir -p pkg"
110
+ sh "gem build #{gemspec_file}"
111
+ sh "mv #{gem_file} pkg"
112
+ end
113
+
114
+ task :install => :build do
115
+ sh "gem install pkg/#{name}-#{version}.gem"
116
+ end
117
+
118
+ task :gemspec do
119
+ # read spec file and split out manifest section
120
+ spec = File.read(gemspec_file)
121
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
122
+
123
+ # replace name version and date
124
+ replace_header(head, :name)
125
+ replace_header(head, :version)
126
+ replace_header(head, :date)
127
+ #comment this out if your rubyforge_project has a different name
128
+ replace_header(head, :rubyforge_project)
129
+
130
+ # determine file list from git ls-files
131
+ files = `git ls-files`.
132
+ split("\n").
133
+ sort.
134
+ reject { |file| file =~ /^\./ }.
135
+ reject { |file| file =~ /^(rdoc|pkg|src|trinidad-libs|rakelib)/ }.
136
+ map { |file| " #{file}" }.
137
+ join("\n")
138
+
139
+ # piece file back together and write
140
+ manifest = " s.files = %w[\n#{files}\n ]\n"
141
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
142
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
143
+ puts "Updated #{gemspec_file}"
144
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env jruby
2
+
3
+ require 'rack'
4
+
5
+ require 'trinidad_rack'
6
+
7
+ server = Rack::Server.new
8
+ server.options[:server] = 'trinidad'
9
+ server.start
@@ -0,0 +1,18 @@
1
+ require 'rack'
2
+ require 'trinidad'
3
+
4
+ module Rack
5
+ module Handler
6
+ class Trinidad
7
+ def self.run(app, options={})
8
+ opts = options.dup
9
+ opts[:app] = app
10
+ opts[:port] = 3000
11
+ opts[:address] = (options[:Host] || 'localhost')
12
+ ::Trinidad::Server.new(opts).start
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ Rack::Handler.register 'trinidad', 'Rack::Handler::Trinidad'
@@ -0,0 +1,10 @@
1
+ require 'rack/handler/trinidad'
2
+
3
+ require 'trinidad_rack/rack_servlet'
4
+ require 'trinidad_rack/trinidad_ext'
5
+
6
+ module Trinidad
7
+ module Rack
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
@@ -0,0 +1,196 @@
1
+ #
2
+ # Wraps a Rack application in a Java servlet.
3
+ #
4
+ # Relevant documentation:
5
+ #
6
+ # http://rack.rubyforge.org/doc/SPEC.html
7
+ # http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax
8
+ # /servlet/http/HttpServlet.html
9
+ #
10
+ module Trinidad
11
+ module Rack
12
+ java_import 'javax.servlet.http.HttpServlet'
13
+
14
+ class RackServlet < HttpServlet
15
+ java_import 'java.io.FileInputStream'
16
+
17
+ #
18
+ # Sets the Rack application that handles requests sent to this
19
+ # servlet container.
20
+ #
21
+ def rackup(app)
22
+ @app = app
23
+ end
24
+
25
+ #
26
+ # Takes an incoming request (as a Java Servlet) and dispatches it to
27
+ # the rack application setup via [rackup]. All this really involves
28
+ # is translating the various bits of the Servlet API into the Rack
29
+ # API on the way in, and translating the response back on the way
30
+ # out.
31
+ #
32
+ # Also, we implement a common extension to the Rack api for
33
+ # asynchronous request processing. We supply an 'async.callback'
34
+ # parameter in env to the Rack application. If we catch an
35
+ # :async symbol thrown by the app, we initiate a Jetty continuation.
36
+ #
37
+ # When 'async.callback' gets a response with empty headers and an
38
+ # empty body, we declare the async response finished.
39
+ #
40
+ def service(request, response)
41
+ # Turn the ServletRequest into a Rack env hash
42
+ env = servlet_to_rack(request)
43
+
44
+ # Add our own special bits to the rack environment so that
45
+ # Rack middleware can have access to the Java internals.
46
+ env['rack.java.servlet'] = true
47
+ env['rack.java.servlet.request'] = request
48
+ env['rack.java.servlet.response'] = response
49
+
50
+ rack_response = @app.call(env)
51
+
52
+ # For apps that don't throw :async.
53
+ unless(rack_response[0] == -1)
54
+ # Nope, nothing asynchronous here.
55
+ rack_to_servlet(rack_response, response)
56
+ return
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ #
63
+ # Turns a Servlet request into a Rack request hash.
64
+ #
65
+ def servlet_to_rack(request)
66
+ # The Rack request that we will pass on.
67
+ env = Hash.new
68
+
69
+ # Map Servlet bits to Rack bits.
70
+ env['REQUEST_METHOD'] = request.getMethod
71
+ env['QUERY_STRING'] = request.getQueryString.to_s
72
+ env['SERVER_NAME'] = request.getServerName
73
+ env['SERVER_PORT'] = request.getServerPort.to_s
74
+ env['rack.version'] = ::Rack::VERSION
75
+ env['rack.url_scheme'] = request.getScheme
76
+ env['HTTP_VERSION'] = request.getProtocol
77
+ env["SERVER_PROTOCOL"] = request.getProtocol
78
+ env['REMOTE_ADDR'] = request.getRemoteAddr
79
+ env['REMOTE_HOST'] = request.getRemoteHost
80
+
81
+ # request.getPathInfo seems to be blank, so we're using the URI.
82
+ env['REQUEST_PATH'] = request.getRequestURI
83
+ env['PATH_INFO'] = request.getRequestURI
84
+ env['SCRIPT_NAME'] = ""
85
+
86
+ # Rack says URI, but it hands off a URL.
87
+ env['REQUEST_URI'] = request.getRequestURL.toString
88
+
89
+ # Java chops off the query string, but a Rack application will
90
+ # expect it, so we'll add it back if present
91
+ env['REQUEST_URI'] << "?#{env['QUERY_STRING']}" \
92
+ if env['QUERY_STRING']
93
+
94
+ # JRuby is like the matrix, only there's no spoon or fork().
95
+ env['rack.multiprocess'] = false
96
+ env['rack.multithread'] = true
97
+ env['rack.run_once'] = false
98
+
99
+ # Populate the HTTP headers.
100
+ request.getHeaderNames.each do |header_name|
101
+ header = header_name.upcase.tr('-', '_')
102
+ env["HTTP_#{header}"] = request.getHeader(header_name)
103
+ end
104
+
105
+ # Rack Weirdness: HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH
106
+ # both need to have the HTTP_ part dropped.
107
+ env["CONTENT_TYPE"] = env.delete("HTTP_CONTENT_TYPE") \
108
+ if env["HTTP_CONTENT_TYPE"]
109
+ env["CONTENT_LENGTH"] = env.delete("HTTP_CONTENT_LENGTH") \
110
+ if env["HTTP_CONTENT_LENGTH"]
111
+
112
+ # The input stream is a wrapper around the Java InputStream.
113
+ env['rack.input'] = request.getInputStream.to_io
114
+
115
+ # The output stream defaults to stderr.
116
+ env['rack.errors'] ||= $stderr
117
+
118
+ # All done, hand back the Rack request.
119
+ return(env)
120
+ end
121
+
122
+ #
123
+ # Turns a Rack response into a Servlet response; can be called
124
+ # multiple times. Returns true if this is the full request (either
125
+ # a synchronous request or the last part of an async request),
126
+ # false otherwise.
127
+ #
128
+ # Note that keep-alive *only* happens if we get either a pathname
129
+ # (because we can find the length ourselves), or if we get a
130
+ # Content-Length header as part of the response. While we can
131
+ # readily buffer the response object to figure out how long it is,
132
+ # we have no guarantee that we aren't going to be buffering
133
+ # something *huge*.
134
+ #
135
+ # http://docstore.mik.ua/orelly/java-ent/servlet/ch05_03.htm
136
+ #
137
+ def rack_to_servlet(rack_response, response)
138
+ # Split apart the Rack response.
139
+ status, headers, body = rack_response
140
+
141
+ # We assume the request is finished if we got empty headers,
142
+ # an empty body, and we have a committed response.
143
+ finished = (headers.empty? and \
144
+ body.respond_to?(:empty?) and body.empty?)
145
+ return(true) if (finished and response.isCommitted)
146
+
147
+ # No need to send headers again if we've already shipped
148
+ # data out on an async request.
149
+ unless(response.isCommitted)
150
+ # Set the HTTP status code.
151
+ response.setStatus(status)
152
+
153
+ # Did we get a Content-Length header?
154
+ content_length = headers.delete('Content-Length')
155
+ response.setContentLength(content_length.to_i) \
156
+ if content_length
157
+
158
+ # Add all the result headers.
159
+ headers.each { |h, v| response.addHeader(h, v) }
160
+ end
161
+
162
+ # How else would we write output?
163
+ output = response.getOutputStream
164
+
165
+ # Turn the body into something nice and Java-y.
166
+ if(body.respond_to?(:to_path))
167
+ # We've been told to serve a file; use FileInputStream to
168
+ # stream the file directly to the servlet, because this
169
+ # is a lot faster than doing it with Ruby.
170
+ file = java.io.File.new(body.to_path)
171
+
172
+ # We set the content-length so we can use Keep-Alive,
173
+ # unless this is an async request.
174
+ response.setContentLength(file.length) \
175
+ unless content_length
176
+
177
+ # Stream the file directly.
178
+ buffer = Java::byte[4096].new
179
+ input_stream = FileInputStream.new(file)
180
+ while((count = input_stream.read(buffer)) != -1)
181
+ output.write(buffer, 0, count)
182
+ end
183
+ input_stream.close
184
+ else
185
+ body.each { |l| output.write(l.to_java_bytes) }
186
+ end
187
+
188
+ # Close the body if we're supposed to.
189
+ body.close if body.respond_to?(:close)
190
+
191
+ # All done.
192
+ output.flush
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,41 @@
1
+ module Trinidad
2
+ class Server
3
+ alias :old_load_config :load_config
4
+ def load_config(config)
5
+ old_load_config(config)
6
+ trap_signals
7
+ end
8
+
9
+ def trap_signals
10
+ # trap signals and stop tomcat properly to make sure resque is also stopped properly
11
+ trap('INT') { tomcat.stop }
12
+ trap('TERM') { tomcat.stop }
13
+ end
14
+ end
15
+
16
+ class WebApp
17
+ def app
18
+ @config[:app]
19
+ end
20
+ end
21
+
22
+ module Lifecycle
23
+ class Default < Base
24
+ def configure_rack_servlet(context)
25
+ wrapper = context.create_wrapper
26
+
27
+ rack = Trinidad::Rack::RackServlet.new
28
+ rack.rackup(@webapp.app)
29
+ wrapper.servlet = rack
30
+ wrapper.name = 'RackServlet'
31
+
32
+ context.add_child(wrapper)
33
+ context.add_servlet_mapping('/*', wrapper.name)
34
+ end
35
+ def configure_rack_listener(context)
36
+ # we need to disable this method to ensure trinidad doesn't load any
37
+ # application listener
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.rubygems_version = '1.3.5'
11
+
12
+ ## Leave these as is they will be modified for you by the rake gemspec task.
13
+ ## If your rubyforge_project name is different, then edit it and comment out
14
+ ## the sub! line in the Rakefile
15
+ s.name = 'trinidad_rack'
16
+ s.version = '0.1.0'
17
+ s.date = '2011-04-10'
18
+ s.rubyforge_project = 'trinidad_rack'
19
+
20
+ ## Make sure your summary is short. The description may be as long
21
+ ## as you like.
22
+ s.summary = "Simple Rack module and servlet for Trinidad"
23
+ s.description = "Simple Rack module and servlet for Trinidad based on Mizuno"
24
+
25
+ ## List the primary authors. If there are a bunch of authors, it's probably
26
+ ## better to set the email to an email list or something. If you don't have
27
+ ## a custom homepage, consider using your GitHub URL or the like.
28
+ s.authors = ["David Calavera"]
29
+ s.email = 'calavera@apache.org'
30
+ s.homepage = 'http://github.com/trinidad/trinidad_rack'
31
+
32
+ s.executables = %w[trinidad_rack]
33
+ s.default_executable = 'trinidad_rack'
34
+
35
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
36
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
37
+ s.require_paths = %w[lib]
38
+
39
+ ## Specify any RDoc options here. You'll want to add your README and
40
+ ## LICENSE files to the extra_rdoc_files list.
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.extra_rdoc_files = %w[README LICENSE]
43
+
44
+ ## List your runtime dependencies here. Runtime dependencies are those
45
+ ## that are needed for an end user to actually USE your code.
46
+ s.add_dependency('trinidad', '>=1.0.1')
47
+
48
+ ## List your development dependencies here. Development dependencies are
49
+ ## those that are only needed during development
50
+ s.add_development_dependency('rspec', '>=2.2')
51
+ s.add_development_dependency('mocha')
52
+
53
+ ## Leave this section as-is. It will be automatically generated from the
54
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
55
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
56
+ # = MANIFEST =
57
+ s.files = %w[
58
+ LICENSE
59
+ README
60
+ Rakefile
61
+ bin/trinidad_rack
62
+ lib/rack/handler/trinidad.rb
63
+ lib/trinidad_rack.rb
64
+ lib/trinidad_rack/rack_servlet.rb
65
+ lib/trinidad_rack/trinidad_ext.rb
66
+ trinidad_rack.gemspec
67
+ ]
68
+ # = MANIFEST =
69
+
70
+ ## Test files will be grabbed from the file list. Make sure the path glob
71
+ ## matches what you actually use.
72
+ s.test_files = s.files.select { |path| path =~ /^spec\/.*\_spec.rb/ }
73
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trinidad_rack
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - David Calavera
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-10 00:00:00 -07:00
14
+ default_executable: trinidad_rack
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: trinidad
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 1.0.1
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "2.2"
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: mocha
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ description: Simple Rack module and servlet for Trinidad based on Mizuno
50
+ email: calavera@apache.org
51
+ executables:
52
+ - trinidad_rack
53
+ extensions: []
54
+
55
+ extra_rdoc_files:
56
+ - README
57
+ - LICENSE
58
+ files:
59
+ - LICENSE
60
+ - README
61
+ - Rakefile
62
+ - bin/trinidad_rack
63
+ - lib/rack/handler/trinidad.rb
64
+ - lib/trinidad_rack.rb
65
+ - lib/trinidad_rack/rack_servlet.rb
66
+ - lib/trinidad_rack/trinidad_ext.rb
67
+ - trinidad_rack.gemspec
68
+ has_rdoc: true
69
+ homepage: http://github.com/trinidad/trinidad_rack
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options:
74
+ - --charset=UTF-8
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project: trinidad_rack
92
+ rubygems_version: 1.5.1
93
+ signing_key:
94
+ specification_version: 2
95
+ summary: Simple Rack module and servlet for Trinidad
96
+ test_files: []
97
+