snowe
12/05/2023, 10:22 PM"java.lang.IllegalStateException: method is invalid\n\tat org.http4k.serverless.ApiGatewayV1AwsHttpAdapter.toHttp4kRequest(ApiGatewayV1.kt:54)\n\tat org.http4k.serverless.ApiGatewayV1AwsHttpAdapter.invoke(ApiGatewayV1.kt:61)\n\tat org.http4k.serverless.ApiGatewayV1AwsHttpAdapter.invoke(ApiGatewayV1.kt:51)\n\tat org.http4k.serverless.ApiGatewayFnLoader.invoke$lambda$0(ApiGatewayFnLoader.kt:24)\n\tat org.http4k.serverless.AwsLambdaRuntime$asServer$1$start$1$1.invoke(AwsLambdaRuntime.kt:44)\n\tat org.http4k.serverless.AwsLambdaRuntime$asServer$1$start$1$1.invoke(AwsLambdaRuntime.kt:40)\n\tat kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)\n"
val http4kApp = routes(
"/echo/{message:.*}" bind GET to {
Response(OK).body(
it.path("message") ?: "(nothing to echo, use /echo/<message>)"
)
},
"/" bind GET to { Response(OK).body("ok") }
)
fun main() {
ApiGatewayV1FnLoader(http4kApp).asServer(AwsLambdaRuntime()).start()
}
s4nchez
12/05/2023, 10:31 PMI will not be setting up my lambda with routes through apigateway, it will just be direct lambda callsHow are you invoking the lambda? By adding an ApiGateway adapter, the function will expect a JSON payload with the request details and will translate that into an http4k Request. If you're invoking the function outside the ApiGateway, you have to produce the payload yourself
s4nchez
12/05/2023, 10:32 PMsnowe
12/05/2023, 10:43 PMs4nchez
12/05/2023, 10:54 PMsnowe
12/05/2023, 10:56 PMsnowe
12/05/2023, 10:57 PM@Named("test")
public class TestLambda implements RequestHandler<InputObject, OutputObject> {
}
@Named("stream")
public class StreamLambda implements RequestStreamHandler {
}
where RequestHandler
automatically deserializes using jackson (🤮 ) and RequestStreamHandler
just hands you the object stream for you to deserialize yourself.snowe
12/05/2023, 11:03 PMval http4kApp = routes(
"/echo/{message:.*}" bind GET to {
Response(OK).body(
it.path("message") ?: "(nothing to echo, use /echo/<message>)"
)
},
"/external" bind GET to {
Response(OK).body(JavaHttpClient()(Request(GET, "<http://httpbin.org/get>")).bodyString())
},
"/" bind GET to { Response(OK).body("Hello from Lambda URL!").header("content-type", "text/html; charset=utf-8") }
)
@Suppress("unused")
class HelloServerlessHttp4k : ApiGatewayV2LambdaFunction(http4kApp)
gives me
Error: Main method not found in class com.sunrun.pricing.lambda.HelloServerlessHttp4k, please define the main method as:
public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application
INIT_REPORT Init Duration: 470.93 ms Phase: init Status: error Error Type: Runtime.ExitError
I actually did expect this one to work so not sure why it’s not.dave
12/06/2023, 4:29 AMdave
12/06/2023, 4:31 AMdave
12/06/2023, 4:33 AMdave
12/06/2023, 4:36 AMAndrew O'Hara
12/06/2023, 4:33 PMinvoke
operation, then is it really necessary to run a web application in your function?
Why not use the quarkus handlers, define your own input and output DTO, and treat it like RPC? If you don't want to be using Quarkus's Jackson to perform the (de)serialization for you, then you can always hook the InputStream and OutputStream into your JSON marshaller.snowe
12/06/2023, 5:33 PMIt may help you to check out the Aws custom runtime that we builtI forgot to update this yesterday but I did do that. This does pretty much exactly what I want:
object TestLambdaAdapter : AwsHttpAdapter<Map<String, Any>, Map<String, Any>> {
private fun Map<String, Any>.toHttp4kRequest(): Request {
return Request(POST, Uri.of("")).body(toBody())
}
override fun invoke(req: Map<String, Any>, ctx: Context): Request = req.toHttp4kRequest()
override fun invoke(resp: Response): Map<String, Any> {
val nonCookies = resp.headers.filterNot { it.first.lowercase(Locale.getDefault()) == "set-cookie" }
return Json.parseToJsonElement(resp.body.payload.asString()).jsonObject.toMap()
}
}
Http4k uses Moshi under the covers and you can also combine that with kotshi to avoid any reflection at all but also get generated adaptersinteresting. I’ll have to look at that.
Why not use the quarkus handlers, define your own input and output DTO, and treat it like RPC? If you don’t want to be using Quarkus’s Jackson to perform the (de)serialization for you, then you can always hook the InputStream and OutputStream into your JSON marshaller.I’m trying to see if http4k is faster than quarkus due to having a smaller final function size. Smaller package should result in better cold start times.
snowe
12/06/2023, 5:38 PMAndrew O'Hara
12/06/2023, 7:57 PMI’m trying to see if http4k is faster than quarkus due to having a smaller final function size. Smaller package should result in better cold start times.Ah, I see! So this still doesn't mean you need to use Http4k as a web application. See how to use http4k-serverless as an event function. You're still taking advantage of the lighter environment, but don't need to build a whole web application to do it. I don't see an example that returns a synchronous response, but you should be able to deduce how to accomplish it.
snowe
12/06/2023, 7:58 PMsnowe
12/06/2023, 7:58 PMdave
12/06/2023, 8:09 PMsnowe
12/11/2023, 7:50 PM// This class is the entry-point for the Lambda function call - configure it when deploying
class EventFunction : AwsLambdaEventFunction(EventFnLoader(JavaHttpClient()))
as the entrypoint, but doing so results in errors stating I’m missing a main method. Not doing so results in only the main method being run (and subsequently blowing up, not sure why).dave
12/11/2023, 7:51 PMdave
12/11/2023, 7:51 PMdave
12/11/2023, 7:51 PMdave
12/11/2023, 7:52 PMsnowe
12/11/2023, 7:52 PMdave
12/11/2023, 7:52 PMdave
12/11/2023, 7:52 PMsnowe
12/11/2023, 7:52 PMmain()
?dave
12/11/2023, 7:52 PMpackage guide.tutorials.going_native_with_graal_on_aws_lambda
import org.http4k.core.Method.GET
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.routing.bind
import org.http4k.routing.path
import org.http4k.routing.routes
import org.http4k.serverless.ApiGatewayV2FnLoader
import org.http4k.serverless.AwsLambdaRuntime
import org.http4k.serverless.asServer
val http4kApp = routes(
"/echo/{message:.*}" bind GET to {
Response(OK).body(
it.path("message") ?: "(nothing to echo, use /echo/<message>)"
)
},
"/" bind GET to { Response(OK).body("ok") }
)
fun main() {
ApiGatewayV2FnLoader(http4kApp).asServer(AwsLambdaRuntime()).start()
}
dave
12/11/2023, 7:53 PMsnowe
12/11/2023, 7:53 PMdave
12/11/2023, 7:53 PMdave
12/11/2023, 7:54 PMthread {
do {
runtime.nextInvocation().use { nextInvocation ->
try {
lambda(nextInvocation.body.stream, LambdaEnvironmentContext(nextInvocation, env)).use {
runtime.success(nextInvocation, it)
}
} catch (e: Exception) {
runtime.error(nextInvocation, e)
}
}
} while (!done.get())
}
dave
12/11/2023, 7:54 PMlambda(nextInvocation.body.stream, LambdaEnvironmentContext(nextInvocation, env))
dave
12/11/2023, 7:54 PMsnowe
12/11/2023, 7:54 PMsnowe
12/11/2023, 7:55 PMdave
12/11/2023, 7:55 PMdave
12/11/2023, 7:55 PMdave
12/11/2023, 7:55 PMdave
12/11/2023, 7:55 PMsnowe
12/11/2023, 7:56 PMdave
12/11/2023, 7:56 PMdave
12/11/2023, 7:56 PMdave
12/11/2023, 7:56 PMdave
12/11/2023, 7:56 PMdave
12/11/2023, 7:56 PMdave
12/11/2023, 7:57 PMdave
12/11/2023, 7:57 PMsnowe
12/11/2023, 7:57 PMsnowe
12/11/2023, 7:57 PMdave
12/11/2023, 7:58 PMdave
12/11/2023, 7:58 PMdave
12/11/2023, 7:59 PMsnowe
12/11/2023, 7:59 PMINIT_REPORT Init Duration: 2871.69 ms Phase: invoke Status: error Error Type: Runtime.ExitError
START RequestId: 6cd6ba0a-8de3-4b36-ae17-4dcc9d4eb848 Version: $LATEST
RequestId: 6cd6ba0a-8de3-4b36-ae17-4dcc9d4eb848 Error: Runtime exited without providing a reason
Runtime.ExitError
snowe
12/11/2023, 7:59 PMinline fun <reified In : Any, Out : Any> FnLoader(
autoMarshalling: AutoMarshalling = AwsLambdaMoshi,
noinline makeHandler: (Map<String, String>) -> FnHandler<In, Context, Out>
) = AutoMarshallingFnLoader(autoMarshalling, In::class, makeHandler)
// This is the handler for the incoming AWS SQS event. It's just a function so you can call it without any infrastructure
fun EventFnHandler(http: HttpHandler) =
FnHandler { e: String, _: Context ->
println("In the FnHandler EventFnHandler")
"processed ${e} messages"
}
// We can add filters to the FnHandler if we want to - in this case print the transaction (with the letency).
val loggingFunction = ReportFnTransaction<String, Context, String> { tx ->
println(tx)
}
// The FnLoader is responsible for constructing the handler and for handling the serialisation of the request and response
fun EventFnLoader(http: HttpHandler) = FnLoader { env: Map<String, String> ->
loggingFunction.then(EventFnHandler(http))
}
// This class is the entry-point for the Lambda function call - configure it when deploying
class EventFunction : AwsLambdaEventFunction(EventFnLoader(JavaHttpClient()))
fun main() {
// this server receives the reversed event
val receivingServer = { req: Request ->
println(req.bodyString())
Response(OK)
}.asServer(SunHttp(8080)).start()
println("i'm here. dfdlkjdf")
fun runLambdaInMemoryOrForTesting() {
println("RUNNING In memory:")
val app = EventFnHandler(JavaHttpClient())
app("testing?", mock())
}
fun runLambdaAsAwsWould() {
println("RUNNING as AWS would invoke the function:")
val out = ByteArrayOutputStream()
EventFunction().handleRequest(AwsLambdaMoshi.asInputStream("""{"event": "value"}"""), out, mock())
// the response is empty b
println(out.toString())
}
runLambdaInMemoryOrForTesting()
println("I'm here. ")
runLambdaAsAwsWould()
println("run lambda as aws would?")
receivingServer.stop()
}
snowe
12/11/2023, 8:00 PMobject AwsLambdaMoshi : ConfigurableMoshi(
Moshi.Builder()
.addLast(MapAdapter)
.addLast(ThrowableAdapter)
.addLast(ListAdapter)
.addLast(EventAdapter)
.asConfigurable(MetadataKotlinJsonAdapterFactory())
.withStandardMappings()
.done()
)
snowe
12/11/2023, 8:01 PMdave
12/11/2023, 8:06 PMfun MyLoader() = FnLoader {
FnHandler { i: InputStream, c: Context ->
"helloworld".byteInputStream()
}
}
fun main() {
MyLoader().asServer(AwsLambdaRuntime())
}
dave
12/11/2023, 8:06 PMdave
12/11/2023, 8:07 PMsnowe
12/11/2023, 8:10 PMasServer
on that. And now AwsLambdaRuntime
makes more sense. Thanks for all the help!snowe
12/11/2023, 8:45 PMHttp4k - Starting Lambda Runtime
INIT_REPORT Init Duration: 203.51 ms Phase: init Status: error Error Type: Runtime.ExitError
Http4k - Starting Lambda Runtime
INIT_REPORT Init Duration: 2456.91 ms Phase: invoke Status: error Error Type: Runtime.ExitError
START RequestId: 2119a44d-41ff-4293-a6fd-8d4020a0434b Version: $LATEST
RequestId: 2119a44d-41ff-4293-a6fd-8d4020a0434b Error: Runtime exited without providing a reason
Runtime.ExitError
END RequestId: 2119a44d-41ff-4293-a6fd-8d4020a0434b
REPORT RequestId: 2119a44d-41ff-4293-a6fd-8d4020a0434b Duration: 2477.70 ms Billed Duration: 2478 ms Memory Size: 128 MB Max Memory Used: 31 MB
XRAY TraceId: 1-65777453-773dc56472227f34768bb21d SegmentId: 0cf810c11e5ead88 Sampled: true
enum class Color{
black,grey,white,brown,splotchy;
}
data class MoshiCat(val color: Color, val name: String)
fun MyLoader() = FnLoader {
FnHandler { i: InputStream, c: Context ->
println("Http4k - Running the function")
try {
val out = MoshiXLite.asA<MoshiCat>(i)
.also(::println)
} catch (e: Exception){
println(e)
}
}
}
fun main() {
println("Http4k - Starting Lambda Runtime")
MyLoader().asServer(AwsLambdaRuntime())
}
dave
12/11/2023, 8:45 PMdave
12/11/2023, 8:45 PMsnowe
12/11/2023, 8:46 PMsnowe
12/11/2023, 8:46 PMdave
12/11/2023, 8:46 PMfun main() {
MyLoader().asServer(AwsLambdaRuntime(http = JavaHttpClient().debug()))
}
dave
12/11/2023, 8:47 PMsnowe
12/11/2023, 8:48 PMsnowe
12/11/2023, 9:30 PM"java.lang.IllegalArgumentException: Platform class java.io.InputStream requires explicit JsonAdapter to be registered\n\tat com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:76)\n\tat com.squareup.moshi.Moshi.adapter(Moshi.java:146)\n\tat com.squareup.moshi.Moshi.adapter(Moshi.java:106)\n\tat com.squareup.moshi.Moshi.adapter(Moshi.java:80)\n\tat org.http4k.format.ConfigurableMoshi.adapterFor(ConfigurableMoshi.kt:93)\n\tat org.http4k.format.ConfigurableMoshi.asA(ConfigurableMoshi.kt:97)\n\tat org.http4k.serverless.AutoMarshallingFnLoader.invoke$lambda$0(AutoMarshallingFnLoader.kt:19)\n\tat org.http4k.serverless.AwsLambdaRuntime$asServer$1$start$1$1.invoke(AwsLambdaRuntime.kt:44)\n\tat org.http4k.serverless.AwsLambdaRuntime$asServer$1$start$1$1.invoke(AwsLambdaRuntime.kt:40)\n\tat kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)\n\tat
indicating that I don’t have an adaptor for this, but I would expect that to be built in somewhere. I also tried just marshalling straight to an object but that results in a different error message, indicating I should have set up KotlinJsonAdapterFactory
even though I have done so.
"java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.sunrun.pricing.lambda.MoshiCat. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapterFactory from the moshi-kotlin artifact or use code gen from the moshi-kotlin-codegen artifact.\n\tat com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:98)\n\tat com.squareup.moshi.Moshi.adapter(Moshi.java:146)\n\tat com.squareup.moshi.Moshi.adapter(Moshi.java:106)\n\tat com.squareup.moshi.Moshi.adapter(Moshi.java:80)\n\tat org.http4k.format.ConfigurableMoshi.adapterFor(ConfigurableMoshi.kt:93)\n\tat org.http4k.format.ConfigurableMoshi.asA(ConfigurableMoshi.kt:97)\n\tat org.http4k.serverless.AutoMarshallingFnLoader.invoke$lambda$0(AutoMarshallingFnLoader.kt:19)\n\tat org.http4k.serverless.AwsLambdaRuntime$asServer$1$start$1$1.invoke(AwsLambdaRuntime.kt:44)\n\tat org.http4k.serverless.AwsLambdaRuntime$asServer$1$start$1$1.invoke(AwsLambdaRuntime.kt:40)\n\tat kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)\n\tat
dave
12/11/2023, 9:31 PMoverride fun <T : Any> asA(input: InputStream, target: KClass<T>): T = adapterFor(target).fromJson(
input.source().buffer()
)!!
dave
12/11/2023, 9:32 PMMoshi.asA(input, MyThang::class)
snowe
12/11/2023, 9:33 PMfun MyLoader() = MoshiFnLoader {
FnHandler { i: MoshiCat, c: Context ->
println("Http4k - Running the function")
"asldkfj"
}
}
snowe
12/11/2023, 9:33 PMsnowe
12/11/2023, 9:33 PMdave
12/11/2023, 9:34 PMdave
12/11/2023, 9:34 PMdave
12/11/2023, 9:35 PMsnowe
12/11/2023, 9:35 PMsnowe
12/11/2023, 9:35 PMdave
12/11/2023, 9:35 PMsnowe
12/11/2023, 9:36 PMsnowe
12/11/2023, 9:36 PMdave
12/11/2023, 9:36 PMdave
12/11/2023, 9:37 PMdave
12/11/2023, 9:37 PMdave
12/11/2023, 9:37 PMsnowe
12/11/2023, 9:37 PMdave
12/11/2023, 9:37 PMdave
12/11/2023, 9:38 PMdave
12/11/2023, 9:39 PMsnowe
12/11/2023, 9:52 PMsnowe
12/11/2023, 9:56 PMsnowe
12/11/2023, 9:56 PMdave
12/11/2023, 9:57 PMdave
12/11/2023, 9:57 PMsnowe
12/11/2023, 9:58 PMdave
12/11/2023, 10:18 PMsnowe
12/11/2023, 10:23 PMdave
12/11/2023, 10:26 PMdave
12/11/2023, 10:26 PMsnowe
12/11/2023, 10:26 PM