jellyfish 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGES.md +14 -0
- data/README.md +194 -119
- data/bench/bench_builder.rb +44 -0
- data/config.ru +5 -39
- data/jellyfish.gemspec +12 -18
- data/lib/jellyfish.rb +1 -3
- data/lib/jellyfish/builder.rb +52 -0
- data/lib/jellyfish/test.rb +8 -8
- data/lib/jellyfish/urlmap.rb +26 -0
- data/lib/jellyfish/version.rb +1 -1
- data/task/gemgem.rb +11 -2
- data/test/rack/test_builder.rb +154 -0
- data/test/rack/test_urlmap.rb +180 -0
- data/test/sinatra/test_base.rb +1 -1
- data/test/sinatra/test_routing.rb +0 -60
- data/test/test_from_readme.rb +31 -20
- metadata +15 -17
- data/lib/jellyfish/multi_actions.rb +0 -31
- data/lib/jellyfish/sinatra.rb +0 -13
- data/lib/jellyfish/swagger.rb +0 -166
- data/public/css/screen.css +0 -1070
- data/public/index.html +0 -45
- data/public/js/shred.bundle.js +0 -2765
- data/public/js/shred/content.js +0 -193
- data/public/js/swagger-ui.js +0 -2116
- data/public/js/swagger.js +0 -1400
- data/test/sinatra/test_multi_actions.rb +0 -217
- data/test/test_swagger.rb +0 -131
data/config.ru
CHANGED
@@ -17,20 +17,6 @@ class Jelly
|
|
17
17
|
end
|
18
18
|
}
|
19
19
|
|
20
|
-
def self.info
|
21
|
-
{:title => 'Jellyfish Swagger UI',
|
22
|
-
:description =>
|
23
|
-
'This is a simple example for using Jellyfish and' \
|
24
|
-
' Swagger UI altogether. You could also try the' \
|
25
|
-
' <a href="http://swagger.wordnik.com/">official Swagger UI app</a>,' \
|
26
|
-
' and fill it with the swagger URL.'
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.swagger_apiVersion
|
31
|
-
'1.0.0'
|
32
|
-
end
|
33
|
-
|
34
20
|
handle Jellyfish::NotFound do |e|
|
35
21
|
status 404
|
36
22
|
body %Q|{"error":{"name":"NotFound"}}\n|
|
@@ -46,30 +32,15 @@ class Jelly
|
|
46
32
|
body render('error' => {'name' => name, 'message' => message})
|
47
33
|
end
|
48
34
|
|
49
|
-
get '/users'
|
50
|
-
:summary => 'List users',
|
51
|
-
:notes => 'Note that we do not really have users.' do
|
35
|
+
get '/users' do
|
52
36
|
render [:name => 'jellyfish']
|
53
37
|
end
|
54
38
|
|
55
|
-
post '/users'
|
56
|
-
:summary => 'Create a user',
|
57
|
-
:notes => 'Here we demonstrate how to write the swagger doc.',
|
58
|
-
:parameters => {:name => {:type => :string, :required => true,
|
59
|
-
:description => 'The name of the user'},
|
60
|
-
:sane => {:type => :boolean,
|
61
|
-
:description => 'If the user is sane'},
|
62
|
-
:type => {:type => :string,
|
63
|
-
:description => 'What kind of user',
|
64
|
-
:enum => %w[good neutral evil]}},
|
65
|
-
:responseMessages => [{:code => 400, :message => 'Invalid name'}] do
|
39
|
+
post '/users' do
|
66
40
|
render :message => "jellyfish #{request.params['name']} created."
|
67
41
|
end
|
68
42
|
|
69
|
-
put %r{\A/users/(?<id>\d+)}
|
70
|
-
:summary => 'Update a user',
|
71
|
-
:parameters => {:id => {:type => :integer,
|
72
|
-
:description => 'The id of the user'}} do |match|
|
43
|
+
put %r{\A/users/(?<id>\d+)} do |match|
|
73
44
|
render :message => "jellyfish ##{match[:id]} updated."
|
74
45
|
end
|
75
46
|
|
@@ -77,8 +48,7 @@ class Jelly
|
|
77
48
|
render :message => "jellyfish ##{match[:id]} deleted."
|
78
49
|
end
|
79
50
|
|
80
|
-
get %r{\A/posts/(?<year>\d+)-(?<month>\d+)/(?<name>\w+)}
|
81
|
-
:summary => 'Get a post' do |match|
|
51
|
+
get %r{\A/posts/(?<year>\d+)-(?<month>\d+)/(?<name>\w+)} do |match|
|
82
52
|
render Hash[match.names.zip(match.captures)]
|
83
53
|
end
|
84
54
|
|
@@ -87,16 +57,12 @@ class Jelly
|
|
87
57
|
end
|
88
58
|
end
|
89
59
|
|
90
|
-
App =
|
60
|
+
App = Jellyfish::Builder.app do
|
91
61
|
use Rack::CommonLogger
|
92
62
|
use Rack::Chunked
|
93
63
|
use Rack::ContentLength
|
94
64
|
use Rack::Deflater
|
95
65
|
|
96
|
-
map '/swagger' do
|
97
|
-
run Jellyfish::Swagger.new('', Jelly)
|
98
|
-
end
|
99
|
-
|
100
66
|
run Rack::Cascade.new([Rack::File.new('public/index.html'),
|
101
67
|
Rack::File.new('public'),
|
102
68
|
Jelly.new])
|
data/jellyfish.gemspec
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: jellyfish 1.0
|
2
|
+
# stub: jellyfish 1.1.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "jellyfish"
|
6
|
-
s.version = "1.0
|
6
|
+
s.version = "1.1.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib"]
|
10
10
|
s.authors = ["Lin Jen-Shin (godfat)"]
|
11
|
-
s.date = "
|
12
|
-
s.description = "Pico web framework for building API-centric web applications.\nFor Rack applications or Rack
|
11
|
+
s.date = "2015-09-25"
|
12
|
+
s.description = "Pico web framework for building API-centric web applications.\nFor Rack applications or Rack middleware. Around 250 lines of code.\n\nCheck [jellyfish-contrib][] for extra extensions.\n\n[jellyfish-contrib]: https://github.com/godfat/jellyfish-contrib"
|
13
13
|
s.email = ["godfat (XD) godfat.org"]
|
14
14
|
s.files = [
|
15
15
|
".gitignore",
|
@@ -21,59 +21,53 @@ Gem::Specification.new do |s|
|
|
21
21
|
"README.md",
|
22
22
|
"Rakefile",
|
23
23
|
"TODO.md",
|
24
|
+
"bench/bench_builder.rb",
|
24
25
|
"config.ru",
|
25
26
|
"jellyfish.gemspec",
|
26
27
|
"jellyfish.png",
|
27
28
|
"lib/jellyfish.rb",
|
29
|
+
"lib/jellyfish/builder.rb",
|
28
30
|
"lib/jellyfish/chunked_body.rb",
|
29
31
|
"lib/jellyfish/json.rb",
|
30
|
-
"lib/jellyfish/multi_actions.rb",
|
31
32
|
"lib/jellyfish/newrelic.rb",
|
32
33
|
"lib/jellyfish/normalized_params.rb",
|
33
34
|
"lib/jellyfish/normalized_path.rb",
|
34
35
|
"lib/jellyfish/public/302.html",
|
35
36
|
"lib/jellyfish/public/404.html",
|
36
37
|
"lib/jellyfish/public/500.html",
|
37
|
-
"lib/jellyfish/sinatra.rb",
|
38
|
-
"lib/jellyfish/swagger.rb",
|
39
38
|
"lib/jellyfish/test.rb",
|
39
|
+
"lib/jellyfish/urlmap.rb",
|
40
40
|
"lib/jellyfish/version.rb",
|
41
41
|
"lib/jellyfish/websocket.rb",
|
42
|
-
"public/css/screen.css",
|
43
|
-
"public/index.html",
|
44
|
-
"public/js/shred.bundle.js",
|
45
|
-
"public/js/shred/content.js",
|
46
|
-
"public/js/swagger-ui.js",
|
47
|
-
"public/js/swagger.js",
|
48
42
|
"task/README.md",
|
49
43
|
"task/gemgem.rb",
|
44
|
+
"test/rack/test_builder.rb",
|
45
|
+
"test/rack/test_urlmap.rb",
|
50
46
|
"test/sinatra/test_base.rb",
|
51
47
|
"test/sinatra/test_chunked_body.rb",
|
52
48
|
"test/sinatra/test_error.rb",
|
53
|
-
"test/sinatra/test_multi_actions.rb",
|
54
49
|
"test/sinatra/test_routing.rb",
|
55
50
|
"test/test_from_readme.rb",
|
56
51
|
"test/test_inheritance.rb",
|
57
52
|
"test/test_log.rb",
|
58
53
|
"test/test_misc.rb",
|
59
|
-
"test/test_swagger.rb",
|
60
54
|
"test/test_threads.rb",
|
61
55
|
"test/test_websocket.rb"]
|
62
56
|
s.homepage = "https://github.com/godfat/jellyfish"
|
63
57
|
s.licenses = ["Apache License 2.0"]
|
64
|
-
s.rubygems_version = "2.4.
|
58
|
+
s.rubygems_version = "2.4.8"
|
65
59
|
s.summary = "Pico web framework for building API-centric web applications."
|
66
60
|
s.test_files = [
|
61
|
+
"test/rack/test_builder.rb",
|
62
|
+
"test/rack/test_urlmap.rb",
|
67
63
|
"test/sinatra/test_base.rb",
|
68
64
|
"test/sinatra/test_chunked_body.rb",
|
69
65
|
"test/sinatra/test_error.rb",
|
70
|
-
"test/sinatra/test_multi_actions.rb",
|
71
66
|
"test/sinatra/test_routing.rb",
|
72
67
|
"test/test_from_readme.rb",
|
73
68
|
"test/test_inheritance.rb",
|
74
69
|
"test/test_log.rb",
|
75
70
|
"test/test_misc.rb",
|
76
|
-
"test/test_swagger.rb",
|
77
71
|
"test/test_threads.rb",
|
78
72
|
"test/test_websocket.rb"]
|
79
73
|
end
|
data/lib/jellyfish.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
|
2
2
|
module Jellyfish
|
3
3
|
autoload :VERSION , 'jellyfish/version'
|
4
|
-
autoload :Sinatra , 'jellyfish/sinatra'
|
5
|
-
autoload :Swagger , 'jellyfish/swagger'
|
6
4
|
autoload :NewRelic, 'jellyfish/newrelic'
|
7
5
|
|
8
|
-
autoload :MultiActions , 'jellyfish/multi_actions'
|
9
6
|
autoload :NormalizedParams, 'jellyfish/normalized_params'
|
10
7
|
autoload :NormalizedPath , 'jellyfish/normalized_path'
|
11
8
|
|
9
|
+
autoload :Builder , 'jellyfish/builder'
|
12
10
|
autoload :ChunkedBody, 'jellyfish/chunked_body'
|
13
11
|
autoload :WebSocket , 'jellyfish/websocket'
|
14
12
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
require 'jellyfish/urlmap'
|
3
|
+
|
4
|
+
module Jellyfish
|
5
|
+
class Builder
|
6
|
+
def self.app app=nil, &block
|
7
|
+
new(app, &block).to_app
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize app=nil, &block
|
11
|
+
@use, @map, @run, @warmup = [], nil, app, nil
|
12
|
+
instance_eval(&block) if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
def use middleware, *args, &block
|
16
|
+
if @map
|
17
|
+
current_map, @map = @map, nil
|
18
|
+
@use.unshift(lambda{ |app| generate_map(current_map, app) })
|
19
|
+
end
|
20
|
+
@use.unshift(lambda{ |app| middleware.new(app, *args, &block) })
|
21
|
+
end
|
22
|
+
|
23
|
+
def run app
|
24
|
+
@run = app
|
25
|
+
end
|
26
|
+
|
27
|
+
def warmup lam=nil, &block
|
28
|
+
@warmup = lam || block
|
29
|
+
end
|
30
|
+
|
31
|
+
def map path, &block
|
32
|
+
(@map ||= {})[path] = block
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_app
|
36
|
+
run = if @map then generate_map(@map, @run) else @run end
|
37
|
+
fail 'missing run or map statement' unless run
|
38
|
+
app = @use.inject(run){ |a, m| m.call(a) }
|
39
|
+
@warmup.call(app) if @warmup
|
40
|
+
app
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def generate_map current_map, app
|
45
|
+
mapped = if app then {'' => app} else {} end
|
46
|
+
current_map.each do |path, block|
|
47
|
+
mapped[path.chomp('/')] = self.class.app(app, &block)
|
48
|
+
end
|
49
|
+
URLMap.new(mapped)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/jellyfish/test.rb
CHANGED
@@ -9,15 +9,15 @@ Pork::Executor.__send__(:include, Muack::API)
|
|
9
9
|
copy :jellyfish do
|
10
10
|
module_eval(%w[options get head post put delete patch].map{ |method|
|
11
11
|
<<-RUBY
|
12
|
-
def #{method} path='/',
|
12
|
+
def #{method} path='/', a=app, env={}
|
13
13
|
File.open(File::NULL) do |input|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
a.call({'PATH_INFO' => path ,
|
15
|
+
'REQUEST_METHOD' => '#{method}'.upcase,
|
16
|
+
'SCRIPT_NAME' => '' ,
|
17
|
+
'rack.input' => input ,
|
18
|
+
'rack.url_scheme'=> 'https' ,
|
19
|
+
'SERVER_NAME' => 'localhost' ,
|
20
|
+
'SERVER_PORT' => '8080'}.merge(env))
|
21
21
|
end
|
22
22
|
end
|
23
23
|
RUBY
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module Jellyfish
|
3
|
+
class URLMap
|
4
|
+
def initialize mapped
|
5
|
+
string = mapped.keys.sort_by{ |k| -k.size }.
|
6
|
+
map{ |k| Regexp.escape(k).gsub('/', '/+') }.
|
7
|
+
join('|')
|
8
|
+
|
9
|
+
@mapped = mapped
|
10
|
+
@routes = Regexp.new("\\A(?:#{string})(?:/|\\z)", 'i', 'n')
|
11
|
+
end
|
12
|
+
|
13
|
+
def call env
|
14
|
+
path_info = env['PATH_INFO']
|
15
|
+
matched = @routes.match(path_info).to_s.chomp('/')
|
16
|
+
squeezed = matched.squeeze('/')
|
17
|
+
|
18
|
+
if app = @mapped[squeezed]
|
19
|
+
app.call(env.merge('PATH_INFO' => path_info[matched.size..-1],
|
20
|
+
'SCRIPT_NAME' => env['SCRIPT_NAME'] + squeezed))
|
21
|
+
else
|
22
|
+
[404, {}, []]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/jellyfish/version.rb
CHANGED
data/task/gemgem.rb
CHANGED
@@ -79,6 +79,11 @@ module Gemgem
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def gem_check
|
82
|
+
unless git('status', '--porcelain').empty?
|
83
|
+
puts("\e[35mWorking copy is not clean.\e[0m")
|
84
|
+
exit(3)
|
85
|
+
end
|
86
|
+
|
82
87
|
ver = spec.version.to_s
|
83
88
|
|
84
89
|
if ENV['VERSION'].nil?
|
@@ -100,8 +105,12 @@ module Gemgem
|
|
100
105
|
if ENV['COV'] || ENV['CI']
|
101
106
|
require 'simplecov'
|
102
107
|
if ENV['CI']
|
103
|
-
|
104
|
-
|
108
|
+
begin
|
109
|
+
require 'coveralls'
|
110
|
+
SimpleCov.formatter = Coveralls::SimpleCov::Formatter
|
111
|
+
rescue LoadError => e
|
112
|
+
puts "Cannot load coveralls, skip: #{e}"
|
113
|
+
end
|
105
114
|
end
|
106
115
|
SimpleCov.start do
|
107
116
|
add_filter('test/')
|
@@ -0,0 +1,154 @@
|
|
1
|
+
|
2
|
+
require 'jellyfish/test'
|
3
|
+
|
4
|
+
require 'rack/lint'
|
5
|
+
require 'rack/mock'
|
6
|
+
require 'rack/showexceptions'
|
7
|
+
require 'rack/urlmap'
|
8
|
+
|
9
|
+
describe Jellyfish::Builder do
|
10
|
+
class NothingMiddleware
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
def call(env)
|
15
|
+
@@env = env
|
16
|
+
response = @app.call(env)
|
17
|
+
response
|
18
|
+
end
|
19
|
+
def self.env
|
20
|
+
@@env
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def builder_to_app(&block)
|
25
|
+
Rack::Lint.new Jellyfish::Builder.app(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
would "supports mapping" do
|
29
|
+
app = builder_to_app do
|
30
|
+
map '/' do |outer_env|
|
31
|
+
run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
|
32
|
+
end
|
33
|
+
map '/sub' do
|
34
|
+
run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
Rack::MockRequest.new(app).get("/").body.to_s.should.eq 'root'
|
38
|
+
Rack::MockRequest.new(app).get("/sub").body.to_s.should.eq 'sub'
|
39
|
+
end
|
40
|
+
|
41
|
+
would "chains apps by default" do
|
42
|
+
app = builder_to_app do
|
43
|
+
use Rack::ShowExceptions
|
44
|
+
run lambda { |env| raise "bzzzt" }
|
45
|
+
end
|
46
|
+
|
47
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
48
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
49
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
50
|
+
end
|
51
|
+
|
52
|
+
would "supports blocks on use" do
|
53
|
+
app = builder_to_app do
|
54
|
+
use Rack::ShowExceptions
|
55
|
+
use Rack::Auth::Basic do |username, password|
|
56
|
+
'secret' == password
|
57
|
+
end
|
58
|
+
|
59
|
+
run lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hi Boss']] }
|
60
|
+
end
|
61
|
+
|
62
|
+
response = Rack::MockRequest.new(app).get("/")
|
63
|
+
response.should.client_error?
|
64
|
+
response.status.should.eq 401
|
65
|
+
|
66
|
+
# with auth...
|
67
|
+
response = Rack::MockRequest.new(app).get("/",
|
68
|
+
'HTTP_AUTHORIZATION' => 'Basic ' + ["joe:secret"].pack("m*"))
|
69
|
+
response.status.should.eq 200
|
70
|
+
response.body.to_s.should.eq 'Hi Boss'
|
71
|
+
end
|
72
|
+
|
73
|
+
would "has explicit #to_app" do
|
74
|
+
app = builder_to_app do
|
75
|
+
use Rack::ShowExceptions
|
76
|
+
run lambda { |env| raise "bzzzt" }
|
77
|
+
end
|
78
|
+
|
79
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
80
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
81
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
82
|
+
end
|
83
|
+
|
84
|
+
would "can mix map and run for endpoints" do
|
85
|
+
app = builder_to_app do
|
86
|
+
map '/sub' do
|
87
|
+
run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
|
88
|
+
end
|
89
|
+
run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
|
90
|
+
end
|
91
|
+
|
92
|
+
Rack::MockRequest.new(app).get("/").body.to_s.should.eq 'root'
|
93
|
+
Rack::MockRequest.new(app).get("/sub").body.to_s.should.eq 'sub'
|
94
|
+
end
|
95
|
+
|
96
|
+
would "accepts middleware-only map blocks" do
|
97
|
+
app = builder_to_app do
|
98
|
+
map('/foo') { use Rack::ShowExceptions }
|
99
|
+
run lambda { |env| raise "bzzzt" }
|
100
|
+
end
|
101
|
+
|
102
|
+
proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError)
|
103
|
+
Rack::MockRequest.new(app).get("/foo").should.server_error?
|
104
|
+
end
|
105
|
+
|
106
|
+
would "yields the generated app to a block for warmup" do
|
107
|
+
warmed_up_app = nil
|
108
|
+
|
109
|
+
app = Rack::Builder.new do
|
110
|
+
warmup { |a| warmed_up_app = a }
|
111
|
+
run lambda { |env| [200, {}, []] }
|
112
|
+
end.to_app
|
113
|
+
|
114
|
+
warmed_up_app.should.eq app
|
115
|
+
end
|
116
|
+
|
117
|
+
would "initialize apps once" do
|
118
|
+
app = builder_to_app do
|
119
|
+
class AppClass
|
120
|
+
def initialize
|
121
|
+
@called = 0
|
122
|
+
end
|
123
|
+
def call(env)
|
124
|
+
raise "bzzzt" if @called > 0
|
125
|
+
@called += 1
|
126
|
+
[200, {'Content-Type' => 'text/plain'}, ['OK']]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
use Rack::ShowExceptions
|
131
|
+
run AppClass.new
|
132
|
+
end
|
133
|
+
|
134
|
+
Rack::MockRequest.new(app).get("/").status.should.eq 200
|
135
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
136
|
+
end
|
137
|
+
|
138
|
+
would "allows use after run" do
|
139
|
+
app = builder_to_app do
|
140
|
+
run lambda { |env| raise "bzzzt" }
|
141
|
+
use Rack::ShowExceptions
|
142
|
+
end
|
143
|
+
|
144
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
145
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
146
|
+
Rack::MockRequest.new(app).get("/").should.server_error?
|
147
|
+
end
|
148
|
+
|
149
|
+
would 'complains about a missing run' do
|
150
|
+
proc do
|
151
|
+
Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions }
|
152
|
+
end.should.raise(RuntimeError)
|
153
|
+
end
|
154
|
+
end
|