Я запрашиваю и анализирую данные json, используя XMLHttpRequest. Сервер Java 8, использующий встроенный причал 9, получает запрос и возвращает данные. Код показан ниже с 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);
}
Вопрос в том, как я могу изменить приведенный выше код для отправки сжатых данных json из java, а затем распаковать данные с помощью XMLHttpRequest?
Я бы предпочел не использовать какие-либо дополнительные клиентские или серверные библиотеки вместо того, что я уже использую.




Добавьте Eclipse Jetty GzipHandler в дерево обработчиков сервера в место перед кодом пользовательского обработчика.
Это будет выполнять 2 задачи:
Просто убедитесь, что вы настроили список включенных mime-типов в GzipHandler, чтобы разрешить обработку application/json содержимого тела ответа.
В данных XMLHttpResponse ничего делать не нужно, см. ниже. Этот ответ был бы подходом на стороне сервера, если бы возвращаемые данные json динамически менялись, а не были более статичными по своей природе.
Вы можете использовать base64 для передачи данных. В нем есть две функции соответственно для декодирования и кодирования строк base64: atob() и btoa(). Найдите, как вы можете сделать это с помощью java.
Хотя это можно сделать как на стороне java, так и на стороне javascript, не увеличит ли это размер данных json, как это предлагается в примере на w3schools.com/jsref/met_win_atob.asp.
Данные, которые я отправляю с Java-сервера клиенту, будут иметь размер до 17 МБ, и это часто происходит из-за плохого мобильного соединения.
В этом случае вы можете использовать JSONC для сжатия строки JSON: JSONC.compress(json); JSONC.decompress(сжатыйJSON);
В этом случае одни и те же наборы данных повторно передаются клиентам. Вместо добавления обработчика причала текстовая строка 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);
}
В коде JavaScript XMLHttpRequest на стороне клиента никаких изменений не требуется. Браузеры (проверено в Edge, IE11, Chrome) автоматически распаковывают json и преобразуют его обратно в текст перед передачей в это.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 + "");
На клиенте этот заголовок считывается в обработчике onreadystatechange как расчетная длина, когда состояние готовности == 2 и обработчик выполнения настроены следующим образом:
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 и любыми сервлетами, которые устанавливают эти заголовки. Подход, который у вас есть в этом ответе, не подходит. Ваш контент будет неоднократно сжиматься (когда в этом нет необходимости) и повторно отправляться клиенту (когда в этом нет необходимости).
Возможно, мне следовало добавить, что я отправляю заголовки response.setHeader("Cache-Control", "public,max-age=1209600"); // 2 недели и response.setHeader("Expires", new Date(System.currentTimeMillis() + 1209600000).toString()); но я опустил это, так как вопрос был о сжатии. Контент правильно кэшируется как в сервис-воркерах, так и в кешах браузера, и это было подтверждено мониторингом журналов сервера и отсутствием новых запросов.
Да, управление кешем старой школы. Имейте в виду, что GzipHandler также поддерживает запросы ETag и range (на тот случай, когда они вам в конечном итоге понадобятся).
Спасибо за подсказку, ETag может хорошо работать с моей конкретной реализацией и вполне может использоваться в будущем.
Если я смогу настроить это, что мне делать со сжатыми данными в ответе XMLHttpRequest?