diff --git a/app/src/main/java/io/timelimit/android/sync/network/api/HttpClient.kt b/app/src/main/java/io/timelimit/android/sync/network/api/HttpClient.kt index 72a7ba5..a99ec49 100644 --- a/app/src/main/java/io/timelimit/android/sync/network/api/HttpClient.kt +++ b/app/src/main/java/io/timelimit/android/sync/network/api/HttpClient.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2020 Jonas Lochmann + * TimeLimit Copyright 2019 - 2023 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,7 @@ val httpClient: OkHttpClient by lazy { if (BuildConfig.DEBUG) { builder.addInterceptor (HttpLoggingInterceptor { Log.d("HttpClient", it) - }) + }.apply { level = HttpLoggingInterceptor.Level.HEADERS }) } builder.build() diff --git a/app/src/main/java/io/timelimit/android/sync/network/api/HttpServerApi.kt b/app/src/main/java/io/timelimit/android/sync/network/api/HttpServerApi.kt index c234c9b..8b34ccb 100644 --- a/app/src/main/java/io/timelimit/android/sync/network/api/HttpServerApi.kt +++ b/app/src/main/java/io/timelimit/android/sync/network/api/HttpServerApi.kt @@ -23,12 +23,15 @@ import io.timelimit.android.async.Threads import io.timelimit.android.coroutines.executeAndWait import io.timelimit.android.coroutines.waitForResponse import io.timelimit.android.sync.network.* +import io.timelimit.android.util.okio.LengthSink import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request import okhttp3.RequestBody import okhttp3.Response +import okhttp3.internal.closeQuietly import okio.BufferedSink import okio.GzipSink +import okio.Sink import okio.buffer import java.io.OutputStreamWriter @@ -63,24 +66,37 @@ class HttpServerApi(private val endpointWithoutSlashAtEnd: String): ServerApi { private val JSON = "application/json; charset=utf-8".toMediaTypeOrNull() - private fun createJsonRequestBody(serialize: (writer: JsonWriter) -> Unit) = object: RequestBody() { - override fun contentType() = JSON - override fun writeTo(sink: BufferedSink) { + private fun createJsonRequestBody( + serialize: (writer: JsonWriter) -> Unit, + measureContentLength: Boolean + ): RequestBody { + fun write(sink: Sink) { val writer = JsonWriter( - OutputStreamWriter( - GzipSink(sink) - .buffer().outputStream() - ) + OutputStreamWriter( + GzipSink(sink) + .buffer().outputStream() + ) ) serialize(writer) - writer.flush() writer.close() } + + val length = + if (measureContentLength) LengthSink().also { write(it) }.length + else null + + return object: RequestBody() { + override fun contentType() = JSON + override fun writeTo(sink: BufferedSink) = write(sink) + override fun contentLength(): Long = length ?: -1 + } } } + private var sendContentLength = false + override suspend fun getTimeInMillis(): Long { httpClient.newCall( Request.Builder() @@ -595,10 +611,30 @@ class HttpServerApi(private val endpointWithoutSlashAtEnd: String): ServerApi { path: String, requestBody: (writer: JsonWriter) -> Unit ): Response { + if (!sendContentLength) { + val response = postJsonRequest(path, requestBody, transmitContentLength = false) + + if (response.code != 411) return response + + Threads.network.executeAndWait { response.closeQuietly() } + + sendContentLength = true + } + + return postJsonRequest(path, requestBody, transmitContentLength = true) + } + + private suspend fun postJsonRequest( + path: String, + requestBody: (writer: JsonWriter) -> Unit, + transmitContentLength: Boolean + ): Response { + val body = createJsonRequestBody(requestBody, transmitContentLength) + return httpClient.newCall( Request.Builder() .url("$endpointWithoutSlashAtEnd/$path") - .post(createJsonRequestBody(requestBody)) + .post(body) .header("Content-Encoding", "gzip") .build() ).waitForResponse() diff --git a/app/src/main/java/io/timelimit/android/util/okio/LengthSink.kt b/app/src/main/java/io/timelimit/android/util/okio/LengthSink.kt new file mode 100644 index 0000000..869c10e --- /dev/null +++ b/app/src/main/java/io/timelimit/android/util/okio/LengthSink.kt @@ -0,0 +1,31 @@ +/* + * TimeLimit Copyright 2019 - 2023 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.util.okio + +import okio.Buffer +import okio.Sink +import okio.Timeout + +class LengthSink: Sink { + private var lengthInternal = 0L + + val length get() = lengthInternal + + override fun write(source: Buffer, byteCount: Long) { lengthInternal += byteCount } + override fun timeout(): Timeout = Timeout.NONE + override fun flush() = Unit + override fun close() = Unit +} \ No newline at end of file