halcyon 0.5.3 → 0.5.4
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/lib/halcyon.rb +2 -1
- data/lib/halcyon/application.rb +1 -1
- data/lib/halcyon/client.rb +45 -5
- data/lib/halcyon/client/ssl.rb +3 -27
- data/lib/rack/jsonp.rb +1 -1
- data/lib/rack/post_body_content_type_parsers.rb +38 -0
- data/spec/halcyon/application_spec.rb +5 -0
- data/spec/halcyon/client_spec.rb +42 -1
- data/spec/rack/jsonp.rb +31 -0
- data/spec/rack/post_body_content_type_parsers_spec.rb +37 -0
- data/spec/spec_helper.rb +12 -0
- data/support/generators/halcyon/templates/runner.ru +5 -0
- data/support/generators/halcyon_flat/templates/runner.ru +4 -0
- metadata +6 -2
data/lib/halcyon.rb
CHANGED
@@ -15,7 +15,7 @@ $:.unshift File.dirname(__FILE__)
|
|
15
15
|
#
|
16
16
|
module Halcyon
|
17
17
|
|
18
|
-
VERSION = [0,5,
|
18
|
+
VERSION = [0,5,4] unless defined?(Halcyon::VERSION)
|
19
19
|
|
20
20
|
autoload :Application, 'halcyon/application'
|
21
21
|
autoload :Client, 'halcyon/client'
|
@@ -81,5 +81,6 @@ Object.send(:include, Halcyon::Logging::Helpers)
|
|
81
81
|
module Rack
|
82
82
|
|
83
83
|
autoload :JSONP, 'rack/jsonp'
|
84
|
+
autoload :PostBodyContentTypeParsers, 'rack/post_body_content_type_parsers'
|
84
85
|
|
85
86
|
end
|
data/lib/halcyon/application.rb
CHANGED
@@ -167,7 +167,7 @@ module Halcyon
|
|
167
167
|
when String
|
168
168
|
# pulled from URL, so camelize (from extlib) and symbolize first
|
169
169
|
begin
|
170
|
-
Object.
|
170
|
+
Object.full_const_get(route[:controller].to_const_string).new(env)
|
171
171
|
rescue NameError => e
|
172
172
|
raise NotFound.new
|
173
173
|
end
|
data/lib/halcyon/client.rb
CHANGED
@@ -43,8 +43,11 @@ module Halcyon
|
|
43
43
|
|
44
44
|
USER_AGENT = "JSON/#{JSON::VERSION} Compatible (en-US) Halcyon::Client/#{Halcyon.version}".freeze
|
45
45
|
CONTENT_TYPE = "application/x-www-form-urlencoded".freeze
|
46
|
+
ACCEPT = "application/json, */*".freeze
|
47
|
+
|
46
48
|
DEFAULT_OPTIONS = {
|
47
|
-
:raise_exceptions => false
|
49
|
+
:raise_exceptions => false,
|
50
|
+
:encode_post_body_as_json => false
|
48
51
|
}
|
49
52
|
|
50
53
|
attr_accessor :uri # The server URI
|
@@ -103,6 +106,7 @@ module Halcyon
|
|
103
106
|
# except that it is not executed in a block.
|
104
107
|
#
|
105
108
|
# The differences are purely semantic and of personal taste.
|
109
|
+
#
|
106
110
|
def initialize(uri, headers = {})
|
107
111
|
self.uri = URI.parse(uri)
|
108
112
|
self.headers = headers
|
@@ -112,21 +116,36 @@ module Halcyon
|
|
112
116
|
end
|
113
117
|
end
|
114
118
|
|
119
|
+
# Sets the option to raise exceptions when the response from the server is
|
120
|
+
# not a +200+ response.
|
121
|
+
#
|
115
122
|
def raise_exceptions!(setting = true)
|
116
123
|
self.options[:raise_exceptions] = setting
|
117
124
|
end
|
118
125
|
|
126
|
+
# Sets the option to encode the POST body as +application/json+ compatible.
|
127
|
+
#
|
128
|
+
def encode_post_body_as_json!(setting = true)
|
129
|
+
if self.options[:encode_post_body_as_json] = setting
|
130
|
+
set_content_type "application/json"
|
131
|
+
else
|
132
|
+
set_content_type "application/x-www-form-urlencoded"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
119
136
|
#--
|
120
137
|
# Request Handling
|
121
138
|
#++
|
122
139
|
|
123
140
|
# Performs a GET request on the URI specified.
|
141
|
+
#
|
124
142
|
def get(uri, headers={})
|
125
143
|
req = Net::HTTP::Get.new(uri)
|
126
144
|
request(req, headers)
|
127
145
|
end
|
128
146
|
|
129
147
|
# Performs a POST request on the URI specified.
|
148
|
+
#
|
130
149
|
def post(uri, data = {}, headers={})
|
131
150
|
req = Net::HTTP::Post.new(uri)
|
132
151
|
req.body = format_body(data)
|
@@ -134,19 +153,21 @@ module Halcyon
|
|
134
153
|
end
|
135
154
|
|
136
155
|
# Performs a DELETE request on the URI specified.
|
156
|
+
#
|
137
157
|
def delete(uri, headers={})
|
138
158
|
req = Net::HTTP::Delete.new(uri)
|
139
159
|
request(req, headers)
|
140
160
|
end
|
141
161
|
|
142
162
|
# Performs a PUT request on the URI specified.
|
163
|
+
#
|
143
164
|
def put(uri, data = {}, headers={})
|
144
165
|
req = Net::HTTP::Put.new(uri)
|
145
166
|
req.body = format_body(data)
|
146
167
|
request(req, headers)
|
147
168
|
end
|
148
169
|
|
149
|
-
|
170
|
+
private
|
150
171
|
|
151
172
|
# Performs an arbitrary HTTP request, receive the response, parse it with
|
152
173
|
# JSON, and return it to the caller. This is a private method because the
|
@@ -160,9 +181,11 @@ module Halcyon
|
|
160
181
|
# (defined in Halcyon::Exceptions) which all inherit from
|
161
182
|
# +Halcyon::Exceptions+. It is up to the client to handle these
|
162
183
|
# exceptions specifically.
|
184
|
+
#
|
163
185
|
def request(req, headers={})
|
164
186
|
# set default headers
|
165
187
|
req["User-Agent"] = USER_AGENT
|
188
|
+
req["Accept"] = ACCEPT
|
166
189
|
req["Content-Type"] = CONTENT_TYPE unless req.body.nil?
|
167
190
|
req["Content-Length"] = req.body unless req.body.nil?
|
168
191
|
|
@@ -171,8 +194,10 @@ module Halcyon
|
|
171
194
|
req[header] = value
|
172
195
|
end
|
173
196
|
|
174
|
-
# prepare and send HTTP request
|
175
|
-
|
197
|
+
# prepare and send HTTP/S request
|
198
|
+
serv = Net::HTTP.new(self.uri.host, self.uri.port)
|
199
|
+
prepare_server(serv) if private_methods.include?('prepare_server')
|
200
|
+
res = serv.start { |http| http.request(req) }
|
176
201
|
|
177
202
|
# parse response
|
178
203
|
# unescape just in case any problematic characters were POSTed through
|
@@ -192,9 +217,24 @@ module Halcyon
|
|
192
217
|
|
193
218
|
# Formats the data of a POST or PUT request (the body) into an acceptable
|
194
219
|
# format according to Net::HTTP for sending through as a Hash.
|
220
|
+
#
|
195
221
|
def format_body(data)
|
196
222
|
data = {:body => data} unless data.is_a? Hash
|
197
|
-
|
223
|
+
case CONTENT_TYPE
|
224
|
+
when "application/x-www-form-urlencoded"
|
225
|
+
Rack::Utils.build_query(data)
|
226
|
+
when "application/json"
|
227
|
+
data.to_json
|
228
|
+
else
|
229
|
+
raise ArgumentError.new("Unsupported Content-Type for POST body: #{CONTENT_TYPE}")
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Sets the +CONTENT_TYPE+ to the appropriate type.
|
234
|
+
#
|
235
|
+
def set_content_type(content_type)
|
236
|
+
self.class.send(:remove_const, :CONTENT_TYPE)
|
237
|
+
self.class.const_set(:CONTENT_TYPE, content_type.freeze)
|
198
238
|
end
|
199
239
|
|
200
240
|
end
|
data/lib/halcyon/client/ssl.rb
CHANGED
@@ -4,34 +4,10 @@ module Halcyon
|
|
4
4
|
class Client
|
5
5
|
private
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
req["User-Agent"] = USER_AGENT
|
11
|
-
|
12
|
-
# apply provided headers
|
13
|
-
self.headers.merge(headers).each do |(header, value)|
|
14
|
-
req[header] = value
|
15
|
-
end
|
16
|
-
|
17
|
-
# prepare and send HTTPS request
|
18
|
-
serv = Net::HTTP.new(self.uri.host, self.uri.port)
|
7
|
+
# Sets the SSL-specific options for the Server.
|
8
|
+
#
|
9
|
+
def prepare_server(serv)
|
19
10
|
serv.use_ssl = true if self.uri.scheme == 'https'
|
20
|
-
res = serv.start {|http|http.request(req)}
|
21
|
-
|
22
|
-
# parse response
|
23
|
-
body = JSON.parse(res.body).to_mash
|
24
|
-
|
25
|
-
# handle non-successes
|
26
|
-
if self.options[:raise_exceptions] && !res.kind_of?(Net::HTTPSuccess)
|
27
|
-
raise self.class.const_get(Exceptions::HTTP_STATUS_CODES[body[:status]].tr(' ', '_').camel_case.gsub(/( |\-)/,'')).new
|
28
|
-
end
|
29
|
-
|
30
|
-
# return response
|
31
|
-
body
|
32
|
-
rescue Halcyon::Exceptions::Base => e
|
33
|
-
# log exception if logger is in place
|
34
|
-
raise
|
35
11
|
end
|
36
12
|
|
37
13
|
end
|
data/lib/rack/jsonp.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
begin
|
2
|
+
require 'json'
|
3
|
+
rescue LoadError => e
|
4
|
+
require 'json/pure'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Rack
|
8
|
+
|
9
|
+
# A Rack middleware for parsing POST/PUT body data when Content-Type is
|
10
|
+
# not one of the standard supported types, like <tt>application/json</tt>.
|
11
|
+
#
|
12
|
+
class PostBodyContentTypeParsers
|
13
|
+
|
14
|
+
# Constants
|
15
|
+
#
|
16
|
+
CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
17
|
+
POST_BODY = 'rack.input'.freeze
|
18
|
+
FORM_INPUT = 'rack.request.form_input'.freeze
|
19
|
+
FORM_HASH = 'rack.request.form_hash'.freeze
|
20
|
+
|
21
|
+
# Supported Content-Types
|
22
|
+
#
|
23
|
+
APPLICATION_JSON = 'application/json'.freeze
|
24
|
+
|
25
|
+
def initialize(app)
|
26
|
+
@app = app
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
case env[CONTENT_TYPE]
|
31
|
+
when APPLICATION_JSON
|
32
|
+
env.update(FORM_HASH => JSON.parse(env[POST_BODY].read), FORM_INPUT => env[POST_BODY])
|
33
|
+
end
|
34
|
+
@app.call(env)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -61,4 +61,9 @@ describe "Halcyon::Application" do
|
|
61
61
|
body['body'].should == "Internal Server Error"
|
62
62
|
end
|
63
63
|
|
64
|
+
it "should dispatch to controllers inside of modules" do
|
65
|
+
response = Rack::MockRequest.new(@app).get("/nested/tests")
|
66
|
+
response.status.should == 200
|
67
|
+
end
|
68
|
+
|
64
69
|
end
|
data/spec/halcyon/client_spec.rb
CHANGED
@@ -31,6 +31,21 @@ end
|
|
31
31
|
# Tests
|
32
32
|
#++
|
33
33
|
|
34
|
+
module Halcyon
|
35
|
+
class Client
|
36
|
+
private
|
37
|
+
def prepare_server(serv)
|
38
|
+
class << serv
|
39
|
+
alias :request_without_header_inspection :request
|
40
|
+
def request(req, body = nil, &block)
|
41
|
+
$accept = req["Accept"]
|
42
|
+
request_without_header_inspection(req, body, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
34
49
|
describe "Halcyon::Client" do
|
35
50
|
|
36
51
|
before do
|
@@ -55,7 +70,7 @@ describe "Halcyon::Client" do
|
|
55
70
|
@client.get('/nonexistent/route')[:status].should == 404
|
56
71
|
|
57
72
|
# tell it to raise exceptions
|
58
|
-
@client.raise_exceptions!
|
73
|
+
@client.raise_exceptions!
|
59
74
|
should.raise(Halcyon::Exceptions::NotFound) { @client.get('/nonexistent/route') }
|
60
75
|
@client.get('/time')[:status].should == 200
|
61
76
|
end
|
@@ -80,4 +95,30 @@ describe "Halcyon::Client" do
|
|
80
95
|
response[:body].should == {'controller' => 'application', 'action' => 'returner', 'key' => "%todd"}
|
81
96
|
end
|
82
97
|
|
98
|
+
it "should render the POST body with the correct content type, allowing application/json is set" do
|
99
|
+
body = {:key => "value"}
|
100
|
+
|
101
|
+
# default behavior is to set the POST body to application/x-www-form-urlencoded
|
102
|
+
@client.send(:format_body, body) == "key=value"
|
103
|
+
@client.post('/returner', body)[:body][:key].should == "value"
|
104
|
+
|
105
|
+
# tell it to send as application/json
|
106
|
+
@client.encode_post_body_as_json!
|
107
|
+
@client.send(:format_body, body).should == body.to_json
|
108
|
+
@client.post('/returner', body)[:body][:key].should == nil
|
109
|
+
# The server will not return the values from the POST body because it is
|
110
|
+
# not set to parse application/json values. Like your own apps, you must
|
111
|
+
# set it manually to accept this type of body encoding.
|
112
|
+
|
113
|
+
# set it back to ensure that the change is reversed
|
114
|
+
@client.encode_post_body_as_json! false
|
115
|
+
@client.send(:format_body, body) == "key=value"
|
116
|
+
@client.post('/returner', body)[:body][:key].should == "value"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should set the Accept header to the appropriate type" do
|
120
|
+
@client.get('/returner')[:status].should == 200
|
121
|
+
$accept.should == Halcyon::Client::ACCEPT
|
122
|
+
end
|
123
|
+
|
83
124
|
end
|
data/spec/rack/jsonp.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
app = lambda do |env|
|
2
|
+
[200, {'Content-Type' => 'text/plain'}, {'bar' => 'foo'}.to_json]
|
3
|
+
end
|
4
|
+
|
5
|
+
jsonp_app = Rack::Builder.new do
|
6
|
+
use Rack::JSONP
|
7
|
+
run app
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "Rack::JSONP" do
|
11
|
+
|
12
|
+
before do
|
13
|
+
@log = ''
|
14
|
+
@logger = Logger.new(StringIO.new(@log))
|
15
|
+
Halcyon.config.use do |c|
|
16
|
+
c[:logger] = @logger
|
17
|
+
end
|
18
|
+
@app = Halcyon::Runner.new
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should wrap the response body in the Javascript callback when provided" do
|
22
|
+
body = jsonp_app.call(Rack::MockRequest.env_for("/", :input => "foo=bar&callback=foo")).last
|
23
|
+
body.should == 'foo({"bar":"foo"})'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not change anything if no :callback param is provided" do
|
27
|
+
body = app.call(Rack::MockRequest.env_for("/", :input => "foo=bar")).last
|
28
|
+
body.should == {'bar' => 'foo'}.to_json
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
app = lambda do |env|
|
2
|
+
request = Rack::Request.new(env)
|
3
|
+
[200, {'Content-Type' => 'text/plain'}, request.POST]
|
4
|
+
end
|
5
|
+
|
6
|
+
def env_for_post_with_headers(path, headers, body)
|
7
|
+
Rack::MockRequest.env_for(path, {:method => "POST", :input => body}.merge(headers))
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "Rack::PostBodyContentTypeParsers" do
|
11
|
+
|
12
|
+
before do
|
13
|
+
@log = ''
|
14
|
+
@logger = Logger.new(StringIO.new(@log))
|
15
|
+
Halcyon.config.use do |c|
|
16
|
+
c[:logger] = @logger
|
17
|
+
end
|
18
|
+
@app = Halcyon::Runner.new
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should handle requests with POST body Content-Type of application/json" do
|
22
|
+
parser = Rack::PostBodyContentTypeParsers.new(app)
|
23
|
+
env = env_for_post_with_headers('/', {'Content_Type'.upcase => 'application/json'}, {:body => "asdf", :status => "12"}.to_json)
|
24
|
+
|
25
|
+
response_body = parser.call(env).last
|
26
|
+
|
27
|
+
response_body['body'].should == "asdf"
|
28
|
+
response_body['status'].should == "12"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should change nothing when the POST body content type isn't application/json" do
|
32
|
+
response_body = app.call(Rack::MockRequest.env_for("/", :input => "body=asdf&status=12")).last
|
33
|
+
response_body['body'].should == "asdf"
|
34
|
+
response_body['status'].should == "12"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -85,6 +85,17 @@ class Resources < Application
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
+
# Nested controller
|
89
|
+
module Nested
|
90
|
+
class Tests < Application
|
91
|
+
|
92
|
+
def index
|
93
|
+
ok
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
88
99
|
# Models
|
89
100
|
|
90
101
|
class Model
|
@@ -100,6 +111,7 @@ Halcyon.configurable_attr(:environment)
|
|
100
111
|
Halcyon::Application.route do |r|
|
101
112
|
r.resources :resources
|
102
113
|
|
114
|
+
r.match('/nested/tests').to(:controller => 'nested/tests', :action => 'index')
|
103
115
|
r.match('/hello/:name').to(:controller => 'specs', :action => 'greeter')
|
104
116
|
r.match('/:action').to(:controller => 'specs')
|
105
117
|
r.match('/:controller/:action').to()
|
@@ -5,4 +5,9 @@ $:.unshift(Halcyon.root/'lib')
|
|
5
5
|
puts "(Starting in #{Halcyon.root})"
|
6
6
|
|
7
7
|
Thin::Logging.silent = true if defined? Thin
|
8
|
+
|
9
|
+
# Uncomment if you plan to allow clients to send requests with the POST body
|
10
|
+
# Content-Type as application/json.
|
11
|
+
# use Rack::PostBodyContentTypeParsers
|
12
|
+
|
8
13
|
run Halcyon::Runner.new
|
@@ -5,4 +5,8 @@ puts "(Starting in #{Halcyon.root})"
|
|
5
5
|
|
6
6
|
Thin::Logging.silent = true if defined? Thin
|
7
7
|
|
8
|
+
# Uncomment if you plan to allow clients to send requests with the POST body
|
9
|
+
# Content-Type as application/json.
|
10
|
+
# use Rack::PostBodyContentTypeParsers
|
11
|
+
|
8
12
|
run Halcyon::Runner.new
|
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.5.
|
4
|
+
version: 0.5.4
|
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-
|
12
|
+
date: 2008-09-19 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -85,6 +85,9 @@ files:
|
|
85
85
|
- spec/halcyon/logging_spec.rb
|
86
86
|
- spec/halcyon/router_spec.rb
|
87
87
|
- spec/halcyon/runner_spec.rb
|
88
|
+
- spec/rack
|
89
|
+
- spec/rack/jsonp.rb
|
90
|
+
- spec/rack/post_body_content_type_parsers_spec.rb
|
88
91
|
- spec/spec_helper.rb
|
89
92
|
- lib/halcyon
|
90
93
|
- lib/halcyon/application
|
@@ -117,6 +120,7 @@ files:
|
|
117
120
|
- lib/halcyon.rb
|
118
121
|
- lib/rack
|
119
122
|
- lib/rack/jsonp.rb
|
123
|
+
- lib/rack/post_body_content_type_parsers.rb
|
120
124
|
- support/generators
|
121
125
|
- support/generators/halcyon
|
122
126
|
- support/generators/halcyon/halcyon_generator.rb
|