As you probably know, the Amazon API Gateway could be a good replacement for the old-fashioned web application.

Web application example on amazon serverless stack! Image from Amazon doc.

This is a short example showing the API Gateway stage variables usage. This is not a deep documentation of the entire functional, which provides AWS API Gateway, but just one of many examples of particular feature.

If you don’t want to complete manually the steps from this tutorial, you can just clone the already finished example from here:

1
git clone https://github.com/Relaximus/lambda-stage-var-example.git

Setup requirements

Before get started, let’s prepare all the tools, which we’ll need.

First of all, you have to install AWS CLI (Amazon command line). You will need it anyway if you are going to study AWS services further. AWS CLI requires the free aws account, so register a new account or use one of yours.

You also will need the S3 backet for the deployment purposes. You can use one of yours or create a new one. Fortunately, it is easy to do with the aws cli:

1
aws s3api create-bucket --bucket lambda-fucntions --region eu-central-1

use your bucket name and region.

Besides the aws cli, if you for some reason still don’t have a Maven installed, please do that also.

Webhook & API Gateway

Ok, now when everything is settled, let me introduce the case, which we are going to solve in this tutorial.

Imagine the case, when your serverless application uses a webhook approach. It provides the url to the caller site, which could be called to notify the application with some event, for instance, that the job is done.

1
2
3
4
Client->Lambda: Give me your url to notify you
Lambda->Client: Here it is: http://my-url.com/notification
Note right of Client: Ok, job is done
Client-->Lambda: POST http://my-url.com/notification

tip

Webhooks in serverless architecture can decrease the total cost of the service hosting.

To provide the URL, you need to know it first. And here comes the API Gateway stage variables. During deploy process we can configure the variable for every API, containing its own url. And the only thing which we need to do is to pass to the the application this variable. Configuring during deploy saves startup time as your the function does not need to determine it by 3d party services, like http://ipecho.net/plain for example.

Spring hello world lambda

Ok, let’s get started. We’ll create a Spring hello world application, using AWS serverless java containers project.

As it shown in the spring quick start doc, we are using Maven archetype to quickly setup the application template.

1
2
3
4
mvn archetype:generate -DgroupId=com.relaximus.example -DartifactId=spring-lambda -Dversion=1.0-SNAPSHOT \
	-DarchetypeGroupId=com.amazonaws.serverless.archetypes \
	-DarchetypeArtifactId=aws-serverless-spring-archetype \
	-DarchetypeVersion=1.3

Congratulations, your spring lambda application is ready to go. We already can try to deploy it to the AWS:

1
2
3
4
5
6
7
mvn clean package

aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket <YOUR S3 BUCKET NAME>

aws cloudformation deploy --template-file output-sam.yaml --stack-name StageVarsExample --capabilities CAPABILITY_IAM

aws cloudformation describe-stacks --stack-name StageVarsExample

We’ve wrote not so many lines of code, but there are happened a lot on the back stage:

  • maven has assembled the (fat) zip-package with our classes and needed libraries,
  • package command uploads the zip-package to the Amazon S3 storage (don’t forget to change <YOUR S3 BUCKET NAME> to one which you created),
  • deploy command will create the needed resources, according the configuration in sam.yaml file,
  • the last command shows your the URL, where your deployed application available.

Ok, go ahead and navigate to the URL, which you get by last command (you can also check the url in the AWS API Gateway console). I had the url like this

https://123456.execute-api.eu-central-1.amazonaws.com/Prod/ping

and response:

1
2
3
{
  "pong": "Hello, World!"
}

The SAM template

The description of the SAM format is for sure the topic of the separate article. Here I’ll try to clarify the main items we need for this tutorial.

Open the file sam.yaml in the project. The main part of it is the Resources which should be created in the AWS. Actually, there will be even more resources created: Amazon API Gateway and Amazon Lambda at least, but all these are compound in one setting of AWS::Serverless::Function. API Gateway is being created implicitly by means of Events section.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Resources:
  SpringLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.relaximus.example.StreamLambdaHandler::handleRequest
      Runtime: java8
      CodeUri: target/spring-lambda-1.0-SNAPSHOT-lambda-package.zip
      MemorySize: 512
      Policies: AWSLambdaBasicExecutionRole
      Timeout: 15
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /{proxy+}
            Method: any

To configure the stage variables of API Gateway, we need to extract the configuration of it and make it explicit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Resources:
  SpringLambdaAPI:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Variables:
        webhookUrl: !Sub '${SpringLambdaAPI}.execute-api.${AWS::Region}.amazonaws.com/Prod/ping'

  SpringLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.relaximus.example.StreamLambdaHandler::handleRequest
      Runtime: java8
      CodeUri: target/spring-lambda-1.0-SNAPSHOT-lambda-package.zip
      MemorySize: 512
      Policies: AWSLambdaBasicExecutionRole
      Timeout: 15
      Events:
        GetResource:
          Type: Api
          Properties:
            RestApiId: !Ref SpringLambdaAPI
            Path: /{proxy+}
            Method: any

We added one more resource SpringLambdaAPI of type AWS::Serverless::Api. The tricky part here is to connect our lambda function and the api part, that was done with this:

1
RestApiId: !Ref SpringLambdaAPI

And, as you probably noticed, I have added the Variables section in the api properties. So now we can add the variable webhookUrl with value almost the same as in Output section, but without the https prefix, as it violates the set of allowed symbols.

Stage variables filter

We did a change in the configuration of our deployment, but we need also adjust our application code to consume these changes. We need to retrieve the stage variable and use it to provide an url for the webhook.

The variable will come as an attribute of the servlet request. All we need to do is to extract it in some explicit way. Dedicated to this servlet filter is the best approach to solve this task. To keep the tutorial as simple as possible we just store the variable in a static field in our PingController - that will be enough to get the idea.

Adding the static field in the controller and changing the response of the ping method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class PingController {
    public static String MY_URL = "my-url";

    @RequestMapping(path = "/ping", method = RequestMethod.GET)
    public Map<String, String> ping() {
        Map<String, String> pong = new HashMap<>();
        pong.put("pong", "Hello, World!");
        pong.put("my-url", "https://"+MY_URL);
        return pong;
    }
}

Now, we will ad a servlet filter for the stage variables and name it accordingly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.relaximus.example;

import com.amazonaws.serverless.proxy.RequestReader;
import com.relaximus.example.controller.PingController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import java.io.IOException;
import java.util.Map;

public class StreamLambdaHandlerFilter implements Filter {
    private Logger log = LoggerFactory.getLogger(StreamLambdaHandlerFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        Object stageVars = servletRequest.getAttribute(RequestReader.API_GATEWAY_STAGE_VARS_PROPERTY);
        if (stageVars == null) {
            log.info("API Gateway stage vars context is null");
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        if (!Map.class.isAssignableFrom(stageVars.getClass())) {
            log.info("API Gateway stage vars object is not of valid type");
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        Map<String, String> vars = (Map<String, String>) stageVars;

        log.info("Vars: " + stageVars.toString());

        PingController.MY_URL = vars.get("webhookUrl");

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {}
}

As you can see, we are searching attribute with the name RequestReader.API_GATEWAY_STAGE_VARS_PROPERTY and storing it to the PingController.MY_URL. And the last part, we need to register in the lambda this filter. Add this to the StreamLambdaHadler:

1
2
3
4
5
// we use the onStartup method of the handler to register our custom filter
handler.onStartup(servletContext -> {
    FilterRegistration.Dynamic filter = servletContext.addFilter("StageVarsFilter", StreamLambdaHandlerFilter.class);
    filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
});

And that is actually everything we need. Let’s repeat the deployment (don’t forget to package an updated zip file):

1
2
3
4
5
6
7
mvn clean package

aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket <YOUR S3 BUCKET NAME>

aws cloudformation deploy --template-file output-sam.yaml --stack-name StageVarsExample --capabilities CAPABILITY_IAM

aws cloudformation describe-stacks --stack-name StageVarsExample

If you get to the same URL, you will get the response:

1
2
3
4
{
  "my-url": "https://123456.execute-api.eu-central-1.amazonaws.com/Prod/ping",
  "pong": "Hello, World!"
}

Our application provides us the “my-url” for the later callback - webhook.

Conclusion

There are many cases of stage variables usage, webhook is just one of them. Usually, it is quite useful if you need to attach a specific configuration to each AWS API Gateway. We saw how easily we can use stage variables in conjunction with the AWS Lambda on basis of Spring application.