如何通过Feign(ApacheHttpClient)向OData发送含空格的请求参数
OData请求含$filter参数时Feign调用报500错误问题
问题描述
需要通过OData发送带$filter参数的GET请求,参数内容为$filter=cast(Договор_Key,'Catalog_Договоры') eq guid'<UUID>',该参数包含两个空格。Feign日志输出的URL在Firefox中可正常请求,但Java应用调用时抛出500内部服务器错误。
报错信息
feign.FeignException$InternalServerError: [500 Internal server error] during [GET] to [https://mysite/odata/standard.odata/Document_%D0%A1%D1%87%D0%B5%D1%82%D0%A4%D0%B0%D0%BA%D1%82%D1%83%D1%80%D0%B0%D0%92%D1%8B%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9?%24format=json%3Bodata%3Dnometadata&%24inlinecount=allpages&%24filter=cast%28%D0%94%D0%BE%D0%B3%D0%BE%D0%B2%D0%BE%D1%80_Key%2C%27Catalog_%D0%94%D0%BE%D0%B3%D0%BE%D0%B2%D0%BE%D1%80%D1%8B%27%29%20eq%20guid%27e1004688-92d7-11ed-8f65-005056992589%27] [OdataClient#getTaxInvoiceByContractId(String,String,String,String)]: [{"odata.error": {"code": "-1","message": {"lang": "ru","value": "{(3, 2)}: Операция не разрешена в предложении \"ГДЕ\"\n<<?>>CAST( sourceAlias.Договор AS Catalog.Договоры )"}}}]
使用技术栈
SpringBoot 3.0.5spring-cloud-starter-openfeign 4.0.1io.github.openfeign.feign-httpclient 12.2
Feign配置类(用于禁用SSL)
import feign.Feign; import feign.Logger; import feign.httpclient.ApacheHttpClient; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.X509Certificate; @Configuration public class FeignClientConfig { @Bean public ApacheHttpClient apacheFeignClient() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} }}, new SecureRandom()); HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; return new ApacheHttpClient(HttpClientBuilder.create().setSSLContext(sslContext).setSSLHostnameVerifier(hostnameVerifier).build()); } @Bean public Feign.Builder feignBuilder() throws Exception { return Feign.builder().client(apacheFeignClient()); } @Bean Logger.Level feignLoggerLevel() { return Logger.Level.BASIC; } }
Feign客户端类
package ru.rcitsakha.bitrixapi.client; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import ru.rcitsakha.bitrixapi.config.FeignClientConfig; @FeignClient(name = "odataClient", url = "${odata.url}", configuration = FeignClientConfig.class) public interface OdataClient { // 其他含西里尔字母但无空格的端点可正常工作 // 这些含空格的端点无法工作 @GetMapping("/Document_СчетФактураВыданный") String getTaxInvoiceByContractId(@RequestHeader("Authorization") String token, @RequestParam("$format") String format, @RequestParam("$inlinecount") String inlineCount, @RequestParam("$filter") String contractId); }
生成$filter参数的Repository类
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Repository; import ru.rcitsakha.bitrixapi.client.OdataClient; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @Repository @RequiredArgsConstructor @Log4j2 public class OdataRepository { private final ObjectMapper mapper; private final OdataClient odataClient; @Value("${odata.responseFormat}") private String responseFormat; @Value("${odata.inlineCount}") private String inlineCount; @Value("${odata.auth_token}") private String token; public JsonNode getTaxInvoiceByContractId(String contractId) { JsonNode node = null; String filterRequest = "cast(Договор_Key,'Catalog_Договоры') eq guid'" + contractId + "'"; String responseData = odataClient.getTaxInvoiceByContractId(token, responseFormat, inlineCount, filterRequest); try { node = mapper.readTree(responseData); } catch (JsonProcessingException e) { log.error("Object mapper could not read tree"); e.printStackTrace(); } return node; } }
已尝试但无效的方法
- 使用
URLEncoder.encode对参数进行编码 - 将参数中的空格手动替换为
%20 - 通过
RequestInterceptor拦截请求并将空格替换为%20 - 使用
@QueryMap替代@RequestParam("$filter") String contractId传递参数
内容的提问来源于stack exchange,提问作者Zakhar Borisov
相关产品推荐
相关产品推荐

