sanford 0.15.1 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,12 +1,13 @@
1
+ require 'much-plugin'
2
+
1
3
  module Sanford
2
4
 
3
5
  module ServiceHandler
6
+ include MuchPlugin
4
7
 
5
- def self.included(klass)
6
- klass.class_eval do
7
- extend ClassMethods
8
- include InstanceMethods
9
- end
8
+ plugin_included do
9
+ extend ClassMethods
10
+ include InstanceMethods
10
11
  end
11
12
 
12
13
  module InstanceMethods
@@ -15,24 +16,29 @@ module Sanford
15
16
  @sanford_runner = runner
16
17
  end
17
18
 
18
- def init
19
- run_callback 'before_init'
19
+ def sanford_init
20
+ self.sanford_run_callback 'before_init'
20
21
  self.init!
21
- run_callback 'after_init'
22
+ self.sanford_run_callback 'after_init'
22
23
  end
23
24
 
24
25
  def init!
25
26
  end
26
27
 
27
- def run
28
- run_callback 'before_run'
28
+ def sanford_run
29
+ self.sanford_run_callback 'before_run'
29
30
  data = self.run!
30
- run_callback 'after_run'
31
- [ 200, data ]
31
+ self.sanford_run_callback 'after_run'
32
+ [200, data]
32
33
  end
33
34
 
34
35
  def run!
35
- raise NotImplementedError
36
+ end
37
+
38
+ def sanford_run_callback(callback)
39
+ (self.class.send("#{callback}_callbacks") || []).each do |callback|
40
+ self.instance_eval(&callback)
41
+ end
36
42
  end
37
43
 
38
44
  def inspect
@@ -40,21 +46,26 @@ module Sanford
40
46
  "#<#{self.class}:#{reference} @request=#{request.inspect}>"
41
47
  end
42
48
 
49
+ def ==(other_handler)
50
+ self.class == other_handler.class
51
+ end
52
+
43
53
  private
44
54
 
45
55
  # Helpers
46
56
 
47
- def render(*args); @sanford_runner.render(*args); end
48
- def halt(*args); @sanford_runner.halt(*args); end
49
- def request; @sanford_runner.request; end
50
- def params; @sanford_runner.params; end
51
- def logger; @sanford_runner.logger; end
57
+ # utils
58
+ def logger; @sanford_runner.logger; end
52
59
 
53
- def run_callback(callback)
54
- (self.class.send("#{callback}_callbacks") || []).each do |callback|
55
- self.instance_eval(&callback)
56
- end
57
- end
60
+ # request
61
+ def request; @sanford_runner.request; end
62
+ def params; @sanford_runner.params; end
63
+
64
+ # response
65
+ def status(*args); @sanford_runner.status(*args); end
66
+ def data(*args); @sanford_runner.data(*args); end
67
+ def halt(*args); @sanford_runner.halt(*args); end
68
+ def render(*args); @sanford_runner.render(*args); end
58
69
 
59
70
  end
60
71
 
@@ -83,6 +94,22 @@ module Sanford
83
94
 
84
95
  end
85
96
 
97
+ module TestHelpers
98
+
99
+ def self.included(klass)
100
+ require 'sanford/test_runner'
101
+ end
102
+
103
+ def test_runner(handler_class, args = nil)
104
+ TestRunner.new(handler_class, args)
105
+ end
106
+
107
+ def test_handler(handler_class, args = nil)
108
+ test_runner(handler_class, args).handler
109
+ end
110
+
111
+ end
112
+
86
113
  end
87
114
 
88
115
  end
@@ -8,53 +8,57 @@ module Sanford
8
8
 
9
9
  class TestRunner < Runner
10
10
 
11
- attr_reader :response
12
-
13
11
  def initialize(handler_class, args = nil)
14
12
  if !handler_class.include?(Sanford::ServiceHandler)
15
- raise InvalidServiceHandlerError, "#{handler_class.inspect} is not a"\
16
- " Sanford::ServiceHandler"
13
+ raise InvalidServiceHandlerError, "#{handler_class.inspect} is not a " \
14
+ "Sanford::ServiceHandler"
17
15
  end
18
16
 
19
- args = (args || {}).dup
17
+ a = (args || {}).dup
20
18
  super(handler_class, {
21
- :request => args.delete(:request),
22
- :params => normalize_params(args.delete(:params) || {}),
23
- :logger => args.delete(:logger),
24
- :router => args.delete(:router),
25
- :template_source => args.delete(:template_source)
19
+ :logger => a.delete(:logger),
20
+ :router => a.delete(:router),
21
+ :template_source => a.delete(:template_source),
22
+ :request => a.delete(:request),
23
+ :params => normalize_params(a.delete(:params) || {})
26
24
  })
27
- args.each{ |key, value| @handler.send("#{key}=", value) }
25
+ a.each{ |key, value| @handler.send("#{key}=", value) }
28
26
 
29
- return_value = catch(:halt){ @handler.init; nil }
30
- @response = build_and_serialize_response{ return_value } if return_value
27
+ @halted = false
28
+ catch(:halt){ self.handler.sanford_init }
31
29
  end
32
30
 
33
- # If `init` generated a response, we don't want to `run` at all. This makes
34
- # the `TestRunner` behave similar to the `SanfordRunner`, i.e. `halt` in
35
- # `init` stops processing where `halt` is called.
31
+ def halted?; @halted; end
36
32
 
37
33
  def run
38
- @response ||= build_and_serialize_response{ self.handler.run }
34
+ catch(:halt){ self.handler.sanford_run } if !self.halted?
35
+ self.to_response
36
+ end
37
+
38
+ # attempt to encode (and then throw away) the response
39
+ # this will error on the developer if it can't encode their response
40
+ def to_response
41
+ super.tap do |response|
42
+ Sanford::Protocol.msg_body.encode(response.to_hash) if response
43
+ end
44
+ end
45
+
46
+ # helpers
47
+
48
+ def halt(*args)
49
+ @halted = true
50
+ super
39
51
  end
40
52
 
41
53
  private
42
54
 
43
- # Stringify and encode/decode to ensure params are valid and are
55
+ # stringify and encode/decode to ensure params are valid and are
44
56
  # in the format they would normally be when a handler is built and run.
45
57
  def normalize_params(params)
46
58
  p = Sanford::Protocol::StringifyParams.new(params)
47
59
  Sanford::Protocol.msg_body.decode(Sanford::Protocol.msg_body.encode(p))
48
60
  end
49
61
 
50
- def build_and_serialize_response(&block)
51
- build_response(&block).tap do |response|
52
- # attempt to serialize (and then throw away) the response data
53
- # this will error on the developer if it can't serialize their response
54
- Sanford::Protocol.msg_body.encode(response.to_hash) if response
55
- end
56
- end
57
-
58
62
  end
59
63
 
60
64
  end
@@ -1,3 +1,3 @@
1
1
  module Sanford
2
- VERSION = "0.15.1"
2
+ VERSION = "0.16.0"
3
3
  end
@@ -0,0 +1,106 @@
1
+ require 'much-plugin'
2
+ require 'dat-tcp/worker'
3
+ require 'sanford/connection_handler'
4
+ require 'sanford/server_data'
5
+
6
+ module Sanford
7
+
8
+ module Worker
9
+ include MuchPlugin
10
+
11
+ plugin_included do
12
+ include DatTCP::Worker
13
+ include InstanceMethods
14
+
15
+ end
16
+
17
+ module InstanceMethods
18
+
19
+ def work!(client_socket)
20
+ connection = Connection.new(client_socket)
21
+ return if sanford_keep_alive_connection?(connection)
22
+ Sanford::ConnectionHandler.new(params[:sanford_server_data], connection).run
23
+ ensure
24
+ connection.close rescue false
25
+ end
26
+
27
+ private
28
+
29
+ def sanford_keep_alive_connection?(connection)
30
+ params[:sanford_server_data].receives_keep_alive && connection.peek_data.empty?
31
+ end
32
+
33
+ end
34
+
35
+ class Connection
36
+ DEFAULT_TIMEOUT = 1
37
+
38
+ attr_reader :timeout
39
+
40
+ def initialize(socket)
41
+ @socket = socket
42
+ @connection = Sanford::Protocol::Connection.new(@socket)
43
+ @timeout = (ENV['SANFORD_TIMEOUT'] || DEFAULT_TIMEOUT).to_f
44
+ end
45
+
46
+ def write_data(data)
47
+ TCPCork.apply(@socket)
48
+ @connection.write data
49
+ TCPCork.remove(@socket)
50
+ end
51
+
52
+ def read_data; @connection.read(@timeout); end
53
+ def peek_data; @connection.peek(@timeout); end
54
+ def close; @connection.close; end
55
+ def close_write; @connection.close_write; end
56
+ end
57
+
58
+ module TCPCork
59
+ # On Linux, use TCP_CORK to better control how the TCP stack
60
+ # packetizes our stream. This improves both latency and throughput.
61
+ # TCP_CORK disables Nagle's algorithm, which is ideal for sporadic
62
+ # traffic (like Telnet) but is less optimal for HTTP. Sanford is similar
63
+ # to HTTP, it doesn't receive sporadic packets, it has all its data
64
+ # come in at once.
65
+ # For more information: http://baus.net/on-tcp_cork
66
+
67
+ if RUBY_PLATFORM =~ /linux/
68
+ # 3 == TCP_CORK
69
+ def self.apply(socket)
70
+ socket.setsockopt(::Socket::IPPROTO_TCP, 3, true)
71
+ end
72
+
73
+ def self.remove(socket)
74
+ socket.setsockopt(::Socket::IPPROTO_TCP, 3, false)
75
+ end
76
+ else
77
+ def self.apply(socket); end
78
+ def self.remove(socket); end
79
+ end
80
+ end
81
+
82
+ module TestHelpers
83
+ include MuchPlugin
84
+
85
+ plugin_included do
86
+ include DatTCP::Worker::TestHelpers
87
+ include InstanceMethods
88
+ end
89
+
90
+ module InstanceMethods
91
+
92
+ def test_runner(worker_class, options = nil)
93
+ options ||= {}
94
+ options[:params] = {
95
+ :sanford_server_data => Sanford::ServerData.new,
96
+ }.merge(options[:params] || {})
97
+ super(worker_class, options)
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+
106
+ end
data/sanford.gemspec CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_dependency("dat-tcp", ["~> 0.7"])
21
+ gem.add_dependency("dat-tcp", ["~> 0.8"])
22
+ gem.add_dependency("much-plugin", ["~> 0.1"])
22
23
  gem.add_dependency("ns-options", ["~> 1.1"])
23
24
  gem.add_dependency("sanford-protocol", ["~> 0.11"])
24
25
 
@@ -6,6 +6,9 @@ if !defined?(ROOT_PATH)
6
6
  ROOT_PATH = Pathname.new(File.expand_path('../../..', __FILE__))
7
7
  end
8
8
 
9
+ LOGGER = Logger.new(ROOT_PATH.join('log/app_server.log').to_s)
10
+ LOGGER.datetime_format = "" # turn off the datetime in the logs
11
+
9
12
  class AppERBEngine < Sanford::TemplateEngine
10
13
  RenderScope = Struct.new(:view)
11
14
 
@@ -26,7 +29,7 @@ class AppServer
26
29
 
27
30
  receives_keep_alive true
28
31
 
29
- logger Logger.new(ROOT_PATH.join('log/app_server.log').to_s)
32
+ logger LOGGER
30
33
  verbose_logging true
31
34
 
32
35
  router do
@@ -62,7 +65,7 @@ module AppHandlers
62
65
  include Sanford::ServiceHandler
63
66
 
64
67
  def run!
65
- params['message']
68
+ data(params['message'])
66
69
  end
67
70
  end
68
71
 
@@ -78,7 +81,7 @@ module AppHandlers
78
81
  include Sanford::ServiceHandler
79
82
 
80
83
  def run!
81
- Class.new
84
+ data(Class.new)
82
85
  end
83
86
  end
84
87
 
@@ -121,7 +124,7 @@ module AppHandlers
121
124
 
122
125
  def run!
123
126
  halt(200, :message => "in run") if params['when'] == 'run'
124
- false
127
+ data(false)
125
128
  end
126
129
 
127
130
  after_run do
@@ -284,7 +284,7 @@ module Sanford::Server
284
284
 
285
285
  assert_equal 200, subject.code
286
286
  assert_equal 'in after run', subject.status.message
287
- assert_nil subject.data
287
+ assert_false subject.data
288
288
  end
289
289
 
290
290
  should "allow halting in an after callback" do
@@ -293,7 +293,7 @@ module Sanford::Server
293
293
 
294
294
  assert_equal 200, subject.code
295
295
  assert_equal 'in after', subject.status.message
296
- assert_nil subject.data
296
+ assert_false subject.data
297
297
  end
298
298
 
299
299
  end
@@ -320,7 +320,11 @@ module Sanford::Server
320
320
  setup do
321
321
  # get our current ip address, need something different than 0.0.0.0 and
322
322
  # 127.0.0.1 to bind to
323
- ENV['SANFORD_IP'] = IPSocket.getaddress(Socket.gethostname)
323
+ ENV['SANFORD_IP'] = Socket.getaddrinfo(
324
+ Socket.gethostname,
325
+ 80,
326
+ Socket::AF_INET
327
+ ).first[3]
324
328
  ENV['SANFORD_PORT'] = (AppServer.port + 1).to_s
325
329
 
326
330
  @server = AppServer.new
@@ -1,13 +1,12 @@
1
1
  require 'assert'
2
2
  require 'sanford/service_handler'
3
3
 
4
- require 'sanford/test_helpers'
5
4
  require 'test/support/app_server'
6
5
 
7
6
  module Sanford::ServiceHandler
8
7
 
9
8
  class SystemTests < Assert::Context
10
- include Sanford::TestHelpers
9
+ include Sanford::ServiceHandler::TestHelpers
11
10
 
12
11
  desc "Sanford::ServiceHandler"
13
12
 
@@ -142,22 +142,6 @@ class Sanford::ConnectionHandler
142
142
 
143
143
  end
144
144
 
145
- class RunWithExceptionWhileDebuggingTests < InitTests
146
- desc "and run with a route that throws an exception in debug mode"
147
- setup do
148
- ENV['SANFORD_DEBUG'] = '1'
149
- Assert.stub(@route, :run){ raise @exception }
150
- end
151
- teardown do
152
- ENV.delete('SANFORD_DEBUG')
153
- end
154
-
155
- should "raise the exception" do
156
- assert_raises(@exception.class){ @connection_handler.run }
157
- end
158
-
159
- end
160
-
161
145
  class RunWithVerboseLoggingTests < UnitTests
162
146
  desc "run with verbose logging"
163
147
  setup do
@@ -1,6 +1,7 @@
1
1
  require 'assert'
2
2
  require 'sanford/runner'
3
3
 
4
+ require 'sanford-protocol'
4
5
  require 'sanford/logger'
5
6
  require 'sanford/router'
6
7
  require 'sanford/template_source'
@@ -12,23 +13,35 @@ class Sanford::Runner
12
13
  desc "Sanford::Runner"
13
14
  setup do
14
15
  @handler_class = TestServiceHandler
15
- @runner_class = Sanford::Runner
16
+ @runner_class = Sanford::Runner
16
17
  end
17
18
  subject{ @runner_class }
18
19
 
20
+ should "know its default status code " do
21
+ assert_equal 200, subject::DEFAULT_STATUS_CODE
22
+ end
23
+
24
+ should "know its default status msg " do
25
+ assert_equal nil, subject::DEFAULT_STATUS_MSG
26
+ end
27
+
28
+ should "know its default data" do
29
+ assert_equal nil, subject::DEFAULT_DATA
30
+ end
31
+
19
32
  end
20
33
 
21
34
  class InitTests < UnitTests
22
35
  desc "when init"
23
36
  setup do
24
- source = FakeTemplateSource.new
25
- @runner = @runner_class.new(@handler_class, :template_source => source)
37
+ @runner = @runner_class.new(@handler_class)
26
38
  end
27
39
  subject{ @runner }
28
40
 
29
41
  should have_readers :handler_class, :handler
30
- should have_readers :request, :params, :logger, :router, :template_source
31
- should have_imeths :run
42
+ should have_readers :logger, :router, :template_source
43
+ should have_readers :request, :params
44
+ should have_imeths :run, :to_response
32
45
  should have_imeths :halt
33
46
 
34
47
  should "know its handler class and handler" do
@@ -36,68 +49,166 @@ class Sanford::Runner
36
49
  assert_instance_of @handler_class, subject.handler
37
50
  end
38
51
 
39
- should "default its settings" do
52
+ should "default its attrs" do
40
53
  runner = @runner_class.new(@handler_class)
41
- assert_nil runner.request
42
- assert_equal ::Hash.new, runner.params
43
54
  assert_kind_of Sanford::NullLogger, runner.logger
44
55
  assert_kind_of Sanford::Router, runner.router
45
56
  assert_kind_of Sanford::NullTemplateSource, runner.template_source
57
+
58
+ assert_nil runner.request
59
+
60
+ assert_equal({}, subject.params)
61
+ end
62
+
63
+ should "know its attrs" do
64
+ args = {
65
+ :logger => 'a-logger',
66
+ :router => 'a-router',
67
+ :template_source => 'a-source',
68
+ :request => 'a-request',
69
+ :params => {}
70
+ }
71
+
72
+ runner = @runner_class.new(@handler_class, args)
73
+
74
+ assert_equal args[:logger], runner.logger
75
+ assert_equal args[:router], runner.router
76
+ assert_equal args[:template_source], runner.template_source
77
+ assert_equal args[:request], runner.request
78
+ assert_equal args[:params], runner.params
46
79
  end
47
80
 
48
81
  should "not implement its run method" do
49
82
  assert_raises(NotImplementedError){ subject.run }
50
83
  end
51
84
 
52
- should "use the template source to render" do
53
- path = 'template.json'
54
- locals = { 'something' => Factory.string }
55
- exp = subject.template_source.render(path, subject.handler, locals)
56
- assert_equal exp, subject.render(path, locals)
85
+ should "know its `to_response` representation" do
86
+ exp = Sanford::Protocol::Response.new(
87
+ [subject.class::DEFAULT_STATUS_CODE, subject.class::DEFAULT_STATUS_MSG],
88
+ subject.class::DEFAULT_DATA
89
+ )
90
+ assert_equal exp, subject.to_response
91
+
92
+ code, msg, data = Factory.integer, Factory.string, Factory.text
93
+ subject.status(code, :message => msg)
94
+ subject.data(data)
95
+ exp = Sanford::Protocol::Response.new([code, msg], data)
96
+ assert_equal exp, subject.to_response
57
97
  end
58
98
 
59
- should "default its locals to an empty hash when rendering" do
60
- path = Factory.file_path
61
- exp = subject.template_source.render(path, subject.handler, {})
62
- assert_equal exp, subject.render(path)
99
+ should "know and set its response status" do
100
+ assert_equal [nil, nil], subject.status
101
+
102
+ code, msg = Factory.integer, Factory.string
103
+ subject.status(code, :message => msg)
104
+ assert_equal [code, msg], subject.status
63
105
  end
64
106
 
65
- should "throw halt with response args using `halt`" do
66
- code = Factory.integer
67
- message = Factory.string
68
- data = Factory.string
107
+ should "know and set its response data" do
108
+ assert_nil subject.data
69
109
 
70
- result = catch(:halt) do
71
- subject.halt(code, :message => message, :data => data)
72
- end
73
- assert_instance_of ResponseArgs, result
74
- assert_equal [ code, message ], result.status
75
- assert_equal data, result.data
110
+ exp = Factory.text
111
+ subject.data exp
112
+ assert_equal exp, subject.data
113
+ end
114
+
115
+ end
116
+
117
+ class HaltTests < InitTests
118
+ desc "the `halt` method"
119
+ setup do
120
+ @code = Factory.integer
121
+ @message = Factory.string
122
+ @data = Factory.string
123
+ end
124
+
125
+ should "set response attrs and halt execution" do
126
+ runner = runner_halted_with()
127
+ assert_nil runner.status.first
128
+ assert_nil runner.status.last
129
+ assert_nil runner.data
130
+
131
+ runner = runner_halted_with(@code)
132
+ assert_equal @code, runner.status.first
133
+ assert_nil runner.status.last
134
+ assert_nil runner.data
135
+
136
+ runner = runner_halted_with(:message => @message)
137
+ assert_nil runner.status.first
138
+ assert_equal @message, runner.status.last
139
+ assert_nil runner.data
140
+
141
+ runner = runner_halted_with(:data => @data)
142
+ assert_nil runner.status.first
143
+ assert_nil runner.status.last
144
+ assert_equal @data, runner.data
145
+
146
+ runner = runner_halted_with(@code, :message => @message)
147
+ assert_equal @code, runner.status.first
148
+ assert_equal @message, runner.status.last
149
+ assert_nil runner.data
150
+
151
+ runner = runner_halted_with(@code, :data => @data)
152
+ assert_equal @code, runner.status.first
153
+ assert_nil runner.status.last
154
+ assert_equal @data, runner.data
155
+
156
+ runner = runner_halted_with({
157
+ :message => @message,
158
+ :data => @data
159
+ })
160
+ assert_nil runner.status.first
161
+ assert_equal @message, runner.status.last
162
+ assert_equal @data, runner.data
163
+
164
+ runner = runner_halted_with(@code, {
165
+ :message => @message,
166
+ :data => @data
167
+ })
168
+ assert_equal @code, runner.status.first
169
+ assert_equal @message, runner.status.last
170
+ assert_equal @data, runner.data
76
171
  end
77
172
 
78
- should "accept string keys using `halt`" do
79
- code = Factory.integer
80
- message = Factory.string
81
- data = Factory.string
173
+ private
82
174
 
83
- result = catch(:halt) do
84
- subject.halt(code, 'message' => message, 'data' => data)
175
+ def runner_halted_with(*halt_args)
176
+ @runner_class.new(@handler_class).tap do |runner|
177
+ catch(:halt){ runner.halt(*halt_args) }
85
178
  end
86
- assert_instance_of ResponseArgs, result
87
- assert_equal [ code, message ], result.status
88
- assert_equal data, result.data
179
+ end
180
+
181
+ end
182
+
183
+ class RenderTests < InitTests
184
+ desc "the `render` method"
185
+ setup do
186
+ @template_name = Factory.path
187
+ @locals = { Factory.string => Factory.string }
188
+ data = @data = Factory.text
189
+ @render_called_with = nil
190
+ @source = @runner.template_source
191
+ Assert.stub(@source, :render){ |*args| @render_called_with = args; data }
192
+ end
193
+
194
+ should "call to the template source's render method and set the return value as data" do
195
+ subject.render(@template_name, @locals)
196
+ exp = [@template_name, subject.handler, @locals]
197
+ assert_equal exp, @render_called_with
198
+ assert_equal @data, subject.data
199
+ end
200
+
201
+ should "default the locals if none given" do
202
+ subject.render(@template_name)
203
+ exp = [@template_name, subject.handler, {}]
204
+ assert_equal exp, @render_called_with
89
205
  end
90
206
 
91
207
  end
92
208
 
93
209
  class TestServiceHandler
94
210
  include Sanford::ServiceHandler
95
- end
96
211
 
97
- class FakeTemplateSource
98
- def render(path, service_handler, locals)
99
- [path.to_s, service_handler.class.to_s, locals]
100
- end
101
212
  end
102
213
 
103
214
  end