Micronaut HttpClient自动编码URL中&与=符号问题求助
问题描述
简述
查看HttpClient日志后,发现部分字符被URL编码,但不希望出现这种情况。
详述
调用的URL由<base url>+<path>+<apitoken>+<query>+<channel>组成。其中query包含<tcins>和<store info>,这两部分中包含=和&,但这些字符在URL中被不一致地编码为%26和%3D。例如,API密钥部分?key=...正常,但后续的&tcins=82691535...被编码为%26tcins%3D82691535...。
Get方法定义如下:
@Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&{tcins}&{targetStore}&channel=WEB") HttpResponse<List<?>> fetchProducts(String tcins, String targetStore);
日志片段
11:58:04.258 [default-nioEventLoopGroup-1-1] DEBUG io.micronaut.http.client.netty.DefaultHttpClient - Sending HTTP GET to https://redsky.target.com/redsky_aggregations/v1/web/product_summary_with_fulfillment_v1?key=9f36aeafbe60771e321a7cc95a78140772ab3e96&%26tcins%3D82691535&store_id%3D1750%26zip%3D84014-1752%26state%3DUT%26latitude%3D40.91825%26longitude%3D-111.887%26scheduled_delivery_store_id%3D1750%26required_store_id%3D1750%26has_required_store_id%3Dtrue&channel=WEB 11:58:04.265 [default-nioEventLoopGroup-1-1] TRACE io.micronaut.http.client.netty.DefaultHttpClient - User-Agent: Micronaut HTTP Client 11:58:04.265 [default-nioEventLoopGroup-1-1] TRACE io.micronaut.http.client.netty.DefaultHttpClient - Accept: application/vnd.github.v3+json, application/json 11:58:04.266 [default-nioEventLoopGroup-1-1] TRACE io.micronaut.http.client.netty.DefaultHttpClient - host: redsky.target.com
控制器代码
package com.github.peanutbutter.unicorn.tarvester; import com.github.peanutbutter.unicorn.tarvester.model.TargetStore; import com.github.peanutbutter.unicorn.tarvester.model.TcinList; import io.micronaut.core.async.annotation.SingleResult; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import java.util.List; @Controller("products") public class TarvesterController { final TarvesterClient tarvesterClient; public TarvesterController(TarvesterClient tarvesterClient) { this.tarvesterClient = tarvesterClient; } @Get("by-tcin") @SingleResult HttpResponse<List<?>> fetchProducts(TcinList tcinlist, TargetStore targetStore) { return tarvesterClient.fetchProducts(tcinlist.toString(), targetStore.toString()); } }
客户端代码
package com.github.peanutbutter.unicorn.tarvester; import com.github.peanutbutter.unicorn.tarvester.model.Product; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Header; import io.micronaut.http.client.annotation.Client; import java.util.List; import static io.micronaut.http.HttpHeaders.ACCEPT; import static io.micronaut.http.HttpHeaders.USER_AGENT; @Client(id = "redsky-api") @Header(name = USER_AGENT, value = "Micronaut HTTP Client") @Header(name = ACCEPT, value = "application/vnd.github.v3+json, application/json") public interface TarvesterClient { @Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&{targetStore}&{tcins}&channel=WEB") HttpResponse<List<?>> fetchProducts(String tcins, String targetStore); }
ToString方法
//tcins片段 @Override public String toString() { return "&tcins=" + Arrays.stream(id) .mapToObj(String::valueOf) .collect(Collectors.joining("%2C")); } ... // TargetStore片段 @Override public String toString() { return String.join("&", new String[]{ "store_id=" + locationId, "zip=" + postalCode, "state=" + region, "latitude=" + latitude, "longitude=" + longitude, "scheduled_delivery_store_id=" + locationId, "required_store_id=" + locationId, "has_required_store_id=true" }); }
application.properties配置
micronaut.application.name=Tarvester micronaut.application.default-charset=UTF-8 micronaut.http.services.redsky-api.url=https://redsky.target.com/ micronaut.http.services.redsky-api.path=redsky_aggregations/v1/web redsky-api.token=9f36aeafbe60771e321a7cc95a78140772ab3e96
已尝试的解决方法
- 在控制器和@Get注解中添加
produces = MediaType.PLAINTEXT,未解决URL编码问题。 - 在客户端或控制器中转换Java对象为字符串,无效。
- 未找到HttpClient禁用URL编码的配置参数。
- 修改默认应用字符集为UTF-8,无效。
怀疑这是一个Bug,因为URL中的编码行为不一致。
解决方案
问题根源在于你用{tcins}和{targetStore}这类路径参数的方式传递查询参数,Micronaut会自动对这类参数内容做URL编码,而你又在toString()里手动拼接了&和=,导致这些字符被二次编码。
方案1:利用Micronaut查询参数绑定(推荐)
放弃手动拼接字符串,直接让框架处理查询参数:
@Client(id = "redsky-api") @Header(name = USER_AGENT, value = "Micronaut HTTP Client") @Header(name = ACCEPT, value = "application/vnd.github.v3+json, application/json") public interface TarvesterClient { @Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&channel=WEB") HttpResponse<List<?>> fetchProducts( @QueryValue("tcins") String tcins, @QueryValue("store_id") String storeId, @QueryValue("zip") String zip, @QueryValue("state") String state, @QueryValue("latitude") String latitude, @QueryValue("longitude") String longitude, @QueryValue("scheduled_delivery_store_id") String scheduledDeliveryStoreId, @QueryValue("required_store_id") String requiredStoreId, @QueryValue("has_required_store_id") boolean hasRequiredStoreId ); }
控制器调用时直接传入对应字段值即可,框架会自动拼接合法的查询参数,无需手动处理&和=。
方案2:用DTO对象绑定查询参数
如果参数较多,可以封装成DTO类:
public class StoreQueryParams { private String store_id; private String zip; private String state; private String latitude; private String longitude; private String scheduled_delivery_store_id; private String required_store_id; private boolean has_required_store_id; // Getters & Setters }
修改客户端接口:
@Client(id = "redsky-api") @Header(name = USER_AGENT, value = "Micronaut HTTP Client") @Header(name = ACCEPT, value = "application/vnd.github.v3+json, application/json") public interface TarvesterClient { @Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&channel=WEB") HttpResponse<List<?>> fetchProducts( @QueryValue("tcins") String tcins, @QueryValue StoreQueryParams storeParams ); }
控制器中把TargetStore转换成StoreQueryParams对象传入即可。
方案3:禁用参数编码(不推荐)
如果坚持手动拼接字符串,可以通过@QueryValue的encode属性关闭编码:
@Client(id = "redsky-api") @Header(name = USER_AGENT, value = "Micronaut HTTP Client") @Header(name = ACCEPT, value = "application/vnd.github.v3+json, application/json") public interface TarvesterClient { @Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&channel=WEB") HttpResponse<List<?>> fetchProducts( @QueryValue(value = "", encode = false) String tcins, @QueryValue(value = "", encode = false) String targetStore ); }
注意:此方式需确保手动拼接的内容是合法URL格式,避免出现安全问题。
内容的提问来源于stack exchange,提问作者Jonathan-Zollinger
相关产品推荐
相关产品推荐

