otto 1.0.1 → 1.1.0.pre.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +18 -0
- data/.rubocop.yml +71 -0
- data/CHANGES.txt +35 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +66 -0
- data/VERSION.yml +3 -3
- data/lib/otto/helpers/request.rb +61 -0
- data/lib/otto/helpers/response.rb +22 -0
- data/lib/otto/route.rb +97 -0
- data/lib/otto/static.rb +36 -0
- data/lib/otto/version.rb +22 -0
- data/lib/otto.rb +37 -236
- data/otto.gemspec +17 -27
- metadata +16 -4
- data/Rakefile +0 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28a8992b56a1b88f3202f7e3561bda9ba0f3816c26fbdd3775b3cdded2d8382c
|
4
|
+
data.tar.gz: 781639dfd7a1fa9a3d7e7e4693eb74a485c6cdddbdf8e8b7fe64596d1322701b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d25fed4875a89b4ea4f2d26552bdea920b2324401bd354fabaa4b62d640e6a2da6eb38575499018020887eeee5db68b67995f7670b7c69d4eb1ca9f0e6cd48ff
|
7
|
+
data.tar.gz: d07d2a99df509515630600d3c22f508e0f1018ce641aea1f52bd917b9ae5868bd63ab0466d5ee0be326942becc29ac1b1f29c15f1f202310858f7290f20384e4
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
##
|
2
|
+
# This is the RuboCop configuration file.
|
3
|
+
# It contains the rules and settings for the RuboCop linter.
|
4
|
+
#
|
5
|
+
# Enable/disable the cops individually. For more information,
|
6
|
+
# refer to the RuboCop documentation:
|
7
|
+
# https://docs.rubocop.org/rubocop/cops.html
|
8
|
+
#
|
9
|
+
# Running `rubocop --regenerate-todo` will update the todo file
|
10
|
+
# with the latest state of the onion (using the same options
|
11
|
+
# as those documented at the top of the todo file). This is
|
12
|
+
# useful for a gradual migration of the codebase.
|
13
|
+
#
|
14
|
+
inherit_from: .rubocop_todo.yml
|
15
|
+
|
16
|
+
require:
|
17
|
+
- rubocop-performance
|
18
|
+
- rubocop-thread_safety
|
19
|
+
|
20
|
+
AllCops:
|
21
|
+
NewCops: enable
|
22
|
+
UseCache: true
|
23
|
+
MaxFilesInCache: 100
|
24
|
+
TargetRubyVersion: 3.0
|
25
|
+
Exclude:
|
26
|
+
- 'migrate/**/*.rb'
|
27
|
+
- 'migrate/*.rb'
|
28
|
+
- 'try/**/*'
|
29
|
+
- 'try/*.rb'
|
30
|
+
- 'vendor/**/*'
|
31
|
+
|
32
|
+
Gemspec/DeprecatedAttributeAssignment:
|
33
|
+
Enabled: true
|
34
|
+
|
35
|
+
Gemspec/DevelopmentDependencies:
|
36
|
+
Enabled: true
|
37
|
+
|
38
|
+
Layout/HashAlignment:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Lint/Void:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
Metrics/AbcSize:
|
45
|
+
Enabled: false
|
46
|
+
Max: 20
|
47
|
+
|
48
|
+
Metrics/ClassLength:
|
49
|
+
Enabled: true
|
50
|
+
Max: 200
|
51
|
+
|
52
|
+
Metrics/CyclomaticComplexity:
|
53
|
+
Enabled: false
|
54
|
+
|
55
|
+
Metrics/MethodLength:
|
56
|
+
Enabled: true
|
57
|
+
Max: 40
|
58
|
+
CountAsOne: ['method_call']
|
59
|
+
|
60
|
+
Metrics/ModuleLength:
|
61
|
+
Enabled: true
|
62
|
+
Max: 250
|
63
|
+
CountAsOne: ['method_call']
|
64
|
+
|
65
|
+
Performance/Size:
|
66
|
+
Enabled: true
|
67
|
+
Exclude:
|
68
|
+
# - lib/example.rb
|
69
|
+
|
70
|
+
Style/NegatedIfElseCondition:
|
71
|
+
Enabled: true
|
data/CHANGES.txt
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
OTTO, CHANGES
|
2
|
+
|
3
|
+
#### 0.4.1 (2015-04-07) ###############################
|
4
|
+
|
5
|
+
* FIXED: Resolved error when ACCEPT-LANGUAGES header doesn't exist
|
6
|
+
|
7
|
+
#### 0.4.0 (2015-04-06) ###############################
|
8
|
+
|
9
|
+
* ADDED: Locale support via env['rack.locale']
|
10
|
+
|
11
|
+
#### 0.3.2 (2013-01-27) ###############################
|
12
|
+
|
13
|
+
* CHANGE: send_cookie doesn't set domain
|
14
|
+
|
15
|
+
#### 0.3.1 (2012-12-17) ###############################
|
16
|
+
|
17
|
+
* ADDED: Otto.debug (set w/ Otto.debug= or env variable OTTO_DEBUG)
|
18
|
+
* ADDED: RequestHelpers#ajax?
|
19
|
+
* CHANGE: Added internal subnets to RequestHelpers#local?
|
20
|
+
|
21
|
+
|
22
|
+
#### 0.3.0 (2011-12-17) ###############################
|
23
|
+
|
24
|
+
* ADDED: Example app, better docs in readme
|
25
|
+
* CHANGE: No default value for user agent
|
26
|
+
|
27
|
+
#### 0.2.1 (2011-07-07) ###############################
|
28
|
+
|
29
|
+
* ADDED: Otto#add_static_path
|
30
|
+
|
31
|
+
#### 0.2.0 (2011-07-06) ###############################
|
32
|
+
|
33
|
+
Initial public release
|
34
|
+
|
35
|
+
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.8.6)
|
5
|
+
public_suffix (>= 2.0.2, < 6.0)
|
6
|
+
ast (2.4.2)
|
7
|
+
byebug (11.1.3)
|
8
|
+
coderay (1.1.3)
|
9
|
+
drydock (0.6.9)
|
10
|
+
json (2.7.2)
|
11
|
+
language_server-protocol (3.17.0.3)
|
12
|
+
method_source (1.0.0)
|
13
|
+
parallel (1.24.0)
|
14
|
+
parser (3.3.0.5)
|
15
|
+
ast (~> 2.4.1)
|
16
|
+
racc
|
17
|
+
pry (0.14.2)
|
18
|
+
coderay (~> 1.1)
|
19
|
+
method_source (~> 1.0)
|
20
|
+
pry-byebug (3.10.1)
|
21
|
+
byebug (~> 11.0)
|
22
|
+
pry (>= 0.13, < 0.15)
|
23
|
+
public_suffix (5.0.5)
|
24
|
+
racc (1.7.3)
|
25
|
+
rack (3.0.10)
|
26
|
+
rainbow (3.1.1)
|
27
|
+
regexp_parser (2.9.0)
|
28
|
+
rexml (3.2.6)
|
29
|
+
rubocop (1.62.1)
|
30
|
+
json (~> 2.3)
|
31
|
+
language_server-protocol (>= 3.17.0)
|
32
|
+
parallel (~> 1.10)
|
33
|
+
parser (>= 3.3.0.2)
|
34
|
+
rainbow (>= 2.2.2, < 4.0)
|
35
|
+
regexp_parser (>= 1.8, < 3.0)
|
36
|
+
rexml (>= 3.2.5, < 4.0)
|
37
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
38
|
+
ruby-progressbar (~> 1.7)
|
39
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
40
|
+
rubocop-ast (1.31.2)
|
41
|
+
parser (>= 3.3.0.4)
|
42
|
+
ruby-progressbar (1.13.0)
|
43
|
+
storable (0.10.0)
|
44
|
+
sysinfo (0.10.0)
|
45
|
+
drydock (< 1.0)
|
46
|
+
storable (~> 0.10)
|
47
|
+
tryouts (2.2.0)
|
48
|
+
sysinfo (~> 0.10)
|
49
|
+
unicode-display_width (2.5.0)
|
50
|
+
|
51
|
+
PLATFORMS
|
52
|
+
arm64-darwin-22
|
53
|
+
ruby
|
54
|
+
|
55
|
+
DEPENDENCIES
|
56
|
+
addressable
|
57
|
+
pry-byebug
|
58
|
+
rack
|
59
|
+
rubocop
|
60
|
+
tryouts
|
61
|
+
|
62
|
+
RUBY VERSION
|
63
|
+
ruby 3.2.0p0
|
64
|
+
|
65
|
+
BUNDLED WITH
|
66
|
+
2.5.7
|
data/VERSION.yml
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
:MAJOR:
|
2
|
-
:MINOR:
|
3
|
-
:PATCH:
|
1
|
+
:MAJOR: 1
|
2
|
+
:MINOR: 1
|
3
|
+
:PATCH: 0
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Otto
|
2
|
+
module RequestHelpers
|
3
|
+
def user_agent
|
4
|
+
env['HTTP_USER_AGENT']
|
5
|
+
end
|
6
|
+
def client_ipaddress
|
7
|
+
env['HTTP_X_FORWARDED_FOR'].to_s.split(/,\s*/).first ||
|
8
|
+
env['HTTP_X_REAL_IP'] || env['REMOTE_ADDR']
|
9
|
+
end
|
10
|
+
def request_method
|
11
|
+
env['REQUEST_METHOD']
|
12
|
+
end
|
13
|
+
def current_server
|
14
|
+
[current_server_name, env['SERVER_PORT']].join(':')
|
15
|
+
end
|
16
|
+
def current_server_name
|
17
|
+
env['SERVER_NAME']
|
18
|
+
end
|
19
|
+
def http_host
|
20
|
+
env['HTTP_HOST']
|
21
|
+
end
|
22
|
+
def request_path
|
23
|
+
env['REQUEST_PATH']
|
24
|
+
end
|
25
|
+
def request_uri
|
26
|
+
env['REQUEST_URI']
|
27
|
+
end
|
28
|
+
def root_path
|
29
|
+
env['SCRIPT_NAME']
|
30
|
+
end
|
31
|
+
def absolute_suri host=current_server_name
|
32
|
+
prefix = local? ? 'http://' : 'https://'
|
33
|
+
[prefix, host, request_path].join
|
34
|
+
end
|
35
|
+
def local?
|
36
|
+
Otto.env?(:dev, :development) &&
|
37
|
+
(client_ipaddress == '127.0.0.1' ||
|
38
|
+
!client_ipaddress.match(/^10\.0\./).nil? ||
|
39
|
+
!client_ipaddress.match(/^192\.168\./).nil?)
|
40
|
+
end
|
41
|
+
def secure?
|
42
|
+
# X-Scheme is set by nginx
|
43
|
+
# X-FORWARDED-PROTO is set by elastic load balancer
|
44
|
+
(env['HTTP_X_FORWARDED_PROTO'] == 'https' || env['HTTP_X_SCHEME'] == "https")
|
45
|
+
end
|
46
|
+
# See: http://stackoverflow.com/questions/10013812/how-to-prevent-jquery-ajax-from-following-a-redirect-after-a-post
|
47
|
+
def ajax?
|
48
|
+
env['HTTP_X_REQUESTED_WITH'].to_s.downcase == 'xmlhttprequest'
|
49
|
+
end
|
50
|
+
def cookie name
|
51
|
+
cookies[name.to_s]
|
52
|
+
end
|
53
|
+
def cookie? name
|
54
|
+
!cookie(name).to_s.empty?
|
55
|
+
end
|
56
|
+
def current_absolute_uri
|
57
|
+
prefix = secure? && !local? ? 'https://' : 'http://'
|
58
|
+
[prefix, http_host, request_path].join
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Otto
|
2
|
+
module ResponseHelpers
|
3
|
+
attr_accessor :request
|
4
|
+
def send_secure_cookie name, value, ttl
|
5
|
+
send_cookie name, value, ttl, true
|
6
|
+
end
|
7
|
+
def send_cookie name, value, ttl, secure=true
|
8
|
+
secure = false if request.local?
|
9
|
+
opts = {
|
10
|
+
:value => value,
|
11
|
+
:path => '/',
|
12
|
+
:expires => (Time.now.utc + ttl + 10),
|
13
|
+
:secure => secure
|
14
|
+
}
|
15
|
+
#opts[:domain] = request.env['SERVER_NAME']
|
16
|
+
set_cookie name, opts
|
17
|
+
end
|
18
|
+
def delete_cookie name
|
19
|
+
send_cookie name, nil, -1.day
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/otto/route.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
|
2
|
+
class Otto
|
3
|
+
# Otto::Route
|
4
|
+
#
|
5
|
+
# A Route is a definition of a URL path and the method to call when
|
6
|
+
# that path is requested. Each route represents a single line in a
|
7
|
+
# routes file.
|
8
|
+
#
|
9
|
+
# e.g.
|
10
|
+
#
|
11
|
+
# GET /uri/path YourApp.method
|
12
|
+
# GET /uri/path2 YourApp#method
|
13
|
+
#
|
14
|
+
#
|
15
|
+
class Route
|
16
|
+
module ClassMethods
|
17
|
+
attr_accessor :otto
|
18
|
+
end
|
19
|
+
attr_reader :verb, :path, :pattern, :method, :klass, :name, :definition, :keys, :kind
|
20
|
+
attr_accessor :otto
|
21
|
+
def initialize verb, path, definition
|
22
|
+
@verb, @path, @definition = verb.to_s.upcase.to_sym, path, definition
|
23
|
+
@pattern, @keys = *compile(@path)
|
24
|
+
if !@definition.index('.').nil?
|
25
|
+
@klass, @name = @definition.split('.')
|
26
|
+
@kind = :class
|
27
|
+
elsif !@definition.index('#').nil?
|
28
|
+
@klass, @name = @definition.split('#')
|
29
|
+
@kind = :instance
|
30
|
+
else
|
31
|
+
raise ArgumentError, "Bad definition: #{@definition}"
|
32
|
+
end
|
33
|
+
@klass = eval(@klass)
|
34
|
+
#@method = eval(@klass).method(@name)
|
35
|
+
end
|
36
|
+
def pattern_regexp
|
37
|
+
Regexp.new(@path.gsub(/\/\*/, '/.+'))
|
38
|
+
end
|
39
|
+
def call(env, extra_params={})
|
40
|
+
extra_params ||= {}
|
41
|
+
req = Rack::Request.new(env)
|
42
|
+
res = Rack::Response.new
|
43
|
+
req.extend Otto::RequestHelpers
|
44
|
+
res.extend Otto::ResponseHelpers
|
45
|
+
res.request = req
|
46
|
+
req.params.merge! extra_params
|
47
|
+
req.params.replace Otto::Static.indifferent_params(req.params)
|
48
|
+
klass.extend Otto::Route::ClassMethods
|
49
|
+
klass.otto = self.otto
|
50
|
+
Otto.logger.debug "Route class: #{klass}"
|
51
|
+
case kind
|
52
|
+
when :instance
|
53
|
+
inst = klass.new req, res
|
54
|
+
inst.send(name)
|
55
|
+
when :class
|
56
|
+
klass.send(name, req, res)
|
57
|
+
else
|
58
|
+
raise RuntimeError, "Unsupported kind for #{@definition}: #{kind}"
|
59
|
+
end
|
60
|
+
res.body = [res.body] unless res.body.respond_to?(:each)
|
61
|
+
res.finish
|
62
|
+
end
|
63
|
+
# Brazenly borrowed from Sinatra::Base:
|
64
|
+
# https://github.com/sinatra/sinatra/blob/v1.2.6/lib/sinatra/base.rb#L1156
|
65
|
+
def compile(path)
|
66
|
+
keys = []
|
67
|
+
if path.respond_to? :to_str
|
68
|
+
special_chars = %w{. + ( ) $}
|
69
|
+
pattern =
|
70
|
+
path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
|
71
|
+
case match
|
72
|
+
when "*"
|
73
|
+
keys << 'splat'
|
74
|
+
"(.*?)"
|
75
|
+
when *special_chars
|
76
|
+
Regexp.escape(match)
|
77
|
+
else
|
78
|
+
keys << $2[1..-1]
|
79
|
+
"([^/?#]+)"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
# Wrap the regex in parens so the regex works properly.
|
83
|
+
# They can fail when there's an | for example (matching only the last one).
|
84
|
+
# Note: this means we also need to remove the first matched value.
|
85
|
+
[/\A(#{pattern})\z/, keys]
|
86
|
+
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
87
|
+
[path, path.keys]
|
88
|
+
elsif path.respond_to?(:names) && path.respond_to?(:match)
|
89
|
+
[path, path.names]
|
90
|
+
elsif path.respond_to? :match
|
91
|
+
[path, keys]
|
92
|
+
else
|
93
|
+
raise TypeError, path
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/otto/static.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
class Otto
|
3
|
+
|
4
|
+
module Static
|
5
|
+
extend self
|
6
|
+
def server_error
|
7
|
+
[500, {'Content-Type'=>'text/plain'}, ["Server error"]]
|
8
|
+
end
|
9
|
+
def not_found
|
10
|
+
[404, {'Content-Type'=>'text/plain'}, ["Not Found"]]
|
11
|
+
end
|
12
|
+
# Enable string or symbol key access to the nested params hash.
|
13
|
+
def indifferent_params(params)
|
14
|
+
if params.is_a?(Hash)
|
15
|
+
params = indifferent_hash.merge(params)
|
16
|
+
params.each do |key, value|
|
17
|
+
next unless value.is_a?(Hash) || value.is_a?(Array)
|
18
|
+
params[key] = indifferent_params(value)
|
19
|
+
end
|
20
|
+
elsif params.is_a?(Array)
|
21
|
+
params.collect! do |value|
|
22
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
23
|
+
indifferent_params(value)
|
24
|
+
else
|
25
|
+
value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
# Creates a Hash with indifferent access.
|
31
|
+
def indifferent_hash
|
32
|
+
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/otto/version.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#
|
2
|
+
|
3
|
+
class Otto
|
4
|
+
# Otto::VERSION
|
5
|
+
#
|
6
|
+
module VERSION
|
7
|
+
def self.to_s
|
8
|
+
load_config
|
9
|
+
version = self.version
|
10
|
+
[version[:MAJOR], version[:MINOR], version[:PATCH]].join('.')
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.inspect
|
14
|
+
to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.load_config
|
18
|
+
require 'yaml'
|
19
|
+
self.version ||= YAML.load_file(File.join(LIB_HOME, '..', 'VERSION.yml'))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/otto.rb
CHANGED
@@ -1,45 +1,44 @@
|
|
1
|
+
require 'logger'
|
1
2
|
|
2
3
|
require 'rack/request'
|
3
4
|
require 'rack/response'
|
4
5
|
require 'rack/utils'
|
5
6
|
require 'addressable/uri'
|
6
7
|
|
8
|
+
require_relative 'otto/route'
|
9
|
+
require_relative 'otto/static'
|
10
|
+
require_relative 'otto/helpers/request'
|
11
|
+
require_relative 'otto/helpers/response'
|
12
|
+
require_relative 'otto/version'
|
13
|
+
|
14
|
+
# Otto is a simple Rack router that allows you to define routes in a file
|
15
|
+
#
|
16
|
+
#
|
7
17
|
class Otto
|
8
|
-
|
9
|
-
LIB_HOME = File.expand_path File.dirname(__FILE__) unless defined?(Otto::LIB_HOME)
|
18
|
+
LIB_HOME = __dir__ unless defined?(Otto::LIB_HOME)
|
10
19
|
|
11
|
-
|
12
|
-
|
13
|
-
load_config
|
14
|
-
[@version[:MAJOR], @version[:MINOR], @version[:PATCH]].join('.')
|
15
|
-
end
|
16
|
-
def self.inspect
|
17
|
-
to_s
|
18
|
-
end
|
19
|
-
def self.load_config
|
20
|
-
require 'yaml'
|
21
|
-
@version ||= YAML.load_file(File.join(LIB_HOME, '..', 'VERSION.yml'))
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
20
|
+
@debug = ENV['OTTO_DEBUG'] == 'true'
|
21
|
+
@logger = Logger.new($stdout, Logger::INFO)
|
25
22
|
|
26
|
-
class Otto
|
27
23
|
attr_reader :routes, :routes_literal, :routes_static, :route_definitions
|
28
24
|
attr_reader :option, :static_route
|
29
25
|
attr_accessor :not_found, :server_error
|
26
|
+
|
30
27
|
def initialize path=nil, opts={}
|
31
|
-
@routes_static = { :
|
32
|
-
@routes = { :
|
33
|
-
@routes_literal = { :
|
28
|
+
@routes_static = { GET: {} }
|
29
|
+
@routes = { GET: [] }
|
30
|
+
@routes_literal = { GET: {} }
|
34
31
|
@route_definitions = {}
|
35
32
|
@option = opts.merge({
|
36
|
-
:
|
37
|
-
:
|
33
|
+
public: nil,
|
34
|
+
locale: 'en'
|
38
35
|
})
|
36
|
+
Otto.logger.debug "new Otto: #{opts}" if Otto.debug
|
39
37
|
load(path) unless path.nil?
|
40
38
|
super()
|
41
39
|
end
|
42
40
|
alias_method :options, :option
|
41
|
+
|
43
42
|
def load path
|
44
43
|
path = File.expand_path(path)
|
45
44
|
raise ArgumentError, "Bad path: #{path}" unless File.exist?(path)
|
@@ -51,13 +50,14 @@ class Otto
|
|
51
50
|
route.otto = self
|
52
51
|
path_clean = path.gsub /\/$/, ''
|
53
52
|
@route_definitions[route.definition] = route
|
54
|
-
|
53
|
+
Otto.logger.debug "route: #{route.pattern}" if Otto.debug
|
55
54
|
@routes[route.verb] ||= []
|
56
55
|
@routes[route.verb] << route
|
57
56
|
@routes_literal[route.verb] ||= {}
|
58
57
|
@routes_literal[route.verb][path_clean] = route
|
59
|
-
|
60
|
-
|
58
|
+
|
59
|
+
rescue StandardError => ex
|
60
|
+
Otto.logger.error "Bad route in #{path}: #{entry}"
|
61
61
|
end
|
62
62
|
}
|
63
63
|
self
|
@@ -79,7 +79,7 @@ class Otto
|
|
79
79
|
# Files in the root directory can refer to themselves
|
80
80
|
base_path = path if base_path == '/'
|
81
81
|
static_path = File.join(option[:public], base_path)
|
82
|
-
|
82
|
+
Otto.logger.debug "new static route: #{base_path} (#{path})" if Otto.debug
|
83
83
|
routes_static[:GET][base_path] = base_path
|
84
84
|
end
|
85
85
|
end
|
@@ -100,15 +100,15 @@ class Otto
|
|
100
100
|
literal_routes = routes_literal[http_verb] || {}
|
101
101
|
literal_routes.merge! routes_literal[:GET] if http_verb == :HEAD
|
102
102
|
if static_route && http_verb == :GET && routes_static[:GET].member?(base_path)
|
103
|
-
#
|
103
|
+
#Otto.logger.debug " request: #{path_info} (static)"
|
104
104
|
static_route.call(env)
|
105
105
|
elsif literal_routes.has_key?(path_info_clean)
|
106
106
|
route = literal_routes[path_info_clean]
|
107
|
-
#
|
107
|
+
#Otto.logger.debug " request: #{http_verb} #{path_info} (literal route: #{route.verb} #{route.path})"
|
108
108
|
route.call(env)
|
109
109
|
elsif static_route && http_verb == :GET && safe_file?(path_info)
|
110
110
|
static_path = File.join(option[:public], base_path)
|
111
|
-
|
111
|
+
Otto.logger.debug " new static route: #{base_path} (#{path_info})"
|
112
112
|
routes_static[:GET][base_path] = base_path
|
113
113
|
static_route.call(env)
|
114
114
|
else
|
@@ -117,7 +117,7 @@ class Otto
|
|
117
117
|
valid_routes = routes[http_verb] || []
|
118
118
|
valid_routes.push *routes[:GET] if http_verb == :HEAD
|
119
119
|
valid_routes.each { |route|
|
120
|
-
#
|
120
|
+
#Otto.logger.debug " request: #{http_verb} #{path_info} (trying route: #{route.verb} #{route.pattern})"
|
121
121
|
if (match = route.pattern.match(path_info))
|
122
122
|
values = match.captures.to_a
|
123
123
|
# The first capture returned is the entire matched string b/c
|
@@ -151,7 +151,7 @@ class Otto
|
|
151
151
|
end
|
152
152
|
end
|
153
153
|
rescue => ex
|
154
|
-
|
154
|
+
Otto.logger.error ex.message, ex.backtrace
|
155
155
|
if found_route = literal_routes['/500']
|
156
156
|
found_route.call env
|
157
157
|
else
|
@@ -159,7 +159,6 @@ class Otto
|
|
159
159
|
end
|
160
160
|
end
|
161
161
|
|
162
|
-
|
163
162
|
# Return the URI path for the given +route_definition+
|
164
163
|
# e.g.
|
165
164
|
#
|
@@ -199,130 +198,15 @@ class Otto
|
|
199
198
|
locale
|
200
199
|
}.reverse
|
201
200
|
end
|
202
|
-
|
201
|
+
Otto.logger.debug "locale: #{locales} (#{accept_langs})" if Otto.debug
|
203
202
|
locales.empty? ? nil : locales
|
204
203
|
end
|
205
204
|
|
206
|
-
module Static
|
207
|
-
extend self
|
208
|
-
def server_error
|
209
|
-
[500, {'Content-Type'=>'text/plain'}, ["Server error"]]
|
210
|
-
end
|
211
|
-
def not_found
|
212
|
-
[404, {'Content-Type'=>'text/plain'}, ["Not Found"]]
|
213
|
-
end
|
214
|
-
# Enable string or symbol key access to the nested params hash.
|
215
|
-
def indifferent_params(params)
|
216
|
-
if params.is_a?(Hash)
|
217
|
-
params = indifferent_hash.merge(params)
|
218
|
-
params.each do |key, value|
|
219
|
-
next unless value.is_a?(Hash) || value.is_a?(Array)
|
220
|
-
params[key] = indifferent_params(value)
|
221
|
-
end
|
222
|
-
elsif params.is_a?(Array)
|
223
|
-
params.collect! do |value|
|
224
|
-
if value.is_a?(Hash) || value.is_a?(Array)
|
225
|
-
indifferent_params(value)
|
226
|
-
else
|
227
|
-
value
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
# Creates a Hash with indifferent access.
|
233
|
-
def indifferent_hash
|
234
|
-
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
235
|
-
end
|
236
|
-
end
|
237
|
-
#
|
238
|
-
# e.g.
|
239
|
-
#
|
240
|
-
# GET /uri/path YourApp.method
|
241
|
-
# GET /uri/path2 YourApp#method
|
242
|
-
#
|
243
|
-
class Route
|
244
|
-
module ClassMethods
|
245
|
-
attr_accessor :otto
|
246
|
-
end
|
247
|
-
attr_reader :verb, :path, :pattern, :method, :klass, :name, :definition, :keys, :kind
|
248
|
-
attr_accessor :otto
|
249
|
-
def initialize verb, path, definition
|
250
|
-
@verb, @path, @definition = verb.to_s.upcase.to_sym, path, definition
|
251
|
-
@pattern, @keys = *compile(@path)
|
252
|
-
if !@definition.index('.').nil?
|
253
|
-
@klass, @name = @definition.split('.')
|
254
|
-
@kind = :class
|
255
|
-
elsif !@definition.index('#').nil?
|
256
|
-
@klass, @name = @definition.split('#')
|
257
|
-
@kind = :instance
|
258
|
-
else
|
259
|
-
raise ArgumentError, "Bad definition: #{@definition}"
|
260
|
-
end
|
261
|
-
@klass = eval(@klass)
|
262
|
-
#@method = eval(@klass).method(@name)
|
263
|
-
end
|
264
|
-
def pattern_regexp
|
265
|
-
Regexp.new(@path.gsub(/\/\*/, '/.+'))
|
266
|
-
end
|
267
|
-
def call(env, extra_params={})
|
268
|
-
extra_params ||= {}
|
269
|
-
req = Rack::Request.new(env)
|
270
|
-
res = Rack::Response.new
|
271
|
-
req.extend Otto::RequestHelpers
|
272
|
-
res.extend Otto::ResponseHelpers
|
273
|
-
res.request = req
|
274
|
-
req.params.merge! extra_params
|
275
|
-
req.params.replace Otto::Static.indifferent_params(req.params)
|
276
|
-
klass.extend Otto::Route::ClassMethods
|
277
|
-
klass.otto = self.otto
|
278
|
-
case kind
|
279
|
-
when :instance
|
280
|
-
inst = klass.new req, res
|
281
|
-
inst.send(name)
|
282
|
-
when :class
|
283
|
-
klass.send(name, req, res)
|
284
|
-
else
|
285
|
-
raise RuntimeError, "Unsupported kind for #{@definition}: #{kind}"
|
286
|
-
end
|
287
|
-
res.body = [res.body] unless res.body.respond_to?(:each)
|
288
|
-
res.finish
|
289
|
-
end
|
290
|
-
# Brazenly borrowed from Sinatra::Base:
|
291
|
-
# https://github.com/sinatra/sinatra/blob/v1.2.6/lib/sinatra/base.rb#L1156
|
292
|
-
def compile(path)
|
293
|
-
keys = []
|
294
|
-
if path.respond_to? :to_str
|
295
|
-
special_chars = %w{. + ( ) $}
|
296
|
-
pattern =
|
297
|
-
path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
|
298
|
-
case match
|
299
|
-
when "*"
|
300
|
-
keys << 'splat'
|
301
|
-
"(.*?)"
|
302
|
-
when *special_chars
|
303
|
-
Regexp.escape(match)
|
304
|
-
else
|
305
|
-
keys << $2[1..-1]
|
306
|
-
"([^/?#]+)"
|
307
|
-
end
|
308
|
-
end
|
309
|
-
# Wrap the regex in parens so the regex works properly.
|
310
|
-
# They can fail when there's an | for example (matching only the last one).
|
311
|
-
# Note: this means we also need to remove the first matched value.
|
312
|
-
[/\A(#{pattern})\z/, keys]
|
313
|
-
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
314
|
-
[path, path.keys]
|
315
|
-
elsif path.respond_to?(:names) && path.respond_to?(:match)
|
316
|
-
[path, path.names]
|
317
|
-
elsif path.respond_to? :match
|
318
|
-
[path, keys]
|
319
|
-
else
|
320
|
-
raise TypeError, path
|
321
|
-
end
|
322
|
-
end
|
323
|
-
end
|
324
205
|
class << self
|
325
|
-
attr_accessor :debug
|
206
|
+
attr_accessor :debug, :logger
|
207
|
+
end
|
208
|
+
|
209
|
+
module ClassMethods
|
326
210
|
def default
|
327
211
|
@default ||= Otto.new
|
328
212
|
@default
|
@@ -340,88 +224,5 @@ class Otto
|
|
340
224
|
!guesses.flatten.select { |n| ENV['RACK_ENV'].to_s == n.to_s }.empty?
|
341
225
|
end
|
342
226
|
end
|
343
|
-
|
344
|
-
def user_agent
|
345
|
-
env['HTTP_USER_AGENT']
|
346
|
-
end
|
347
|
-
# HTTP_X_FORWARDED_FOR is from the ELB (non-https only)
|
348
|
-
# and it can take the form: 74.121.244.2, 10.252.130.147
|
349
|
-
# HTTP_X_REAL_IP is from nginx
|
350
|
-
# REMOTE_ADDR is from thin
|
351
|
-
# There's no way to get the client IP address in HTTPS.
|
352
|
-
def client_ipaddress
|
353
|
-
env['HTTP_X_FORWARDED_FOR'].to_s.split(/,\s*/).first ||
|
354
|
-
env['HTTP_X_REAL_IP'] || env['REMOTE_ADDR']
|
355
|
-
end
|
356
|
-
def request_method
|
357
|
-
env['REQUEST_METHOD']
|
358
|
-
end
|
359
|
-
def current_server
|
360
|
-
[current_server_name, env['SERVER_PORT']].join(':')
|
361
|
-
end
|
362
|
-
def current_server_name
|
363
|
-
env['SERVER_NAME']
|
364
|
-
end
|
365
|
-
def http_host
|
366
|
-
env['HTTP_HOST']
|
367
|
-
end
|
368
|
-
def request_path
|
369
|
-
env['REQUEST_PATH']
|
370
|
-
end
|
371
|
-
def request_uri
|
372
|
-
env['REQUEST_URI']
|
373
|
-
end
|
374
|
-
def root_path
|
375
|
-
env['SCRIPT_NAME']
|
376
|
-
end
|
377
|
-
def absolute_suri host=current_server_name
|
378
|
-
prefix = local? ? 'http://' : 'https://'
|
379
|
-
[prefix, host, request_path].join
|
380
|
-
end
|
381
|
-
def local?
|
382
|
-
Otto.env?(:dev, :development) &&
|
383
|
-
(client_ipaddress == '127.0.0.1' ||
|
384
|
-
!client_ipaddress.match(/^10\.0\./).nil? ||
|
385
|
-
!client_ipaddress.match(/^192\.168\./).nil?)
|
386
|
-
end
|
387
|
-
def secure?
|
388
|
-
# X-Scheme is set by nginx
|
389
|
-
# X-FORWARDED-PROTO is set by elastic load balancer
|
390
|
-
(env['HTTP_X_FORWARDED_PROTO'] == 'https' || env['HTTP_X_SCHEME'] == "https")
|
391
|
-
end
|
392
|
-
# See: http://stackoverflow.com/questions/10013812/how-to-prevent-jquery-ajax-from-following-a-redirect-after-a-post
|
393
|
-
def ajax?
|
394
|
-
env['HTTP_X_REQUESTED_WITH'].to_s.downcase == 'xmlhttprequest'
|
395
|
-
end
|
396
|
-
def cookie name
|
397
|
-
cookies[name.to_s]
|
398
|
-
end
|
399
|
-
def cookie? name
|
400
|
-
!cookie(name).to_s.empty?
|
401
|
-
end
|
402
|
-
def current_absolute_uri
|
403
|
-
prefix = secure? && !local? ? 'https://' : 'http://'
|
404
|
-
[prefix, http_host, request_path].join
|
405
|
-
end
|
406
|
-
end
|
407
|
-
module ResponseHelpers
|
408
|
-
attr_accessor :request
|
409
|
-
def send_secure_cookie name, value, ttl
|
410
|
-
send_cookie name, value, ttl, true
|
411
|
-
end
|
412
|
-
def send_cookie name, value, ttl, secure=true
|
413
|
-
secure = false if request.local?
|
414
|
-
opts = {
|
415
|
-
:value => value,
|
416
|
-
:path => '/',
|
417
|
-
:expires => (Time.now.utc + ttl + 10),
|
418
|
-
:secure => secure
|
419
|
-
}
|
420
|
-
#opts[:domain] = request.env['SERVER_NAME']
|
421
|
-
set_cookie name, opts
|
422
|
-
end
|
423
|
-
def delete_cookie name
|
424
|
-
send_cookie name, nil, -1.day
|
425
|
-
end
|
426
|
-
end
|
227
|
+
extend ClassMethods
|
427
228
|
end
|
data/otto.gemspec
CHANGED
@@ -1,32 +1,22 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
-
Gem::Specification.new do |
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
"
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
"example/config.ru",
|
18
|
-
"example/public/favicon.ico",
|
19
|
-
"example/public/img/otto.jpg",
|
20
|
-
"example/routes",
|
21
|
-
"lib/otto.rb",
|
22
|
-
"otto.gemspec"
|
23
|
-
]
|
24
|
-
s.homepage = "https://github.com/delano/otto"
|
25
|
-
s.require_paths = ["lib"]
|
26
|
-
s.rubygems_version = "3.2.22" # Update to the latest version
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "otto"
|
5
|
+
spec.version = "1.1.0-alpha1"
|
6
|
+
spec.summary = "Auto-define your rack-apps in plaintext."
|
7
|
+
spec.description = "Otto: #{spec.summary}"
|
8
|
+
spec.email = "gems@solutious.com"
|
9
|
+
spec.authors = ["Delano Mandelbaum"]
|
10
|
+
spec.license = "MIT"
|
11
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
12
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
13
|
+
end
|
14
|
+
spec.homepage = "https://github.com/delano/otto"
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
spec.rubygems_version = "3.2.22" # Update to the latest version
|
27
17
|
|
28
|
-
|
18
|
+
spec.required_ruby_version = ['>= 2.6.8', '< 4.0']
|
29
19
|
|
30
|
-
|
31
|
-
|
20
|
+
spec.add_dependency 'addressable', '>= 2.2.6'
|
21
|
+
spec.add_dependency 'rack', '>= 1.2.1'
|
32
22
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: otto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.1.0.pre.alpha1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -44,9 +44,13 @@ executables: []
|
|
44
44
|
extensions: []
|
45
45
|
extra_rdoc_files: []
|
46
46
|
files:
|
47
|
+
- ".gitignore"
|
48
|
+
- ".rubocop.yml"
|
49
|
+
- CHANGES.txt
|
50
|
+
- Gemfile
|
51
|
+
- Gemfile.lock
|
47
52
|
- LICENSE.txt
|
48
53
|
- README.md
|
49
|
-
- Rakefile
|
50
54
|
- VERSION.yml
|
51
55
|
- example/app.rb
|
52
56
|
- example/config.ru
|
@@ -54,6 +58,11 @@ files:
|
|
54
58
|
- example/public/img/otto.jpg
|
55
59
|
- example/routes
|
56
60
|
- lib/otto.rb
|
61
|
+
- lib/otto/helpers/request.rb
|
62
|
+
- lib/otto/helpers/response.rb
|
63
|
+
- lib/otto/route.rb
|
64
|
+
- lib/otto/static.rb
|
65
|
+
- lib/otto/version.rb
|
57
66
|
- otto.gemspec
|
58
67
|
homepage: https://github.com/delano/otto
|
59
68
|
licenses:
|
@@ -68,13 +77,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
77
|
- - ">="
|
69
78
|
- !ruby/object:Gem::Version
|
70
79
|
version: 2.6.8
|
80
|
+
- - "<"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.0'
|
71
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
84
|
requirements:
|
73
85
|
- - ">="
|
74
86
|
- !ruby/object:Gem::Version
|
75
87
|
version: '0'
|
76
88
|
requirements: []
|
77
|
-
rubygems_version: 3.
|
89
|
+
rubygems_version: 3.5.15
|
78
90
|
signing_key:
|
79
91
|
specification_version: 4
|
80
92
|
summary: Auto-define your rack-apps in plaintext.
|
data/Rakefile
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
require "rubygems"
|
2
|
-
require "rake"
|
3
|
-
require "rake/clean"
|
4
|
-
require 'yaml'
|
5
|
-
|
6
|
-
require 'rdoc/task'
|
7
|
-
|
8
|
-
config = YAML.load_file("VERSION.yml")
|
9
|
-
task :default => ["build"]
|
10
|
-
CLEAN.include [ 'pkg', 'doc' ]
|
11
|
-
name = "otto"
|
12
|
-
|
13
|
-
begin
|
14
|
-
require "jeweler"
|
15
|
-
Jeweler::Tasks.new do |gem|
|
16
|
-
gem.version = "#{config[:MAJOR]}.#{config[:MINOR]}.#{config[:PATCH]}"
|
17
|
-
gem.name = "otto"
|
18
|
-
gem.rubyforge_project = gem.name
|
19
|
-
gem.summary = "Auto-define your rack-apps in plaintext."
|
20
|
-
gem.description = "Auto-define your rack-apps in plaintext."
|
21
|
-
gem.email = "delano@solutious.com"
|
22
|
-
gem.homepage = "http://github.com/delano/otto"
|
23
|
-
gem.authors = ["Delano Mandelbaum"]
|
24
|
-
gem.add_dependency('rack', '>= 1.2.1')
|
25
|
-
gem.add_dependency('addressable', '>= 2.2.6')
|
26
|
-
end
|
27
|
-
Jeweler::GemcutterTasks.new
|
28
|
-
rescue LoadError
|
29
|
-
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
RDoc::Task.new do |rdoc|
|
34
|
-
version = "#{config[:MAJOR]}.#{config[:MINOR]}.#{config[:PATCH]}"
|
35
|
-
rdoc.rdoc_dir = "doc"
|
36
|
-
rdoc.title = "otto #{version}"
|
37
|
-
rdoc.rdoc_files.include("README*")
|
38
|
-
rdoc.rdoc_files.include("LICENSE.txt")
|
39
|
-
rdoc.rdoc_files.include("bin/*.rb")
|
40
|
-
rdoc.rdoc_files.include("lib/**/*.rb")
|
41
|
-
end
|
42
|
-
|
43
|
-
|