otto 1.0.1 → 1.1.0.pre.alpha1
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 -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
|
-
|