Skip to content

Commit

Permalink
Aws SDK client (#486)
Browse files Browse the repository at this point in the history
  • Loading branch information
daviddenton committed Sep 17, 2020
1 parent d0e06cf commit ca258ae
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@ changes with their rationale when appropriate:
### v3.261.0 (uncut)
- **http4k-*** : Upgrade some dependency versions.
- **http4k-*** : Remove some example code which was mistakenly added to some main src dirs. No impact on anything other than JAR size.
- **http4k-aws*** : Add pluggable Amazon SDK client, allowing you to plug an HttpHandler into the Amazon SDK.

### v3.260.0
- **http4k-*** : Upgrade some dependency versions.
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Expand Up @@ -239,6 +239,7 @@ dependencies {
compile project(":http4k-testing-servirtium")
compile project(":http4k-testing-webdriver")

testCompile Libs.s3
testCompile Config.TestDependencies
testCompile project(path: ":http4k-testing-servirtium", configuration: 'testArtifacts')
testCompile project(path: ":http4k-serverless-gcf", configuration: 'testArtifacts')
Expand Down
13 changes: 12 additions & 1 deletion buildSrc/src/main/kotlin/Libs.kt
Expand Up @@ -240,6 +240,12 @@ object Libs {
const val jackson_databind: String = "com.fasterxml.jackson.core:jackson-databind:" +
Versions.jackson_databind

/**
* https://aws.amazon.com/sdkforjava
*/
const val http_client_spi: String = "software.amazon.awssdk:http-client-spi:" +
Versions.http_client_spi

/**
* http://hc.apache.org/httpcomponents-asyncclient
*/
Expand Down Expand Up @@ -314,7 +320,7 @@ object Libs {
const val bunting4k: String = "dev.forkhandles:bunting4k:" + Versions.bunting4k

/**
* http://hc.apache.org/httpcomponents-core-ga
* https://hc.apache.org/httpcomponents-core-5.0.x/
*/
const val httpcore5: String = "org.apache.httpcomponents.core5:httpcore5:" + Versions.httpcore5

Expand Down Expand Up @@ -384,4 +390,9 @@ object Libs {
* https://github.com/douglascrockford/JSON-java
*/
const val json: String = "org.json:json:" + Versions.json

/**
* https://aws.amazon.com/sdkforjava
*/
const val s3: String = "software.amazon.awssdk:s3:" + Versions.s3
}
14 changes: 9 additions & 5 deletions buildSrc/src/main/kotlin/Versions.kt
Expand Up @@ -24,9 +24,9 @@ object Versions {

const val org_eclipse_jetty: String = "9.4.31.v20200723"

const val org_junit_jupiter: String = "5.6.2"
const val org_junit_jupiter: String = "5.7.0"

const val io_undertow: String = "2.1.3.Final"
const val io_undertow: String = "2.2.0.Final"

const val io_ktor: String = "1.3.2" // available: "1.4.0"

Expand All @@ -40,13 +40,13 @@ object Versions {

const val javax_websocket_server_impl: String = "9.4.31.v20200723"

const val kotest_assertions_core_jvm: String = "4.1.3" // available: "4.2.4"
const val kotest_assertions_core_jvm: String = "4.1.3" // available: "4.2.5"

const val coveralls_gradle_plugin: String = "2.8.3"

const val functions_framework_api: String = "1.0.2"

const val aws_lambda_java_events: String = "3.2.0"
const val aws_lambda_java_events: String = "3.3.0"

const val jackson_module_kotlin: String = "2.11.2"

Expand All @@ -60,6 +60,8 @@ object Versions {

const val jackson_databind: String = "2.11.2"

const val http_client_spi: String = "2.14.19"

const val httpasyncclient: String = "4.1.4"

const val micrometer_core: String = "1.5.4"
Expand Down Expand Up @@ -104,7 +106,7 @@ object Versions {

const val jade4j: String = "1.3.2"

const val okhttp: String = "4.8.1"
const val okhttp: String = "4.8.1" // available: "4.9.0" - 1.4.10

const val pebble: String = "3.1.4"

Expand All @@ -118,6 +120,8 @@ object Versions {

const val json: String = "20200518"

const val s3: String = "2.14.19"

/**
* Current version: "6.6"
* See issue 19: How to update Gradle itself?
Expand Down
1 change: 1 addition & 0 deletions http4k-aws/build.gradle
Expand Up @@ -2,6 +2,7 @@ description = 'Http4k AWS integration and request signing'

dependencies {
compile project(":http4k-core")
implementation Libs.http_client_spi

testCompile project(path: ":http4k-core", configuration: 'testArtifacts')
testCompile project(":http4k-client-apache")
Expand Down
46 changes: 46 additions & 0 deletions http4k-aws/src/main/kotlin/org/http4k/aws/AwsSdkClient.kt
@@ -0,0 +1,46 @@
package org.http4k.aws

import org.http4k.core.Body
import org.http4k.core.Body.Companion.EMPTY
import org.http4k.core.HttpHandler
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Uri
import software.amazon.awssdk.http.AbortableInputStream.create
import software.amazon.awssdk.http.ExecutableHttpRequest
import software.amazon.awssdk.http.HttpExecuteRequest
import software.amazon.awssdk.http.HttpExecuteResponse
import software.amazon.awssdk.http.SdkHttpClient
import software.amazon.awssdk.http.SdkHttpFullRequest
import software.amazon.awssdk.http.SdkHttpFullResponse.builder

class AwsSdkClient(private val http: HttpHandler) : SdkHttpClient {
override fun close() {
}

override fun prepareRequest(request: HttpExecuteRequest) = object : ExecutableHttpRequest {
override fun call() = http(request.fromAws()).asAws()
override fun abort() {}
}
}

private fun HttpExecuteRequest.fromAws() = with(httpRequest()) {
val init = Request(Method.valueOf(method().name), Uri.of(uri.toString()))
.headers(headers().entries.flatMap { (name, values) -> values.map { name to it } })

when (this) {
is SdkHttpFullRequest ->
init.body(contentStreamProvider().map { Body(it.newStream()) }.orElse(EMPTY))
else -> init
}
}

private fun Response.asAws() = HttpExecuteResponse.builder()
.response(builder()
.statusCode(status.code)
.statusText(status.description)
.headers(headers.groupBy { it.first }.mapValues { it.value.map { it.second } })
.content(create(body.stream))
.build())
.build()
71 changes: 71 additions & 0 deletions http4k-aws/src/test/kotlin/org/http4k/aws/AwsSdkClientTest.kt
@@ -0,0 +1,71 @@
package org.http4k.aws

import com.natpryce.hamkrest.MatchResult
import com.natpryce.hamkrest.Matcher
import com.natpryce.hamkrest.and
import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.equalTo
import com.natpryce.hamkrest.has
import org.http4k.core.Method.POST
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.I_M_A_TEAPOT
import org.junit.jupiter.api.Test
import software.amazon.awssdk.http.AbortableInputStream
import software.amazon.awssdk.http.HttpExecuteRequest.builder
import software.amazon.awssdk.http.SdkHttpFullRequest
import software.amazon.awssdk.http.SdkHttpFullResponse
import software.amazon.awssdk.http.SdkHttpMethod
import software.amazon.awssdk.http.SdkHttpResponse
import java.net.URL
import java.util.Optional

class AwsSdkClientTest {

@Test
fun `converts formats correctly`() {
val headers = listOf("bar" to null, "foo" to "bar1")
val request = Request(POST, "https://foobar/123")
.query("foo", "bar1")
.query("foo", "bar2")
.headers(headers)
.body("hello")

val response = Response(I_M_A_TEAPOT).headers(headers).body("world")

val client = AwsSdkClient {
assertThat(it.toString(), equalTo(request.toString()))
response
}

val out = client.prepareRequest(
builder().request(
SdkHttpFullRequest.builder()
.method(SdkHttpMethod.POST)
.headers(headers.groupBy { it.first }
.mapValues { it.value.map { it.second } })
.uri(URL(request.uri.toString()).toURI())
.putRawQueryParameter("foo", listOf("bar1", "bar2"))
.contentStreamProvider { request.body.stream }.build()
).build()
).call()

assertThat(out.httpResponse() as SdkHttpFullResponse,
has(SdkHttpResponse::statusCode, equalTo(I_M_A_TEAPOT.code))
.and(
has(SdkHttpFullResponse::headers, equalTo(mapOf(
"bar" to listOf(null),
"foo" to listOf("bar1")
)))
)
.and(has(SdkHttpFullResponse::content, object : Matcher<Optional<AbortableInputStream>> {
override val description = "same content"

override fun invoke(actual: Optional<AbortableInputStream>): MatchResult {
val content = actual.get().reader().readText()
return if (content == response.bodyString()) MatchResult.Match else MatchResult.Mismatch(content)
}
}))
)
}
}
18 changes: 18 additions & 0 deletions src/docs/guide/modules/aws/example_sdk.kt
@@ -0,0 +1,18 @@
package guide.modules.aws

import org.http4k.aws.AwsSdkClient
import org.http4k.client.OkHttp
import org.http4k.core.then
import org.http4k.filter.DebuggingFilters
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.CreateBucketRequest

fun main() {
val fakeS3 = DebuggingFilters.PrintRequestAndResponse().then(OkHttp())

val s3 = S3Client.builder()
.httpClient(AwsSdkClient(fakeS3))
.build()

s3.createBucket(CreateBucketRequest.builder().bucket("hello").build())
}

0 comments on commit ca258ae

Please sign in to comment.