DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • Commonly Occurring Errors in Microsoft Graph Integrations and How To Troubleshoot Them (Part 4)
  • Serverless Patterns: Web
  • How To Perform OCR on a Photograph of a Receipt Using Java

Trending

  • DZone's Article Submission Guidelines
  • The End of “Good Enough Agile”
  • MCP Servers: The Technical Debt That Is Coming
  • GitHub Copilot's New AI Coding Agent Saves Developers Time – And Requires Their Oversight
  1. DZone
  2. Data Engineering
  3. Databases
  4. Creating a Simple API Stub With API Gateway and S3

Creating a Simple API Stub With API Gateway and S3

This post shows a nice way of using Mapping Templates in API Gateway to transform the path and create a simple stub.

By 
Jeroen Reijn user avatar
Jeroen Reijn
DZone Core CORE ·
Jun. 03, 22 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
5.2K Views

Join the DZone community and get the full member experience.

Join For Free

A while ago my team was looking to create a stub for an internal JSON HTTP-based API. The to-be stubbed service was quite simple. The service exposed a REST API endpoint for listing resources of a specific type. The API supported paging and some specific request/query parameters.

GET requests to the service looked something like this:

/items?type=sometype&page=2


We wanted to create a simple stub for the service within our AWS account which we could use for testing. One of the tests we wanted to perform was to test if the service was down. If so, our application would follow a specific code path. We could of course not easily test this with the real services of the remote API, so we tried to keep things as simple as possible.

Creating the API With API Gateway, Lambda, and S3

As most of the services within our domain are based on Amazon API Gateway and AWS Lambda, we started looking into that direction at first. As our stub was read-only and we didn’t have to modify the items, we chose to create an initial export of the dataset from the remote API into JSON files, which we could store in S3. To store the files, we chose to use a file name pattern that resembled our type and page parameter.

{TYPE}_{PAGE_NUMER}(.json)

This resulted in a bucket like this:

apigatways3 integration bucket

The simplified design of the API stub was going to be as follows:

Simplified design of the API stub

An example version of what our code looked like was something similar to this:

Java
 
public static final String TYPE_QUERY_PARAM = "type";
public static final String JSON_FILE_EXTENSION = ".json";
public static final String PAGE_PREFIX = "_p";

private S3Client s3Client = S3Client.builder()
        .region(Region.EU_WEST_1)
        .httpClient(UrlConnectionHttpClient.builder().build())
        .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
        .build();

@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
    Map<String, String> queryStringParameters = input.getQueryStringParameters();

    String page = queryStringParameters.getOrDefault("page", "1");
    String type;

    if(!queryStringParameters.containsKey(TYPE_QUERY_PARAM)){
        throw new MissingTypeException("Parameter Type cannot be null");
    } else {
        type = queryStringParameters.get(TYPE_QUERY_PARAM);
    }

    String key = type + PAGE_PREFIX + page + JSON_FILE_EXTENSION;

    ResponseInputStream<GetObjectResponse> s3ClientObject = s3Client
            .getObject(GetObjectRequest
                    .builder()
                    .bucket("apigatelambdas3integrationdemo")
                    .key(key)
                    .build());

    return new APIGatewayProxyResponseEvent()
            .withStatusCode(200)
            .withHeaders(
                    Map.of("Content-Type", s3ClientObject.response().contentType(),
                            "Content-Length", s3ClientObject.response().contentLength().toString()))
            .withBody(getFileContentAsString(context, s3ClientObject));
}

private String getFileContentAsString(Context context, ResponseInputStream<GetObjectResponse> s3ClientObject) {
    String fileAsString = "";
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(s3ClientObject))) {
        fileAsString = reader.lines().collect(Collectors.joining(System.lineSeparator()));
    } catch (IOException e) {
        context.getLogger().log("Oops! Something went wrong while converting the file from S3 to a String");
        e.printStackTrace();
    }
    return fileAsString;
}


As you can see in the above snippet, we’re essentially calculating the path to the object in S3 based on some request parameters. When the path is resolved we just fetch the file from S3 and convert it to a string for the reply to API Gateway, which in turn returns the file to the consumer. Our lambda function was just acting as a simple proxy and we were wondering if we could get rid of the lambda function at all. Maintaining code, dependencies, etc. is a burden so if we don’t need it we would like to get rid of it.

Creating the API With Just API Gateway and S3

API Gateway has great support for direct integration with other AWS services, so we started exploring our options. The solution we hoped for was something similar to the image below.

API Gateway to S3 Bucket

While going through the documentation for API Gateway we found a pretty good example of how to use API Gateway as a proxy for S3. The provided solution lets API Gateway mirror the folder and file structure(s) in S3: useful, but it did not cover our use case.

One of the other options that looked promising was configuring mapping templates. We had used that before to transform an incoming request body to a different format for the remote backend. In case you’re unfamiliar with mapping templates in API Gateway, a mapping template is a script expressed in Velocity Template Language (VTL) and applied to the payload using JSONPath expressions.

After digging through the API Gateway documentation we also discovered that mapping templates can be used to alter the query string, headers, and path.

Request body mapping template Response body mapping template
$context.requestOverride.header.header_name $context.responseOverride.header.header_name
$context.requestOverride.path.path_name $context.responseOverride.status
$context.requestOverride.querystring.querystring_name

Modifying the path was exactly what we wanted, so we tried that and it worked like a charm. Let’s take a look at the resulting setup for our API Gateway GET request.

Setup for our API Gateway GET request

As you can see in the above section we’ve added a GET method to the root of our API. The GET method has a method request which defines both query parameters; page and type.

Method request which defines both query parameters; page and type

For the integration request, we define the remote service we want to integrate with and we specify the bucket and a dynamic fileName.

Define the remote service we want to integrate with and we specify the bucket and a dynamic fileName

In the path override of the integration request there are two important things to notice:

  1. At the start of the Path override parameter, we provide the S3 bucket name.
  2. As the second argument, we provide a dynamic value named fileName.

The path override will therefor be {bucket-name}/{fileName}.

Now in our mapping template, we will fill the fileName parameter so that API Gateway knows which file to get from S3.

Let’s take a look at the mapping template.

Mapping Template

As you can see we’ve set up a template for requests for content-type application/json. Now when a GET request arrives with the type and page query parameters, it will assemble the resulting fileName variable in the path override.

Velocity
 
#set($type = $input.params('type')
#set($page = $input.params('page'))
#if( $page == '') 
  #set($context.requestOverride.path.fileName = $type + '_p1.json')
#else
  #set($context.requestOverride.path.fileName = $type + '_p'+ $page + '.json')
#end


When the file in that specific bucket is found it will return the corresponding JSON. When the file is not found, it will throw a 404 response body. We can also map the response code with a mapping template to produce some nice-looking error messages.

Some Last Thoughts

While researching this solution I also came across a post by Tom Vincent. Tom wrote a nice post about what he calls Lambdaless. I like the term and it resonates well with what we also tried to achieve in this post.

I think this post shows a nice way of using Mapping Templates in API Gateway to transform the path and create a simple stub. Do keep in mind we use this for testing only and we don’t run production workloads with this setup. I also think that if the API would have been more complex we would not have taken this approach. Nevertheless, it was simple to set up and use for our stub.

API AWS Strings Stub (distributed computing) Data Types

Published at DZone with permission of Jeroen Reijn. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • Commonly Occurring Errors in Microsoft Graph Integrations and How To Troubleshoot Them (Part 4)
  • Serverless Patterns: Web
  • How To Perform OCR on a Photograph of a Receipt Using Java

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!