Menggunakan AWS Lambda dengan API Gateway

1. Gambaran keseluruhan

AWS Lambda adalah perkhidmatan pengkomputeran tanpa pelayan yang disediakan oleh Amazon Web Services.

Dalam dua artikel sebelumnya, kami membincangkan cara membuat fungsi AWS Lambda menggunakan Java, dan juga cara mengakses DynamoDB dari fungsi Lambda.

Dalam tutorial ini, kita akan membincangkan cara menerbitkan fungsi Lambda sebagai titik akhir REST, menggunakan AWS Gateway .

Kami akan melihat secara terperinci topik berikut:

  • Konsep asas dan syarat API Gateway
  • Integrasi fungsi Lambda dengan API Gateway menggunakan integrasi Lambda Proxy
  • Pembuatan API, strukturnya, dan cara memetakan sumber daya API ke fungsi Lambda
  • Penerapan dan ujian API

2. Asas dan Syarat

API Gateway adalah perkhidmatan yang dikendalikan sepenuhnya yang membolehkan pembangun membuat, menerbitkan, menyelenggara, memantau, dan mengamankan API pada skala apa pun .

Kami dapat melaksanakan antara muka pengaturcaraan berasaskan HTTP yang konsisten dan terukur (juga disebut sebagai layanan RESTful) untuk mengakses perkhidmatan backend seperti fungsi Lambda, perkhidmatan AWS lebih lanjut (misalnya, EC2, S3, DynamoDB), dan mana-mana titik akhir HTTP .

Ciri-ciri termasuk, tetapi tidak terhad kepada:

  • Pengurusan lalu lintas
  • Kebenaran dan kawalan akses
  • Pemantauan
  • Pengurusan versi API
  • Permintaan pendikit untuk mengelakkan serangan

Seperti AWS Lambda, API Gateway secara automatik diperkecil dan dikenakan setiap panggilan API.

Maklumat terperinci boleh didapati dalam dokumentasi rasmi.

2.1. Syarat

API Gateway adalah perkhidmatan AWS yang menyokong membuat, menyebarkan, dan mengelola antara muka pengaturcaraan aplikasi RESTful untuk memperlihatkan titik akhir HTTP, fungsi AWS Lambda, dan perkhidmatan AWS lain.

An API Gateway API adalah koleksi sumber dan kaedah yang boleh disepadukan dengan fungsi Lambda, perkhidmatan AWS lain, atau HTTP titik hujung dalam backend. API terdiri daripada sumber yang membentuk struktur API. Setiap sumber API dapat memperlihatkan satu atau lebih kaedah API yang mesti mempunyai kata kerja HTTP yang unik.

Untuk menerbitkan API, kita harus membuat penyebaran API dan mengaitkannya dengan tahap yang disebut . Tahap seperti snapshot pada masa API. Sekiranya kita menggunakan semula API, kita boleh mengemas kini tahap yang ada atau membuat yang baru. Oleh itu, versi API yang berlainan dapat dilakukan secara bersamaan, misalnya tahap dev , tahap ujian , dan bahkan beberapa versi pengeluaran, seperti v1 , v2 , dll.

Integrasi Lambda Proxy adalah konfigurasi yang dipermudahkan untuk penyatuan antara fungsi Lambda dan API Gateway.

API Gateway mengirimkan seluruh permintaan sebagai input ke fungsi Lambda backend. Secara responsif, API Gateway mengubah output fungsi Lambda kembali ke respons HTTP frontend.

3. Kebergantungan

Kami memerlukan kebergantungan yang sama seperti dalam artikel AWS Lambda Menggunakan DynamoDB With Java.

Selain itu, kami juga memerlukan perpustakaan JSON Simple:

 com.googlecode.json-simple json-simple 1.1.1 

4. Membangun dan Menggunakan Fungsi Lambda

Di bahagian ini, kami akan mengembangkan dan membangun fungsi Lambda kami di Java, kami akan menggunakannya menggunakan AWS Console, dan kami akan menjalankan ujian cepat.

Oleh kerana kami ingin menunjukkan keupayaan asas mengintegrasikan API Gateway dengan Lambda, kami akan membuat dua fungsi:

  • Fungsi 1: menerima muatan dari API, menggunakan kaedah PUT
  • Fungsi 2: menunjukkan cara menggunakan parameter jalur HTTP atau parameter pertanyaan HTTP yang berasal dari API

Pelaksanaannya, kami akan membuat satu kelas RequestHandler , yang mempunyai dua kaedah - satu untuk setiap fungsi.

4.1. Model

Sebelum kita melaksanakan pengendali permintaan sebenar, mari kita lihat model data kami dengan cepat:

public class Person { private int id; private String name; public Person(String json) { Gson gson = new Gson(); Person request = gson.fromJson(json, Person.class); this.id = request.getId(); this.name = request.getName(); } public String toString() { Gson gson = new GsonBuilder().setPrettyPrinting().create(); return gson.toJson(this); } // getters and setters }

Model kami terdiri daripada satu kelas Orang sederhana , yang mempunyai dua sifat. Satu-satunya bahagian yang penting ialah pembina Person (String) , yang menerima JSON String.

4.2. Pelaksanaan Kelas RequestHandler

Sama seperti dalam artikel AWS Lambda With Java, kami akan membuat pelaksanaan antara muka RequestStreamHandler :

public class APIDemoHandler implements RequestStreamHandler { private static final String DYNAMODB_TABLE_NAME = System.getenv("TABLE_NAME"); @Override public void handleRequest( InputStream inputStream, OutputStream outputStream, Context context) throws IOException { // implementation } public void handleGetByParam( InputStream inputStream, OutputStream outputStream, Context context) throws IOException { // implementation } }

Seperti yang dapat kita lihat, antara muka RequestStreamHander menentukan hanya satu kaedah, handeRequest () . Bagaimanapun, kita dapat menentukan fungsi selanjutnya dalam kelas yang sama, seperti yang telah kita lakukan di sini. Pilihan lain ialah membuat satu pelaksanaan RequestStreamHander untuk setiap fungsi.

Dalam kes khusus kami, kami memilih yang pertama untuk kesederhanaan. Walau bagaimanapun, pilihan mesti dibuat berdasarkan kes demi kes, dengan mempertimbangkan faktor-faktor seperti prestasi dan pemeliharaan kod.

Kami juga membaca nama jadual DynamoDB kami dari pemboleh ubah persekitaran TABLE_NAME . Kami akan menentukan pemboleh ubah itu kemudian semasa penyebaran.

4.3. Pelaksanaan Fungsi 1

Dalam fungsi pertama kami, kami ingin menunjukkan cara mendapatkan muatan (seperti dari permintaan PUT atau POST) dari Gerbang API :

public void handleRequest( InputStream inputStream, OutputStream outputStream, Context context) throws IOException { JSONParser parser = new JSONParser(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); JSONObject responseJson = new JSONObject(); AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient(); DynamoDB dynamoDb = new DynamoDB(client); try { JSONObject event = (JSONObject) parser.parse(reader); if (event.get("body") != null) { Person person = new Person((String) event.get("body")); dynamoDb.getTable(DYNAMODB_TABLE_NAME) .putItem(new PutItemSpec().withItem(new Item().withNumber("id", person.getId()) .withString("name", person.getName()))); } JSONObject responseBody = new JSONObject(); responseBody.put("message", "New item created"); JSONObject headerJson = new JSONObject(); headerJson.put("x-custom-header", "my custom header value"); responseJson.put("statusCode", 200); responseJson.put("headers", headerJson); responseJson.put("body", responseBody.toString()); } catch (ParseException pex) { responseJson.put("statusCode", 400); responseJson.put("exception", pex); } OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8"); writer.write(responseJson.toString()); writer.close(); }

Seperti yang dibincangkan sebelumnya, kami akan mengkonfigurasi API kemudian untuk menggunakan integrasi proksi Lambda. Kami mengharapkan API Gateway menyampaikan permintaan lengkap ke fungsi Lambda dalam parameter InputStream .

Yang harus kita lakukan adalah memilih atribut yang relevan dari struktur JSON yang terkandung.

Seperti yang kita lihat, kaedah ini pada dasarnya terdiri daripada tiga langkah:

  1. Mengambil objek badan dari aliran input kami dan membuat objek Orang dari itu
  2. Menyimpan objek Orang itu dalam jadual DynamoDB
  3. Membangun objek JSON, yang dapat menyimpan beberapa atribut, seperti badan untuk respons, tajuk khusus, dan juga kod status HTTP

Satu perkara yang perlu disebutkan di sini: API Gateway mengharapkan badan menjadi String (untuk permintaan dan respons).

Seperti yang kami harapkan untuk mendapatkan String sebagai badan dari API Gateway, kami memasukkan badan ke String dan menginisialisasi objek Person kami :

Person person = new Person((String) event.get("body"));

API Gateway juga mengharapkan badan tindak balas menjadi String :

responseJson.put("body", responseBody.toString());

Topik ini tidak disebut secara eksplisit dalam dokumentasi rasmi. Namun, jika kita melihat dari dekat, kita dapat melihat bahawa atribut body adalah String di kedua potongan untuk permintaan dan juga untuk respons.

Kelebihannya harus jelas: walaupun JSON adalah format antara API Gateway dan fungsi Lambda, badan sebenarnya boleh mengandungi teks biasa, JSON, XML, atau apa sahaja. Maka adalah tanggungjawab fungsi Lambda untuk mengendalikan format dengan betul.

Kami akan melihat bagaimana bentuk permintaan dan respons kemudian ketika kami menguji fungsi kami di AWS Console.

Perkara yang sama juga berlaku untuk dua fungsi berikut.

4.4. Pelaksanaan Fungsi 2

Pada langkah kedua, kami ingin menunjukkan cara menggunakan parameter jalur atau parameter rentetan pertanyaan untuk mengambil item Orang dari pangkalan data menggunakan IDnya:

public void handleGetByParam( InputStream inputStream, OutputStream outputStream, Context context) throws IOException { JSONParser parser = new JSONParser(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); JSONObject responseJson = new JSONObject(); AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient(); DynamoDB dynamoDb = new DynamoDB(client); Item result = null; try { JSONObject event = (JSONObject) parser.parse(reader); JSONObject responseBody = new JSONObject(); if (event.get("pathParameters") != null) { JSONObject pps = (JSONObject) event.get("pathParameters"); if (pps.get("id") != null) { int id = Integer.parseInt((String) pps.get("id")); result = dynamoDb.getTable(DYNAMODB_TABLE_NAME).getItem("id", id); } } else if (event.get("queryStringParameters") != null) { JSONObject qps = (JSONObject) event.get("queryStringParameters"); if (qps.get("id") != null) { int id = Integer.parseInt((String) qps.get("id")); result = dynamoDb.getTable(DYNAMODB_TABLE_NAME) .getItem("id", id); } } if (result != null) { Person person = new Person(result.toJSON()); responseBody.put("Person", person); responseJson.put("statusCode", 200); } else { responseBody.put("message", "No item found"); responseJson.put("statusCode", 404); } JSONObject headerJson = new JSONObject(); headerJson.put("x-custom-header", "my custom header value"); responseJson.put("headers", headerJson); responseJson.put("body", responseBody.toString()); } catch (ParseException pex) { responseJson.put("statusCode", 400); responseJson.put("exception", pex); } OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8"); writer.write(responseJson.toString()); writer.close(); }

Sekali lagi, tiga langkah adalah relevan:

  1. Kami memeriksa sama ada pathParameters atau array queryStringParameters dengan atribut id ada.
  2. Sekiranya benar , kami menggunakan nilai kepunyaan untuk meminta item Orang dengan ID itu dari pangkalan data.
  3. Kami menambahkan representasi JSON dari item yang diterima pada respons.

The official documentation provides a more detailed explanation of input format and output format for Proxy Integration.

4.5. Building Code

Again, we can simply build our code using Maven:

mvn clean package shade:shade

The JAR file will be created under the target folder.

4.6. Creating the DynamoDB Table

We can create the table as explained in AWS Lambda Using DynamoDB With Java.

Let's choose Person as table name, id as primary key name, and Number as type of the primary key.

4.7. Deploying Code via AWS Console

After building our code and creating the table, we can now create the functions and upload the code.

This can be done by repeating steps 1-5 from the AWS Lambda with Java article, one time for each of our two methods.

Let's use the following function names:

  • StorePersonFunction for the handleRequest method (function 1)
  • GetPersonByHTTPParamFunction for the handleGetByParam method (function 2)

We also have to define an environment variable TABLE_NAME with value “Person”.

4.8. Testing the Functions

Before continuing with the actual API Gateway part, we can run a quick test in the AWS Console, just to check that our Lambda functions are running correctly and can handle the Proxy Integration format.

Testing a Lambda function from the AWS Console works as described in AWS Lambda with Java article.

However, when we create a test event, we have to consider the special Proxy Integration format, which our functions are expecting. We can either use the API Gateway AWS Proxy template and customize that for our needs, or we can copy and paste the following events:

For the StorePersonFunction, we should use this:

{ "body": "{\"id\": 1, \"name\": \"John Doe\"}" }

As discussed before, the body must have the type String, even if containing a JSON structure. The reason is that the API Gateway will send its requests in the same format.

The following response should be returned:

{ "isBase64Encoded": false, "headers": { "x-custom-header": "my custom header value" }, "body": "{\"message\":\"New item created\"}", "statusCode": 200 }

Here, we can see that the body of our response is a String, although it contains a JSON structure.

Let's look at the input for the GetPersonByHTTPParamFunction.

For testing the path parameter functionality, the input would look like this:

{ "pathParameters": { "id": "1" } }

And the input for sending a query string parameter would be:

{ "queryStringParameters": { "id": "1" } }

As a response, we should get the following for both cases methods:

{ "headers": { "x-custom-header": "my custom header value" }, "body": "{\"Person\":{\n \"id\": 88,\n \"name\": \"John Doe\"\n}}", "statusCode": 200 }

Again, the body is a String.

5. Creating and Testing the API

After we created and deployed the Lambda functions in the previous section, we can now create the actual API using the AWS Console.

Let's look at the basic workflow:

  1. Create an API in our AWS account.
  2. Add a resource to the resources hierarchy of the API.
  3. Create one or more methods for the resource.
  4. Set up the integration between a method and the belonging Lambda function.

We'll repeat steps 2-4 for each of our two functions in the following sections.

5.1. Creating the API

For creating the API, we'll have to:

  1. Sign in to the API Gateway console at //console.aws.amazon.com/apigateway
  2. Click on “Get Started” and then select “New API”
  3. Type in the name of our API (TestAPI) and acknowledge by clicking on “Create API”

Having created the API, we can now create the API structure and link it to our Lambda functions.

5.2. API Structure for Function 1

The following steps are necessary for our StorePersonFunction:

  1. Choose the parent resource item under the “Resources” tree and then select “Create Resource” from the “Actions” drop-down menu. Then, we have to do the following in the “New Child Resource” pane:
    • Type “Persons” as a name in the “Resource Name” input text field
    • Leave the default value in the “Resource Path” input text field
    • Choose “Create Resource”
  2. Choose the resource just created, choose “Create Method” from the “Actions” drop-down menu, and carry out the following steps:
    • Choose PUT from the HTTP method drop-down list and then choose the check mark icon to save the choice
    • Leave “Lambda Function” as integration type, and select the “Use Lambda Proxy integration” option
    • Choose the region from “Lambda Region”, where we deployed our Lambda functions before
    • Type “StorePersonFunction” in “Lambda Function”
  3. Choose “Save” and acknowledge with “OK” when prompted with “Add Permission to Lambda Function”

5.3. API Structure for Function 2 – Path Parameters

The steps for our retrieving path parameters are similar:

  1. Choose the /persons resource item under the “Resources” tree and then select “Create Resource” from the “Actions” drop-down menu. Then, we have to do the following in the New Child Resource pane:
    • Type “Person” as a name in the “Resource Name” input text field
    • Change the “Resource Path” input text field to “{id}”
    • Choose “Create Resource”
  2. Choose the resource just created, select “Create Method” from the “Actions” drop-down menu, and carry out the following steps:
    • Choose GET from the HTTP method drop-down list and then choose the check mark icon to save the choice
    • Leave “Lambda Function” as integration type, and select the “Use Lambda Proxy integration” option
    • Choose the region from “Lambda Region”, where we deployed our Lambda functions before
    • Type “GetPersonByHTTPParamFunction” in “Lambda Function”
  3. Choose “Save” and acknowledge with “OK” when prompted with “Add Permission to Lambda Function”

Note: it is important here to set the “Resource Path” parameter to “{id}”, as our GetPersonByPathParamFunction expects this parameter to be named exactly like this.

5.4. API Structure for Function 2 – Query String Parameters

The steps for receiving query string parameters are a bit different, as we don't have to create a resource, but instead have to create a query parameter for the id parameter:

  1. Choose the /persons resource item under the “Resources” tree, select “Create Method” from the “Actions” drop-down menu, and carry out the following steps:
    • Choose GET from the HTTP method drop-down list and then select the checkmark icon to save the choice
    • Leave “Lambda Function” as integration type, and select the “Use Lambda Proxy integration” option
    • Choose the region from “Lambda Region”, where we deployed our Lambda functions before
    • Type “GetPersonByHTTPParamFunction” in “Lambda Function”.
  2. Choose “Save” and acknowledge with “OK” when prompted with “Add Permission to Lambda Function”
  3. Choose “Method Request” on the right and carry out the following steps:
    • Expand the URL Query String Parameters list
    • Click on “Add Query String”
    • Type “id” in the name field, and choose the check mark icon to save
    • Select the “Required” checkbox
    • Click on the pen symbol next to “Request validator” on the top of the panel, select “Validate query string parameters and headers”, and choose the check mark icon

Note: It is important to set the “Query String” parameter to “id”, as our GetPersonByHTTPParamFunction expects this parameter to be named exactly like this.

5.5. Testing the API

Our API is now ready, but it's not public yet. Before we publish it, we want to run a quick test from the Console first.

For that, we can select the respective method to be tested in the “Resources” tree and click on the “Test” button. On the following screen, we can type in our input, as we would send it with a client via HTTP.

For StorePersonFunction, we have to type the following structure into the “Request Body” field:

{ "id": 2, "name": "Jane Doe" }

For the GetPersonByHTTPParamFunction with path parameters, we have to type 2 as a value into the “{id}” field under “Path”.

For the GetPersonByHTTPParamFunction with query string parameters, we have to type id=2 as a value into the “{persons}” field under “Query Strings”.

5.6. Deploying the API

Up to now, our API wasn't public and thereby was only available from the AWS Console.

As discussed before, when we deploy an API, we have to associate it with a stage, which is like a snapshot in time of the API. If we redeploy an API, we can either update an existing stage or create a new one.

Let's see how the URL scheme for our API will look:

//{restapi-id}.execute-api.{region}.amazonaws.com/{stageName}

The following steps are required for deployment:

  1. Choose the particular API in the “APIs” navigation pane
  2. Choose “Actions” in the Resources navigation pane and select “Deploy API” from the “Actions” drop-down menu
  3. Choose “[New Stage]” from the “Deployment stage” drop-down, type “test” in “Stage name”, and optionally provide a description of the stage and deployment
  4. Trigger the deployment by choosing “Deploy”

After the last step, the console will provide the root URL of the API, for example, //0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test.

5.7. Invoking the Endpoint

As the API is public now, we can call it using any HTTP client we want.

With cURL, the calls would look like as follows.

StorePersonFunction:

curl -X PUT '//0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons' \   -H 'content-type: application/json' \   -d '{"id": 3, "name": "Richard Roe"}'

GetPersonByHTTPParamFunction for path parameters:

curl -X GET '//0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons/3' \   -H 'content-type: application/json'

GetPersonByHTTPParamFunction for query string parameters:

curl -X GET '//0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons?id=3' \   -H 'content-type: application/json'

6. Conclusion

In this article, we had a look how to make AWS Lambda functions available as REST endpoints, using AWS API Gateway.

We explored the basic concepts and terminology of API Gateway, and we learned how to integrate Lambda functions using Lambda Proxy Integration.

Akhirnya, kami melihat cara membuat, menyebarkan, dan menguji API.

Seperti biasa, semua kod untuk artikel ini terdapat di GitHub.