Как в Spring Boot включить Content-Length в ответ?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 06.05.22 10:01
Оценка:
Разрабатываю онлайн-игру с сервером на Java и клиентом на Си. Для передачи данных используется REST + XML (Jackson). Клиент подключается к серверу через TCP сокет, отправляет HTTP-запросы, парсит тело из ответа с помощью Expat'а и обновляет картинку. На сервере используется обычное Spring Boot приложение со встроенным Jetty.

Проблема в том, что по умолчанию Spring Boot использует Transfer-Encoding с разбиением ответа на chunk'и. Возвращаемые заголовки в ответе:

Content-Type: application/xml
Date: Fri, 06 May 2022 09:41:32 GMT
Transfer-Encoding: chunked

Естественно, читать такой ответ, собирая из кусков, неудобно и очень медленно. Как из заголовков убрать 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 должен рассчитывать его автоматически. Как это сделать?
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Отредактировано 06.05.2022 10:54 Worminator X . Предыдущая версия .
Re: Как в Spring Boot включить Content-Length в ответ?
От: Pzz Россия https://github.com/alexpevzner
Дата: 06.05.22 10:44
Оценка: +1
Здравствуйте, Worminator X, Вы писали:

WX>Проблема в том, что по умолчанию Spring Boot использует Transfer-Encoding с разбиением ответа на chunk'и. Возвращаемые заголовки в ответе:


Ничего не понимаю в твоей Яве, но сразу скажу, что chunked transfer encoding используется тогда, когда размер тела сообщения заранее не известен.

И дам я тебе так же добрый совет. Не пиши своего HTTP-клиента на Си. Возьми готовую библиотеку, CURL, например. Оно само за тебя справится с encoding'ами и прочими нюансами, о которых ты даже не знаешь. Это я тебе говорю как человек, который сам несколько таких клиентов написал, причем вполне работоспособных
Re[2]: Как в Spring Boot включить Content-Length в ответ?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 06.05.22 10:59
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>И дам я тебе так же добрый совет. Не пиши своего HTTP-клиента на Си. Возьми готовую библиотеку, CURL, например. Оно само за тебя справится с encoding'ами и прочими нюансами, о которых ты даже не знаешь. Это я тебе говорю как человек, который сам несколько таких клиентов написал, причем вполне работоспособных


В смысле libcurl? Она же вроде только для UNIX?

Нужно что-то кроссплатформенное (Windows и Linux как минимум, а лучше и Android). И не C++, его я не знаю. Чтобы подключить к готовому игровому 2D движку на SDL.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re: Как в Spring Boot включить Content-Length в ответ?
От: GarryIV  
Дата: 06.05.22 11:22
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX>Естественно, читать такой ответ, собирая из кусков, неудобно и очень медленно.


Я нихрена не понял кому это неудобно. Это как бы внутре http клиента прозрачно делается.
Почему это вдруг медленно тоже не особо понятно. Бенчмарки делал?

Но если прям вот надо можно так https://stackoverflow.com/a/37749537
WBR, Igor Evgrafov
Re: Как в Spring Boot включить Content-Length в ответ?
От: Micht  
Дата: 06.05.22 11:23
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX> @GetMapping(value = "/{id}")

WX> public ResponseEntity<User> getUser(@PathVariable String id) {
WX> final Optional<User> user = UsersService.getInstance().find(id);
WX> return user.isPresent() ?
WX> ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(user.get()) :
WX> ResponseEntity.notFound().build();
WX> }

99% это из-за ResponseEntity (тем более, как ниже ответили, такое бывает, когда заранее неизвестна длина ответа). Я бы предложил

@GetMapping(value = "/{id}")
public ResponseEntity<User> getUser(@PathVariable String id) {
return UsersService.getInstance().find(id).orElseThrow(() -> new MyNotFoundException("user not found: " + id));
}


И обработчик, посмотри в соседней теме про валидацию данных.
Отредактировано 06.05.2022 11:41 Micht . Предыдущая версия .
Re[3]: Как в Spring Boot включить Content-Length в ответ?
От: Pzz Россия https://github.com/alexpevzner
Дата: 06.05.22 11:26
Оценка:
Здравствуйте, Worminator X, Вы писали:

Pzz>>И дам я тебе так же добрый совет. Не пиши своего HTTP-клиента на Си. Возьми готовую библиотеку, CURL, например. Оно само за тебя справится с encoding'ами и прочими нюансами, о которых ты даже не знаешь. Это я тебе говорю как человек, который сам несколько таких клиентов написал, причем вполне работоспособных


WX>В смысле libcurl? Она же вроде только для UNIX?


Нет, она не только для UNIX. https://curl.se/windows/

Есть еще mingw'шные сборки.

HTTP только кажется простым. Там много тонкостей и нюансов. Не связывайся, пока не приперло, бери готовое.
Re: Как в Spring Boot включить Content-Length в ответ?
От: · Великобритания  
Дата: 06.05.22 11:40
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX> final String xml = mapper.writeValueAsString(user);

WX> return xml.length();
Здесь у тебя будет размер в символах, а Content-Length должен быть в байтах. Это не всегда одно и то же.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: Как в Spring Boot включить Content-Length в ответ?
От: maxkar  
Дата: 06.05.22 11:42
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX>Клиент подключается к серверу через TCP сокет, отправляет HTTP-запросы,

Какую версию HTTP заявляет клиент в запросе? Если 1.1, то это его проблемы, Chunked должно поддерживаться. Если не поддерживается, то нужно завлять 1.0 и не заявлять поддержку chunked в заголовке TE.
Re: Как в Spring Boot включить Content-Length в ответ?
От: · Великобритания  
Дата: 06.05.22 11:52
Оценка:
Здравствуйте, 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 в ответ?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 06.05.22 11:58
Оценка:
Здравствуйте, 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 в ответ?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 06.05.22 12:01
Оценка:
Здравствуйте, maxkar, Вы писали:

M>Какую версию HTTP заявляет клиент в запросе? Если 1.1, то это его проблемы, Chunked должно поддерживаться. Если не поддерживается, то нужно завлять 1.0 и не заявлять поддержку chunked в заголовке TE.


С клиента посылается запрос:
static char *request = "GET /users/1 HTTP/1.1\r\nHost: 127.0.0.1:2992\r\nUser-Agent: GameClient (Windows/x64)\r\n\r\n";


Если отправлять GET /users/1 HTTP/1.0..., то приходит ответ без chunked и без Content-Length (тоже неудобно, нужно знать размер тела).
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Отредактировано 06.05.2022 12:15 Worminator X . Предыдущая версия .
Re[4]: Как в Spring Boot включить Content-Length в ответ?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 06.05.22 12:08
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Нет, она не только для UNIX. https://curl.se/windows/


Хорошо, попробуем.

Pzz>HTTP только кажется простым. Там много тонкостей и нюансов. Не связывайся, пока не приперло, бери готовое.


Вот сейчас раздумываю, стоит ли вообще использовать HTTP. Может, лучше перейти на WebSocket? Конечно, REST сервис писать проще.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[5]: Как в Spring Boot включить Content-Length в ответ?
От: Pzz Россия https://github.com/alexpevzner
Дата: 06.05.22 12:11
Оценка:
Здравствуйте, Worminator X, Вы писали:

Pzz>>HTTP только кажется простым. Там много тонкостей и нюансов. Не связывайся, пока не приперло, бери готовое.


WX>Вот сейчас раздумываю, стоит ли вообще использовать HTTP. Может, лучше перейти на WebSocket? Конечно, REST сервис писать проще.


WebSocket — это HTTP плюс еще полстолькоже. И готовая реализация на Си мне не известна (это не значит, что ее нет).
Re[3]: Как в Spring Boot включить Content-Length в ответ?
От: maxkar  
Дата: 06.05.22 12:13
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX>С клиента посылается запрос:

WX>
static char *request = "GET /users/1 HTTP/1.1\r\nHost: 127.0.0.1:2992\r\nUser-Agent: GameClient (Windows/x64)\r\n\r\n";


Ну вот же сами написали (я выделил). Назвался 1.1 — будь добр поддерживать Chunked. Сервер его вообще по рандому может выставлять. И если вдруг у вас когда-нибудь появятся маршрутизаторы или балансировщики нагрузки, они тоже могут это делать. Начните с версии 1.0 и потом разбирайтесь с поступающими проблемами. Есть шансы, что просто весрии уже будет достаточно.
Re[3]: Как в Spring Boot включить Content-Length в ответ?
От: Pzz Россия https://github.com/alexpevzner
Дата: 06.05.22 12:16
Оценка:
Здравствуйте, 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 в ответ?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 06.05.22 12:19
Оценка:
Здравствуйте, maxkar, Вы писали:

M>Ну вот же сами написали (я выделил). Назвался 1.1 — будь добр поддерживать Chunked. Сервер его вообще по рандому может выставлять. И если вдруг у вас когда-нибудь появятся маршрутизаторы или балансировщики нагрузки, они тоже могут это делать. Начните с версии 1.0 и потом разбирайтесь с поступающими проблемами. Есть шансы, что просто весрии уже будет достаточно.


Spring ведет себя странно, отправляет ответ все равно HTTP/1.1 200 OK. И без Content-Length. Как-то нужно его включать именно на сервере.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[2]: Как в Spring Boot включить Content-Length в ответ?
От: Worminator X Россия #StandWithPalestine 🖤🤍💚
Дата: 06.05.22 12:25
Оценка:
Здравствуйте, ·, Вы писали:

·>Магии тут не бывает. Чтобы узнать размер объекта в сериализованном виде — его нужно сериализовать. Единственное, что можно найти готовый компонент, а не свой кривой костыль.

·>Это уже пробовал? https://stackoverflow.com/questions/24156490/how-to-set-content-length-in-spring-mvc-rest-for-json

Изучаю, пока не очень понятно.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Re[3]: Как в Spring Boot включить Content-Length в ответ?
От: Stanislav V. Zudin Россия  
Дата: 06.05.22 12:56
Оценка:
Здравствуйте, Worminator X, Вы писали:

WX>В смысле libcurl? Она же вроде только для UNIX?


WX>Нужно что-то кроссплатформенное (Windows и Linux как минимум, а лучше и Android). И не C++, его я не знаю. Чтобы подключить к готовому игровому 2D движку на SDL.


Тогда посмотри Poco.
_____________________
С уважением,
Stanislav V. Zudin
Re: Как в Spring Boot включить Content-Length в ответ?
От: vsb Казахстан  
Дата: 06.05.22 12:59
Оценка: +2
Для этого надо реализовать Servlet Filter (если у вас приложение на сервлетах). В нём надо в буфер складывать записываемые данные и в конце поставить content-length и после этого выдать буфер.
Re[4]: Как в Spring Boot включить Content-Length в ответ?
От: Pzz Россия https://github.com/alexpevzner
Дата: 06.05.22 13:49
Оценка:
Здравствуйте, Stanislav V. Zudin, Вы писали:

WX>>Нужно что-то кроссплатформенное (Windows и Linux как минимум, а лучше и Android). И не C++, его я не знаю. Чтобы подключить к готовому игровому 2D движку на SDL.


SVZ>Тогда посмотри Poco.


Товарищ просил не на C++
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.