听起来您想将 JSON 直接流式传输到客户端,而不是将其全部构建在内存中。这可能是减少内存使用的最佳方法。例如,您可以使用 yajl 将 JSON 直接编码为流。
编辑:我为yajl 重写了整个代码,因为它的API 更引人注目并且允许更简洁的代码。我还包括了一个以块为单位读取响应的示例。这是我编写的流式 JSON 数组助手:
require 'yajl'
module JsonArray
class StreamWriter
def initialize(out)
super()
@out = out
@encoder = Yajl::Encoder.new
@first = true
end
def <<(object)
@out << ',' unless @first
@out << @encoder.encode(object)
@out << "\n"
@first = false
end
end
def self.write_stream(app, &block)
app.stream do |out|
out << '['
block.call StreamWriter.new(out)
out << ']'
end
end
end
用法:
require 'sinatra'
require 'mongoid'
Mongoid.identity_map_enabled = false
# use a server that supports streaming
set :server, :thin
get '/' do
content_type :json
JsonArray.write_stream(self) do |json|
Book.all.each do |book|
json << book.attributes
end
end
end
要在客户端解码,您可以分块读取和解析响应,例如使用em-http。请注意,此解决方案要求客户端内存足够大以存储整个对象数组。这是相应的流式解析器助手:
require 'yajl'
module JsonArray
class StreamParser
def initialize(&callback)
@parser = Yajl::Parser.new
@parser.on_parse_complete = callback
end
def <<(str)
@parser << str
end
end
def self.parse_stream(&callback)
StreamParser.new(&callback)
end
end
用法:
require 'em-http'
parser = JsonArray.parse_stream do |object|
# block is called when we are done parsing the
# entire array; now we can handle the data
p object
end
EventMachine.run do
http = EventMachine::HttpRequest.new('http://localhost:4567').get
http.stream do |chunk|
parser << chunk
end
http.callback do
EventMachine.stop
end
end
替代解决方案
当您放弃生成“正确”JSON 数组的需要时,您实际上可以大大简化整个事情。上述解决方案生成的是这种形式的 JSON:
[{ ... book_1 ... }
,{ ... book_2 ... }
,{ ... book_3 ... }
...
,{ ... book_n ... }
]
但是,我们可以将每本书作为单独的 JSON 流式传输,从而将格式简化为以下格式:
{ ... book_1 ... }
{ ... book_2 ... }
{ ... book_3 ... }
...
{ ... book_n ... }
服务器上的代码会更加更简单:
require 'sinatra'
require 'mongoid'
require 'yajl'
Mongoid.identity_map_enabled = false
set :server, :thin
get '/' do
content_type :json
encoder = Yajl::Encoder.new
stream do |out|
Book.all.each do |book|
out << encoder.encode(book.attributes) << "\n"
end
end
end
还有客户:
require 'em-http'
require 'yajl'
parser = Yajl::Parser.new
parser.on_parse_complete = Proc.new do |book|
# this will now be called separately for every book
p book
end
EventMachine.run do
http = EventMachine::HttpRequest.new('http://localhost:4567').get
http.stream do |chunk|
parser << chunk
end
http.callback do
EventMachine.stop
end
end
很棒的是,现在客户端不必等待整个响应,而是单独解析每本书。但是,如果您的客户之一需要一个大的 JSON 数组,这将不起作用。