otto 1.0.2 → 1.1.0.pre.alpha2
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 -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.
|