diff --git a/app/_src/gateway/plugin-development/get-started/http.md b/app/_src/gateway/plugin-development/get-started/http.md index 4f9eef9d80ca..6fc439b79f6e 100644 --- a/app/_src/gateway/plugin-development/get-started/http.md +++ b/app/_src/gateway/plugin-development/get-started/http.md @@ -47,12 +47,12 @@ local cjson = require("cjson.safe") ### 2. Invoke 3rd party http request -The `lua-rest-htpp` library provides a simple "single shot" http request +The `lua-rest-http` library provides a simple "single shot" http request function we can use to reach out to our 3rd party service. Here we show invoking a `GET` request to the _httpbin.org/anything_ API which will echo back various information in the response. -Add the following to the `MyPluginHandler:response` function inside the +Add the following to the top of the `MyPluginHandler:response` function inside the `handler.lua` module: ```lua @@ -63,24 +63,92 @@ local res, err = httpc:request_uri("http://httpbin.org/anything", { }) ``` +If the request to the 3rd party service is successful, the `res` +variable will contain the response. Before showing how to process the +successful response, what about error events? +Errors will be provided in the `err` return value, let's see what +options there are for handling them. + ### 3. Handle response errors -handle errors +The {{site.base_gateway}} +[Plugin Development Kit](/gateway/{{page.release_version}}/plugin-development/pdk/) +provides you with various functions to help you handle error conditions. + +In this example you are processing responses from the upstream service +and decorating your client response with values from the 3rd party service. +If the request to the 3rd party service fails, you have a choice to make. +You can terminate processing of the response and return to the client with +an error, or you could continue processing the response and not complete the +custom header logic. In this example, we show how to terminate the +response processing and return a `500` internal server error to the client. + +Add the following to the `MyPluginHandler:response` function immediately +after the `httpc:request_uri` call: + +```lua +if err then + return kong.response.error(500, + "Error when trying to access 3rd party service: " .. err, + { ["Content-Type"] = "text/html" }) +end +``` + +If you choose to continue processing instead, you could log an +error message and `return` from the `MyPluginHandler:response` function, +similar to this: + +```lua +if err then + kong.log("Error when trying to access 3rd party service:", err) + return +end +``` ### 4. Process JSON data from 3rd party response -introduce CJSON library -process response -handle errors +This 3rd party service returns a JSON object in the +response body. Here we are going to show how to parse and extract a +single value from the JSON body. + +Use the `decode` function in the `lua-cjson` library passing in the +`res.body` value received from the `request_uri` function: + +```lua +local body_table, err = cjson.decode(res.body) +``` + +The `decode` function returns a tuple of values. The first value contains the +result of a successful decoding and represents the JSON as a table containing +the parsed data. If an error has occurred, the second value will contain +error information (or `nil` on success). + +Same as during the HTTP processing above, in the event of an error return an +error response to the client and stop processing. Add the following to +the `MyPluginHandler:response` function after the previous line: -### 5. Decorate upstream response +```lua +if err then + return kong.response.error(500, + "Error while decoding 3rd party service response: " .. err, + { ["Content-Type"] = "text/html" }) +end +``` -take JSON field and add it to response before completing -return response +At this point, given no error conditions, you have a valid response from the +3rd party you can use to decorate your client response. The _httpbin_ service returns +a variety of fields including a `url` field which is an echo of the URL requested. +For this example pass the `url` field to the client response by adding the following +after the previous error handling: -### 6. Full code listing +```lua +kong.response.set_header(conf.response_header_name, body_table.url) +``` -This is now the full code listing for the `handler.lua` file: +We've broken down each section of the code. The following is a full code +listing for the `handler.lua` file. + +### 5. Full code listing ```lua local http = require("resty.http") @@ -93,29 +161,68 @@ local MyPluginHandler = { function MyPluginHandler:response(conf) + kong.log("response handler") + local httpc = http.new() local res, err = httpc:request_uri("http://httpbin.org/anything", { method = "GET", }) + if err then - kong.log("request failed: ", err) - return + return kong.response.error(500, + "Error when trying to access 3rd party service: " .. err, + { ["Content-Type"] = "text/html" }) end local body_table, err = cjson.decode(res.body) + if err then - kong.log("failed to decode response body: ", err) - return + return kong.response.error(500, + "Error when decoding 3rd party service response: " .. err, + { ["Content-Type"] = "text/html" }) end - kong.response.set_header(conf.response_header_name, body_table.origin) + kong.response.set_header( + conf.response_header_name, + body_table.url) end return MyPluginHandler ``` +### 6. Update testing code + +At this stage, if you re-run the `pongo run` command to execute +the integration tests previously built, you will receive errors. The +expected value in the header has changed from `response` to +`http://httpbin.org/anything`. Update the +`spec/my-plugin/01-integration_spec.lua` file to assert the new +value in the header. + +```lua +-- validate the value of that header +assert.equal("http://httpbin.org/anything", header_value) +``` + +Re-run the test with `pongo run` and verify success: + +```sh +... +[----------] Global test environment teardown. +[==========] 2 tests from 1 test file ran. (23171.28 ms total) +[ PASSED ] 2 tests. +``` + +This guide provides examples to get you started. In a real plugin development +scenario, you would want to build integration tests for 3rd party services by +providing a test dependency using a mock service instead +of making network calls to the actual 3rd party service used. Pongo supports +test dependencies for this purpose. See the +[Pongo documentation](https://github.com/Kong/kong-pongo?tab=readme-ov-file#test-dependencies) +for details on setting test dependencies. + ## What's Next Deploying {{site.base_gateway}} plugins is the final step in building an end to end diff --git a/app/_src/gateway/plugin-development/get-started/testing.md b/app/_src/gateway/plugin-development/get-started/testing.md index ac3191b54f50..fcc1c10f03c2 100644 --- a/app/_src/gateway/plugin-development/get-started/testing.md +++ b/app/_src/gateway/plugin-development/get-started/testing.md @@ -91,7 +91,7 @@ Once the dependencies are running successfully, we run a {{site.base_gateway}} c shell within it so we can interact with the gateway. Pongo runs a {{site.base_gateway}} container with various CLI tools pre-installed to help with testing. -Launch the gateway and open shell with: +Launch the gateway and open a shell with: ```sh pongo shell