octarine 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|