【问题标题】:Special characters in @QueryParam name in Spring feign clientSpring feign客户端中@QueryParam名称中的特殊字符
【发布时间】:2019-10-14 16:34:19
【问题描述】:

我们的一个外部 API 使用带有特殊字符的查询参数名称(不要问我为什么,我不知道)。我的这个 API 的 feign 客户端的方法是这样声明的:

@GetMapping("/v1/user/{userId}/orders")
List<Order> getOrders(
    @PathVariable("userId") String userId,
    @RequestParam("page[number]") Integer pageNumber,
    @RequestParam("page[size]") Integer pageSize);

正如我所提到的,请求参数包含特殊字符 []。 我正在使用 Spock 进行测试,我想像这样设置 Wiremock 存根:

wiremock.stubFor(
  get(urlPathMatching('/v1/users/' + userId + '/orders'))
    .withQueryParam("page[number]", new EqualToPattern("1"))
    .withQueryParam("page[size]", new AnythingPattern())
    .willReturn(
      status(200)
        .withHeader("Content-type", "application/json")
        .withBody("""[]""")
    ))

但我明白了:

--------------------------------------------------------------------------------------------------
| Closest stub                        | Request                                                  |
--------------------------------------------------------------------------------------------------
                                      |
GET                                   | GET
/v1/users/123/orders                  | /v1/users/123/orders?page%5Bnumber%5D=%7Bpage%5Bnumber%5
                                      | D%7D&page%5Bsize%5D=%7Bpage%5Bsize%5D%7D
                                      |
Query: page[number] = 1               |                                 <<<<< Query is not present
Query: page[size] [anything] (always) |                                 <<<<< Query is not present
                                      |
--------------------------------------------------------------------------------------------------

经过大量试验和错误,我想出了一个解决方案,在 feign 客户端方法中使用 @PathVariables 而不是 @RequestParams:

@GetMapping("/v1/users/{userId}/orders?page[number]={pageNumber}&page[size]={pageSize}")
List<Order> getOrders(
    @PathVariable("userId") String userId,
    @PathVariable("pageNumber") Integer pageNumber,
    @PathVariable("pageSize") Integer pageSize);

并在 Wiremock 中编码所有查询参数

wiremock.stubFor(
    get(urlPathMatching('/v1/users/' + userId + '/orders'))
    .withQueryParam("page%5Bnumber%5D", new EqualToPattern("1"))
    .withQueryParam("page%5Bsize%5D", new AnythingPattern())
    .willReturn(
        status(200)
        .withHeader("Content-type", "application/json")
        .withBody("""[]""")
    ))

然后就可以了。但它看起来像是一种黑客行为。使用可选的查询参数也是有问题的。

有没有办法将@RequestParams 与特殊字符一起使用? 它看起来像 Spring 中的错误?

同时我会尝试调试它以了解问题出在哪里。

【问题讨论】:

    标签: spring query-parameters wiremock feign


    【解决方案1】:

    首先,这不是 Spring 中的错误。这些类似弹簧的注解(spring-web 中的@RequestParam@RequestMapping 等)的主要目标是在服务器端的 Spring MVC 控制器中使用它们,而不是为请求创建 url。

    对于 Feign 客户端,还有另一个库 spring-cloud-openfeign,它允许您将它们放在 Feign 客户端中进行请求映射,而不是原始的 Feign 注释(如 @RequestLine@Param 等)。 SpringMvcContract 负责,it is used by default in Spring applications。但这只是为了方便这群使用 Feign 和 Spring 的特定开发人员,因此没有理由更改这些注释的签名或添加一些新设置。

    关于所描述的问题 - 即使您使用原始 Feign 注释进行请求映射,似乎也没有任何“官方”方法来禁用 url 编码 - 目前(版本 10.7.4 ),您只能对斜杠执行此操作(例如,检查 this issue)。

    我进行了一些进一步的调查并得出以下几点:

    1. 在构建请求查询模板时,您不能手动设置编码选项,因为它只是硬编码(查看UriTemplateQueryTemplate 源代码)。
    2. 您实际上可以稍后通过编写自己的RequestInterceptor(通过反射,f.e.)来修复构造的 url,但这实际上是没有意义的,因为after applying all interceptors the request is built 并且目前它再次隐式编码括号(check what's发生在幕后,基本上它使用queryTemplate.toString(),它反过来在使用UriUtils类的方法时对括号进行编码)
    3. 请求创建后,由Client开始执行,我实际上在这一步修复了url。我从标准 Client.Default 继承了一个类,注册了它而不是原来的自动配置的类,然后我在实际调用客户端之前完成了我的工作(ofc 可以为不同的客户端做同样的事情,例如,对于 @ 987654339@):
    import feign.Client;
    import feign.Request;
    import feign.Response;
    import org.springframework.stereotype.Component;
    import org.springframework.util.ReflectionUtils;
    
    import java.io.IOException;
    import java.lang.reflect.Field;
    
    @Component
    public class MyClient implements Client {
    
        private Client delegate;
    
        public MyClient() {
            this.delegate = new Client.Default(null, null);
        }
    
        @Override
        public Response execute(Request request, Request.Options options) throws IOException {
            fixRequestUrlIfPossible(request);
            return delegate.execute(request, options);
        }
    
        private void fixRequestUrlIfPossible(Request request) {
            try {
                Field urlField = ReflectionUtils.findField(Request.class, "url");
                urlField.setAccessible(true);
    
                String encodedUrl = (String) urlField.get(request);
                String url = encodedUrl.replace("%5B", "[").replace("%5D", "]");
                urlField.set(request, url);
            } catch (Exception e) {
                /*NOP*/
            }
        }
    }
    

    但由于反射容易出错(可能会在以后版本的 Feign 中停止工作)。

    所以,这取决于您 - 如果您不害怕做这种事情,请使用这种方法。我其实更喜欢你的版本。主要问题是:您的真实服务器是否能够处理带有编码括号的请求?在我看来,Wiremock 的问题不太重要。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-01
      • 1970-01-01
      • 2020-12-20
      • 2020-01-27
      • 1970-01-01
      • 2018-04-19
      • 2016-07-01
      • 1970-01-01
      相关资源
      最近更新 更多