Gid*_*eon 5 javascript java string character-encoding websocket
我试图通过压缩String我通过 WebSocket 从 Springboot 应用程序发送到浏览器客户端的 JSON (这是在permessage-deflateWebSocket 扩展之上)来减少带宽消耗。此场景使用以下String长度为 383 个字符的JSON :
{"headers":{},"body":{"message":{"errors":{"password":"Password length must be at least 8 characters.","retype":"Retype Password cannot be null.","username":"Username length must be between 6 to 64 characters."},"links":[],"success":false,"target":{"password":"","retype":"","username":""}},"target":"/user/session/signup"},"statusCode":"UNPROCESSABLE_ENTITY","statusCodeValue":422}
Run Code Online (Sandbox Code Playgroud)
为了进行基准测试,我从服务器发送压缩和未压缩的字符串,如下所示:
Object response = …,
SimpMessageHeaderAccessor simpHeaderAccessor =
SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
simpHeaderAccessor.setSessionId(sessionId);
simpHeaderAccessor.setContentType(new MimeType("application", "json",
StandardCharsets.UTF_8));
simpHeaderAccessor.setLeaveMutable(true);
// Sends the uncompressed message.
messagingTemplate.convertAndSendToUser(sessionId, uri, response,
simpHeaderAccessor.getMessageHeaders());
ObjectMapper mapper = new ObjectMapper();
String jsonString;
try {
jsonString = mapper.writeValueAsString(response);
}
catch(JsonProcessingException e) {
jsonString = response.toString();
}
log.info("The payload is application/json.");
log.info("uncompressed payload (" + jsonString.length() + " character):");
log.info(jsonString);
String lzStringCompressed = LZString.compress(jsonString);
simpHeaderAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
simpHeaderAccessor.setSessionId(sessionId);
simpHeaderAccessor.setContentType(new MimeType("text", "plain",
StandardCharsets.UTF_8));
simpHeaderAccessor.setLeaveMutable(true);
// Sends the compressed message.
messagingTemplate.convertAndSendToUser(sessionId, uri, lzStringCompressed,
simpHeaderAccessor.getMessageHeaders());
log.info("The payload is text/plain.");
log.info("compressed payload (" + lzStringCompressed.length() + " character):");
log.info(lzStringCompressed);
Run Code Online (Sandbox Code Playgroud)
在 Java 控制台中记录以下行:
The payload is application/json.
uncompressed payload (383 character):
{"headers":{},"body":{"message":{"errors":{"password":"Password length must be at least 8 characters.","retype":"Retype Password cannot be null.","username":"Username length must be between 6 to 64 characters."},"links":[],"success":false,"target":{"password":"","retype":"","username":""}},"target":"/user/session/signup"},"statusCode":"UNPROCESSABLE_ENTITY","statusCodeValue":422}
The payload is text/plain.
compressed payload (157 character):
??????????¼??????????????p??!-??7??????????????????????????????????u??????????????????????·}???????????????????????????????????????/??R??b,??????m??????????
Run Code Online (Sandbox Code Playgroud)
然后浏览器收到服务器发送的两条消息,并被这个javascript捕获:
stompClient.connect({}, function(frame) {
stompClient.subscribe(stompClientUri, function(payload) {
try {
JSON.parse(payload.body);
console.log("The payload is application/json.");
console.log("uncompressed payload (" + payload.body.length + " character):");
console.log(payload.body);
payload = JSON.parse(payload.body);
} catch (e) {
try {
payload = payload.body;
console.log("The payload is text/plain.");
console.log("compressed payload (" + payload.length + " character):");
console.log(payload);
var decompressPayload = LZString.decompress(payload);
console.log("decompressed payload (" + decompressPayload.length + " character):");
console.log(decompressPayload);
payload = JSON.parse(decompressPayload);
} catch (e) {
} finally {
}
} finally {
}
});
});
Run Code Online (Sandbox Code Playgroud)
在浏览器的调试控制台中显示以下几行:
The payload is application/json.
uncompressed payload (383 character):
{"headers":{},"body":{"message":{"errors":{"password":"Password length must be at least 8 characters.","retype":"Retype Password cannot be null.","username":"Username length must be between 6 to 64 characters."},"links":[],"success":false,"target":{"password":"","retype":"","username":""}},"target":"/user/session/sign-up"},"statusCode":"UNPROCESSABLE_ENTITY","statusCodeValue":422}
The payload is text/plain.
compressed payload (157 character):
??????????¬??????????????p??!-??7??????????????????????????????????u?????????????????????ú}???????????????????????????????????????/?ÂR??b,??????m??????????
decompressed payload (383 character):
{"headers":{},"body":{"message":{"errors":{"password":"Password length must be at least 8 characters.","retype":"Retype Password cannot be null.","username":"Username length must be between 6 to 64 characters."},"links":[],"success":false,"target":{"password":"","retype":"","username":""}},"target":"/user/session/sign-up"},"statusCode":"UNPROCESSABLE_ENTITY","statusCodeValue":422}
Run Code Online (Sandbox Code Playgroud)
在这一点上,我现在可以验证String我的 Springboot 应用程序压缩的任何值,浏览器都可以解压缩并获取原始String. 但是有一个问题。当我检查浏览器调试器时,如果传输的消息的大小实际上减少了,它告诉我事实并非如此。
这是未压缩的原始消息 (598B):
a["MESSAGE destination:/user/session/broadcast
content-type:application/json;charset=UTF-8
subscription:sub-0
message-id:5lrv4kl1-1
content-length:383
{"headers":{},"body":{"message":{"errors":{"password":"Password length must be at least 8 characters.","retype":"Retype Password cannot be null.","username":"Username length must be between 6 to 64 characters."},"links":[],"success":false,"target":{"password":"","retype":"","username":""}},"target":"/user/session/sign-up"},"statusCode":"UNPROCESSABLE_ENTITY","statusCodeValue":422}
Run Code Online (Sandbox Code Playgroud)
虽然这是原始压缩消息 (589B):
a["MESSAGE destination:/user/session/broadcast
content-type:text/plain;charset=UTF-8
subscription:sub-0
message-id:5lrv4kl1-2
content-length:425
á¯¡à ¥ä¬à¢á¨á¡ä¹à®¸Ì͢¬ßäå°Ë¸â±á£ä±á¢ç¤â½Ýá®çâ©pçæ¼¦!-ä á·7á¡å¡âº¨ç¤ç£àª®ååµÜ¸äá¡ç¡±äáÏۯĮãá´´á䫯â»Öç¹âåçá£å¥¢âã¥â¡âæuâã¥âá²â«äáªä¸¨à²¸ääá¤å塬æ¶â¬»ã¶¶Ð¢\u2029ã°Í»á°Ãº}ã᥸æ²âƹâ᧸ã¦â´¼ä¶¨âæã¢¡á±¼æºæ¶¤ç°²â㺮橿äç¡ç§á®¬æâ¼ºâæ»ä¢æ¦µâ±çີâ£Ð¨ç¨àª°Ä籯/á¤ÃRå°È¨b,å¸°Ðæ°ä¥â¤ä°mãளÇäâ⧼㪠Өæä \u0000"]
Run Code Online (Sandbox Code Playgroud)
调试控制台显示未压缩的消息以 598B 的大小传输,其中 383 个字符作为消息有效负载的大小(由content-length标题指示)。而另一方面,压缩消息的总大小为 589B,比未压缩的小 9B,消息负载大小为 425 个字符。我有几个问题:
content-length的STOMP消息的以字节为单位表示,或字符?content-length未压缩消息的 383 比压缩消息的 425 小?content-length压缩消息的 425 与 Java 控制台(使用lzStringCompressed.length())中返回的值157 不同,考虑到未压缩消息传输的 acontent-length为 383,这与 Java 控制台中的长度相同. 两者也都通过charset=UTF-8编码传输。content-length压缩消息的 425 与 Java 控制台(使用lzStringCompressed.length())中payload.length返回的值 157 不同,但 JavaScript 代码返回 157,而不是 425?application/json不受影响而只有plain/text膨胀?虽然 9B 的差异仍然是一个差异,但我正在重新考虑压缩/解压缩消息的开销成本是否值得保留。我必须为此测试其他String值。
所有的问题都是密切相关的。
- 是
content-length的STOMP消息的以字节为单位表示,或字符?
正如您在STOMP 规范中所见:
所有的帧都可以包含一个
content-length头部。此标头是消息正文长度的八位字节计数....
从 STOMP 的角度来看,主体是一个字节数组和标头,content-type并content-length确定主体包含的内容以及应该如何解释它。
- 为什么
content-length未压缩消息的383小于压缩消息的425?
因为UTF-8当您将信息发送到 STOMP 服务器中的客户端时会进行转换。
您有一条消息 a String,这条消息由一系列字符组成。
无需详细说明 -如果您需要更多信息,请查看这个或另一个优秀答案 - 在内部,charJava 中的每一个都以 Unicode 代码单元表示。
为了在特定字符集中表示这些 Unicode 代码单元,UTF-8在您的情况下,可能需要可变数量的字节,在您的特定情况下从 1 到 4。
在未压缩的消息的情况下,必须383 charS,纯ASCII,这将被编码以UTF-8具有一个byte每char。这就是您在content-length标头中获得相同值的原因。
但压缩消息的情况并非如此:当您压缩消息时,它将为您提供任意数量的字节,对应于157 chars - Unicode 代码单元 - 具有任意信息。获得的字节数将少于原始消息。但是然后您将其编码为UTF-8. 其中一些157 chars 将用 1 表示byte,就像原始消息的情况一样,但由于压缩消息的信息的任意性,在许多情况下更有可能需要两个、三个或四个字节代表其中一些。这就是为什么您获得的字节数大于未压缩消息的字节数的原因。
- 这是否意味着减少字符长度并不一定意味着减少大小?
通常,在压缩数据时,您将始终获得少量信息。
如果信息足以使使用压缩变得有价值,并且您有能力发送压缩的原始二进制信息 - 类似于服务器发送指示Content-Encoding: gzip或 的信息deflate,它会给您带来很大的好处。
但是如果客户端库只能处理文本消息而不是二进制消息,例如 SockJS,如您所见,编码问题实际上可能会给您带来不适当的结果。
为了缓解这个问题,您可以首先尝试将您的信息压缩为其他中间编码,例如Base 64,这将为您提供1.6压缩字节数的大致倍数:如果此值小于未压缩的字节数,则压缩消息可能是值得的它。
在任何情况下,正如规范中所指出的,STOMP 是基于文本的,但也允许传输二进制消息。此外,它表明 STOMP 的默认编码是UTF-8,但它支持消息正文的替代编码规范。
如果您正在使用,正如您的代码所建议的那样,stomp-js请注意我没有使用过这个库,正如文档所指出的,它似乎也可以处理二进制消息。
基本上,您的服务器必须发送content-type带有 value 标头的原始字节信息application/octet-stream。
然后,库可以在客户端使用类似于以下内容的方式处理此信息:
// within message callback
if (message.headers['content-type'] === 'application/octet-stream') {
// message is binary
// call message.binaryBody
} else {
// message is text
// call message.body
}
Run Code Online (Sandbox Code Playgroud)
如果这可行,并且您可以通过这种方式发送压缩信息,如前所述,压缩可以为您带来很大的好处。
- 为什么
content-length压缩消息的 ,即425,与 Java 控制台(使用lzStringCompressed.length())中返回的值不同,即157,考虑到未压缩消息是通过 传输content-length的383,在 Java 控制台中的长度相同。两者也都与charset=UTF-8 encoding.
考虑类的length方法的Javadoc String:
返回此字符串的长度。长度等于字符串中Unicode 代码单元的数量。
如您所见,该length方法将为您提供表示 所需的 Unicode 代码单元数String,同时content-length标头将为您提供表示它们所需的字节数,UTF-8如前所述。
事实上,计算字符串的长度可能是一项棘手的任务。
- 为什么
content-length压缩消息425的 与 Java 控制台中返回的值不同(使用lzStringCompressed.length()),157但 JavaScript 代码 payload.length 返回的157不是425?
因为,正如您在文档中看到的,length在 Javascript 中也String以UTF-16代码单元表示对象的长度:
对象的
length属性String包含字符串的长度,以UTF-16代码为单位。length是字符串实例的只读数据属性。
- 如果它真的在传输过程中变得臃肿,为什么消息
application/json不受影响而只有text/plain膨胀?
如上所述,它Content-Type与信息的编码无关。
| 归档时间: |
|
| 查看次数: |
300 次 |
| 最近记录: |