aboutsummaryrefslogtreecommitdiff
path: root/lib/mauve
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mauve')
-rw-r--r--lib/mauve/configuration.rb56
-rw-r--r--lib/mauve/heartbeat.rb64
-rw-r--r--lib/mauve/http_server.rb7
-rw-r--r--lib/mauve/mauve_thread.rb70
-rw-r--r--lib/mauve/notifier.rb6
-rw-r--r--lib/mauve/notifiers/templates/email.html.erb4
-rw-r--r--lib/mauve/notifiers/templates/email.txt.erb2
-rw-r--r--lib/mauve/notifiers/templates/xmpp.html.erb21
-rw-r--r--lib/mauve/notifiers/templates/xmpp.txt.erb8
-rw-r--r--lib/mauve/notifiers/xmpp.rb75
-rw-r--r--lib/mauve/server.rb25
-rw-r--r--lib/mauve/timer.rb15
-rw-r--r--lib/mauve/web_interface.rb12
13 files changed, 237 insertions, 128 deletions
diff --git a/lib/mauve/configuration.rb b/lib/mauve/configuration.rb
index 0d3e520..2794c03 100644
--- a/lib/mauve/configuration.rb
+++ b/lib/mauve/configuration.rb
@@ -7,6 +7,7 @@ require 'mauve/notification'
require 'mauve/alert_group'
require 'mauve/people_list'
require 'mauve/source_list'
+require 'mauve/heartbeat'
# Seconds, minutes, hours, days, and weeks... More than that, we
# really should not need it.
@@ -64,8 +65,8 @@ module Mauve
attr_reader :alert_groups
attr_reader :people_lists
attr_reader :source_lists
- attr_reader :logger
-
+ attr_reader :logger
+
def initialize
@notification_methods = {}
@people = {}
@@ -115,6 +116,7 @@ module Mauve
is_builder "outputter", LoggerOutputterBuilder
+
def builder_setup
logger = Log4r::Logger.new("Mauve")
@default_format = nil
@@ -166,14 +168,11 @@ module Mauve
class ProcessorBuilder < ObjectBuilder
is_attribute "sleep_interval"
+ is_attribute "transmission_cache_expire_time"
def builder_setup
@result = Processor.instance
end
-
- def method_missing(name, value)
- @args[name] = value
- end
end
class UDPServerBuilder < ObjectBuilder
@@ -184,10 +183,6 @@ module Mauve
def builder_setup
@result = UDPServer.instance
end
-
- def method_missing(name, value)
- @args[name] = value
- end
end
class TimerBuilder < ObjectBuilder
@@ -196,12 +191,15 @@ module Mauve
def builder_setup
@result = Timer.instance
end
+ end
- def method_missing(name, value)
- @args[name] = value
+ class HeartbeatBuilder < ObjectBuilder
+ is_attribute "destination"
+ is_attribute "interval"
+
+ def builder_setup
+ @result = Heartbeat.instance
end
-
-
end
class HTTPServerBuilder < ObjectBuilder
@@ -210,15 +208,11 @@ module Mauve
is_attribute "ip"
is_attribute "document_root"
is_attribute "session_secret"
+ is_attribute "base_url"
def builder_setup
@result = HTTPServer.instance
end
-
- def method_missing(name, value)
- @args[name] = value
- end
-
end
class NotifierBuilder < ObjectBuilder
@@ -227,11 +221,6 @@ module Mauve
def builder_setup
@result = Notifier.instance
end
-
- def method_missing(name, value)
- @args[name] = value
- end
-
end
class ServerBuilder < ObjectBuilder
@@ -241,22 +230,22 @@ module Mauve
is_builder "processor", ProcessorBuilder
is_builder "timer", TimerBuilder
is_builder "notifier", NotifierBuilder
-
+ is_builder "heartbeat", HeartbeatBuilder
+
+ is_attribute "hostname"
+ is_attribute "database"
+ is_attribute "initial_sleep"
+
def builder_setup
+ @result = Mauve::Server.instance
@args = {}
end
def result
- @result = Mauve::Server.instance
@result.configure(@args)
- @result.web_interface = @web_interface
@result
end
- def method_missing(name, value)
- @args[name] = value
- end
-
def created_web_interface(web_interface)
@web_interface = web_interface
end
@@ -272,6 +261,10 @@ module Mauve
def created_notifier(notifier)
@notifier = notifier
end
+
+ def created_heartbeat(heartbeat)
+ @heartbeat = heartbeat
+ end
end
class NotificationMethodBuilder < ObjectBuilder
@@ -282,7 +275,6 @@ module Mauve
provider("Default")
end
-
def provider(name)
notifiers_base = Mauve::Notifiers
notifiers_type = notifiers_base.const_get(@notification_type)
diff --git a/lib/mauve/heartbeat.rb b/lib/mauve/heartbeat.rb
new file mode 100644
index 0000000..0f51f80
--- /dev/null
+++ b/lib/mauve/heartbeat.rb
@@ -0,0 +1,64 @@
+require 'mauve/sender'
+require 'mauve/proto'
+require 'mauve/mauve_thread'
+require 'log4r'
+
+#
+# This class is responsible for sending a heartbeat to another mauve instance elsewhere.
+#
+module Mauve
+
+ class Heartbeat < MauveThread
+
+ include Singleton
+
+ attr_accessor :destination, :summary, :detail
+ attr_reader :sleep_interval, :raise_at
+
+ def initialize
+ super
+
+ @destination = nil
+ @summary = "Mauve alert server down."
+ @detail = "The Mauve server at #{Server.instance.hostname} has failed to send a heartbeat."
+ self.raise_at = 600
+ end
+
+ def raise_at=(i)
+ @raise_at = i
+ @sleep_interval = ((i.to_f)/2.5).round.to_i
+ end
+
+ def logger
+ @logger ||= Log4r::Logger.new(self.class.to_s)
+ end
+
+ def main_loop
+ #
+ # Don't send if no destination set.
+ #
+ return if @destination.nil?
+
+ update = Mauve::Proto::AlertUpdate.new
+ update.replace = false
+ update.alert = []
+ update.source = Server.instance.hostname
+ update.transmission_id = rand(2**63)
+
+ message = Mauve::Proto::Alert.new
+ message.id = "mauve-heartbeat"
+ message.summary = self.summary
+ message.detail = self.detail
+ message.raise_time = (MauveTime.now.to_f+self.raise_at).to_i
+ message.clear_time = MauveTime.now.to_i
+
+ update.alert << message
+
+ Mauve::Sender.new(self.destination).send(update)
+ end
+
+ end
+
+end
+
+
diff --git a/lib/mauve/http_server.rb b/lib/mauve/http_server.rb
index 71261c8..7bd4467 100644
--- a/lib/mauve/http_server.rb
+++ b/lib/mauve/http_server.rb
@@ -87,7 +87,7 @@ module Mauve
include Singleton
- attr_accessor :port, :ip, :document_root
+ attr_accessor :port, :ip, :document_root, :base_url
attr_accessor :session_secret
def initialize
@@ -96,6 +96,7 @@ module Mauve
@ip = "127.0.0.1"
@document_root = "/usr/share/mauvealert"
@session_secret = "%x" % rand(2**100)
+ @server_name = nil
end
def main_loop
@@ -105,6 +106,10 @@ module Mauve
@server = ::Thin::Server.new(@ip, @port, Rack::Session::Cookie.new(WebInterface.new, {:key => "mauvealert", :secret => @session_secret, :expire_after => 691200}), :signals => false)
@server.start
end
+
+ def base_url
+ @base_url ||= "http://"+Server.instance.hostname
+ end
def stop
@server.stop if @server
diff --git a/lib/mauve/mauve_thread.rb b/lib/mauve/mauve_thread.rb
index f6c0cbc..6409f98 100644
--- a/lib/mauve/mauve_thread.rb
+++ b/lib/mauve/mauve_thread.rb
@@ -5,9 +5,11 @@ module Mauve
class MauveThread
+ attr_reader :state
+
def initialize
@thread = nil
- @stop = true
+ @state = :stopped
end
def logger
@@ -18,62 +20,77 @@ module Mauve
#
# Good to go.
#
- @frozen = false
- @stop = false
-
- logger.debug("Started")
+ self.state = :starting
@sleep_interval ||= interval
- while !@stop do
+ sleep_loops = (@sleep_interval.to_f / 0.1).round.to_i
+ sleep_loops = 1 if sleep_loops.nil? or sleep_loops < 1
+
+ while self.state != :stopping do
+
+ self.state = :started if self.state == :starting
+
#
# Schtop!
#
- if @frozen
- logger.debug("Frozen")
+ if self.state == :freezing
+ self.state = :frozen
Thread.stop
- logger.debug("Thawed")
+ self.state = :started
end
yield
- next if self.should_stop?
+ #
+ # Ah-ha! Sleep with a break clause.
+ #
+ sleep_loops.times do
+
+ break if self.should_stop?
- Kernel.sleep(@sleep_interval)
+ #
+ # This is a rate-limiting step
+ #
+ Kernel.sleep 0.1
+ end
end
- logger.debug("Stopped")
+ self.state = :stopped
end
def should_stop?
- @frozen or @stop
+ [:freezing, :stopping].include?(self.state)
+ end
+
+ def state=(s)
+ raise "Bad state for mauve_thread #{s.inspect}" unless [:stopped, :starting, :started, :freezing, :frozen, :stopping, :killing, :killed].include?(s)
+ unless @state == s
+ @state = s
+ logger.debug(s.to_s.capitalize)
+ end
end
def freeze
- logger.debug("Freezing") unless @frozen
+ self.state = :freezing
- @frozen = true
-
20.times { Kernel.sleep 0.1 ; break if @thread.stop? }
logger.debug("Thread has not frozen!") unless @thread.stop?
end
def frozen?
- self.stop?
+ self.stop? and self.state == :frozen
end
def run
if self.alive?
- if self.stop?
- logger.debug("Thawing") if @frozen
- @frozen = false
+ if self.stop?
@thread.wakeup
end
else
@logger = nil
- logger.debug("Starting") if @stop
- @stop = false
+ self.state = :starting
@thread = Thread.new{ self.run_thread { self.main_loop } }
end
end
@@ -107,9 +124,7 @@ module Mauve
end
def stop
- logger.debug("Stopping") unless @stop
-
- @stop = true
+ self.state = :stopping
10.times do
break unless self.alive?
@@ -127,10 +142,9 @@ module Mauve
alias exit stop
def kill
- logger.debug("Killing")
- @frozen = @stop = true
+ self.state = :killing
@thread.kill
- logger.debug("Killed")
+ self.state = :killed
end
def thread
diff --git a/lib/mauve/notifier.rb b/lib/mauve/notifier.rb
index 5bedeb9..6099457 100644
--- a/lib/mauve/notifier.rb
+++ b/lib/mauve/notifier.rb
@@ -5,17 +5,11 @@ require 'mauve/notifiers/xmpp'
module Mauve
class Notifier < MauveThread
-
- DEFAULT_XMPP_MESSAGE = "Mauve server started."
include Singleton
attr_accessor :sleep_interval
- def initialize
- super
- end
-
def main_loop
#
# Cycle through the buffer.
diff --git a/lib/mauve/notifiers/templates/email.html.erb b/lib/mauve/notifiers/templates/email.html.erb
index f0ce1be..24b5e7b 100644
--- a/lib/mauve/notifiers/templates/email.html.erb
+++ b/lib/mauve/notifiers/templates/email.html.erb
@@ -1,12 +1,12 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><title></title></head><body>
-<p><strong><%= alert.update_type.upcase %>:</strong> <%
+<p><a href="<%= WebInterface.url_for(alert) %>"><%= alert.update_type.upcase %></a>:<%
case alert.update_type
when "cleared"
%><%= alert.cleared_at.to_s_relative %><%
when "acknowledged"
-%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by%><%
+%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by%> until <%= alert.will_unacknowledge_at.to_s_human %><%
else
%><%= alert.raised_at.to_s_relative %><%
end
diff --git a/lib/mauve/notifiers/templates/email.txt.erb b/lib/mauve/notifiers/templates/email.txt.erb
index dc5762b..aab44a8 100644
--- a/lib/mauve/notifiers/templates/email.txt.erb
+++ b/lib/mauve/notifiers/templates/email.txt.erb
@@ -3,7 +3,7 @@ case alert.update_type
when "cleared"
%><%= alert.cleared_at.to_s_relative %><%
when "acknowledged"
-%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %><%
+%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %> until <%= alert.will_unacknowledge_at.to_s_human %><%
else
%><%= alert.raised_at.to_s_relative %><%
end
diff --git a/lib/mauve/notifiers/templates/xmpp.html.erb b/lib/mauve/notifiers/templates/xmpp.html.erb
new file mode 100644
index 0000000..c6bfaed
--- /dev/null
+++ b/lib/mauve/notifiers/templates/xmpp.html.erb
@@ -0,0 +1,21 @@
+ <html xmlns="http://jabber.org/protocol/xhtml-im"><body xmlns="OAhttp://www.w3.org/1999/xhtml">
+<a href="<%=WebInterface.url_for(alert)%>"><%= alert.update_type.upcase %></a>: <%
+case alert.update_type
+when "cleared"
+%><%= alert.cleared_at.to_s_relative %><%
+when "acknowledged"
+%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %> until <%= alert.will_unacknowledge_at.to_s_human %><%
+else
+%><%= alert.raised_at.to_s_relative %><%
+end
+%>: <%= alert.subject %>: <%= alert.summary %><%
+if alert.source != alert.subject
+%> -- from <%= alert.source %><%
+end
+%>.<%
+if was_suppressed and not is_suppressed
+%><em> Normal service has resumed.</em><%
+elsif is_suppressed and not was_suppressed
+%><em> Further alerts suppressed until things calm down.</em><%
+end
+%></body></html>
diff --git a/lib/mauve/notifiers/templates/xmpp.txt.erb b/lib/mauve/notifiers/templates/xmpp.txt.erb
index 837fd67..a73f41f 100644
--- a/lib/mauve/notifiers/templates/xmpp.txt.erb
+++ b/lib/mauve/notifiers/templates/xmpp.txt.erb
@@ -3,7 +3,7 @@ case alert.update_type
when "cleared"
%><%= alert.cleared_at.to_s_relative %><%
when "acknowledged"
-%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %><%
+%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %> until <%= alert.will_unacknowledge_at.to_s_human %><%
else
%><%= alert.raised_at.to_s_relative %><%
end
@@ -11,10 +11,10 @@ end
if alert.source != alert.subject
%> -- from <%= alert.source %><%
end
-%>.<%
+%>. <%=WebInterface.url_for(alert)%><%
if was_suppressed and not is_suppressed
-%> Normal service has resumed.<%
+%> (Normal service has resumed.)<%
elsif is_suppressed and not was_suppressed
-%> Further alerts suppressed until things calm down.<%
+%> (Further alerts suppressed until things calm down.)<%
end
%>
diff --git a/lib/mauve/notifiers/xmpp.rb b/lib/mauve/notifiers/xmpp.rb
index 7fe1e39..991194d 100644
--- a/lib/mauve/notifiers/xmpp.rb
+++ b/lib/mauve/notifiers/xmpp.rb
@@ -34,9 +34,11 @@ module Jabber
# mean the other parts of the method fail to execute.
# That would be bad. So kill parser_thread last
@tbcbmutex.synchronize { @processing = 0 }
- @fd.close if @fd and !@fd.closed?
+ if @fd and !@fd.closed?
+ @fd.close
+ stop
+ end
@status = DISCONNECTED
- stop
end
end
end
@@ -66,15 +68,15 @@ module Mauve
include Jabber
# Atrtribute.
- attr_reader :name, :jid
+ attr_reader :name
# Atrtribute.
attr_accessor :password
def initialize(name)
Jabber::logger = self.logger
- #Jabber::debug = true
- #Jabber::warnings = true
+# Jabber::debug = true
+# Jabber::warnings = true
@name = name
@mucs = {}
@@ -138,10 +140,11 @@ module Mauve
#
unless ex.nil? or @closing
logger.warn(["Caught",ex.class,ex.to_s,"during XMPP",where].join(" "))
+ @closing = true
connect
@mucs.each do |jid, muc|
@mucs.delete(jid)
- join_muc(jid)
+ join_muc(muc[:jid], muc[:password])
end
end
end
@@ -156,11 +159,13 @@ module Mauve
def close
@closing = true
- if @client and @client.is_connected?
- @mucs.each do |jid, muc|
- muc.exit("Goodbye!") if muc.active?
- end
- @client.send(Presence.new(nil, "Goodbye!").set_type(:unavailable))
+ if @client
+ if @client.is_connected?
+ @mucs.each do |jid, muc|
+ muc[:client].exit("Goodbye!") if muc[:client].active?
+ end
+ @client.send(Presence.new(nil, "Goodbye!").set_type(:unavailable))
+ end
@client.close!
end
end
@@ -202,7 +207,16 @@ module Mauve
alert.to_s
end
- send_message(destination_jid, txt)
+ template_file = File.join(File.dirname(__FILE__),"templates","xmpp.html.erb")
+
+ xhtml = if File.exists?(template_file)
+ ERB.new(File.read(template_file)).result(binding).chomp
+ else
+ logger.error("Could not find xmpp.txt.erb template")
+ alert.to_s
+ end
+
+ send_message(destination_jid, txt, xhtml)
end
# Sends a message to the destionation.
@@ -210,20 +224,23 @@ module Mauve
# @param [String] destination The (full) JID to send to.
# @param [String] msg The (formatted) message to send.
# @return [NIL] nada.
- def send_message(jid, msg)
+ def send_message(jid, msg, html_msg=nil)
jid = JID.new(jid) unless jid.is_a?(JID)
message = Message.new(jid)
-
- #if msg.is_a?(XHTML::HTML)
- # message.add_element(msg)
- #else
message.body = msg
- #end
+ if html_msg
+ begin
+ html_msg = REXML::Document.new(html_msg) unless html_msg.is_a?(REXML::Document)
+ message.add_element(html_msg)
+ rescue REXML::ParseException
+ logger.error "Bad XHTML: #{html_msg.inspect}"
+ end
+ end
if is_muc?(jid)
jid = join_muc(jid.strip)
- muc = @mucs[jid]
+ muc = @mucs[jid][:client]
if muc
message.to = muc.jid
@@ -272,28 +289,28 @@ module Mauve
logger.debug("Adding new MUC client for #{jid}")
- @mucs[jid.strip] = Jabber::MUC::MUCClient.new(@client)
+ @mucs[jid.strip] = {:jid => jid, :password => password, :client => Jabber::MUC::MUCClient.new(@client)}
# Add some callbacks
- @mucs[jid.strip].add_message_callback do |m|
+ @mucs[jid.strip][:client].add_message_callback do |m|
receive_message(m)
end
- @mucs[jid.strip].add_private_message_callback do |m|
+ @mucs[jid.strip][:client].add_private_message_callback do |m|
receive_message(m)
end
end
- if !@mucs[jid.strip].active?
+ if !@mucs[jid.strip][:client].active?
#
# Make sure we have a resource.
#
- @mucs[jid.strip].join(jid, password)
+ @mucs[jid.strip][:client].join(jid, password)
- logger.info("Joined #{jid}")
+ logger.info("Joined #{jid.strip}")
else
- logger.debug("Already joined #{jid}.")
+ logger.debug("Already joined #{jid.strip}.")
end
#
@@ -410,10 +427,10 @@ module Mauve
# that we've not sent ourselves, that are not historical, and that
# match our resource or node in the body.
#
- if @mucs[msg.from.strip].is_a?(MUC::MUCClient) and
- msg.from != @mucs[msg.from.strip].jid and
+ if @mucs[msg.from.strip][:client].is_a?(MUC::MUCClient) and
+ msg.from != @mucs[msg.from.strip][:client].jid and
msg.x("jabber:x:delay") == nil and
- (msg.body =~ /\b#{Regexp.escape(@mucs[msg.from.strip].jid.resource)}\b/i or
+ (msg.body =~ /\b#{Regexp.escape(@mucs[msg.from.strip][:client].jid.resource)}\b/i or
msg.body =~ /\b#{Regexp.escape(@client.jid.node)}\b/i)
receive_normal_message(msg)
end
diff --git a/lib/mauve/server.rb b/lib/mauve/server.rb
index 57ddca8..20f7045 100644
--- a/lib/mauve/server.rb
+++ b/lib/mauve/server.rb
@@ -11,29 +11,23 @@ require 'mauve/timer'
require 'mauve/udp_server'
require 'mauve/processor'
require 'mauve/http_server'
+require 'mauve/heartbeat'
require 'log4r'
module Mauve
class Server
- DEFAULT_CONFIGURATION = {
- :ip => "127.0.0.1",
- :port => 32741,
- :database => "sqlite3:///./mauvealert.db",
- :log_file => "stdout",
- :log_level => 1,
- :transmission_cache_expire_time => 600
- }
+ DEFAULT_CONFIGURATION = { }
#
# This is the order in which the threads should be started.
#
- THREAD_CLASSES = [UDPServer, HTTPServer, Processor, Timer, Notifier]
+ THREAD_CLASSES = [UDPServer, HTTPServer, Processor, Timer, Notifier, Heartbeat]
- attr_accessor :web_interface
- attr_reader :stopped_at, :started_at, :initial_sleep, :packet_buffer, :notification_buffer
+ attr_accessor :hostname, :database, :initial_sleep
+ attr_reader :stopped_at, :started_at, :packet_buffer, :notification_buffer
include Singleton
@@ -43,8 +37,11 @@ module Mauve
# Sleep time between pooling the @buffer buffer.
@sleep = 1
- @frozen = false
- @stop = false
+ @frozen = false
+ @stop = false
+ @hostname = "localhost"
+ @database = "sqlite3:///./mauvealert.db"
+
@stopped_at = MauveTime.now
@started_at = MauveTime.now
@@ -79,7 +76,7 @@ module Mauve
end
#
- DataMapper.setup(:default, @config[:database])
+ DataMapper.setup(:default, @database)
# DataObjects::Sqlite3.logger = Log4r::Logger.new("Mauve::DataMapper")
#
diff --git a/lib/mauve/timer.rb b/lib/mauve/timer.rb
index f6ada88..91dea18 100644
--- a/lib/mauve/timer.rb
+++ b/lib/mauve/timer.rb
@@ -13,12 +13,6 @@ module Mauve
attr_accessor :sleep_interval, :last_run_at
- def initialize
- super
- @initial_sleep = 300
- @initial_sleep_threshold = 300
- end
-
def main_loop
#
# Get the next alert.
@@ -51,22 +45,27 @@ module Mauve
#
# Sleep indefinitely
#
- logger.info("Nothing to notify about -- snoozing indefinitely.")
+ logger.info("Nothing to notify about -- snoozing for a while.")
+ sleep_loops = 600
else
#
# La la la nothing to do.
#
logger.info("Next to notify: #{next_to_notify} -- snoozing until #{next_to_notify.due_at}")
+ sleep_loops = ((next_to_notify.due_at - MauveTime.now).to_f / 0.1).round.to_i
end
+ sleep_loops = 1 if sleep_loops.nil? or sleep_loops < 1
+
#
# Ah-ha! Sleep with a break clause.
#
- while next_to_notify.nil? or MauveTime.now <= next_to_notify.due_at
+ sleep_loops.times do
#
# Start again if the situation has changed.
#
break if self.should_stop?
+
#
# This is a rate-limiting step for alerts.
#
diff --git a/lib/mauve/web_interface.rb b/lib/mauve/web_interface.rb
index 4594e10..4bb9517 100644
--- a/lib/mauve/web_interface.rb
+++ b/lib/mauve/web_interface.rb
@@ -22,7 +22,14 @@ module Mauve
def self._logger
Log4r::Logger.new(self.to_s)
end
-
+
+ #
+ # Generic URL for
+ #
+ def self.url_for(obj)
+ [Mauve::HTTPServer.instance.base_url, obj.class.to_s.split("::").last.downcase, obj.id.to_s].join("/")
+ end
+
use Rack::CommonLogger
use Rack::Chunked
use Rack::ContentLength
@@ -519,7 +526,6 @@ EOF
env['rack.errors'] = RackErrorsProxy.new(logger)
super(env)
end
-
- end
+ end
end