Smartgwt RestDataSource与SpringMVC和跨客户端
问题内容:
经过大量工作后,我有一个现有的后端Web服务应用程序,该应用程序由Spring-RS,Spring
MVC,Spring控制器提供支持,并且这些控制器在Spring框架内使用用户Jackson来将响应转换为JSON。
这是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>
这个网络服务应用程序很棒!我可以将WAR部署到我的本地tomcat,并且可以很好地部署。我可以对控制器进行单元测试,以确保在Spring中正确配置了URL并正确配置了Web应用。我可以点击URL并完全按照我的预期获取JSON数据。网址是:
http://mylocalhost/myproject/invoices/invoiceId/1
返回1张发票。
现在,我正在运行一个SmartGWT网络应用程序(免费版本),并且我有一个RestDataScource控制器。我之前写过许多SmartGWT
Web应用程序,这些应用程序都是包含在内的:实体,Dao’s,服务层,控制器和数据源。这样,只要控制器和数据源都在同一个应用程序中,就根本没有跨客户端问题。我不反对再次这样做,但我想尝试将它们分开。
我最近才看到这行不通!!!我的SmartGWT网络应用程序在Jetty中本地运行,用于开发模式。起始URL为:
http://mylocalhost:8888/myapp
当这尝试调用后端时
http://mylocalhost:8080/my-ws, then my listgrid gives me a warning message.
如果我可以只添加一行:RPCManager.setAllowCrossDomainCalls(true);
是否可以在RESTDataSource中添加它?我要在哪里添加?真的会使一切正常工作吗?还有什么我需要补充的吗?
因此,我在查看XJSONDataSource时,发现需要对RestDataSource进行一些更改以将其转换为XJsonDataSource。这里有一些很好的信息,另外一篇文章,他们建议添加:
// 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);
我不确定此代码的去向,因此在那里我需要一些额外的帮助。就控制器而言,这是它看起来的一部分:
@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;
}
在上面带有“ request”和“ response”的代码中,必须进入控制器,我该如何处理?
最终,我希望只使用RestDataSource并对其进行调整以使其达到我想要的方式,而忽略任何这些跨站点问题。如果确实需要使用XJSONDataSource,则只需要一些真实的好例子,并提供一个有关如何根据需要调整控制器的例子。
谢谢!
问题答案:
RPCManager.setAllowCrossDomainCalls(true);
应该在初始化的早期阶段调用(例如-
onModuleLoad()
)。
getContactTypeByUserId
可能必须添加Access-Control-Allow-Origin
具有适当值的响应标头。
检查http://en.wikipedia.org/wiki/Cross-
origin_resource_sharing。
基于http://forums.smartclient.com/showthread.php?t=15487,SmartGWT应该自行处理跨域请求。
在最坏的情况下,您可能必须发送JSONP样式响应以及必需的标头才能使此工作正常进行。
在这种情况下,最好使用类似于以下方法的单独方法来服务SmartGWT请求。
我没有使用XJSONDataSource,因此以下只是一个指导原则。
// 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 + " ) " ;
}
更新资料
清理代码(或从头开始/最少)可能是个好主意,原因是先前的工作遗留了余下的时间。
解决此问题的过程分为三个阶段:
1. 在 不 使用服务的 情况下 使SmartGWT正常工作
2.在CORS请求下使服务正常工作
3.切换SmartGWT以使用服务
应该使用阶段1来解决所有客户端问题。
如果客户端在同一主机/域中部署时正在使用服务,请跳至阶段2。
阶段1
为此,可以使用提供静态响应的数据URL,如RestDataSource
JSON响应中所述。
将样本响应放置在类似于的文件中,test.json
并使其可从客户端Web应用程序访问。
将DataSource代码保持在最低限度,并setDataURL();
与test.json
位置一起使用。
test.json
-更改(并根据需要添加)字段名称和值
{
response:{
status:0,
startRow:0,
endRow:3,
totalRows:3,
data:[
{field1:"value", field2:"value"},
{field1:"value", field2:"value"},
{field1:"value", field2:"value"},
]
}
}
数据源
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);
}
}
阶段2
检查参考以获取更多详细信息。
从托管的页面和托管的服务发出的预检CORS请求 失败的标 头。 由于端口不同而失败。在不同的方案(https / ftp / file /
etc。)或不同的主机/域上也会失败。localhost:8118``localhost:7117
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
成功请求的请求/响应头对。
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
为了支持CORS请求,服务后端必须正确响应预检OPTIONS请求,而不仅仅是服务调用。
可以使用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);
}
一个简单的测试页,使用jQuery使用XHR检索JSON响应。
更改URL并部署在客户端Web应用程序中以直接测试服务,而无需使用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
标头Access-Control-Allow-Headers
对于SmartGWT
是必需的。在SmartGWT初始化期间
使用RPCManager.setAllowCrossDomainCalls(true);
以避免警告。
大多数现代浏览器(浏览器兼容性1)和SmartGWT RestDataSource 支持CORS请求。
由于浏览器与CORS请求不兼容,仅在必须依赖JSONP时才使用XJSONDataSource。
发送Access-Control-Allow-Origin: *
飞行前请求将允许任何站点对服务进行跨域调用,这可能造成安全问题,*
并且不能在某些CORS请求中使用。
更好的方法是指定允许跨域请求的确切站点- Access-Control-Allow-Origin: http://www.foo.com
。
在这种情况下可能不需要,但是请检查“访问控制允许起源多源域”吗?如果需要,可以找到允许多个站点发出CORS请求的方法。
参考文献:
[1] https://developer.mozilla.org/zh-
CN/docs/HTTP/Access_control_CORS
[2] http://java-success.blogspot.com/2012/11/cors-and-jquery-with-spring
-mvc-restful.html