Throw a PATCH request with HttpURLConnection

When I wanted to perform API communication when creating an Android application, I had to implement it without using the OSS library. Therefore, I created my own communication part using HttpURLConnection.

However, an error occurs when I want to send a PATCH request. It seems that HttpURLConnection cannot make PATCH requests. Lie ... I guess ...

There is also a way to spoof using the X-HTTP-Method-Override header, but it seems uncertain due to server-side dependencies. https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch

How do you do the OSS library? So, refer to Public Code of the HTTP client library Jersey And implemented it. It's a little old library, but I was able to confirm its operation.

Implementation example

Implement the HttpClient class as follows. This time, Bearer authentication is assumed.

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)
                    }
                }
            }
        }
    }
}

Example of use

Import the above HttpClient and use it as follows.

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

The status code is returned in statusCode, and the response is returned in responseJsonString.

Even if I looked it up, I found only how to override the header, so I'll leave it as a reminder.

Recommended Posts

Throw a PATCH request with HttpURLConnection
How to request a CSV file as JSON with jMeter
[Java] Throw a request and display the screen (GET / POST)
Create a playground with Xcode 12
Draw a gradient with CAGradientLayer
A story stuck with NotSerializableException
Things to forget when intercepting a request with Android's WebView # shouldInterceptRequest