halcyon 0.3.22 → 0.4.0

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 CHANGED
@@ -78,17 +78,17 @@ end
78
78
  namespace 'spec' do
79
79
  desc "generate spec"
80
80
  task :gen do
81
- sh "spec -c -rlib/halcyon -rspec/spec_helper spec/**/* --format s:spec/SPEC --format h:spec/SPEC.html"
81
+ sh "bacon -r~/lib/bacon/output -rlib/halcyon -rtest/spec_helper spec/**/* -s > spec/SPEC"
82
82
  end
83
83
 
84
84
  desc "run rspec"
85
85
  task :run do
86
- sh "spec -c -rlib/halcyon -rspec/spec_helper spec/**/*"
86
+ sh "bacon -r~/lib/bacon/output -rlib/halcyon -rspec/spec_helper spec/**/* -o CTestUnit"
87
87
  end
88
88
 
89
89
  desc "run rspec verbosely"
90
90
  task :verb do
91
- sh "spec -c -rlib/halcyon -rspec/spec_helper spec/**/* --format s"
91
+ sh "bacon -r~/lib/bacon/output -rlib/halcyon -rspec/spec_helper spec/**/* -o CSpecDox"
92
92
  end
93
93
  end
94
94
 
data/bin/halcyon CHANGED
@@ -146,17 +146,22 @@ if !File.exists?("#{options[:app]}.rb")
146
146
  abort "Halcyon did not find the app #{options[:app]}. Check your path and try again."
147
147
  end
148
148
 
149
+ require options[:app]
150
+ appname = File.basename(options[:app]).capitalize.gsub(/_([a-z])/){|m|m[1].chr.capitalize}
149
151
  begin
150
- require options[:app]
151
- app = Object.const_get(File.basename(options[:app]).capitalize.gsub(/_([a-z])/){|m|m[1].chr.capitalize})
152
+ app = Object.const_get(appname)
152
153
  rescue NameError => e
153
- abort "Unable to load #{File.basename(options[:app]).capitalize.gsub(/_([a-z])/){|m|m[1].chr.capitalize}}. Please ensure your server is so named."
154
+ abort "Unable to load #{appname}. Please ensure your server is so named."
154
155
  end
155
156
 
156
157
  #--
157
158
  # prepare server
158
159
  #++
159
160
  begin
161
+ # attempt to require the server
162
+ begin; require options[:server].capitalize; rescue LoadError; end
163
+
164
+ # get the appropriate Rack Handler
160
165
  server = Rack::Handler.const_get(options[:server].capitalize)
161
166
  rescue NameError
162
167
  servers = {
@@ -164,7 +169,8 @@ rescue NameError
164
169
  'fastcgi' => 'FastCGI',
165
170
  'lsws' => 'LSWS',
166
171
  'mongrel' => 'Mongrel',
167
- 'webrick' => 'WEBrick'
172
+ 'webrick' => 'WEBrick',
173
+ 'thin' => 'Thin'
168
174
  }
169
175
  abort "Unsupported server (missing Rack Handler). Did you mean to specify #{options[:server]}?" unless servers.key? options[:server]
170
176
  server = Rack::Handler.const_get(servers[options[:server]])
@@ -196,4 +202,4 @@ end
196
202
  # start server
197
203
  #++
198
204
 
199
- server.run app, :Port => options[:port]
205
+ server.run app, :Port => Integer(options[:port])
data/lib/halcyon.rb CHANGED
@@ -10,18 +10,14 @@ $:.unshift File.dirname(__FILE__)
10
10
  # dependencies
11
11
  #++
12
12
 
13
- %w(halcyon/support/hashext).each {|dep|require dep}
14
-
15
- class Hash
16
- include HashExt::Keys
17
- end
13
+ %w(rubygems merb/core_ext).each {|dep|require dep}
18
14
 
19
15
  #--
20
16
  # module
21
17
  #++
22
18
 
23
19
  module Halcyon
24
- VERSION = [0,3,22]
20
+ VERSION = [0,4,0]
25
21
  def self.version
26
22
  VERSION.join('.')
27
23
  end
@@ -43,6 +39,16 @@ module Halcyon
43
39
  abort "READ THE DAMNED RDOCS!"
44
40
  end
45
41
 
42
+ #--
43
+ # Module Autoloading
44
+ #++
45
+
46
+ class Server
47
+ module Auth
48
+ autoload :Basic, 'halcyon/server/auth/basic'
49
+ end
50
+ end
51
+
46
52
  end
47
53
 
48
54
  %w(halcyon/exceptions).each {|dep|require dep}
@@ -176,37 +176,29 @@ module Halcyon
176
176
  #++
177
177
 
178
178
  # Performs a GET request on the URI specified.
179
- def get(uri)
179
+ def get(uri, headers={})
180
180
  req = Net::HTTP::Get.new(uri)
181
- req["Content-Type"] = CONTENT_TYPE
182
- req["User-Agent"] = USER_AGENT
183
- request(req)
181
+ request(req, headers)
184
182
  end
185
183
 
186
184
  # Performs a POST request on the URI specified.
187
- def post(uri, data)
185
+ def post(uri, data, headers={})
188
186
  req = Net::HTTP::Post.new(uri)
189
- req["Content-Type"] = CONTENT_TYPE
190
- req["User-Agent"] = USER_AGENT
191
187
  req.body = format_body(data)
192
- request(req)
188
+ request(req, headers)
193
189
  end
194
190
 
195
191
  # Performs a DELETE request on the URI specified.
196
- def delete(uri)
192
+ def delete(uri, headers={})
197
193
  req = Net::HTTP::Delete.new(uri)
198
- req["Content-Type"] = CONTENT_TYPE
199
- req["User-Agent"] = USER_AGENT
200
- request(req)
194
+ request(req, headers)
201
195
  end
202
196
 
203
197
  # Performs a PUT request on the URI specified.
204
- def put(uri, data)
198
+ def put(uri, data, headers={})
205
199
  req = Net::HTTP::Put.new(uri)
206
- req["Content-Type"] = CONTENT_TYPE
207
- req["User-Agent"] = USER_AGENT
208
200
  req.body = format_body(data)
209
- request(req)
201
+ request(req, headers)
210
202
  end
211
203
 
212
204
  private
@@ -223,13 +215,26 @@ module Halcyon
223
215
  # (defined in Halcyon::Exceptions) which all inherit from
224
216
  # +Halcyon::Exceptions::Base+. It is up to the client to handle these
225
217
  # exceptions specifically.
226
- def request(req)
218
+ def request(req, headers={})
219
+ # define essential headers for Halcyon::Server's picky requirements
220
+ req["Content-Type"] = CONTENT_TYPE
221
+ req["User-Agent"] = USER_AGENT
222
+
223
+ # apply provided headers
224
+ headers.each do |pair|
225
+ header, value = pair
226
+ req[header] = value
227
+ end
228
+
229
+ # provide hook for modifying the headers
230
+ req = headers(req) if respond_to? :headers
231
+
227
232
  # prepare and send HTTP request
228
233
  res = Net::HTTP.start(@uri.host, @uri.port) {|http|http.request(req)}
229
234
 
230
235
  # parse response
231
236
  body = JSON.parse(res.body)
232
- body.symbolize_keys! if body.respond_to? :symbolize_keys!
237
+ body.symbolize_keys!
233
238
 
234
239
  # handle non-successes
235
240
  raise Halcyon::Client::Base::Exceptions.lookup(body[:status]).new unless res.kind_of? Net::HTTPSuccess
@@ -245,7 +250,9 @@ module Halcyon
245
250
  # format according to Net::HTTP for sending through as a Hash.
246
251
  def format_body(data)
247
252
  data = {:body => data} unless data.is_a? Hash
248
- data.map{|key,value|"&#{key}=#{value}&"}.join
253
+ data.symbolize_keys!
254
+ # uses the Merb Hash#to_params method defined in merb/core_ext.
255
+ data.to_params
249
256
  end
250
257
 
251
258
  end
@@ -21,7 +21,7 @@ module Halcyon
21
21
  class_eval(
22
22
  "class #{body.gsub(/( |\-)/,'')} < Halcyon::Exceptions::Base\n"+
23
23
  " def initialize(s=#{status}, e='#{body}')\n"+
24
- " super s, e\n"+
24
+ " super\n"+
25
25
  " end\n"+
26
26
  "end"
27
27
  );
@@ -19,6 +19,7 @@ module Halcyon
19
19
  def initialize(status, error)
20
20
  @status = status
21
21
  @error = error
22
+ super "[#{@status}] #{@error}"
22
23
  end
23
24
  end
24
25
 
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env ruby
2
+ #--
3
+ # Created by Matt Todd on 2008-01-16.
4
+ # Copyright (c) 2008. All rights reserved.
5
+ #++
6
+
7
+ #--
8
+ # module
9
+ #++
10
+
11
+ module Halcyon
12
+ class Server
13
+ module Auth
14
+
15
+ # = Introduction
16
+ #
17
+ # The Auth::Basic class provides an alternative to the Server::Base
18
+ # class for creating servers with HTTP Basic Authentication built in.
19
+ #
20
+ # == Usage
21
+ #
22
+ # In order to provide for HTTP Basic Authentication in your server,
23
+ # it would first need to inherit from this class instead of Server::Base
24
+ # and then provide a method to check for the existence of the credentials
25
+ # and respond accordingly. This looks like the following:
26
+ #
27
+ # class AuthenticatedApp < Halcyon::Server::Auth::Basic
28
+ # def basic_authorization(username, password)
29
+ # [username, password] == ['rupert', 'secret']
30
+ # end
31
+ # # write normal Halcyon server app here
32
+ # end
33
+ #
34
+ # The credentials passed to the +basic_authorization+ method are pulled
35
+ # from the appropriate Authorization header value and parsed from the
36
+ # base64 values. If no Authorization header value is passed, an exception
37
+ # is thrown resulting in the appropriate response to the client.
38
+ class Basic < Server::Base
39
+
40
+ AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION']
41
+
42
+ # Determines the appropriate HTTP Authorization header to refer to when
43
+ # plucking out the header for processing.
44
+ def authorization_key
45
+ @authorization_key ||= AUTHORIZATION_KEYS.detect{|k|@env.has_key?(k)}
46
+ end
47
+
48
+ alias :_run :run
49
+
50
+ # Ensures that the HTTP Authentication header is included, the Basic
51
+ # scheme is being used, and the credentials pass the +basic_auth+
52
+ # test. If any of these fail, an Unauthorized exception is raised
53
+ # (except for non-Basic schemes), otherwise the +route+ is +run+
54
+ # normally.
55
+ #
56
+ # See the documentation for the +basic_auth+ class method for details
57
+ # concerning the credentials and action inclusion/exclusion.
58
+ def run(route)
59
+ # test credentials if the action is one specified to be tested
60
+ if ((@@auth[:except].nil? && @@auth[:only].nil?) || # the default is to test if no restrictions
61
+ (!@@auth[:only].nil? && @@auth[:only].include?(route[:action].to_sym)) || # but if the action is in the :only directive, test
62
+ (!@@auth[:except].nil? && !@@auth[:except].include?(route[:action].to_sym))) # or if the action is not in the :except directive, test
63
+
64
+ # make sure there's an authorization header
65
+ raise Base::Exceptions::Unauthorized.new unless !authorization_key.nil?
66
+
67
+ # make sure the request is via the Basic protocol
68
+ scheme = @env[authorization_key].split.first.downcase.to_sym
69
+ raise Base::Exceptions::BadRequest.new unless scheme == :basic
70
+
71
+ # make sure the credentials pass the test
72
+ credentials = @env[authorization_key].split.last.unpack("m*").first.split(':', 2)
73
+ raise Base::Exceptions::Unauthorized.new unless @@auth[:method].call(*credentials)
74
+ end
75
+
76
+ # success, so run the route normally
77
+ _run(route)
78
+ rescue Halcyon::Exceptions::Base => e
79
+ @logger.warn "#{uri} => #{e.error}"
80
+ # handles all content error exceptions
81
+ @res.status = e.status
82
+ {:status => e.status, :body => e.error}
83
+ end
84
+
85
+ # Provides a way to define a test as well as set limits on what is
86
+ # tested for Basic Authorization. This method should be called in the
87
+ # definition of the server. A simple example would look like:
88
+ #
89
+ # class Servr < Halcyon::Server::Auth::Basic
90
+ # basic_auth :only => [:grant] do |user, pass|
91
+ # # test credentials
92
+ # end
93
+ # # routes and actions follow...
94
+ # end
95
+ #
96
+ # Two acceptable options include <tt>:only</tt> and <tt>:except</tt>.
97
+ def self.basic_auth(options={}, &proc)
98
+ instance_eval do
99
+ @@auth = options.merge(:method => proc)
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -17,10 +17,11 @@ module Halcyon
17
17
  class Server
18
18
 
19
19
  DEFAULT_OPTIONS = {
20
+ :root => Dir.pwd,
20
21
  :environment => 'none',
21
22
  :port => 9267,
22
23
  :host => 'localhost',
23
- :server => 'mongrel',
24
+ :server => Gem.searcher.find('thin').nil? ? 'mongrel' : 'thin',
24
25
  :pid_file => '/var/run/halcyon.{server}.{app}.{port}.pid',
25
26
  :log_file => '/var/log/halcyon.{app}.log',
26
27
  :log_level => 'info',
@@ -66,8 +67,8 @@ module Halcyon
66
67
  # end
67
68
  #
68
69
  # Two routes are (effectively) defined here, the first being to watch for
69
- # all requests in the format +/hello/:name+ where the word pattern is
70
- # stored and transmitted as the appropriate keys in the params hash.
70
+ # all requests in the format <tt>/hello/:name</tt> where the word pattern
71
+ # is stored and transmitted as the appropriate keys in the params hash.
71
72
  #
72
73
  # Once we've got our inputs specified, we can start to handle requests:
73
74
  #
@@ -76,18 +77,18 @@ module Halcyon
76
77
  # r.match('/hello/:name').to(:action => 'greet')
77
78
  # {:action => 'not_found'} # default route
78
79
  # end
79
- # def greet(p); {:status=>200, :body=>"Hi #{p[:name]}"}; end
80
+ # def greet; {:status=>200, :body=>"Hi #{params[:name]}"}; end
80
81
  # end
81
82
  #
82
83
  # You will notice that we only define the method +greet+ and that it
83
84
  # returns a Hash object containing a +status+ code and +body+ content.
84
85
  # This is the most basic way to send data, but if all you're doing is
85
86
  # replying that the request was successful and you have data to return,
86
- # the method +ok+ (an alias of +standard_response+) with the +body+ param
87
- # as its sole parameter is sufficient.
87
+ # the method +ok+ (an alias of <tt>standard_response</tt>) with the +body+
88
+ # param as its sole parameter is sufficient.
88
89
  #
89
90
  #
90
- # def greet(p); ok("Hi #{p[:name]}"); end
91
+ # def greet; ok("Hi #{params[:name]}"); end
91
92
  #
92
93
  # You'll also notice that there's no method called +not_found+; this is
93
94
  # because it is already defined and behaves almost exactly like the +ok+
@@ -99,9 +100,13 @@ module Halcyon
99
100
  # route actually matches, so it doesn't need any of the extra path to match
100
101
  # against.
101
102
  #
103
+ # Lastly, the use of +params+ inside the method is simply a method call
104
+ # to a hash of the parameters gleaned from the route, such as +:name+ or
105
+ # any other variables passed to it.
106
+ #
102
107
  # == The Filesystem
103
108
  #
104
- # It's important to note that the +halcyon+ commandline tool expects the to
109
+ # It's important to note that the +halcyon+ commandline tool expects to
105
110
  # find your server inheriting +Halcyon::Server::Base+ with the same exact
106
111
  # name as its filename, though with special rules.
107
112
  #
@@ -109,7 +114,8 @@ module Halcyon
109
114
  # that your server's class name be +AppServer+ as it capitalizes each word
110
115
  # and removes all underscores, etc.
111
116
  #
112
- # Keep this in mind when naming your class and your file.
117
+ # Keep this in mind when naming your class and your file, though this
118
+ # restriction is only temporary.
113
119
  #
114
120
  # NOTE: This really isn't a necessary step if you write your own deployment
115
121
  # script instead of using the +halcyon+ commandline tool (as it is simply
@@ -175,11 +181,11 @@ module Halcyon
175
181
  # Most of your requests will have all the data it needs inside of the
176
182
  # +params+ you receive for your action, but for POST and PUT requests
177
183
  # (you are being RESTful, right?) you will need to retrieve your data
178
- # from the +POST+ property of the +@req+ request. Here's how:
184
+ # from the method +post+. Here's how:
179
185
  #
180
- # @req.POST['key'] => "value"
186
+ # post[:key] => "value"
181
187
  #
182
- # As you can see, keys specifically are strings and values as well. What
188
+ # As you can see, keys specifically are symbols and values as well. What
183
189
  # this means is that your POST data that you send to the server needs to
184
190
  # be careful to provide a flat Hash (if anything other than a Hash is
185
191
  # passed, it is packed up into a hash similar to +{:body=>data}+) or at
@@ -188,7 +194,7 @@ module Halcyon
188
194
  # (though this could change). Here's how you would reconstruct your
189
195
  # special hash:
190
196
  #
191
- # value = JSON.parse(@req.POST['key'])
197
+ # value = JSON.parse(post[:key])
192
198
  #
193
199
  # That will take care of reconstructing your Hash.
194
200
  #
@@ -253,7 +259,7 @@ module Halcyon
253
259
  # that a proper JSON response may be rendered by +call+.
254
260
  #
255
261
  # With this in mind, it is preferred that, for any errors that should
256
- # result in a given HTTP Response code other than 200, an appropriate
262
+ # result in a given HTTP Response code other than 2xx, an appropriate
257
263
  # exception should be thrown which is then handled by this method's
258
264
  # rescue clause.
259
265
  #
@@ -309,32 +315,37 @@ module Halcyon
309
315
  # requests.
310
316
  #
311
317
  # It is preferred for these methods to throw Exceptions::Base exceptions
312
- # (or one of its inheriters) instead of handling them manually.
318
+ # (or one of its inheriters) instead of handling them manually. This
319
+ # ensures that the actual action is not run when in fact it shouldn't,
320
+ # otherwise you could be allowing unauthenticated users privileged
321
+ # information or allowing them to perform destructive actions.
313
322
  def run(route)
314
323
  # make sure the request meets our expectations
315
324
  acceptable_request! unless $debug || $test
316
325
 
317
326
  # pull params
318
- params = route.reject{|key, val| [:action, :module].include? key}
319
- params.merge!(query_params)
327
+ @params = route.reject{|key, val| [:action, :module].include? key}
328
+ @params.merge!(query_params)
320
329
 
321
330
  # pre call hook
322
- before_call(route, params) if respond_to? :before_call
331
+ before_call if respond_to? :before_call
323
332
 
324
333
  # handle module actions differently than non-module actions
325
334
  if route[:module].nil?
326
335
  # call action
327
- res = send(route[:action], params)
336
+ res = send(route[:action])
328
337
  else
329
338
  # call module action
330
339
  mod = self.dup
331
340
  mod.instance_eval(&(@@modules[route[:module].to_sym]))
332
- res = mod.send(route[:action], params)
341
+ res = mod.send(route[:action])
333
342
  end
334
343
 
335
344
  # after call hook
336
345
  after_call if respond_to? :after_call
337
346
 
347
+ @params = {}
348
+
338
349
  res
339
350
  rescue Halcyon::Exceptions::Base => e
340
351
  @logger.warn "#{uri} => #{e.error}"
@@ -399,8 +410,13 @@ module Halcyon
399
410
  @pid = File.new(@config[:pid_file].gsub('{n}', server_cluster_number), "w", 0644)
400
411
  @pid << "#{$$}\n"; @pid.close
401
412
 
402
- # log existence and ready status
413
+ # log existence
403
414
  @logger.info "PID file created. PID is #{$$}."
415
+
416
+ # call startup callback if defined
417
+ startup if respond_to? :startup
418
+
419
+ # log ready state
404
420
  @logger.info "Started. Awaiting connectivity. Listening on #{@config[:port]}..."
405
421
 
406
422
  # trap signals to die (when killed by the user) gracefully
@@ -424,7 +440,13 @@ module Halcyon
424
440
 
425
441
  # Closes the logger and deletes the PID file.
426
442
  def clean_up
443
+ # don't try to clean up what's cleaned up already
427
444
  return if defined? @cleaned_up
445
+
446
+ # run shutdown hook if defined
447
+ shutdown if respond_to? :shutdown
448
+
449
+ # close logger, delete PID file, flag clean state
428
450
  @logger.close
429
451
  File.delete(@pid.path) if File.exist?(@pid.path)
430
452
  @cleaned_up = true
@@ -528,12 +550,12 @@ module Halcyon
528
550
  #
529
551
  # The accepted level values are as follows:
530
552
  #
531
- # debug
532
- # info
533
- # warn
534
- # error
535
- # fatal
536
- # unknown
553
+ # * debug
554
+ # * info
555
+ # * warn
556
+ # * error
557
+ # * fatal
558
+ # * unknown
537
559
  #
538
560
  # These are the exact way you can refer to the logger level you'd like to
539
561
  # log at from all points of option specification (listed above in order
@@ -694,9 +716,14 @@ module Halcyon
694
716
  raise Exceptions::NotFound.new(404, body)
695
717
  end
696
718
 
719
+ # Returns the params of the current request, set in the +run+ method.
720
+ def params
721
+ @params
722
+ end
723
+
697
724
  # Returns the params following the ? in a given URL as a hash
698
725
  def query_params
699
- @env['QUERY_STRING'].split(/&/).inject({}){|h,kp| k,v = kp.split(/=/); h[k] = v; h}
726
+ @env['QUERY_STRING'].split(/&/).inject({}){|h,kp| k,v = kp.split(/=/); h[k] = v; h}.symbolize_keys!
700
727
  end
701
728
 
702
729
  # Returns the URI requested
@@ -706,11 +733,41 @@ module Halcyon
706
733
  URI.parse(@env['REQUEST_URI'] || @env['PATH_INFO']).path
707
734
  end
708
735
 
709
- # Returns the Request Method as a lowercase symbol
736
+ # Returns the Request Method as a lowercase symbol.
737
+ #
738
+ # One useful situation for this method would be similar to this:
739
+ #
740
+ # case method
741
+ # when :get
742
+ # # perform reading operations
743
+ # when :post
744
+ # # perform updating operations
745
+ # when :put
746
+ # # perform creating operations
747
+ # when :delete
748
+ # # perform deleting options
749
+ # end
750
+ #
751
+ # It can also be used in many other cases, like throwing an exception if
752
+ # an action is called with an unexpected method.
710
753
  def method
711
754
  @env['REQUEST_METHOD'].downcase.to_sym
712
755
  end
713
756
 
757
+ # Returns the POST data hash, making the keys symbols first.
758
+ #
759
+ # Use like <tt>post[:post_param]</tt>.
760
+ def post
761
+ @req.POST.symbolize_keys!
762
+ end
763
+
764
+ # Returns the GET data hash, making the keys symbols first.
765
+ #
766
+ # Use like <tt>get[:get_param]</tt>.
767
+ def get
768
+ @req.GET.symbolize_keys!
769
+ end
770
+
714
771
  end
715
772
 
716
773
  end
@@ -21,7 +21,7 @@ module Halcyon
21
21
  class_eval(
22
22
  "class #{body.gsub(/( |\-)/,'')} < Halcyon::Exceptions::Base\n"+
23
23
  " def initialize(s=#{status}, e='#{body}')\n"+
24
- " super s, e\n"+
24
+ " super\n"+
25
25
  " end\n"+
26
26
  "end"
27
27
  );
@@ -8,7 +8,7 @@
8
8
  #++
9
9
 
10
10
  begin
11
- %w(rubygems merb/core_ext merb/router).each {|dep|require dep}
11
+ %w(rubygems merb/core_ext merb/router uri).each {|dep|require dep}
12
12
  rescue LoadError => e
13
13
  abort "Merb must be installed for Routing to function. Please install Merb."
14
14
  end
@@ -1,10 +1,10 @@
1
- context "Halcyon::Server Errors" do
1
+ describe "Halcyon::Server Errors" do
2
2
 
3
- before(:each) do
3
+ before do
4
4
  @app = Specr.new :port => 4000
5
5
  end
6
6
 
7
- specify "should provide shorthand methods for errors which should throw an appropriate exception" do
7
+ it "should provide shorthand methods for errors which should throw an appropriate exception" do
8
8
  begin
9
9
  @app.not_found
10
10
  rescue Halcyon::Exceptions::Base => e
@@ -20,7 +20,7 @@ context "Halcyon::Server Errors" do
20
20
  end
21
21
  end
22
22
 
23
- specify "supports numerous standard HTTP request error exceptions with lookup by status code" do
23
+ it "supports numerous standard HTTP request error exceptions with lookup by status code" do
24
24
  begin
25
25
  Halcyon::Server::Base::Exceptions::NotFound.new
26
26
  rescue Halcyon::Exceptions::Base => e
@@ -44,7 +44,7 @@ context "Halcyon::Server Errors" do
44
44
  end
45
45
  end
46
46
 
47
- specify "should have a short inheritence chain to make catching generically simple" do
47
+ it "should have a short inheritence chain to make catching generically simple" do
48
48
  begin
49
49
  Halcyon::Server::Base::Exceptions::NotFound.new
50
50
  rescue Halcon::Exceptions::Base => e
@@ -1,31 +1,31 @@
1
- context "Halcyon::Server::Router" do
1
+ describe "Halcyon::Server::Router" do
2
2
 
3
- before(:each) do
3
+ before do
4
4
  @app = Specr.new :port => 4000
5
5
  end
6
6
 
7
- specify "should prepares routes correctly when written correctly" do
7
+ it "should prepares routes correctly when written correctly" do
8
8
  # routes have been defined for Specr
9
- Halcyon::Server::Router.routes.should_not == []
9
+ Halcyon::Server::Router.routes.should.not == []
10
10
  Halcyon::Server::Router.routes.length.should > 0
11
11
  end
12
12
 
13
- specify "should match URIs to the correct route" do
13
+ it "should match URIs to the correct route" do
14
14
  Halcyon::Server::Router.route(Rack::MockRequest.env_for('/'))[:action].should == 'index'
15
15
  end
16
16
 
17
- specify "should use the default route if no matching route is found" do
17
+ it "should use the default route if no matching route is found" do
18
18
  Halcyon::Server::Router.route(Rack::MockRequest.env_for('/erroneous/path'))[:action].should == 'not_found'
19
19
  Halcyon::Server::Router.route(Rack::MockRequest.env_for("/random/#{rand}"))[:action].should == 'not_found'
20
20
  end
21
21
 
22
- specify "should map params in routes to parameters" do
22
+ it "should map params in routes to parameters" do
23
23
  response = Halcyon::Server::Router.route(Rack::MockRequest.env_for('/hello/Matt'))
24
24
  response[:action].should == 'greeter'
25
25
  response[:name].should == 'Matt'
26
26
  end
27
27
 
28
- specify "should supply arbitrary routing param values included as a param even if not in the URI" do
28
+ it "should supply arbitrary routing param values included as a param even if not in the URI" do
29
29
  Halcyon::Server::Router.route(Rack::MockRequest.env_for('/'))[:arbitrary].should == 'random'
30
30
  end
31
31
 
@@ -1,16 +1,16 @@
1
- context "Halcyon::Server" do
1
+ describe "Halcyon::Server" do
2
2
 
3
- before(:each) do
3
+ before do
4
4
  @app = Specr.new :port => 4000
5
5
  end
6
6
 
7
- specify "should dispatch methods according to their respective routes" do
7
+ it "should dispatch methods according to their respective routes" do
8
8
  Rack::MockRequest.new(@app).get("/hello/Matt")
9
9
  last_line = File.new(@app.instance_variable_get("@config")[:log_file]).readlines.last
10
10
  last_line.should =~ /INFO \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] \(\d+\) Specr#test :: \[200\] .* => greeter \(.+\)/
11
11
  end
12
12
 
13
- specify "should provide various shorthand methods for simple responses but take custom response values" do
13
+ it "should provide various shorthand methods for simple responses but take custom response values" do
14
14
  response = {:status => 200, :body => 'OK'}
15
15
  @app.ok.should == response
16
16
  @app.success.should == response
@@ -20,44 +20,44 @@ context "Halcyon::Server" do
20
20
  @app.ok(['OK', 'Sure Thang', 'Correcto']).should == {:status => 200, :body => ['OK', 'Sure Thang', 'Correcto']}
21
21
  end
22
22
 
23
- specify "should handle requests and respond with JSON" do
23
+ it "should handle requests and respond with JSON" do
24
24
  body = JSON.parse(Rack::MockRequest.new(@app).get("/").body)
25
25
  body['status'].should == 200
26
26
  body['body'].should == "Found"
27
27
  end
28
28
 
29
- specify "should handle requests with param values in the URL" do
29
+ it "should handle requests with param values in the URL" do
30
30
  body = JSON.parse(Rack::MockRequest.new(@app).get("/hello/Matt").body)
31
31
  body['status'].should == 200
32
32
  body['body'].should == "Hello Matt"
33
33
  end
34
34
 
35
- specify "should route unmatchable requests to the default route and return JSON with appropriate status" do
35
+ it "should route unmatchable requests to the default route and return JSON with appropriate status" do
36
36
  body = JSON.parse(Rack::MockRequest.new(@app).get("/garbage/request/url").body)
37
37
  body['status'].should == 404
38
38
  body['body'].should == "Not Found"
39
39
  end
40
40
 
41
- specify "should log activity" do
41
+ it "should log activity" do
42
42
  prev_line = File.new(@app.instance_variable_get("@config")[:log_file]).readlines.last
43
43
  Rack::MockRequest.new(@app).get("/url/that/will/not/be/found/#{rand}")
44
44
  last_line = File.new(@app.instance_variable_get("@config")[:log_file]).readlines.last
45
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
46
+ prev_line.should.not == last_line
47
47
  end
48
48
 
49
- specify "should create a PID file while running with the correct process ID" do
49
+ it "should create a PID file while running with the correct process ID" do
50
50
  pid_file = @app.instance_variable_get("@config")[:pid_file]
51
- File.exist?(pid_file).should be_true
51
+ File.exist?(pid_file).should.be.true?
52
52
  File.open(pid_file){|file|file.read.should == "#{$$}\n"}
53
53
  end
54
54
 
55
- specify "should parse URI query params correctly" do
55
+ it "should parse URI query params correctly" do
56
56
  Rack::MockRequest.new(@app).get("/?query=value&lang=en-US")
57
- @app.query_params.should == {'query' => 'value', 'lang' => 'en-US'}
57
+ @app.query_params.should == {:query => 'value', :lang => 'en-US'}
58
58
  end
59
59
 
60
- specify "should parse the URI correctly" do
60
+ it "should parse the URI correctly" do
61
61
  Rack::MockRequest.new(@app).get("http://localhost:4000/slaughterhouse/5")
62
62
  @app.uri.should == '/slaughterhouse/5'
63
63
 
@@ -68,7 +68,7 @@ context "Halcyon::Server" do
68
68
  @app.uri.should == '/'
69
69
  end
70
70
 
71
- specify "should provide a quick way to find out what method the request was performed using" do
71
+ it "should provide a quick way to find out what method the request was performed using" do
72
72
  Rack::MockRequest.new(@app).get("/#{rand}")
73
73
  @app.method.should == :get
74
74
 
@@ -82,12 +82,24 @@ context "Halcyon::Server" do
82
82
  @app.method.should == :delete
83
83
  end
84
84
 
85
- specify "should deny all unacceptable requests" do
85
+ it "should provide convenient access to GET and POST data" do
86
+ Rack::MockRequest.new(@app).get("/#{rand}?foo=bar")
87
+ @app.get[:foo].should == 'bar'
88
+
89
+ Rack::MockRequest.new(@app).post("/#{rand}", :input => {:foo => 'bar'}.to_params)
90
+ @app.post[:foo].should == 'bar'
91
+ end
92
+
93
+ it "should deny all unacceptable requests" do
86
94
  conf = @app.instance_variable_get("@config")
87
95
  conf[:acceptable_requests] = Halcyon::Server::ACCEPTABLE_REQUESTS
88
96
 
89
- Rack::MockRequest.new(@app).put("/#{rand}")
97
+ Rack::MockRequest.new(@app).get("/#{rand}")
90
98
  @app.acceptable_request! rescue Halcyon::Exceptions::Base
91
99
  end
92
100
 
101
+ it "should record the correct environment details" do
102
+ @app.instance_eval { @config[:root].should == Dir.pwd }
103
+ end
104
+
93
105
  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.22
4
+ version: 0.4.0
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: 2008-01-07 00:00:00 -05:00
12
+ date: 2008-02-10 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -54,8 +54,6 @@ files:
54
54
  - spec/halcyon/error_spec.rb
55
55
  - spec/halcyon/router_spec.rb
56
56
  - spec/halcyon/server_spec.rb
57
- - spec/SPEC
58
- - spec/SPEC.html
59
57
  - spec/spec_helper.rb
60
58
  - lib/halcyon
61
59
  - lib/halcyon/client
@@ -65,12 +63,12 @@ files:
65
63
  - lib/halcyon/client.rb
66
64
  - lib/halcyon/exceptions.rb
67
65
  - lib/halcyon/server
66
+ - lib/halcyon/server/auth
67
+ - lib/halcyon/server/auth/basic.rb
68
68
  - lib/halcyon/server/base.rb
69
69
  - lib/halcyon/server/exceptions.rb
70
70
  - lib/halcyon/server/router.rb
71
71
  - lib/halcyon/server.rb
72
- - lib/halcyon/support
73
- - lib/halcyon/support/hashext.rb
74
72
  - lib/halcyon.rb
75
73
  has_rdoc: true
76
74
  homepage: http://halcyon.rubyforge.org
@@ -1,59 +0,0 @@
1
- # Pulled out from http://dev.rubyonrails.org/browser/trunk/activesupport/lib/active_support/core_ext/hash/keys.rb
2
- module HashExt #:nodoc:
3
- module Keys #:nodoc:
4
- # Return a new hash with all keys converted to strings.
5
- def stringify_keys
6
- inject({}) do |options, (key, value)|
7
- options[key.to_s] = value
8
- options
9
- end
10
- end
11
-
12
- # Destructively convert all keys to strings.
13
- def stringify_keys!
14
- keys.each do |key|
15
- unless key.class.to_s == "String" # weird hack to make the tests run when string_ext_test.rb is also running
16
- self[key.to_s] = self[key]
17
- delete(key)
18
- end
19
- end
20
- self
21
- end
22
-
23
- # Return a new hash with all keys converted to symbols.
24
- def symbolize_keys
25
- inject({}) do |options, (key, value)|
26
- value = value.symbolize_keys if value.respond_to? :symbolize_keys # recursive # MT 2007-12-04
27
- options[key.to_sym || key] = value
28
- options
29
- end
30
- end
31
-
32
- # Destructively convert all keys to symbols.
33
- def symbolize_keys!
34
- keys.each do |key|
35
- unless key.is_a?(Symbol) || (new_key = key.to_sym).nil?
36
- self[new_key] = self[key]
37
- self[new_key].symbolize_keys! if self[new_key].respond_to? :symbolize_keys!
38
- delete(key)
39
- end
40
- end
41
- self
42
- end
43
-
44
- alias_method :to_options, :symbolize_keys
45
- alias_method :to_options!, :symbolize_keys!
46
-
47
- # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
48
- # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbol
49
- # as keys, this will fail.
50
- # examples:
51
- # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
52
- # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): years, name"
53
- # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
54
- def assert_valid_keys(*valid_keys)
55
- unknown_keys = keys - [valid_keys].flatten
56
- raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
57
- end
58
- end
59
- end
data/spec/SPEC DELETED
@@ -1,29 +0,0 @@
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 DELETED
@@ -1,237 +0,0 @@
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">&nbsp;</p>
174
- <p id="duration">&nbsp;</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>