LoginSignup
1
2

More than 1 year has passed since last update.

OkHttpのレスポンスボディを文字列に変換しても文字化けするとき

Posted at

ハマりました・・・

現象としては、Postmanを使ってもCURLを使ってもきちんとレスポンスボディが読めるのですが、なぜかOkHttpを使うと文字化けしてしまい、ボディをデシリアライズできませんでした。
結論から言うと、gzipという暗号化処理がされていたせいでした。

これを解決するには二通りの方法があります。

  1. Encodingを許可しない(こっちは簡単ですがセキュリティ面でリスクがありそうです)
  2. GZIPで復号する

1. Encodingを許可しない

これは簡単で、リクエストヘッダーから「Accept-Encoding」を削除してしまえば大丈夫かと。
例えばInterceptorなどを用いて

        val request = chain.request().newBuilder()
            .removeHeader("Accept-Encoding")
            .build()
        val response = chain.proceed(request)

リクエストからAccept-Encodingを取り除きました。
ここにgzipが入っていなければサーバー側は配慮してくれる場合が多そう?です。実際にFirebaseのとあるエンドポイントは暗号化せずにレスポンスしてくれました。

2. GZIPで復号する

復号方法は

val jsonFormat = Json { ignoreUnknownKeys = true }
val buffer = GzipSource(response.body!!.source()).buffer()
val bodyJson = jsonFormat.decodeFromString<ErrorResponse>(buffer.readUtf8())

こんな感じです。Kotlin Serializationを使っていますが、そこはGsonでもJacksonでも何でもオッケーです。
着目すべきはGzipSourceでresponse.body!!.source()を囲っている部分ですね。
これによりGzipの復号化処理を行ってくれるようです。
※ちなみに暗号化されていないものを復号しようとするとまた別のエラーが出るので注意してください

ちなみにテストコードを書くときは

        val responseJsonString = """
                    {
                        "error": {
                            "code": 400,
                            "message": "INVALID_ID_TOKEN",
                            "errors": [
                                {
                                    "message": "INVALID_ID_TOKEN",
                                    "domain": "global",
                                    "reason": "invalid"
                                }
                            ]
                        }
                    }
                    """.trimIndent()

        /**
         * レスポンスがgzipでエンコーディングされているため、エンコーディングする
         */
        val buffer = Buffer()
        val byteOut = ByteArrayOutputStream()
        val gzipOut = GZIPOutputStream(byteOut)
        gzipOut.write(responseJsonString.toByteArray())
        gzipOut.close()
        buffer.write(byteOut.toByteArray())

        val server = MockWebServer().apply {
            enqueue(
                MockResponse().apply {
                    setResponseCode(400)
                    setBody(buffer)
                }
            )
        }

↑こんな感じで、GZIPOutStreamを使ってgzipで暗号化してbufferに書き込んで、MockResponseにsetBody()すれば暗号化されたレスポンスを再現できました。

恥ずかしながらたった今初めてgzipなるものを知りました。
一体何の文字化けなんだろう?とutf-8やら何やら探りましたが、こんなものがあったとは・・・
勉強します。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2