Quién establece el tipo de contenido de respuesta en Spring MVC (@ResponseBody)

126

Tengo en mi aplicación web Spring MVC Java de Annotation ejecutada en el servidor web de embarcadero (actualmente en el complemento de embarcadero de Maven).

Estoy tratando de hacer algo de soporte AJAX con un método de controlador que devuelve solo el texto de ayuda de String. Los recursos están en codificación UTF-8 y también la cadena, pero mi respuesta del servidor viene con

content-encoding: text/plain;charset=ISO-8859-1 

incluso cuando mi navegador envía

Accept-Charset  windows-1250,utf-8;q=0.7,*;q=0.7

Estoy usando de alguna manera la configuración predeterminada de spring

He encontrado una pista para agregar este bean a la configuración, pero creo que simplemente no se usa, porque dice que no admite la codificación y en su lugar se usa uno predeterminado.

<bean class="org.springframework.http.converter.StringHttpMessageConverter">
    <property name="supportedMediaTypes" value="text/plain;charset=UTF-8" />
</bean>

Mi código de controlador es (tenga en cuenta que este tipo de cambio de respuesta no funciona para mí):

@RequestMapping(value = "ajax/gethelp")
public @ResponseBody String handleGetHelp(Locale loc, String code, HttpServletResponse response) {
    log.debug("Getting help for code: " + code);
    response.setContentType("text/plain;charset=UTF-8");
    String help = messageSource.getMessage(code, null, loc);
    log.debug("Help is: " + help);
    return help;
}
Hurda
fuente

Respuestas:

59

La declaración simple del StringHttpMessageConverterbean no es suficiente, debe inyectarlo en AnnotationMethodHandlerAdapter:

<bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <array>
            <bean class = "org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
            </bean>
        </array>
    </property>
</bean>

Sin embargo, al usar este método, debe redefinir todos los correos HttpMessageConverterelectrónicos, y tampoco funciona <mvc:annotation-driven />.

Entonces, quizás el método más conveniente pero feo es interceptar la instanciación de AnnotationMethodHandlerAdaptercon BeanPostProcessor:

public class EncodingPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String name)
            throws BeansException {
        if (bean instanceof AnnotationMethodHandlerAdapter) {
            HttpMessageConverter<?>[] convs = ((AnnotationMethodHandlerAdapter) bean).getMessageConverters();
            for (HttpMessageConverter<?> conv: convs) {
                if (conv instanceof StringHttpMessageConverter) {
                    ((StringHttpMessageConverter) conv).setSupportedMediaTypes(
                        Arrays.asList(new MediaType("text", "html", 
                            Charset.forName("UTF-8"))));
                }
            }
        }
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String name)
            throws BeansException {
        return bean;
    }
}

-

<bean class = "EncodingPostProcessor " />
axtavt
fuente
10
Parece un truco sucio. No me gusta sino usar. ¡Los desarrolladores de Spring Framework deberían trabajar en este caso!
digz6666
¿A dónde va la línea <bean class = "EncodingPostProcessor" />?
zod
1
@zod: In DispatcherServlet's config ( ...-servlet.xml)
axtavt
Gracias. Parece ser ignorado. Estamos usando mvc (creo) y tenemos una clase con un atributo @Controller, que parece ser el punto de entrada. La clase no se menciona en ningún otro lugar (tiene una interfaz con un nombre similar) pero se instancia y se llama correctamente. Las rutas se asignan con un atributo @RequestMapping. No podemos controlar el tipo de contenido de la respuesta (necesitamos xml). Como probablemente pueda ver, no tengo idea de lo que estoy haciendo, y el desarrollador que creó esto dejó mi empresa. Gracias.
zod
3
Como @ digz6666 dice que este es un truco sucio. Spring debería ver cómo lo hace JAX-RS.
Adam Gent
166

Encontré la solución para Spring 3.1. con el uso de la anotación @ResponseBody. Aquí hay un ejemplo de controlador que usa salida Json:

@RequestMapping(value = "/getDealers", method = RequestMethod.GET, 
produces = "application/json; charset=utf-8")
@ResponseBody
public String sendMobileData() {

}
Guerrero
fuente
77
+1. Esto también lo resolvió para mí, pero solo después de que cambié a usar <mvc:annotation-driven/>en applicationContext. (En lugar de <bean class=" [...] DefaultAnnotationHandlerMapping"/>, lo cual está en desuso en la primavera 3.2 de todos modos ...)
Jonik
¿Puede esto producir aplicación / xml si se anota de esta manera?
Hurda
2
@Hurda: Obviamente, puede especificar cualquier tipo de contenido que desee cambiando el valor del producesatributo.
Jonik
1
Hay un MediaType.APPLICATION_JSON_VALUE, para "application / json" también.
dev
2
Para UTF-8, ver MediaType.APPLICATION_JSON_UTF8_VALUE.
calvinf
51

Tenga en cuenta que en Spring MVC 3.1 puede usar el espacio de nombres MVC para configurar convertidores de mensajes:

<mvc:annotation-driven>
  <mvc:message-converters register-defaults="true">
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
      <property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
    </bean>
  </mvc:message-converters>
</mvc:annotation-driven>

O configuración basada en código:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

  private static final Charset UTF8 = Charset.forName("UTF-8");

  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
    stringConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
    converters.add(stringConverter);

    // Add other converters ...
  }
}
Rossen Stoyanchev
fuente
Tipo de trabajos, excepto que 1) contamina la respuesta con un Accept-Charsetencabezado que probablemente enumera cada codificación de caracteres conocida, y 2) cuando la solicitud tiene un Acceptencabezado, la supportedMediaTypespropiedad del convertidor no se usa , por ejemplo, cuando hago la solicitud escribiendo directamente la URL en un navegador, la respuesta tiene un Content-Type: text/htmlencabezado en su lugar.
Giulio Piancastelli
3
Puede simplificar ya que "texto / sin formato" es predeterminado de todos modos: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Igor Mukhin
Esta respuesta debe aceptarse como la respuesta correcta. Además, la forma de @IgorMukhin de definir el bean StringHttpMessageConverter funciona. Esta respuesta se utiliza para establecer tipos de contenido de respuesta para todos los servlets. Si solo necesita establecer el tipo de contenido de respuesta para un método de controlador en particular, use la respuesta de Warrior en su lugar (use el argumento produce en @RequestMapping)
PickBoy
3
@GiulioPiancastelli su primera pregunta puede resolverse agregando <property name = "writeAcceptCharset" value = "false" /> al bean
PickBoy
44

En caso de que también pueda configurar la codificación de la siguiente manera:

@RequestMapping(value = "ajax/gethelp")
public ResponseEntity<String> handleGetHelp(Locale loc, String code, HttpServletResponse response) {
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "text/html; charset=utf-8");

    log.debug("Getting help for code: " + code);
    String help = messageSource.getMessage(code, null, loc);
    log.debug("Help is: " + help);

    return new ResponseEntity<String>("returning: " + help, responseHeaders, HttpStatus.CREATED);
}

Creo que usar StringHttpMessageConverter es mejor que esto.

digz6666
fuente
Esta es también la solución si obtiene el error the manifest may not be valid or the file could not be opened.en IE 11. ¡Gracias digz!
Arun Christopher
21

puede agregar produce = "text / plain; charset = UTF-8" a RequestMapping

@RequestMapping(value = "/rest/create/document", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String create(Document document, HttpServletRespone respone) throws UnsupportedEncodingException {

    Document newDocument = DocumentService.create(Document);

    return jsonSerializer.serialize(newDocument);
}

mira este blog para más detalles

Charlie Wu
fuente
2
Ese código no se compilaría; Estás devolviendo algo de un método vacío.
Andrew Swan
2
lo siento, error malo, ya está solucionado
Charlie Wu
3
Es una respuesta incorrecta. Según los documentos de Spring: los tipos de medios producibles de la solicitud asignada, reduciendo la asignación primaria. El formato es una secuencia de tipos de medios ("text / plain", "application / *), con una solicitud asignada solo si Accept coincide con uno de estos tipos de medios. Las expresiones se pueden negar utilizando el operador"! ", Como en "! text / plain", que coincide con todas las solicitudes con un Aceptar que no sea "text / plain".
Oleksandr_DJ
@CharlieWu Hay un problema con el enlace
Matt
10

Estaba luchando contra este problema recientemente y encontré una respuesta mucho mejor disponible en Spring 3.1:

@RequestMapping(value = "ajax/gethelp", produces = "text/plain")

Entonces, tan fácil como JAX-RS como todos los comentarios indicaron que podría / debería ser.

dbyoung
fuente
Vale la pena portar a Spring 3.1 para!
young.fu.panda
55
@dbyoung Eso no parece correcto, el javadoc para el producesdice: "... la solicitud solo se asigna si Content-Type coincide con uno de estos tipos de medios". lo que significa AFAIK que produceses relevante si el método coincide con una solicitud y no con qué tipo de contenido debería tener la respuesta.
Ittai
@Ittai correcto! "produce" determina si el método coincide con la solicitud, pero NO qué tipo de contenido está en la respuesta. algo más debe estar mirando "produce" al determinar qué tipo de contenido configurar
anton1980
6

Puede usar produce para indicar el tipo de respuesta que está enviando desde el controlador. Esta palabra clave "produce" será más útil en la solicitud ajax y fue muy útil en mi proyecto

@RequestMapping(value = "/aURLMapping.htm", method = RequestMethod.GET, produces = "text/html; charset=utf-8") 

public @ResponseBody String getMobileData() {

}
Jayaraman Balasubramanian
fuente
4

Gracias digz6666, su solución funciona para mí con ligeros cambios porque estoy usando json:

responseHeaders.add ("Content-Type", "application / json; charset = utf-8");

La respuesta dada por axtavt (que me has recomendado) no funcionará para mí. Incluso si he agregado el tipo de medio correcto:

if (conv instanceof StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) conv) .setSupportedMediaTypes (
                        Arrays.asList (
                                nuevo MediaType ("texto", "html", Charset.forName ("UTF-8")),
                                nuevo MediaType ("aplicación", "json", Charset.forName ("UTF-8"))));
                }
redochka
fuente
4

Configuré el tipo de contenido en MarshallingView en el bean ContentNegotiatingViewResolver . Funciona fácilmente, limpio y sin problemas:

<property name="defaultViews">
  <list>
    <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
      <constructor-arg>
        <bean class="org.springframework.oxm.xstream.XStreamMarshaller" />     
      </constructor-arg>
      <property name="contentType" value="application/xml;charset=UTF-8" />
    </bean>
  </list>
</property>
Reto-san
fuente
3

Estoy usando CharacterEncodingFilter, configurado en web.xml. Quizás eso ayude.

    <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
Theresia Sofía Nieve
fuente
1
Esto solo filtra el carácter en la solicitud, no en la respuesta: ya estoy usando este
Hurda
@Hurda: con forceEncoding=trueél también se filtra la respuesta, pero no ayudaría en este caso.
axtavt
La mejor y más rápida respuesta hasta ahora. También ya estaba declarando y usando este filtro, pero con forceEncoding=false. Simplemente lo configuré falsey "charset = UTF-8" se agregó con éxito al Content-Typeencabezado.
Saad Benbouzid
2

si nada de lo anterior funcionó para usted, intente hacer solicitudes ajax en "POST" y no en "GET", eso funcionó muy bien para mí ... ninguno de los anteriores funcionó. También tengo el personaje EncodingFilter.

Mario
fuente
2
package com.your.package.spring.fix;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * @author Szilard_Jakab (JaKi)
 * Workaround for Spring 3 @ResponseBody issue - get incorrectly 
   encoded parameters     from the URL (in example @ JSON response)
 * Tested @ Spring 3.0.4
 */
public class RepairWrongUrlParamEncoding {
    private static String restoredParamToOriginal;

    /**
    * @param wrongUrlParam
    * @return Repaired url param (UTF-8 encoded)
    * @throws UnsupportedEncodingException
    */
    public static String repair(String wrongUrlParam) throws 
                                            UnsupportedEncodingException {
    /* First step: encode the incorrectly converted UTF-8 strings back to 
                  the original URL format
    */
    restoredParamToOriginal = URLEncoder.encode(wrongUrlParam, "ISO-8859-1");

    /* Second step: decode to UTF-8 again from the original one
    */
    return URLDecoder.decode(restoredParamToOriginal, "UTF-8");
    }
}

Después de haber intentado muchas soluciones para este problema ... lo pensé y funciona bien.

Szilard Jakab
fuente
2

La manera simple de resolver este problema en Spring 3.1.1 es que: agregue los siguientes códigos de configuración en servlet-context.xml

    <annotation-driven>
    <message-converters register-defaults="true">
    <beans:bean class="org.springframework.http.converter.StringHttpMessageConverter">
    <beans:property name="supportedMediaTypes">    
    <beans:value>text/plain;charset=UTF-8</beans:value>
    </beans:property>
    </beans:bean>
    </message-converters>
    </annotation-driven>

No es necesario anular ni implementar nada.

AdaroMu
fuente
2

si decide solucionar este problema mediante la siguiente configuración:

<mvc:annotation-driven>
  <mvc:message-converters register-defaults="true">
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
      <property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
    </bean>
  </mvc:message-converters>
</mvc:annotation-driven>

debe confirmar que solo debe haber una etiqueta mvc: basada en anotaciones en todo su archivo * .xml. de lo contrario, la configuración puede no ser efectiva.

Lich
fuente
1

De acuerdo con el enlace "Si no se especifica una codificación de caracteres, la especificación de Servlet requiere que se utilice una codificación de ISO-8859-1". Si está utilizando Spring 3.1 o posterior, use la siguiente configuración para configurar charset = UTF-8 cuerpo de respuesta
@RequestMapping (value = "su url de mapeo", produce = "text / plain; charset = UTF-8")

Ramesh Papaganti
fuente
0
public final class ConfigurableStringHttpMessageConverter extends AbstractHttpMessageConverter<String> {

    private Charset defaultCharset;

    public Charset getDefaultCharset() {
        return defaultCharset;
    }

    private final List<Charset> availableCharsets;

    private boolean writeAcceptCharset = true;

    public ConfigurableStringHttpMessageConverter() {
        super(new MediaType("text", "plain", StringHttpMessageConverter.DEFAULT_CHARSET), MediaType.ALL);
        defaultCharset = StringHttpMessageConverter.DEFAULT_CHARSET;
        this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
    }

    public ConfigurableStringHttpMessageConverter(String charsetName) {
        super(new MediaType("text", "plain", Charset.forName(charsetName)), MediaType.ALL);
        defaultCharset = Charset.forName(charsetName);
        this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
    }

    /**
     * Indicates whether the {@code Accept-Charset} should be written to any outgoing request.
     * <p>Default is {@code true}.
     */
    public void setWriteAcceptCharset(boolean writeAcceptCharset) {
        this.writeAcceptCharset = writeAcceptCharset;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return String.class.equals(clazz);
    }

    @Override
    protected String readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException {
        Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
        return FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset));
    }

    @Override
    protected Long getContentLength(String s, MediaType contentType) {
        Charset charset = getContentTypeCharset(contentType);
        try {
            return (long) s.getBytes(charset.name()).length;
        }
        catch (UnsupportedEncodingException ex) {
            // should not occur
            throw new InternalError(ex.getMessage());
        }
    }

    @Override
    protected void writeInternal(String s, HttpOutputMessage outputMessage) throws IOException {
        if (writeAcceptCharset) {
            outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
        }
        Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
        FileCopyUtils.copy(s, new OutputStreamWriter(outputMessage.getBody(), charset));
    }

    /**
     * Return the list of supported {@link Charset}.
     *
     * <p>By default, returns {@link Charset#availableCharsets()}. Can be overridden in subclasses.
     *
     * @return the list of accepted charsets
     */
    protected List<Charset> getAcceptedCharsets() {
        return this.availableCharsets;
    }

    private Charset getContentTypeCharset(MediaType contentType) {
        if (contentType != null && contentType.getCharSet() != null) {
            return contentType.getCharSet();
        }
        else {
            return defaultCharset;
        }
    }
}

Configuración de muestra:

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="messageConverters">
            <util:list>
                <bean class="ru.dz.mvk.util.ConfigurableStringHttpMessageConverter">
                    <constructor-arg index="0" value="UTF-8"/>
                </bean>
            </util:list>
        </property>
    </bean>
Igor Kostomin
fuente