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