-
-
Save jonasbark/e37d535239ae28facc83bd4ff2c0772a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.stihl.app.module.connect.api | |
import io.ktor.client.call.* | |
import io.ktor.client.engine.* | |
import io.ktor.client.engine.ios.IosHttpRequestException | |
import io.ktor.client.engine.ios.IosHttpResponse | |
import io.ktor.client.engine.ios.toByteArray | |
import io.ktor.client.engine.ios.toNSData | |
import io.ktor.client.request.* | |
import io.ktor.client.utils.* | |
import io.ktor.http.* | |
import io.ktor.http.content.* | |
import io.ktor.util.* | |
import io.ktor.util.date.* | |
import kotlin.coroutines.* | |
import kotlinx.coroutines.* | |
import kotlinx.coroutines.channels.* | |
import kotlinx.coroutines.io.* | |
import kotlinx.io.core.* | |
import platform.Foundation.* | |
import platform.darwin.* | |
object Ios2 : HttpClientEngineFactory<IosURLSessionConfig> { | |
@KtorExperimentalAPI | |
override fun create(block: IosURLSessionConfig.() -> Unit): HttpClientEngine = | |
Ios2ClientEngine(IosURLSessionConfig().apply(block)) | |
} | |
class IosURLSessionConfig : HttpClientEngineConfig() { | |
internal var config: NSURLSession.() -> Unit = {} | |
fun config(block: NSURLSession.() -> Unit) { | |
val oldConfig = config | |
config = { | |
oldConfig() | |
block() | |
} | |
} | |
} | |
@KtorExperimentalAPI | |
class Ios2ClientEngine(override val config: IosURLSessionConfig) : HttpClientEngine { | |
private val context: Job = Job() | |
// TODO: replace with UI dispatcher | |
override val dispatcher: CoroutineDispatcher = Dispatchers.Unconfined | |
override val coroutineContext: CoroutineContext = dispatcher + SupervisorJob() | |
@InternalAPI | |
override suspend fun execute( | |
call: HttpClientCall, | |
data: HttpRequestData | |
): HttpEngineCall = suspendCancellableCoroutine { continuation -> | |
val callContext = coroutineContext + CompletableDeferred<Unit>() | |
val request = DefaultHttpRequest(call, data) | |
val requestTime = GMTDate() | |
val delegate = object : NSObject(), NSURLSessionDataDelegateProtocol { | |
val chunks = Channel<ByteArray>(Channel.UNLIMITED) | |
override fun URLSession(session: NSURLSession, didReceiveChallenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Unit) { | |
val credential = NSURLCredential.credentialForTrust(didReceiveChallenge.protectionSpace.serverTrust) | |
completionHandler(NSURLSessionAuthChallengeUseCredential, credential) | |
} | |
override fun URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData: NSData) { | |
val content = didReceiveData.toByteArray() | |
if (!chunks.offer(content)) throw IosHttpRequestException() | |
} | |
override fun URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError: NSError?) { | |
chunks.close() | |
if (didCompleteWithError != null) { | |
continuation.resumeWithException(IosHttpRequestException(didCompleteWithError)) | |
return | |
} | |
val response = task.response as NSHTTPURLResponse | |
@Suppress("UNCHECKED_CAST") | |
val headersDict = response.allHeaderFields as Map<String, String> | |
val status = HttpStatusCode.fromValue(response.statusCode.toInt()) | |
val headers = buildHeaders { | |
headersDict.mapKeys { (key, value) -> append(key, value) } | |
} | |
val responseContext = writer(coroutineContext, autoFlush = true) { | |
while (!chunks.isClosedForReceive) { | |
val chunk = chunks.receive() | |
channel.writeFully(chunk) | |
} | |
} | |
val result = IosHttpResponse( | |
call, status, headers, requestTime, | |
responseContext.channel, callContext | |
) | |
continuation.resume(HttpEngineCall(request, result)) | |
} | |
} | |
val session = NSURLSession.sessionWithConfiguration( | |
NSURLSessionConfiguration.defaultSessionConfiguration(), | |
delegate, delegateQueue = NSOperationQueue.mainQueue() | |
) | |
//config.config(session) | |
val url = URLBuilder().takeFrom(request.url).buildString() | |
val nativeRequest = NSMutableURLRequest.requestWithURL(NSURL(string = url)) | |
mergeHeaders(request.headers, request.content) { key, value -> | |
nativeRequest.setValue(value, key) | |
} | |
nativeRequest.setCachePolicy(NSURLRequestReloadIgnoringCacheData) | |
nativeRequest.setHTTPMethod(request.method.value) | |
launch(callContext) { | |
val content = request.content | |
val body = when (content) { | |
is OutgoingContent.ByteArrayContent -> content.bytes().toNSData() | |
is OutgoingContent.WriteChannelContent -> writer(dispatcher) { | |
content.writeTo(channel) | |
}.channel.readRemaining().readBytes().toNSData() | |
is OutgoingContent.ReadChannelContent -> content.readFrom().readRemaining().readBytes().toNSData() | |
is OutgoingContent.NoContent -> null | |
else -> throw UnsupportedContentTypeException(content) | |
} | |
body?.let { nativeRequest.setHTTPBody(it) } | |
session.dataTaskWithRequest(nativeRequest).resume() | |
} | |
} | |
override fun close() { | |
coroutineContext.cancel() | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment