Background
Many Apache APISIX users have required customization in enterprise environments to meet specific requirements in certain scenarios, despite it having many powerful built-in plugin features.
Users often choose to write plugins in Lua and mount them onto APISIX instances for use. However, Lua has a relatively limited audience. While it is easy to get started with, mastering it is not straightforward, and implementing complex data transformation logic in Lua can become quite intricate. Presently, only a portion of hooks is exposed by the Java Plugin Runner for developers to call, necessitating modifications to the Java Plugin Runner's source code for functionalities that are not directly supported.
The diagram below illustrates three common plugin usage patterns: Lua plugins run directly embedded in the APISIX core; Plugin Runner communicates via RPC with Plugin Runners in languages like Java, Golang, etc.; and WASM plugins are converted to bytecode and run internally in the APISIX core.
It has been observed from user feedback that the need for custom plugins is often related to data transformation such as HTTP request parameters and calling external APIs for data processing.
To address this, a new approach has been proposed to enhance the capabilities of Apache APISIX. This approach involves using only built-in plugins in Apache APISIX to configure common capabilities like authentication and rate limiting while placing newly customized logic in external services. The external service can be either a binary program or another API service, which will be treated as an upstream for APISIX. This external service will handle requests and responses like middleware. This approach can also be applied to other API gateways or proxy services.
Scenario Description
We have a series of services that provide data query services upstream (this article uses https://api-ninjas.com/api as an example). For instance, one can retrieve the latest weather information and information about the country (such as the country's GDP, capital name, and currency unit) based on the city name.
Our primary objective is to provide a generic request interface to developers while still being able to determine the data content they wish to retrieve based on the city name and data scope parameters. Additionally, to safeguard upstream services from abuse, we must add an authentication service to the developer interface, allowing only requests with the correct API Key to be forwarded to the upstream service.
Problem Analysis
Before introducing Node-Red, to meet the above requirements, APISIX developers would consider using Lua plugins. Although Apache APISIX provides detailed plugin development documentation, business developers need to learn Lua syntax and tuning techniques, understand the different request hooks exposed by APISIX, and continuously reload plugins for validation while writing logic for parameter extraction and validation. After completing testing, they also need to package Lua plugins into the APISIX program or distribute them to all APISIX instances for mounting.
The example requirements we provide in this blog involve parsing specific parameters from client requests and then constructing requests to retrieve data from different upstream services. However, we spent a lot of time dealing with transactions outside of business writing. Therefore, for such logic involving parameter conversion, format conversion, or external calls, we can adopt a lighter, more intuitive approach, which is precisely the problem that Node-Red can solve.
Node-Red Introduction
Node-RED is a powerful and easy-to-use flow-based programming tool suitable for automation and data flow processing tasks across various domains. Its programming interface, rich node library, and flexible extensibility allow us to quickly build complex flows and implement various application scenarios. Here are some of the nodes provided by Node-RED:
HTTP_IN node: Exposes an endpoint for external service invocation, which we will use as an upstream service for APISIX.
Function node: Allows developers to write code functions in JavaScript for modifying, deleting, etc., input/output.
Switch node: Allows developers to set a set of conditions to enter the next specified node when a condition is met.
HTTP_Request node: Can set URL, etc., to send data to this endpoint via Node-RED when executing the entire workflow.
Change node: Can add, modify, or delete specified values of a specified object.
HTTP_Response node: Used to return responses to clients.
In addition to the nodes listed above, Node-RED also provides many other built-in nodes. This article will show readers how to implement the above requirements through Node-RED.
Example Demonstration
Environment Setup
We will deploy the required components through containerization, using a DigitalOcean Droplet as the server resource.
$ doctl compute ssh-key list
ID Name FingerPrint
25621060 Zhiyuan Ju 2c:84:b7:d8:14:0a:a0:0f:ca:fe:ca:24:06:a4:fe:39
$ doctl compute droplet create \
--image docker-20-04 \
--size s-2vcpu-4gb-amd \
--region sgp1 \
--vpc-uuid 646cf2b8-03d8-4f48-b7c8-57cdee60ad27 \
--ssh-keys 25621060 \
apisix-nodered-docker-ubuntu-s-2vcpu-4gb-amd-sgp1-01
$ doctl compute droplet list
ID Name Public IPv4 Private IPv4 Public IPv6 Memory VCPUs Disk Region Image VPC UUID Status Tags Features Volumes
404094941 apisix-nodered-docker-ubuntu-s-2vcpu-4gb-amd-sgp1-01 143.198.192.64 10.104.0.3 4096 2 80 sgp1 Ubuntu Docker 25.0.3 on Ubuntu 22.04 646cf2b8-03d8-4f48-b7c8-57cdee60ad27 active droplet_agent,private_networking
Deploy Apache APISIX
We will use the APISIX Quickstart to start a new APISIX instance, for specific documentation please refer to https://docs.api7.ai/apisix/getting-started/.
$ curl -sL https://run.api7.ai/apisix/quickstart | sh
Deploy Node-RED
Node-RED provides multiple deployment methods, and we will deploy quickly via Docker and integrate it with the existing environment. For more deployment details, please refer to the official documentation: https://nodered.org/docs/getting-started/.
When deploying Node-RED, ensure that the container is added to the APISIX network to ensure it can communicate with APISIX and handle requests.
$ docker run -d -it -p 1880:1880 -v $PWD/configs/nodered/data:/data --network=apisix-quickstart-net --name mynodered -u Node-Red:dialout nodered/Node-Red
Configure Node-RED
- To handle requests entering Node-RED from APISIX, Node-Red needs to check if the parameters in the request exist and are valid. If the parameters are missing or invalid, an error message will be returned. If they are valid, the next node will be executed. In this specific scenario, we only allow data queries for two cities, Stockholm (city=stockholm) and Berlin (city=berlin).
- Once the request enters the next node, Node-RED needs to determine the type of data requested. In this scenario, there are three types: weather information (
scope=weather
), information about the country where the city is located (scope=country
), and the GDP of the country where the city is located (scope=gdp
).
- If both the
City
andScope
parameters are valid, Node-RED will determine which API to retrieve data from based on the value of scope. After setting the URL, Method, Payload, X-API-Key, etc., Node-RED's node will access the corresponding endpoint to retrieve data when triggered.
- When retrieving data for
scope=gdp
, Node-RED needs to extract the value of GDP from the response body of the external API. This can be done using the Change node for extraction or the Function node.
- Finally, Node-RED will return the processed data to APISIX, which will pass it on to the client. The final Node-RED diagram is shown below.
Creating APISIX Routes
To make the Node-Red service available to clients, we need to use APISIX as a reverse proxy for the endpoints that are exposed by Node-Red. Here are the steps that you need to follow:
Create an APISIX Route and set
mynodered:1880
as the upstream for this Route. By doing so, all requests sent to this endpoint will be forwarded to the Node-Red service.Enable Key Authentication to ensure that only requests carrying a valid API Key can pass the authentication and access the Node-Red service.
By following the above steps, we can securely expose the Node-Red service to clients and ensure that only authorized users can access it.
$ curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id": "proxy-global-data-endpoint",
"uri": "/global-data",
"upstream": {
"type": "roundrobin",
"nodes": {
"mynodered:1880": 1
}
},
"plugins": {
"key-auth": {}
}
}'
$ curl -i "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT -d '
{
"username": "tom",
"plugins": {
"key-auth": {
"key": "secret-key"
}
}
}'
Request Validation
We will attempt several scenarios separately to verify if APISIX and Node-Red behave as expected:
Scenario 1
Scenario Description: Access the API with an incorrect Key.
Expected Result: Since the API Key provided is incorrect, the request should be rejected, and the corresponding error message should be returned.
$ curl http://143.198.192.64:9080/global-data -H "apikey: invalid-key" -i
HTTP/1.1 401 Unauthorized
Date: Mon, 04 Mar 2024 07:47:24 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.8.0
{"message":"Invalid API key in request"}
Scenario 2
Scenario Description: Access the API with the correct Key, but an invalid
City
field.Expected Result: Since the request parameters do not meet the requirements, the corresponding error message should be returned, indicating that the
City
field is invalid.
$ curl "http://143.198.192.64:9080/global-data?city=singapore&scope=country" -H "apikey: secret-key" -i
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 69
Connection: keep-alive
Access-Control-Allow-Origin: *
X-Content-Type-Options: nosniff
ETag: W/"45-IOhgB2XkDHi2Kt4PP42n1xa8Gys"
Date: Mon, 04 Mar 2024 07:48:02 GMT
Server: APISIX/3.8.0
{"errorCode":400,"message":"Allowed city Options: Stockholm, Berlin"}
Scenario 3
Scenario Description: Access the API with the correct Key, and valid
City
andScope
fields to retrieve country data.Expected Result: The request should be successful, and relevant information about the country where the
City
is located should be returned.
$ curl "http://143.198.192.64:9080/global-data?city=stockholm&scope=country" -H "apikey: secret-key" -i
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 947
Connection: keep-alive
Access-Control-Allow-Origin: *
X-Content-Type-Options: nosniff
ETag: W/"3b3-XDlm9OHfuUrWH+g42q8L1F2uu/o"
Date: Mon, 04 Mar 2024 07:48:26 GMT
Server: APISIX/3.8.0
[{"gdp":556086,"sex_ratio":100.4,"surface_area":438574,"life_expectancy_male":80.8,"unemployment":6.7,"imports":158710,"homicide_rate":1.1,"currency":{"code":"SEK","name":"Swedish Krona"},"iso2":"SE","employment_services":80.7,"employment_industry":17.7,"urban_population_growth":1.1,"secondary_school_enrollment_female":157.9,"employment_agriculture":1.6,"capital":"Stockholm","co2_emissions":37.6,"forested_area":68.9,"tourists":7440,"exports":160538,"life_expectancy_female":84.4,"post_secondary_enrollment_female":82.1,"post_secondary_enrollment_male":52.7,"primary_school_enrollment_female":127.4,"infant_mortality":2,"gdp_growth":2.2,"threatened_species":98,"population":10099,"urban_population":87.7,"secondary_school_enrollment_male":148.1,"name":"Sweden","pop_growth":0.7,"region":"Northern Europe","pop_density":24.6,"internet_users":92.1,"gdp_per_capita":55766.8,"fertility":1.8,"refugees":310.4,"primary_school_enrollment_male":125.8}]
Scenario 4
Scenario Description: Access the API with the correct Key, and valid
City
andScope
fields to retrieveGDP
data.Expected Result: The request should be successful, and
GDP
data for the country where theCity
is located should be returned.
$ curl "http://143.198.192.64:9080/global-data?city=stockholm&scope=gdp" -H "apikey: secret-key" -i
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 6
Connection: keep-alive
Access-Control-Allow-Origin: *
ETag: W/"6-j8I5kokycgWjCeKC1c2UfJW7AQY"
Date: Mon, 04 Mar 2024 07:48:48 GMT
Server: APISIX/3.8.0
556086
By verifying these four scenarios, we can confirm that APISIX and Node-Red are working as expected and can correctly handle various types of requests.
Summary
We provide a new approach to more cleverly address the problem of custom capability development, demonstrated by a detailed example.
API Request Routing and Identity Verification: First, utilizing Apache APISIX's routing functionality and authentication plugins, APISIX forwards client requests to the Node-Red service when the provided credentials are valid.
Request Processing and Transformation: In Node-Red, we create a flow to process incoming API requests. By using the HTTP input node to receive requests from APISIX, we parse and validate request parameters to ensure they meet business requirements.
Business Logic Processing: Once a valid request is received, we can execute business logic in Node-Red. For example, sending requests to different business APIs based on parameters to retrieve data and extracting required fields from the response. After completing these operations, the final data is returned to APISIX.
Error Handling and Logging: During processing, if any errors or exceptions occur, we can add error-handling nodes in Node-Red to capture and handle exceptional situations. Additionally, we can use logging nodes to record key information during processing for subsequent troubleshooting and analysis, which is not demonstrated in this example.
By combining APISIX and Node-Red, we can visually implement a complete request-handling process, including request routing, data processing, business logic, etc., without the need for writing complex code or plugins. This flexible, customizable solution can help us build and adjust system functionality more quickly, improve development efficiency, reduce development costs, and ensure system stability and scalability.