Can I use the Ktor client to receive a file with a...
# ktor
d
Can I use the Ktor client to receive a file with a simple
get
?
๐Ÿ“ 1
โœ… 1
d
Do you mean to get a ByteArray from an url with a single method invocation?
d
Nope, to get it to download the file to a temporary folder and give me a File object...
d
I see, I believe there is no built-in functionality for that, but I guess an extension method would do
d
For uploads TO the server I have:
Copy code
suspend fun ApplicationCall.receiveFile(uploadDir: String): File =
		receiveMultipart().readPart().let {
			if (it is PartData.FileItem) {
				val ext = File(it.originalFileName).extension
				val uploadName = "upload-${System.currentTimeMillis()}-${it.originalFileName!!.hashCode()}.$ext"

				File(uploadDir, uploadName).also { file ->
					withContext(CommonPool) {
						it.streamProvider().use { its -> file.outputStream().buffered().use { its.copyToSuspend(it) } }
					}
				}
			} else error("Error retreiving upload")
		}
But I need an option to provide an url to "upload" from, meaning I'd download from there...
d
1 sec, Iโ€™m trying to do an extension method for that and I will paste it/put in the samples
๐Ÿ‘๐Ÿผ 1
Untested (but could be something like this):
Copy code
suspend fun HttpClient.getAsTempFile(url: String, callback: suspend (file: File) -> Unit) {
	val file = getAsTempFile(url)
	try {
		callback(file)
	} finally {
		file.delete()
	}
}
suspend fun HttpClient.getAsTempFile(url: String): File {
	val file = File.createTempFile("ktor", "http-client").apply { deleteOnExit() }
	val call = call {
		url(URL(url))
		method = HttpMethod.Get
	}
	call.response.content.copyAndClose(file.writeChannel())
	return file
}
ideally you would use the vesion with the callback to handle the lifecycle of the File so it gets deleted asap
d
Thanks alot! I'll give it a try simple smile
copyAndClose
reads everything to memory before it writes to disk?
๐Ÿšซ 1
e
It use
ByteChannel
as buffer
๐Ÿ‘๐Ÿผ 1
d
Funny, it seems to give me a file of 0 bytes... @Deactivated User
Say from this url
<https://www.apkmirror.com/wp-content/themes/APKMirror/download.php?id=452787>
d
Ok, didnโ€™t tested, so it is possible. Let me try
The problem is that it returns a Found with a Location I guess
you should configure the client to follow redirects
e
or you could use
HttpRedirect
feature from ktor
d
Copy code
fun main(args: Array<String>) {
    runBlocking {
        val client = HttpClient(Apache.config {
            followRedirects = true
        })
        val file = client.getAsTempFile("<https://www.apkmirror.com/wp-content/themes/APKMirror/download.php?id=452787>")
        println(file.readBytes().size)
    }
}

suspend fun HttpClient.getAsTempFile(url: String, callback: suspend (file: File) -> Unit) {
    val file = getAsTempFile(url)
    try {
        callback(file)
    } finally {
        file.delete()
    }
}

suspend fun HttpClient.getAsTempFile(url: String): File {
    val file = File.createTempFile("ktor", "http-client").apply { deleteOnExit() }
    val call = call {
        url(URL(url))
        method = HttpMethod.Get
    }
    println(call.response.status)
    call.response.content.copyAndClose(file.writeChannel())
    return file
}
this works for me
@e5l is there a HttpRedirect feature for the client?
๐Ÿ‘Œ 1
d
Right, I didn't see it in the docs...
d
not documented
the followRedirects is in the documentation, and I propose to make it the default, but the HttpRedirect is not documented
d
But then if it's default, people switching to other clients won't realize they must install HttpRedirect feature to get the same behavior...
It does make sense to have such a default, but it might be inconsistent...
Btw, thanks! Now it works perfectly.
๐ŸŽŠ 1
d
BTW. Adjusted it to check for errors and to use the HttpRedirect:
Copy code
fun main(args: Array<String>) {
    runBlocking {
        val client = HttpClient(Apache) {
            install(HttpRedirect) {
                maxJumps = 20
            }
        }
        val file = client.getAsTempFile("<https://www.apkmirror.com/wp-content/themes/APKMirror/download.php?id=452787>")
        println(file.readBytes().size)
    }
}

data class HttpClientException(val response: HttpResponse) : IOException("HTTP Error ${response.status}")

suspend fun HttpClient.getAsTempFile(url: String, callback: suspend (file: File) -> Unit) {
    val file = getAsTempFile(url)
    try {
        callback(file)
    } finally {
        file.delete()
    }
}

suspend fun HttpClient.getAsTempFile(url: String): File {
    val file = File.createTempFile("ktor", "http-client").apply { deleteOnExit() }
    val call = call {
        url(URL(url))
        method = HttpMethod.Get
    }
    if (!call.response.status.isSuccess()) {
        throw HttpClientException(call.response)
    }
    call.response.content.copyAndClose(file.writeChannel())
    return file
}
๐Ÿ“ 3
d
This would be a great addition to the lib ๐Ÿ‘Œ๐Ÿผ