halcyon 0.3.7
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/Rakefile +135 -0
- data/bin/halcyon +152 -0
- data/lib/halcyon.rb +54 -0
- data/lib/halcyon/client.rb +43 -0
- data/lib/halcyon/client/base.rb +236 -0
- data/lib/halcyon/client/exceptions.rb +53 -0
- data/lib/halcyon/client/router.rb +106 -0
- data/lib/halcyon/exceptions.rb +19 -0
- data/lib/halcyon/server.rb +55 -0
- data/lib/halcyon/server/base.rb +392 -0
- data/lib/halcyon/server/exceptions.rb +53 -0
- data/lib/halcyon/server/router.rb +100 -0
- data/lib/halcyon/support/hashext.rb +59 -0
- metadata +104 -0
data/Rakefile
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
|
2
|
+
|
3
|
+
%w(rubygems rake rake/clean rake/packagetask rake/gempackagetask rake/rdoctask rake/contrib/rubyforgepublisher fileutils pp).each{|dep|require dep}
|
4
|
+
|
5
|
+
include FileUtils
|
6
|
+
|
7
|
+
require 'halcyon'
|
8
|
+
|
9
|
+
project = {
|
10
|
+
:name => "halcyon",
|
11
|
+
:version => Halcyon.version,
|
12
|
+
:author => "Matt Todd",
|
13
|
+
:email => "chiology@gmail.com",
|
14
|
+
:description => "A JSON App Server Framework",
|
15
|
+
:homepath => 'http://halcyon.rubyforge.org',
|
16
|
+
:bin_files => %w(halcyon),
|
17
|
+
:rdoc_files => %w(lib),
|
18
|
+
:rdoc_opts => %w[
|
19
|
+
--all
|
20
|
+
--quiet
|
21
|
+
--op rdoc
|
22
|
+
--line-numbers
|
23
|
+
--inline-source
|
24
|
+
--title "Halcyon\ documentation"
|
25
|
+
--exclude "^(_darcs|spec|pkg)/"
|
26
|
+
]
|
27
|
+
}
|
28
|
+
|
29
|
+
BASEDIR = File.expand_path(File.dirname(__FILE__))
|
30
|
+
|
31
|
+
spec = Gem::Specification.new do |s|
|
32
|
+
s.name = project[:name]
|
33
|
+
s.version = project[:version]
|
34
|
+
s.platform = Gem::Platform::RUBY
|
35
|
+
s.has_rdoc = true
|
36
|
+
s.extra_rdoc_files = project[:rdoc_files]
|
37
|
+
s.rdoc_options += project[:rdoc_opts]
|
38
|
+
s.summary = project[:description]
|
39
|
+
s.description = project[:description]
|
40
|
+
s.author = project[:author]
|
41
|
+
s.email = project[:email]
|
42
|
+
s.homepage = project[:homepath]
|
43
|
+
s.executables = project[:bin_files]
|
44
|
+
s.bindir = "bin"
|
45
|
+
s.require_path = "lib"
|
46
|
+
s.add_dependency('rack', '>=0.2.0')
|
47
|
+
s.add_dependency('json', '>=1.1.1')
|
48
|
+
s.add_dependency('merb', '>=0.4.1')
|
49
|
+
s.required_ruby_version = '>= 1.8.6'
|
50
|
+
s.files = (project[:rdoc_files] + %w[Rakefile] + Dir["{spec,lib}/**/*"]).uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
Rake::GemPackageTask.new(spec) do |p|
|
54
|
+
p.need_zip = true
|
55
|
+
p.need_tar = true
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Package and Install halcyon"
|
59
|
+
task :install do
|
60
|
+
name = "#{project[:name]}-#{project[:version]}.gem"
|
61
|
+
sh %{rake package}
|
62
|
+
sh %{sudo gem install pkg/#{name}}
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "Uninstall the halcyon gem"
|
66
|
+
task :uninstall => [:clean] do
|
67
|
+
sh %{sudo gem uninstall #{project[:name]}}
|
68
|
+
end
|
69
|
+
|
70
|
+
task 'run-spec' do
|
71
|
+
require 'spec'
|
72
|
+
$:.unshift(File.dirname(__FILE__))
|
73
|
+
stdout = []
|
74
|
+
class << stdout
|
75
|
+
def print(*e) concat(e); Kernel.print(*e); end
|
76
|
+
def puts(*e) concat(e); Kernel.puts(*e); end
|
77
|
+
def flush; end
|
78
|
+
end
|
79
|
+
stderr = []
|
80
|
+
class << stderr
|
81
|
+
alias print <<
|
82
|
+
def print(*e) concat(e); Kernel.print(*e); end
|
83
|
+
def puts(*e) concat(e); Kernel.puts(*e); end
|
84
|
+
def flush; end
|
85
|
+
end
|
86
|
+
::Spec::Runner::CommandLine.run(['spec'], stderr, stdout, false, true)
|
87
|
+
exit_status = stdout.last.strip[/(\d+) failures?/, 1].to_i
|
88
|
+
at_exit{
|
89
|
+
exit(exit_status == 0 ? 0 : 1)
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "run rspec"
|
94
|
+
task :spec do
|
95
|
+
run = Rake::Task['run-spec']
|
96
|
+
run.execute
|
97
|
+
end
|
98
|
+
|
99
|
+
task :default => :spec
|
100
|
+
|
101
|
+
desc "Do predistribution stuff"
|
102
|
+
task :predist => [:chmod, :changelog, :rdoc]
|
103
|
+
|
104
|
+
def manifest
|
105
|
+
`darcs query manifest`.split("\n").map { |f| f.gsub(/\A\.\//, '') }
|
106
|
+
end
|
107
|
+
|
108
|
+
desc "Make binaries executable"
|
109
|
+
task :chmod do
|
110
|
+
Dir["bin/*"].each { |binary| File.chmod(0775, binary) }
|
111
|
+
Dir["test/cgi/test*"].each { |binary| File.chmod(0775, binary) }
|
112
|
+
end
|
113
|
+
|
114
|
+
desc "Generate a ChangeLog"
|
115
|
+
task :changelog do
|
116
|
+
sh "svn log >ChangeLog"
|
117
|
+
end
|
118
|
+
|
119
|
+
desc "Generate RDoc documentation"
|
120
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
121
|
+
rdoc.options << '--line-numbers' << '--inline-source' <<
|
122
|
+
'--main' << 'README' <<
|
123
|
+
'--title' << 'Halcyon Documentation' <<
|
124
|
+
'--charset' << 'utf-8'
|
125
|
+
rdoc.rdoc_dir = "doc"
|
126
|
+
rdoc.rdoc_files.include 'README'
|
127
|
+
rdoc.rdoc_files.include('lib/halcyon.rb')
|
128
|
+
rdoc.rdoc_files.include('lib/halcyon/*.rb')
|
129
|
+
rdoc.rdoc_files.include('lib/halcyon/*/*.rb')
|
130
|
+
end
|
131
|
+
|
132
|
+
task :pushsite => [:rdoc] do
|
133
|
+
sh "rsync -avz doc/ mtodd@halcyon.rubyforge.org:/var/www/gforge-projects/halcyon/doc/"
|
134
|
+
sh "rsync -avz site/ mtodd@halcyon.rubyforge.org:/var/www/gforge-projects/halcyon/"
|
135
|
+
end
|
data/bin/halcyon
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
#!/usr/bin/env ruby -wKU
|
2
|
+
#--
|
3
|
+
# Created by Matt Todd on 2007-10-25.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
#++
|
6
|
+
|
7
|
+
# Blatantly stolen from Chris Neukirchen's rackup utility for running Rack
|
8
|
+
# apps. (Forgive me, it just made too much sense to use your Rack bootstrap
|
9
|
+
# code for my Rack bootstrap.)
|
10
|
+
|
11
|
+
#--
|
12
|
+
# dependencies
|
13
|
+
#++
|
14
|
+
|
15
|
+
%w(optparse).each{|dep|require dep}
|
16
|
+
|
17
|
+
#--
|
18
|
+
# default options
|
19
|
+
#++
|
20
|
+
|
21
|
+
$debug = false
|
22
|
+
options = {
|
23
|
+
:environment => 'none',
|
24
|
+
:port => 9267,
|
25
|
+
:host => 'localhost',
|
26
|
+
:server => 'mongrel',
|
27
|
+
:log_file => '/tmp/halcyon.log'
|
28
|
+
}
|
29
|
+
|
30
|
+
#--
|
31
|
+
# parse options
|
32
|
+
#++
|
33
|
+
|
34
|
+
opts = OptionParser.new("", 24, ' ') do |opts|
|
35
|
+
opts.banner << "Halcyon, JSON Server Framework\n"
|
36
|
+
opts.banner << "http://halcyon.rubyforge.org/\n"
|
37
|
+
opts.banner << "\n"
|
38
|
+
opts.banner << "Usage: halcyon [options] appname"
|
39
|
+
|
40
|
+
opts.separator ""
|
41
|
+
opts.separator "Options:"
|
42
|
+
|
43
|
+
opts.on("-d", "--debug", "set debugging flags (set $debug to true)") { $debug = true }
|
44
|
+
opts.on("-w", "--warn", "turn warnings on for your script") { $-w = true }
|
45
|
+
|
46
|
+
opts.on("-I", "--include PATH", "specify $LOAD_PATH (may be used more than once)") do |path|
|
47
|
+
$:.unshift(*path.split(":"))
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-r", "--require LIBRARY", "require the library, before executing your script") do |library|
|
51
|
+
require library
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on("-c", "--config PATH", "configuration stored in PATH") do |conf|
|
55
|
+
options[:config_file] = conf
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on("-s", "--server SERVER", "serve using SERVER (default: #{options[:server]})") do |serv|
|
59
|
+
options[:server] = serv
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on("-o", "--host HOST", "listen on HOST (default: #{options[:host]})") do |host|
|
63
|
+
options[:host] = host
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.on("-p", "--port PORT", "use PORT (default: #{options[:port]})") do |port|
|
67
|
+
options[:port] = port
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on("-l", "--logfile PATH", "log access to PATH (default: #{options[:log_file]})") do |log_file|
|
71
|
+
options[:log_file] = log_file
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.on("-e", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: #{options[:environment]})") do |env|
|
75
|
+
options[:environment] = env
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
79
|
+
puts opts
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.on_tail("-v", "--version", "Show version") do
|
84
|
+
# require 'halcyon'
|
85
|
+
puts "Halcyon #{Halcyon::Server.version}"
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
|
89
|
+
begin
|
90
|
+
opts.parse! ARGV
|
91
|
+
rescue OptionParser::InvalidOption => e
|
92
|
+
abort "You used an unsupported option. Try: halcyon -h"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
abort "Halcyon needs an app to run. Try: halcyon -h" if ARGV.empty?
|
97
|
+
options[:app] = ARGV.shift
|
98
|
+
|
99
|
+
#--
|
100
|
+
# load dependencies
|
101
|
+
#++
|
102
|
+
|
103
|
+
%w(rubygems rack).each{|dep|require dep}
|
104
|
+
|
105
|
+
$:.unshift '/Users/mtodd/Sites/halcyon/trunk/lib/'
|
106
|
+
%w(halcyon/server).each {|dep|require dep}
|
107
|
+
|
108
|
+
#--
|
109
|
+
# load app
|
110
|
+
#++
|
111
|
+
|
112
|
+
if !File.exists?("#{options[:app]}.rb")
|
113
|
+
abort "Halcyon did not find the app #{options[:app]}. Check your path and try again."
|
114
|
+
end
|
115
|
+
|
116
|
+
require options[:app]
|
117
|
+
app = Object.const_get(File.basename(options[:app]).capitalize.gsub(/_([a-z])/){|m|m[1].chr.capitalize})
|
118
|
+
|
119
|
+
#--
|
120
|
+
# prepare server
|
121
|
+
#++
|
122
|
+
|
123
|
+
require options[:server]
|
124
|
+
server = Rack::Handler.const_get(options[:server].capitalize)
|
125
|
+
|
126
|
+
#--
|
127
|
+
# prepare app environment
|
128
|
+
#++
|
129
|
+
|
130
|
+
case options[:environment]
|
131
|
+
when "development"
|
132
|
+
app = Rack::Builder.new {
|
133
|
+
use Rack::CommonLogger, STDERR unless server.name =~ /CGI/
|
134
|
+
use Rack::ShowExceptions
|
135
|
+
use Rack::Reloader
|
136
|
+
use Rack::Lint
|
137
|
+
run app.new(options)
|
138
|
+
}.to_app
|
139
|
+
when "deployment"
|
140
|
+
app = Rack::Builder.new {
|
141
|
+
use Rack::CommonLogger, STDERR unless server.name =~ /CGI/
|
142
|
+
run app.new(options)
|
143
|
+
}.to_app
|
144
|
+
else
|
145
|
+
app = app.new(options)
|
146
|
+
end
|
147
|
+
|
148
|
+
#--
|
149
|
+
# start server
|
150
|
+
#++
|
151
|
+
|
152
|
+
server.run app, :Port => options[:port]
|
data/lib/halcyon.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#--
|
3
|
+
# Created by Matt Todd on 2007-12-14.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
#++
|
6
|
+
|
7
|
+
$:.unshift File.dirname(__FILE__)
|
8
|
+
|
9
|
+
#--
|
10
|
+
# dependencies
|
11
|
+
#++
|
12
|
+
|
13
|
+
%w(halcyon/support/hashext).each {|dep|require dep}
|
14
|
+
|
15
|
+
class Hash
|
16
|
+
include HashExt::Keys
|
17
|
+
end
|
18
|
+
|
19
|
+
#--
|
20
|
+
# module
|
21
|
+
#++
|
22
|
+
|
23
|
+
module Halcyon
|
24
|
+
VERSION = [0,3,7]
|
25
|
+
def self.version
|
26
|
+
VERSION.join('.')
|
27
|
+
end
|
28
|
+
|
29
|
+
# = Introduction
|
30
|
+
#
|
31
|
+
# Halcyon is a JSON Web Server Framework intended to be used for fast, small
|
32
|
+
# data transactions, like for AJAX-intensive sites or for special services like
|
33
|
+
# authentication centralized for numerous web apps in the same cluster.
|
34
|
+
#
|
35
|
+
# The possibilities are pretty limitless: the goal of Halcyon was simply to be
|
36
|
+
# lightweight, fast, simple to implement and use, and able to be extended.
|
37
|
+
#
|
38
|
+
# == Usage
|
39
|
+
#
|
40
|
+
# For documentation on using Halcyon, check out the Halcyon::Server::Base and
|
41
|
+
# Halcyon::Client::Base classes which contain much more usage documentation.
|
42
|
+
def introduction
|
43
|
+
abort "READ THE DAMNED RDOCS FOO"
|
44
|
+
end
|
45
|
+
|
46
|
+
#--
|
47
|
+
# module dependencies
|
48
|
+
#++
|
49
|
+
|
50
|
+
autoload :Exceptions, 'halcyon/exceptions'
|
51
|
+
autoload :Server, 'halcyon/server'
|
52
|
+
autoload :Client, 'halcyon/client'
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#--
|
2
|
+
# Created by Matt Todd on 2007-12-14.
|
3
|
+
# Copyright (c) 2007. All rights reserved.
|
4
|
+
#++
|
5
|
+
|
6
|
+
$:.unshift File.dirname(File.join('..', __FILE__))
|
7
|
+
$:.unshift File.dirname(__FILE__)
|
8
|
+
|
9
|
+
#--
|
10
|
+
# dependencies
|
11
|
+
#++
|
12
|
+
|
13
|
+
%w(halcyon rubygems json).each {|dep|require dep}
|
14
|
+
|
15
|
+
#--
|
16
|
+
# module
|
17
|
+
#++
|
18
|
+
|
19
|
+
module Halcyon
|
20
|
+
|
21
|
+
# The Client library provides a simple way to package up a client lib to
|
22
|
+
# simplify communicating with the accompanying Halcyon server app.
|
23
|
+
#
|
24
|
+
# = Usage
|
25
|
+
#
|
26
|
+
# For documentation on using Halcyon, check out the Halcyon::Server::Base and
|
27
|
+
# Halcyon::Client::Base classes which contain much more usage documentation.
|
28
|
+
class Client
|
29
|
+
VERSION.replace [0,2,12]
|
30
|
+
def self.version
|
31
|
+
VERSION.join('.')
|
32
|
+
end
|
33
|
+
|
34
|
+
#--
|
35
|
+
# module dependencies
|
36
|
+
#++
|
37
|
+
|
38
|
+
autoload :Base, 'halcyon/client/base'
|
39
|
+
autoload :Exceptions, 'halcyon/client/exceptions'
|
40
|
+
autoload :Router, 'halcyon/client/router'
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
#--
|
2
|
+
# Created by Matt Todd on 2007-12-14.
|
3
|
+
# Copyright (c) 2007. All rights reserved.
|
4
|
+
#++
|
5
|
+
|
6
|
+
#--
|
7
|
+
# dependencies
|
8
|
+
#++
|
9
|
+
|
10
|
+
%w(net/http uri json).each {|dep|require dep}
|
11
|
+
|
12
|
+
#--
|
13
|
+
# module
|
14
|
+
#++
|
15
|
+
|
16
|
+
module Halcyon
|
17
|
+
class Client
|
18
|
+
|
19
|
+
DEFAULT_OPTIONS = {}
|
20
|
+
USER_AGENT = "JSON/#{JSON::VERSION} Compatible (en-US) Halcyon/#{Halcyon.version} Client/#{Halcyon::Client.version}"
|
21
|
+
CONTENT_TYPE = 'application/json'
|
22
|
+
|
23
|
+
# = Building Custom Clients
|
24
|
+
#
|
25
|
+
# Once your Halcyon JSON Server App starts to take shape, it may be useful
|
26
|
+
# to begin to write tests on expected functionality, and then to implement
|
27
|
+
# API calls with a designated Client lib for your Ruby or Rails apps, etc.
|
28
|
+
# The Base class provides a standard implementation and several options for
|
29
|
+
# wrapping up functionality of your app from the server side into the
|
30
|
+
# client side so that you may begin to use response data.
|
31
|
+
#
|
32
|
+
# == Creating Your Client
|
33
|
+
#
|
34
|
+
# Creating a simple client can be as simple as this:
|
35
|
+
#
|
36
|
+
# class Simple < Halcyon::Client::Base
|
37
|
+
# def greet(name)
|
38
|
+
# get("/hello/#{name}")
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# The only thing simply may be actually using the Simple client you just
|
43
|
+
# created.
|
44
|
+
#
|
45
|
+
# But to actually get in and use the library, one has to take full
|
46
|
+
# advantage of the HTTP request methods, +get+, +post+, +put+, and
|
47
|
+
# +delete+. These methods simply return the JSON-parsed data from the
|
48
|
+
# server, effectively returning a hash with two key values, +status+ which
|
49
|
+
# contains the HTTP status code, and +body+ which contains the body of the
|
50
|
+
# content returned which can be any number of objects, including, but not
|
51
|
+
# limited to Hash, Array, Numeric, Nil, Boolean, String, etc.
|
52
|
+
#
|
53
|
+
# You are not limited to what your methods can call: they are arbitrarily
|
54
|
+
# and solely up to your whims and needs. It is simply a matter of good
|
55
|
+
# design and performance when it comes to structuring and implementing
|
56
|
+
# client actions which can be complex or simple series of requests to the
|
57
|
+
# server.
|
58
|
+
#
|
59
|
+
# == Acceptable Clients
|
60
|
+
#
|
61
|
+
# The Halcyon Server is intended to be very picky with whom it will speak
|
62
|
+
# to, so it requires that we specifically mention that we speak only
|
63
|
+
# "application/html", that we're "JSON/1.1.1 Compatible", and that we're
|
64
|
+
# local to the server itself (in process, anyways). This ensures that it
|
65
|
+
# has to deal with as little noise as possible and focus it's attention on
|
66
|
+
# performing our requests.
|
67
|
+
#
|
68
|
+
# This shouldn't affect usage when working with the Client or in production
|
69
|
+
# but might if you're trying to check things in your browser. Just make
|
70
|
+
# certain that the debug option is turned on (-d for the +halcyon+ command)
|
71
|
+
# when you start the server so that it knows to be a little more lenient
|
72
|
+
# about to whom it speaks.
|
73
|
+
class Base
|
74
|
+
|
75
|
+
#--
|
76
|
+
# Initialization and setup
|
77
|
+
#++
|
78
|
+
|
79
|
+
# = Connecting to the Server
|
80
|
+
#
|
81
|
+
# Creates a new Client object to allow for requests and responses from
|
82
|
+
# the specified server.
|
83
|
+
#
|
84
|
+
# The +uri+ param contains the URL to the actual server, and should be in
|
85
|
+
# the format: "http://localhost:3801" or "http://app.domain.com:3401/"
|
86
|
+
#
|
87
|
+
# == Server Connections
|
88
|
+
#
|
89
|
+
# Connecting only occurs at the actual event that a request is performed,
|
90
|
+
# so there is no need to worry about closing connections or managing
|
91
|
+
# connections in general other than good object housecleaning. (Be nice
|
92
|
+
# to your Garbage Collector.)
|
93
|
+
#
|
94
|
+
# == Usage
|
95
|
+
#
|
96
|
+
# You can either provide a block to perform all of your requests and
|
97
|
+
# processing inside of or you can simply accept the object in response
|
98
|
+
# and call your request methods off of the returned object.
|
99
|
+
#
|
100
|
+
# Alternatively, you could do both.
|
101
|
+
#
|
102
|
+
# An example of creating and using a Simple client:
|
103
|
+
#
|
104
|
+
# class Simple < Halcyon::Client::Base
|
105
|
+
# def greet(name)
|
106
|
+
# get("/hello/#{name}")
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
# Simple.new('http://localhost:3801') do |s|
|
110
|
+
# puts s.greet("Johnny").inspect
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# This should effectively call +inspect+ on a response hash similar to
|
114
|
+
# this:
|
115
|
+
#
|
116
|
+
# {:status => 200, :body => 'Hello Johnny'}
|
117
|
+
#
|
118
|
+
# Alternatively, you could perform the same with the following:
|
119
|
+
#
|
120
|
+
# s = Simple.new('http://localhost:3801')
|
121
|
+
# puts s.greet("Johnny").inspect
|
122
|
+
#
|
123
|
+
# This should generate the exact same outcome as the previous example,
|
124
|
+
# except that it is not executed in a block.
|
125
|
+
#
|
126
|
+
# The differences are purely semantic and of personal taste.
|
127
|
+
def initialize(uri)
|
128
|
+
@uri = URI.parse(uri)
|
129
|
+
if block_given?
|
130
|
+
yield self
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
#--
|
135
|
+
# Reverse Routing
|
136
|
+
#++
|
137
|
+
|
138
|
+
# = Reverse Routing
|
139
|
+
#
|
140
|
+
# The concept of writing our Routes in our Client is to be able to
|
141
|
+
# automatically generate the appropriate URL based on the hash given
|
142
|
+
# and where it was called from. This makes writing actions in Clients
|
143
|
+
# go from something like this:
|
144
|
+
#
|
145
|
+
# def greet(name)
|
146
|
+
# get("/hello/#{name}")
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# to this:
|
150
|
+
#
|
151
|
+
# def greet(name)
|
152
|
+
# get(url_for(__method__, :name))
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# This doesn't immediately seem to be beneficial, but it is better for
|
156
|
+
# automating URL generating, taking out the hardcoding, and has room to
|
157
|
+
# to improve in the future.
|
158
|
+
def url_for(action, params = {})
|
159
|
+
Halcyon::Client::Router.route(action, params)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Sets up routing for creating preparing +url_for+ URLs. See the
|
163
|
+
# +url_for+ method documentation and the Halcyon::Client::Router docs.
|
164
|
+
def self.route
|
165
|
+
if block_given?
|
166
|
+
Halcyon::Client::Router.prepare do |r|
|
167
|
+
Halcyon::Client::Router.default_to yield(r)
|
168
|
+
end
|
169
|
+
else
|
170
|
+
warn "Routes should be defined in a block."
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
#--
|
175
|
+
# Request Handling
|
176
|
+
#++
|
177
|
+
|
178
|
+
# Performs a GET request on the URI specified.
|
179
|
+
def get(uri)
|
180
|
+
req = Net::HTTP::Get.new(uri)
|
181
|
+
req["Content-Type"] = CONTENT_TYPE
|
182
|
+
req["User-Agent"] = USER_AGENT
|
183
|
+
request(req)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Performs a POST request on the URI specified.
|
187
|
+
def post(uri, data)
|
188
|
+
req = Net::HTTP::Post.new(uri)
|
189
|
+
req["Content-Type"] = CONTENT_TYPE
|
190
|
+
req["User-Agent"] = USER_AGENT
|
191
|
+
req.body = data.to_json
|
192
|
+
request(req)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Performs a DELETE request on the URI specified.
|
196
|
+
def delete(uri)
|
197
|
+
req = Net::HTTP::Delete.new(uri)
|
198
|
+
req["Content-Type"] = CONTENT_TYPE
|
199
|
+
req["User-Agent"] = USER_AGENT
|
200
|
+
request(req)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Performs a PUT request on the URI specified.
|
204
|
+
def put(uri, data)
|
205
|
+
req = Net::HTTP::Put.new(uri)
|
206
|
+
req["Content-Type"] = CONTENT_TYPE
|
207
|
+
req["User-Agent"] = USER_AGENT
|
208
|
+
req.body = data.to_json
|
209
|
+
request(req)
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
# Performs an arbitrary HTTP request, receive the response, parse it with
|
215
|
+
# JSON, and return it to the caller. This is a private method because the
|
216
|
+
# user/developer should be quite satisfied with the +get+, +post+, +put+,
|
217
|
+
# and +delete+ methods.
|
218
|
+
def request(req)
|
219
|
+
# prepare and send HTTP request
|
220
|
+
res = Net::HTTP.start(@uri.host, @uri.port) {|http|http.request(req)}
|
221
|
+
body = JSON.parse(res.body)
|
222
|
+
body.symbolize_keys! if body.respond_to? :symbolize_keys!
|
223
|
+
|
224
|
+
# handle non-successes
|
225
|
+
raise Halcyon::Client::Base::Exceptions.lookup(body[:status]).new unless res.kind_of? Net::HTTPSuccess
|
226
|
+
|
227
|
+
# parse response
|
228
|
+
body
|
229
|
+
rescue Halcyon::Client::Base::Exceptions::Base => e
|
230
|
+
puts "#{e.status}: #{e.error}"
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
end
|