在Retrofit中添加头部参数

79

我正在尝试调用一个需要传递API密钥的API。

我的服务调用使用HttpURLConnection已经完美运行。

url = new URL("https://developers.zomato.com/api/v2.1/search?entity_id=3&entity_type=city&q=" + params[0]);
urlConnection = (HttpURLConnection) url.openConnection();

urlConnection.setRequestProperty("user-key","9900a9720d31dfd5fdb4352700c");

if (urlConnection.getResponseCode() != 200) {
    Toast.makeText(con, "url connection response not 200 | " + urlConnection.getResponseCode(), Toast.LENGTH_SHORT).show();
    Log.d("jamian", "url connection response not 200 | " + urlConnection.getResponseCode());
    throw new RuntimeException("Failed : HTTP error code : " + urlConnection.getResponseCode());
}

然而,我不确定这如何与Retrofit配合使用,因为我的调用总是失败。 这是我用于相同服务调用的代码。

@GET("search")
Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query,@Header("Accept") String accept, @Header("user-key") String userkey);

我正在使用这个来调用它

Call<String> call = endpoint.getRestaurantsBySearch("3","city","mumbai","application/json","9900a9720d31dfd5fdb4352700c");
所有这些调用都进入了RetroFit中的OnFailure方法。 如果我不发送HeaderParameters,它会因为403而成功,因为我显然需要在某个地方传递API密钥,但我无法弄清楚如何传递。
@GET("search")
Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query);
我在OnFailure中收到的错误是什么。
java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 2 path $

为您的Retrofit实例添加一个日志拦截器,因为您的调用参数错误。 - bvarga
编译 'com.squareup.okhttp3:logging-interceptor:3.0.0' - jamian
我使用了上述依赖项。有什么想法我该从哪里开始? - jamian
1
@jamian 这取决于你使用的 okhttp 版本是否相同。 - Kaushik
6个回答

118

尝试使用此类型的标题,适用于 Retrofit 1.9 和 2.0 版本,用于 JSON 内容类型。

@Headers({"Accept: application/json"})
@POST("user/classes")
Call<playlist> addToPlaylist(@Body PlaylistParm parm);

您可以添加许多其他标题,例如:

@Headers({
        "Accept: application/json",
        "User-Agent: Your-App-Name",
        "Cache-Control: max-age=640000"
    })

动态地向标题中添加内容:

@POST("user/classes")
Call<ResponseModel> addToPlaylist(@Header("Content-Type") String content_type, @Body RequestModel req);

调用你的方法,即:

mAPI.addToPlayList("application/json", playListParam);

或者

想要每次都通过,那么使用HTTP拦截器创建一个HttpClient对象:

OkHttpClient httpClient = new OkHttpClient();
        httpClient.networkInterceptors().add(new Interceptor() {
            @Override
            public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
                Request.Builder requestBuilder = chain.request().newBuilder();
                requestBuilder.header("Content-Type", "application/json");
                return chain.proceed(requestBuilder.build());
            }
        });

然后将其添加到Retrofit对象中。

Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL).client(httpClient).build();

如果您使用的是 Kotlin,请删除 { }。否则它将无法正常工作。


如何在运行时动态添加值(例如,我们的示例中的640000)? - M. Usman Khan
你需要将它添加到OkHttp客户端中。 - Avinash Verma
1
我在Kotlin中犯了使用花括号的错误。感谢更新! - Uncaught Exception

54
你可以使用以下内容。
 @Headers("user-key: 9900a9720d31dfd5fdb4352700c")
 @GET("api/v2.1/search")
 Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query);

 Call<String> call = endpoint.getRestaurantsBySearch("3","city","cafes");

以上内容基于Zomato API,文档位于:

https://developers.zomato.com/documentation#!/restaurant/search

需要注意的是端点的变化api/v2.1/search和头信息 @Headers("user-key: 9900a9720d31dfd5fdb4352700c")

还要检查你的 base URL .baseUrl("https://developers.zomato.com/")

我使用生成的 API 密钥尝试了上述内容,并且它可以工作,我的查询词是 咖啡馆,正如 Zomato 文档所建议的。

注意:希望您有以下内容

 .addConverterFactory(ScalarsConverterFactory.create()) // for string conversion
 .build();

并且在 build.gradle 文件中添加以下内容:

compile group: 'com.squareup.retrofit2', name: 'converter-scalars', version: '2.2.0'

编辑:

您还可以通过以下方式传递动态值的头信息

@GET("api/v2.1/search")
Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query,@Header("user-key") String userkey);

并且

Call<String> call = endpoint.getRestaurantsBySearch("3","city","cafes","9900a9720d31dfd5fdb4352700c");

9

我尝试了几次后找到了答案。

错误信息:

java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 2 path $

由于无法解析json,导致出现了问题。

在方法调用中,我传递的是字符串而不是POJO类。

@Headers("user-key: 9900a9720d31dfd5fdb4352700c")
@GET("api/v2.1/search")
Call<String> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query);

我应该传递Call<Data>类型而不是Call<String>,其中Data是指普通Java对象(POJO)类。

类似这样:

@Headers("user-key: 9900a9720d31dfd5fdb4352700c")
@GET("api/v2.1/search")
Call<Data> getRestaurantsBySearch(@Query("entity_id") String entity_id, @Query("entity_type") String entity_type, @Query("q") String query);

这取决于您使用的转换器类型。使用Gson转换器来解析json,或者使用标量转换器来处理字符串。由于您没有提及其中任何一个,我在我的答案中加了一条注释。Call<String>是可能的。 - Raghunandan
使用 Kotlin 时,我遇到了错误 "注解参数必须是编译时常量"。 - Peter Chaula

3

让我也稍微评论一下(实际上是很多)关于在 Kotlin 中添加头部,重点是依赖注入。

最好的方法是在同一个 di 方法中提供 OkHttpClientHttpLoggingInterceptor,利用方便的 Kotlin 作用域函数,在这种情况下使用 alsoapply

我们将需要这些依赖项:Retrofit (2.9)OkHttpClient - 此示例使用 Kotlin DSL,但在 Groovy 中应该更或多或少相同。当然,如果您正在使用依赖注入,则还需要其他依赖项,如 Hilt

implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.7")
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.7")

下一步是创建@Provide函数,该函数返回OkHttpClient
@Provides
@Singleton
fun provideOkHttpClient():OkHttpClient { ...}

拦截器的背景理论非常重要;要使用拦截器,您需要创建一个实现Interceptor接口覆盖intercept()方法的类。

intercept()接收一个Interceptor.Chain对象 - 代表当前请求,并允许您通过调用proceed()方法继续请求,或通过抛出异常取消请求。 intercept()覆盖函数返回一个Response对象,这正是chain.proceed(request)返回的内容。

class MyInterceptor : Interceptor {
    //throw an exception to cancel request
    @Throws(IOException::class)

    override fun intercept(chain: Interceptor.Chain): Response {

        val request = chain.request()
                .newBuilder() // returns Request.Builder
                .addHeader("Header_1", "value_1")
                .build()

        //proceed with the request
        return chain.proceed(request)
    }

}

感谢 Kotlin 中的匿名函数语法和构建器模式,我们可以跳过上述理论步骤,开始构建具有 addInterceptor() 函数的 OkHttpClient。
fun provideOkHttpClient(): OkHttpClient {
    
            //build client
            return OkHttpClient.Builder()
    
                    //create anonymous interceptor in the lambda and override intercept
                    // passing in Interceptor.Chain parameter
                    .addInterceptor { chain ->
                      //return response
                        chain.proceed(
                              //create request
                                chain.request()
                                        .newBuilder()
                                       //add headers to the request builder
                                        .also {
                                            it.addHeader("Header_1", "value_1")
                                            it.addHeader("Header_2", "value_2")
                                        }
                                        .build()
                        )
                    }
                    .also { okHttpClient ->.... }

在以上代码中,addInterceptor() 开启一个 lambda 表达式,我们通过匿名方式覆盖了 intercept() 方法并传入了一个 chain 参数。
我们使用 chain.proceed(request) 来返回一个 Response。当构建要传递给 chain.proceed()request 时,我们修改实际请求以添加头部信息
您还可以继续构建 OkHttpClient 并添加超时等功能。
.also { okHttpClient ->

                    okHttpClient.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
                    okHttpClient.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)

                    if (BuildConfig.DEBUG) {
                        val httpLoggingInterceptor = HttpLoggingInterceptor().apply {

                            level = HttpLoggingInterceptor.Level.BODY
                        }

                        okHttpClient.addInterceptor(httpLoggingInterceptor)
                    }
                }
                .build()

这是最终代码。

@Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {

        //build client
        return OkHttpClient.Builder()

                //create anonymous interceptor in the lambda and override intercept
                // passing in Interceptor.Chain parameter
                .addInterceptor { chain ->

                    //return response
                    chain.proceed(
                            //create request
                            chain.request()
                                    .newBuilder()

                                    //add headers to the request builder
                                    .also {
                                        it.addHeader("Header_1", "value_1")
                                        it.addHeader("Header_2", "value_2")
                                    }.build()

                    )
                }
                //add timeouts, logging
                .also { okHttpClient ->

                    okHttpClient.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
                    okHttpClient.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
                        //log if in debugging phase
                    if (BuildConfig.DEBUG) {
                        val httpLoggingInterceptor = HttpLoggingInterceptor().apply {

                            level = HttpLoggingInterceptor.Level.BODY
                        }

                        okHttpClient.addInterceptor(httpLoggingInterceptor)
                    }
                }
                .build()


    }

这是我在StackOverflow上发布的最长帖子,对不起大家。


0
据我所见,您正在以错误的方式传递数据。 您的方法getRestaurantsBySearch将最后两个参数作为标题字段接受,即acceptuser-key。但是在调用该方法时,您首先传递了标题。 请按照getRestaurantsBySearch方法签名中声明的方式传递数据。

你能否在改变参数位置后,直接发布你尝试过的代码? - Nitin Joshi

0

enter image description here

请查看响应。它清楚地显示您提供的 API 密钥是错误的。首先获取正确的 API 密钥,然后调用请求即可正常工作。

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