Search code examples
scalaamazon-web-serviceslambdaaws-lambdaterraform

AWS Lambda scala app No public method named handle with appropriate method signature


I have a scala application which I have deployed on AWS lambda. Previously my Main class signature looked like:

object Main extends App {...}

With this I was able to provide a handler in the AWS Lambda definition which Lambda was happy with to run. An issue I was having was that the return of the lambda was always null.

To overcome this null issue when integrating with the API Gateway I've started using Lambda's java API. I've made Main a class which extends RequestHandler

package example

import com.typesafe.scalalogging.LazyLogging
import com.amazonaws.services.lambda.runtime.{Context, RequestHandler}

class Main extends RequestHandler[String, String] with ArgParser with LazyLogging  {

  override def handleRequest(input: String, context: Context): String = {
    val (wins, losses) = /* ... */
    s"""{"wins":$wins,"losses":$losses}"""
  }

If I provide the handler example.Main::handleRequest I get the following error:

(My actual handler contains the full package name which worked before I introduced the RequestHandler, so I'm confident it's not a mistyping on the handler.)

{
  "errorMessage": "No public method named handleRequest with appropriate method signature found on class class example.Main"
}

I'm using the following dependency:

"com.amazonaws"              % "aws-lambda-java-core"              % "1.2.0"

The definition of my lambda is defined in Terraform code here and my jar is stored in s3.


Solution

  • Through quite a bit of trial and error, and a post on reddit. I finally have a working format that allows me to execute my lambda in the lambda UI, providing plain JSON, as well as being able to trigger the lambda via the API Gateway, which was my main goal.

    class Main extends RequestStreamHandler {
    
      override def handleRequest(request: InputStream, 
                                    output: OutputStream, context: Context): Unit = {
    
        val input = scala.io.Source.fromInputStream(request).mkString
    
        val (wins, losses) = /* ... */
        output.write(s"""{"wins":$wins,"losses":$losses}""".getBytes("UTF-8"))
      }
    }
    

    This provided me with a working lambda function. However I still ran into issues when invoking the API Gateway.

    Manually creating an API Gateway API, specifying an integration with my Lambda function, worked straight away. This highlighted an issue with my Terraform deployment.

    Before I was using Use Lambda Proxy integration which was causing issues. Unchecking this box in the UI or , if using Terraform, setting type = "AWS" in aws_api_gateway_integration fixed this issue.

    The next problem was with the JSON being provided in the request.

    In the Integration Request section in the gateway UI, under Mapping Templates, I selected:

    Request Body Passthrough: When no template matches the request Content-Type header

    In Terraform this can be set on aws_api_gateway_integration as:

    passthrough_behavior = "WHEN_NO_MATCH"

    With this I was able to use the circe JSON library to decode the the InputStream.