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