Friday, December 13, 2019

Integrating with a Real Server

Go to the Table of Contents for the Java API Gateway

Now things start to get interesting.  We have completed our "characterization test" phase of AWS and we have figured out how to build a gateway, deploy some Java into it and handle the request and response objects.  Now we want to connect this to a system designed to do real work.

The eagle-eyed among you will have noticed that when I created more resources and methods during the input blog post, I connected them to the same lambda I had been working with.  This is very definitely a choice - the "integration" part of each APIGateway Method definition allows you to choose which lambda to proxy to - but it was a choice I intentionally made.  All the information we need is there to handle our own routing within the lambda.

How much to put into a single lambda - and when to start creating new lambdas - is a thorny topic and not one I am going to get into here.  Suffice it to say that general software engineering best practices should apply and if there is significant code duplication, put it all in one lambda.  On the other hand, the duplication only needs to be in the packaged product, which is just storage and configuration, so you may find going towards more lambdas is a good idea.  What I would definitely advise against is literally duplicating code to make multiple lambdas.  Code duplication is almost always a bad idea.

All of this code is in the repository, tagged API_GATEWAY_TDA.

A Tell-Don't-Ask Server

As trailed in the last post, I'm going to be working towards integrating a tell-don't-ask HTTP server into API Gateway here.  Again, for full disclosure, I already have this working within a Grizzly setup.  I am going to copy-and-paste some of that code into the blog area to make it clear what is going on, and then try and rework the handler, request and response to fit with that.  You will also notice that I have changed the API Gateway configuration to point to a new resource, so if you are following along and haven't already, you will need to drop and recreate your gateway configuration.

What is a tell-don't-ask server?  Simply put, it views an HTTP request as a pipeline: that is, the data from the request is fed into the handler which then places its output (status, data, etc) in a response object, which in turn feeds it back to a client.

To be clear, no public HTTP processing framework I am aware of works like this: most pass a handler both a request object (from which it is supposed to pull data) and a response object to write to.  The AWS API we are working with here expects the handler to pull from a request object and then create a response object.  While I'm not going to get into the philosophy of why I prefer functional and pipeline approaches here, suffice it to say that there are reasons of modularity, composability and testability which I find compelling.

The intent is that the server is configured to map input paths to "handler generators": that is, classes which act as factories for handlers, creating a new object for each incoming request and attaching any global configuration (such as access to databases).  The server then processes the incoming request and "tells" the handlers the data that it wants to know to do its job.  Once the handler has been created and configured, it is given a response object and told to get on with it.  When complete, the response can be processed by the server. 

What is being Requested, anyway?

One of the things we did not look at in the last post when considering input data was how to find out what the actual resource being requested was.  It is possible to determine this by implementing additional setters on the Request object.  The documentation says that httpMethod, resource and path are the ones to look at.  For our purposes, the overlap between path and resource means that path is probably not going to be particularly useful except for debugging and errors.

For this post, I've essentially thrown away what I've done up to this point and started again.  Specifically, I've created a set of new packages to hold the "client" code, the necessary interfaces and an implementation server.

The code that we have been working with over the past posts becomes "implementation detail" in this version and thus is buried inside the server.  At the same time, I have taken the opportunity to add setters for these additional data items.

Initialization in Lambda

As far as I can tell from reading the AWS documentation, there is no specific mechanism for starting up or "bootstrapping" a lambda.  They are clear that by and large lambdas can be reused, but the first time through you have to hope that everything is initialized.

The one guarantee that we have is that the API Gateway is going to call our Handler method before anything else we care about, and we are guaranteed by Java semantics that before an object is created all of the static members will be initialized and any static constructor will have been called.

Like most software engineers, I don't like static variables and I don't like this way of approaching problems.  On the other hand, it is possible to use a static member to declare that there is a singleton configuration object which needs to be created.

So to make the initialization work, I have defined an Initialization class and created a single static instance of it in the top-level handler.  For testing purposes (not that I am seriously testing this version of this code) it is possible to create the Initialization class independently of this static variable and I will treat the Handler class as I would a main method: it does the minimal amount of wiring up which can be "tested" in production and otherwise depend on thorough testing of all its components.

This Initialization class is again an "implementation detail" and is mainly responsible for making sure that a central configuration repository is initialized, accessible to all handlers, and then calling a user-provided configuration class (passing it the repository instance).  The name of this class is determined from an environment variable passed into the lambda definition.

The Processing Flow

The processing of a request begins in the "implementation detail" handler.  It is provided with a ProcessorRequest object which has been populated by the AWS server.  But our handler, being tell-don't-ask, rejects the opportunity to extract data from the request and instead creates a response object and asks the request to get on with the job of processing the request and passing any output to the response.  When complete, it returns the response to AWS.

The ProcessorRequest object first talks back to the central repository and asks it whether it can create a handler for the given combination of method and resource.  If it cannot, it populates the response with a 404 and gives up.

Otherwise, it looks at the capabilities of the defined handler using reflection and populates it accordingly: if it wants headers, it gives it those; if it wants path parameters, it gives it those; if it wants query or form parameters, it gives it those; and if it wants the body, it gives it the body.

Finally, when the handler is fully configured, the obligatory process method is called, providing the response object, and the handler is expected to "do its thing" and complete the response object.  Ultimately, the userspace handler completes, so does the handler method in the ProcessorRequest object, and finally the AWS handler returns the response.

What about the duplication?

Apologies to those of you who have been following along and asking this question.   Clearly, there is duplication between the paths that are set up in API Gateway and the path matching that is being done here.  The question is how to avoid it.

If you follow the "pure" API Gateway approach, it is possible to eliminate the duplication by associating each of the methods with its own endpoint in its own lambda, and could then remove the initialization and pattern matching here.  The problem with that (from my perspective) is you are switching a small amount of mapping duplication for a huge amount of infrastructural duplication.

The other possibility is to deal with the duplication by creating one of the copies from the other.  The duplication will still technically "exist" but it will no longer be your problem.

My solution in real life is to extract all of the mapping setup into external (JSON) files which are then read to configure the server.  These same files can then be used at deployment time to generate the appropriate configurations in API Gateway.  Doing this is left as an exercise to the reader.

Abstractions and Testing

What may or may not be clear is that I have taken the same (actually slightly simplified) abstraction as I was already using for Grizzly and redeployed it under API Gateway.  That means, that with minimal effort, I can continue to run "local" end-to-end tests inside a Grizzly server on actual hardware, and only deploy to API Gateway for "staging" and "production" purposes.

The whole point, of course, of using these interfaces is to isolate my development cycle from the underlying technologies and so unit testing of handlers in this form becomes a breeze (although you do need to be careful that your tests populate the handlers in the same way that the configuration does).

Conclusion

Hopefully that was not too breathless a tour of integrating a separate method dispatcher into API Gateway.  In principle, I don't see any reason why the same technique would not work for commercial libraries such as Spring or Struts.

For me, the key conclusion is that it is possible to deploy an API Gateway with minimal or no changes to the underlying logic, just a separate adapter/wrapper to the web technology.  This gives me the freedom to develop and test my code in units and deploy to either grizzly containers or to AWS API Gateway.

Next: Logging to Cloudwatch

Logging to Cloudwatch from API Gateway

Go to the Table of Contents for the Java API Gateway

Logging is an important means of being able to determine what is going on in any system.  This is particularly true for otherwise opaque systems in the cloud.

API Gateway offers the ability to log through Cloudwatch by providing the handler with a Context object containing a logging service in its getLogger property.  We can obtain this in our handler, wrap it in a "generic" interface (to log a string) and then pass that to any userspace handlers that want it.

These changes are tagged AWS_GATEWAY_LOGGING in the repository.

So, in TDAHandler we add this constructor:
  ServerLogger logger = new AWSLambdaLogger(cx.getLogger());
The AWSLambdaLogger is define as:
public class AWSLambdaLogger implements ServerLogger {
  private final LambdaLogger logger;

  public AWSLambdaLogger(LambdaLogger logger) {
    this.logger = logger;
  }

  @Override
  public void log(String string) {
    logger.log(string);
  }
}
Finally, in ProcessorRequest we pass it off to anybody who asks for it by implementing the DesiresLogger interface:
  if (handler instanceof DesiresLogger) {
    ((DesiresLogger)handler).provideLogger(logger);
  }
It would, of course, be possible to integrate this with frameworks such as SLF4J by providing a suitable implementation of the concrete classes.  This is left as an exercise to the reader (but look at aws-lambda-java-log4j).

If you don't have access to "the context" you can still log.  You just need to obtain the logger from a static method on LambdaRuntime:

LambdaLogger logger = LambdaRuntime.getLogger();
One thing to note (if you are unused to Cloudwatch Logging): it often takes a while for logging to work its way through Amazon's cloud.  This can be very distracting, particularly since a lot of issues never seem to get logged at all.  How do you determine if a log failed to get through, or just hasn't come through yet?  And having to wait a minute or two (sometimes more) for feedback on what just went wrong can kill productivity.  Sadly, I don't have a solution for  this other than to "test" as little as possible on AWS and to always assume that every time you push everything except the environment is perfect.  And then to change the environment as little as possible, and always in small steps so that it is always "green" to "green" and any time you do see a failure, you know what the problem "must be".

Next: Getting Started with Websockets

Thursday, December 12, 2019

Handling Input in a Lambda Proxy

Go to the Table of Contents for the Java API Gateway

If we don't want to greet the entire world, but one individual by name, then we will need to obtain information about that person and their name.  There are many ways to do this, and this section will look at some of them in an attempt to develop a more complete Lambda Integration before we go full-bore with trying to actually integrate an existing Java server.

All of the code here is checked in together and can be found at the tag API_GATEWAY_HELLO_INPUT.

Reading from a query parameter

According to the AWS documentation, the query parameters are available as a JSON object in the lambda proxy input request in a field called queryStringParameters. There is also a multiValueQueryStringParameters which allows multiple copies of the same parameter to be specified, but let's start small.

In order to obtain this, we need to create a setter on our IgnorantRequest POJO.  I am guessing that in the Java binding, it will happily translate a JSON object into a Java Map.  Thus I can write this code:
public class IgnorantRequest {
 private Map<String, String> values = new HashMap<>();

 public void setQueryStringParameters(Map<String, String> values) {
  if (values != null)
   this.values = values;
 }
}
In my experiments, I found that with no parameters, AWS could choose to pass null in to this function, which could cause an exception.  On the other hand, it does seem to always call the function.  But since I can't be sure, I wrote this code in the most defensive way possible: values will always have a valid map regardless of how it is invoked.

For full disclosure (for those that don't know me), I really don't like the usual Java-style POJOs with setters and getters (to be precise, it's the getters that get me) and prefer a "tell-don't-ask" style of programming.  We'll see what this looks like when we integrate my "tell-don't-ask-server" in a later post, but for now I'm just going to grit my teeth and add a getter to this class.  To make myself a little happier (and a little less primitive-obsessed), I'm not going to let you ask for all of the parameters: you have to know which one you want.  I'm also going to let you ask first if we have that one.
public class IgnorantRequest {
 private Map<String, String> values = new HashMap<>();

 public void setQueryStringParameters(Map<String, String> values) {
  if (values != null)
   this.values = values;
 }
 
 public boolean hasQueryParameter(String p) {
  return values.containsKey(p);
 }
 
 public String queryParameter(String p) {
  return values.get(p);
 }
}
It's now quite easy to update both the main function and the response to handle the fact that we may have a query parameter (or may not) and that we want to use it in the greeting if we do, and carry on with the old "hello, world" behavior if not.
public class Handler implements RequestHandler<IgnorantRequest, IgnorantResponse> {
 public IgnorantResponse handleRequest(IgnorantRequest arg0, Context arg1) {
  if (arg0.hasQueryParameter("name"))
   return new IgnorantResponse(arg0.queryParameter("name"));
  else
   return new IgnorantResponse("world");
 }
}
public class IgnorantResponse {
 private final String helloTo;

 public IgnorantResponse(String helloTo) {
  this.helloTo = helloTo;
 }

 public String getBody() {
  return "hello, " + helloTo;
 }
}
And you can package that up and try to curl it again with and without a parameter:
$ scripts/package.sh
$ curl https://tovogqsfoj.execute-api.us-east-1.amazonaws.com/ignorance/hello
hello, world
$ curl https://tovogqsfoj.execute-api.us-east-1.amazonaws.com/ignorance/hello?name=Fred
hello, Fred

Reading from an HTTP header

HTTP headers are a standard way of passing information between clients and servers.  The headers are passed to a lambda proxy through the headers field of the JSON object, which can again be interpreted as a Map in Java.

There is something of a wrinkle here, which possibly applies to the query parameters as well, but simply didn't come up, that often HTTP headers are thought of in upper case or mixed case but can be any case and curl in particular shifts them to lower case.

To handle this, we define a case-insensitive TreeMap to hold the header values and then put all of the headers passed across into that.  Note that I don't see how it is possible for the headers array to be null, but I've defended against it anyway.
public class IgnorantRequest {
  private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

  public void setHeaders(Map<String, String> headers) {
    if (headers != null)
      this.headers.putAll(headers);
  }
 
  public boolean hasHeader(String hdr) {
    return headers != null && headers.containsKey(hdr);
  }

  public String getHeader(String hdr) {
    return headers.get(hdr);
  }
}
(Note that I have omitted code already shown for brevity.)

The updates to the main code are very similar to those already shown for the query parameter case.
$ scripts/package.sh
$ curl -HX-USER-NAME:Fred https://tovogqsfoj.execute-api.us-east-1.amazonaws.com/ignorance/hello
hello, Fred

Reading from a path parameter

In order to move on, we need new resource methods.  I'm going to add two at the same time to the gateway configuration: a POST method on the existing hello resource to handle reading from the body (see next section) and another GET method on a new hello/{who} resource that will enable us to read from the who path parameter to find out who we should be greeting.  If you are following along and have already deployed the appropriate version of the gateway, you'll be fine.  If you have checked out the code but not dropped and recreated the gateway, you will need to do that before you can continue.

Path parameters come across in a map called pathParameters.  This is starting to look easy (and repetitive).  A little bit of cutten-and-pasten (making sure not to make any stupid duplication mistakes) and we can try again:
$ scripts/package.sh
$ curl https://tovogqsfoj.execute-api.us-east-1.amazonaws.com/ignorance/hello/George
hello, George

Reading from the body

The body can also be passed in for a POST request, so it is important to add that method (done above) and then the body should be available through the body parameter.  The documentation describes it as a "JSON string" but we have to assume that will be translated into a Java String for our purposes (although note that binary bodies apparently come across as Byte64 encoded strings).

I am not going to try and parse the body or do anything fancy.  I just assume that if there is a non-empty body, it is the name to be greeted.  I store the body (if any) and provide code to test the body exists and return it much the same as for the other cases.  The handler is updated to test this in turn.

$ scripts/package.sh
$ curl --data Henry https://vuhsa5vvlj.execute-api.us-east-1.amazonaws.com/ignorance/hello
hello, Henry

Ordering

You may be wondering at this point what happens if you specify multiple names to greet.  The answer, of course, is that it depends on the logic in the handler.  What I chose to do was to test each of the cases we have considered in turn and return the first one that matches; if none of them match, we continue to greet the world.

This is not the only possible choice, but it is simple and, in any case, it is not our greeting strategy that is under review here: it is whether we can integrate with AWS.

Conclusion

We have experimented with four different ways of handling input from a client (query parameters, path parameters, headers and body) and used these to customize our greeting response.

Next: Integrating our TDA Server

Generating a Response for Lambda Proxy Integration

Go to the Table of Contents for the Java API Gateway

We now find ourselves in the position where we have working code and scripts that can redeploy that code: what is commonly known as "Green".

But the objective of our code at the moment is to output "hello, world" and it doesn't do that.  We need to return a body output somehow.

The output we are providing is in the IgnorantResponse class, but at the moment that's just an empty class.  We need to update it to return a body.

As far as I can tell, all of this works by Amazon magic using Java reflection on beans.

Lambda Proxy Communication

You can see what the lambda proxy input looks like by going back and studying the output from testing the API in the UI.  About halfway down there is a line that starts "Endpoint request body after transformations:".   Looking along that, you can see a big JSON structure which has fields called "resource", "path" and "httpMethod" among others.  If you are familiar with how HTTP works, all of this should seem very reasonable, even if some of it is AWS specific and generally there is too much information so it ends up being truncated.

Fortunately, the input and output formats are described in the AWS documentation.

So, fairly obviously, we want to set the body field of the response.

Creating a Response

Because AWS uses Java reflection - and assumes our response object is a POJO or bean, all we should need to do is to create the getBody() method on our IgnorantResponse and have it return the relevant message.

public class IgnorantResponse {
  public String getBody() {
    return "hello, world";
  }
}
We can then repackage and retry our curl command:
scripts/package.sh
curl https://tovogqsfoj.execute-api.us-east-1.amazonaws.com/ignorance/hello
We can also have the handler specifically set the status code.  This, for example, will return a 204 with no data:
public class IgnorantResponse {
  public int getStatusCode() {
    return 204;
  }

  public String getBody() {
    return "";
  }
}
In order to see the status code, you will need to use curl -v.

Conclusion

Working with the Response object is fairly simple: we just create the appropriate POJO getters for the fields that we want to populate in the lambda response.

You can find all the code in the repository tagged API_GATEWAY_HELLO_WORLD.

Next: We Need to Handle Input

Diagnosing 403 Errors on AWS API Gateway

Go to the Table of Contents for the Java API Gateway

Inwardly I groan every time I see a 403 come back from API Gateway.

Personally, I think they do it "wrong", but at the same time, I can see where they are coming from.

404 Issues

According to the HTTP standards, if a page doesn't exist, you should see a 404 response.  This is what we all expect.  When we see a 403, we start asking security-like questions, not "have I typed the right URL" questions.

In fact, the majority of times that I see a 403 with AWS API Gateway, the problem is that I have typed the wrong URL or am using the wrong method.  To make matters worse, API Gateway gives you absolutely no logging to let you know that this is happening (at least, I haven't found any), possibly because it cannot think of anywhere to put that logging for a resource it cannot find.

The root cause of this appears to be that API Gateway can't tell if the non-existent resource would be secured if it did exist.  Since it can't tell, it decides that it should tell you that you can't access it, rather than telling you that it doesn't exist.

Fixing the problem

So the first thing you need to do when encountering a 403 is to look very, very carefully at your URL and see if there is anything that could possibly be wrong with it.  On the upside, if the hostname is wrong you do get a sensible error - but mainly because Route53 has not registered the domain name and so your browser cannot resolve it!

If you are still wondering what went wrong in the first post, it's that the stage/deployment name "Ignorance" is misspelled "ignorance" (yes, just a case error) in the URI that is assembled at the end of the createGateway.sh script.  But it's a valuable point in two regards:
  • URLs must contain the "stage name" in order to function (otherwise you'll get a 403)
  • The stage name and resource name must both be spelled correctly (including case) or you will see a 403.
I am going to fix this from now on by having the stage name be lower case - but you will need to drop and recreate the gateway to see the changes.

Next: Let's Generate a Response

Wednesday, December 11, 2019

Using API Gateway with Java

This is a multipart experiment at trying to deploy an API Gateway instance on AWS which can integrate with a tell-don't-ask dispatch server, websockets and Couchbase.

The first few steps are to try and get a basic API Gateway server up with a Proxy Lambda behind it.
Then we want to merge in the Tell-Don't-Ask server
Then we want to introduce websockets
Finally, we want to handle unsolicited traffic

Minimal Setup for AWS API Gateway with Java

Go to the Table of Contents for the Java API Gateway

Although I've been using clouds for the past decade or so, I do not (yet) consider myself an expert with all things serverless.  This year,  I have done some work with AWS API Gateway and Lambdas, but everything I've done has been with node.js.

The node.js environment in Lambda is really quite friendly: you can (for small enough packages) view and even edit your code online.  But it seems fairly obvious that the same approach will not work for Java since "runtime" Java consists of bytecodes and jar files.

The background for this investigation is that I want to take one of my mainline servers, currently running on provisioned AWS boxes with high availability proxies and get it running in a serverless environment.

Of course, because it's a real system, it's a little complicated.  So I need to make sure that I can do all of the following things:
  • Actually get something up and running with API Gateway, Lambda and Java
  • Obtain all the information sent across from the client (method, headers, path information, payloads, etc)
  • Integrate with my existing code which uses custom servlet handlers
  • Handle WebSocket traffic (a showstopper a couple of years ago, but available since last December)
  • Integrate with complex backend components (specifically Couchbase on a different configured server)
Given that this is quite a few separate things, I'm going to split my treatment up into a series of blog posts (there is a contents page linked from the top of every post).  All the code is in one place - git@github.com:gmmapowell/ignorance-may-be-strength.git - under the directory aws-java-gateway.  Each post has its own git tag: for this post you will want to check out the tag API_GATEWAY_MINIMAL.

Before we Begin

If you want to do more than follow along, you'll obviously need an AWS account and you'll need to have it appropriately configured along with suitable configuration on your machine.  I think most of that is beyond the scope of this article.

You will definitely need to install the AWS CLI.  For MacOS, you can follow these instructions.

The operations we are going to perform need to take place in an AWS region.  You need to specify this to the scripts I have written in the REGION environment variable.  To move the code from your machine to AWS Lambda, we use an S3 bucket.  You need to provide the name of a bucket you have permission to write to in the BUCKET environment variable.

Getting Something Working

The first thing we want to do is to get an AWS API Gateway up and running.  I've done this bit before and - if you know me at all - you will know that there's no way I'm going to do it through the UI other than to experiment.  So we're going to start by using CloudFormation to build an API Gateway.  If you've not used CloudFormation before, this is a good time to start.  I should probably write a separate blog about that, but I haven't yet.

You can invoke CloudFormation in a number of ways, but for simplicity I'm just going to invoke it from the command line.  Everything we're going to do here is wrapped up in a couple of scripts (createGateway.sh and dropGateway.sh) and they reference a CloudFormation description (gateway-cf.json).  You can specify CloudFormation in either JSON or YAML; YAML is more terse, but I tend to find it's harder to read.  Moreover, it is less "machine-friendly": it's harder to parse and generate (at least with languages I tend to use).

You would think that it wouldn't be that hard to get a simple gateway up and running, and it's probably not, but by the time you have all the plumbing in place, you'll find you've created:
  • An execution role for the lambda function we're going to want to execute
  • The lambda function itself
  • A "permission" that allows api gateway to invoke the lambda
  • The gateway itself
  • A resource on the gateway
  • A method on the resource
  • A "deployment" for the gateway (i.e. where it shows up in the real world)
Running the createGateway.sh script creates all these resources which you can confirm in the UI if you'd like.

Buried deep in there is the fact that we are pulling a zip file from S3 in the hopes that it will have some useful Java resources in it.  If you look, you'll see that zip file is built earlier on in the script with just junk in it.  For now, what is in the zip file is unimportant: it just matters that it is a zip file.

One thing I do find very useful to do in the UI is testing.  It may well be possible to do the same level of testing with the same quality of output from the command line, but if so, I don't know how.  Even if it is, the ability to see and scroll through the output makes the UI worthwhile.

To test the gateway in the UI, start on the CloudFormation page in the console. Select the Resources and identify the gateway (called SimpleGateway).  Click on the named link and you will go to the API Gateway page.  Click on through to the appropriate gateway and you can see the resources.  Select the GET method on the left hand side and press "TEST".  Scroll down and press "TEST" at the bottom.  On the right hand side, you'll see a testing pane appear.  At this point, I'd expect you to see an error something like this:

Lambda execution failed with status 200 due to customer function error: Class not found: blog.ignorance.apigateway.Handler
If noted above,  we created a zip file from the scripts directory - there is no Java in sight!  It's not surprising AWS can't find a handler.

Which is really the point of this blog post - and what we have been building up to.  We need to create some Java, put it in the right place and see if it works.

Creating a Java Handler for a Lambda

There are a number of different ways of writing Java Lambda Handlers and the exact technique you choose depends on what you are trying to do.  The options are outlined in the AWS Java Documentation, but I have to say that I didn't myself come away very clear on how any of it worked - hence these experiments here.

But after considerable research, and finding madhead's repository, I realized that with a Lambda Proxy integration, the POST packet from API Gateway gives you a serious quantity of information that you can pull into a POJO.  This uses the technique that AWS describes as Leveraging Predefined Interfaces for Creating Handler (Java).

So, let's begin to write some code.

First, we need a class.  If you look back carefully at what we created, you will see that the handler for the lambda was defined to be blog.ignorance.apigateway.Handler.  The plethora of options in how to define a Handler seems complicated, but for the case I want to pursue, this appears to be the name of a class that implements the com.amazonaws.services.lambda.runtime.RequestHandler interface.

Let's create that class, and provide a minimal implementation of the required Request and Response objects and call our work here "done".

public class Handler implements RequestHandler<IgnorantRequest, IgnorantResponse> {
  @Override
  public IgnorantResponse handleRequest(IgnorantRequest arg0, Context arg1) {
    return new IgnorantResponse();
  }
}
We can now build this code and update the lambda with the appropriate zip file using the scripts/package.sh script.  This script does four things:

  • Downloads all the necessary libraries from maven
  • Compiles and assembles a lambda zip file
  • Uploads the file to S3
  • Calls aws lambda update-function-code to notify AWS to refresh the lambda code from the S3 bucket 

Note that this final step is very important - without it, your lambda will keep on using the same code it has always had.

With all the code in place, it should be possible to test the handler again with hopefully delightful results:

Thu Dec 05 10:09:10 UTC 2019 : Successfully completed execution
Thu Dec 05 10:09:10 UTC 2019 : Method completed with status: 200
It doesn't do anything useful yet, but in addition to testing in the UI, you should also be able to hit it from the command line or a browser using the URI that was printed during the creation of the gateway.

curl https://tctf8f9r45.execute-api.us-east-1.amazonaws.com/ignorance/hello
{"message":"Forbidden"}
Oh, well, such is life.

Cleaning Up

There is a script to clean up as well: scripts/dropGateway.sh.

Conclusion

So ends the first part of this tutorial.  I have created (and committed to github) a very simple project that builds the minimal resources to deploy an AWS API Gateway backed by a very simple Java Lambda.

What I haven't done yet (apart from being able to access it from outside the test environment) is to access any of the fields coming from AWS, do any useful work, integrate with anything else or return anything to the user.

We clearly have more work to do.

Next: Let's figure out that 403