0

经过大量工作,我有一个现有的后端 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,并且部署得很好。我可以对他的控制器进行单元测试,以确保 URL 正确,并且在 Spring 中正确配置了 Web 应用程序。我可以点击 URL 并完全按照我的预期获取 JSON 数据。网址是:

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

退回 1 张发票。

现在,我正在运行一个 SmartGWT Web 应用程序,即免费版本,并且我有一个 RestDataSource 控制器。我之前写过很多 SmartGWT web-apps,这些应用程序包罗万象:实体、dao、服务层、控制器和数据源。有了这个,只要控制器和数据源在同一个应用程序中,就完全没有跨客户端问题。而且我不反对再次这样做,但我想尝试将它们分开。

我最近才看到这不起作用!!!使用我的 SmartGWT Web 应用程序在 Jetty 本地运行以进行开发模式。起始网址是:

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

在上面的代码中,“请求”和“响应”必须进入控制器,我该怎么做?

最终,我很想只使用我的 RestDataSource 并对其进行调整以按照我想要的方式工作,而忽略任何这些跨站点问题。如果我确实需要使用 XJSONDataSource,我只需要一些真正好的示例,以及如何在需要时调整我的控制器的示例。

谢谢!

4

1 回答 1

1

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 应用程序访问。
将数据源代码保持在最低限度并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:8118localhost: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-SinceSmartGWT需要标头Access-Control-Allow-Headers
在 SmartGWT 初始化期间使用RPCManager.setAllowCrossDomainCalls(true);以避免警告。

大多数现代浏览器(浏览器兼容性1)和 SmartGWT RestDataSource 都支持 CORS 请求。
仅当您必须依赖 JSONP 时才使用 XJSONDataSource,因为浏览器与 CORS 请求不兼容。

发送Access-Control-Allow-Origin: *飞行前请求将允许任何站点对服务进行跨域调用,这可能会带来安全问题,*并且不能在某些 CORS 请求中使用。
更好的方法是指定允许跨域请求的确切站点 - Access-Control-Allow-Origin: http://www.foo.com.
在这种情况下可能不需要,但检查Access-Control-Allow-Origin Multiple Origin Domains? 如果需要,找到允许多个站点发出 CORS 请求的方法。

参考文献:
[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

于 2013-08-16T22:07:19.707 回答