Skip to content

Instantly share code, notes, and snippets.

@jleskovar-tyro
Created July 14, 2019 01:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jleskovar-tyro/620e18d05c69ee9568ece8609c21be66 to your computer and use it in GitHub Desktop.
Save jleskovar-tyro/620e18d05c69ee9568ece8609c21be66 to your computer and use it in GitHub Desktop.
Kotlin and AWS SDK v2 port of aws-request-signing-apache-interceptor
/**
* An [HttpRequestInterceptor] that signs requests using any AWS [Aws4Signer] and [AwsCredentialsProvider].
*
* NOTE: Ported to kotlin and v2 SDK from https://github.com/awslabs/aws-request-signing-apache-interceptor/
*/
class AwsRequestSigningInterceptor(
private val region: Region,
private val service: String,
private val signer: Aws4Signer,
private val awsCredentialsProvider: AwsCredentialsProvider
) : HttpRequestInterceptor {
override fun process(request: HttpRequest, context: HttpContext) {
val uriBuilder = URIBuilder(request.requestLine.uri)
// Copy Apache HttpRequest to an SDK request
val sdkRequest = createSignableRequest(context, request, uriBuilder)
// Sign it
val credentials = awsCredentialsProvider.resolveCredentials()
val signerParams = Aws4SignerParams.builder()
.signingRegion(region)
.signingName(service)
.awsCredentials(credentials)
.doubleUrlEncode(true)
.build()
val response = signer.sign(sdkRequest, signerParams)
// Now copy everything back
request.setHeaders(mapToHeaderList(response.headers()).toTypedArray())
if (request is HttpEntityEnclosingRequest) {
if (request.entity != null) {
val basicHttpEntity = BasicHttpEntity()
basicHttpEntity.content = response.contentStreamProvider().get().newStream()
request.entity = basicHttpEntity
}
}
}
private fun createSignableRequest(context: HttpContext, request: HttpRequest, uriBuilder: URIBuilder): SdkHttpFullRequest {
val sdkHttpFullRequest = SdkHttpFullRequest.builder()
(context.getAttribute(HTTP_TARGET_HOST) as? HttpHost)?.let {
sdkHttpFullRequest.uri(URI.create(it.toURI()))
}
val httpMethod = HttpMethodName.fromValue(request.requestLine.method)
sdkHttpFullRequest.method(SdkHttpMethod.fromValue(httpMethod.name))
sdkHttpFullRequest.encodedPath(uriBuilder.build().rawPath)
sdkHttpFullRequest.contentStreamProvider { ByteArrayInputStream(ByteArray(0)) }
(request as? HttpEntityEnclosingRequest)?.let {
it.entity?.content?.let { sdkHttpFullRequest.contentStreamProvider { it } }
}
uriBuilder.queryParams.forEach { sdkHttpFullRequest.appendRawQueryParameter(it.name, it.value) }
request.allHeaders.filter { !skipHeader(it) }.forEach { sdkHttpFullRequest.appendHeader(it.name, it.value) }
return sdkHttpFullRequest.build()
}
private fun mapToHeaderList(mapHeaders: MutableMap<String, MutableList<String>>): List<Header?> =
mapHeaders.map { BasicHeader(it.key, it.value.joinToString(",")) }
private fun skipHeader(header: Header): Boolean =
(("content-length".equals(header.name, ignoreCase = true) && "0" == header.value) // Strip Content-Length: 0
|| "host".equals(header.name, ignoreCase = true)) // Host comes from endpoint
}
@Khalilw1
Copy link

Khalilw1 commented Feb 18, 2020

Hello there,

Thank you so much because this helped us write our own adapter for v2. I just had one question since I am not familiar with Kotlin:

it.entity?.content?.let { sdkHttpFullRequest.contentStreamProvider { it } }

how is the conversion done here. Is it implicit?

@jleskovar-tyro
Copy link
Author

jleskovar-tyro commented Feb 18, 2020

Hi there,

This snippet is using Kotlin’s shorthand syntax for creating inline implementations of @FunctionalInterface interfaces using lambda syntax (see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions)

Here, the contentStreamProvider builder method takes in a ContentStreamProvider interface. This interface is marked as @FunctionalInterface. “{ it }” is defining a ContentStreamProvider whose newStream implementation (https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/ContentStreamProvider.html#newStream) returns “it”, which in this is input stream provided by the HttpEntity (HttpEntityEnclosingRequest.getEntity().getContent())

@Khalilw1
Copy link

I see thank you very much for your valuable input

@alxgrk
Copy link

alxgrk commented Sep 4, 2020

Thank you so much, it's really a pity that Amazon doesn't care about keeping their code/documentation up to date...
One remark: I think you can remove line 47 and instead use SdkHttpMethod.fromValue(request.requestLine.method) directly. This removes every to AWS-SDK-V1.

@Muenze
Copy link

Muenze commented Oct 7, 2021

Thank you so much, it's really a pity that Amazon doesn't care about keeping their code/documentation up to date... One remark: I think you can remove line 47 and instead use SdkHttpMethod.fromValue(request.requestLine.method) directly. This removes every to AWS-SDK-V1.

Also found that issue, it can be removed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment