origins origin example control application allow all json spring-mvc cors smartgwt same-origin-policy

json - origin - Smartgwt RestDataSource con SpringMVC y cliente cruzado



spring boot cors allow all origins (1)

Después de mucho trabajo, tengo una aplicación de servicios web back-end que funciona con Spring-RS, Spring MVC, controladores de Spring, y estos controladores usan al usuario Jackson dentro del marco Spring para convertir las respuestas a JSON.

Aquí se encuentra parte de WEB-INF / myproject-servlet.xml

<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="com.fasterxml.jackson.databind.ObjectMapper"> <property name="dateFormat"> <bean class="java.text.SimpleDateFormat"> <constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg> </bean> </property> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean id="jsonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes" value="application/json"/> </bean> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="jsonHttpMessageConverter" /> </list> </property> </bean>

¡Esta aplicación de servicios web funciona genial! Puedo implementar WAR en mi tomcat local y se implementa correctamente. Puedo probar el controlador unitario para asegurarme de que la URL sea correcta y que la aplicación web esté configurada correctamente en Spring. Puedo presionar la URL y recuperar los datos JSON exactamente como esperaba. La url es:

http://mylocalhost/myproject/invoices/invoiceId/1

devuelve 1 factura.

Ahora, estoy ejecutando una aplicación web SmartGWT, la versión gratuita, y tengo un controlador RestDataScource. He escrito muchas aplicaciones web SmartGWT anteriormente, y estas aplicaciones fueron todo incluido: entidades, dao''s, capa de servicio, controladores y fuentes de datos. Con esto, no hubo problemas de clientes cruzados, siempre que los controladores y las fuentes de datos estuvieran dentro de la misma aplicación. Y no estoy en contra de hacer eso de nuevo, pero quiero tratar de separarlos.

¡Hace poco vi que esto no funciona! Con mi aplicación web SmartGWT ejecutándose localmente en Jetty para el modo de desarrollo. La URL de inicio es:

http://mylocalhost:8888/myapp

Y cuando esto intenta llamar al back-end en

http://mylocalhost:8080/my-ws, then my listgrid gives me a warning message.

Si solo puedo agregar una línea: RPCManager.setAllowCrossDomainCalls (true); ¿Lo agrego dentro de mi RESTDataSource? ¿A dónde agrego esto? ¿Y realmente hará que todo funcione? ¿Hay algo más que deba agregar?

Entonces, estaba buscando en XJSONDataSource y vi que necesitaba hacer algunos cambios en mi RestDataSource para convertirlo a XJsonDataSource. Aquí hay buena información con otra publicación y sugirieron agregar:

// Where do I put this line? the controller or the datasource String callbackString = request.getParameter("callback"); // Where do I put this code? the controller or the datasource response.setContentType("text/X-JSON"); response.getWriter().write( callbackString + " ( " + JSONstring + " ) " ); response.setStatus(HttpServletResponse.SC_OK);

No estoy seguro de dónde va este código, entonces necesito ayuda adicional allí. En cuanto al controlador, esta es parte de lo que parece:

@RequestMapping(value = "/invoiceId", method = RequestMethod.GET, headers = "Accept=application/json") public @ResponseBody InvoiceDetailDTO getContactTypeByUserId(@RequestBody String invoiceNumber) { InvoiceDetailDTO invoiceDetailDto = invoiceService.getInvoiceDetail(invoiceNumber); // invoiceDetailDto is automatically converted to JSON by Spring return invoiceDetailDto; }

En el código de arriba con "solicitud" y "respuesta" tiene que ir al controlador, ¿cómo hago eso?

En última instancia, me encantaría usar mi RestDataSource y modificarla para que funcione de la manera que quiero, e ignorar cualquiera de estos problemas entre sitios. Si necesito usar XJSONDataSource, solo necesito algunos buenos ejemplos reales, y un ejemplo sobre cómo ajustar mis controladores si es necesario.

¡Gracias!


RPCManager.setAllowCrossDomainCalls(true); debe llamarse durante las primeras etapas de inicialización (por ejemplo, onModuleLoad() ).

getContactTypeByUserId podría tener que agregar Access-Control-Allow-Origin como encabezado de respuesta con el valor adecuado.
Verifique http://en.wikipedia.org/wiki/Cross-origin_resource_sharing .
Basado en http://forums.smartclient.com/showthread.php?t=15487 , SmartGWT debe manejar solicitudes de dominios cruzados por sí mismo.

En el peor de los casos, es posible que deba enviar una respuesta de estilo JSONP junto con los encabezados necesarios para que funcione.
En ese caso, probablemente sea mejor tener un método separado, similar al siguiente, para atender solicitudes de SmartGWT.
No he trabajado con XJSONDataSource, así que seguirlo es solo una guía.

// use a slightly different URI to distinguish from other controller method @RequestMapping(value = "/invoiceId/sgwt", method = RequestMethod.GET, headers = "Accept=application/json") public @ResponseBody String getContactTypeByUserIdForSgwt(@RequestBody String invoiceNumber, HttpServletRequest request, HttpServletResponse response) { // can reuse normal controller method InvoiceDetailDTO invoiceDetailDto = getContactTypeByUserId(invoiceNumber); // use jackson or other tool to convert invoiceDetailDto to a JSON string String JSONstring = convertToJson(invoiceDetailDto); // will have to check SmartGWT request to make sure actual parameter name that send the callback name String callbackString = request.getParameter("callback"); response.setContentType("text/X-JSON"); return callbackString + " ( " + JSONstring + " ) " ; }

Actualizar

Probablemente sea una buena idea limpiar el código (o comenzar desde cero / mínimo) debido a sobras de esfuerzos previos.

Hay tres fases para resolver esto:
1. Haz que SmartGWT funcione correctamente, sin usar el servicio
2. Haga que el servicio funcione correctamente con las solicitudes CORS
3. Cambie SmartGWT para usar el servicio

La fase 1 se debe utilizar para resolver cualquier problema del lado del cliente.
Pase a la fase 2, si el cliente está trabajando con el servicio cuando se implementa en el mismo host / dominio.

Fase 1
Para esto, es posible utilizar una URL de datos que proporcione una respuesta estática, como se explica en las respuestas de RestDataSource JSON .
Coloque la respuesta de muestra en un archivo similar a test.json y haga que esté accesible desde la aplicación web del cliente.
Mantenga el código de DataSource al mínimo y use setDataURL(); con la ubicación test.json .

test.json - cambia (y agrega si es necesario) los nombres y valores de los campos

{ response:{ status:0, startRow:0, endRow:3, totalRows:3, data:[ {field1:"value", field2:"value"}, {field1:"value", field2:"value"}, {field1:"value", field2:"value"}, ] } }

Fuente de datos

public class TestDS extends RestDataSource { private static TestDS instance = new TestDS(); public static TestDS getInstance() { return instance; } private TestDS() { setDataURL("data/test.json"); // => http://<client-app-host:port>/<context>/data/test.json setDataFormat(DSDataFormat.JSON); // setClientOnly(true); DataSourceTextField field1 = new DataSourceTextField("field1", "Field 1"); DataSourceTextField field2 = new DataSourceTextField("field2", "Field 2"); setFields(field1, field2); } }

Fase 2
Verifique las referencias para detalles adicionales.

Encabezados de una solicitud CORS de verificación previa fallida realizada desde una página alojada en localhost:8118 , y servicio alojado en localhost:7117 .
Falló debido a un puerto diferente. Fallará en un esquema diferente (https / ftp / file / etc.) O en otro host / dominio también.

Host: localhost:7117 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100101 Firefox/22.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Origin: http://localhost:8118 <= indicates origin to which access should be granted Access-Control-Request-Method: GET <= indicates the method that will be used in actual request Access-Control-Request-Headers: content-type <= indicates the headers that will be used in actual request Server: Apache-Coyote/1.1 Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS Content-Length: 0

Los pares de encabezado de solicitud / respuesta de una solicitud exitosa.

Host: localhost:7117 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100101 Firefox/22.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Origin: http://localhost:8118 Access-Control-Request-Method: GET Access-Control-Request-Headers: content-type Server: Apache-Coyote/1.1 Access-Control-Allow-Origin: http://localhost:8118 Access-Control-Allow-Methods: GET Access-Control-Allow-Headers: Content-Type Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS Content-Length: 0 Host: localhost:7117 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100101 Firefox/22.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/json Referer: http://localhost:8118/cors-test.html Origin: http://localhost:8118 Server: Apache-Coyote/1.1 Access-Control-Allow-Origin: * Content-Type: application/json Transfer-Encoding: chunked

Para soportar las solicitudes CORS, el backend de servicio debe responder correctamente a la solicitud de OPCIONES de verificación previa, no solo a la llamada de servicio.
Esto se puede hacer usando un ServletFilter.

<filter> <filter-name>corsfilter</filter-name> <filter-class>test.CorsFilter</filter-class> </filter> <filter-mapping> <filter-name>corsfilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> public class CorsFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) { response.addHeader("Access-Control-Allow-Origin", "http://localhost:8118"); // list of allowed methods, Access-Control-Request-Method must be a subset of this response.addHeader("Access-Control-Allow-Methods", "GET"); // list of allowed headers, Access-Control-Request-Headers must be a subset of this response.addHeader("Access-Control-Allow-Headers", "Content-Type, If-Modified-Since"); // pre-flight request cache timeout // response.addHeader("Access-Control-Max-Age", "60"); } filterChain.doFilter(request, response); } } @RequestMapping(method = RequestMethod.GET, value = "/values", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Map> getValues() { List<Map<String, Object>> values = getValues(); // handle actual data processing and return a list suitable for response SgwtResponse sgwtResponse = new SgwtResponse(); // A POJO with basic (public) attributes sgwtResponse.status = 0L; sgwtResponse.startRow = 0L; sgwtResponse.endRow = Long.valueOf(values.size()); sgwtResponse.totalRows = sgwtResponse.startRow + sgwtResponse.endRow; sgwtResponse.data = values; // java.util.List Map<String, SgwtResponse> jsonData = new HashMap<String, SgwtResponse>(); jsonData.put("response", sgwtResponse); HttpHeaders headers = new HttpHeaders(); headers.add("Access-Control-Allow-Origin", "*"); // required return new ResponseEntity<Map>(jsonData, headers, HttpStatus.OK); }

Una página de prueba simple que usa jQuery para recuperar una respuesta JSON usando XHR.
Cambie la URL y despliegue en la aplicación web del cliente para probar el servicio directamente, sin usar SmartGWT.

<!DOCTYPE html> <html> <head> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script> $(document).ready(function () { $("#retrieve").click(function () { $.ajax({ type: "GET", contentType: "application/json", url: "<URL-of-service>", dataType: "json", success: function (data, status, xhr) { $("#content").text(JSON.stringify(data, null, 2)); }, error: function (xhr, status, error) { $("#content").text("Unable to retrieve data"); } }); }); }); </script> </head> <body> <input type="button" id="retrieve" value="Retrieve"/> <div id="content"/> </body> </html>

If-Modified-Since header fue requerido en Access-Control-Allow-Headers para SmartGWT.
Use RPCManager.setAllowCrossDomainCalls(true); durante la inicialización SmartGWT para evitar la advertencia.

La mayoría de los navegadores modernos (compatibilidad con el navegador 1 ) y SmartGWT RestDataSource son compatibles con las solicitudes CORS.
Utilice XJSONDataSource solo cuando tenga que confiar en JSONP, debido a la incompatibilidad del navegador con las solicitudes CORS.

El envío de Access-Control-Allow-Origin: * para la solicitud previa al vuelo permitirá a cualquier sitio realizar llamadas de dominio cruzado al servicio, lo que podría suponer un problema de seguridad, además * no se puede utilizar en determinadas solicitudes CORS.
Un mejor enfoque es especificar el sitio exacto al que se permiten las solicitudes de dominios cruzados: Access-Control-Allow-Origin: http://www.foo.com .
Probablemente no sea necesario en este caso, pero compruebe los dominios de origen múltiple de Access-Control-Allow-Origin. si es necesario, para encontrar maneras de permitir que múltiples sitios hagan solicitudes de CORS.

Referencias
[1] https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS
[2] http://java-success.blogspot.com/2012/11/cors-and-jquery-with-spring-mvc-restful.html