All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.aws.smithy.kotlin.runtime.http.response.HttpResponse.kt Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */
package aws.smithy.kotlin.runtime.http.response

import aws.smithy.kotlin.runtime.InternalApi
import aws.smithy.kotlin.runtime.ProtocolResponse
import aws.smithy.kotlin.runtime.http.Headers
import aws.smithy.kotlin.runtime.http.HttpBody
import aws.smithy.kotlin.runtime.http.HttpStatusCode
import aws.smithy.kotlin.runtime.http.content.ByteArrayContent
import aws.smithy.kotlin.runtime.http.readAll
import aws.smithy.kotlin.runtime.io.*

/**
 * Immutable container for an HTTP response
 */
public sealed interface HttpResponse : ProtocolResponse {
    /**
     * The response status code
     */
    public val status: HttpStatusCode

    /**
     * The response headers
     */
    public val headers: Headers

    /**
     * The response body
     */
    public val body: HttpBody
}

/**
 * Use the default HTTP response implementation
 */
public fun HttpResponse(
    status: HttpStatusCode,
    headers: Headers = Headers.Empty,
    body: HttpBody = HttpBody.Empty,
): HttpResponse = DefaultHttpResponse(status, headers, body)

private data class DefaultHttpResponse(
    override val status: HttpStatusCode,
    override val headers: Headers,
    override val body: HttpBody,
) : HttpResponse {
    override val summary: String = "HTTP ${status.value} ${status.description}"
}

/**
 * Replace the response body
 */
public fun HttpResponse.copy(
    status: HttpStatusCode = this.status,
    headers: Headers = this.headers,
    body: HttpBody = this.body,
): HttpResponse = HttpResponse(status, headers, body)

/**
 * Get an HTTP header value by name. Returns the first header if multiple headers are set
 */
public fun ProtocolResponse.header(name: String): String? {
    val httpResp = this as? HttpResponse
    return httpResp?.headers?.get(name)
}

/**
 * Get all HTTP header values associated with the given name.
 */
public fun ProtocolResponse.getAllHeaders(name: String): List? {
    val httpResp = this as? HttpResponse
    return httpResp?.headers?.getAll(name)
}

/**
 * Get the HTTP status code of the response
 */
public fun ProtocolResponse.statusCode(): HttpStatusCode? {
    val httpResp = this as? HttpResponse
    return httpResp?.status
}

/**
 * Dump a debug description of the response. Either the original response or a copy will be returned to the caller
 * depending on if the body is consumed.
 *
 * @param dumpBody Flag controlling whether to also dump the body out. If true the body will be consumed and
 * replaced.
 */
@InternalApi
public suspend fun dumpResponse(response: HttpResponse, dumpBody: Boolean): Pair {
    val buffer = SdkBuffer()
    buffer.writeUtf8("HTTP ${response.status}\r\n")
    response.headers.forEach { key, values ->
        buffer.writeUtf8(values.joinToString(separator = ";", prefix = "$key: ", postfix = "\r\n"))
    }
    buffer.writeUtf8("\r\n")

    var respCopy = response
    if (dumpBody) {
        when (val body = response.body) {
            is HttpBody.Bytes -> buffer.write(body.bytes())
            is HttpBody.ChannelContent, is HttpBody.SourceContent -> {
                // consume the stream and replace the body. There isn't much rewinding we can do here, most engines
                // use a stream that reads right off the wire.
                val content = body.readAll()
                if (content != null) {
                    buffer.write(content)
                    val newBody = ByteArrayContent(content)
                    respCopy = response.copy(body = newBody)
                }
            }
            is HttpBody.Empty -> { } // nothing to dump
        }
    }

    return respCopy to buffer.readUtf8()
}

/**
 * Convert an HttpResponse to an [HttpResponseBuilder]
 */
public fun HttpResponse.toBuilder(): HttpResponseBuilder = when (this) {
    is HttpResponseBuilderView -> {
        check(allowToBuilder) { "This is an immutable HttpResponse that should not be converted to a builder" }
        builder
    }
    else -> {
        val resp = this
        HttpResponseBuilder().apply {
            status = resp.status
            headers.appendAll(resp.headers)
            body = resp.body
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy