thin 1.2.4 → 1.2.5

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.
@@ -207,6 +207,9 @@ static void header_done(void *data, const char *at, size_t length)
207
207
  if (rb_hash_aref(req, global_query_string) == Qnil) {
208
208
  rb_hash_aset(req, global_query_string, global_empty);
209
209
  }
210
+ if (rb_hash_aref(req, global_path_info) == Qnil) {
211
+ rb_hash_aset(req, global_path_info, global_empty);
212
+ }
210
213
 
211
214
  /* set some constants */
212
215
  rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
@@ -32,9 +32,8 @@ module Rack
32
32
  end
33
33
 
34
34
  def rack_based?
35
- ActionController.const_defined?(:Dispatcher) &&
36
- (ActionController::Dispatcher.instance_methods.include?(:call) ||
37
- ActionController::Dispatcher.instance_methods.include?("call"))
35
+ rails_version = ::Rails::VERSION
36
+ rails_version::MAJOR >= 2 && rails_version::MINOR >= 2 && rails_version::TINY >= 3
38
37
  end
39
38
 
40
39
  def load_application
@@ -1,4 +1,9 @@
1
+ require 'socket'
2
+
1
3
  module Thin
4
+ # An exception class to handle the event that server didn't start on time
5
+ class RestartTimeout < RuntimeError; end
6
+
2
7
  module Controllers
3
8
  # Control a set of servers.
4
9
  # * Generate start and stop commands and run them.
@@ -7,7 +12,10 @@ module Thin
7
12
  class Cluster < Controller
8
13
  # Cluster only options that should not be passed in the command sent
9
14
  # to the indiviual servers.
10
- CLUSTER_OPTIONS = [:servers, :only]
15
+ CLUSTER_OPTIONS = [:servers, :only, :onebyone, :wait]
16
+
17
+ # Maximum wait time for the server to be restarted
18
+ DEFAULT_WAIT_TIME = 30 # seconds
11
19
 
12
20
  # Create a new cluster of servers launched using +options+.
13
21
  def initialize(options)
@@ -15,7 +23,7 @@ module Thin
15
23
  # Cluster can only contain daemonized servers
16
24
  @options.merge!(:daemonize => true)
17
25
  end
18
-
26
+
19
27
  def first_port; @options[:port] end
20
28
  def address; @options[:address] end
21
29
  def socket; @options[:socket] end
@@ -23,7 +31,9 @@ module Thin
23
31
  def log_file; @options[:log] end
24
32
  def size; @options[:servers] end
25
33
  def only; @options[:only] end
26
-
34
+ def onebyone; @options[:onebyone] end
35
+ def wait; @options[:wait] end
36
+
27
37
  def swiftiply?
28
38
  @options.has_key?(:swiftiply)
29
39
  end
@@ -54,9 +64,50 @@ module Thin
54
64
 
55
65
  # Stop and start the servers.
56
66
  def restart
57
- stop
58
- sleep 0.1 # Let's breath a bit shall we ?
59
- start
67
+ unless onebyone
68
+ # Let's do a normal restart by defaults
69
+ stop
70
+ sleep 0.1 # Let's breath a bit shall we ?
71
+ start
72
+ else
73
+ with_each_server do |n|
74
+ stop_server(n)
75
+ sleep 0.1 # Let's breath a bit shall we ?
76
+ start_server(n)
77
+ wait_until_server_started(n)
78
+ end
79
+ end
80
+ end
81
+
82
+ def test_socket(number)
83
+ if socket
84
+ UNIXSocket.new(socket_for(number))
85
+ else
86
+ TCPSocket.new(address, number)
87
+ end
88
+ rescue
89
+ nil
90
+ end
91
+
92
+ # Make sure the server is running before moving on to the next one.
93
+ def wait_until_server_started(number)
94
+ log "Waiting for server to start ..."
95
+ STDOUT.flush # Need this to make sure user got the message
96
+
97
+ tries = 0
98
+ loop do
99
+ if test_socket = test_socket(number)
100
+ test_socket.close
101
+ break
102
+ elsif tries < wait
103
+ sleep 1
104
+ tries += 1
105
+ else
106
+ raise RestartTimeout, "The server didn't start in time. Please look at server's log file " +
107
+ "for more information, or set the value of 'wait' in your config " +
108
+ "file to be higher (defaults: 30)."
109
+ end
110
+ end
60
111
  end
61
112
 
62
113
  def server_id(number)
data/lib/thin/request.rb CHANGED
@@ -13,7 +13,11 @@ module Thin
13
13
  MAX_BODY = 1024 * (80 + 32)
14
14
  BODY_TMPFILE = 'thin-body'.freeze
15
15
  MAX_HEADER = 1024 * (80 + 32)
16
-
16
+
17
+ INITIAL_BODY = ''
18
+ # Force external_encoding of request's body to ASCII_8BIT
19
+ INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode)
20
+
17
21
  # Freeze some HTTP header names & values
18
22
  SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
19
23
  SERVER_NAME = 'SERVER_NAME'.freeze
@@ -49,7 +53,7 @@ module Thin
49
53
  @parser = Thin::HttpParser.new
50
54
  @data = ''
51
55
  @nparsed = 0
52
- @body = StringIO.new
56
+ @body = StringIO.new(INITIAL_BODY.dup)
53
57
  @env = {
54
58
  SERVER_SOFTWARE => SERVER,
55
59
  SERVER_NAME => LOCALHOST,
data/lib/thin/runner.rb CHANGED
@@ -41,7 +41,8 @@ module Thin
41
41
  :pid => 'tmp/pids/thin.pid',
42
42
  :max_conns => Server::DEFAULT_MAXIMUM_CONNECTIONS,
43
43
  :max_persistent_conns => Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS,
44
- :require => []
44
+ :require => [],
45
+ :wait => Controllers::Cluster::DEFAULT_WAIT_TIME
45
46
  }
46
47
 
47
48
  parse!
@@ -96,6 +97,8 @@ module Thin
96
97
  opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only.to_i }
97
98
  opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
98
99
  opts.on( "--all [DIR]", "Send command to each config files in DIR") { |dir| @options[:all] = dir } if Thin.linux?
100
+ opts.on("-O", "--onebyone", "Restart the cluster one by one (only works with restart command)") { @options[:onebyone] = true }
101
+ opts.on("-w", "--wait NUM", "Maximum wait time for server to be started in seconds (use with -O)") { |time| @options[:wait] = time.to_i }
99
102
  end
100
103
 
101
104
  opts.separator ""
@@ -105,7 +108,7 @@ module Thin
105
108
  opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
106
109
  "(default: #{@options[:timeout]})") { |sec| @options[:timeout] = sec.to_i }
107
110
  opts.on("-f", "--force", "Force the execution of the command") { @options[:force] = true }
108
- opts.on( "--max-conns NUM", "Maximum number of connections " +
111
+ opts.on( "--max-conns NUM", "Maximum number of open file descriptors " +
109
112
  "(default: #{@options[:max_conns]})",
110
113
  "Might require sudo to set higher then 1024") { |num| @options[:max_conns] = num.to_i } unless Thin.win?
111
114
  opts.on( "--max-persistent-conns NUM",
data/lib/thin/version.rb CHANGED
@@ -6,11 +6,11 @@ module Thin
6
6
  module VERSION #:nodoc:
7
7
  MAJOR = 1
8
8
  MINOR = 2
9
- TINY = 4
9
+ TINY = 5
10
10
 
11
11
  STRING = [MAJOR, MINOR, TINY].join('.')
12
12
 
13
- CODENAME = "Flaming Astroboy".freeze
13
+ CODENAME = "This Is Not A Web Server".freeze
14
14
 
15
15
  RACK = [1, 0].freeze # Rack protocol version
16
16
  end
@@ -232,4 +232,36 @@ describe Cluster, "with Swiftiply" do
232
232
  def options_for_swiftiply(number)
233
233
  { :address => '0.0.0.0', :port => 3000, :daemonize => true, :log => "thin.#{number}.log", :timeout => 10, :pid => "thin.#{number}.pid", :chdir => "/rails_app", :swiftiply => true }
234
234
  end
235
+ end
236
+
237
+ describe Cluster, "rolling restart" do
238
+ before do
239
+ @cluster = Cluster.new(:chdir => '/rails_app',
240
+ :address => '0.0.0.0',
241
+ :port => 3000,
242
+ :servers => 2,
243
+ :timeout => 10,
244
+ :log => 'thin.log',
245
+ :pid => 'thin.pid',
246
+ :onebyone => true,
247
+ :wait => 30
248
+ )
249
+ end
250
+
251
+ it "should restart servers one by one" do
252
+ Command.should_receive(:run).with(:stop, options_for_port(3000))
253
+ Command.should_receive(:run).with(:start, options_for_port(3000))
254
+ @cluster.should_receive(:wait_until_server_started).with(3000)
255
+
256
+ Command.should_receive(:run).with(:stop, options_for_port(3001))
257
+ Command.should_receive(:run).with(:start, options_for_port(3001))
258
+ @cluster.should_receive(:wait_until_server_started).with(3001)
259
+
260
+ @cluster.restart
261
+ end
262
+
263
+ private
264
+ def options_for_port(port)
265
+ { :daemonize => true, :log => "thin.#{port}.log", :timeout => 10, :address => "0.0.0.0", :port => port, :pid => "thin.#{port}.pid", :chdir => "/rails_app" }
266
+ end
235
267
  end
@@ -209,7 +209,35 @@ EOS
209
209
  parser = HttpParser.new
210
210
  req = {}
211
211
  nread = parser.execute(req, req_str, 0)
212
- req.should be_has_key('HTTP_HOS_T')
212
+ req.should have_key('HTTP_HOS_T')
213
213
  }
214
214
  end
215
+
216
+ it "should parse PATH_INFO with semicolon" do
217
+ qs = "QUERY_STRING"
218
+ pi = "PATH_INFO"
219
+ {
220
+ "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
221
+ "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
222
+ "/1;a=b" => { qs => "", pi => "/1;a=b" },
223
+ "/1;a=b?" => { qs => "", pi => "/1;a=b" },
224
+ "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
225
+ "*" => { qs => "", pi => "" },
226
+ }.each do |uri, expect|
227
+ parser = HttpParser.new
228
+ env = {}
229
+ nread = parser.execute(env, "GET #{uri} HTTP/1.1\r\nHost: www.example.com\r\n\r\n", 0)
230
+
231
+ env[pi].should == expect[pi]
232
+ env[qs].should == expect[qs]
233
+ env["REQUEST_URI"].should == uri
234
+
235
+ next if uri == "*"
236
+
237
+ # Validate w/ Ruby's URI.parse
238
+ uri = URI.parse("http://example.com#{uri}")
239
+ env[qs].should == uri.query.to_s
240
+ env[pi].should == uri.path
241
+ end
242
+ end
215
243
  end
@@ -42,4 +42,8 @@ describe Request, 'processing' do
42
42
  big_headers = "X-Test: X\r\n" * (1024 * (80 + 32))
43
43
  proc { R("GET / HTTP/1.1\r\n#{big_headers}\r\n") }.should raise_error(InvalidRequest)
44
44
  end
45
+
46
+ it "should set body external encoding to ASCII_8BIT" do
47
+ Request.new.body.external_encoding.should == Encoding::ASCII_8BIT
48
+ end
45
49
  end
data/spec/spec_helper.rb CHANGED
@@ -4,7 +4,6 @@ require 'spec'
4
4
  require 'benchmark'
5
5
  require 'timeout'
6
6
  require 'fileutils'
7
- require 'benchmark_unit'
8
7
  require 'net/http'
9
8
  require 'socket'
10
9
 
@@ -23,6 +22,7 @@ end
23
22
  module Matchers
24
23
  class BeFasterThen
25
24
  def initialize(max_time)
25
+ require 'benchmark_unit'
26
26
  @max_time = max_time
27
27
  end
28
28
 
data/tasks/gem.rake CHANGED
@@ -1,8 +1,6 @@
1
1
  require 'rake/gempackagetask'
2
2
  require 'yaml'
3
3
 
4
- WIN_SUFFIX = ENV['WIN_SUFFIX'] || 'x86-mswin32'
5
-
6
4
  task :clean => :clobber_package
7
5
 
8
6
  Thin::GemSpec = Gem::Specification.new do |s|
@@ -59,16 +57,10 @@ end
59
57
  task :gem => :tag_warn
60
58
 
61
59
  namespace :gem do
62
- desc "Update the gemspec for GitHub's gem server"
63
- task :github do
64
- File.open("thin.gemspec", 'w') { |f| f << YAML.dump(Thin::GemSpec) }
65
- end
66
-
67
- desc 'Upload gems (ruby & win32) to rubyforge.org'
68
- task :upload => :gem do
69
- sh 'rubyforge login'
70
- sh "rubyforge add_release thin thin #{Thin::VERSION::STRING} pkg/#{Thin::GemSpec.full_name}.gem"
71
- sh "rubyforge add_file thin thin #{Thin::VERSION::STRING} pkg/#{Thin::GemSpec.full_name}.gem"
72
- sh "rubyforge add_file thin thin #{Thin::VERSION::STRING} pkg/#{Thin::GemSpec.full_name}-#{WIN_SUFFIX}.gem"
60
+ desc 'Upload gems to gemcutter.org'
61
+ task :push => :gem do
62
+ Dir["pkg/#{Thin::GemSpec.full_name}*.gem"].each do |file|
63
+ puts "gem push #{file}"
64
+ end
73
65
  end
74
66
  end
data/tasks/spec.rake CHANGED
@@ -1,49 +1,43 @@
1
1
  CLEAN.include %w(coverage tmp log)
2
2
 
3
- if RUBY_1_9 # RSpec not yet working w/ Ruby 1.9
4
- task :spec do
5
- warn 'RSpec not yet supporting Ruby 1.9, so cannot run the specs :('
6
- end
7
- else
8
- require 'spec/rake/spectask'
9
-
10
- PERF_SPECS = FileList['spec/perf/*_spec.rb']
11
- WIN_SPECS = %w(
12
- spec/backends/unix_server_spec.rb
13
- spec/controllers/service_spec.rb
14
- spec/daemonizing_spec.rb
15
- spec/server/unix_socket_spec.rb
16
- spec/server/swiftiply_spec.rb
17
- )
18
- # HACK Event machine causes some problems when running multiple
19
- # tests in the same VM so we split the specs in 2 before I find
20
- # a better solution...
21
- SPECS2 = %w(spec/server/threaded_spec.rb spec/server/tcp_spec.rb)
22
- SPECS = FileList['spec/**/*_spec.rb'] - PERF_SPECS - SPECS2
23
-
24
- def spec_task(name, specs)
25
- Spec::Rake::SpecTask.new(name) do |t|
26
- t.libs << 'lib'
27
- t.spec_opts = %w(-fs -c)
28
- t.spec_files = specs
29
- end
3
+ require 'spec/rake/spectask'
4
+
5
+ PERF_SPECS = FileList['spec/perf/*_spec.rb']
6
+ WIN_SPECS = %w(
7
+ spec/backends/unix_server_spec.rb
8
+ spec/controllers/service_spec.rb
9
+ spec/daemonizing_spec.rb
10
+ spec/server/unix_socket_spec.rb
11
+ spec/server/swiftiply_spec.rb
12
+ )
13
+ # HACK Event machine causes some problems when running multiple
14
+ # tests in the same VM so we split the specs in 2 before I find
15
+ # a better solution...
16
+ SPECS2 = %w(spec/server/threaded_spec.rb spec/server/tcp_spec.rb)
17
+ SPECS = FileList['spec/**/*_spec.rb'] - PERF_SPECS - SPECS2
18
+
19
+ def spec_task(name, specs)
20
+ Spec::Rake::SpecTask.new(name) do |t|
21
+ t.libs << 'lib'
22
+ t.spec_opts = %w(-fs -c)
23
+ t.spec_files = specs
30
24
  end
31
-
32
- desc "Run all examples"
33
- spec_task :spec, SPECS
34
- spec_task :spec2, SPECS2
35
- task :spec => [:compile, :spec2]
36
-
37
- desc "Run all performance examples"
38
- spec_task 'spec:perf', PERF_SPECS
39
-
40
- task :check_benchmark_unit_gem do
41
- begin
42
- require 'benchmark_unit'
43
- rescue LoadError
44
- abort "To run specs, install benchmark_unit gem"
45
- end
25
+ end
26
+
27
+ desc "Run all examples"
28
+ spec_task :spec, SPECS
29
+ spec_task :spec2, SPECS2
30
+ task :spec => [:compile, :spec2]
31
+
32
+ desc "Run all performance examples"
33
+ spec_task 'spec:perf', PERF_SPECS
34
+
35
+ task :check_benchmark_unit_gem do
36
+ begin
37
+ require 'benchmark_unit'
38
+ rescue LoadError
39
+ abort "To run specs, install benchmark_unit gem"
46
40
  end
47
-
48
- task 'spec:perf' => :check_benchmark_unit_gem
49
- end
41
+ end
42
+
43
+ task 'spec:perf' => :check_benchmark_unit_gem
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.4
4
+ version: 1.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc-Andre Cournoyer
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-05 00:00:00 -04:00
12
+ date: 2009-12-13 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -72,19 +72,14 @@ files:
72
72
  - example/thin_solaris_smf.erb
73
73
  - example/thin_solaris_smf.readme.txt
74
74
  - example/vlad.rake
75
- - lib/rack
76
- - lib/rack/adapter
77
75
  - lib/rack/adapter/loader.rb
78
76
  - lib/rack/adapter/rails.rb
79
- - lib/thin
80
- - lib/thin/backends
81
77
  - lib/thin/backends/base.rb
82
78
  - lib/thin/backends/swiftiply_client.rb
83
79
  - lib/thin/backends/tcp_server.rb
84
80
  - lib/thin/backends/unix_server.rb
85
81
  - lib/thin/command.rb
86
82
  - lib/thin/connection.rb
87
- - lib/thin/controllers
88
83
  - lib/thin/controllers/cluster.rb
89
84
  - lib/thin/controllers/controller.rb
90
85
  - lib/thin/controllers/service.rb
@@ -101,53 +96,36 @@ files:
101
96
  - lib/thin/statuses.rb
102
97
  - lib/thin/version.rb
103
98
  - lib/thin.rb
104
- - spec/backends
105
99
  - spec/backends/swiftiply_client_spec.rb
106
100
  - spec/backends/tcp_server_spec.rb
107
101
  - spec/backends/unix_server_spec.rb
108
102
  - spec/command_spec.rb
109
- - spec/configs
110
103
  - spec/configs/cluster.yml
111
104
  - spec/configs/single.yml
112
105
  - spec/connection_spec.rb
113
- - spec/controllers
114
106
  - spec/controllers/cluster_spec.rb
115
107
  - spec/controllers/controller_spec.rb
116
108
  - spec/controllers/service_spec.rb
117
109
  - spec/daemonizing_spec.rb
118
110
  - spec/headers_spec.rb
119
111
  - spec/logging_spec.rb
120
- - spec/perf
121
112
  - spec/perf/request_perf_spec.rb
122
113
  - spec/perf/response_perf_spec.rb
123
114
  - spec/perf/server_perf_spec.rb
124
- - spec/rack
125
115
  - spec/rack/loader_spec.rb
126
116
  - spec/rack/rails_adapter_spec.rb
127
- - spec/rails_app
128
- - spec/rails_app/app
129
- - spec/rails_app/app/controllers
130
117
  - spec/rails_app/app/controllers/application.rb
131
118
  - spec/rails_app/app/controllers/simple_controller.rb
132
- - spec/rails_app/app/helpers
133
119
  - spec/rails_app/app/helpers/application_helper.rb
134
- - spec/rails_app/app/views
135
- - spec/rails_app/app/views/simple
136
120
  - spec/rails_app/app/views/simple/index.html.erb
137
- - spec/rails_app/config
138
121
  - spec/rails_app/config/boot.rb
139
122
  - spec/rails_app/config/environment.rb
140
- - spec/rails_app/config/environments
141
123
  - spec/rails_app/config/environments/development.rb
142
124
  - spec/rails_app/config/environments/production.rb
143
125
  - spec/rails_app/config/environments/test.rb
144
- - spec/rails_app/config/initializers
145
126
  - spec/rails_app/config/initializers/inflections.rb
146
127
  - spec/rails_app/config/initializers/mime_types.rb
147
128
  - spec/rails_app/config/routes.rb
148
- - spec/rails_app/log
149
- - spec/rails_app/log/mongrel_debug
150
- - spec/rails_app/public
151
129
  - spec/rails_app/public/404.html
152
130
  - spec/rails_app/public/422.html
153
131
  - spec/rails_app/public/500.html
@@ -155,40 +133,33 @@ files:
155
133
  - spec/rails_app/public/dispatch.fcgi
156
134
  - spec/rails_app/public/dispatch.rb
157
135
  - spec/rails_app/public/favicon.ico
158
- - spec/rails_app/public/images
159
136
  - spec/rails_app/public/images/rails.png
160
137
  - spec/rails_app/public/index.html
161
- - spec/rails_app/public/javascripts
162
138
  - spec/rails_app/public/javascripts/application.js
163
139
  - spec/rails_app/public/javascripts/controls.js
164
140
  - spec/rails_app/public/javascripts/dragdrop.js
165
141
  - spec/rails_app/public/javascripts/effects.js
166
142
  - spec/rails_app/public/javascripts/prototype.js
167
143
  - spec/rails_app/public/robots.txt
168
- - spec/rails_app/script
169
144
  - spec/rails_app/script/about
170
145
  - spec/rails_app/script/console
171
146
  - spec/rails_app/script/destroy
172
147
  - spec/rails_app/script/generate
173
- - spec/rails_app/script/performance
174
148
  - spec/rails_app/script/performance/benchmarker
175
149
  - spec/rails_app/script/performance/profiler
176
150
  - spec/rails_app/script/performance/request
177
151
  - spec/rails_app/script/plugin
178
- - spec/rails_app/script/process
179
152
  - spec/rails_app/script/process/inspector
180
153
  - spec/rails_app/script/process/reaper
181
154
  - spec/rails_app/script/process/spawner
182
155
  - spec/rails_app/script/runner
183
156
  - spec/rails_app/script/server
184
- - spec/request
185
157
  - spec/request/mongrel_spec.rb
186
158
  - spec/request/parser_spec.rb
187
159
  - spec/request/persistent_spec.rb
188
160
  - spec/request/processing_spec.rb
189
161
  - spec/response_spec.rb
190
162
  - spec/runner_spec.rb
191
- - spec/server
192
163
  - spec/server/builder_spec.rb
193
164
  - spec/server/pipelining_spec.rb
194
165
  - spec/server/robustness_spec.rb
@@ -217,6 +188,8 @@ files:
217
188
  - ext/thin_parser/parser.rl
218
189
  has_rdoc: true
219
190
  homepage: http://code.macournoyer.com/thin/
191
+ licenses: []
192
+
220
193
  post_install_message:
221
194
  rdoc_options: []
222
195
 
@@ -237,9 +210,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
237
210
  requirements: []
238
211
 
239
212
  rubyforge_project: thin
240
- rubygems_version: 1.3.1
213
+ rubygems_version: 1.3.5
241
214
  signing_key:
242
- specification_version: 2
215
+ specification_version: 3
243
216
  summary: A thin and fast web server
244
217
  test_files: []
245
218