otto 1.0.2 → 1.1.0.pre.alpha2
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 +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 -2
- 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 +27 -0
- data/lib/otto.rb +38 -236
- data/otto.gemspec +9 -16
- metadata +25 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23a54df729acf5a96e337f0d096aee18376e579771e85e0f3f02d9355b049baf
|
4
|
+
data.tar.gz: 871c1bebc912b19fda8f3730f905d29077f6aa5e88ab9a087a95be3ccf29d312
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 903a3264f15fdcf38b523c7c380dc3b6897f9670a1573c9a9a22edfc1e8e6d61f3f30f89fbb0e78b3db11dee2e824d423444d98d3010f03530e577410bade70c
|
7
|
+
data.tar.gz: c096b04cf99f7bf7d18c6441c625f4a304b1b98d8c04d83ee58f2a71f047196ea2dacae75abcfe04ad97324b40d169a952a1c1f4555e388c9c426e2d628e53a9
|
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
@@ -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,27 @@
|
|
1
|
+
#
|
2
|
+
|
3
|
+
class Otto
|
4
|
+
# Otto::VERSION
|
5
|
+
#
|
6
|
+
module VERSION
|
7
|
+
def self.to_a
|
8
|
+
load_config
|
9
|
+
[@version[:MAJOR], @version[:MINOR], @version[:PATCH]]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.to_s
|
13
|
+
version = to_a.join('.')
|
14
|
+
"#{version}-#{@version[:PRE]}" if @version[:PRE]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.inspect
|
18
|
+
to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.load_config
|
22
|
+
return if @version
|
23
|
+
require 'yaml'
|
24
|
+
@version = YAML.load_file(File.join(__dir__, '..', '..', 'VERSION.yml'))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
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,8 @@ class Otto
|
|
151
151
|
end
|
152
152
|
end
|
153
153
|
rescue => ex
|
154
|
-
|
154
|
+
Otto.logger.error "#{ex.class}: #{ex.message} #{ex.backtrace.join("\n")}"
|
155
|
+
|
155
156
|
if found_route = literal_routes['/500']
|
156
157
|
found_route.call env
|
157
158
|
else
|
@@ -159,7 +160,6 @@ class Otto
|
|
159
160
|
end
|
160
161
|
end
|
161
162
|
|
162
|
-
|
163
163
|
# Return the URI path for the given +route_definition+
|
164
164
|
# e.g.
|
165
165
|
#
|
@@ -199,130 +199,15 @@ class Otto
|
|
199
199
|
locale
|
200
200
|
}.reverse
|
201
201
|
end
|
202
|
-
|
202
|
+
Otto.logger.debug "locale: #{locales} (#{accept_langs})" if Otto.debug
|
203
203
|
locales.empty? ? nil : locales
|
204
204
|
end
|
205
205
|
|
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
206
|
class << self
|
325
|
-
attr_accessor :debug
|
207
|
+
attr_accessor :debug, :logger
|
208
|
+
end
|
209
|
+
|
210
|
+
module ClassMethods
|
326
211
|
def default
|
327
212
|
@default ||= Otto.new
|
328
213
|
@default
|
@@ -340,88 +225,5 @@ class Otto
|
|
340
225
|
!guesses.flatten.select { |n| ENV['RACK_ENV'].to_s == n.to_s }.empty?
|
341
226
|
end
|
342
227
|
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
|
228
|
+
extend ClassMethods
|
427
229
|
end
|
data/otto.gemspec
CHANGED
@@ -1,31 +1,24 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
+
require_relative 'lib/otto/version'
|
4
|
+
|
3
5
|
Gem::Specification.new do |spec|
|
4
6
|
spec.name = "otto"
|
5
|
-
spec.version =
|
7
|
+
spec.version = Otto::VERSION.to_s
|
6
8
|
spec.summary = "Auto-define your rack-apps in plaintext."
|
7
9
|
spec.description = "Otto: #{spec.summary}"
|
8
10
|
spec.email = "gems@solutious.com"
|
9
11
|
spec.authors = ["Delano Mandelbaum"]
|
10
12
|
spec.license = "MIT"
|
11
|
-
spec.files =
|
12
|
-
"
|
13
|
-
|
14
|
-
"VERSION.yml",
|
15
|
-
"example/app.rb",
|
16
|
-
"example/config.ru",
|
17
|
-
"example/public/favicon.ico",
|
18
|
-
"example/public/img/otto.jpg",
|
19
|
-
"example/routes",
|
20
|
-
"lib/otto.rb",
|
21
|
-
"otto.gemspec"
|
22
|
-
]
|
13
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
14
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
15
|
+
end
|
23
16
|
spec.homepage = "https://github.com/delano/otto"
|
24
17
|
spec.require_paths = ["lib"]
|
25
|
-
spec.rubygems_version = "3.
|
18
|
+
spec.rubygems_version = "3.5.15" # Update to the latest version
|
26
19
|
|
27
20
|
spec.required_ruby_version = ['>= 2.6.8', '< 4.0']
|
28
21
|
|
29
|
-
spec.
|
30
|
-
spec.
|
22
|
+
spec.add_runtime_dependency 'addressable', '~> 2.2', '>= 2.2.6'
|
23
|
+
spec.add_runtime_dependency 'rack', '~> 1.2', '>= 1.2.1'
|
31
24
|
end
|
metadata
CHANGED
@@ -1,19 +1,22 @@
|
|
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.alpha2
|
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-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
17
20
|
- - ">="
|
18
21
|
- !ruby/object:Gem::Version
|
19
22
|
version: 2.2.6
|
@@ -21,6 +24,9 @@ dependencies:
|
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.2'
|
24
30
|
- - ">="
|
25
31
|
- !ruby/object:Gem::Version
|
26
32
|
version: 2.2.6
|
@@ -28,6 +34,9 @@ dependencies:
|
|
28
34
|
name: rack
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.2'
|
31
40
|
- - ">="
|
32
41
|
- !ruby/object:Gem::Version
|
33
42
|
version: 1.2.1
|
@@ -35,6 +44,9 @@ dependencies:
|
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
37
46
|
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.2'
|
38
50
|
- - ">="
|
39
51
|
- !ruby/object:Gem::Version
|
40
52
|
version: 1.2.1
|
@@ -44,6 +56,11 @@ executables: []
|
|
44
56
|
extensions: []
|
45
57
|
extra_rdoc_files: []
|
46
58
|
files:
|
59
|
+
- ".gitignore"
|
60
|
+
- ".rubocop.yml"
|
61
|
+
- CHANGES.txt
|
62
|
+
- Gemfile
|
63
|
+
- Gemfile.lock
|
47
64
|
- LICENSE.txt
|
48
65
|
- README.md
|
49
66
|
- VERSION.yml
|
@@ -53,6 +70,11 @@ files:
|
|
53
70
|
- example/public/img/otto.jpg
|
54
71
|
- example/routes
|
55
72
|
- lib/otto.rb
|
73
|
+
- lib/otto/helpers/request.rb
|
74
|
+
- lib/otto/helpers/response.rb
|
75
|
+
- lib/otto/route.rb
|
76
|
+
- lib/otto/static.rb
|
77
|
+
- lib/otto/version.rb
|
56
78
|
- otto.gemspec
|
57
79
|
homepage: https://github.com/delano/otto
|
58
80
|
licenses:
|
@@ -76,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
98
|
- !ruby/object:Gem::Version
|
77
99
|
version: '0'
|
78
100
|
requirements: []
|
79
|
-
rubygems_version: 3.5.
|
101
|
+
rubygems_version: 3.5.15
|
80
102
|
signing_key:
|
81
103
|
specification_version: 4
|
82
104
|
summary: Auto-define your rack-apps in plaintext.
|