halcyon 0.3.7

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