HttpURLConnectionでPATCHリクエストを投げる

Androidアプリ作成時にAPI通信を行いたいとき、訳合ってOSSライブラリを使用せずに実装する必要がありました。
そのため、 `HttpURLConnection` を使って独自に通信部分を作成しました。

しかし、PATCHリクエストを送りたいときにエラー発生。
なんと、 `HttpURLConnection` はPATCHリクエストを投げられないらしいです。
うそ…だろ…。

`X-HTTP-Method-Override` ヘッダーを使って偽装する方法もありますが、サーバー側の依存もあり確実では無いようです。
https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch

OSSライブラリはどうやってるんだ？
ということで、 [Jersey](https://eclipse-ee4j.github.io/jersey/) というHTTPクライアントライブラリの[公開されているコード](https://github.com/jersey/jersey)を参考にして、実装しました。
少し古いライブラリですが、動作確認できました。

# 実装例

`HttpClient` クラスを、下記のように実装します。
今回はBearer認証を想定しています。

```kotlin:HttpClient.kt

internal class HttpClient {
    companion object {

        fun patch(url: String, accessToken: String? = null, body: String? = null): Pair<Int, String?> {
            var responseJsonString: String? = null
            val urlTmp = URL(url)
            val con = urlTmp.openConnection() as HttpURLConnection
            try {
                setRequestMethodViaJreBugWorkaround( con, "PATCH")
                con.instanceFollowRedirects = false
                con.doOutput = true
                con.connectTimeout = 60000
                con.readTimeout = 60000
                con.setRequestProperty("Content-Type", "application/json")
                con.setRequestProperty("Accept", "application/json")
                if(accessToken != null) con.setRequestProperty("Authorization", "Bearer $accessToken")
                if(body != null) {
                    val os: OutputStream = con.outputStream
                    val ps = PrintStream(os)
                    ps.print(body)
                    ps.close()
                }
                val reader: BufferedReader
                reader = if (con.responseCode == HttpURLConnection.HTTP_OK) {
                    BufferedReader(InputStreamReader(con.inputStream, "UTF-8"))
                } else {
                    BufferedReader(InputStreamReader(con.errorStream, "UTF-8"))
                }
                responseJsonString = reader.readLine()
                con.disconnect()
            } catch (e: Exception) {
                e.printStackTrace()
                Log.e("ApiClientException", e.toString())
            }
            return con.responseCode to responseJsonString
        }

        private fun setRequestMethodViaJreBugWorkaround(httpURLConnection: HttpURLConnection, method: String) {
            try {
                httpURLConnection.requestMethod = method // Check whether we are running on a buggy JRE
            } catch (pe: ProtocolException) {
                try {
                    AccessController.doPrivileged(PrivilegedExceptionAction<Any?> {
                        try {
                            httpURLConnection.requestMethod = method
                            // Check whether we are running on a buggy
                            // JRE
                        } catch (pe: ProtocolException) {
                            var connectionClass: Class<*>? = httpURLConnection.javaClass
                            val delegateField: Field?
                            try {
                                delegateField = connectionClass!!.getDeclaredField("delegate")
                                delegateField.isAccessible = true
                                val delegateConnection = delegateField[httpURLConnection] as HttpURLConnection
                                setRequestMethodViaJreBugWorkaround(delegateConnection, method)
                            } catch (e: NoSuchFieldException) {
                                // Ignore for now, keep going
                            } catch (e: IllegalArgumentException) {
                                throw RuntimeException(e)
                            } catch (e: IllegalAccessException) {
                                throw RuntimeException(e)
                            }
                            try {
                                var methodField: Field
                                while (connectionClass != null) {
                                    try {
                                        methodField = connectionClass.getDeclaredField("method")
                                    } catch (e: NoSuchFieldException) {
                                        connectionClass = connectionClass.superclass
                                        continue
                                    }
                                    methodField.isAccessible = true
                                    methodField[httpURLConnection] = method
                                    break
                                }
                            } catch (e: java.lang.Exception) {
                                throw RuntimeException(e)
                            }
                        }
                        null
                    })
                } catch (e: PrivilegedActionException) {
                    val cause: Throwable? = e.cause
                    if (cause is RuntimeException) {
                        throw cause
                    } else {
                        throw RuntimeException(cause)
                    }
                }
            }
        }
    }
}
```

# 使用例

上記 `HttpClient` をimportして、下記のように使用します。

```kotlin
fun patchRequestSample() {
    val (statusCode, responseJsonString) = HttpClient.patch("https://hoge/users/10", accessToken ="xxxx", body = sampleJson.toString())
}
```

`statusCode` にステータスコード、 `responseJsonString` にレスポンスが返ってきます。

調べてもヘッダーのオーバーライドの方法ばかり出てきたので、備忘として残しておきます。