Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose ExecutionContext to HttpEngine and add OkHttp helpers #5383

Merged
merged 1 commit into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion libraries/apollo-api/api/apollo-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -979,8 +979,9 @@ public final class com/apollographql/apollo3/api/http/HttpMethod : java/lang/Enu
}

public final class com/apollographql/apollo3/api/http/HttpRequest {
public synthetic fun <init> (Lcom/apollographql/apollo3/api/http/HttpMethod;Ljava/lang/String;Ljava/util/List;Lcom/apollographql/apollo3/api/http/HttpBody;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcom/apollographql/apollo3/api/http/HttpMethod;Ljava/lang/String;Ljava/util/List;Lcom/apollographql/apollo3/api/http/HttpBody;Lcom/apollographql/apollo3/api/ExecutionContext;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getBody ()Lcom/apollographql/apollo3/api/http/HttpBody;
public final fun getExecutionContext ()Lcom/apollographql/apollo3/api/ExecutionContext;
public final fun getHeaders ()Ljava/util/List;
public final fun getMethod ()Lcom/apollographql/apollo3/api/http/HttpMethod;
public final fun getUrl ()Ljava/lang/String;
Expand All @@ -992,6 +993,7 @@ public final class com/apollographql/apollo3/api/http/HttpRequest {

public final class com/apollographql/apollo3/api/http/HttpRequest$Builder {
public fun <init> (Lcom/apollographql/apollo3/api/http/HttpMethod;Ljava/lang/String;)V
public final fun addExecutionContext (Lcom/apollographql/apollo3/api/ExecutionContext;)Lcom/apollographql/apollo3/api/http/HttpRequest$Builder;
public final fun addHeader (Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/apollo3/api/http/HttpRequest$Builder;
public final fun addHeaders (Ljava/util/List;)Lcom/apollographql/apollo3/api/http/HttpRequest$Builder;
public final fun body (Lcom/apollographql/apollo3/api/http/HttpBody;)Lcom/apollographql/apollo3/api/http/HttpRequest$Builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,27 @@ class DefaultHttpRequestComposer(
val sendApqExtensions = apolloRequest.sendApqExtensions ?: false
val sendDocument = apolloRequest.sendDocument ?: true

return when (apolloRequest.httpMethod ?: HttpMethod.Post) {
val httpRequestBuilder = when (apolloRequest.httpMethod ?: HttpMethod.Post) {
HttpMethod.Get -> {
HttpRequest.Builder(
method = HttpMethod.Get,
url = buildGetUrl(serverUrl, operation, customScalarAdapters, sendApqExtensions, sendDocument),
).addHeaders(requestHeaders)
.build()
)
}

HttpMethod.Post -> {
val query = if (sendDocument) operation.document() else null
HttpRequest.Builder(
method = HttpMethod.Post,
url = serverUrl,
).addHeaders(requestHeaders)
.body(buildPostBody(operation, customScalarAdapters, sendApqExtensions, query))
.build()
).body(buildPostBody(operation, customScalarAdapters, sendApqExtensions, query))
}
}

return httpRequestBuilder
.addHeaders(requestHeaders)
.addExecutionContext(apolloRequest.executionContext)
.build()
}

companion object {
Expand Down Expand Up @@ -149,7 +151,7 @@ class DefaultHttpRequestComposer(

/**
* This mostly duplicates [composePostParams] but encode variables and extensions as strings
* and not json elements. I tried factoring in that code but it ended up being more clunky that
* and not json elements. I tried factoring in that code, but it ended up being more clunky that
* duplicating it
*/
private fun <D : Operation.Data> composeGetParams(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.apollographql.apollo3.api.http
import com.apollographql.apollo3.annotations.ApolloDeprecatedSince
import com.apollographql.apollo3.annotations.ApolloDeprecatedSince.Version.v3_4_1
import com.apollographql.apollo3.annotations.ApolloExperimental
import com.apollographql.apollo3.api.ExecutionContext
import okio.Buffer
import okio.BufferedSink
import okio.BufferedSource
Expand Down Expand Up @@ -35,7 +36,7 @@ data class HttpHeader(val name: String, val value: String)
/**
* Get the value for header [name] or null if this header doesn't exist or is defined multiple times
*
* The header name matching is case insensitive
* The header name matching is case-insensitive
*/
@ApolloExperimental
fun List<HttpHeader>.get(name: String): String? {
Expand All @@ -51,6 +52,7 @@ private constructor(
val url: String,
val headers: List<HttpHeader>,
val body: HttpBody?,
val executionContext: ExecutionContext
) {

@JvmOverloads
Expand All @@ -67,12 +69,13 @@ private constructor(

/**
* The URL to send the request to.
* Must be conform to [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-2).
* Must conform to [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-2).
*/
private val url: String,
) {
private var body: HttpBody? = null
private val headers = mutableListOf<HttpHeader>()
private var executionContext = ExecutionContext.Empty

fun body(body: HttpBody) = apply {
this.body = body
Expand All @@ -86,17 +89,21 @@ private constructor(
this.headers.addAll(headers)
}

fun addExecutionContext(executionContext: ExecutionContext) = apply {
this.executionContext += executionContext
}

fun headers(headers: List<HttpHeader>) = apply {
this.headers.clear()
this.headers.addAll(headers)
}

@Suppress("DEPRECATION")
fun build() = HttpRequest(
method = method,
url = url,
headers = headers,
body = body,
executionContext = executionContext
)
}
}
Expand All @@ -106,7 +113,7 @@ private constructor(
*
* Specifying both [bodySource] and [bodyString] is invalid
*
* The [body] of a [HttpResponse] must always be closed if non null
* The [body] of a [HttpResponse] must always be closed if non-null
*/
class HttpResponse
private constructor(
Expand Down Expand Up @@ -177,7 +184,6 @@ private constructor(
}

fun build(): HttpResponse {
@Suppress("DEPRECATION")
return HttpResponse(
statusCode = statusCode,
headers = headers,
Expand Down
8 changes: 8 additions & 0 deletions libraries/apollo-runtime/api/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ public final class com/apollographql/apollo3/network/http/BatchingHttpIntercepto
}

public final class com/apollographql/apollo3/network/http/DefaultHttpEngine : com/apollographql/apollo3/network/http/HttpEngine {
public static final field Companion Lcom/apollographql/apollo3/network/http/DefaultHttpEngine$Companion;
public fun <init> (J)V
public synthetic fun <init> (JILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (JJ)V
Expand All @@ -212,13 +213,20 @@ public final class com/apollographql/apollo3/network/http/DefaultHttpEngine : co
public fun execute (Lcom/apollographql/apollo3/api/http/HttpRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class com/apollographql/apollo3/network/http/DefaultHttpEngine$Companion {
public final fun execute (Lokhttp3/Call$Factory;Lokhttp3/Request;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun toApolloHttpResponse (Lokhttp3/Response;)Lcom/apollographql/apollo3/api/http/HttpResponse;
public final fun toOkHttpRequest (Lcom/apollographql/apollo3/api/http/HttpRequest;)Lokhttp3/Request;
}

public final class com/apollographql/apollo3/network/http/HeadersInterceptor : com/apollographql/apollo3/network/http/HttpInterceptor {
public fun <init> (Ljava/util/List;)V
public fun intercept (Lcom/apollographql/apollo3/api/http/HttpRequest;Lcom/apollographql/apollo3/network/http/HttpInterceptorChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class com/apollographql/apollo3/network/http/HttpCall {
public fun <init> (Lcom/apollographql/apollo3/network/http/HttpEngine;Lcom/apollographql/apollo3/api/http/HttpMethod;Ljava/lang/String;)V
public final fun addExecutionContext (Lcom/apollographql/apollo3/api/ExecutionContext;)Lcom/apollographql/apollo3/network/http/HttpCall;
public final fun addHeader (Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/apollo3/network/http/HttpCall;
public final fun addHeaders (Ljava/util/List;)Lcom/apollographql/apollo3/network/http/HttpCall;
public final fun body (Lcom/apollographql/apollo3/api/http/HttpBody;)Lcom/apollographql/apollo3/network/http/HttpCall;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.apollographql.apollo3.network.http

import com.apollographql.apollo3.api.ExecutionContext
import com.apollographql.apollo3.api.http.HttpBody
import com.apollographql.apollo3.api.http.HttpHeader
import com.apollographql.apollo3.api.http.HttpMethod
Expand Down Expand Up @@ -30,7 +31,7 @@ interface HttpEngine {
}

/**
* @param timeoutMillis: The timeout interval to use when connecting or waiting for additional data.
* @param timeoutMillis The timeout interval to use when connecting or waiting for additional data.
*
* - on iOS (NSURLRequest), it is used to set `NSMutableURLRequest.setTimeoutInterval`
* - on Android (OkHttp), it is used to set both `OkHttpClient.connectTimeout` and `OkHttpClient.readTimeout`
Expand Down Expand Up @@ -60,6 +61,9 @@ class HttpCall(private val engine: HttpEngine, method: HttpMethod, url: String)
requestBuilder.addHeaders(headers)
}

fun addExecutionContext(executionContext: ExecutionContext) = apply {
requestBuilder.addExecutionContext(executionContext)
}
fun headers(headers: List<HttpHeader>) = apply {
requestBuilder.headers(headers)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import okio.BufferedSink
import okio.IOException
import java.util.concurrent.TimeUnit
Expand All @@ -35,75 +36,84 @@ actual class DefaultHttpEngine constructor(
.build()
)

actual override suspend fun execute(request: HttpRequest): HttpResponse = suspendCancellableCoroutine { continuation ->
val httpRequest = Request.Builder()
.url(request.url)
.headers(request.headers.toOkHttpHeaders())
.apply {
if (request.method == HttpMethod.Get) {
get()
} else {
val body = request.body
check(body != null) {
"HTTP POST requires a request body"
}
post(object : RequestBody() {
override fun contentType() = body.contentType.toMediaType()

override fun contentLength() = body.contentLength
actual override suspend fun execute(request: HttpRequest): HttpResponse {
return httpCallFactory.execute(request.toOkHttpRequest()).toApolloHttpResponse()
}

// This prevents OkHttp from reading the body several times (e.g. when using its logging interceptor)
// which could consume files when using Uploads
override fun isOneShot() = body is UploadsHttpBody
actual override fun dispose() {
}

override fun writeTo(sink: BufferedSink) {
body.writeTo(sink)
companion object {
fun HttpRequest.toOkHttpRequest(): Request {
return Request.Builder()
.url(url)
.headers(headers.toOkHttpHeaders())
.apply {
if (method == HttpMethod.Get) {
get()
} else {
val body = body
check(body != null) {
"HTTP POST requires a request body"
}
})
}
}
.build()
post(object : RequestBody() {
override fun contentType() = body.contentType.toMediaType()

override fun contentLength() = body.contentLength

val call = httpCallFactory.newCall(httpRequest)
continuation.invokeOnCancellation {
call.cancel()
// This prevents OkHttp from reading the body several times (e.g. when using its logging interceptor)
// which could consume files when using Uploads
override fun isOneShot() = body is UploadsHttpBody

override fun writeTo(sink: BufferedSink) {
body.writeTo(sink)
}
})
}
}
.build()
}

var exception: IOException? = null
val response = try {
call.execute()
} catch (e: IOException) {
exception = e
null
suspend fun Call.Factory.execute(request: Request): Response = suspendCancellableCoroutine { continuation ->
val call = newCall(request)
continuation.invokeOnCancellation {
call.cancel()
}

var exception: IOException? = null
val response = try {
call.execute()
} catch (e: IOException) {
exception = e
null
}

if (exception != null) {
continuation.resumeWithException(
ApolloNetworkException(
message = "Failed to execute GraphQL http network request",
platformCause = exception
)
)
return@suspendCancellableCoroutine
} else {
continuation.resume(response!!)
}
}

if (exception != null) {
continuation.resumeWithException(
ApolloNetworkException(
message = "Failed to execute GraphQL http network request",
platformCause = exception
fun Response.toApolloHttpResponse(): HttpResponse {
return HttpResponse.Builder(statusCode = code)
.body(body!!.source())
.addHeaders(
headers.let { headers ->
0.until(headers.size).map { index ->
HttpHeader(headers.name(index), headers.value(index))
}
}
)
)
return@suspendCancellableCoroutine
} else {
val result = Result.success(
HttpResponse.Builder(statusCode = response!!.code)
.body(response.body!!.source())
.addHeaders(
response.headers.let { headers ->
0.until(headers.size).map { index ->
HttpHeader(headers.name(index), headers.value(index))
}
}
)
.build()
)
continuation.resume(result.getOrThrow())
.build()
}
}

actual override fun dispose() {
}
}


Loading
Loading