3

我们的应用程序的某些部分是用 Ruby 编写的,而其他部分是使用 node.js 编写的。

我们使用存储 zlib 块的 redis 存储在它们之间共享数据。我们使用节点使用以下代码写入它:

zlib.deflate(xml.toString(), function(error, deflated) {
  ...
  deflated.toString('binary'); // That's the string we write in Redis
  ...
});

现在,我们使用 Ruby (1.8.7) 在 redis 存储中读取了这些数据,我不得不说我不知道​​该怎么做。

我们从商店获得的典型字符串如下所示:

=> "xuAo \020ÿ\ná.£v½\030dÿCO½±:«¤(\004ƪÿ¾¬®5MÚ\003÷½IÞ q¤°²e°c¼òÈ×\000ó<ùM¸ÐAç\025ÜÈ\r|gê\016Ý/.é\020ãÆî×\003Ôç<Ýù2´F\n¨Å\020!zl \0209\034p|üÀqò\030\036m\020\e`\031¼ÏütÓ=ø¦U/ÔO±\177zB{\037½£-ðBu©ò¢X\000kb­*Ó[V\024Y^½EÎ¥üpúrò­¦\177ÁÃdÈ¢j\0353$a\027²q#¥]*Ýi3J8¤´füd\eså[³öʵ%\fcÇY\037ð¬ÿg§í^¥8£Õ§a¶\001=\r;¡¾\001\020Pí" 

当然,我尝试使用, Zlib::Inflate.new.inflate(compressed)但使用Zlib::DataError: incorrect header check.

关于我们应该对该字符串进行何种转换以从 Ruby 中对其进行膨胀的任何想法?

PS:从节点膨胀它很容易并且有效,所以问题不在于我们如何压缩它。

4

3 回答 3

5

关于我们应该对该字符串进行何种转换以从 Ruby 中对其进行膨胀的任何想法?

UTF-8 到 Latin-1

理想情况下,只要您直接在 Node 端使用 Buffers,就不需要进行任何转换。请参阅下面最底部的一对 Node 和 Ruby 代码块;然而,问题的本质是关于在 Ruby 方面可以做些什么来解决这个问题。

仅限 Ruby - 从 UTF-8 转换为 LATIN-1

require 'zlib'
require 'rubygems'
require 'redis'
require 'iconv'

redis = Redis.new

def inflate(buffer)
    zstream = Zlib::Inflate.new
    buf = zstream.inflate(buffer)
    zstream.finish
    zstream.close
    buf
end


def convert(buffer)
    utf8_to_latin1 = Iconv.new("LATIN1//TRANSLIT//IGNORE", "UTF8")
    utf8_to_latin1.iconv(buffer) 
end

value = redis.get("testkey")
value = convert(value)
puts inflate(value);

解释

上面的代码使用 iconv 将从 Redis 检索到的值从 UTF-8 转换回预期的字节。

在 Node 中放气时,生成的缓冲区包含正确的 zlib 生成的字节;来自toString('binary'), 字符的结果字符串也与 deflate 结果缓冲区的内容匹配;但是,当放气结果存储在 Redis 中时,它是 UTF-8 编码的。一个例子:

对字符串“ABCABC”进行放气会导致:

<Buffer 78 9c 73 74 72 76 74 72 06 00 05 6c 01 8d>

然而,Redis 返回:

<Buffer 78 c2 9c 73 74 72 76 74 72 06 00 05 6c 01 c2 8d>

稍微假设一下,似乎从结果得到的字符串最终toString('binary')作为 new Buffer(...) 某个地方的参数,可能在 node-redis 中。如果 new Buffer() 没有指定的编码参数,则应用默认的 UTF-8 编码。(见第一个参考)。进一步假设,通过仅使用缓冲区,您避免了从字符串创建缓冲区的需要,因此,避免了 UTF8 编码,因此正确的 deflate 值使其进出 Redis。

参考

节点

var zlib = require('zlib');
var redis = require("redis").createClient();

var message = new Buffer('your stuff goes here.');
//var message = new Buffer(xml.toString());

redis.on("error", function (err) {
console.log("Error " + err);
});

redis.on("connect", function() {
    console.log(message);
    zlib.deflate(message, function(error, deflated) {
        console.log(deflated);          
        redis.set("testkey",deflated,function (err, reply) {
            console.log(reply.toString());
        });
    });
});

红宝石

require 'zlib'
require 'rubygems'
require 'redis'

redis = Redis.new

def inflate(buffer)
    zstream = Zlib::Inflate.new
    buf = zstream.inflate(buffer)
    zstream.finish
    zstream.close
    buf
end

value = redis.get("testkey")    

puts inflate(value)
于 2012-11-14T09:46:49.927 回答
2

如果您使用 node-redis 来保存数据,那么它将直接处理缓冲区,因此您可以简单地使用 client.set(key, buff) 或 client.append(key, buff),因此您不需要(想要)做任何转换。

Node.js(由 Kevin 简化)

var zlib = require('zlib');
var redis = require("redis");
var rc = redis.createClient(null, null, {detect_buffers: true}); // allow Buffers

var message = new Buffer('My message');

zlib.deflate(message, function (err, deflated) {
  if (err) return console.error(err);
  rc.set("testkey", deflated, function (err, result) {
    if (err) return console.error(err);
    rc.quit();
  });
});

Ruby 代码(从上面的 Kevin 复制)

require 'zlib'
require 'rubygems'
require 'redis'

redis = Redis.new

def inflate(buffer)
    zstream = Zlib::Inflate.new
    buf = zstream.inflate(buffer)
    zstream.finish
    zstream.close
    buf
end

value = redis.get("testkey")

puts inflate(value)

.toString('binary')这适用于正确检索值,但是像您提到的那样更改 Node.js 代码以使用最初会破坏您上面所说的 Ruby 解码。

这是一个示例,表明 toString('binary') 确实会弄乱数据

 console.log(deflated);
 console.log(new Buffer(deflated.toString('binary')));

所以我无法弄清楚 Buffer.toString('binary') 正在做什么,因为我相信它进入了 V8 缓冲区代码。

但是,如果您仍然能够使用 Node 读取它,那么您可能希望将其提取出来并以正确的方式保存,而无需使用.toString('binary')just give Buffer to the redis client set 方法,它会正确保存它。

然后它将以二进制形式存储,您可以使用上面的代码正确地用 ruby​​ 读取它。

至于您的 node.js 代码,一旦您将其正确保存为二进制文件(直接在 set 调用中使用 Buffer),然后检索它:

var rc = redis.createClient(null, null, {detect_buffers: true}); // allow Buffers
rc.get(new Buffer(key), function (err, buff) {  // use a Buffer for the key
   // buff is a Buffer now
});

通过为node-redis打开detect_buffers,然后当您将Buffer作为键传入时,它将作为Buffer检索并且不会转换。

您可以选择使用return_buffers = true选项,但我喜欢detect_buffers这样您可以将相同的客户端用于缓冲区和非缓冲区数据。

PS。确保您的 Ruby gem 使用的是最新版本之一,而不是像 1.x 之类的旧版本(2.x 添加了二进制修复)。

于 2012-11-15T00:59:29.163 回答
0

转换使用的行为toString已经使您处于犯罪状态。您需要保留和传输由 deflate 生成的原始二进制缓冲区,而不进行任何类型的转换,以便 Ruby 中的 inflate 能够对其进行解码。

目前尚不清楚转换'binary'的作用,但它可能会去除空值,这会弄乱数据。在任何情况下,文档都说binary不应该使用并且不推荐使用。您需要找到一种方法来直接传递 Buffer 类中的原始压缩数据,或者如果您确实需要一个字符串,请将其转换为可以在 Ruby 中反转的字符串格式,然后再尝试膨胀。例如base64。

于 2012-11-05T23:53:19.783 回答