mirror of
				https://github.com/owncloud/android-library.git
				synced 2025-10-31 02:17:41 +00:00 
			
		
		
		
	Log http body if it is not binary
This commit is contained in:
		
							parent
							
								
									aa665a7295
								
							
						
					
					
						commit
						c0d2e20bb1
					
				| @ -29,8 +29,9 @@ object LogBuilder { | |||||||
|     fun logHttp( |     fun logHttp( | ||||||
|         networkPetition: NetworkPetition, |         networkPetition: NetworkPetition, | ||||||
|         networkNode: NetworkNode, |         networkNode: NetworkNode, | ||||||
|  |         requestId: String? = "", | ||||||
|         description: String |         description: String | ||||||
|     ) = Timber.d("[Network, $networkPetition] [$networkNode] $description") |     ) = Timber.d("[Network, $networkPetition] [$networkNode] [$requestId] $description") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| enum class NetworkPetition { | enum class NetworkPetition { | ||||||
|  | |||||||
| @ -23,37 +23,145 @@ | |||||||
|  */ |  */ | ||||||
| package com.owncloud.android.lib.common.http | package com.owncloud.android.lib.common.http | ||||||
| 
 | 
 | ||||||
|  | import com.owncloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID | ||||||
| import com.owncloud.android.lib.common.http.LogBuilder.logHttp | import com.owncloud.android.lib.common.http.LogBuilder.logHttp | ||||||
| import com.owncloud.android.lib.common.http.NetworkNode.BODY | import com.owncloud.android.lib.common.http.NetworkNode.BODY | ||||||
| import com.owncloud.android.lib.common.http.NetworkNode.HEADER | import com.owncloud.android.lib.common.http.NetworkNode.HEADER | ||||||
| import com.owncloud.android.lib.common.http.NetworkNode.INFO | import com.owncloud.android.lib.common.http.NetworkNode.INFO | ||||||
| import com.owncloud.android.lib.common.http.NetworkPetition.REQUEST | import com.owncloud.android.lib.common.http.NetworkPetition.REQUEST | ||||||
| import com.owncloud.android.lib.common.http.NetworkPetition.RESPONSE | import com.owncloud.android.lib.common.http.NetworkPetition.RESPONSE | ||||||
|  | import okhttp3.Headers | ||||||
| import okhttp3.Interceptor | import okhttp3.Interceptor | ||||||
|  | import okhttp3.RequestBody | ||||||
| import okhttp3.Response | import okhttp3.Response | ||||||
|  | import okhttp3.ResponseBody | ||||||
|  | import okio.Buffer | ||||||
|  | import java.nio.charset.Charset | ||||||
|  | import java.nio.charset.StandardCharsets | ||||||
|  | import kotlin.math.max | ||||||
| 
 | 
 | ||||||
| class LogInterceptor : Interceptor { | class LogInterceptor : Interceptor { | ||||||
| 
 | 
 | ||||||
|     override fun intercept(chain: Interceptor.Chain): Response { |     override fun intercept(chain: Interceptor.Chain): Response { | ||||||
| 
 | 
 | ||||||
|         val response = chain.proceed(chain.request()) |         if (!httpLogsEnabled) { | ||||||
|  |             return chain.proceed(chain.request()) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val request = chain.request().also { | ||||||
|  |             val requestId = it.headers[OC_X_REQUEST_ID] | ||||||
|  |             logHttp(REQUEST, INFO, requestId, "Type: ${it.method} URL: ${it.url}") | ||||||
|  |             logHeaders(requestId, it.headers, REQUEST) | ||||||
|  |             logRequestBody(requestId, it.body) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val response = chain.proceed(request) | ||||||
| 
 | 
 | ||||||
|         return response.also { |         return response.also { | ||||||
|             if (httpLogsEnabled) { |             val requestId = it.request.headers[OC_X_REQUEST_ID] | ||||||
|                 // Log request |             logHttp( | ||||||
|                 logHttp(REQUEST, INFO, "Type: ${it.request.method} URL: ${it.request.url}") |                 RESPONSE, | ||||||
|                 it.request.headers.forEach { header -> logHttp(REQUEST, HEADER, header.toString()) } |                 INFO, | ||||||
|                 logHttp(REQUEST, BODY, it.request.body.toString()) |                 requestId, | ||||||
|  |                 "Code: ${it.code}  Message: ${it.message} IsSuccessful: ${it.isSuccessful}" | ||||||
|  |             ) | ||||||
|  |             logHeaders(requestId, it.headers, RESPONSE) | ||||||
|  |             logResponseBody(requestId, it.body) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|                 // Log response |     private fun logHeaders(requestId: String?, headers: Headers, networkPetition: NetworkPetition) { | ||||||
|                 logHttp(RESPONSE, INFO, "Code: ${it.code}  Message: ${it.message} IsSuccessful: ${it.isSuccessful}") |         headers.forEach { header -> | ||||||
|                 it.headers.forEach { header -> logHttp(RESPONSE, HEADER, header.toString()) } |             logHttp(networkPetition, HEADER, requestId, "${header.first}: ${header.second}") | ||||||
|                 logHttp(RESPONSE, BODY, it.body.toString()) |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     private fun logRequestBody(requestId: String?, requestBodyParam: RequestBody?) { | ||||||
|  |         requestBodyParam?.let { requestBody -> | ||||||
|  | 
 | ||||||
|  |             if (requestBody.isOneShot()) { | ||||||
|  |                 logHttp(REQUEST, BODY, requestId, "One shot body -- Omitted") | ||||||
|  |                 return@let | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (requestBody.isDuplex()) { | ||||||
|  |                 logHttp(REQUEST, BODY, requestId, "Duplex body -- Omitted") | ||||||
|  |                 return@let | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             val buffer = Buffer() | ||||||
|  |             requestBody.writeTo(buffer) | ||||||
|  | 
 | ||||||
|  |             val contentType = requestBody.contentType() | ||||||
|  |             val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8 | ||||||
|  | 
 | ||||||
|  |             logHttp(REQUEST, BODY, requestId, "Length: ${requestBody.contentLength()} byte body") | ||||||
|  |             logHttp(REQUEST, BODY, requestId, "Type: ${requestBody.contentType()}") | ||||||
|  |             logHttp(REQUEST, BODY, requestId, "--> Body start for request") | ||||||
|  | 
 | ||||||
|  |             if (buffer.isProbablyUtf8()) { | ||||||
|  |                 if (requestBody.contentLength() < LIMIT_BODY_LOG) { | ||||||
|  |                     logHttp(REQUEST, BODY, requestId, buffer.readString(charset)) | ||||||
|  |                 } else { | ||||||
|  |                     logHttp(REQUEST, BODY, requestId, buffer.readString(LIMIT_BODY_LOG, charset)) | ||||||
|  |                 } | ||||||
|  |                 logHttp( | ||||||
|  |                     REQUEST, | ||||||
|  |                     BODY, | ||||||
|  |                     requestId, | ||||||
|  |                     "<-- Body end for request -- Omitted: ${max(0, requestBody.contentLength() - LIMIT_BODY_LOG)} bytes" | ||||||
|  |                 ) | ||||||
|  |             } else { | ||||||
|  |                 logHttp( | ||||||
|  |                     REQUEST, | ||||||
|  |                     BODY, | ||||||
|  |                     requestId, | ||||||
|  |                     "<-- Body end for request -- Binary -- Omitted: ${requestBody.contentLength()} bytes" | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } ?: logHttp(REQUEST, BODY, requestId, "Empty body") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun logResponseBody(requestId: String?, responseBodyParam: ResponseBody?) { | ||||||
|  |         responseBodyParam?.let { responseBody -> | ||||||
|  | 
 | ||||||
|  |             val contentType = responseBody.contentType() | ||||||
|  |             val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8 | ||||||
|  | 
 | ||||||
|  |             logHttp(RESPONSE, BODY, requestId, "Length: ${responseBody.contentLength()} byte body") | ||||||
|  |             logHttp(RESPONSE, BODY, requestId, "Type: ${responseBody.contentType()}") | ||||||
|  |             logHttp(RESPONSE, BODY, requestId, "--> Body start for request") | ||||||
|  | 
 | ||||||
|  |             val source = responseBody.source() | ||||||
|  |             source.request(LIMIT_BODY_LOG) | ||||||
|  |             val buffer = source.buffer | ||||||
|  | 
 | ||||||
|  |             if (!buffer.isProbablyUtf8()) { | ||||||
|  |                 logHttp( | ||||||
|  |                     REQUEST, | ||||||
|  |                     BODY, | ||||||
|  |                     requestId, | ||||||
|  |                     "<-- Body end for request -- Binary -- Omitted: ${responseBody.contentLength()} bytes" | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (responseBody.contentLength() < LIMIT_BODY_LOG) { | ||||||
|  |                 logHttp(REQUEST, BODY, requestId, buffer.clone().readString(charset)) | ||||||
|  |             } else { | ||||||
|  |                 logHttp(REQUEST, BODY, requestId, buffer.clone().readString(LIMIT_BODY_LOG, charset)) | ||||||
|  |             } | ||||||
|  |             logHttp( | ||||||
|  |                 REQUEST, | ||||||
|  |                 BODY, | ||||||
|  |                 requestId, | ||||||
|  |                 "<-- Body end for request -- Omitted: ${max(0, responseBody.contentLength() - LIMIT_BODY_LOG)} bytes" | ||||||
|  |             ) | ||||||
|  |         } ?: logHttp(RESPONSE, BODY, requestId, "Empty body") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     companion object { |     companion object { | ||||||
|         var httpLogsEnabled: Boolean = false |         var httpLogsEnabled: Boolean = false | ||||||
|  |         private const val LIMIT_BODY_LOG: Long = 1024 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,44 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (C) 2015 Square, Inc. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | package com.owncloud.android.lib.common.http | ||||||
|  | 
 | ||||||
|  | import java.io.EOFException | ||||||
|  | import okio.Buffer | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Returns true if the body in question probably contains human readable text. Uses a small | ||||||
|  |  * sample of code points to detect unicode control characters commonly used in binary file | ||||||
|  |  * signatures. | ||||||
|  |  */ | ||||||
|  | internal fun Buffer.isProbablyUtf8(): Boolean { | ||||||
|  |     try { | ||||||
|  |         val prefix = Buffer() | ||||||
|  |         val byteCount = size.coerceAtMost(64) | ||||||
|  |         copyTo(prefix, 0, byteCount) | ||||||
|  |         for (i in 0 until 16) { | ||||||
|  |             if (prefix.exhausted()) { | ||||||
|  |                 break | ||||||
|  |             } | ||||||
|  |             val codePoint = prefix.readUtf8CodePoint() | ||||||
|  |             if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { | ||||||
|  |                 return false | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     } catch (_: EOFException) { | ||||||
|  |         return false // Truncated UTF-8 sequence. | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user