【问题标题】:How to receive compressed JSON data using XMLHttpRequest from a java server如何使用 XMLHttpRequest 从 Java 服务器接收压缩的 JSON 数据
【发布时间】:2019-09-12 15:13:47
【问题描述】:

我正在使用 XMLHttpRequest 请求和解析 json 数据。使用嵌入式 jetty 9 的 java 8 服务器接收请求并返回数据。代码如下所示,第一块是客户端javascript,第二块是服务器端java代码。

var http = new XMLHttpRequest();
http.onreadystatechange=function() {
  if (this.readyState == 4) {
    if (this.status == 200) {
      data = JSON.parse(this.responseText);
    }
  }
};
http.open("GET", "/mydata", true);
http.send();

 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

  response.setContentType("application/json");
  response.setStatus(Response.SC_OK);
  PrintWriter pw = response.getWriter();
  pw.write(myDataInJsonStringFormat);
  pw.close();
  baseRequest.setHandled(true);
}

问题是如何修改上述代码以从 java 发送压缩的 json 数据,然后使用 XMLHttpRequest 解压缩数据?

我不希望使用任何额外的客户端或服务器端库而不是我已经在使用的库。

【问题讨论】:

    标签: java json xmlhttprequest embedded-jetty


    【解决方案1】:

    将 Eclipse Jetty GzipHandler 添加到您的服务器处理程序树中的自定义处理程序代码之前的位置。

    这将执行 2 个任务:

    • 它将解压缩传入的压缩请求正文内容
    • 它将压缩传出的响应正文内容

    只需确保在 GzipHandler 上配置包含的 mime 类型列表,以允许在响应正文内容上处理 application/json

    【讨论】:

    • 如果我可以设置,我该如何处理 XMLHttpRequest 响应中的压缩数据?
    • 在 XMLHttpResponse 数据中不需要做任何事情,见下文。如果要返回的 json 数据是动态变化的,而不是本质上更静态的,那么这个答案将是服务器端的方法。
    【解决方案2】:

    您可以使用 base64 来传输数据。其中有两个函数分别用于解码和编码base64字符串:atob()和btoa()。搜索如何使用 java 做到这一点。

    【讨论】:

    • 虽然这可以在 java 和 javascript 端完成,但它不会像 w3schools.com/jsref/met_win_atob.asp 示例所建议的那样增加 json 数据的大小
    • 我从 java 服务器发送到客户端的数据最大为 17MB,这通常是由于移动连接不佳。
    • 这种情况下可以使用 JSONC 来压缩 JSON 字符串: JSONC.compress( json ); JSONC.decompress(compressedJSON);
    【解决方案3】:

    在这种情况下,相同的数据集被重复提供给客户端。不是添加码头处理程序,而是将 json 文本字符串转换为压缩字节数组:

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.nio.charset.StandardCharsets;
    import java.util.zip.GZIPOutputStream;
    
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    GZIPOutputStream gzip = new GZIPOutputStream(bos);
    OutputStreamWriter osw = new OutputStreamWriter(gzip, StandardCharsets.UTF_8);
    osw.write(myDataInJsonStringFormat);
    osw.close();
    
    compressedJsonData = bos.toByteArray();
    

    然后数据由码头处理程序返回给java客户端:

    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    
      response.setContentType("application/json");
      response.setHeader("Content-Encoding", "gzip");
      response.setContentLength(compressedJsonData .length);
    
      response.setStatus(Response.SC_OK);
    
      response.getOutputStream().write(compressedJsonData );
      response.getOutputStream().close();
    
      baseRequest.setHandled(true);
    }
    

    在客户端的 XMLHttpRequest javascript 代码中无需更改。浏览器(在 Edge、IE11、Chrome 上验证)会自动解压缩 json 并将其转换回文本,然后再将其传递给 this.responseText

    var http = new XMLHttpRequest();
    http.onreadystatechange=function() {
      if (this.readyState == 4) {
        if (this.status == 200) {
          data = JSON.parse(this.responseText);
        }
      }
    };
    http.open("GET", "/mydata", true);
    http.send();
    

    我还添加了一个进度条来向客户端显示已下载了多少数据。为此,压缩前的长度被保存并添加为服务器端的自定义 http 标头

    response.setHeader("Uncompressed-Length", myDataInJsonStringFormat.length + "");
    

    在客户端,当 readyState == 2 时,这个标头在 onreadystatechange 处理程序中被读取为 estimatedLength,并且进度处理程序设置如下:

        http.onprogress = function(event) {
          var percentComplete = Math.round((event.loaded/estimatedLength) * 100);
          progressMessage = Math.round(event.loaded/1000) + "KB downloaded and uncompressed, " + percentComplete + "%";
        };
    

    【讨论】:

    • 如果您使用 Jetty GzipHandler,那么它会尊重浏览器、任何中介、您的 Jetty 服务器以及设置这些标头的任何 servlet 之间的各种缓存标头。您在此答案中采用的方法没有。您的内容将被反复压缩(不需要时)并反复发送给客户端(不需要时)。
    • 也许我应该补充一点,我发送标头 response.setHeader("Cache-Control", "public,max-age=1209600"); // 2 周和 response.setHeader("Expires", new Date(System.currentTimeMillis() + 1209600000).toString());但我省略了这一点,因为问题是关于压缩的。内容已正确缓存在服务工作者和浏览器缓存中,并且已通过监控服务器日志和未看到新请求来验证这一点。
    • 是的,老式缓存管理。请记住,GzipHandler 还支持 ETag 和范围请求(当您最终想要/需要它们时)
    • 感谢您的提示,ETag 可以很好地与我的特定实现配合使用,并且可以很好地使用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-21
    • 1970-01-01
    • 2012-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-28
    相关资源
    最近更新 更多