Я пишу конечную точку, которая динамически генерирует GIF-файл. Я пойду с нуля.Выход GIF из ServletOutputStream
У меня есть класс с именем Function
, который работает как абстрактный класс, и у меня есть несколько классов, в этом примере AddFunction
, которые представляют небольшие куски функциональности. В этом случае AddFunction
добавляет некоторые числа вместе. Когда удаляется конечная точка, ему передается идентификатор AddFunction
(он может быть любым, в этом примере это функция добавления). Код в контроллер выглядит следующим образом:
/**
* Returns the image for a function
*/
@RequestMapping(value = "/function/{functionId}/image.gif", produces = "image/gif")
public void getImage(@PathVariable(value = "functionId") String functionId, HttpServletResponse response) throws IOException {
Function function = functionService.getFunction(Integer.valueOf(functionId));
Logger logger = Logger.getLogger(FunctionController.class);
ServletOutputStream servOut = response.getOutputStream();
// Uses default values if you pass in nulls.
function.getImage(servOut, null, null);
servOut.flush();
servOut.close();
}
Во-первых, Function
обнаруживается по его ID. Я проверил, и найдена правильная функция. Этот код нуждается в некоторой проверке (например, проверка пройденного идентификатора является допустимым числом), но я позабочусь об этом позже. Затем я захватываю выходной поток сервлета и передаю его методам функции getImage
. Этот метод генерирует GIF, который описывает эту функцию. Этот код выглядит следующим образом:
public void getImage(OutputStream out, String staticContent, String changedContent) throws IOException {
String[] data = {"2", "+", "2", "=", "4"};
Logger logger = Logger.getLogger(AddFunction.class);
logger.info("Getting the add image.");
ImageUtils.writeSequenceToImage(ImageIO.createImageOutputStream(out), data, 5, Constants.IMAGE_HEIGHT/2);
}
Как вы можете видеть, он игнорирует значения и используют данные о запасах на данный момент. Он создает массив значений. Каждое из этих значений отображается в каждом кадре GIF. Итак, что я делаю, я беру ServletOutputStream
, и я использую ImageIO.createImageOutputStream
, чтобы обернуть это с помощью объекта ImageOutputStream
. Это когда передается в метод writeSequenceToImage
в моем собственном классе ImageUtils
. Последние два значения - это координаты для записи. В этом случае, вертикальная середина изображения, в крайнем левом углу. Код для метода writeSequenceToImage
выглядит следующим образом:
public static void writeSequenceToImage(ImageOutputStream out, String[] contentList, int x, int y) throws IOException {
StringBuilder dataBuilder = new StringBuilder();
Test test = new Test(out, BufferedImage.TYPE_INT_RGB, 500, true);
Logger logger = Logger.getLogger(ImageUtils.class);
logger.info("Writing sequence to image.");
for (String content : contentList) {
dataBuilder.append(content);
logger.info("writing " + dataBuilder.toString() + " to the gif.");
test.writeToSequence(generateAndWriteToImage(dataBuilder.toString(), x, y));
}
}
В этом коде я использую класс Test
(временное название), который содержит код, который записывает данные в файл GIF. Все, что я делаю здесь, это цикл и добавление каждого значения в фрейм в GIF. Код для класса Test
можно найти here. То, что я делаю, я застроить строку, поэтому в нашем примере журналы выведет:
2014-12-31 14:37:15 INFO ImageUtils:48 - Writing sequence to image.
2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2 to the gif.
2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2+ to the gif.
2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2+2 to the gif.
2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2+2= to the gif.
2014-12-31 14:37:15 INFO ImageUtils:53 - writing 2+2=4 to the gif.
Это даст появление в каждом кадре GIF из его построения строки. Теперь, когда я пишу это в формат GIF, и я ожидаю, что это будет толкнуло прямо в ServletOutputStream
, только тогда, когда я пытаюсь ссылаться на него со следующей HTML:
<div class="panel columns large-12" ng-show="selectedFunction">
<h2>{{selectedFunction.name}}</h2>
<p>{{selectedFunction.description}}</p>
<p>This function expects a {{selectedFunction.expectedParameterType}} as a parameter.</p>
<p>This function will return a {{selectedFunction.expectedReturnType}}</p>
<img src="/autoalgorithm/functions/function/{{selectedFunction.id}}/image.gif" alt="{{selectedFunction.name}}"/>
</div>
Я вижу следующие данные возвращающихся в Chrome:
И я не вижу никакого образа на моей странице:
Что я пробовал
Я попытался увидеть размер того, что возвращается. Для этого я заменил ServletOutputStream
на ByteArrayOutputStream
, чтобы получить размер данных.Если я это сделаю, мой код выглядит следующим образом:
/**
* Returns the image for a function
*/
@RequestMapping(value = "/function/{functionId}/image.gif", produces = "image/gif")
public @ResponseBody byte[] getImage(@PathVariable(value = "functionId") String functionId, HttpServletResponse response) throws IOException {
Function function = functionService.getFunction(Integer.valueOf(functionId));
Logger logger = Logger.getLogger(FunctionController.class);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Uses default values if you pass in nulls.
function.getImage(baos, null, null);
logger.info("The number of bytes returned is " + baos.toByteArray().length);
return baos.toByteArray();
}
И бревенчатые выходы:
2014-12-31 15:34:09 INFO FunctionController:85 - The number of bytes returned is 0
Так что говорит мне, что это не написано слишком. Поэтому я изменил свой подход и отредактировал код, поэтому я сохранил ссылку на ImageOutputStream
в своем контроллере. Это означало, что у меня был полный контроль над объектом, поэтому теперь выведен журнал:
2014-12-31 15:39:56 INFO FunctionController:85 - The number of bytes returned is 2708
Что было обнадеживающим! И 2KB звучит правильно для очень простого GIF. Однако, когда я проверяю ответ от Google, подобной истории:
Хотя на этот раз она имеет длину контента, но нет предварительного просмотра доступен и изображение все еще не появляется.
Мне было интересно, если бы кто-нибудь здесь занялся подобной проблемой? Я подозреваю, что это связано с кодировкой GIF, но ImageIO
не поддерживает преобразование из одного потока в другой, только от одного BufferedImage
до другого. Поэтому я использовал метод ImageIO.read
для чтения его в BufferedImage
и использовал ImageIO.write
, чтобы написать его как gif
на ServletOutputStream
. Это привело к ошибке:
java.lang.IllegalArgumentException: image == null!
На данный момент я в тупике. Я надеюсь, что свежий набор глаз поможет мне. У кого-нибудь есть идеи?
Если вы пишете массив байтов в виде gif-файла, можете ли вы его открыть и сделать его правильно? Кроме того, почему тип контента - image/webp? Не должно быть image/gif как аннотировано в методе контроллера? –
It * should * be. Если данные не возвращаются, это будет выглядеть как «text/plain», что еще более странно! Сейчас я проведу тест и узнаю для вас. – christopher
Вы слишком много пытаетесь. Это затрудняет решение проблем. У вас, наверное, несколько. Создайте тест (отдельно от структуры Spring MVC), который сохраняет GIF в файле и проверяет, работает ли это. Создайте более простой метод контроллера, который считывает рабочий GIF с диска и отправляет его как ответ. – Codo