octarine 0.0.1 → 0.0.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.
- data/README.rdoc +1 -1
- data/lib/octarine.rb +4 -2
- data/lib/octarine/app.rb +87 -27
- data/lib/octarine/path.rb +84 -43
- data/lib/octarine/path_template.rb +163 -0
- data/lib/octarine/request.rb +47 -30
- data/lib/octarine/response.rb +2 -1
- metadata +6 -6
- data/lib/octarine/endpoint.rb +0 -125
data/README.rdoc
CHANGED
data/lib/octarine.rb
CHANGED
@@ -1,2 +1,4 @@
|
|
1
|
-
libs = %W{app
|
2
|
-
libs.map {|lib| File.expand_path(lib, __FILE__)}.each
|
1
|
+
libs = %W{app path path_template request response}
|
2
|
+
libs.map {|lib| File.expand_path("../octarine/#{lib}", __FILE__)}.each do |lib|
|
3
|
+
require lib
|
4
|
+
end
|
data/lib/octarine/app.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require "forwardable"
|
2
2
|
require "http_router"
|
3
3
|
require_relative "request"
|
4
|
-
require_relative "
|
4
|
+
require_relative "path"
|
5
5
|
|
6
6
|
module Octarine # :nodoc:
|
7
7
|
module App
|
@@ -45,22 +45,47 @@ module Octarine # :nodoc:
|
|
45
45
|
|
46
46
|
[:add, :get, :post, :delete, :put, :default].each do |method|
|
47
47
|
define_method(method) do |*args, &block|
|
48
|
+
if Hash === args.last
|
49
|
+
restrictions = Array(args.last[:restrict])
|
50
|
+
else
|
51
|
+
restrictions = []
|
52
|
+
args << {}
|
53
|
+
end
|
54
|
+
restrictions << @current_restriction if @current_restriction
|
55
|
+
args.last[:restrict] = restrictions.flatten
|
48
56
|
(@handlers ||= []) << [method, *args, block]
|
49
57
|
end
|
50
58
|
end
|
51
59
|
|
52
|
-
|
53
|
-
|
60
|
+
# :call-seq:
|
61
|
+
# restriction(name, response=401) {|request| block }
|
62
|
+
# restriction(name, response=401, proc)
|
63
|
+
#
|
64
|
+
# Create a named restriction. response will be returned if the block
|
65
|
+
# returns true, otherwise the handler subject to the restriction will be
|
66
|
+
# executed as usual.
|
67
|
+
#
|
68
|
+
def restriction(name, response=401, proc=nil, &block)
|
69
|
+
(@restrictions ||= {})[name] = [response, proc || block]
|
54
70
|
end
|
55
71
|
|
56
|
-
# :call-seq:
|
72
|
+
# :call-seq: restrict(name) { block }
|
57
73
|
#
|
74
|
+
# All handlers defined within the block will be subject to the named
|
75
|
+
# restriction.
|
76
|
+
#
|
77
|
+
def restrict(restriction)
|
78
|
+
(@current_restriction ||= []) << restriction
|
79
|
+
yield
|
80
|
+
ensure
|
81
|
+
@current_restriction.pop
|
82
|
+
end
|
83
|
+
|
58
84
|
# Set the class of the request object handed to the path handler blocks.
|
59
85
|
# Defaults to Octarine::Request.
|
60
86
|
#
|
61
|
-
|
62
|
-
|
63
|
-
end
|
87
|
+
attr_writer :request_class
|
88
|
+
alias request_class request_class=
|
64
89
|
|
65
90
|
# :call-seq: environment -> string
|
66
91
|
#
|
@@ -71,41 +96,76 @@ module Octarine # :nodoc:
|
|
71
96
|
end
|
72
97
|
|
73
98
|
def new(*args) # :nodoc:
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
99
|
+
request_class = @request_class || Octarine::Request
|
100
|
+
restrictions = @restrictions
|
101
|
+
handlers = @handlers
|
102
|
+
super.instance_eval do
|
103
|
+
@request_class ||= request_class
|
104
|
+
@router ||= HttpRouter.new
|
105
|
+
@restrictions = restrictions
|
106
|
+
handlers.each {|m,*args| register_handler(m, *args[0..-2], &args[-1])}
|
107
|
+
self
|
81
108
|
end
|
82
|
-
instance
|
83
109
|
end
|
110
|
+
|
84
111
|
end
|
85
112
|
|
86
|
-
|
113
|
+
extend Forwardable
|
114
|
+
|
115
|
+
attr_reader :router, :request_class
|
87
116
|
|
88
117
|
##
|
89
118
|
# :method: call
|
90
119
|
# :call-seq: app.call(env) -> array
|
91
120
|
#
|
92
121
|
# Rack-compatible #call method.
|
93
|
-
|
94
|
-
|
95
|
-
def_delegators :router, :url, :call
|
122
|
+
#
|
123
|
+
def_delegator :router, :call
|
96
124
|
|
97
125
|
def self.included(includer) # :nodoc:
|
98
126
|
includer.extend(ClassMethods)
|
99
127
|
end
|
100
128
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
129
|
+
private
|
130
|
+
|
131
|
+
def to_rack_response(res)
|
132
|
+
if res.respond_to?(:status) && res.respond_to?(:headers) &&
|
133
|
+
res.respond_to?(:body)
|
134
|
+
status, headers, body = res.status, res.headers, res.body
|
135
|
+
[status, headers, body.respond_to?(:each) ? body : [body].compact]
|
136
|
+
elsif res.respond_to?(:to_ary)
|
137
|
+
res.to_ary
|
138
|
+
elsif res.respond_to?(:to_str)
|
139
|
+
[200, {}, [res.to_str]]
|
140
|
+
elsif res.respond_to?(:to_i)
|
141
|
+
[res.to_i, {}, []]
|
142
|
+
else
|
143
|
+
[200, {}, res]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def register_handler(method, *args, &block)
|
148
|
+
return register_default(&block) if method == :default
|
149
|
+
restrictions = Hash === args[-1] ? Array(args[-1].delete(:restrict)) : []
|
150
|
+
restrictions.map! {|name| @restrictions[name]}
|
151
|
+
route = router.send(method, *args)
|
152
|
+
route.to do |env|
|
153
|
+
env.merge!("router.route" => route.original_path)
|
154
|
+
request = request_class.new(env)
|
155
|
+
response, = restrictions.find {|_,restr| instance_exec(request, &restr)}
|
156
|
+
if response.respond_to?(:to_proc)
|
157
|
+
response = instance_exec(request, &response)
|
158
|
+
elsif response.nil?
|
159
|
+
response = instance_exec(request, &block)
|
160
|
+
end
|
161
|
+
to_rack_response(response)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def register_default(&block)
|
166
|
+
router.default(Proc.new do |env|
|
167
|
+
to_rack_response(instance_exec(request_class.new(env), &block))
|
168
|
+
end)
|
109
169
|
end
|
110
170
|
|
111
171
|
end
|
data/lib/octarine/path.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "forwardable"
|
2
|
+
require_relative "path_template"
|
2
3
|
|
3
4
|
module Octarine # :nodoc:
|
4
5
|
|
@@ -19,75 +20,115 @@ module Octarine # :nodoc:
|
|
19
20
|
# path["history"] #=> "true"
|
20
21
|
# path.to_s #=> "/users/1234/messages/4567?history=true"
|
21
22
|
# path.path #=> "/users/1234/messages/4567"
|
22
|
-
# path.query_string #=> "history=true"
|
23
23
|
# path.to_hash #=> {:user_id=>"1234", :message_id=>"5678", "history"=>"true"}
|
24
|
-
# path.query #=> {"history"=>"true"}
|
25
24
|
#
|
26
25
|
# The following methods are available and behave as if the path was a hash:
|
27
|
-
# assoc, [], each_key, each_pair, each_value, empty?, fetch, has_key?,
|
28
|
-
# has_value?, key, key?, keys,
|
26
|
+
# assoc, [], each, each_key, each_pair, each_value, empty?, fetch, has_key?,
|
27
|
+
# has_value?, key, key?, keys, rassoc, to_a, to_hash, value?, values,
|
29
28
|
# values_at and all Enumerable methods
|
30
29
|
#
|
31
30
|
# The following methods are available and behave as if path was a string:
|
32
|
-
#
|
31
|
+
# =~, bytesize, length, size
|
33
32
|
#
|
34
33
|
class Path
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
attr_reader :query_string
|
39
|
-
# Hash of the query string.
|
40
|
-
attr_reader :query
|
34
|
+
attr_reader :params
|
35
|
+
attr_accessor :template
|
36
|
+
protected :params, :template, :template=
|
41
37
|
|
42
38
|
extend Forwardable
|
43
|
-
def_delegators :@params, :assoc, :[], :each_key, :each_pair,
|
39
|
+
def_delegators :@params, :assoc, :[], :each, :each_key, :each_pair,
|
44
40
|
:each_value, :empty?, :fetch, :has_key?, :has_value?, :key, :key?, :keys,
|
45
|
-
:
|
41
|
+
:rassoc, :to_a, :value?, :values, :values_at,
|
46
42
|
*Enumerable.public_instance_methods
|
47
43
|
def_delegator :@params, :dup, :to_hash
|
48
|
-
def_delegators :@full_path,
|
49
|
-
:to_str, :to_s
|
44
|
+
def_delegators :@full_path, :=~, :bytesize, :length, :size
|
50
45
|
|
51
|
-
# :call-seq: Path.new(
|
46
|
+
# :call-seq: Path.new(template, path_string) -> path
|
52
47
|
#
|
53
|
-
# Create a new Path instance from
|
54
|
-
# parameters as a hash, and a string of the query string.
|
48
|
+
# Create a new Path instance from a path template and a string of the path.
|
55
49
|
#
|
56
|
-
def initialize(
|
57
|
-
@
|
58
|
-
params
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
def initialize(template, path)
|
51
|
+
@template = Octarine::PathTemplate.new(template)
|
52
|
+
@params = @template.recognize(path)
|
53
|
+
|
54
|
+
@params.each do |key, value|
|
55
|
+
next unless Symbol === key
|
56
|
+
self.class.class_eval {define_method(key) {@params[key]}}
|
62
57
|
end
|
63
|
-
@query_string = query_string
|
64
|
-
@query = parse_query(@query_string)
|
65
|
-
@full_path = @path.dup
|
66
|
-
@full_path << "?#{@query_string}" if @query_string && !@query_string.empty?
|
67
|
-
@params = params.merge(@query)
|
68
58
|
end
|
69
59
|
|
70
|
-
# :call-seq: path.
|
60
|
+
# :call-seq: path.without(part) -> new_path
|
71
61
|
#
|
72
|
-
#
|
62
|
+
# Return a new path without part.
|
73
63
|
#
|
74
|
-
|
75
|
-
|
76
|
-
|
64
|
+
# path = Path.new("/users/:id", "/users/1")
|
65
|
+
# path.without("users").to_s #=> "/1"
|
66
|
+
#
|
67
|
+
# path = Path.new("/users/:id", "/users/1")
|
68
|
+
# path.without(":id").to_s #=> "/users"
|
69
|
+
#
|
70
|
+
def without(part)
|
71
|
+
dup.tap do |cp|
|
72
|
+
cp.template = @template.without(part)
|
73
|
+
cp.params.delete(part)
|
74
|
+
end
|
77
75
|
end
|
78
76
|
|
79
|
-
|
77
|
+
# :call-seq: path.merge(hash) -> new_path
|
78
|
+
#
|
79
|
+
# Returns a new path with contents of hash merged in to the path parameters.
|
80
|
+
#
|
81
|
+
# path = Path.new("/users/:id/favourites", "/users/1/favourites?limit=10")
|
82
|
+
# new_path = path.merge(:id => 2, "offset" => 20)
|
83
|
+
# new_path.to_s #=> "users/2/favourites?limit=10&offset=20"
|
84
|
+
#
|
85
|
+
def merge(other)
|
86
|
+
dup.tap {|cp| cp.params.merge!(other)}
|
87
|
+
end
|
80
88
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
89
|
+
# :call-seq: path + string -> new_path
|
90
|
+
#
|
91
|
+
# Returns a new path with string appended.
|
92
|
+
#
|
93
|
+
# path = Path.new("/users/:id", "/users/1")
|
94
|
+
# new_path = (path + "favourites/:favourite_id").merge(:favourite_id => 2)
|
95
|
+
# new_path.to_s #=> "/users/1/favourites/2"
|
96
|
+
#
|
97
|
+
def +(part)
|
98
|
+
dup.tap {|cp| cp.template = @template + part}
|
99
|
+
end
|
100
|
+
|
101
|
+
# :call-seq: path.to_s -> string
|
102
|
+
#
|
103
|
+
# Returns the path as a string.
|
104
|
+
#
|
105
|
+
def to_s
|
106
|
+
@template.apply(@params)
|
107
|
+
end
|
108
|
+
alias to_str to_s
|
109
|
+
|
110
|
+
# :call-seq: path == other -> bool
|
111
|
+
#
|
112
|
+
# Returns true if other is equal to path, false otherwise.
|
113
|
+
#
|
114
|
+
def ==(other)
|
115
|
+
self.class === other && to_s == other.to_s
|
116
|
+
end
|
117
|
+
|
118
|
+
# :call-seq: path === other -> bool
|
119
|
+
#
|
120
|
+
# Returns true if other as a string is equal to path as a string, false
|
121
|
+
# otherwise.
|
122
|
+
#
|
123
|
+
def ===(other)
|
124
|
+
to_s === other.to_s
|
87
125
|
end
|
88
126
|
|
89
|
-
def
|
90
|
-
|
127
|
+
def initialize_copy(source) # :nodoc:
|
128
|
+
super
|
129
|
+
@template = @template.dup
|
130
|
+
@params = @params.dup
|
91
131
|
end
|
132
|
+
|
92
133
|
end
|
93
134
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module Octarine
|
2
|
+
class PathTemplate
|
3
|
+
BadTemplateError = Class.new(ArgumentError)
|
4
|
+
|
5
|
+
module StringExtention
|
6
|
+
def /(arg)
|
7
|
+
PathTemplate.new(self).apply(*arg.respond_to?(:to_ary) ? arg : [arg])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :parts
|
12
|
+
protected :parts
|
13
|
+
|
14
|
+
def initialize(string)
|
15
|
+
@parts = parse(string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def apply(*args)
|
19
|
+
params = args.last.respond_to?(:each) ? args.pop.dup : {}
|
20
|
+
|
21
|
+
@parts.find {|type, val| params[val] ||= args.pop if type == :format}
|
22
|
+
@parts.select do |type, value|
|
23
|
+
params[value] ||= args.shift if type == :variable && !args.empty?
|
24
|
+
end
|
25
|
+
@parts.find {|type, val| params[val] ||= args if type == :glob}
|
26
|
+
|
27
|
+
format = nil
|
28
|
+
path = []
|
29
|
+
@parts.each do |type, value|
|
30
|
+
case type
|
31
|
+
when :variable
|
32
|
+
path << params.delete(value)
|
33
|
+
when :glob
|
34
|
+
path << params.delete(value).join("/")
|
35
|
+
when :format
|
36
|
+
format = params.delete(value)
|
37
|
+
else
|
38
|
+
path << value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
out = path == [nil] ? "/" : path.join("/")
|
43
|
+
out << ".#{format}" if format
|
44
|
+
out << query(params) if !params.empty?
|
45
|
+
out
|
46
|
+
end
|
47
|
+
|
48
|
+
def recognize(string)
|
49
|
+
other_parts = parse(string).each
|
50
|
+
params = {}
|
51
|
+
|
52
|
+
@parts.each do |type, value|
|
53
|
+
other_type, other_value = (other_parts.next rescue nil)
|
54
|
+
case type
|
55
|
+
when :variable
|
56
|
+
return unless other_value
|
57
|
+
params[value] = other_value
|
58
|
+
when :glob
|
59
|
+
params[value] = other_value ? [other_value] : []
|
60
|
+
while (other_parts.peek.first == :string rescue nil)
|
61
|
+
other_type, other_value = other_parts.next
|
62
|
+
params[value] << other_value
|
63
|
+
end
|
64
|
+
when :format
|
65
|
+
return nil unless type == other_type
|
66
|
+
params[value] = other_value.to_s
|
67
|
+
when :string
|
68
|
+
return nil unless type == other_type && value == other_value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
other_type, other_value = (other_parts.next rescue nil)
|
72
|
+
return nil unless other_type == :query_string || other_type.nil?
|
73
|
+
if other_value
|
74
|
+
query = parse_query(other_value)
|
75
|
+
params.merge!(query)
|
76
|
+
end
|
77
|
+
params
|
78
|
+
end
|
79
|
+
alias === recognize
|
80
|
+
|
81
|
+
def +(string)
|
82
|
+
parts = parse(string)
|
83
|
+
parts.shift if parts.first == [:leading_joiner, nil]
|
84
|
+
dup.tap {|cp| cp.parts.concat(parts)}
|
85
|
+
end
|
86
|
+
|
87
|
+
def without(string)
|
88
|
+
part = parse(string).first
|
89
|
+
dup.tap {|cp| cp.parts.reject! {|pt| pt == part}}
|
90
|
+
end
|
91
|
+
alias - without
|
92
|
+
|
93
|
+
def ==(other)
|
94
|
+
self.class === other && parts == other.parts
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize_copy(source)
|
98
|
+
super
|
99
|
+
@parts = @parts.dup
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def query(params)
|
105
|
+
return nil if !params || params.empty?
|
106
|
+
"?" << params.map {|kv| kv.join("=")}.join("&")
|
107
|
+
end
|
108
|
+
|
109
|
+
def tokenize(string)
|
110
|
+
string.scan(%r{([/:*.?]|[^/:*.?]+)}).flatten
|
111
|
+
end
|
112
|
+
|
113
|
+
def lex(tokens)
|
114
|
+
enum = tokens.each
|
115
|
+
parts = []
|
116
|
+
seen_glob = false
|
117
|
+
seen_format = false
|
118
|
+
|
119
|
+
parts << [:leading_joiner, nil] if enum.peek == "/"
|
120
|
+
while part = enum.next
|
121
|
+
raise BadTemplateError.new(".format must be last") if seen_format
|
122
|
+
|
123
|
+
case part
|
124
|
+
when "/"
|
125
|
+
when ":"
|
126
|
+
raise BadTemplateError.new(":variable cannot follow *glob") if seen_glob
|
127
|
+
parts << [:variable, enum.next.to_sym]
|
128
|
+
when "*"
|
129
|
+
raise BadTemplateError.new("multiple *glob not allowed") if seen_glob
|
130
|
+
seen_glob = true
|
131
|
+
parts << [:glob, enum.next.to_sym]
|
132
|
+
when "."
|
133
|
+
seen_format = true
|
134
|
+
parts << [:format, enum.next.to_sym]
|
135
|
+
when "?"
|
136
|
+
parts << [:query_string, enum.next.freeze]
|
137
|
+
else
|
138
|
+
parts << [:string, part.freeze]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
rescue StopIteration
|
142
|
+
parts
|
143
|
+
end
|
144
|
+
|
145
|
+
def parse(string)
|
146
|
+
string = Symbol === string ? ":#{string}" : string.to_s
|
147
|
+
lex(tokenize(string))
|
148
|
+
end
|
149
|
+
|
150
|
+
def parse_query(string)
|
151
|
+
string.split("&").each_with_object({}) do |key_value, out|
|
152
|
+
key, value = key_value.split("=")
|
153
|
+
key, value = url_decode(key), url_decode(value)
|
154
|
+
out[key] = out.key?(key) ? [*out[key]].push(value) : value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def url_decode(str)
|
159
|
+
str.tr("+", " ").gsub(/(%[0-9a-f]{2})+/i) {|m| [m.delete("%")].pack("H*")}
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
data/lib/octarine/request.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative "response"
|
2
|
-
require_relative "
|
2
|
+
require_relative "path"
|
3
|
+
require_relative "simple_http"
|
3
4
|
|
4
5
|
module Octarine # :nodoc:
|
5
6
|
class Request
|
@@ -23,18 +24,22 @@ module Octarine # :nodoc:
|
|
23
24
|
def initialize(env)
|
24
25
|
@env = env
|
25
26
|
env.delete("router")
|
26
|
-
|
27
|
+
env.delete("router.params")
|
28
|
+
template = env.delete("router.route")
|
27
29
|
@method = env["REQUEST_METHOD"]
|
28
30
|
@host = env["SERVER_NAME"]
|
29
31
|
@port = env["SERVER_PORT"]
|
30
|
-
|
31
|
-
|
32
|
+
path = env["SCRIPT_NAME"] || ""
|
33
|
+
path << env["PATH_INFO"] unless env["PATH_INFO"].empty?
|
34
|
+
full_path = path.dup
|
35
|
+
full_path << "?" << env["QUERY_STRING"] unless env["QUERY_STRING"].empty?
|
36
|
+
@path = Path.new(template || path, full_path)
|
32
37
|
@input = env["rack.input"]
|
33
38
|
end
|
34
39
|
|
35
40
|
# :call-seq: request[header_name] -> header_value
|
36
41
|
#
|
37
|
-
# Retrieve
|
42
|
+
# Retrieve header.
|
38
43
|
# request["Content-Length"] #=> "123"
|
39
44
|
# request["Content-Type"] #=> "application/json"
|
40
45
|
#
|
@@ -43,47 +48,59 @@ module Octarine # :nodoc:
|
|
43
48
|
unless upper_key == "CONTENT_LENGTH" || upper_key == "CONTENT_TYPE"
|
44
49
|
upper_key[0,0] = "HTTP_"
|
45
50
|
end
|
46
|
-
@env[
|
51
|
+
@env[upper_key]
|
47
52
|
end
|
48
53
|
|
49
|
-
# :call-seq: request.
|
50
|
-
# request.
|
51
|
-
# request.to(endpoint, path, input) -> response
|
54
|
+
# :call-seq: request.header -> hash
|
55
|
+
# request.headers -> hash
|
52
56
|
#
|
53
|
-
#
|
57
|
+
# Get header as a hash.
|
58
|
+
# request.header
|
59
|
+
# #=> {"content-length" => "123", "content-type" => "application/json"}
|
54
60
|
#
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
-
headers = res.headers
|
63
|
-
headers.delete("transfer-encoding")
|
64
|
-
headers.delete("content-length")
|
65
|
-
Octarine::Response.new(res.body, headers, res.status)
|
61
|
+
def header
|
62
|
+
Hash[@env.select do |k,v|
|
63
|
+
k =~ /^HTTP_[^(VERSION)]/ || %W{CONTENT_LENGTH CONTENT_TYPE}.include?(k)
|
64
|
+
end.map do |key, value|
|
65
|
+
[key.sub(/HTTP_/, "").tr("_", "-").downcase, value]
|
66
|
+
end]
|
66
67
|
end
|
68
|
+
alias headers header
|
67
69
|
|
68
|
-
# :call-seq: request.
|
70
|
+
# :call-seq: request.to(host) -> response
|
71
|
+
# request.to(host, path) -> response
|
72
|
+
# request.to(host, path, input) -> response
|
69
73
|
#
|
70
|
-
#
|
74
|
+
# Re-issue request to new host/path.
|
71
75
|
#
|
72
|
-
def
|
73
|
-
|
76
|
+
def to(client, to_path=path, to_input=input)
|
77
|
+
client = SimpleHTTP.new(client.to_str) if client.respond_to?(:to_str)
|
78
|
+
res = if %W{POST PUT}.include?(method)
|
79
|
+
client.__send__(method.downcase, to_path, to_input, header_for_rerequest)
|
80
|
+
else
|
81
|
+
client.__send__(method.downcase, to_path, header_for_rerequest)
|
82
|
+
end
|
83
|
+
response_headers = res.headers
|
84
|
+
response_headers.delete("transfer-encoding")
|
85
|
+
response_headers.delete("content-length")
|
86
|
+
Octarine::Response.new(res.body, response_headers, res.status)
|
74
87
|
end
|
75
88
|
|
76
89
|
def to_s # :nodoc:
|
77
|
-
header = Hash[@env.select do |k,v|
|
78
|
-
k =~ /^HTTP_[^(VERSION)]/ || %W{CONTENT_LENGTH CONTENT_TYPE}.include?(k)
|
79
|
-
end.map do |key, value|
|
80
|
-
[key.sub(/HTTP_/, "").split(/_/).map(&:capitalize).join("-"), value]
|
81
|
-
end]
|
82
90
|
version = " " + @env["HTTP_VERSION"] if @env.key?("HTTP_VERSION")
|
83
91
|
"#{method} #{path}#{version}\r\n" << header.map do |key, value|
|
92
|
+
key = key.split(/-/).map(&:capitalize).join("-")
|
84
93
|
"#{key}: #{value}"
|
85
94
|
end.join("\r\n") << "\r\n\r\n"
|
86
95
|
end
|
87
96
|
|
97
|
+
private
|
98
|
+
|
99
|
+
def header_for_rerequest
|
100
|
+
head = header
|
101
|
+
head.delete("host")
|
102
|
+
head
|
103
|
+
end
|
104
|
+
|
88
105
|
end
|
89
106
|
end
|
data/lib/octarine/response.rb
CHANGED
@@ -73,7 +73,7 @@ module Octarine # :nodoc:
|
|
73
73
|
when 2
|
74
74
|
@body = value
|
75
75
|
else
|
76
|
-
raise ArgumentError("Unexpected key #{key}")
|
76
|
+
raise ArgumentError.new("Unexpected key #{key}")
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
@@ -91,6 +91,7 @@ module Octarine # :nodoc:
|
|
91
91
|
|
92
92
|
def apply(object, path=nil, &block)
|
93
93
|
if object.respond_to?(:to_ary)
|
94
|
+
path = nil if path == "."
|
94
95
|
return object.to_ary.map {|obj| apply(obj, path, &block)}
|
95
96
|
end
|
96
97
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: octarine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-06-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: http_router
|
16
|
-
requirement: &
|
16
|
+
requirement: &2152395640 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2152395640
|
25
25
|
description: Sinatra-like DSL for writing a HTTP routing proxy.
|
26
26
|
email: mat@sourcetagsandcodes.com
|
27
27
|
executables: []
|
@@ -30,8 +30,8 @@ extra_rdoc_files:
|
|
30
30
|
- README.rdoc
|
31
31
|
files:
|
32
32
|
- lib/octarine/app.rb
|
33
|
-
- lib/octarine/endpoint.rb
|
34
33
|
- lib/octarine/path.rb
|
34
|
+
- lib/octarine/path_template.rb
|
35
35
|
- lib/octarine/request.rb
|
36
36
|
- lib/octarine/response.rb
|
37
37
|
- lib/octarine/simple_http.rb
|
@@ -61,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
61
|
version: '0'
|
62
62
|
requirements: []
|
63
63
|
rubyforge_project:
|
64
|
-
rubygems_version: 1.8.
|
64
|
+
rubygems_version: 1.8.11
|
65
65
|
signing_key:
|
66
66
|
specification_version: 3
|
67
67
|
summary: HTTP routing proxy DSL
|
data/lib/octarine/endpoint.rb
DELETED
@@ -1,125 +0,0 @@
|
|
1
|
-
require "forwardable"
|
2
|
-
require_relative "simple_http"
|
3
|
-
require_relative "response"
|
4
|
-
|
5
|
-
module Octarine # :nodoc:
|
6
|
-
|
7
|
-
# Octarine::Endpoint is a wrapper round a http client that presents a DSL for
|
8
|
-
# generating paths and making requests.
|
9
|
-
#
|
10
|
-
# Examples:
|
11
|
-
#
|
12
|
-
# # GET users/123/messages/567
|
13
|
-
# endpoint.users(123).messages[567]
|
14
|
-
#
|
15
|
-
# # GET 123/details/address
|
16
|
-
# endpoint.(123).details["address"]
|
17
|
-
#
|
18
|
-
# # POST users/123/messages
|
19
|
-
# endpoint.users(123).post("messages", body)
|
20
|
-
#
|
21
|
-
class Endpoint
|
22
|
-
attr_accessor :path
|
23
|
-
protected :path, :path=
|
24
|
-
attr_reader :client
|
25
|
-
private :client
|
26
|
-
|
27
|
-
extend Forwardable
|
28
|
-
def_delegators :@client, :host, :port
|
29
|
-
|
30
|
-
# :call-seq: Endpoint.new(host) -> endpoint
|
31
|
-
# Endpoint.new(client) -> endpoint
|
32
|
-
#
|
33
|
-
# Create a new Endpoint instance, either with a string of host:port, or a
|
34
|
-
# http client instance API compatible with Octarine::SimpleHTTP
|
35
|
-
#
|
36
|
-
def initialize(host_or_client)
|
37
|
-
if host_or_client.respond_to?(:get)
|
38
|
-
@client = host_or_client
|
39
|
-
else
|
40
|
-
@client = Octarine::SimpleHTTP.new(host_or_client)
|
41
|
-
end
|
42
|
-
@path = ""
|
43
|
-
end
|
44
|
-
|
45
|
-
# :call-seq: endpoint.call(obj) -> new_endpoint
|
46
|
-
# endpoint.(obj) -> new_endpoint
|
47
|
-
#
|
48
|
-
# Returns a new endpoint with the string respresentation of obj added to the
|
49
|
-
# path. E.g. `endpoint.(123)` will return an endpoint with `/123` appended
|
50
|
-
# to the path.
|
51
|
-
#
|
52
|
-
def call(id)
|
53
|
-
method_missing(id)
|
54
|
-
end
|
55
|
-
|
56
|
-
# :call-seq: endpoint.method_missing(name, id) -> new_endpoint
|
57
|
-
# endpoint.method_missing(name) -> new_endpoint
|
58
|
-
# endpoint.anything(id) -> new_endpoint
|
59
|
-
# endpoint.anything -> new_endpoint
|
60
|
-
#
|
61
|
-
# Implements the path generation DSL.
|
62
|
-
# endpoint.foo(1).bar #=> new endpoint with path set to /foo/1/bar
|
63
|
-
#
|
64
|
-
def method_missing(name, *args)
|
65
|
-
super unless args.length <= 1 && !block_given?
|
66
|
-
copy = dup
|
67
|
-
copy.path = join(copy.path, name.to_s, *args)
|
68
|
-
copy
|
69
|
-
end
|
70
|
-
|
71
|
-
# :call-seq: endpoint.head(id, headers={}) -> response
|
72
|
-
#
|
73
|
-
# Make a HEAD request to endpoint's path plus `/id`.
|
74
|
-
#
|
75
|
-
def head(id, headers={})
|
76
|
-
response = client.head(join(path, id.to_s), headers)
|
77
|
-
Octarine::Response.new(response.body, response.headers, response.status)
|
78
|
-
end
|
79
|
-
|
80
|
-
# :call-seq: endpoint.head(id, headers={}) -> response
|
81
|
-
# endpoint[id] -> response
|
82
|
-
#
|
83
|
-
# Make a GET request to endpoint's path plus `/id`.
|
84
|
-
#
|
85
|
-
def get(id, headers={})
|
86
|
-
response = client.get(join(path, id.to_s), headers)
|
87
|
-
Octarine::Response.new(response.body, response.headers, response.status)
|
88
|
-
end
|
89
|
-
alias [] get
|
90
|
-
|
91
|
-
# :call-seq: endpoint.head(id, body=nil, headers={}) -> response
|
92
|
-
#
|
93
|
-
# Make a POST request to endpoint's path plus `/id`.
|
94
|
-
#
|
95
|
-
def post(id, body=nil, headers={})
|
96
|
-
response = client.post(join(path, id.to_s), body, headers)
|
97
|
-
Octarine::Response.new(response.body, response.headers, response.status)
|
98
|
-
end
|
99
|
-
|
100
|
-
# :call-seq: endpoint.head(id, body=nil, headers={}) -> response
|
101
|
-
#
|
102
|
-
# Make a PUT request to endpoint's path plus `/id`.
|
103
|
-
#
|
104
|
-
def put(id, body=nil, headers={})
|
105
|
-
response = client.put(join(path, id.to_s), body, headers)
|
106
|
-
Octarine::Response.new(response.body, response.headers, response.status)
|
107
|
-
end
|
108
|
-
|
109
|
-
# :call-seq: endpoint.head(id, headers={}) -> response
|
110
|
-
#
|
111
|
-
# Make a DELETE request to endpoint's path plus `/id`.
|
112
|
-
#
|
113
|
-
def delete(id, headers={})
|
114
|
-
response = client.delete(join(path, id.to_s), headers)
|
115
|
-
Octarine::Response.new(response.body, response.headers, response.status)
|
116
|
-
end
|
117
|
-
|
118
|
-
private
|
119
|
-
|
120
|
-
def join(*paths)
|
121
|
-
paths.map {|path| path.gsub(%r{(^/|/$)}, "")}.join("/")
|
122
|
-
end
|
123
|
-
|
124
|
-
end
|
125
|
-
end
|