How to Read Barcodes in AWS Lambda with Python

In the previous article, we built a simple barcode reader server in Python and deployed it on Vercel. In this article, we are going to deploy it on AWS Lambda to provide a serverless barcode reading interface.

Getting started with Dynamsoft Barcode Reader

New Function

Create a new AWS Lambda function. Here, we do this in the AWS console.

New function

Add a Layer

A Lambda layer is a .zip file archive that contains supplementary code or data. Layers usually contain library dependencies, a custom runtime, or configuration files.

Here, we need to add the package of Dynamsoft Barcode Reader in the layer.

  1. Download the wheel file of Dynamsoft Barcode Reader from pypi.
  2. Unzip it into a folder named python.
  3. Zip the folder for uploading as a layer.

New layer

Remember to use the layer for the function:

Use layer

Write the Handler

Next, modify the handler to use Dynamsoft Barcode Reader.

The handler receives a request like the following:

{
  "base64":"<base64-encoded-image>"
}

Then outputs the following:

{"results": 
  [
    {
      "barcodeFormat": "QR_CODE", 
      "barcodeText": "https://www.dynamsoft.com",
      "barcodeBytes": "aHR0cHM6Ly93d3cuZHluYW1zb2Z0LmNvbQ==",
      "confidence": 82,
      "x1": 7,
      "y1": 7,
      "x2": 93,
      "y2": 6,
      "x3": 94,
      "y3": 94,
      "x4": 6,
      "y4": 93
    }
  ]
}
  1. Import Dynamsoft Barcode Reader and initialize its license. It needs to write caches so that if the license is valid, it does not need to connect to the license server again. Since in AWS Lambda, we can only write to “/tmp”, we need to set a path for saving the cache.

    from dbr import *
    folder_path = "/tmp/dynamsoft"
    if not os.path.exists(folder_path):
        os.makedirs(folder_path)
    # Sets a directory path for saving the license cache.
    BarcodeReader.set_license_cache_path(folder_path)
    error = BarcodeReader.init_license("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==") #one-day public trial
    

    You can apply for a trial license here.

  2. In the handler, create a barcode reader instance to read barcodes from the base64.

    def lambda_handler(event, context):
        try:
            if error[0] != EnumErrorCode.DBR_OK:
                return ("License error: "+ error[1])
    
            dbr = BarcodeReader()
            base64_string = event["base64"]
    
            text_results = dbr.decode_base64_string(base64_string)
            result_dict = {}
            results = []
    
            if text_results != None:
                for tr in text_results:
                    result = {}
                    result["barcodeFormat"] = tr.barcode_format_string
                    result["barcodeText"] = tr.barcode_text
                    result["barcodeBytes"] = str(base64.b64encode(tr.barcode_bytes))[2:-1]
                    result["confidence"] = tr.extended_results[0].confidence
                    points = tr.localization_result.localization_points
                    result["x1"] = points[0][0]
                    result["y1"] = points[0][1]
                    result["x2"] = points[1][0]
                    result["y2"] = points[1][1]
                    result["x3"] = points[2][0]
                    result["y3"] = points[2][1]
                    result["x4"] = points[3][0]
                    result["y4"] = points[3][1]
                    results.append(result)
            result_dict["results"] = results
            return json.dumps(result_dict)
        except BarcodeReaderError as bre:
            print(bre)
            return bre.error_info
    

Add an API Gateway Trigger

We can add an API gateway as the trigger of the function.

New trigger

Then, we can call the Lambda function through HTTP.

Here is the Python script to test it:

import base64
import json
import requests

endpoint = "" #like https://*****.execute-api.us-east-2.amazonaws.com/default/BarcodeReader

def get_picture_base64_data(image_path):
    with open(image_path, 'rb') as image_file:
        base64_data = base64.b64encode(image_file.read())
    return base64_data.decode('utf-8')
    
def decode():
    base64 = get_picture_base64_data("./sample_qr.png")
    body = {"base64": base64}
    json_data = json.dumps(body)
    headers = {'Content-type': 'application/json'}
    r = requests.post(endpoint, data=json_data, headers=headers)
    print(r.json())

if __name__ == "__main__":
    decode()

However, you may encounter a 500 internal server error. The actual request we get in the Lambda function is like the following:

{'version': '1.0', 'resource': '/BarcodeReader', 'path': '/default/BarcodeReader', 'httpMethod': 'POST', 'headers': {'Content-Length': '562', 'Content-Type': 'application/json', 'Host': 'toln285pca.execute-api.us-east-2.amazonaws.com', 'User-Agent': 'python-requests/2.30.0', 'X-Amzn-Trace-Id': 'Root=1-64ed99a4-1e6d47e30bea8194466bc16a', 'X-Forwarded-For': '144.34.164.64', 'X-Forwarded-Port': '443', 'X-Forwarded-Proto': 'https', 'accept': '*/*', 'accept-encoding': 'gzip, deflate, zstd'}, 'multiValueHeaders': {'Content-Length': ['562'], 'Content-Type': ['application/json'], 'Host': ['toln285pca.execute-api.us-east-2.amazonaws.com'], 'User-Agent': ['python-requests/2.30.0'], 'X-Amzn-Trace-Id': ['Root=1-64ed99a4-1e6d47e30bea8194466bc16a'], 'X-Forwarded-For': ['144.34.164.64'], 'X-Forwarded-Port': ['443'], 'X-Forwarded-Proto': ['https'], 'accept': ['*/*'], 'accept-encoding': ['gzip, deflate, zstd']}, 'queryStringParameters': None, 'multiValueQueryStringParameters': None, 'requestContext': {'accountId': '660918440189', 'apiId': 'toln285pca', 'domainName': 'toln285pca.execute-api.us-east-2.amazonaws.com', 'domainPrefix': 'toln285pca', 'extendedRequestId': 'KaTx0h0vCYcEJVQ=', 'httpMethod': 'POST', 'identity': {'accessKey': None, 'accountId': None, 'caller': None, 'cognitoAmr': None, 'cognitoAuthenticationProvider': None, 'cognitoAuthenticationType': None, 'cognitoIdentityId': None, 'cognitoIdentityPoolId': None, 'principalOrgId': None, 'sourceIp': '144.34.164.64', 'user': None, 'userAgent': 'python-requests/2.30.0', 'userArn': None}, 'path': '/default/BarcodeReader', 'protocol': 'HTTP/1.1', 'requestId': 'KaTx0h0vCYcEJVQ=', 'requestTime': '29/Aug/2023:07:09:24 +0000', 'requestTimeEpoch': 1693292964948, 'resourceId': 'POST /BarcodeReader', 'resourcePath': '/BarcodeReader', 'stage': 'default'}, 'pathParameters': None, 'stageVariables': None, 'body': '{"base64": "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkAQAAAABYmaj5AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAd2KE6QAAAAJcEhZcwAAFxEAABcRAcom8z8AAAEBSURBVDjLtdQ9roUgEAXgIRR0sgETt2FhwrbsoHNbJBRug8QNQEdBmDfed39eI8JLrt1XqGeOg4B/rgDfUAFgWcsjAYgbKYxopJ8R9y4tEK2QmAJvUIYy/Ue4Bd4gSpZ1gU/ORlETbhvG+d3Llc5WrQD+6fpKBVbghsbAo1fSkV7JroVWA2i6T3rRo7JodOl8SlZ1oV0jIvMwjKJLzgw8wTiXaa+rLCIoI9E+k7WKJhpAs4OiqrqKMoEKpmvCHj2+5nrWMIq6HhuC0c/3oq2zdOhU+H1fs2jLXWL5mfNGuLFjpzXsldUBVHyf6StRMrcB0ESqS9QETeSVzKKur/8VfwD2jzXBAMmUVwAAAABJRU5ErkJggg=="}', 'isBase64Encoded': False}

And it requires us to return the response in the body with a status code.

{
  "statusCode":200,
  "body":""
}

We have to modify the handler like the following to make it work.

 import json
 import base64
 from dbr import *

 folder_path = "/tmp/dynamsoft"
 if not os.path.exists(folder_path):
     os.makedirs(folder_path)
 # Sets a directory path for saving the license cache.
 BarcodeReader.set_license_cache_path(folder_path)
 error = BarcodeReader.init_license("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==") #one-day public trial

 def lambda_handler(event, context):
     try:
         if error[0] != EnumErrorCode.DBR_OK:
             return ("License error: "+ error[1])

         dbr = BarcodeReader()
-        base64_string = event["base64"]
+        request_body = event["body"]
+        base64_string = json.loads(request_body)["base64"]

         text_results = dbr.decode_base64_string(base64_string)
         result_dict = {}
         results = []

         if text_results != None:
             for tr in text_results:
                 result = {}
                 result["barcodeFormat"] = tr.barcode_format_string
                 result["barcodeText"] = tr.barcode_text
                 result["barcodeBytes"] = str(base64.b64encode(tr.barcode_bytes))[2:-1]
                 result["confidence"] = tr.extended_results[0].confidence
                 points = tr.localization_result.localization_points
                 result["x1"] = points[0][0]
                 result["y1"] = points[0][1]
                 result["x2"] = points[1][0]
                 result["y2"] = points[1][1]
                 result["x3"] = points[2][0]
                 result["y3"] = points[2][1]
                 result["x4"] = points[3][0]
                 result["y4"] = points[3][1]
                 results.append(result)
         result_dict["results"] = results
-        return json.dumps(result_dict)
+        return {
+            "statusCode": 200,
+            "body": json.dumps(result_dict)
+        }
     except BarcodeReaderError as bre:
         print(bre)
         return bre.error_info
         

Source Code

The source code of the project is available here: https://github.com/tony-xlh/barcode-reader-aws-lambda/