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