Androidアプリ作成時にAPI通信を行いたいとき、訳合ってOSSライブラリを使用せずに実装する必要がありました。
そのため、 HttpURLConnection
を使って独自に通信部分を作成しました。
しかし、PATCHリクエストを送りたいときにエラー発生。
なんと、 HttpURLConnection
はPATCHリクエストを投げられないらしいです。
うそ…だろ…。
X-HTTP-Method-Override
ヘッダーを使って偽装する方法もありますが、サーバー側の依存もあり確実では無いようです。
https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch
OSSライブラリはどうやってるんだ?
ということで、 Jersey というHTTPクライアントライブラリの公開されているコードを参考にして、実装しました。
少し古いライブラリですが、動作確認できました。
実装例
HttpClient
クラスを、下記のように実装します。
今回はBearer認証を想定しています。
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して、下記のように使用します。
fun patchRequestSample() {
val (statusCode, responseJsonString) = HttpClient.patch("https://hoge/users/10", accessToken ="xxxx", body = sampleJson.toString())
}
statusCode
にステータスコード、 responseJsonString
にレスポンスが返ってきます。
調べてもヘッダーのオーバーライドの方法ばかり出てきたので、備忘として残しておきます。