我的地图应用程序需要从服务器加载大量 JSON 数据以呈现初始地图视图。一个地图有很多区域,一个区域看起来像下面这样。数据字段存储 gzip 压缩的 JSON 数据。一个大地图可以有 50 个区域,每个区域压缩后大约 2MB。
create_table "regions", force: :cascade do |t|
t.integer "map_id", null: false
t.binary "data", null: false
t.index ["map_id"], name: "index_regions_on_map_id"
end
我有一个控制器,它流式传输地图的所有区域:
class MapController < ApplicationController
include ActionController::Live
def stream_regions
response.headers['Content-Type'] = 'text/event-stream'
response.headers['Last-Modified'] = Time.now.httpdate
sse = SSE.new(response.stream, retry: 300, event: 'open')
index = 0
@map.regions.each do |region|
sse.write(
id: index += 1,
event: 'message',
{ done: false, region: Base64.encode64(region.data) }
)
end
sse.write(id: index += 1, event: 'message', { done: true })
rescue ActionController::Live::ClientDisconnected
sse.close
ensure
sse.close
end
end
在客户端,我有这样的东西:
const streamRegions = async (mapId: string): Promise<string[]> => {
return new Promise((resolve) => {
const source = new EventSource(
`/maps/${mapId}/stream_regions`
);
const regions: string[] = [];
source.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.done) {
source.close();
resolve(regions);
} else {
const bytes = Buffer.from(data.region, "base64");
const json_string = pako.inflate(bytes, { to: "string" });
const json = JSON.parse(json_string);
regions.push(json);
}
};
});
};
虽然这很有效而且速度很快(加载大地图需要 3 秒),但有几件事让我觉得很奇怪:
SSE 代表服务器发送的事件。我的区域对象绝对不是事件,它们是 gzipped JSON 数据的大量有效载荷。
SSE 不支持二进制数据。所以在服务器端,我必须先将二进制数据转换为 base64 字符串,然后再使用 SSE 传输它们。然后在客户端,我不得不再次将反向对话恢复为二进制。对于大地图,这似乎只增加了 100-200 毫秒的额外处理时间。然而,它也将通过网络传输的数据大小扩大了大约 30%,因此并不理想。
再次因为 SSE 不支持二进制数据,我不得不使用 pako.inflate 在客户端手动处理数据膨胀(解压缩)
所以我的问题是:我想念在这里使用 SSE 吗?使用 Rails 有更好的方法吗?基本上,我想尽量减少用户在等待地图加载时看到微调器的时间。在下载所有区域之前,无法显示地图。