aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2017-06-08 11:36:52 +0900
committerKazuki Yamaguchi <k@rhe.jp>2017-06-08 12:47:51 +0900
commit353fd27a96c576f248fe54e74ce75aeaab711322 (patch)
tree97640fc86206c24c53a1f99dac3a365f8f1c297b
parent4d86351b6799db4338561ec84c5531ba14ce64b2 (diff)
downloadpuke-353fd27a96c576f248fe54e74ce75aeaab711322.tar.gz
Use IO-like object as the response body
Introduce Puke::ConcatenatedIO that behaves like IO and reads sequentially from multiple sources. As of Ruby 2.4, #readpartial (with two argument) and #close are required by WEBrick. This allows us to avoid loading the entire content of the mboxes on memory.
-rw-r--r--lib/puke/concatenated_io.rb29
-rw-r--r--lib/puke/core.rb4
-rw-r--r--lib/puke/server.rb14
3 files changed, 40 insertions, 7 deletions
diff --git a/lib/puke/concatenated_io.rb b/lib/puke/concatenated_io.rb
new file mode 100644
index 0000000..dfa2940
--- /dev/null
+++ b/lib/puke/concatenated_io.rb
@@ -0,0 +1,29 @@
+require_relative "core"
+
+class Puke::ConcatenatedIO
+ def initialize(ios)
+ @ios = ios
+ end
+
+ def readpartial(n, buf)
+ rotate_if_needed
+ f = @ios.first or raise EOFError
+ r = f.readpartial(n, buf)
+ r
+ end
+
+ def close
+ @ios.each(&:close)
+ # Ensure #readpartial won't be called anymore
+ @ios = nil
+ end
+
+ private
+
+ def rotate_if_needed
+ if f = @ios.first and f.eof?
+ f.close
+ @ios.shift
+ end
+ end
+end
diff --git a/lib/puke/core.rb b/lib/puke/core.rb
index c37b17a..77652aa 100644
--- a/lib/puke/core.rb
+++ b/lib/puke/core.rb
@@ -52,8 +52,8 @@ module Puke
{ mid: mid, tid: tid.to_i, subject: subject, date: Time.rfc2822(date) }
end
- def read(mid)
- File.read(File.join(datadir, mangle_mid(mid) + ".mbox"))
+ def open(mid, &blk)
+ File.open(File.join(datadir, mangle_mid(mid) + ".mbox"), &blk)
end
CreateMutex = Thread::Mutex.new
diff --git a/lib/puke/server.rb b/lib/puke/server.rb
index 29d4479..432d84a 100644
--- a/lib/puke/server.rb
+++ b/lib/puke/server.rb
@@ -1,4 +1,5 @@
require_relative "core"
+require_relative "concatenated_io"
require "webrick"
require "erb"
@@ -52,14 +53,17 @@ EOF
def mid(req, res)
/\A\/(?<mid>.+@.+?)(?:\/(?<unya>\w+)\.mbox)?\z/ =~ req.path_info and
tid = Puke.mid?(mid) or (res.status = 404 and return)
+ # NB: Puke::ConcatenatedIO is not really an IO object and
+ # 'res.body.is_a?(IO)' returns false, leading the non-existent 'bytesize'
+ # method to be called by WEBrick, unless chunked response is enabled.
+ res.chunked = true
+
case unya
when "thread"
- # TODO: Concatenated-IO object?(?)
- res.body = Puke.thread(tid).inject("") do |s, imid|
- s << Puke.read(imid)
- end
+ files = Puke.thread(tid).map { |mid| Puke.open(mid) }
+ res.body = Puke::ConcatenatedIO.new(files)
when nil, "raw"
- res.body = Puke.read(mid)
+ res.body = Puke.open(mid)
else
res.status = 400
return