octarine 0.0.1
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 +44 -0
- data/lib/octarine.rb +2 -0
- data/lib/octarine/app.rb +112 -0
- data/lib/octarine/endpoint.rb +125 -0
- data/lib/octarine/path.rb +93 -0
- data/lib/octarine/request.rb +89 -0
- data/lib/octarine/response.rb +109 -0
- data/lib/octarine/simple_http.rb +82 -0
- metadata +68 -0
data/README.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= Octarine
|
2
|
+
|
3
|
+
Octarine is a Sinatra-like DSL for writing a HTTP routing proxy, i.e. a service
|
4
|
+
that accepts incoming requests, re-issues them to the appropriate endpoint, and
|
5
|
+
relays the output. It also includes support for modifying the output, or
|
6
|
+
transforming one external request in to multiple internal requests.
|
7
|
+
|
8
|
+
== Installation
|
9
|
+
|
10
|
+
gem install octarine
|
11
|
+
|
12
|
+
== Usage
|
13
|
+
|
14
|
+
class Router
|
15
|
+
include Octarine::App
|
16
|
+
|
17
|
+
get "/gp*rest" do |request|
|
18
|
+
request.to("globalpersonals.co.uk", request.path.lchomp("/gp"))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
== Licence
|
23
|
+
|
24
|
+
(The MIT License)
|
25
|
+
|
26
|
+
Copyright (c) 2011 Global Personals, Ltd.
|
27
|
+
|
28
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
29
|
+
of this software and associated documentation files (the "Software"), to deal
|
30
|
+
in the Software without restriction, including without limitation the rights
|
31
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
32
|
+
copies of the Software, and to permit persons to whom the Software is
|
33
|
+
furnished to do so, subject to the following conditions:
|
34
|
+
|
35
|
+
The above copyright notice and this permission notice shall be included in
|
36
|
+
all copies or substantial portions of the Software.
|
37
|
+
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
39
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
40
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
41
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
42
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
43
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
44
|
+
THE SOFTWARE.
|
data/lib/octarine.rb
ADDED
data/lib/octarine/app.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "http_router"
|
3
|
+
require_relative "request"
|
4
|
+
require_relative "endpoint"
|
5
|
+
|
6
|
+
module Octarine # :nodoc:
|
7
|
+
module App
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
##
|
11
|
+
# :method: add
|
12
|
+
# :call-seq: add(path, opts={}) {|request| block }
|
13
|
+
#
|
14
|
+
# Adds block as a handler for path.
|
15
|
+
|
16
|
+
##
|
17
|
+
# :method: get
|
18
|
+
# :call-seq: get(path, opts={}) {|request| block }
|
19
|
+
#
|
20
|
+
# Adds block as a handler for path when the request method is GET.
|
21
|
+
|
22
|
+
##
|
23
|
+
# :method: post
|
24
|
+
# :call-seq: post(path, opts={}) {|request| block }
|
25
|
+
#
|
26
|
+
# Adds block as a handler for path when the request method is POST.
|
27
|
+
|
28
|
+
##
|
29
|
+
# :method: delete
|
30
|
+
# :call-seq: delete(path, opts={}) {|request| block }
|
31
|
+
#
|
32
|
+
# Adds block as a handler for path when the request method is DELETE.
|
33
|
+
|
34
|
+
##
|
35
|
+
# :method: put
|
36
|
+
# :call-seq: put(path, opts={}) {|request| block }
|
37
|
+
#
|
38
|
+
# Adds block as a handler for path when the request method is PUT.
|
39
|
+
|
40
|
+
##
|
41
|
+
# :method: default
|
42
|
+
# :call-seq: default {|request| block }
|
43
|
+
#
|
44
|
+
# Adds block as a handler for when no path is matched.
|
45
|
+
|
46
|
+
[:add, :get, :post, :delete, :put, :default].each do |method|
|
47
|
+
define_method(method) do |*args, &block|
|
48
|
+
(@handlers ||= []) << [method, *args, block]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_route(route) # :nodoc:
|
53
|
+
(@handlers ||= []) << [__method__, route, nil]
|
54
|
+
end
|
55
|
+
|
56
|
+
# :call-seq: request_class(klass)
|
57
|
+
#
|
58
|
+
# Set the class of the request object handed to the path handler blocks.
|
59
|
+
# Defaults to Octarine::Request.
|
60
|
+
#
|
61
|
+
def request_class(klass=nil)
|
62
|
+
klass ? @request_class = klass : @request_class || Octarine::Request
|
63
|
+
end
|
64
|
+
|
65
|
+
# :call-seq: environment -> string
|
66
|
+
#
|
67
|
+
# Returns the current enviroment. Defaults to "development".
|
68
|
+
#
|
69
|
+
def environment
|
70
|
+
ENV["RACK_ENV"] || "development"
|
71
|
+
end
|
72
|
+
|
73
|
+
def new(*args) # :nodoc:
|
74
|
+
instance = super
|
75
|
+
instance.router = HttpRouter.new
|
76
|
+
@handlers.each do |method, *args|
|
77
|
+
block = args.pop
|
78
|
+
instance.router.send(method, *args) do |env|
|
79
|
+
instance.instance_exec(request_class.new(env), &block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
instance
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_accessor :router # :nodoc:
|
87
|
+
|
88
|
+
##
|
89
|
+
# :method: call
|
90
|
+
# :call-seq: app.call(env) -> array
|
91
|
+
#
|
92
|
+
# Rack-compatible #call method.
|
93
|
+
|
94
|
+
extend Forwardable
|
95
|
+
def_delegators :router, :url, :call
|
96
|
+
|
97
|
+
def self.included(includer) # :nodoc:
|
98
|
+
includer.extend(ClassMethods)
|
99
|
+
end
|
100
|
+
|
101
|
+
# :call-seq: app.endpoint(string) -> endpoint
|
102
|
+
# app.endpoint(client) -> endpoint
|
103
|
+
#
|
104
|
+
# Create an Octarine::Endpoint with either a string of the host:port or
|
105
|
+
# a client instance API compatible with Octarine::SimpleHTTP.
|
106
|
+
#
|
107
|
+
def endpoint(host_or_client)
|
108
|
+
Octarine::Endpoint.new(host_or_client)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,125 @@
|
|
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
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Octarine # :nodoc:
|
4
|
+
|
5
|
+
# Octarine::Path represents the path and query string portion of a url.
|
6
|
+
#
|
7
|
+
# You are unlikely to need to create a Path instance yourself, insted you
|
8
|
+
# will usually obtain one from Request#path.
|
9
|
+
#
|
10
|
+
# If you set a handler like so:
|
11
|
+
# get "/users/:user_id/messages/:message_id" {|request| ... }
|
12
|
+
# and a request is made like:
|
13
|
+
# GET /users/1234/messages/4567?history=true
|
14
|
+
# then `request.path` will behave as below:
|
15
|
+
# path.user_id #=> "1234"
|
16
|
+
# path[:user_id] #=> "1234"
|
17
|
+
# path.message_id #=> "5678"
|
18
|
+
# path[:message_id] #=> "5678"
|
19
|
+
# path["history"] #=> "true"
|
20
|
+
# path.to_s #=> "/users/1234/messages/4567?history=true"
|
21
|
+
# path.path #=> "/users/1234/messages/4567"
|
22
|
+
# path.query_string #=> "history=true"
|
23
|
+
# path.to_hash #=> {:user_id=>"1234", :message_id=>"5678", "history"=>"true"}
|
24
|
+
# path.query #=> {"history"=>"true"}
|
25
|
+
#
|
26
|
+
# 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, merge, rassoc, to_a, to_hash, value?, values,
|
29
|
+
# values_at and all Enumerable methods
|
30
|
+
#
|
31
|
+
# The following methods are available and behave as if path was a string:
|
32
|
+
# +, =~, bytesize, gsub, length, size, sub, to_str, to_s
|
33
|
+
#
|
34
|
+
class Path
|
35
|
+
# String of the path, without the query string.
|
36
|
+
attr_reader :path
|
37
|
+
# String of the query string.
|
38
|
+
attr_reader :query_string
|
39
|
+
# Hash of the query string.
|
40
|
+
attr_reader :query
|
41
|
+
|
42
|
+
extend Forwardable
|
43
|
+
def_delegators :@params, :assoc, :[], :each_key, :each_pair,
|
44
|
+
:each_value, :empty?, :fetch, :has_key?, :has_value?, :key, :key?, :keys,
|
45
|
+
:merge, :rassoc, :to_a, :value?, :values, :values_at,
|
46
|
+
*Enumerable.public_instance_methods
|
47
|
+
def_delegator :@params, :dup, :to_hash
|
48
|
+
def_delegators :@full_path, :+, :=~, :bytesize, :gsub, :length, :size, :sub,
|
49
|
+
:to_str, :to_s
|
50
|
+
|
51
|
+
# :call-seq: Path.new(string, path_params, query_string) -> path
|
52
|
+
#
|
53
|
+
# Create a new Path instance from the a string or the path, the path
|
54
|
+
# parameters as a hash, and a string of the query string.
|
55
|
+
#
|
56
|
+
def initialize(path, params, query_string)
|
57
|
+
@path = path
|
58
|
+
params.each do |key, value|
|
59
|
+
self.class.class_eval do
|
60
|
+
define_method(key) {@params[key]}
|
61
|
+
end
|
62
|
+
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
|
+
end
|
69
|
+
|
70
|
+
# :call-seq: path.lchomp(separator=$/) -> string
|
71
|
+
#
|
72
|
+
# Like String#chomp, but removes seperator from the left of the path.
|
73
|
+
#
|
74
|
+
def lchomp(separator=$/)
|
75
|
+
string = to_s
|
76
|
+
string.start_with?(separator) ? string[separator.length..-1] : string
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def parse_query(string)
|
82
|
+
string.split("&").each_with_object({}) do |key_value, out|
|
83
|
+
key, value = key_value.split("=")
|
84
|
+
key, value = url_decode(key), url_decode(value)
|
85
|
+
out[key] = out.key?(key) ? [*out[key]].push(value) : value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def url_decode(str)
|
90
|
+
str.tr("+", " ").gsub(/(%[0-9a-f]{2})+/i) {|m| [m.delete("%")].pack("H*")}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require_relative "response"
|
2
|
+
require_relative "endpoint"
|
3
|
+
|
4
|
+
module Octarine # :nodoc:
|
5
|
+
class Request
|
6
|
+
# The Rack enviroment hash
|
7
|
+
attr_reader :env
|
8
|
+
# The request method, e.g. "GET", "POST"
|
9
|
+
attr_reader :method
|
10
|
+
# The host name the request was made to
|
11
|
+
attr_reader :host
|
12
|
+
# The port the request was made to
|
13
|
+
attr_reader :port
|
14
|
+
# An Octarine::Path representing the path request was made to
|
15
|
+
attr_reader :path
|
16
|
+
# The request POST/PUT body
|
17
|
+
attr_reader :input
|
18
|
+
|
19
|
+
# :call-seq: Request.new(env) -> request
|
20
|
+
#
|
21
|
+
# Create a Request instance with a Rack enviroment hash.
|
22
|
+
#
|
23
|
+
def initialize(env)
|
24
|
+
@env = env
|
25
|
+
env.delete("router")
|
26
|
+
path_params = env.delete("router.params")
|
27
|
+
@method = env["REQUEST_METHOD"]
|
28
|
+
@host = env["SERVER_NAME"]
|
29
|
+
@port = env["SERVER_PORT"]
|
30
|
+
@path = Path.new(env["SCRIPT_NAME"] || env["PATH_INFO"], path_params,
|
31
|
+
env["QUERY_STRING"])
|
32
|
+
@input = env["rack.input"]
|
33
|
+
end
|
34
|
+
|
35
|
+
# :call-seq: request[header_name] -> header_value
|
36
|
+
#
|
37
|
+
# Retrieve headers.
|
38
|
+
# request["Content-Length"] #=> "123"
|
39
|
+
# request["Content-Type"] #=> "application/json"
|
40
|
+
#
|
41
|
+
def [](key)
|
42
|
+
upper_key = key.to_s.tr("a-z-", "A-Z_")
|
43
|
+
unless upper_key == "CONTENT_LENGTH" || upper_key == "CONTENT_TYPE"
|
44
|
+
upper_key[0,0] = "HTTP_"
|
45
|
+
end
|
46
|
+
@env[key]
|
47
|
+
end
|
48
|
+
|
49
|
+
# :call-seq: request.to(endpoint) -> response
|
50
|
+
# request.to(endpoint, path) -> response
|
51
|
+
# request.to(endpoint, path, input) -> response
|
52
|
+
#
|
53
|
+
# Re-issue request to new host/path.
|
54
|
+
#
|
55
|
+
def to(endpoint=Octarine::Endpoint.new(host), to_path=path, to_input=input)
|
56
|
+
res = if %W{POST PUT}.include?(method)
|
57
|
+
header = {"content-type" => "application/json"}
|
58
|
+
endpoint.send(method.downcase, to_path, to_input, header)
|
59
|
+
else
|
60
|
+
endpoint.send(method.downcase, to_path)
|
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)
|
66
|
+
end
|
67
|
+
|
68
|
+
# :call-seq: request.redirect(path) -> response
|
69
|
+
#
|
70
|
+
# Issue redirect to path.
|
71
|
+
#
|
72
|
+
def redirect(path)
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
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
|
+
version = " " + @env["HTTP_VERSION"] if @env.key?("HTTP_VERSION")
|
83
|
+
"#{method} #{path}#{version}\r\n" << header.map do |key, value|
|
84
|
+
"#{key}: #{value}"
|
85
|
+
end.join("\r\n") << "\r\n\r\n"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Octarine # :nodoc:
|
4
|
+
class Response
|
5
|
+
attr_accessor :body, :status, :header
|
6
|
+
alias headers header
|
7
|
+
alias headers= header=
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators :to_ary, :first, :last
|
11
|
+
|
12
|
+
# :call-seq: Response.new(body) -> response
|
13
|
+
# Response.new(body, header) -> response
|
14
|
+
# Response.new(body, header, status) -> response
|
15
|
+
#
|
16
|
+
# Create a new Response instance.
|
17
|
+
#
|
18
|
+
def initialize(body=[], header={}, status=200)
|
19
|
+
status, header = header, status if header.respond_to?(:to_i)
|
20
|
+
@body = body
|
21
|
+
@header = header
|
22
|
+
@status = status.to_i
|
23
|
+
header["content-type"] ||= "text/html" unless [204, 304].include?(@status)
|
24
|
+
end
|
25
|
+
|
26
|
+
# :call-seq: response.update {|body| block } -> response
|
27
|
+
# response.update(path) {|value| block } -> response
|
28
|
+
#
|
29
|
+
# Called without an argument, the block will be supplied the response body,
|
30
|
+
# and the response body will be set to the result of the block. The response
|
31
|
+
# itself is returned.
|
32
|
+
#
|
33
|
+
# When called with an argument the body should be a hash, the body will be
|
34
|
+
# traversed accoring to the path supplied, the value of the body will be
|
35
|
+
# yielded to the block, and then replaced with the result of the block.
|
36
|
+
# Example:
|
37
|
+
# response.body
|
38
|
+
# #=> {"data" => [{"user" => "1234", "message" => "..."}]}
|
39
|
+
#
|
40
|
+
# response.update("data.user") {|id| User.find(id).to_hash}
|
41
|
+
#
|
42
|
+
# response.body
|
43
|
+
# #=> {"data" => [{"user" => {"id" => "1234", ...}, "message" => "..."}]}
|
44
|
+
#
|
45
|
+
def update(path=nil, &block)
|
46
|
+
@body = if body.respond_to?(:to_ary) && path.nil?
|
47
|
+
block.call(body)
|
48
|
+
else
|
49
|
+
apply(body, path, &block)
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# :call-seq: response[key] -> value
|
55
|
+
#
|
56
|
+
# Get a header.
|
57
|
+
#
|
58
|
+
def [](key)
|
59
|
+
(key.is_a?(Numeric) ? to_ary : header)[key]
|
60
|
+
end
|
61
|
+
|
62
|
+
# :call-seq: response[key] = value -> value
|
63
|
+
#
|
64
|
+
# Set a header.
|
65
|
+
#
|
66
|
+
def []=(key, value)
|
67
|
+
return header[key] = value unless key.is_a?(Numeric)
|
68
|
+
case key
|
69
|
+
when 0
|
70
|
+
@status = value
|
71
|
+
when 1
|
72
|
+
@header = value
|
73
|
+
when 2
|
74
|
+
@body = value
|
75
|
+
else
|
76
|
+
raise ArgumentError("Unexpected key #{key}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# :call-seq: response.to_ary -> array
|
81
|
+
# response.to_a -> array
|
82
|
+
#
|
83
|
+
# Convert to a Rack response array of [status, headers, body]
|
84
|
+
#
|
85
|
+
def to_ary
|
86
|
+
[status, header, body.respond_to?(:each) ? body : [body].compact]
|
87
|
+
end
|
88
|
+
alias to_a to_ary
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def apply(object, path=nil, &block)
|
93
|
+
if object.respond_to?(:to_ary)
|
94
|
+
return object.to_ary.map {|obj| apply(obj, path, &block)}
|
95
|
+
end
|
96
|
+
|
97
|
+
key, rest = path.split(".", 2) if path
|
98
|
+
if rest
|
99
|
+
object[key] = apply(object[key], rest, &block)
|
100
|
+
elsif key
|
101
|
+
object[key] = block.call(object[key])
|
102
|
+
else
|
103
|
+
return block.call(object)
|
104
|
+
end
|
105
|
+
object
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "net/http"
|
2
|
+
|
3
|
+
module Octarine # :nodoc:
|
4
|
+
|
5
|
+
# SimpleHTTP is a bare-bones implementation of a simple http client, designed
|
6
|
+
# to be easily replaceable with another implementation.
|
7
|
+
#
|
8
|
+
class SimpleHTTP
|
9
|
+
Response = Struct.new(:status, :headers, :body)
|
10
|
+
|
11
|
+
# :call-seq: SimpleHTTP.new(url, options={}) -> simple_http
|
12
|
+
# SimpleHTTP.new(options) -> simple_http
|
13
|
+
#
|
14
|
+
# Create a SimpleHTTP instace with either a url and options or options with
|
15
|
+
# a :url key.
|
16
|
+
#
|
17
|
+
def initialize(url, options={})
|
18
|
+
unless url.respond_to?(:to_str)
|
19
|
+
options = url
|
20
|
+
url = options[:url]
|
21
|
+
end
|
22
|
+
@host, @port = url.to_s.split(/:/)
|
23
|
+
end
|
24
|
+
|
25
|
+
# :call-seq: simple_http.head(path, headers={}) -> response
|
26
|
+
#
|
27
|
+
# Perform a HEAD request, returns a response that responds to #status,
|
28
|
+
# #headers, and #body
|
29
|
+
#
|
30
|
+
def head(path, headers={})
|
31
|
+
request(Net::HTTP::Head.new(path, headers))
|
32
|
+
end
|
33
|
+
|
34
|
+
# :call-seq: simple_http.get(path, headers={}) -> response
|
35
|
+
#
|
36
|
+
# Perform a GET request, returns a response that responds to #status,
|
37
|
+
# #headers, and #body
|
38
|
+
#
|
39
|
+
def get(path, headers={})
|
40
|
+
request(Net::HTTP::Get.new(path, headers))
|
41
|
+
end
|
42
|
+
|
43
|
+
# :call-seq: simple_http.post(path, body=nil, headers={}) -> response
|
44
|
+
#
|
45
|
+
# Perform a POST request, returns a response that responds to #status,
|
46
|
+
# #headers, and #body
|
47
|
+
#
|
48
|
+
def post(path, body=nil, headers={})
|
49
|
+
req = Net::HTTP::Post.new(path, headers)
|
50
|
+
req.body = body if body
|
51
|
+
request(req)
|
52
|
+
end
|
53
|
+
|
54
|
+
# :call-seq: simple_http.put(path, body=nil, headers={}) -> response
|
55
|
+
#
|
56
|
+
# Perform a PUT request, returns a response that responds to #status,
|
57
|
+
# #headers, and #body
|
58
|
+
#
|
59
|
+
def put(path, body=nil, headers={})
|
60
|
+
req = Net::HTTP::Put.new(path, headers)
|
61
|
+
req.body = body if body
|
62
|
+
request(req)
|
63
|
+
end
|
64
|
+
|
65
|
+
# :call-seq: simple_http.delete(path, headers={}) -> response
|
66
|
+
#
|
67
|
+
# Perform a DELETE request, returns a response that responds to #status,
|
68
|
+
# #headers, and #body
|
69
|
+
#
|
70
|
+
def delete(path, headers={})
|
71
|
+
request(Net::HTTP::Delete.new(path, headers))
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def request(request)
|
76
|
+
response = Net::HTTP.start(@host, @port) {|http| http.request(request)}
|
77
|
+
headers = Hash[response.to_hash.map {|h,k| [h, k.join("\n")]}]
|
78
|
+
Response.new(response.code, headers, response.body)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: octarine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matthew Sadler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-02 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: http_router
|
16
|
+
requirement: &2153077060 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2153077060
|
25
|
+
description: Sinatra-like DSL for writing a HTTP routing proxy.
|
26
|
+
email: mat@sourcetagsandcodes.com
|
27
|
+
executables: []
|
28
|
+
extensions: []
|
29
|
+
extra_rdoc_files:
|
30
|
+
- README.rdoc
|
31
|
+
files:
|
32
|
+
- lib/octarine/app.rb
|
33
|
+
- lib/octarine/endpoint.rb
|
34
|
+
- lib/octarine/path.rb
|
35
|
+
- lib/octarine/request.rb
|
36
|
+
- lib/octarine/response.rb
|
37
|
+
- lib/octarine/simple_http.rb
|
38
|
+
- lib/octarine.rb
|
39
|
+
- README.rdoc
|
40
|
+
homepage: http://github.com/globaldev/octarine
|
41
|
+
licenses: []
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --main
|
45
|
+
- README.rdoc
|
46
|
+
- --charset
|
47
|
+
- utf-8
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.8.7
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: HTTP routing proxy DSL
|
68
|
+
test_files: []
|