Update request path

In this tutorial we will see how to conditionally update the request path by using a transformation.

Setup

This guide assumes that you have installed Gloo into the gloo-system namespace and that glooctl is installed on your machine. We will also use the jq command line utility to pretty print JSON strings.

We will need an upstream service to serve as the target for the requests that we will send to test the Gloo configurations in this tutorial. To this end, we will use the publicly available Postman Echo service. It exposes a set of endpoints that are very useful for inspecting both the requests sent upstream and the resulting responses; please refer to the official documentation for more information about the service.

Let’s create a static upstream to represent the postman-echo.com remote service.

apiVersion: gloo.solo.io/v1
kind: Upstream
metadata:
  name: postman-echo
  namespace: gloo-system
spec:
  static:
    hosts:
    - addr: postman-echo.com
      port: 80

Let’s also create a simple Virtual Service that matches any path and routes all traffic to our Upstream:


apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: update-request-path
  namespace: gloo-system
spec:
  virtualHost:
    domains:
    - '*'
    routes:
    - matcher:
        prefix: /
      routeAction:
        single:
          upstream:
            name: postman-echo
            namespace: gloo-system

glooctl create vs --name update-request-path --namespace gloo-system 
glooctl add route --name update-request-path --path-prefix / --dest-name postman-echo

Let’s test that the configuration was correctly picked up by Gloo by executing the following command:

curl $(glooctl proxy url)/get | jq

You should get a response with status 200 and a JSON body similar to this:

{
  "args": {},
  "headers": {
    "x-forwarded-proto": "https",
    "host": "postman-echo.com",
    "accept": "*/*",
    "user-agent": "curl/7.54.0",
    "x-envoy-expected-rq-timeout-ms": "15000",
    "x-request-id": "3ed578a1-b33a-40db-b18a-e8d11e24c22c",
    "x-forwarded-port": "80"
  },
  "url": "https://postman-echo.com/get"
}

Update Virtual Service

We will now configure Gloo to update the request path from /get to /post if a header named foo is present and has the value bar. Since the /post endpoint on the Postman Echo service expected POST requests, we will need to update the HTTP method of the request as well.

To do this, we need to add the following to our Virtual Service definition:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: update-request-path
  namespace: gloo-system
spec:
  virtualHost:
    domains:
    - '*'
    routes:
    - matcher:
        prefix: /
      routeAction:
        single:
          upstream:
            name: postman-echo
            namespace: gloo-system
    virtualHostPlugins:
      transformations:
        requestTransformation:
          transformationTemplate:
            headers:
              # By updating the :path pseudo-header, we update the request URI
              ":path":
                text: '{% if header("foo") == "bar" %}/post{% else %}{{ header(":path") }}{% endif %}'
              # By updating the :method pseudo-header, we update the request HTTP method
              ":method":
                text: '{% if header("foo") == "bar" %}POST{% else %}{{ header(":method") }}{% endif %}'

The above virtualHostPlugins configuration is to be interpreted as following:

  1. Add a transformation to all traffic handled by this Virtual Host.
  2. Apply the transformation only to requests.
  3. Use a template transformation.
  4. Update the :path and :method pseudo-headers if the foo header is present and has value bar; otherwise keep the original values.

The template uses the Inja templating language to define the conditional logic that will be applied to the :path and :method pseudo-headers.

Test our configuration

To test that our configuration has been correctly applied, let’s add the foo header with the expected bar value to the previously used curl command:

curl -H "foo: bar" $(glooctl proxy url)/get | jq

You should get the following output:

{
  "args": {},
  "data": {},
  "files": {},
  "form": {},
  "headers": {
    "x-forwarded-proto": "https",
    "host": "postman-echo.com",
    "content-length": "0",
    "accept": "*/*",
    "foo": "bar",
    "user-agent": "curl/7.54.0",
    "x-envoy-expected-rq-timeout-ms": "15000",
    "x-request-id": "193637cb-d551-4c0e-80a6-866218c25c3b",
    "x-forwarded-port": "80"
  },
  "json": null,
  "url": "https://postman-echo.com/post"
}

Notice that the url attribute now displays a /post path where it previously displayed /get.

Now let’s try omitting the header from the request:

curl $(glooctl proxy url)/get | jq

We will hit the same endpoint as we did at the beginning of this tutorial:

{
  "args": {},
  "headers": {
    "x-forwarded-proto": "https",
    "host": "postman-echo.com",
    "accept": "*/*",
    "user-agent": "curl/7.54.0",
    "x-envoy-expected-rq-timeout-ms": "15000",
    "x-request-id": "f8844eba-85cd-4253-81a2-12f143d9db41",
    "x-forwarded-port": "80"
  },
  "url": "https://postman-echo.com/get"
}

Cleanup

To cleanup the resources created in this tutorial you can run the following commands:

kubectl delete virtualservice -n gloo-system update-request-path
kubectl delete upstream -n gloo-system postman-echo