halcyon 0.3.18 → 0.3.22
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 +35 -31
- data/bin/halcyon +1 -0
- data/lib/halcyon.rb +2 -2
- data/lib/halcyon/client/base.rb +3 -3
- data/lib/halcyon/client/exceptions.rb +1 -13
- data/lib/halcyon/exceptions.rb +17 -0
- data/lib/halcyon/server/base.rb +56 -22
- data/lib/halcyon/server/exceptions.rb +1 -13
- data/lib/halcyon/server/router.rb +3 -2
- data/spec/SPEC +29 -0
- data/spec/SPEC.html +237 -0
- data/spec/halcyon/error_spec.rb +55 -0
- data/spec/halcyon/router_spec.rb +32 -0
- data/spec/halcyon/server_spec.rb +93 -0
- data/spec/spec_helper.rb +21 -0
- metadata +9 -2
data/Rakefile
CHANGED
@@ -75,42 +75,39 @@ task :uninstall => [:clean] do
|
|
75
75
|
sh %{sudo gem uninstall #{project[:name]}}
|
76
76
|
end
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
class << stdout
|
83
|
-
def print(*e) concat(e); Kernel.print(*e); end
|
84
|
-
def puts(*e) concat(e); Kernel.puts(*e); end
|
85
|
-
def flush; end
|
78
|
+
namespace 'spec' do
|
79
|
+
desc "generate spec"
|
80
|
+
task :gen do
|
81
|
+
sh "spec -c -rlib/halcyon -rspec/spec_helper spec/**/* --format s:spec/SPEC --format h:spec/SPEC.html"
|
86
82
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
83
|
+
|
84
|
+
desc "run rspec"
|
85
|
+
task :run do
|
86
|
+
sh "spec -c -rlib/halcyon -rspec/spec_helper spec/**/*"
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "run rspec verbosely"
|
90
|
+
task :verb do
|
91
|
+
sh "spec -c -rlib/halcyon -rspec/spec_helper spec/**/* --format s"
|
93
92
|
end
|
94
|
-
::Spec::Runner::CommandLine.run(['spec'], stderr, stdout, false, true)
|
95
|
-
exit_status = stdout.last.strip[/(\d+) failures?/, 1].to_i
|
96
|
-
at_exit{
|
97
|
-
exit(exit_status == 0 ? 0 : 1)
|
98
|
-
}
|
99
|
-
end
|
100
|
-
|
101
|
-
desc "run rspec"
|
102
|
-
task :spec do
|
103
|
-
run = Rake::Task['run-spec']
|
104
|
-
run.execute
|
105
93
|
end
|
106
94
|
|
107
|
-
task :default => :spec
|
108
|
-
|
109
95
|
desc "Do predistribution stuff"
|
110
|
-
task :predist => [:chmod, :changelog, :rdoc]
|
96
|
+
task :predist => [:chmod, :changelog, :manifest, :rdoc]
|
111
97
|
|
112
98
|
def manifest
|
113
|
-
|
99
|
+
require 'find'
|
100
|
+
paths = []
|
101
|
+
manifest = File.new('MANIFEST', 'w+')
|
102
|
+
Find.find('.') do |path|
|
103
|
+
path.gsub!(/\A\.\//, '')
|
104
|
+
next if path =~ /(\.svn|doc|pkg|^\.|MANIFEST)/
|
105
|
+
paths << path
|
106
|
+
end
|
107
|
+
paths.sort.each do |path|
|
108
|
+
manifest.puts path
|
109
|
+
end
|
110
|
+
manifest.close
|
114
111
|
end
|
115
112
|
|
116
113
|
desc "Make binaries executable"
|
@@ -119,9 +116,14 @@ task :chmod do
|
|
119
116
|
Dir["test/cgi/test*"].each { |binary| File.chmod(0775, binary) }
|
120
117
|
end
|
121
118
|
|
122
|
-
desc "Generate a
|
119
|
+
desc "Generate a MANIFEST"
|
120
|
+
task :manifest do
|
121
|
+
manifest
|
122
|
+
end
|
123
|
+
|
124
|
+
desc "Generate a CHANGELOG"
|
123
125
|
task :changelog do
|
124
|
-
sh "svn log >
|
126
|
+
sh "svn log > CHANGELOG"
|
125
127
|
end
|
126
128
|
|
127
129
|
desc "Generate RDoc documentation"
|
@@ -146,3 +148,5 @@ desc "find . -name \"*.rb\" | xargs wc -l | grep total"
|
|
146
148
|
task :loc do
|
147
149
|
sh "find . -name \"*.rb\" | xargs wc -l | grep total"
|
148
150
|
end
|
151
|
+
|
152
|
+
task :default => Rake::Task['spec:run']
|
data/bin/halcyon
CHANGED
data/lib/halcyon.rb
CHANGED
@@ -21,7 +21,7 @@ end
|
|
21
21
|
#++
|
22
22
|
|
23
23
|
module Halcyon
|
24
|
-
VERSION = [0,3,
|
24
|
+
VERSION = [0,3,22]
|
25
25
|
def self.version
|
26
26
|
VERSION.join('.')
|
27
27
|
end
|
@@ -40,7 +40,7 @@ module Halcyon
|
|
40
40
|
# For documentation on using Halcyon, check out the Halcyon::Server::Base and
|
41
41
|
# Halcyon::Client::Base classes which contain much more usage documentation.
|
42
42
|
def introduction
|
43
|
-
abort "READ THE DAMNED RDOCS
|
43
|
+
abort "READ THE DAMNED RDOCS!"
|
44
44
|
end
|
45
45
|
|
46
46
|
end
|
data/lib/halcyon/client/base.rb
CHANGED
@@ -221,8 +221,8 @@ module Halcyon
|
|
221
221
|
# If the server responds with any kind of failure (anything with a status
|
222
222
|
# that isn't 200), Halcyon will in turn raise the respective exception
|
223
223
|
# (defined in Halcyon::Exceptions) which all inherit from
|
224
|
-
# +Halcyon::
|
225
|
-
#
|
224
|
+
# +Halcyon::Exceptions::Base+. It is up to the client to handle these
|
225
|
+
# exceptions specifically.
|
226
226
|
def request(req)
|
227
227
|
# prepare and send HTTP request
|
228
228
|
res = Net::HTTP.start(@uri.host, @uri.port) {|http|http.request(req)}
|
@@ -236,7 +236,7 @@ module Halcyon
|
|
236
236
|
|
237
237
|
# return response
|
238
238
|
body
|
239
|
-
rescue Halcyon::
|
239
|
+
rescue Halcyon::Exceptions::Base => e
|
240
240
|
# log exception if logger is in place
|
241
241
|
raise
|
242
242
|
end
|
@@ -12,18 +12,6 @@ module Halcyon
|
|
12
12
|
class Base
|
13
13
|
module Exceptions #:nodoc:
|
14
14
|
|
15
|
-
#--
|
16
|
-
# Base Halcyon Exception
|
17
|
-
#++
|
18
|
-
|
19
|
-
class Base < StandardError #:nodoc:
|
20
|
-
attr_accessor :status, :error
|
21
|
-
def initialize(status, error)
|
22
|
-
@status = status
|
23
|
-
@error = error
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
15
|
#--
|
28
16
|
# Exception classes
|
29
17
|
#++
|
@@ -31,7 +19,7 @@ module Halcyon
|
|
31
19
|
Halcyon::Exceptions::HTTP_ERROR_CODES.to_a.each do |http_error|
|
32
20
|
status, body = http_error
|
33
21
|
class_eval(
|
34
|
-
"class #{body.gsub(/( |\-)/,'')} < Base\n"+
|
22
|
+
"class #{body.gsub(/( |\-)/,'')} < Halcyon::Exceptions::Base\n"+
|
35
23
|
" def initialize(s=#{status}, e='#{body}')\n"+
|
36
24
|
" super s, e\n"+
|
37
25
|
" end\n"+
|
data/lib/halcyon/exceptions.rb
CHANGED
@@ -9,6 +9,23 @@
|
|
9
9
|
|
10
10
|
module Halcyon
|
11
11
|
module Exceptions #:nodoc:
|
12
|
+
|
13
|
+
#--
|
14
|
+
# Base Halcyon Exception
|
15
|
+
#++
|
16
|
+
|
17
|
+
class Base < StandardError #:nodoc:
|
18
|
+
attr_accessor :status, :error
|
19
|
+
def initialize(status, error)
|
20
|
+
@status = status
|
21
|
+
@error = error
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#--
|
26
|
+
# HTTP Error Codes and Errors
|
27
|
+
#++
|
28
|
+
|
12
29
|
HTTP_ERROR_CODES = {
|
13
30
|
400 => 'Bad Request',
|
14
31
|
401 => 'Unauthorized',
|
data/lib/halcyon/server/base.rb
CHANGED
@@ -219,9 +219,6 @@ module Halcyon
|
|
219
219
|
# instance
|
220
220
|
@env['halcyon.logger'] = @logger
|
221
221
|
|
222
|
-
# set the acceptable remotes to include the remote IP if debugging is enabled
|
223
|
-
@config[:acceptable_remotes] << @env["REMOTE_ADDR"] if $debug
|
224
|
-
|
225
222
|
# pre run hook
|
226
223
|
before_run(Time.now - @time_started) if respond_to? :before_run
|
227
224
|
|
@@ -289,6 +286,11 @@ module Halcyon
|
|
289
286
|
# primarily only speaking with other apps on the same machine, though
|
290
287
|
# your specific requirements may differ and change that.
|
291
288
|
#
|
289
|
+
# When in debug mode or in testing mode, the request filtering test is
|
290
|
+
# not fired, so all requests from all User Agents and locations will
|
291
|
+
# succeed. This is important to know if you plan on testing this specific
|
292
|
+
# feature while in debugging or testing modes.
|
293
|
+
#
|
292
294
|
# == Hooks, Callbacks, and Authentication
|
293
295
|
#
|
294
296
|
# There is no Authentication mechanism built in to Halcyon (for the time
|
@@ -310,10 +312,7 @@ module Halcyon
|
|
310
312
|
# (or one of its inheriters) instead of handling them manually.
|
311
313
|
def run(route)
|
312
314
|
# make sure the request meets our expectations
|
313
|
-
|
314
|
-
raise Exceptions::Base.new(req[2], req[3]) unless @env[req[0]] =~ req[1]
|
315
|
-
end
|
316
|
-
raise Exceptions::Forbidden.new unless @config[:acceptable_remotes].member? @env["REMOTE_ADDR"]
|
315
|
+
acceptable_request! unless $debug || $test
|
317
316
|
|
318
317
|
# pull params
|
319
318
|
params = route.reject{|key, val| [:action, :module].include? key}
|
@@ -337,13 +336,21 @@ module Halcyon
|
|
337
336
|
after_call if respond_to? :after_call
|
338
337
|
|
339
338
|
res
|
340
|
-
rescue Exceptions::Base => e
|
339
|
+
rescue Halcyon::Exceptions::Base => e
|
341
340
|
@logger.warn "#{uri} => #{e.error}"
|
342
341
|
# handles all content error exceptions
|
343
342
|
@res.status = e.status
|
344
343
|
{:status => e.status, :body => e.error}
|
345
344
|
end
|
346
345
|
|
346
|
+
# Tests for acceptable requests if +$debug+ and +$test+ are not set.
|
347
|
+
def acceptable_request!
|
348
|
+
@config[:acceptable_requests].each do |req|
|
349
|
+
raise Halcyon::Exceptions::Base.new(req[2], req[3]) unless @env[req[0]] =~ req[1]
|
350
|
+
end
|
351
|
+
raise Exceptions::Forbidden.new unless @config[:acceptable_remotes].member? @env["REMOTE_ADDR"]
|
352
|
+
end
|
353
|
+
|
347
354
|
#--
|
348
355
|
# Initialization and setup
|
349
356
|
#++
|
@@ -373,18 +380,20 @@ module Halcyon
|
|
373
380
|
def initialize(options = {})
|
374
381
|
# save configuration options
|
375
382
|
@config = DEFAULT_OPTIONS.merge(options)
|
383
|
+
@config[:app] ||= self.class.to_s.downcase
|
376
384
|
|
377
385
|
# apply name options to log_file and pid_file configs
|
378
386
|
apply_log_and_pid_file_name_options
|
379
387
|
|
380
|
-
# debug mode handling
|
388
|
+
# debug and test mode handling
|
381
389
|
enable_debugging if $debug
|
390
|
+
enable_testing if $test
|
382
391
|
|
383
392
|
# setup logging
|
384
|
-
setup_logging unless $debug
|
393
|
+
setup_logging unless $debug || $test
|
385
394
|
|
386
395
|
# setup request filtering
|
387
|
-
setup_request_filters unless $debug
|
396
|
+
setup_request_filters unless $debug || $test
|
388
397
|
|
389
398
|
# create PID file
|
390
399
|
@pid = File.new(@config[:pid_file].gsub('{n}', server_cluster_number), "w", 0644)
|
@@ -397,8 +406,7 @@ module Halcyon
|
|
397
406
|
# trap signals to die (when killed by the user) gracefully
|
398
407
|
finalize = Proc.new do
|
399
408
|
@logger.info "Shutting down #{$$}."
|
400
|
-
|
401
|
-
File.delete(@pid.path)
|
409
|
+
clean_up
|
402
410
|
exit
|
403
411
|
end
|
404
412
|
# http://en.wikipedia.org/wiki/Signal_%28computing%29
|
@@ -414,6 +422,14 @@ module Halcyon
|
|
414
422
|
end
|
415
423
|
end
|
416
424
|
|
425
|
+
# Closes the logger and deletes the PID file.
|
426
|
+
def clean_up
|
427
|
+
return if defined? @cleaned_up
|
428
|
+
@logger.close
|
429
|
+
File.delete(@pid.path) if File.exist?(@pid.path)
|
430
|
+
@cleaned_up = true
|
431
|
+
end
|
432
|
+
|
417
433
|
# Retreives the server cluster sequence number for the PID file.
|
418
434
|
#
|
419
435
|
# This is deprecated and will be removed soon, probably for the 0.4.0
|
@@ -448,23 +464,41 @@ module Halcyon
|
|
448
464
|
# debugging.
|
449
465
|
def enable_debugging
|
450
466
|
$debug = true
|
467
|
+
|
468
|
+
# set the PID file name to /tmp/ unless PID file already exists
|
469
|
+
@config[:pid_file] = '/tmp/halcyon.{server}.{app}.{port}.pid' unless defined? @pid
|
470
|
+
apply_log_and_pid_file_name_options # reapply for {server}, {app}, and {port} to be set
|
471
|
+
|
451
472
|
# setup logger to STDOUT and log entering debugging mode
|
452
473
|
@logger = Logger.new(STDOUT)
|
453
474
|
@logger.progname = "#{self.class}#debug"
|
454
475
|
@logger.level = Logger::DEBUG
|
455
476
|
@logger.formatter = @config[:log_format]
|
456
477
|
@logger.info "Entering debugging mode..."
|
457
|
-
|
478
|
+
rescue Errno::EACCES
|
479
|
+
abort "Can't access #{@config[:pid_file]}, try 'sudo #{$0}'"
|
480
|
+
end
|
481
|
+
|
482
|
+
# This method is used to setup logging and the request handling methods
|
483
|
+
# for debugging.
|
484
|
+
def enable_testing
|
458
485
|
# set the PID file name to /tmp/ unless PID file already exists
|
459
|
-
@config[:pid_file] = '/tmp/halcyon.
|
486
|
+
@config[:pid_file] = '/tmp/halcyon.testing.{app}.{port}.pid' unless defined? @pid
|
487
|
+
@config[:log_file] = '/tmp/halcyon.testing.{app}.log'
|
460
488
|
apply_log_and_pid_file_name_options # reapply for {server}, {app}, and {port} to be set
|
461
489
|
|
462
|
-
#
|
463
|
-
@config[:
|
464
|
-
|
465
|
-
|
466
|
-
]
|
467
|
-
@logger.
|
490
|
+
# setup logger and log entering testing mode
|
491
|
+
@logger = Logger.new(@config[:log_file])
|
492
|
+
@logger.progname = "#{self.class}#test"
|
493
|
+
@logger.level = Logger::DEBUG
|
494
|
+
@logger.formatter = @config[:log_format]
|
495
|
+
@logger.info "Entering testing mode..."
|
496
|
+
|
497
|
+
# make sure we clean up after ourselves since we're in testing mode
|
498
|
+
at_exit {
|
499
|
+
clean_up
|
500
|
+
File.delete(@config[:log_file]) if File.exist?(@config[:log_file])
|
501
|
+
}
|
468
502
|
rescue Errno::EACCES
|
469
503
|
abort "Can't access #{@config[:pid_file]}, try 'sudo #{$0}'"
|
470
504
|
end
|
@@ -669,7 +703,7 @@ module Halcyon
|
|
669
703
|
def uri
|
670
704
|
# special parsing is done to remove the protocol, host, and port that
|
671
705
|
# some Handlers leave in there. (Fixes inconsistencies.)
|
672
|
-
URI.parse(@env['REQUEST_URI']).path
|
706
|
+
URI.parse(@env['REQUEST_URI'] || @env['PATH_INFO']).path
|
673
707
|
end
|
674
708
|
|
675
709
|
# Returns the Request Method as a lowercase symbol
|
@@ -12,18 +12,6 @@ module Halcyon
|
|
12
12
|
class Base
|
13
13
|
module Exceptions #:nodoc:
|
14
14
|
|
15
|
-
#--
|
16
|
-
# Base Halcyon Exception
|
17
|
-
#++
|
18
|
-
|
19
|
-
class Base < StandardError #:nodoc:
|
20
|
-
attr_accessor :status, :error
|
21
|
-
def initialize(status, error)
|
22
|
-
@status = status
|
23
|
-
@error = error
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
15
|
#--
|
28
16
|
# Exception classes
|
29
17
|
#++
|
@@ -31,7 +19,7 @@ module Halcyon
|
|
31
19
|
Halcyon::Exceptions::HTTP_ERROR_CODES.to_a.each do |http_error|
|
32
20
|
status, body = http_error
|
33
21
|
class_eval(
|
34
|
-
"class #{body.gsub(/( |\-)/,'')} < Base\n"+
|
22
|
+
"class #{body.gsub(/( |\-)/,'')} < Halcyon::Exceptions::Base\n"+
|
35
23
|
" def initialize(s=#{status}, e='#{body}')\n"+
|
36
24
|
" super s, e\n"+
|
37
25
|
" end\n"+
|
@@ -76,7 +76,8 @@ module Halcyon
|
|
76
76
|
# default route if no match is made.
|
77
77
|
def self.route(env)
|
78
78
|
# pull out the path requested (WEBrick keeps the host and port and protocol in REQUEST_URI)
|
79
|
-
|
79
|
+
# PATH_INFO is failover if REQUEST_URI is blank (like what Rack::MockRequest does)
|
80
|
+
uri = URI.parse(env['REQUEST_URI'] || env['PATH_INFO']).path
|
80
81
|
|
81
82
|
# prepare request
|
82
83
|
path = (uri ? uri.split('?').first : '').sub(/\/+/, '/')
|
@@ -89,7 +90,7 @@ module Halcyon
|
|
89
90
|
# make sure a route is returned even if no match is found
|
90
91
|
if route[0].nil?
|
91
92
|
#return default route
|
92
|
-
env['halcyon.logger'].debug "No route found. Using default."
|
93
|
+
env['halcyon.logger'].debug "No route found. Using default." if env['halcyon.logger'].is_a? Logger
|
93
94
|
@@default_route
|
94
95
|
else
|
95
96
|
# params (including action and module if set) for the matching route
|
data/spec/SPEC
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
Halcyon::Server Errors
|
3
|
+
- should provide shorthand methods for errors which should throw an appropriate exception
|
4
|
+
- supports numerous standard HTTP request error exceptions with lookup by status code
|
5
|
+
- should have a short inheritence chain to make catching generically simple
|
6
|
+
|
7
|
+
Halcyon::Server::Router
|
8
|
+
- should prepares routes correctly when written correctly
|
9
|
+
- should match URIs to the correct route
|
10
|
+
- should use the default route if no matching route is found
|
11
|
+
- should map params in routes to parameters
|
12
|
+
- should supply arbitrary routing param values included as a param even if not in the URI
|
13
|
+
|
14
|
+
Halcyon::Server
|
15
|
+
- should dispatch methods according to their respective routes
|
16
|
+
- should provide various shorthand methods for simple responses but take custom response values
|
17
|
+
- should handle requests and respond with JSON
|
18
|
+
- should handle requests with param values in the URL
|
19
|
+
- should route unmatchable requests to the default route and return JSON with appropriate status
|
20
|
+
- should log activity
|
21
|
+
- should create a PID file while running with the correct process ID
|
22
|
+
- should parse URI query params correctly
|
23
|
+
- should parse the URI correctly
|
24
|
+
- should provide a quick way to find out what method the request was performed using
|
25
|
+
- should deny all unacceptable requests
|
26
|
+
|
27
|
+
Finished in 0.211129 seconds
|
28
|
+
|
29
|
+
19 examples, 0 failures
|
data/spec/SPEC.html
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE html
|
3
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
4
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
5
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
6
|
+
<head>
|
7
|
+
<title>RSpec results</title>
|
8
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
9
|
+
<meta http-equiv="Expires" content="-1" />
|
10
|
+
<meta http-equiv="Pragma" content="no-cache" />
|
11
|
+
<style type="text/css">
|
12
|
+
body {
|
13
|
+
margin: 0;
|
14
|
+
padding: 0;
|
15
|
+
background: #fff;
|
16
|
+
font-size: 80%;
|
17
|
+
}
|
18
|
+
</style>
|
19
|
+
</head>
|
20
|
+
<body>
|
21
|
+
<div class="rspec-report">
|
22
|
+
<script type="text/javascript">
|
23
|
+
// <![CDATA[
|
24
|
+
function moveProgressBar(percentDone) {
|
25
|
+
document.getElementById("rspec-header").style.width = percentDone +"%";
|
26
|
+
}
|
27
|
+
function makeRed(element_id) {
|
28
|
+
document.getElementById(element_id).style.background = '#C40D0D';
|
29
|
+
document.getElementById(element_id).style.color = '#FFFFFF';
|
30
|
+
}
|
31
|
+
|
32
|
+
function makeYellow(element_id) {
|
33
|
+
if (element_id == "rspec-header" && document.getElementById(element_id).style.background != '#C40D0D')
|
34
|
+
{
|
35
|
+
document.getElementById(element_id).style.background = '#FAF834';
|
36
|
+
document.getElementById(element_id).style.color = '#000000';
|
37
|
+
}
|
38
|
+
else
|
39
|
+
{
|
40
|
+
document.getElementById(element_id).style.background = '#FAF834';
|
41
|
+
document.getElementById(element_id).style.color = '#000000';
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
// ]]>
|
46
|
+
</script>
|
47
|
+
<style type="text/css">
|
48
|
+
#rspec-header {
|
49
|
+
background: #65C400; color: #fff;
|
50
|
+
}
|
51
|
+
|
52
|
+
.rspec-report h1 {
|
53
|
+
margin: 0px 10px 0px 10px;
|
54
|
+
padding: 10px;
|
55
|
+
font-family: "Lucida Grande", Helvetica, sans-serif;
|
56
|
+
font-size: 1.8em;
|
57
|
+
}
|
58
|
+
|
59
|
+
#summary {
|
60
|
+
margin: 0; padding: 5px 10px;
|
61
|
+
font-family: "Lucida Grande", Helvetica, sans-serif;
|
62
|
+
text-align: right;
|
63
|
+
position: absolute;
|
64
|
+
top: 0px;
|
65
|
+
right: 0px;
|
66
|
+
}
|
67
|
+
|
68
|
+
#summary p {
|
69
|
+
margin: 0 0 0 2px;
|
70
|
+
}
|
71
|
+
|
72
|
+
#summary #totals {
|
73
|
+
font-size: 1.2em;
|
74
|
+
}
|
75
|
+
|
76
|
+
.example_group {
|
77
|
+
margin: 0 10px 5px;
|
78
|
+
background: #fff;
|
79
|
+
}
|
80
|
+
|
81
|
+
dl {
|
82
|
+
margin: 0; padding: 0 0 5px;
|
83
|
+
font: normal 11px "Lucida Grande", Helvetica, sans-serif;
|
84
|
+
}
|
85
|
+
|
86
|
+
dt {
|
87
|
+
padding: 3px;
|
88
|
+
background: #65C400;
|
89
|
+
color: #fff;
|
90
|
+
font-weight: bold;
|
91
|
+
}
|
92
|
+
|
93
|
+
dd {
|
94
|
+
margin: 5px 0 5px 5px;
|
95
|
+
padding: 3px 3px 3px 18px;
|
96
|
+
}
|
97
|
+
|
98
|
+
dd.spec.passed {
|
99
|
+
border-left: 5px solid #65C400;
|
100
|
+
border-bottom: 1px solid #65C400;
|
101
|
+
background: #DBFFB4; color: #3D7700;
|
102
|
+
}
|
103
|
+
|
104
|
+
dd.spec.failed {
|
105
|
+
border-left: 5px solid #C20000;
|
106
|
+
border-bottom: 1px solid #C20000;
|
107
|
+
color: #C20000; background: #FFFBD3;
|
108
|
+
}
|
109
|
+
|
110
|
+
dd.spec.not_implemented {
|
111
|
+
border-left: 5px solid #FAF834;
|
112
|
+
border-bottom: 1px solid #FAF834;
|
113
|
+
background: #FCFB98; color: #131313;
|
114
|
+
}
|
115
|
+
|
116
|
+
dd.spec.pending_fixed {
|
117
|
+
border-left: 5px solid #0000C2;
|
118
|
+
border-bottom: 1px solid #0000C2;
|
119
|
+
color: #0000C2; background: #D3FBFF;
|
120
|
+
}
|
121
|
+
|
122
|
+
.backtrace {
|
123
|
+
color: #000;
|
124
|
+
font-size: 12px;
|
125
|
+
}
|
126
|
+
|
127
|
+
a {
|
128
|
+
color: #BE5C00;
|
129
|
+
}
|
130
|
+
|
131
|
+
/* Ruby code, style similar to vibrant ink */
|
132
|
+
.ruby {
|
133
|
+
font-size: 12px;
|
134
|
+
font-family: monospace;
|
135
|
+
color: white;
|
136
|
+
background-color: black;
|
137
|
+
padding: 0.1em 0 0.2em 0;
|
138
|
+
}
|
139
|
+
|
140
|
+
.ruby .keyword { color: #FF6600; }
|
141
|
+
.ruby .constant { color: #339999; }
|
142
|
+
.ruby .attribute { color: white; }
|
143
|
+
.ruby .global { color: white; }
|
144
|
+
.ruby .module { color: white; }
|
145
|
+
.ruby .class { color: white; }
|
146
|
+
.ruby .string { color: #66FF00; }
|
147
|
+
.ruby .ident { color: white; }
|
148
|
+
.ruby .method { color: #FFCC00; }
|
149
|
+
.ruby .number { color: white; }
|
150
|
+
.ruby .char { color: white; }
|
151
|
+
.ruby .comment { color: #9933CC; }
|
152
|
+
.ruby .symbol { color: white; }
|
153
|
+
.ruby .regex { color: #44B4CC; }
|
154
|
+
.ruby .punct { color: white; }
|
155
|
+
.ruby .escape { color: white; }
|
156
|
+
.ruby .interp { color: white; }
|
157
|
+
.ruby .expr { color: white; }
|
158
|
+
|
159
|
+
.ruby .offending { background-color: gray; }
|
160
|
+
.ruby .linenum {
|
161
|
+
width: 75px;
|
162
|
+
padding: 0.1em 1em 0.2em 0;
|
163
|
+
color: #000000;
|
164
|
+
background-color: #FFFBD3;
|
165
|
+
}
|
166
|
+
|
167
|
+
</style>
|
168
|
+
|
169
|
+
<div id="rspec-header">
|
170
|
+
<h1>RSpec Results</h1>
|
171
|
+
|
172
|
+
<div id="summary">
|
173
|
+
<p id="totals"> </p>
|
174
|
+
<p id="duration"> </p>
|
175
|
+
</div>
|
176
|
+
</div>
|
177
|
+
|
178
|
+
<div class="results">
|
179
|
+
<div class="example_group">
|
180
|
+
<dl>
|
181
|
+
<dt id="example_group_1">Halcyon::Server Errors</dt>
|
182
|
+
<script type="text/javascript">moveProgressBar('5.2');</script>
|
183
|
+
<dd class="spec passed"><span class="passed_spec_name">should provide shorthand methods for errors which should throw an appropriate exception</span></dd>
|
184
|
+
<script type="text/javascript">moveProgressBar('10.5');</script>
|
185
|
+
<dd class="spec passed"><span class="passed_spec_name">supports numerous standard HTTP request error exceptions with lookup by status code</span></dd>
|
186
|
+
<script type="text/javascript">moveProgressBar('15.7');</script>
|
187
|
+
<dd class="spec passed"><span class="passed_spec_name">should have a short inheritence chain to make catching generically simple</span></dd>
|
188
|
+
</dl>
|
189
|
+
</div>
|
190
|
+
<div class="example_group">
|
191
|
+
<dl>
|
192
|
+
<dt id="example_group_2">Halcyon::Server::Router</dt>
|
193
|
+
<script type="text/javascript">moveProgressBar('21.0');</script>
|
194
|
+
<dd class="spec passed"><span class="passed_spec_name">should prepares routes correctly when written correctly</span></dd>
|
195
|
+
<script type="text/javascript">moveProgressBar('26.3');</script>
|
196
|
+
<dd class="spec passed"><span class="passed_spec_name">should match URIs to the correct route</span></dd>
|
197
|
+
<script type="text/javascript">moveProgressBar('31.5');</script>
|
198
|
+
<dd class="spec passed"><span class="passed_spec_name">should use the default route if no matching route is found</span></dd>
|
199
|
+
<script type="text/javascript">moveProgressBar('36.8');</script>
|
200
|
+
<dd class="spec passed"><span class="passed_spec_name">should map params in routes to parameters</span></dd>
|
201
|
+
<script type="text/javascript">moveProgressBar('42.1');</script>
|
202
|
+
<dd class="spec passed"><span class="passed_spec_name">should supply arbitrary routing param values included as a param even if not in the URI</span></dd>
|
203
|
+
</dl>
|
204
|
+
</div>
|
205
|
+
<div class="example_group">
|
206
|
+
<dl>
|
207
|
+
<dt id="example_group_3">Halcyon::Server</dt>
|
208
|
+
<script type="text/javascript">moveProgressBar('47.3');</script>
|
209
|
+
<dd class="spec passed"><span class="passed_spec_name">should dispatch methods according to their respective routes</span></dd>
|
210
|
+
<script type="text/javascript">moveProgressBar('52.6');</script>
|
211
|
+
<dd class="spec passed"><span class="passed_spec_name">should provide various shorthand methods for simple responses but take custom response values</span></dd>
|
212
|
+
<script type="text/javascript">moveProgressBar('57.8');</script>
|
213
|
+
<dd class="spec passed"><span class="passed_spec_name">should handle requests and respond with JSON</span></dd>
|
214
|
+
<script type="text/javascript">moveProgressBar('63.1');</script>
|
215
|
+
<dd class="spec passed"><span class="passed_spec_name">should handle requests with param values in the URL</span></dd>
|
216
|
+
<script type="text/javascript">moveProgressBar('68.4');</script>
|
217
|
+
<dd class="spec passed"><span class="passed_spec_name">should route unmatchable requests to the default route and return JSON with appropriate status</span></dd>
|
218
|
+
<script type="text/javascript">moveProgressBar('73.6');</script>
|
219
|
+
<dd class="spec passed"><span class="passed_spec_name">should log activity</span></dd>
|
220
|
+
<script type="text/javascript">moveProgressBar('78.9');</script>
|
221
|
+
<dd class="spec passed"><span class="passed_spec_name">should create a PID file while running with the correct process ID</span></dd>
|
222
|
+
<script type="text/javascript">moveProgressBar('84.2');</script>
|
223
|
+
<dd class="spec passed"><span class="passed_spec_name">should parse URI query params correctly</span></dd>
|
224
|
+
<script type="text/javascript">moveProgressBar('89.4');</script>
|
225
|
+
<dd class="spec passed"><span class="passed_spec_name">should parse the URI correctly</span></dd>
|
226
|
+
<script type="text/javascript">moveProgressBar('94.7');</script>
|
227
|
+
<dd class="spec passed"><span class="passed_spec_name">should provide a quick way to find out what method the request was performed using</span></dd>
|
228
|
+
<script type="text/javascript">moveProgressBar('100.0');</script>
|
229
|
+
<dd class="spec passed"><span class="passed_spec_name">should deny all unacceptable requests</span></dd>
|
230
|
+
</dl>
|
231
|
+
</div>
|
232
|
+
<script type="text/javascript">document.getElementById('duration').innerHTML = "Finished in <strong>0.211129 seconds</strong>";</script>
|
233
|
+
<script type="text/javascript">document.getElementById('totals').innerHTML = "19 examples, 0 failures";</script>
|
234
|
+
</div>
|
235
|
+
</div>
|
236
|
+
</body>
|
237
|
+
</html>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
context "Halcyon::Server Errors" do
|
2
|
+
|
3
|
+
before(:each) do
|
4
|
+
@app = Specr.new :port => 4000
|
5
|
+
end
|
6
|
+
|
7
|
+
specify "should provide shorthand methods for errors which should throw an appropriate exception" do
|
8
|
+
begin
|
9
|
+
@app.not_found
|
10
|
+
rescue Halcyon::Exceptions::Base => e
|
11
|
+
e.status.should == 404
|
12
|
+
e.error.should == 'Not Found'
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
@app.not_found('Missing')
|
17
|
+
rescue Halcyon::Exceptions::Base => e
|
18
|
+
e.status.should == 404
|
19
|
+
e.error.should == 'Missing'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
specify "supports numerous standard HTTP request error exceptions with lookup by status code" do
|
24
|
+
begin
|
25
|
+
Halcyon::Server::Base::Exceptions::NotFound.new
|
26
|
+
rescue Halcyon::Exceptions::Base => e
|
27
|
+
e.status.should == 404
|
28
|
+
e.error.should == 'Not Found'
|
29
|
+
end
|
30
|
+
|
31
|
+
Halcyon::Exceptions::HTTP_ERROR_CODES.each do |code, error|
|
32
|
+
begin
|
33
|
+
Halcyon::Server::Base::Exceptions.const_get(error.gsub(/( |\-)/,'')).new
|
34
|
+
rescue Halcyon::Exceptions::Base => e
|
35
|
+
e.status.should == code
|
36
|
+
e.error.should == error
|
37
|
+
end
|
38
|
+
begin
|
39
|
+
Halcyon::Server::Base::Exceptions.lookup(code).new
|
40
|
+
rescue Halcyon::Exceptions::Base => e
|
41
|
+
e.status.should == code
|
42
|
+
e.error.should == error
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
specify "should have a short inheritence chain to make catching generically simple" do
|
48
|
+
begin
|
49
|
+
Halcyon::Server::Base::Exceptions::NotFound.new
|
50
|
+
rescue Halcon::Exceptions::Base => e
|
51
|
+
e.class.to_s.should == 'NotFound'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
context "Halcyon::Server::Router" do
|
2
|
+
|
3
|
+
before(:each) do
|
4
|
+
@app = Specr.new :port => 4000
|
5
|
+
end
|
6
|
+
|
7
|
+
specify "should prepares routes correctly when written correctly" do
|
8
|
+
# routes have been defined for Specr
|
9
|
+
Halcyon::Server::Router.routes.should_not == []
|
10
|
+
Halcyon::Server::Router.routes.length.should > 0
|
11
|
+
end
|
12
|
+
|
13
|
+
specify "should match URIs to the correct route" do
|
14
|
+
Halcyon::Server::Router.route(Rack::MockRequest.env_for('/'))[:action].should == 'index'
|
15
|
+
end
|
16
|
+
|
17
|
+
specify "should use the default route if no matching route is found" do
|
18
|
+
Halcyon::Server::Router.route(Rack::MockRequest.env_for('/erroneous/path'))[:action].should == 'not_found'
|
19
|
+
Halcyon::Server::Router.route(Rack::MockRequest.env_for("/random/#{rand}"))[:action].should == 'not_found'
|
20
|
+
end
|
21
|
+
|
22
|
+
specify "should map params in routes to parameters" do
|
23
|
+
response = Halcyon::Server::Router.route(Rack::MockRequest.env_for('/hello/Matt'))
|
24
|
+
response[:action].should == 'greeter'
|
25
|
+
response[:name].should == 'Matt'
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "should supply arbitrary routing param values included as a param even if not in the URI" do
|
29
|
+
Halcyon::Server::Router.route(Rack::MockRequest.env_for('/'))[:arbitrary].should == 'random'
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
context "Halcyon::Server" do
|
2
|
+
|
3
|
+
before(:each) do
|
4
|
+
@app = Specr.new :port => 4000
|
5
|
+
end
|
6
|
+
|
7
|
+
specify "should dispatch methods according to their respective routes" do
|
8
|
+
Rack::MockRequest.new(@app).get("/hello/Matt")
|
9
|
+
last_line = File.new(@app.instance_variable_get("@config")[:log_file]).readlines.last
|
10
|
+
last_line.should =~ /INFO \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] \(\d+\) Specr#test :: \[200\] .* => greeter \(.+\)/
|
11
|
+
end
|
12
|
+
|
13
|
+
specify "should provide various shorthand methods for simple responses but take custom response values" do
|
14
|
+
response = {:status => 200, :body => 'OK'}
|
15
|
+
@app.ok.should == response
|
16
|
+
@app.success.should == response
|
17
|
+
@app.standard_response.should == response
|
18
|
+
|
19
|
+
@app.ok('').should == {:status => 200, :body => ''}
|
20
|
+
@app.ok(['OK', 'Sure Thang', 'Correcto']).should == {:status => 200, :body => ['OK', 'Sure Thang', 'Correcto']}
|
21
|
+
end
|
22
|
+
|
23
|
+
specify "should handle requests and respond with JSON" do
|
24
|
+
body = JSON.parse(Rack::MockRequest.new(@app).get("/").body)
|
25
|
+
body['status'].should == 200
|
26
|
+
body['body'].should == "Found"
|
27
|
+
end
|
28
|
+
|
29
|
+
specify "should handle requests with param values in the URL" do
|
30
|
+
body = JSON.parse(Rack::MockRequest.new(@app).get("/hello/Matt").body)
|
31
|
+
body['status'].should == 200
|
32
|
+
body['body'].should == "Hello Matt"
|
33
|
+
end
|
34
|
+
|
35
|
+
specify "should route unmatchable requests to the default route and return JSON with appropriate status" do
|
36
|
+
body = JSON.parse(Rack::MockRequest.new(@app).get("/garbage/request/url").body)
|
37
|
+
body['status'].should == 404
|
38
|
+
body['body'].should == "Not Found"
|
39
|
+
end
|
40
|
+
|
41
|
+
specify "should log activity" do
|
42
|
+
prev_line = File.new(@app.instance_variable_get("@config")[:log_file]).readlines.last
|
43
|
+
Rack::MockRequest.new(@app).get("/url/that/will/not/be/found/#{rand}")
|
44
|
+
last_line = File.new(@app.instance_variable_get("@config")[:log_file]).readlines.last
|
45
|
+
last_line.should =~ /INFO \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] \(\d+\) Specr#test :: \[404\] .* => not_found \(.+\)/
|
46
|
+
prev_line.should_not == last_line
|
47
|
+
end
|
48
|
+
|
49
|
+
specify "should create a PID file while running with the correct process ID" do
|
50
|
+
pid_file = @app.instance_variable_get("@config")[:pid_file]
|
51
|
+
File.exist?(pid_file).should be_true
|
52
|
+
File.open(pid_file){|file|file.read.should == "#{$$}\n"}
|
53
|
+
end
|
54
|
+
|
55
|
+
specify "should parse URI query params correctly" do
|
56
|
+
Rack::MockRequest.new(@app).get("/?query=value&lang=en-US")
|
57
|
+
@app.query_params.should == {'query' => 'value', 'lang' => 'en-US'}
|
58
|
+
end
|
59
|
+
|
60
|
+
specify "should parse the URI correctly" do
|
61
|
+
Rack::MockRequest.new(@app).get("http://localhost:4000/slaughterhouse/5")
|
62
|
+
@app.uri.should == '/slaughterhouse/5'
|
63
|
+
|
64
|
+
Rack::MockRequest.new(@app).get("/slaughterhouse/5")
|
65
|
+
@app.uri.should == '/slaughterhouse/5'
|
66
|
+
|
67
|
+
Rack::MockRequest.new(@app).get("")
|
68
|
+
@app.uri.should == '/'
|
69
|
+
end
|
70
|
+
|
71
|
+
specify "should provide a quick way to find out what method the request was performed using" do
|
72
|
+
Rack::MockRequest.new(@app).get("/#{rand}")
|
73
|
+
@app.method.should == :get
|
74
|
+
|
75
|
+
Rack::MockRequest.new(@app).post("/#{rand}")
|
76
|
+
@app.method.should == :post
|
77
|
+
|
78
|
+
Rack::MockRequest.new(@app).put("/#{rand}")
|
79
|
+
@app.method.should == :put
|
80
|
+
|
81
|
+
Rack::MockRequest.new(@app).delete("/#{rand}")
|
82
|
+
@app.method.should == :delete
|
83
|
+
end
|
84
|
+
|
85
|
+
specify "should deny all unacceptable requests" do
|
86
|
+
conf = @app.instance_variable_get("@config")
|
87
|
+
conf[:acceptable_requests] = Halcyon::Server::ACCEPTABLE_REQUESTS
|
88
|
+
|
89
|
+
Rack::MockRequest.new(@app).put("/#{rand}")
|
90
|
+
@app.acceptable_request! rescue Halcyon::Exceptions::Base
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'halcyon/server'
|
2
|
+
require 'rack/mock'
|
3
|
+
|
4
|
+
$test = true
|
5
|
+
|
6
|
+
class Specr < Halcyon::Server::Base
|
7
|
+
|
8
|
+
route do |r|
|
9
|
+
r.match('/hello/:name').to(:action => 'greeter')
|
10
|
+
r.match('/').to(:action => 'index', :arbitrary => 'random')
|
11
|
+
end
|
12
|
+
|
13
|
+
def index(params)
|
14
|
+
ok('Found')
|
15
|
+
end
|
16
|
+
|
17
|
+
def greeter(params)
|
18
|
+
ok("Hello #{params[:name]}")
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: halcyon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.22
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Todd
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2008-01-07 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -50,6 +50,13 @@ extra_rdoc_files:
|
|
50
50
|
files:
|
51
51
|
- lib
|
52
52
|
- Rakefile
|
53
|
+
- spec/halcyon
|
54
|
+
- spec/halcyon/error_spec.rb
|
55
|
+
- spec/halcyon/router_spec.rb
|
56
|
+
- spec/halcyon/server_spec.rb
|
57
|
+
- spec/SPEC
|
58
|
+
- spec/SPEC.html
|
59
|
+
- spec/spec_helper.rb
|
53
60
|
- lib/halcyon
|
54
61
|
- lib/halcyon/client
|
55
62
|
- lib/halcyon/client/base.rb
|