sanford 0.15.1 → 0.16.0

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.
@@ -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