Разрабатываю онлайн-игру с сервером на Java и клиентом на Си. Для передачи данных используется REST + XML (Jackson). Клиент подключается к серверу через TCP сокет, отправляет HTTP-запросы, парсит тело из ответа с помощью Expat'а и обновляет картинку. На сервере используется обычное Spring Boot приложение со встроенным Jetty.
Проблема в том, что по умолчанию Spring Boot использует Transfer-Encoding с разбиением ответа на chunk'и. Возвращаемые заголовки в ответе:
Естественно, читать такой ответ, собирая из кусков, неудобно и очень медленно. Как из заголовков убрать Transfer-Encoding: chunked и соответственно добавить Content-Length с указанием размера данных, чтобы тело шло единым набором байтов, который сразу можно было бы прочитать?
Вот пример контроллера Spring:
@RestController
@RequestMapping("/users")
public class UsersController {
@GetMapping(value = "/")
public List<User> getAllUsers() {
return UsersService.getInstance().findAll();
}
@GetMapping(value = "/{id}")
public ResponseEntity<User> getUser(@PathVariable String id) {
final Optional<User> user = UsersService.getInstance().find(id);
return user.isPresent() ?
ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(user.get()) :
ResponseEntity.notFound().build();
}
}
На StackOverflow пишут, что это Spring таким образом использует сжатие ответа. Хорошо, в файле конфигурации application.yml отключаю его:
server:
compression: enabled: false
port: 2992
Не помогает, все равно ответ возвращается с Transfer-Encoding: chunked. Как исправить?
UPD Работает, если задать Content-Length в контроллере вручную через метод ReponseEntity:
@RestController
@RequestMapping("/users")
public class UsersController {
@GetMapping(value = "/")
public List<User> getAllUsers() {
return UsersService.getInstance().findAll();
}
@GetMapping(value = "/{id}")
public ResponseEntity<User> getUser(@PathVariable String id) {
final Optional<User> user = UsersService.getInstance().find(id);
return user.isPresent() ?
ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).contentLength(
getContentLength(user.get())
).body(user.get()) :
ResponseEntity.notFound().build();
}
private static long getContentLength(User user) {
try {
final XmlMapper mapper = new XmlMapper();
final String xml = mapper.writeValueAsString(user);
return xml.length();
} catch (JsonProcessingException ex) {
return 0;
}
}
}
Но это костыль и быдлокод, 2 раза выполяется сериализация для объекта. Очевидно, Spring должен рассчитывать его автоматически. Как это сделать?
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Здравствуйте, Worminator X, Вы писали:
WX>Проблема в том, что по умолчанию Spring Boot использует Transfer-Encoding с разбиением ответа на chunk'и. Возвращаемые заголовки в ответе:
Ничего не понимаю в твоей Яве, но сразу скажу, что chunked transfer encoding используется тогда, когда размер тела сообщения заранее не известен.
И дам я тебе так же добрый совет. Не пиши своего HTTP-клиента на Си. Возьми готовую библиотеку, CURL, например. Оно само за тебя справится с encoding'ами и прочими нюансами, о которых ты даже не знаешь. Это я тебе говорю как человек, который сам несколько таких клиентов написал, причем вполне работоспособных
Re[2]: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, Pzz, Вы писали:
Pzz>И дам я тебе так же добрый совет. Не пиши своего HTTP-клиента на Си. Возьми готовую библиотеку, CURL, например. Оно само за тебя справится с encoding'ами и прочими нюансами, о которых ты даже не знаешь. Это я тебе говорю как человек, который сам несколько таких клиентов написал, причем вполне работоспособных
В смысле libcurl? Она же вроде только для UNIX?
Нужно что-то кроссплатформенное (Windows и Linux как минимум, а лучше и Android). И не C++, его я не знаю. Чтобы подключить к готовому игровому 2D движку на SDL.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, Worminator X, Вы писали:
WX>Естественно, читать такой ответ, собирая из кусков, неудобно и очень медленно.
Я нихрена не понял кому это неудобно. Это как бы внутре http клиента прозрачно делается.
Почему это вдруг медленно тоже не особо понятно. Бенчмарки делал?
Здравствуйте, Worminator X, Вы писали:
Pzz>>И дам я тебе так же добрый совет. Не пиши своего HTTP-клиента на Си. Возьми готовую библиотеку, CURL, например. Оно само за тебя справится с encoding'ами и прочими нюансами, о которых ты даже не знаешь. Это я тебе говорю как человек, который сам несколько таких клиентов написал, причем вполне работоспособных
WX>В смысле libcurl? Она же вроде только для UNIX?
Здравствуйте, Worminator X, Вы писали:
WX> final String xml = mapper.writeValueAsString(user); WX> return xml.length();
Здесь у тебя будет размер в символах, а Content-Length должен быть в байтах. Это не всегда одно и то же.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, Worminator X, Вы писали:
WX>Клиент подключается к серверу через TCP сокет, отправляет HTTP-запросы,
Какую версию HTTP заявляет клиент в запросе? Если 1.1, то это его проблемы, Chunked должно поддерживаться. Если не поддерживается, то нужно завлять 1.0 и не заявлять поддержку chunked в заголовке TE.
Re: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, Worminator X, Вы писали:
WX>Но это костыль и быдлокод, 2 раза выполяется сериализация для объекта. Очевидно, Spring должен рассчитывать его автоматически. Как это сделать?
Магии тут не бывает. Чтобы узнать размер объекта в сериализованном виде — его нужно сериализовать. Единственное, что можно найти готовый компонент, а не свой кривой костыль.
Это уже пробовал? https://stackoverflow.com/questions/24156490/how-to-set-content-length-in-spring-mvc-rest-for-json
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[2]: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, GarryIV, Вы писали:
GIV>Я нихрена не понял кому это неудобно. Это как бы внутре http клиента прозрачно делается.
Так клиент я пишу сам на сокетах.
GIV>Почему это вдруг медленно тоже не особо понятно. Бенчмарки делал?
Запрос должен отправляться несколько раз в секунду (пока запланировано 50), для обновления игрового состояния. С Transfer-Encoding: chunked:
1) читаем размер chuck'а
2) если не 0, то читаем chuck в буфер, сдвигаем указатель
3) читаем размер следующего chunk'а
4) если не 0, то читаем следующий chunk и т.д.
Нужно собрать тело по кускам. Это очевидно медленно. С заданием Content-Length:
1) получаем размер данных из Content-Length
2) читаем тело указанного размера и сразу можно его парсить
GIV>Но если прям вот надо можно так https://stackoverflow.com/a/37749537
Там аналогично моему 2-му варианту, вручную задается Content-Length (и, как отметили, не совсем правильно):
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(new ObjectMapper().writeValueAsString(map).length()));
return new ResponseEntity<Map<String, ContactInfo>>(map, headers, HttpStatus.CREATED);
Должен быть какой-то способ, чтобы Spring его вычислял и добавлял сам, вместо Transfer-Encoding: chunked.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[2]: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, maxkar, Вы писали:
M>Какую версию HTTP заявляет клиент в запросе? Если 1.1, то это его проблемы, Chunked должно поддерживаться. Если не поддерживается, то нужно завлять 1.0 и не заявлять поддержку chunked в заголовке TE.
Если отправлять GET /users/1 HTTP/1.0..., то приходит ответ без chunked и без Content-Length (тоже неудобно, нужно знать размер тела).
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Хорошо, попробуем.
Pzz>HTTP только кажется простым. Там много тонкостей и нюансов. Не связывайся, пока не приперло, бери готовое.
Вот сейчас раздумываю, стоит ли вообще использовать HTTP. Может, лучше перейти на WebSocket? Конечно, REST сервис писать проще.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[5]: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, Worminator X, Вы писали:
Pzz>>HTTP только кажется простым. Там много тонкостей и нюансов. Не связывайся, пока не приперло, бери готовое.
WX>Вот сейчас раздумываю, стоит ли вообще использовать HTTP. Может, лучше перейти на WebSocket? Конечно, REST сервис писать проще.
WebSocket — это HTTP плюс еще полстолькоже. И готовая реализация на Си мне не известна (это не значит, что ее нет).
Re[3]: Как в Spring Boot включить Content-Length в ответ?
Ну вот же сами написали (я выделил). Назвался 1.1 — будь добр поддерживать Chunked. Сервер его вообще по рандому может выставлять. И если вдруг у вас когда-нибудь появятся маршрутизаторы или балансировщики нагрузки, они тоже могут это делать. Начните с версии 1.0 и потом разбирайтесь с поступающими проблемами. Есть шансы, что просто весрии уже будет достаточно.
Re[3]: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, Worminator X, Вы писали:
WX>Запрос должен отправляться несколько раз в секунду (пока запланировано 50), для обновления игрового состояния. С Transfer-Encoding: chunked: WX>1) читаем размер chuck'а WX>2) если не 0, то читаем chuck в буфер, сдвигаем указатель WX>3) читаем размер следующего chunk'а WX>4) если не 0, то читаем следующий chunk и т.д.
Читаешь в буфер, сколько прочлось, потом парсишь in place. Это не шибко медленно, но это муторно. Особенно с учетом того, что при очередном чтении может прочитаться половина размера chunk'а, а следущее чтение принесет вторую половину.
Когда тебе к этому хозяйству приспичит добавить, HTTPS, gzip и поддержку редиректов и авторизации, ты пожалеешь, что связался
Re[4]: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, maxkar, Вы писали:
M>Ну вот же сами написали (я выделил). Назвался 1.1 — будь добр поддерживать Chunked. Сервер его вообще по рандому может выставлять. И если вдруг у вас когда-нибудь появятся маршрутизаторы или балансировщики нагрузки, они тоже могут это делать. Начните с версии 1.0 и потом разбирайтесь с поступающими проблемами. Есть шансы, что просто весрии уже будет достаточно.
Spring ведет себя странно, отправляет ответ все равно HTTP/1.1 200 OK. И без Content-Length. Как-то нужно его включать именно на сервере.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[2]: Как в Spring Boot включить Content-Length в ответ?
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[3]: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, Worminator X, Вы писали:
WX>В смысле libcurl? Она же вроде только для UNIX?
WX>Нужно что-то кроссплатформенное (Windows и Linux как минимум, а лучше и Android). И не C++, его я не знаю. Чтобы подключить к готовому игровому 2D движку на SDL.
Для этого надо реализовать Servlet Filter (если у вас приложение на сервлетах). В нём надо в буфер складывать записываемые данные и в конце поставить content-length и после этого выдать буфер.
Re[4]: Как в Spring Boot включить Content-Length в ответ?
Здравствуйте, Stanislav V. Zudin, Вы писали:
WX>>Нужно что-то кроссплатформенное (Windows и Linux как минимум, а лучше и Android). И не C++, его я не знаю. Чтобы подключить к готовому игровому 2D движку на SDL.
SVZ>Тогда посмотри Poco.