httparrot 0.0.1
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/.gitignore +4 -0
- data/Gemfile +8 -0
- data/Rakefile +9 -0
- data/Readme.rdoc +97 -0
- data/httparrot.gemspec +27 -0
- data/lib/httparrot/response_factory.rb +89 -0
- data/lib/httparrot/server.rb +254 -0
- data/lib/httparrot/ssl/server.crt +14 -0
- data/lib/httparrot/ssl/server.key +15 -0
- data/lib/httparrot/version.rb +3 -0
- data/lib/httparrot/widget.rb +113 -0
- data/lib/httparrot.rb +51 -0
- data/spec/httparrot_config_spec.rb +50 -0
- data/spec/httparrot_spec.rb +3 -0
- data/spec/response_factory_spec.rb +135 -0
- data/spec/server_spec.rb +192 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/templates/awesometown_protocol.erb +11 -0
- data/spec/templates/widget.erb +12 -0
- data/spec/widget_spec.rb +183 -0
- metadata +115 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/Readme.rdoc
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
= HTTParrot
|
2
|
+
|
3
|
+
Do you need to mock an API endpoint over an HTTP connection? Does your API only speak HTTP? Other things and stuff related to mock servers and HTTP?
|
4
|
+
|
5
|
+
Then you need HTTParrot! (or something else that does similar things maybe)
|
6
|
+
|
7
|
+
= Install it
|
8
|
+
gem install httparrot #=> OR include it in the development/test group of your Gemfile
|
9
|
+
|
10
|
+
|
11
|
+
HTTParrot's goal is to simplify building mock HTTP responses and scripting the responses during an HTTP session interaction.
|
12
|
+
Responses are mocked in ERB and can be returned via matching params that are setup during use. (We use HTTParrot to
|
13
|
+
help mock our HTTP protocol support around the webs)
|
14
|
+
|
15
|
+
== Example
|
16
|
+
TotallyNewAwesomeCompany has decided that too many protocols exist in the Widget space. Unfortunately none of those protocols
|
17
|
+
account for how awesome the protocol could be if only TotallyNewAwesomeCompany had a way to force adoption. TNAC has decided
|
18
|
+
to implement an HTTP based protocol to exchange Widget data, but they NEED to write tests for the session based HTTP protocol
|
19
|
+
(Request => Response => Request => Response)
|
20
|
+
|
21
|
+
Protocol logic:
|
22
|
+
A connection is established with an ConnectWidget document sent to tnac.com/connect_widget
|
23
|
+
A valid connection response contains "TNAC: VERIFIED"
|
24
|
+
Widget data is requested with a POST sent to tnac.com/widget AND must POST the widget id
|
25
|
+
|
26
|
+
(we know this isn't very RESTful, but it's a contrived example)
|
27
|
+
|
28
|
+
Using HTTParrot we can test said protocol in a "simple" way :)
|
29
|
+
|
30
|
+
describe TNAC::Widget do
|
31
|
+
|
32
|
+
before(:all) do
|
33
|
+
@server = HTTParrot::Server.new
|
34
|
+
@server.start
|
35
|
+
end
|
36
|
+
|
37
|
+
after(:all) do
|
38
|
+
@server.stop
|
39
|
+
end
|
40
|
+
|
41
|
+
before(:each) do
|
42
|
+
HTTParrot::ResponseFactory.clear!
|
43
|
+
HTTParrot::ResponseFactory.define(:connection) { |c| c.tnac = "VERIFIED" }
|
44
|
+
HTTParrot::ResponseFactory.define(:widget) { |w| w.awesomeness = "YEPYEP" }
|
45
|
+
@connection = HTTParrot::ResponseFactory.build(:connection)
|
46
|
+
@widget = HTTParrot::ResponseFactory.build(:widget)
|
47
|
+
@server.clear!
|
48
|
+
end
|
49
|
+
|
50
|
+
it "totally connects" do
|
51
|
+
connection_reg = @server.register(:get, "connect_widget", @connection.to_rack)
|
52
|
+
http_connection = Net::HTTP.new("127.0.0.1", HTTParrot::Config.Port)
|
53
|
+
http_connection.get("/connect_widget")
|
54
|
+
connection_reg.response_count.should eq(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "allows me to get a widget" do
|
58
|
+
@server.register(:post, /WIDGET/, @widget.to_rack)
|
59
|
+
data = "widget=WIDGET&id=1224"
|
60
|
+
http_connection = Net::HTTP.new("127.0.0.1", HTTParrot::Config.Port)
|
61
|
+
http_connection.post("/widget", data).should match(/VERIFIED/)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
= Matchers
|
68
|
+
Matchers are used to register responses based on scripted requests
|
69
|
+
|
70
|
+
== Regex Matcher
|
71
|
+
Matches based on the body of a request matching the Regex passed
|
72
|
+
|
73
|
+
@server.register(:get, /Match This/, @response.to_rack)
|
74
|
+
|
75
|
+
== Endpoint Matchers
|
76
|
+
Matches based on the endpoint URI that the HTTP verb acts on
|
77
|
+
|
78
|
+
@server.register(:get, "endpoint", @response.to_rack)
|
79
|
+
|
80
|
+
== Callable Matcher
|
81
|
+
Any parameter that responds to :call can be used to return true/false depending on the body being passed to the callable
|
82
|
+
(primarily used with lambdas/procs/method object)
|
83
|
+
|
84
|
+
@server.register(:get, lambda{ |b| b =~ /awesome/ }, @response.to_rack)
|
85
|
+
|
86
|
+
== Complex Matcher
|
87
|
+
A complex matcher is an Array comprised of any number of the other Matchers
|
88
|
+
|
89
|
+
@server.register(:get, [/awesome/, lambda{|v| v =~ /awesome/], @response.to_rack)
|
90
|
+
|
91
|
+
|
92
|
+
= Registering Responses
|
93
|
+
A response is registered as a HTTP verb type [:get, :post, :head, :delete] followed by the MATCHER and finally a Rack Response
|
94
|
+
of the data that will be returned when a match is found. The primary means of the Rack Response in our tests comes from using
|
95
|
+
HTTParrot::ResponseFactory to build responses based on ERB templates.
|
96
|
+
|
97
|
+
(Checkout the tests in the spec folder to build out the templates.....more documentation to come)
|
data/httparrot.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "httparrot/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "httparrot"
|
7
|
+
s.version = HTTParrot::VERSION
|
8
|
+
s.authors = ["Brandon Dewitt"]
|
9
|
+
s.email = ["brandonsdewitt@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Mock Server for testing HTTP anything!}
|
12
|
+
s.description = %q{helps cut through the clutter of HTTP testing}
|
13
|
+
|
14
|
+
s.rubyforge_project = "httparrot"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec", "=2.7"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
|
25
|
+
s.add_runtime_dependency "rack"
|
26
|
+
s.add_runtime_dependency "active_support"
|
27
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'httparrot/widget'
|
2
|
+
|
3
|
+
module HTTParrot
|
4
|
+
|
5
|
+
class ResponseFactory
|
6
|
+
|
7
|
+
def self.clear!
|
8
|
+
@factory_classes = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.define(factory_class, &block)
|
12
|
+
raise error_no_block(factory_class) if block.nil?
|
13
|
+
@factory_classes ||= {}
|
14
|
+
warn_factory_exists(factory_class) if @factory_classes.keys.include?(factory_class)
|
15
|
+
default_object = HTTParrot::Widget.new(:_class => "Widget::#{factory_class.to_s.camelize}")
|
16
|
+
@factory_classes[factory_class] = lambda{ block.call(default_object); default_object }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build(factory_class, new_options={})
|
20
|
+
raise error_for_missing(factory_class) if @factory_classes[factory_class].nil?
|
21
|
+
object = @factory_classes[factory_class].call
|
22
|
+
|
23
|
+
new_options.each do |k, v|
|
24
|
+
object.send("#{k}=", v)
|
25
|
+
end
|
26
|
+
|
27
|
+
return Marshal.load(Marshal.dump(object))
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.collection_of(factory_class, number, new_options={})
|
31
|
+
collection = []
|
32
|
+
|
33
|
+
number.times do
|
34
|
+
collection << build(factory_class, new_options)
|
35
|
+
end
|
36
|
+
|
37
|
+
return collection
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.one_of(choices)
|
41
|
+
warn_no_choices if choices.size <= 0
|
42
|
+
choices[rand(choices.size)]
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def self.error_for_missing(factory_class)
|
48
|
+
"Unknown factory type: #{factory_class} in known factories: #{@factory_classes.keys}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.error_no_block(factory_class)
|
52
|
+
"No block included in definition of #{factory_class} factory"
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.warn_no_choices
|
56
|
+
raise "foo"
|
57
|
+
rescue => e
|
58
|
+
warn <<-WARNING
|
59
|
+
==============================================================
|
60
|
+
No choices were provided for #one_of
|
61
|
+
|
62
|
+
This constitutes a nil choice
|
63
|
+
|
64
|
+
At:
|
65
|
+
|
66
|
+
#{e.backtrace.join("#{$/} ")}
|
67
|
+
==============================================================
|
68
|
+
WARNING
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.warn_factory_exists(factory_class)
|
72
|
+
raise "foo"
|
73
|
+
rescue => e
|
74
|
+
warn <<-WARNING
|
75
|
+
==============================================================
|
76
|
+
#{factory_class} is already defined as a ResponseFactory
|
77
|
+
|
78
|
+
This constitutes a redefinition of the factory
|
79
|
+
|
80
|
+
Redefined at:
|
81
|
+
|
82
|
+
#{e.backtrace.join("#{$/} ")}
|
83
|
+
==============================================================
|
84
|
+
WARNING
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# Mock server to accept requests and return known responses based on request content
|
2
|
+
#
|
3
|
+
# influenced by: http://dynamicorange.com/2009/02/18/ruby-mock-web-server
|
4
|
+
require 'openssl' unless defined?(JRUBY_VERSION)
|
5
|
+
require 'ostruct'
|
6
|
+
require 'net/http'
|
7
|
+
require 'net/https'
|
8
|
+
require 'webrick'
|
9
|
+
require 'webrick/https'
|
10
|
+
require 'rack'
|
11
|
+
require 'thread'
|
12
|
+
|
13
|
+
module HTTParrot
|
14
|
+
|
15
|
+
class Server
|
16
|
+
|
17
|
+
attr_accessor :options
|
18
|
+
|
19
|
+
def initialize(opts={})
|
20
|
+
@server_started = @server = nil
|
21
|
+
@server_thread = nil
|
22
|
+
@secure_started = @secure_server = nil
|
23
|
+
@secure_thread = nil
|
24
|
+
|
25
|
+
@parent_thread = Thread.current
|
26
|
+
@options = {
|
27
|
+
:Port => 4000,
|
28
|
+
:Host => "127.0.0.1"
|
29
|
+
}.merge(HTTParrot::Config.config).merge(opts)
|
30
|
+
|
31
|
+
quiet_options = {
|
32
|
+
:Logger => WEBrick::Log::new("/dev/null", 7),
|
33
|
+
:AccessLog => []
|
34
|
+
}
|
35
|
+
|
36
|
+
@options.merge!(quiet_options) unless HTTParrot::Config.verbose
|
37
|
+
|
38
|
+
self.clear!
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear!
|
42
|
+
blank_handler = {
|
43
|
+
:get => [],
|
44
|
+
:post => [],
|
45
|
+
:head => [],
|
46
|
+
:delete => []
|
47
|
+
}
|
48
|
+
|
49
|
+
# Using Marshal for deep copy purposes
|
50
|
+
# slow from an efficiency perspective, but #clear!
|
51
|
+
# is not called frequently and this gem is for testing
|
52
|
+
@call_handlers = Marshal.load(Marshal.dump(blank_handler))
|
53
|
+
@regex_handlers = Marshal.load(Marshal.dump(blank_handler))
|
54
|
+
@endpoint_handlers = Marshal.load(Marshal.dump(blank_handler))
|
55
|
+
@complex_handlers = Marshal.load(Marshal.dump(blank_handler))
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset_counts
|
60
|
+
[@call_handlers, @regex_handlers, @endpoint_handlers, @complex_handlers].each do |handler_type|
|
61
|
+
handler_type.each_key do |req_meth|
|
62
|
+
handler_type[req_meth].each { |handler| handler.response_count = 0 }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def call(env)
|
68
|
+
req = Rack::Request.new(env)
|
69
|
+
response = respond_with(env)
|
70
|
+
|
71
|
+
# TODO move this to WEBrick's builtin logging facilities
|
72
|
+
if ENV["PARROT_VERBOSE"] || HTTParrot::Config.verbose
|
73
|
+
puts "\n>>>>> REQUEST\n"
|
74
|
+
puts req.inspect
|
75
|
+
#puts req.body.string
|
76
|
+
|
77
|
+
puts "\n<<<<< RESPONSE\n"
|
78
|
+
puts response.inspect
|
79
|
+
#response[2].each{ |l| puts l }
|
80
|
+
end
|
81
|
+
|
82
|
+
return response
|
83
|
+
|
84
|
+
rescue Exception => e
|
85
|
+
# reraise the exception in the parent thread if it Errors out
|
86
|
+
@parent_thread.raise e
|
87
|
+
end
|
88
|
+
|
89
|
+
def register(http_method, method_key, response, call_with_env = false)
|
90
|
+
http_method = http_method.to_s.downcase.to_sym
|
91
|
+
raise "http_method in register must be one of [:get, :post, :head, :delete] : #{http_method}" if ![:get, :post, :head, :delete].include?(http_method)
|
92
|
+
|
93
|
+
response_handler = OpenStruct.new({
|
94
|
+
:method_key => method_key,
|
95
|
+
:response => response,
|
96
|
+
:env? => call_with_env,
|
97
|
+
:response_count => 0
|
98
|
+
})
|
99
|
+
|
100
|
+
case
|
101
|
+
when method_key.respond_to?(:call) then
|
102
|
+
@call_handlers[http_method] << response_handler
|
103
|
+
when method_key.is_a?(Regexp) then
|
104
|
+
@regex_handlers[http_method] << response_handler
|
105
|
+
when method_key.is_a?(String) then
|
106
|
+
@endpoint_handlers[http_method] << response_handler
|
107
|
+
when method_key.is_a?(Array) then
|
108
|
+
@complex_handlers[http_method] << response_handler
|
109
|
+
else
|
110
|
+
raise "method_key (Handler) must be callable, Regexp, Array, or String"
|
111
|
+
end
|
112
|
+
|
113
|
+
return response_handler
|
114
|
+
end
|
115
|
+
|
116
|
+
def start(startup_interval = 1)
|
117
|
+
start_server
|
118
|
+
start_secure_server if options[:ssl]
|
119
|
+
sleep startup_interval # Ensure the server has time to startup
|
120
|
+
sleep startup_interval if !running? # Give it a little more time if they didn't start
|
121
|
+
end
|
122
|
+
|
123
|
+
def running?
|
124
|
+
secure_run_running = options[:ssl] ? (!@secure_server.nil? && @secure_started) : true
|
125
|
+
@server_started && !@server.nil? && secure_run_running
|
126
|
+
end
|
127
|
+
alias_method :started?, :running?
|
128
|
+
|
129
|
+
def stop(shutdown_interval = 0)
|
130
|
+
@server.shutdown if @server.respond_to?(:shutdown)
|
131
|
+
@secure_server.shutdown if @secure_server.respond_to?(:shutdown)
|
132
|
+
sleep shutdown_interval
|
133
|
+
Thread.kill(@server_thread) if !@server_thread.nil?
|
134
|
+
Thread.kill(@secure_thread) if !@secure_thread.nil?
|
135
|
+
@server_started = @server = nil
|
136
|
+
@secure_started = @secure_server = nil
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# Increment the number of times a handler was used (for asserting usage)
|
142
|
+
def increment_return(handler)
|
143
|
+
handler.response_count = handler.response_count + 1
|
144
|
+
return handler.response
|
145
|
+
end
|
146
|
+
|
147
|
+
def call_handler_match?(call_handler, env, request, request_method, request_body)
|
148
|
+
call_arg = (call_handler.env? ? env : request_body)
|
149
|
+
|
150
|
+
return call_handler.method_key.call(call_arg)
|
151
|
+
end
|
152
|
+
|
153
|
+
def complex_handler_match?(complex_handler, env, request, request_method, request_body)
|
154
|
+
|
155
|
+
complex_handler.method_key.inject(true) do |matching, handler|
|
156
|
+
current_handler_match = false
|
157
|
+
|
158
|
+
if matching
|
159
|
+
current_handler = OpenStruct.new({
|
160
|
+
:env? => complex_handler.env?,
|
161
|
+
:method_key => handler
|
162
|
+
})
|
163
|
+
|
164
|
+
case
|
165
|
+
when handler.respond_to?(:call) then
|
166
|
+
current_handler_match = call_handler_match?(current_handler, env, request, request_method, request_body)
|
167
|
+
when handler.is_a?(Regexp) then
|
168
|
+
current_handler_match = regex_handler_match?(current_handler, env, request, request_method, request_body)
|
169
|
+
when handler.is_a?(String) then
|
170
|
+
current_handler_match = endpoint_handler_match?(current_handler, env, request, request_method, request_body)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
matching && current_handler_match
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def endpoint_handler_match?(endpoint_handler, env, request, request_method, request_body)
|
179
|
+
return request.path_info =~ /#{endpoint_handler.method_key}/i
|
180
|
+
end
|
181
|
+
|
182
|
+
def regex_handler_match?(regex_handler, env, request, request_method, request_body)
|
183
|
+
return regex_handler.method_key =~ request_body
|
184
|
+
end
|
185
|
+
|
186
|
+
def respond_with(env)
|
187
|
+
req = Rack::Request.new(env)
|
188
|
+
req_meth = req.request_method.downcase.to_sym
|
189
|
+
request_body = req.body.string
|
190
|
+
|
191
|
+
@complex_handlers[req_meth].each do |com_handler|
|
192
|
+
return increment_return(com_handler) if complex_handler_match?(com_handler, env, req, req_meth, request_body)
|
193
|
+
end
|
194
|
+
|
195
|
+
@call_handlers[req_meth].each do |call_handler|
|
196
|
+
return increment_return(call_handler) if call_handler_match?(call_handler, env, req, req_meth, request_body)
|
197
|
+
end
|
198
|
+
|
199
|
+
@regex_handlers[req_meth].each do |reg_handler|
|
200
|
+
return increment_return(reg_handler) if regex_handler_match?(reg_handler, env, req, req_meth, request_body)
|
201
|
+
end
|
202
|
+
|
203
|
+
@endpoint_handlers[req_meth].each do |end_handler|
|
204
|
+
return increment_return(end_handler) if endpoint_handler_match?(end_handler, env, req, req_meth, request_body)
|
205
|
+
end
|
206
|
+
|
207
|
+
return no_mock_error(req_meth)
|
208
|
+
end
|
209
|
+
|
210
|
+
def no_mock_error(request_method)
|
211
|
+
error_message = "No matched request handlers for: #{request_method}"
|
212
|
+
[404, { "Content-Type" => "text/plain", "Content-Length" => error_message.size.to_s }, [error_message]]
|
213
|
+
end
|
214
|
+
|
215
|
+
def start_server
|
216
|
+
if @server_thread.nil? || !@server_thread.alive?
|
217
|
+
@server_thread = Thread.new(self, @options) do |server, options|
|
218
|
+
Rack::Handler::WEBrick.run(server, options) { |s| @server = s }
|
219
|
+
end
|
220
|
+
|
221
|
+
trap(:INT){ @server_thread.terminate; @server_thread = nil }
|
222
|
+
@server_started = true
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def start_secure_server
|
227
|
+
if @secure_thread.nil? || !@secure_thread.alive?
|
228
|
+
@secure_thread = Thread.new(self, @options) do |server, options|
|
229
|
+
options = options.merge(:Port => options[:SSLPort],
|
230
|
+
:SSLEnable => true,
|
231
|
+
:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
|
232
|
+
:SSLCertificate => ssl_cert,
|
233
|
+
:SSLPrivateKey => ssl_key,
|
234
|
+
:SSLCertName => [[ "CN", "127.0.0.1" ]])
|
235
|
+
|
236
|
+
Rack::Handler::WEBrick.run(server, options) { |s| @secure_server = s }
|
237
|
+
end
|
238
|
+
|
239
|
+
trap(:INT){ @secure_thread.terminate; @secure_thread = nil }
|
240
|
+
@secure_started = true
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def ssl_key
|
245
|
+
OpenSSL::PKey::RSA.new(File.read(File.dirname(__FILE__) + "/ssl/" + "server.key"), "httparrot")
|
246
|
+
end
|
247
|
+
|
248
|
+
def ssl_cert
|
249
|
+
OpenSSL::X509::Certificate.new(File.read(File.dirname(__FILE__) + "/ssl/" + "server.crt"))
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIICEzCCAXwCCQCqQYI2AsyUpDANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
|
3
|
+
UzETMBEGA1UECBMKU29tZS1TdGF0ZTEUMBIGA1UEBxMLU3ByaW5nZmllbGQxFDAS
|
4
|
+
BgNVBAoTC0F3ZXNvbWV0b3duMB4XDTExMTIyMTAwMjQwMVoXDTIxMTIxODAwMjQw
|
5
|
+
MVowTjELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxFDASBgNVBAcT
|
6
|
+
C1NwcmluZ2ZpZWxkMRQwEgYDVQQKEwtBd2Vzb21ldG93bjCBnzANBgkqhkiG9w0B
|
7
|
+
AQEFAAOBjQAwgYkCgYEA1sbHVwOzZqIKFKkd8bBaGma9qXYrSrf8bGj9SZREIpAk
|
8
|
+
kZGH2Dyta27P7njUGcB3RecTogQHAS/YVkPuSfPHlbU2qVDBmqT1ZVIComW0A7iI
|
9
|
+
HiSsevGMgJAPNG2ekLBKJexxoS0nqfHO6G8nvPXIjWsXwT4IIL59sRxOx96Fc+0C
|
10
|
+
AwEAATANBgkqhkiG9w0BAQUFAAOBgQCaDVQErv6g7SU7f5NJ3F+CHksXEkgAGrYd
|
11
|
+
xb9mnaJlnJX8G/iG2jsE5lsNttQWBsLRrZ1x6eaVBOMlYnHTOny56TexLQqqFvSW
|
12
|
+
HiJeG8HcrVdW3u9aCjxLojcWVlnf+doIwbS87Q6qGu6N7cAof3UmI9vMwuwnNjty
|
13
|
+
9lXwRcxjMA==
|
14
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,15 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIICXgIBAAKBgQDWxsdXA7NmogoUqR3xsFoaZr2pditKt/xsaP1JlEQikCSRkYfY
|
3
|
+
PK1rbs/ueNQZwHdF5xOiBAcBL9hWQ+5J88eVtTapUMGapPVlUgKiZbQDuIgeJKx6
|
4
|
+
8YyAkA80bZ6QsEol7HGhLSep8c7obye89ciNaxfBPgggvn2xHE7H3oVz7QIDAQAB
|
5
|
+
AoGBAMUvQMqhqi7bLBgl4EkKGN9OXmjcBgkWfBjoF0tbZWa6IejHzQl5Q9pzpVGS
|
6
|
+
+2AdNSQnb/36ZpfvXlZtDbQ1rZEUzbMaPy6e2YV90B95xLNgVlSa4tOcoZ70Lpfu
|
7
|
+
2avWv5RaOHtySyV76JMdpiREJaNhDLEfkDBNRcuoI5Xu1RohAkEA+AaKy1YU4z2Z
|
8
|
+
KNaNi1rzXAxvdiC+RsKuOHXyyoy75wbwD4TB3pQuyFoXlvzsvw6ijmTUHxLA4aGv
|
9
|
+
tLKPu2JRhQJBAN2ukZxOAzf57B3HoI5Oti7fyoCUpxXfnJSypMK9KyihJRVX0/Ko
|
10
|
+
3+VEKdSEABcqWF2/ViAbHkq3AuX9g9sm8UkCQEQF108JHtVr8XOH1G4h1ZirOG6X
|
11
|
+
cFgL0KhfgOUYT/h+qJw49srKrUH5o3qfh3am1uJiuOKEzC2VoJDYYB8uSdkCQQDL
|
12
|
+
xlN9XczhoLAmM8Hn7nzTm83W4k6w8atKmOiRRjitEWw4MVLYJdoiMsVM38YBhWBT
|
13
|
+
VLXDr4np3k8gwSh6xFJZAkEAqPQIaRNZEI/uVoxAOhgkzZ8ci9YjRP1HmVdYy/Sv
|
14
|
+
nSrcq5XcGzIwYQmrUDMnp9k3pL0Lm611fAhgicgbq9a0ew==
|
15
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'active_support/core_ext/numeric'
|
4
|
+
require 'active_support/core_ext/time/conversions'
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
6
|
+
|
7
|
+
module HTTParrot
|
8
|
+
|
9
|
+
class Widget < OpenStruct
|
10
|
+
|
11
|
+
def initialize(defaults = {})
|
12
|
+
@parent_overwrite = false
|
13
|
+
@headers = {}
|
14
|
+
super(defaults)
|
15
|
+
end
|
16
|
+
|
17
|
+
def header
|
18
|
+
@headers
|
19
|
+
end
|
20
|
+
|
21
|
+
def parent(parental_class, defaults={})
|
22
|
+
case
|
23
|
+
when parental_class.is_a?(Symbol) then
|
24
|
+
parental_class = HTTParrot::ResponseFactory.build(parental_class, defaults)
|
25
|
+
when !parental_class.respond_to?(:to_hash) then
|
26
|
+
raise "Parent must be a symbol or respond_to #to_hash"
|
27
|
+
end
|
28
|
+
|
29
|
+
merge_parent(parental_class)
|
30
|
+
end
|
31
|
+
|
32
|
+
def parent!(parental_class, defaults = {})
|
33
|
+
@parent_overwrite = true
|
34
|
+
parent(parental_class, defaults)
|
35
|
+
@parent_overwrite = false
|
36
|
+
end
|
37
|
+
|
38
|
+
def rack_response(response_code = 200)
|
39
|
+
rendered_response = self.to_s
|
40
|
+
return [response_code, @headers.merge({"Content-Length" => rendered_response.size.to_s}), [rendered_response]]
|
41
|
+
end
|
42
|
+
alias_method :to_rack, :rack_response
|
43
|
+
alias_method :to_rack_response, :rack_response
|
44
|
+
|
45
|
+
def to_hash
|
46
|
+
self.marshal_dump
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
set_template_file
|
51
|
+
|
52
|
+
if @table.has_key?(:template_file) && !self.template_file.nil?
|
53
|
+
@headers = {}
|
54
|
+
file_string = File.read(self.template_file)
|
55
|
+
current_template = ERB.new(file_string, nil, "<>")
|
56
|
+
return current_template.result(binding)
|
57
|
+
else
|
58
|
+
warn_no_template
|
59
|
+
return self.inspect
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def merge_parent(parental_class)
|
66
|
+
case
|
67
|
+
when @parent_overwrite then
|
68
|
+
@table.merge!(parental_class.to_hash)
|
69
|
+
else
|
70
|
+
@table.merge!(parental_class.to_hash.merge(@table))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def template_root_search
|
75
|
+
self._class ||= self.class.to_s
|
76
|
+
|
77
|
+
template_root = HTTParrot::Config.config[:template_root] || ".."
|
78
|
+
template_root = template_root[0..-1] if template_root[-1] == "/"
|
79
|
+
filename = self._class.gsub("Widget::", "")
|
80
|
+
filename.gsub!("HTTParrot::", "")
|
81
|
+
return "#{template_root}/**/#{filename.underscore}.erb"
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_template_file
|
85
|
+
if self.template_file.nil? || self.template_file.empty?
|
86
|
+
self.template_file = Dir.glob(template_root_search).first
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def warn_no_template
|
91
|
+
raise "foo"
|
92
|
+
rescue => e
|
93
|
+
warn <<-WARNING
|
94
|
+
===================================================================
|
95
|
+
#{self.class} does not have a template_file associated
|
96
|
+
|
97
|
+
This will leave the response as an inspection of the class
|
98
|
+
and is probably not the intended behavior. Before returning
|
99
|
+
a rack_response, define a template file to render with.
|
100
|
+
|
101
|
+
template_root search:
|
102
|
+
#{template_root_search}
|
103
|
+
|
104
|
+
Called at:
|
105
|
+
|
106
|
+
#{e.backtrace.join("#{$/} ")}
|
107
|
+
===================================================================
|
108
|
+
WARNING
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
data/lib/httparrot.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "httparrot/version"
|
2
|
+
require "httparrot/server"
|
3
|
+
require "httparrot/response_factory"
|
4
|
+
|
5
|
+
require "ostruct"
|
6
|
+
|
7
|
+
module HTTParrot
|
8
|
+
class Config
|
9
|
+
@config = OpenStruct.new
|
10
|
+
|
11
|
+
def self.configure
|
12
|
+
yield self
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.config
|
16
|
+
@config.instance_variable_get(:@table)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.valid_key?(key)
|
20
|
+
[ :Port,
|
21
|
+
:SSLPort,
|
22
|
+
:ssl,
|
23
|
+
:template_root,
|
24
|
+
:verbose ].include?(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.restore_defaults!
|
28
|
+
self.configure do |c|
|
29
|
+
c.Port = 4000
|
30
|
+
c.SSLPort = c.Port + 1
|
31
|
+
c.ssl = true
|
32
|
+
c.verbose = false
|
33
|
+
c.template_root = nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.method_missing(sym, *args, &blk)
|
38
|
+
case
|
39
|
+
when sym.to_s =~ /(.+)=$/ && valid_key?($1.to_sym) then
|
40
|
+
@config.send(sym, *args, &blk)
|
41
|
+
when @config.respond_to?(sym) then
|
42
|
+
@config.send(sym, *args, &blk)
|
43
|
+
else
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
HTTParrot::Config.restore_defaults!
|