Files
swift-aws-lambda-runtime/Sources/AWSLambdaRuntime/ControlPlaneRequestEncoder.swift
T
Sébastien Stormacq e786f2f620 fix: [core] Hard code version number in user agent string (#98) (#533)
Add a hard coded version number to the user agent string, for an
eventual identification by the Lambda service

### Motivation:

It's [an
issue](https://github.com/swift-server/swift-aws-lambda-runtime/issues/108)
that was open more than 5 years ago and was never addressed. At the
time, the consensus was to pickup a version number for the Package.swift
file and the maintainer at the time decided to wait for Swift to
implement this.
Five years later, and several major version of Swift later, this is
still not available. I decided to move on and implement a less optimal
solution. This can be replaced in the future if package version ever
becomes part of Package.swift.

### Modifications:

Add a version enum to isolate the versioning in one place. I decided to
keep it simple and not over engineering it with major, minor, patch and
pre-release. At the time, it's a simple string. This is all what we need
for usage in the user agent string.

### Result:

User agent now identifies as `Swift-Lambda/2,0` instead of
`Swift-Lambda/unknown`
2025-07-21 15:15:02 +02:00

132 lines
5.1 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2021 Apple Inc. and the SwiftAWSLambdaRuntime project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIOCore
struct ControlPlaneRequestEncoder: _EmittingChannelHandler {
typealias OutboundOut = ByteBuffer
private var host: String
private var byteBuffer: ByteBuffer!
init(host: String) {
self.host = host
}
mutating func writeRequest(
_ request: ControlPlaneRequest,
context: ChannelHandlerContext,
promise: EventLoopPromise<Void>?
) {
self.byteBuffer.clear(minimumCapacity: self.byteBuffer.storageCapacity)
switch request {
case .next:
self.byteBuffer.writeString(.nextInvocationRequestLine)
self.byteBuffer.writeHostHeader(host: self.host)
self.byteBuffer.writeString(.userAgentHeader)
self.byteBuffer.writeString(.CRLF) // end of head
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
context.flush()
case .invocationResponse(let requestID, let payload):
let contentLength = payload?.readableBytes ?? 0
self.byteBuffer.writeInvocationResultRequestLine(requestID)
self.byteBuffer.writeHostHeader(host: self.host)
self.byteBuffer.writeString(.userAgentHeader)
self.byteBuffer.writeContentLengthHeader(length: contentLength)
self.byteBuffer.writeString(.CRLF) // end of head
if let payload = payload, contentLength > 0 {
context.write(self.wrapOutboundOut(self.byteBuffer), promise: nil)
context.write(self.wrapOutboundOut(payload), promise: promise)
} else {
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
}
context.flush()
case .invocationError(let requestID, let errorMessage):
let payload = errorMessage.toJSONBytes()
self.byteBuffer.writeInvocationErrorRequestLine(requestID)
self.byteBuffer.writeContentLengthHeader(length: payload.count)
self.byteBuffer.writeHostHeader(host: self.host)
self.byteBuffer.writeString(.userAgentHeader)
self.byteBuffer.writeString(.unhandledErrorHeader)
self.byteBuffer.writeString(.CRLF) // end of head
self.byteBuffer.writeBytes(payload)
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
context.flush()
case .initializationError(let errorMessage):
let payload = errorMessage.toJSONBytes()
self.byteBuffer.writeString(.runtimeInitErrorRequestLine)
self.byteBuffer.writeContentLengthHeader(length: payload.count)
self.byteBuffer.writeHostHeader(host: self.host)
self.byteBuffer.writeString(.userAgentHeader)
self.byteBuffer.writeString(.unhandledErrorHeader)
self.byteBuffer.writeString(.CRLF) // end of head
self.byteBuffer.writeBytes(payload)
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
context.flush()
}
}
mutating func writerAdded(context: ChannelHandlerContext) {
self.byteBuffer = context.channel.allocator.buffer(capacity: 256)
}
mutating func writerRemoved(context: ChannelHandlerContext) {
self.byteBuffer = nil
}
}
extension String {
static let CRLF: String = "\r\n"
static let userAgent = "Swift-Lambda/\(Version.current)"
static let userAgentHeader: String = "user-agent: \(userAgent)\r\n"
static let unhandledErrorHeader: String = "lambda-runtime-function-error-type: Unhandled\r\n"
static let nextInvocationRequestLine: String =
"GET /2018-06-01/runtime/invocation/next HTTP/1.1\r\n"
static let runtimeInitErrorRequestLine: String =
"POST /2018-06-01/runtime/init/error HTTP/1.1\r\n"
}
extension ByteBuffer {
fileprivate mutating func writeInvocationResultRequestLine(_ requestID: String) {
self.writeString("POST /2018-06-01/runtime/invocation/")
self.writeString(requestID)
self.writeString("/response HTTP/1.1\r\n")
}
fileprivate mutating func writeInvocationErrorRequestLine(_ requestID: String) {
self.writeString("POST /2018-06-01/runtime/invocation/")
self.writeString(requestID)
self.writeString("/error HTTP/1.1\r\n")
}
fileprivate mutating func writeHostHeader(host: String) {
self.writeString("host: ")
self.writeString(host)
self.writeString(.CRLF)
}
fileprivate mutating func writeContentLengthHeader(length: Int) {
self.writeString("content-length: ")
self.writeString("\(length)")
self.writeString(.CRLF)
}
}