Retrofit2 authentication error to IBM's Speech to Text

别等时光非礼了梦想. 提交于 2020-01-06 08:12:48

问题


I am trying to access IBM's Speech to Text service without using the library. I am using Retrofit with GSON.

The issue is in the authentication, which apparently does not occur correctly, returning code 401. From the official documentation, the HTTP request should come in this format

curl -X POST -u "apikey:{apikey}" \
--header "Content-Type: audio/flac" \
--data-binary @{path_to_file}audio-file.flac \
"{url}/v1/recognize"

When I test the curl command with my credentials, the service works fine.

This is the interface I'm using

interface SpeechToTextApi {

    @Multipart
    @POST("v1/recognize")
    fun speechToText(
        @Header("Authorization") authKey: String,
        @Part("file") filename: RequestBody,
        @Part voiceFile: MultipartBody.Part
    ): Call<List<SpeechToText>>
}

where I have the following data classes

data class SpeechToText(val results: List<SttResult>)
data class SttResult(val alternatives: List<RecognitionResult>, val final: Boolean)
data class RecognitionResult(val confidence: Float, val transcript: String)

and this is how I set up Retrofit

private val retrofit = Retrofit.Builder()
        .baseUrl(STT_BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

private val service = retrofit.create(SpeechToTextApi::class.java)

while calling the actual service looks like this

val requestFile = RequestBody.create(MediaType.parse("audio/mp3"), file.name)
val body = MultipartBody.Part.createFormData("file", file.name, requestFile)
service
    .speechToText(getString(R.string.stt_iam_api_key), requestFile, body)
    .enqueue(object: Callback<List<SpeechToText>> {
    override fun onResponse(call: Call<List<SpeechToText>>, response: Response<List<SpeechToText>>) {
        val listOfStts = response.body()
        Log.d(TAG, "Response code: ${response.code()}")
        if (listOfStts != null) {
            for (stt in listOfStts) {
                for (res in stt.results) {
                    Log.d(TAG, "Final value: ${res.final}")
                    for (alt in res.alternatives) {
                        Log.d(TAG, "Alternative confidence: ${alt.confidence}\nTranscript: ${alt.transcript}")
                        Toast.makeText(this@MainActivity, alt.transcript, Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }

    override fun onFailure(call: Call<List<SpeechToText>>, t: Throwable) {
        Log.d(TAG, "Error: ${t.message}")
        t.printStackTrace()
    }
})

Recordings are MP3 files, for which I am sure they are stored correctly and accessible. I have replaced audio/flac with audio/mp3 as well.

Issue seems to be in the way authentication works. Prior to the code I have shown above, I've used

private val retrofit = Retrofit.Builder()
        .baseUrl(STT_BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .client(OkHttpClient.Builder()
            .addInterceptor { chain ->
                val request = chain.request()
                val headers = request
                    .headers()
                    .newBuilder()
                    .add("Authorization", getString(R.string.stt_iam_api_key))
                    .build()
                val finalRequest = request.newBuilder().headers(headers).build()
                chain.proceed(finalRequest)
            }
            .build())
    .build()

but the same response code 401 persisted. Of course, the interface method lacked the @Header parameter.

Any sort of help is much appreciated.


回答1:


I am kind of saddened by the fact nobody was able to solve this one sooner, but here's the solution I came across by accident when working on a different project altogether.

As you can see from the curl command, authentication comes in the form of username: password pattern, in this case, username being apikey string and password is your API key.

So the way you should tackle this is by building your Retrofit instance this way:

fun init(token: String) {
    //Set logging interceptor to BODY and redact Authorization header
    interceptor.level = HttpLoggingInterceptor.Level.BODY
    interceptor.redactHeader("Authorization")

    //Build OkHttp client with logging and token interceptors
    val okhttp = OkHttpClient().newBuilder()
        .addInterceptor(interceptor)
        .addInterceptor(TokenInterceptor(token))
        .build()

    //Set field naming policy for Gson
    val gsonBuilder = GsonBuilder()
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)

    //Build Retrofit instance
    retrofit = Retrofit.Builder()
        .baseUrl(IBM_BASE_URL)
        .addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
        .client(okhttp)
        .build()
}

and create this custom interceptor

class TokenInterceptor constructor(private val token: String) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val original = chain.request()
        val requestBuilder = original
            .newBuilder()
            .addHeader("Authorization", Credentials.basic("apikey", token))
            .url(original.url)
        return chain.proceed(requestBuilder.build())
    }
}

You need to use Credentials.basic() in order to encode credentials.

I really hope somebody with a similar issue stumbles across this and saves themselves some time.



来源:https://stackoverflow.com/questions/58573256/retrofit2-authentication-error-to-ibms-speech-to-text

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!