使用Retrofit v1.9.0添加重复参数

7

这里有一个类似的问题在这里,但我的情况略有不同。

我正在尝试发起类似于以下请求:

http://www.example.com/abc?foo=def&foo=ghi&foo=jkl&bar=xyz

我有两个问题让事情变得困难。首先,重复的参数(多次设置“foo”值)阻止了使用QueryMap(我没有像数组一样以不同的查询字符串传递值的选项)。其次,我使用的查询参数有点动态,因此我无法真正使用Query并为给定参数名称提供值列表,因为在发出请求之前我不知道参数名称。
我试图升级的代码使用较旧版本的Retrofit,但是它具有QueryList的概念,该概念接受NameValuePairList以将查询参数作为名称(其值作为值)传递,并允许重复参数。我在Retrofit的源代码历史记录或网络上没有找到retrofit.http.QueryList的参考,因此我不确定这是否是当时的自定义添加。无论如何,我正在尝试找到复制该功能的最佳方法,因此任何帮助都将不胜感激!

为什么你想要传递重复的参数?如果是一个数组那还可以理解,但你的例子只是一个重复的参数。不同的服务器对此可能会有不同的响应,攻击者可以利用这一点来利用服务器。 - hidro
它被称为“参数污染”:https://www.owasp.org/index.php/Testing_for_HTTP_Parameter_pollution_%28OTG-INPVAL-004%29 - hidro
同意,我不会这样设置,每次看到它我都感到不安。不幸的是,这是服务器/API的设置方式,我无法控制更改它,所以我必须使用现有的内容来工作... - Niraj
4个回答

23
为了跟进,我使用QueryMap和一些技巧解决了这个问题。基本上,我将我的NameValuePair列表转换为一个HashMap,我会检查是否已经有该键,如果有,我就将相同键的新值附加到旧值上。所以实质上(key, value) 将变成 (key, value&key=value2)。这样当构建查询字符串时,我将获得所需的key=value&key=value2。为了使它工作,我需要自己处理值的编码,以便我包含在值中的额外&和=不被编码。
所以,HashMapList 构造如下:
public static HashMap<String, String> getPathMap(List<NameValuePair> params) {
    HashMap<String, String> paramMap = new HashMap<>();
    String paramValue;

    for (NameValuePair paramPair : params) {
        if (!TextUtils.isEmpty(paramPair.getName())) {
            try {
                if (paramMap.containsKey(paramPair.getName())) {
                    // Add the duplicate key and new value onto the previous value
                    // so (key, value) will now look like (key, value&key=value2)
                    // which is a hack to work with Retrofit's QueryMap
                    paramValue = paramMap.get(paramPair.getName());
                    paramValue += "&" + paramPair.getName() + "=" + URLEncoder.encode(String.valueOf(paramPair.getValue()), "UTF-8");
                } else {
                    // This is the first value, so directly map it
                    paramValue = URLEncoder.encode(String.valueOf(paramPair.getValue()), "UTF-8");
                }
                paramMap.put(paramPair.getName(), paramValue);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    }

    return paramMap;
}

那么我的请求看起来像这样:

@GET("/api/")
List<Repo> listRepos(@QueryMap(encodeValues=false) Map<String, String> params);

我的服务请求看起来像这样:

// Get the list of params for the service call
ArrayList<NameValuePair> paramList = getParams();

// Convert the list into a map and make the call with it
Map<String, String> params = getPathMap(paramList);
List<Repo> repos = service.listRepos(params);

我最初尝试使用Path解决方案,尝试手动构建查询字符串,但是在查询字符串中不允许使用替换块,因此我选择了这个QueryMap方案。希望这对遇到同样问题的其他人有所帮助!


谢谢,你的解决方案让我省了很多麻烦! - Mavamaarten

2

接口:

@GET()
Call<List<CustomDto>> getCustomData(@Url String url);

请求数据:

 String params = "custom?";
 for (Integer id : ids) {
      params += "id=" + id.toString() + "&";
 }
 params = params.substring(0, params.length() - 1);
 api.getCustomData(params).enqueue(callback);

1
这是对我有效的方法。我似乎无法让被接受的答案起作用。 - Corey Johnson

1

在Retrofit 1.9/2+中有一种更简单的方法来添加重复参数。

public interface AbcService {  
    @GET("/abc")
    Call<List<Abc>> getAbcData(@Query("foo") List<String> foos, @Query("bar") String bar);
}

其中foos可以这样定义:

List<String> foos = Arrays.asList("def", "ghi", "jkl");

更多细节可以查看link


-1

只需将列表传递给您的@Query:

@GET("/api")
Response shareItem(@Query("email")List<String> email);

我无法像我在这里提到的那样使用List: “其次,我使用的查询参数有点动态,因此我不能真正使用Query并为给定参数名称提供值列表,因为在发出请求之前我不知道参数名称。”但是,我通过在此页面上发布的替代解决方案解决了它。 - Niraj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接