landline 0.9.2
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 +7 -0
- data/HACKING.md +30 -0
- data/LAYOUT.md +59 -0
- data/LICENSE.md +660 -0
- data/README.md +159 -0
- data/lib/landline/dsl/constructors_path.rb +107 -0
- data/lib/landline/dsl/constructors_probe.rb +28 -0
- data/lib/landline/dsl/methods_common.rb +28 -0
- data/lib/landline/dsl/methods_path.rb +75 -0
- data/lib/landline/dsl/methods_probe.rb +129 -0
- data/lib/landline/dsl/methods_template.rb +16 -0
- data/lib/landline/node.rb +87 -0
- data/lib/landline/path.rb +157 -0
- data/lib/landline/pattern_matching/glob.rb +168 -0
- data/lib/landline/pattern_matching/rematch.rb +49 -0
- data/lib/landline/pattern_matching/util.rb +15 -0
- data/lib/landline/pattern_matching.rb +75 -0
- data/lib/landline/probe/handler.rb +56 -0
- data/lib/landline/probe/http_method.rb +74 -0
- data/lib/landline/probe/serve_handler.rb +39 -0
- data/lib/landline/probe.rb +62 -0
- data/lib/landline/request.rb +135 -0
- data/lib/landline/response.rb +140 -0
- data/lib/landline/server.rb +49 -0
- data/lib/landline/template/erb.rb +27 -0
- data/lib/landline/template/erubi.rb +36 -0
- data/lib/landline/template.rb +95 -0
- data/lib/landline/util/cookie.rb +150 -0
- data/lib/landline/util/errors.rb +11 -0
- data/lib/landline/util/html.rb +119 -0
- data/lib/landline/util/lookup.rb +37 -0
- data/lib/landline/util/mime.rb +1276 -0
- data/lib/landline/util/multipart.rb +175 -0
- data/lib/landline/util/parsesorting.rb +37 -0
- data/lib/landline/util/parseutils.rb +111 -0
- data/lib/landline/util/query.rb +66 -0
- data/lib/landline.rb +20 -0
- metadata +85 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'pattern_matching'
|
4
|
+
require_relative 'node'
|
5
|
+
require_relative 'dsl/constructors_path'
|
6
|
+
require_relative 'dsl/methods_path'
|
7
|
+
require_relative 'dsl/methods_common'
|
8
|
+
require_relative 'util/lookup'
|
9
|
+
|
10
|
+
module Landline
|
11
|
+
# Execution context for filters and preprocessors.
|
12
|
+
class ProcessorContext
|
13
|
+
include Landline::DSL::CommonMethods
|
14
|
+
|
15
|
+
def initialize(path)
|
16
|
+
@origin = path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Execution context for path setup block.
|
21
|
+
class PathContext
|
22
|
+
include Landline::DSL::PathConstructors
|
23
|
+
include Landline::DSL::PathMethods
|
24
|
+
|
25
|
+
def initialize(path)
|
26
|
+
@origin = path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Primary building block of request navigation.
|
31
|
+
class Path < Landline::Node
|
32
|
+
ProcContext = Landline::ProcessorContext
|
33
|
+
Context = Landline::PathContext
|
34
|
+
|
35
|
+
# @param path [Object] Object to generate {Landline::Pattern} from
|
36
|
+
# @param parent [Landline::Node] Parent object to inherit properties to
|
37
|
+
# @param setup [#call] Setup block
|
38
|
+
def initialize(path, parent:, **params, &setup)
|
39
|
+
super(path, parent: parent, **params)
|
40
|
+
# Child nodes array
|
41
|
+
@children = []
|
42
|
+
# Arrays of preprocessors, postprocessors and filters
|
43
|
+
@preprocessors = []
|
44
|
+
@postprocessors = []
|
45
|
+
@filters = []
|
46
|
+
# Contexts setup
|
47
|
+
context = self.class::Context.new(self)
|
48
|
+
context.instance_exec(&setup)
|
49
|
+
@proccontext = self.class::ProcContext.new(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Method callback on successful request navigation.
|
53
|
+
# Finds the next appropriate path to go to.
|
54
|
+
# @return [Boolean] true if further navigation will be done
|
55
|
+
# @raise [UncaughtThrowError] by default throws :response if no matches found.
|
56
|
+
def process(request)
|
57
|
+
return false unless run_filters(request)
|
58
|
+
|
59
|
+
run_preprocessors(request)
|
60
|
+
enqueue_postprocessors(request)
|
61
|
+
@children.each do |x|
|
62
|
+
if (value = x.go(request))
|
63
|
+
return value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
value = index(request)
|
67
|
+
return value if value
|
68
|
+
|
69
|
+
_die(404)
|
70
|
+
rescue StandardError => e
|
71
|
+
_die(500, backtrace: [e.to_s] + e.backtrace)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Add a preprocessor to the path.
|
75
|
+
# Does not modify path execution.
|
76
|
+
# @param block [#call]
|
77
|
+
# @yieldparam request [Landline::Request]
|
78
|
+
def preprocess(&block)
|
79
|
+
@preprocessors.append(block)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add a postprocessor to the path.
|
83
|
+
# @param block [#call]
|
84
|
+
# @yieldparam request [Landline::Request]
|
85
|
+
# @yieldparam response [Landline::Response]
|
86
|
+
def postprocess(&block)
|
87
|
+
@postprocessors.append(block)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Add a filter to the path.
|
91
|
+
# Blocks path access if a filter returns false.
|
92
|
+
# @param block [#call]
|
93
|
+
# @yieldparam request [Landline::Request]
|
94
|
+
def filter(&block)
|
95
|
+
@filters.append(block)
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_reader :children, :properties
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# Sequentially run through all filters and drop request if one is false
|
103
|
+
# @param request [Landline::Request]
|
104
|
+
# @return [Boolean] true if request passed all filters
|
105
|
+
def run_filters(request)
|
106
|
+
@filters.each do |filter|
|
107
|
+
return false unless @proccontext.instance_exec(request, &filter)
|
108
|
+
end
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
# Sequentially run all preprocessors on a request
|
113
|
+
# @param request [Landline::Request]
|
114
|
+
def run_preprocessors(request)
|
115
|
+
@preprocessors.each do |preproc|
|
116
|
+
@proccontext.instance_exec(request, &preproc)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Append postprocessors to request
|
121
|
+
# @param request [Landline::Request]
|
122
|
+
def enqueue_postprocessors(request)
|
123
|
+
request.postprocessors.append(*@postprocessors)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Try to perform indexing on the path if possible
|
127
|
+
# @param request [Landline::Request]
|
128
|
+
# @return [Boolean] true if indexing succeeded
|
129
|
+
def index(request)
|
130
|
+
return false unless request.path.match?(/^\/?$/)
|
131
|
+
|
132
|
+
@properties["index"].each do |index|
|
133
|
+
request.path = index
|
134
|
+
@children.each do |x|
|
135
|
+
if (value = x.go(request))
|
136
|
+
return value
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
# Handle an errorcode
|
144
|
+
# @param errorcode [Integer]
|
145
|
+
# @param backtrace [Array(String), nil]
|
146
|
+
# @raise [UncaughtThrowError] throws :finish to stop processing
|
147
|
+
def _die(errorcode, backtrace: nil)
|
148
|
+
throw :finish, [errorcode].append(
|
149
|
+
*(@properties["handle.#{errorcode}"] or
|
150
|
+
@properties["handle.default"]).call(
|
151
|
+
errorcode,
|
152
|
+
backtrace: backtrace
|
153
|
+
)
|
154
|
+
)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Landline
|
4
|
+
module PatternMatching
|
5
|
+
# someday i've heard this quote:
|
6
|
+
# "if you love regexp so much why don't you marry the damn thing?"
|
7
|
+
# and so i did, as you can see here.
|
8
|
+
|
9
|
+
# Implements glob-like pattern matching.
|
10
|
+
# Exact specifications for globbing rules:
|
11
|
+
#
|
12
|
+
# "/"
|
13
|
+
# - act as directory separators
|
14
|
+
# - multiple slashes (i.e. "///") are the same as one slash ("/")
|
15
|
+
# - slashes are stripped at start and end of an expression or path
|
16
|
+
# - slashes are not matched by anything but the globstar ("**")
|
17
|
+
#
|
18
|
+
# "*" ( regexp: /([^/]*)/ )
|
19
|
+
# - matches from 0 to any number of characters
|
20
|
+
# - does not match nothing if placed between two slashes (i.e "/*/")
|
21
|
+
# - result is captured in an array
|
22
|
+
# - stops at slashes
|
23
|
+
# - greedy (matches as much as possible)
|
24
|
+
#
|
25
|
+
# "**" ( regexp: /(.*)/ )
|
26
|
+
# - matches any number of characters
|
27
|
+
# - matches slashes ("/")
|
28
|
+
# - result is captured in an array
|
29
|
+
# - does not stop at slashes
|
30
|
+
# - greedy (matches as much as possible)
|
31
|
+
#
|
32
|
+
# "[...]+" ( regexp: itself, ! and ^ at the start are interchangeable )
|
33
|
+
# - acts like a regexp range
|
34
|
+
# - matches any characters, including slashes if specified
|
35
|
+
# - matches any number of characters
|
36
|
+
# - valid ways to specify a range: [A-z], [a-z], [9-z] (ascii order)
|
37
|
+
# - ! or ^ at the start invert meaning (any character not in range)
|
38
|
+
# - result is captured in an array
|
39
|
+
#
|
40
|
+
# ":name" ( regexp: acts like a named group for /[^/]*/ )
|
41
|
+
# - acts like * as defined above
|
42
|
+
# - result is captured in a hash with "name" as key
|
43
|
+
# - name allows alphanumeric characters and underscores
|
44
|
+
# - ignored unless placed between two slashes
|
45
|
+
#
|
46
|
+
# "(name|name2|...)" ( regexp: itself )
|
47
|
+
# - matches any of (full keywords) name, name2, ...
|
48
|
+
# - result is captured in an array
|
49
|
+
# - each name may include only these groups of characters:
|
50
|
+
# - alphanumeric
|
51
|
+
# - slashes
|
52
|
+
# - underscores
|
53
|
+
# - dashes
|
54
|
+
class Glob
|
55
|
+
# @param input [String] Glob pattern
|
56
|
+
def initialize(pattern)
|
57
|
+
pattern = Landline::PatternMatching.canonicalize(pattern)
|
58
|
+
pieces = pattern.split(TOKENS)
|
59
|
+
# @type [Array<String,Integer>]
|
60
|
+
@index = build_index(pieces)
|
61
|
+
# @type [Regexp]
|
62
|
+
@glob = build_regexp(pieces)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Match the string and assign matches to parameters.
|
66
|
+
# Returns:
|
67
|
+
#
|
68
|
+
# - Unmatched part of a string
|
69
|
+
# - Unnamed parameters
|
70
|
+
# - Named parameters
|
71
|
+
# @param input [String] String to match
|
72
|
+
# @return [Array(String,Array,Hash)]
|
73
|
+
# @return [FalseClass] if input doesn't match glob
|
74
|
+
def match(input)
|
75
|
+
input = Landline::PatternMatching.canonicalize(input)
|
76
|
+
result = input.match(@glob)
|
77
|
+
return false unless result
|
78
|
+
|
79
|
+
input = result.post_match
|
80
|
+
named_params, params = assign_by_index(@index, result.captures)
|
81
|
+
[input, params, named_params]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Test if a string can be matched.
|
85
|
+
# Lighter version of match that doesn't assign any variables.
|
86
|
+
# @param input [String] String to match
|
87
|
+
# @return [Boolean]
|
88
|
+
def match?(input)
|
89
|
+
input = Landline::PatternMatching.canonicalize(input)
|
90
|
+
input.match? @glob
|
91
|
+
end
|
92
|
+
|
93
|
+
# Test if input is convertible to a Glob and if it should be converted
|
94
|
+
# @param input
|
95
|
+
# @return [Boolean] Input can be safely converted to Glob
|
96
|
+
def self.can_convert?(input)
|
97
|
+
input.is_a? String and
|
98
|
+
input.match?(TOKENS)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# i shall name thee Norxondor Glorbulon
|
104
|
+
|
105
|
+
# Regexp pattern to match glob tokens
|
106
|
+
TOKENS = /
|
107
|
+
( # Glob-specific tokens
|
108
|
+
(?<=(?:\/|^))\*\*(?:\/|$) | # Freestanding globstar
|
109
|
+
\* | # Regular glob
|
110
|
+
\[!?\w-\w\]\+ | # Character group
|
111
|
+
(?<=\/):[\w_]+(?=(?:\/|$)) | # Named glob
|
112
|
+
\([\w\/|_-]+\) # Alternator group
|
113
|
+
)
|
114
|
+
/x.freeze
|
115
|
+
|
116
|
+
# Build an index for separating normal matches from named matches
|
117
|
+
# @param pieces [Array(String)] Glob pattern after being splitted
|
118
|
+
# @return [Array(String,Integer)] Index array for assign_by_index
|
119
|
+
def build_index(pieces)
|
120
|
+
count = -1
|
121
|
+
index = []
|
122
|
+
pieces.each do |x|
|
123
|
+
if (name = x.match(/^:([\w_]+)$/))
|
124
|
+
index.append(name[1])
|
125
|
+
elsif x.match?(TOKENS)
|
126
|
+
index.append(count += 1)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
index
|
130
|
+
end
|
131
|
+
|
132
|
+
# Assign values from match.captures to named and numbered groups
|
133
|
+
# @param index [Array(String,Integer)] Index array
|
134
|
+
# @param params [Array] Unnamed captures from a String.match
|
135
|
+
# @return [Array(Hash,Array)]
|
136
|
+
def assign_by_index(index, params)
|
137
|
+
named_params = {}
|
138
|
+
new_params = []
|
139
|
+
params.each_with_index do |x, k|
|
140
|
+
# FIXME: this is a hack for globstar implementation at line 152
|
141
|
+
x = (x.nil? ? "" : x)
|
142
|
+
if index[k].is_a? String
|
143
|
+
named_params[index[k].to_sym] = x
|
144
|
+
else
|
145
|
+
new_params[index[k]] = x
|
146
|
+
end
|
147
|
+
end
|
148
|
+
[named_params, new_params]
|
149
|
+
end
|
150
|
+
|
151
|
+
# Create a Regexp from a glob pattern according to defined syntax
|
152
|
+
# @param tokens [Array(String)] Split array of glob tokens
|
153
|
+
# @return [Regexp]
|
154
|
+
def build_regexp(tokens)
|
155
|
+
Regexp.new(tokens.map do |filter|
|
156
|
+
case filter
|
157
|
+
when /(\/|^)\*\*(\/|$)/ then "(?:(.*)/|)" # FIXME: this may return nil
|
158
|
+
when "*" then "([^/]*)"
|
159
|
+
when /^\([\w\/|_-]+\)$/ then filter.sub('-', '\\-')
|
160
|
+
when /^\[!?\w-\w\]\+$/ then filter.sub('!', '^')
|
161
|
+
when /^:[\w_]+$/ then "([^/]*)"
|
162
|
+
else filter.gsub(/[\^$.|+(){}]/, '\\\\\\0')
|
163
|
+
end
|
164
|
+
end.join("").prepend("^/?"))
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Landline
|
4
|
+
module PatternMatching
|
5
|
+
# Regexp pattern matching wrapper.
|
6
|
+
# Following principles apply to the wrapper:
|
7
|
+
#
|
8
|
+
# - Regexp input is canonicalized using Landline::PatternMatching.canonicalize
|
9
|
+
# - Matched content and anything before it is consumed in #match output
|
10
|
+
#
|
11
|
+
# @note If you are planning to write your own pattern, this is the easiest one to read.
|
12
|
+
class ReMatch
|
13
|
+
def initialize(pattern)
|
14
|
+
@glob = pattern
|
15
|
+
end
|
16
|
+
|
17
|
+
# Match the string and assign matches to parameters.
|
18
|
+
# Returns:
|
19
|
+
#
|
20
|
+
# - Unmatched part of a string
|
21
|
+
# - Unnamed parameters
|
22
|
+
# - Named parameters
|
23
|
+
# @param input [String] String to match
|
24
|
+
# @return [Array(String,Array,Hash)]
|
25
|
+
# @return [FalseClass] if input doesn't match regexp
|
26
|
+
def match(input)
|
27
|
+
result = Landline::PatternMatching.canonicalize(input).match(@glob)
|
28
|
+
return false unless result
|
29
|
+
|
30
|
+
[result.post_match, result.captures, result.named_captures]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Test if a string can be matched.
|
34
|
+
# Lighter version of match that doesn't assign any variables.
|
35
|
+
# @param input [String] String to match
|
36
|
+
# @return [Boolean]
|
37
|
+
def match?(input)
|
38
|
+
Landline::PatternMatching.canonicalize(input).match? @glob
|
39
|
+
end
|
40
|
+
|
41
|
+
# Test if input is convertible and if it should be converted.
|
42
|
+
# @param input
|
43
|
+
# @return [Boolean] Input can be safely converted to Glob
|
44
|
+
def self.can_convert?(string)
|
45
|
+
string.is_a? Regexp
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Landline
|
4
|
+
module PatternMatching
|
5
|
+
# Strips extra slashes from a string.
|
6
|
+
# (including slashes at the start and end of the string)
|
7
|
+
# @param string [String]
|
8
|
+
# @return [String]
|
9
|
+
def self.canonicalize(string)
|
10
|
+
string.gsub(/\/+/, "/")
|
11
|
+
.delete_prefix("/")
|
12
|
+
.delete_suffix("/")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'pattern_matching/util'
|
4
|
+
require_relative 'pattern_matching/glob'
|
5
|
+
require_relative 'pattern_matching/rematch'
|
6
|
+
|
7
|
+
module Landline
|
8
|
+
# Utility functions and pattern-generator classes.
|
9
|
+
# Used primarily to create patterns for path definitions.
|
10
|
+
module PatternMatching end
|
11
|
+
|
12
|
+
# Delegate class for all available patterns.
|
13
|
+
# Picks appropriate pattern based on contents.
|
14
|
+
class Pattern
|
15
|
+
# @param pattern [Object] object to generate pattern from
|
16
|
+
def initialize(pattern)
|
17
|
+
@pattern = patternify(pattern)
|
18
|
+
@static = @pattern.is_a? String
|
19
|
+
end
|
20
|
+
|
21
|
+
# Checks if pattern object is static (is a simple String pattern).
|
22
|
+
def static?
|
23
|
+
@static
|
24
|
+
end
|
25
|
+
|
26
|
+
# Match the string and assign matches to parameters.
|
27
|
+
# Returns:
|
28
|
+
#
|
29
|
+
# - Unmatched part of a string
|
30
|
+
# - Unnamed parameters
|
31
|
+
# - Named parameters
|
32
|
+
# @param input [String] String to match
|
33
|
+
# @return [Array(String,Array,Hash)]
|
34
|
+
# @return [FalseClass] if input doesn't match pattern
|
35
|
+
def match(input)
|
36
|
+
if @pattern.is_a? String
|
37
|
+
input = Landline::PatternMatching.canonicalize(input)
|
38
|
+
if input.start_with?(@pattern)
|
39
|
+
[input.delete_prefix(@pattern), [], {}]
|
40
|
+
else
|
41
|
+
false
|
42
|
+
end
|
43
|
+
else
|
44
|
+
@pattern.match(input)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Test if a string can be matched.
|
49
|
+
# Lighter version of match that doesn't assign any variables.
|
50
|
+
# @param input [String] String to match
|
51
|
+
# @return [Boolean]
|
52
|
+
def match?(input)
|
53
|
+
if @pattern.is_a? String
|
54
|
+
Landline::PatternMatching.canonicalize(input).start_with? @pattern
|
55
|
+
else
|
56
|
+
@pattern.match?(input)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def patternify(pattern)
|
63
|
+
classdomain = Landline::PatternMatching
|
64
|
+
classdomain.constants
|
65
|
+
.filter { |x| classdomain.const_get(x).is_a? Class }
|
66
|
+
.map { |x| classdomain.const_get(x) }
|
67
|
+
.each do |pattern_generator|
|
68
|
+
if pattern_generator.can_convert? pattern
|
69
|
+
return pattern_generator.new(pattern)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
Landline::PatternMatching.canonicalize(pattern)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../probe"
|
4
|
+
|
5
|
+
module Landline
|
6
|
+
module Handlers
|
7
|
+
# Probe that executes a callback on request
|
8
|
+
class Handler < Landline::Probe
|
9
|
+
# @param path [Object]
|
10
|
+
# @param parent [Landline::Node]
|
11
|
+
# @param exec [#call]
|
12
|
+
def initialize(path, **args, &exec)
|
13
|
+
super(path, **args)
|
14
|
+
@callback = exec
|
15
|
+
@context = Landline::ProbeContext.new(self)
|
16
|
+
@response = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :response
|
20
|
+
attr_reader :request
|
21
|
+
|
22
|
+
# Method callback on successful request navigation.
|
23
|
+
# Runs block supplied with object initialization.
|
24
|
+
# Request's #splat and #param are passed to block.
|
25
|
+
#
|
26
|
+
# Callback's returned should be one of viable responses:
|
27
|
+
#
|
28
|
+
# - {Landline::Response} object
|
29
|
+
# - An array that matches Rack return form
|
30
|
+
# - An array that matches old (Rack 2.x) return form
|
31
|
+
# - A string (returned as HTML with code 200)
|
32
|
+
# - false (bounces the request to next handler)
|
33
|
+
# @param request [Landline::Request]
|
34
|
+
# @return [Boolean] true if further navigation is possible
|
35
|
+
# @raise [UncaughtThrowError] may raise if die() is called.
|
36
|
+
def process(request)
|
37
|
+
@response = nil
|
38
|
+
return reject(request) unless request.path.match?(/^\/?$/)
|
39
|
+
|
40
|
+
@request = request
|
41
|
+
response = catch(:break) do
|
42
|
+
@context.instance_exec(*request.splat,
|
43
|
+
**request.param,
|
44
|
+
&@callback)
|
45
|
+
end
|
46
|
+
return false unless response
|
47
|
+
|
48
|
+
if @response and [String, File, IO].include? response.class
|
49
|
+
@response.body = response
|
50
|
+
throw :finish, @response
|
51
|
+
end
|
52
|
+
throw :finish, response
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../probe"
|
4
|
+
|
5
|
+
module Landline
|
6
|
+
module Handlers
|
7
|
+
# Probe that executes callback on a GET
|
8
|
+
class GET < Landline::Handlers::Handler
|
9
|
+
METHOD = "GET"
|
10
|
+
|
11
|
+
# Method callback on successful request navigation.
|
12
|
+
# Runs block supplied with object initialization.
|
13
|
+
# Request's #splat and #param are passed to block.
|
14
|
+
#
|
15
|
+
# Callback's returned should be one of viable responses:
|
16
|
+
#
|
17
|
+
# - {Landline::Response} object
|
18
|
+
# - An array that matches Rack return form
|
19
|
+
# - An array that matches old (Rack 2.x) return form
|
20
|
+
# - A string (returned as HTML with code 200)
|
21
|
+
# - false (bounces the request to next handler)
|
22
|
+
# @param request [Landline::Request]
|
23
|
+
# @return [Boolean] true if further navigation is possible
|
24
|
+
# @raise [UncaughtThrowError] may raise if die() is called.
|
25
|
+
def process(request)
|
26
|
+
unless request.request_method.casecmp(self.class::METHOD).zero?
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
30
|
+
super(request)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Probe that executes callback on a POST
|
35
|
+
class POST < GET
|
36
|
+
METHOD = "POST"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Probe that executes callback on a HEAD
|
40
|
+
class HEAD < GET
|
41
|
+
METHOD = "HEAD"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Probe that executes callback on a PUT
|
45
|
+
class PUT < GET
|
46
|
+
METHOD = "PUT"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Probe that executes callback on a DELETE
|
50
|
+
class DELETE < GET
|
51
|
+
METHOD = "DELETE"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Probe that executes callback on a CONNECT
|
55
|
+
class CONNECT < GET
|
56
|
+
METHOD = "CONNECT"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Probe that executes callback on a OPTIONS
|
60
|
+
class OPTIONS < GET
|
61
|
+
METHOD = "OPTIONS"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Probe that executes callback on a TRACE
|
65
|
+
class TRACE < GET
|
66
|
+
METHOD = "TRACE"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Probe that executes callback on a PATCH
|
70
|
+
class PATCH < GET
|
71
|
+
METHOD = "PATCH"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../probe"
|
4
|
+
require_relative "../util/mime"
|
5
|
+
|
6
|
+
module Landline
|
7
|
+
module Handlers
|
8
|
+
# Probe that sends files from a location
|
9
|
+
class Serve < Landline::Probe
|
10
|
+
# @param path [Object]
|
11
|
+
# @param parent [Landline::Node]
|
12
|
+
# @param exec [#call]
|
13
|
+
def initialize(path, parent:)
|
14
|
+
super(path, parent: parent, filepath: true)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_accessor :response
|
18
|
+
|
19
|
+
# Method callback on successful request navigation.
|
20
|
+
# Tries to serve files matched by handler
|
21
|
+
# @param request [Landline::Request]
|
22
|
+
# @return [Boolean, Array] true if file was found
|
23
|
+
def process(request)
|
24
|
+
path = File.expand_path(request.filepath)
|
25
|
+
return unless path.start_with? @properties["path"]
|
26
|
+
|
27
|
+
filepath = path.delete_suffix("/")
|
28
|
+
|
29
|
+
[200,
|
30
|
+
{
|
31
|
+
"content-type" => Landline::MIME.get_mime_type(filepath)
|
32
|
+
},
|
33
|
+
File.open(filepath)]
|
34
|
+
rescue StandardError
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'node'
|
4
|
+
require_relative 'util/lookup'
|
5
|
+
require_relative 'dsl/methods_probe'
|
6
|
+
require_relative 'dsl/methods_common'
|
7
|
+
require_relative 'dsl/constructors_probe'
|
8
|
+
require 'pp'
|
9
|
+
|
10
|
+
module Landline
|
11
|
+
# All subclasses of Landline::Probe that respond to requests
|
12
|
+
module Handlers
|
13
|
+
autoload :Handler, "landline/probe/handler"
|
14
|
+
autoload :GET, "landline/probe/http_method"
|
15
|
+
autoload :POST, "landline/probe/http_method"
|
16
|
+
autoload :HEAD, "landline/probe/http_method"
|
17
|
+
autoload :PUT, "landline/probe/http_method"
|
18
|
+
autoload :DELETE, "landline/probe/http_method"
|
19
|
+
autoload :CONNECT, "landline/probe/http_method"
|
20
|
+
autoload :OPTIONS, "landline/probe/http_method"
|
21
|
+
autoload :TRACE, "landline/probe/http_method"
|
22
|
+
autoload :PATCH, "landline/probe/http_method"
|
23
|
+
autoload :Serve, "landline/probe/serve_handler"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Context that provides execution context for Probes.
|
27
|
+
class ProbeContext
|
28
|
+
include Landline::DSL::ProbeConstructors
|
29
|
+
include Landline::DSL::ProbeMethods
|
30
|
+
include Landline::DSL::CommonMethods
|
31
|
+
|
32
|
+
def initialize(origin)
|
33
|
+
@origin = origin
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Test probe. Also base for all "reactive" nodes.
|
38
|
+
class Probe < Landline::Node
|
39
|
+
# @param path [Object]
|
40
|
+
# @param parent [Landline::Node]
|
41
|
+
def initialize(path, parent:, **params)
|
42
|
+
super(path, parent: parent, **params)
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :properties
|
46
|
+
|
47
|
+
# Method callback on successful request navigation.
|
48
|
+
# Throws an error upon reaching the path.
|
49
|
+
# This behaviour should only be used internally.
|
50
|
+
# @param request [Landline::Request]
|
51
|
+
# @return [Boolean] true if further navigation is possible
|
52
|
+
# @raise [StandardError]
|
53
|
+
def process(request)
|
54
|
+
return reject(request) unless request.path.match?(/^\/?$/)
|
55
|
+
|
56
|
+
raise StandardError, <<~STREND
|
57
|
+
probe reached #{request.splat.inspect}, #{request.param.inspect}
|
58
|
+
#{request.pretty_inspect}
|
59
|
+
STREND
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|