2014-12-31 2 views
3

Я пишу конечную точку, которая динамически генерирует 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:

Data Response

И я не вижу никакого образа на моей странице:

View

Что я пробовал

Я попытался увидеть размер того, что возвращается. Для этого я заменил 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, подобной истории:

ios response

Хотя на этот раз она имеет длину контента, но нет предварительного просмотра доступен и изображение все еще не появляется.

Мне было интересно, если бы кто-нибудь здесь занялся подобной проблемой? Я подозреваю, что это связано с кодировкой GIF, но ImageIO не поддерживает преобразование из одного потока в другой, только от одного BufferedImage до другого. Поэтому я использовал метод ImageIO.read для чтения его в BufferedImage и использовал ImageIO.write, чтобы написать его как gif на ServletOutputStream. Это привело к ошибке:

java.lang.IllegalArgumentException: image == null! 

На данный момент я в тупике. Я надеюсь, что свежий набор глаз поможет мне. У кого-нибудь есть идеи?

+0

Если вы пишете массив байтов в виде gif-файла, можете ли вы его открыть и сделать его правильно? Кроме того, почему тип контента - image/webp? Не должно быть image/gif как аннотировано в методе контроллера? –

+0

It * should * be. Если данные не возвращаются, это будет выглядеть как «text/plain», что еще более странно! Сейчас я проведу тест и узнаю для вас. – christopher

+2

Вы слишком много пытаетесь. Это затрудняет решение проблем. У вас, наверное, несколько. Создайте тест (отдельно от структуры Spring MVC), который сохраняет GIF в файле и проверяет, работает ли это. Создайте более простой метод контроллера, который считывает рабочий GIF с диска и отправляет его как ответ. – Codo

ответ

2

Как уже отмечалось в комментариях, ваш вопрос немного несовместим, но я попытаюсь показать вам, как он может работать.

Сначала попробуйте следующее:

@RequestMapping(value = "/function/{functionId}/image.gif", produces = "image/gif") 
public void getImage(@PathVariable(value = "functionId") String functionId, HttpServletResponse response) throws IOException { 

    BufferedImage firstImage = ImageIO.read(new File("/bla.jpg")); 
    response.setContentType("image/gif"); // this should happen automatically 

    ImageIO.write(firstImage, "gif", response.getOutputStream()); 
    response.getOutputStream().close(); 
} 

Поместите какой-то файл с именем bla.jpg в корневой директории или изменить путь к какому-то существующему файлу изображения (также может быть GIF). Убедитесь, что у вас есть хотя бы права доступа на чтение.

Это должно работать в любом случае независимо от jpg или gif-файла. Если это не работает, может быть что-то не так с вашей конфигурацией Spring. Вы должны это исключить.

Если это работает, вы можете использовать свой метод generateAndWriteToImage() для замены ImageIO.read(new File("/bla.jpg"));. И вы должны это сделать.

Теперь я не знаю, что делает ваш метод generateAndWriteToImage(), но я предполагаю, что он создает экземпляр BufferedImage, записывает текст в изображение и возвращает его. Что-то вроде этого:

public static BufferedImage generateAndWriteToImage(String string, int x, int y) { 

    BufferedImage image = new BufferedImage(x,y,BufferedImage.TYPE_INT_RGB); 
    Graphics g = image.getGraphics(); 
    g.setPaintMode(); 
    g.setFont(g.getFont().deriveFont(30f)); 
    g.drawString(string, 100, 100); 
    g.dispose(); 
    return image; 
} 

Если вы создаете образ с типом BufferedImage.TYPE_INT_RGB это не должно вызывать никаких проблем.





TL; DR Другая вещь, которую вы уже выяснили себя, что немного рефакторинг дает возможность закрыть ImageOutputStream.

Пусть следующий метод:

@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)); 

    ServletOutputStream servOut = response.getOutputStream(); 

    // Uses default values if you pass in nulls. 
    function.getImage(servOut, null, null); 

    servOut.flush(); 
    servOut.close(); 
} 

и этот метод:

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); 
} 

Во втором методе, вы создаете локальный экземпляр ImageOutputStream с ImageIO.createImageOutputStream(out) (последней строки).

Я предполагаю, что основная проблема заключается в том, что вы не закрываете этот ImageOutputStream, это может привести к тому, что данные не будут записываться ни на один другой номер OutputStream (из-за буферизации).

Чтобы сделать его работу вы можете реорганизовать свои методы к этому:

@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)); 

    ImageOutputStream servOut = ImageIO.createImageOutputStream(response.getOutputStream()); 

    // Uses default values if you pass in nulls. 
    function.getImage(servOut, null, null); 

    servOut.close(); 
} 

и это:

public void getImage(ImageOutputStream 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(out, data, 5, Constants.IMAGE_HEIGHT/2); 
} 

То же самое относится и здесь метод generateAndWriteToImage(). Если он правильно возвращает экземпляр BufferedImage, это должно работать (с рефакторингом).

+1

Ваши советы приводят меня к правильному ответу, и я чувствую, что это действительно хорошее руководство для тех, кто возвращается к этому вопросу. Хорошо зарабатывать! – christopher

+1

Я рад, что смог помочь :) – unwichtich

0

Я не пробовал пружину, но вместо этого пытался использовать J2EE. Ниже подход работает для меня!

private void process(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { 

    String baseServletPath = httpServletRequest.getServletContext().getRealPath("/"); 

    System.out.println("Base Servlet Path :"+baseServletPath); 
    String relativeInputFilePath = "images/Tulips.gif"; 
    String imageFilePath = baseServletPath + relativeInputFilePath; 
    File file = new File(imageFilePath); 

    BufferedImage bufferedImage = null; 
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
    try { 
     bufferedImage = ImageIO.read(file); 
     ImageIO.write(bufferedImage, "GIF", byteArrayOutputStream); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 

    byte[] imageData = byteArrayOutputStream.toByteArray(); 
    String relativeOutFilePath = "images/TulipsOut.gif"; 
    String imageOutFilePath = baseServletPath + relativeOutFilePath; 
    File fileOut = new File(imageOutFilePath); 
    FileOutputStream fileOutputStream=null; 
    try { 
     fileOutputStream = new FileOutputStream(fileOut); 
     fileOutputStream.write(imageData); 
    } catch (FileNotFoundException e) { 
     e.printStackTrace(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } finally { 
     try { 
      fileOutputStream.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 


    try { 
     httpServletResponse.getOutputStream().write(imageData); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 
+0

Хорошо, проблема с этим подходом заключается в том, что вы получаете GIF из файла и выводите его в ответ. Я ищу, как сгенерировать его с помощью кода. – christopher

+0

Я просто пытался помочь с сломанной частью изображения. Это все. Для того, чтобы узнать, как создать образ в java, должно быть несколько руководств и примеров. Как [здесь] (http://docs.oracle.com/javase/tutorial/2d/images/saveimage.html) –

+1

Нет, я согласен, ваш ввод был хороший.Но то, что я искал, было полным, самодостаточным, поэтапным объяснением проблемы. Это было указано выше, в хорошем формате «устранения неполадок». Все равно +1 для действительной информации. Редактировать: Кстати, я не спускал вниз. – christopher

Смежные вопросы