halcyon 0.3.22 → 0.4.0

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