Several years ago I was tinkering with AWS API Gateway and reading through the documentation on the Apache Velocity Template Language (VTL) that is used for data transformations. I noticed that one of the built-in variables was $context.identity.sourceIp which contained the client’s IP address.1 It occurred to me that I could make one of those “what’s my IP” services by pointing the root of the API gateway at a MOCK integration with a response of type text/plain containing only that variable. No Lambda function was needed. I tried it and it worked as expected. It has been running in one of my accounts for years and I use it in many of my build scripts (there isn’t much point to this since AWS has their own at https://checkip.amazonaws.com/).

In Terraform (or OpenTofu), the relevant resources look like this:

# Return caller IP as plain text via API Gateway (no backend)

resource "aws_api_gateway_rest_api" "ip" {
  name        = "ip"
  description = "My IP"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
}

resource "aws_api_gateway_method" "ip_method_get" {
  rest_api_id   = aws_api_gateway_rest_api.ip.id
  resource_id   = aws_api_gateway_rest_api.ip.root_resource_id
  http_method   = "GET"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "ip_get_integration" {
  rest_api_id = aws_api_gateway_rest_api.ip.id
  resource_id = aws_api_gateway_rest_api.ip.root_resource_id
  http_method = aws_api_gateway_method.ip_method_get.http_method

  type = "MOCK"

  passthrough_behavior = "WHEN_NO_MATCH"

  request_templates = {
    "application/json"                  = "{ \"statusCode\": 200 }"
    "application/xml"                   = "{ \"statusCode\": 200 }"
    "application/x-www-form-urlencoded" = "{ \"statusCode\": 200 }"
    "text/plain"                        = "{ \"statusCode\": 200 }"
  }
}

resource "aws_api_gateway_method_response" "ip_get_method_response_200" {
  rest_api_id = aws_api_gateway_rest_api.ip.id
  resource_id = aws_api_gateway_rest_api.ip.root_resource_id
  http_method = aws_api_gateway_method.ip_method_get.http_method

  status_code = 200

  response_models = {
    "text/plain" = "Empty"
  }

  response_parameters = {
    "method.response.header.Content-Type" = true
  }
}

resource "aws_api_gateway_integration_response" "ip_get_integration_response_200" {
  rest_api_id = aws_api_gateway_rest_api.ip.id
  resource_id = aws_api_gateway_rest_api.ip.root_resource_id
  http_method = aws_api_gateway_method.ip_method_get.http_method

  status_code = aws_api_gateway_method_response.ip_get_method_response_200.status_code

  response_templates = {
    "text/plain" = "$context.identity.sourceIp\n"
  }

  response_parameters = {
    "method.response.header.Content-Type" = "'text/plain'"
  }
}

The IP example is trivial, but the underlying pattern is more broadly useful. API gateway’s mapping layer can read request metadata and generate responses without a backend. In the spirit of serverless computing, I called this computeless servering.2 There are probably a lot of opportunities to provide similarly trivial but useful services using only the intermediary building blocks in various cloud services.


  1. From the perspective of the gateway, not necessarily its real original IP address. ↩︎

  2. Yes, technically there is a compute layer. There are servers in serverless computing too. ↩︎