The easy way to trigger any HTTP service

Jonathan Michaux

Jonathan Michaux

May 31, 2023
The easy way to trigger any HTTP service
The easy way to trigger any HTTP service

TriggerMesh is a powerful open-source solution for building event-driven applications. One of the defining characteristics of TriggerMesh and similar event brokers like Amazon EventBridge, Azure EventGrid, and Google EventArc, is that they are push-based. This means that events are pushed to consumers, as opposed to consumers pulling events from the broker like they would with Kafka or most queue-based systems. This makes TriggerMesh very well suited to triggering HTTP-based services such as Google Cloud functions, Google Cloud Run services, Azure Functions, Knative services, or microservices. In this post, we’ll make use of improvements to the HTTP Target in 1.25 that make it simpler than ever to trigger any HTTP service with events from TriggerMesh's broad range of event sources.

If on the other hand your service specifically expects a CloudEvent as an input, or you'd like an example of how to build a CloudEvents service from the ground up, then take a look at this post that shows how to build a CloudEvents Knative service using Python FastAPI and route CloudEvents to it with TriggerMesh.

The general idea for this post is illustrated below :

In this example, I'm going to deploy a simple service called the http-order-service, that receives a POST request on /orders , where the payload contains a JSON object representing an order. The service prints the order to standard out and responds to the caller with Order received. when things go well. We'll use TriggerMesh to expose another HTTP endpoint (the Webhook source) whose role will be to produce events into the TriggerMesh broker. We're using the Webhook source as an example here that doesn't require any external dependencies, but you could easily replace this with something like a Kafka source, AWS SQS source, Google Pub/Sub source, or Azure Service Bus source, among others. 

As always, be sure to join the TriggerMesh community Slack so we can help you make the best of your event-driven journey 😀.

Local development with Docker and tmctl

As you may know, TriggerMesh comes in two flavours: local development with our CLI called tmctl which only requires Docker as a prerequisite. The other flavour is TriggerMesh on Kubernetes, thanks to our provided custom resources and controllers, and Helm charts or YAML files to install them. 

We'll start with local development, then show you how you can easily transition what you've done to a Kubernetes cluster (Minikube shown here).

Setup

If you haven't already, make sure you've installed tmctl (e.g. brew install triggermesh/cli/tmctl) and that you have Docker on your machine.

Run the following sequence of commands to create the necessary components to run this example locally.

tmctl create broker triggermesh
tmctl create source webhook --eventType com.acme.orders.orderCreated
tmctl create target http --name orders-http-target --method POST --endpoint http://host.docker.internal:49160/orders --headers 'Content-type: application/json'
tmctl create trigger --target orders-http-target --eventTypes com.acme.orders.orderCreated
docker run -p 49160:8080 -d jmcx/http-order-service

The first line creates a TriggerMesh broker that will receive and buffer events. The second line creates a Webhook source, which acts as an HTTP endpoint to which you can send events, which will be passed to the broker. We're explicitly setting the type of all events coming from the Webhook source to com.acme.orders.orderCreated. Then we're creating an HTTP target that is configured to send events as JSON to our HTTP service using the POST method on path /orders. We then create a trigger which tells the broker to send events of type com.acme.orders.orderCreated to the HTTP target. 

The last line deploys a simple example HTTP service (built by yours truly) that expects a JSON object representing an order to arrive via HTTP POST on path /orders. If you already have your own HTTP service that you want to route events to (Cloud Run service, function, etc...) you can omit the docker run command, and modify the http target's --endpoint parameter to point to your service instead. For example, if you have a Cloud Run service available at a public URL like https://triggermesh-console-tu4luqbmqq-uc.a.run.app, then you can set the --endpoint parameter for the HTTP target to that value. 

After running these commands, if you now run tmctl describe, you should see the components you've created:

tmctl describe 
Broker          Status
triggermesh     online(http://localhost:54023)

Trigger                          Target                 Filter
triggermesh-trigger-685d47e3     orders-http-target     type is com.acme.orders.orderCreated

Source                        Kind              EventTypes                       Status
triggermesh-webhooksource     webhooksource     com.acme.orders.orderCreated     online(http://localhost:54034)

Target                 Kind           Expected Events                    Status
orders-http-target     httptarget     io.triggermesh.http.request, *     online(http://localhost:54044)

You can also run docker ps to make sure the http-order-service is up and running (you'll also see the TriggerMesh components running, except the Trigger which is built into the Broker):

docker ps
CONTAINER ID   IMAGE                                              COMMAND                  CREATED          STATUS          PORTS                     NAMES
8f0927b4ff74   jmcx/http-order-service                            "docker-entrypoint.s…"   43 seconds ago   Up 42 seconds   0.0.0.0:49160->8080/tcp   brave_austin
e7e1edca5a72   gcr.io/triggermesh/httptarget-adapter:v1.25.0      "/ko-app/httptarget-…"   2 minutes ago    Up 2 minutes    0.0.0.0:54044->8080/tcp   orders-http-target
d558b296a2e2   gcr.io/triggermesh/webhooksource-adapter:v1.25.0   "/ko-app/webhooksour…"   2 minutes ago    Up 2 minutes    0.0.0.0:54034->8080/tcp   triggermesh-webhooksource
f9e764c87e25   gcr.io/triggermesh/memory-broker:v1.3.0            "/memory-broker star…"   3 minutes ago    Up 3 minutes    0.0.0.0:54023->8080/tcp   triggermesh-broker

Test it out

Before doing anything else, lets start a new terminal and run tmctl watch so that we can observe events passing through the broker.

Now that everything is running, we can send an order JSON object into the Webhook source using curl. Note the URL next to the Webhook source in the tmctl describe output, you'll need to modify this to use your own value for the curl URL:

curl -d '{"orderid": 11, "ordertime": 1497014121580, "category": "books", "itemid": "331", "brand": "Penguin", "name": "Bonnie Garmus - Lessons in Chemistry"}' \
		 -H "Content-Type: application/json" \
     http://localhost:54034/orders

In the tmctl watch terminal, you should see two CloudEvents appear:

tmctl watch
2023/05/28 21:32:24 triggermesh | Connecting to broker
2023/05/28 21:32:28 triggermesh | Watching...
☁️  cloudevents.Event
Context Attributes,
  specversion: 1.0
  type: com.acme.orders.orderCreated
  source: local.triggermesh-webhooksource
  id: 71bc3db8-9b7e-4215-9864-bb1684d9be55
  time: 2023-05-28T19:34:50.978620125Z
  datacontenttype: application/json
Data,
  {
    "orderid": 11,
    "ordertime": 1497014121580,
    "category": "books",
    "itemid": "331",
    "brand": "Penguin",
    "name": "Bonnie Garmus - Lessons in Chemistry"
  }
☁️  cloudevents.Event
Context Attributes,
  specversion: 1.0
  type: io.triggermesh.targets.response
  source: io.triggermesh.httptarget.local.orders-http-target
  id: eb8d5100-83ca-4521-b88e-6b4ddde26030
  time: 2023-05-28T19:34:51.091479625Z
  datacontenttype: text/html; charset=utf-8
Data,
  Order received.

The first event was sent to the broker by the Webhook source, after making the curl request. You can see that the payload of the HTTP request has been wrapped into the Data attribute, and that CloudEvents metadata has been added to the event. 

The second event is actually the response from the external HTTP service that I've routed the event to, confirming that it has correctly received the event from the HTTP target. Success 👌. 

In case you're wondering, the HTTP target will create a new event from the response it got from the external target. You don't have to use this response, but if you wanted to you could write another Trigger on the Broker that fires when the HTTP service responds, to trigger an additional action. 

We can also check the logs of the http-order-service docker container (update the container ID to your own):

docker logs 8f0927b4ff74
Example app listening on port 8080
{
  orderid: 11,
  ordertime: 1497014121580,
  category: 'books',
  itemid: '331',
  brand: 'Penguin',
  name: 'Bonnie Garmus - Lessons in Chemistry'
}

As you can see the service has successfully received the event and logged it. 

As a next step, if you haven't already done so you could replace the docker http-order-service by your own service, and replace the webhook source with your own source of events such as Kafka, SQS, or any of the other event sources supported by TriggerMesh. 

Read on if you want to learn about how to run the same event flow on Kubernetes.

On Kubernetes

Once we've implemented our application with tmctl, it is very easy to export is as a Kubernetes manifest by using the tmctl dump command. Try it and see for yourself! Before I do it here though, I'm going to do a couple more things with tmctl first. See, the http-order-service is currently running as a Docker container and won't be accessible to TriggerMesh if the latter is running on Kubernetes. So I'm going to use a little trick that will let me turn the http-order-service into a Knative service that'll run on Kubernetes. To do this, I'll run the following command:

tmctl create target --from-image jmcx/http-order-service

This command will run my http-order-service and make it available as a TriggerMesh target from tmctl. This can be very useful, but that not exactly how I'm going to use it just now. The other effect this command has is that this service will now become a Knative service when I do tmctl dump. Lets run it now and have a look:

tmctl dump
---
apiVersion: eventing.triggermesh.io/v1alpha1
kind: RedisBroker
metadata:
  labels:
    triggermesh.io/context: triggermesh
  name: triggermesh
---
apiVersion: sources.triggermesh.io/v1alpha1
kind: WebhookSource
metadata:
  labels:
    triggermesh.io/context: triggermesh
  name: triggermesh-webhooksource
spec:
  eventType: com.acme.orders.orderCreated
  sink:
    ref:
      apiVersion: eventing.triggermesh.io/v1alpha1
      kind: RedisBroker
      name: triggermesh
---
apiVersion: targets.triggermesh.io/v1alpha1
kind: HTTPTarget
metadata:
  labels:
    triggermesh.io/context: triggermesh
  name: orders-http-target
spec:
  endpoint: http://host.docker.internal:49160/orders
  headers:
    Content-type: ' application/json'
  method: POST
---
apiVersion: eventing.triggermesh.io/v1alpha1
kind: Trigger
metadata:
  labels:
    triggermesh.io/context: triggermesh
  name: triggermesh-trigger-685d47e3
spec:
  broker:
    group: eventing.triggermesh.io
    kind: RedisBroker
    name: triggermesh
  filters:
  - exact:
      type: com.acme.orders.orderCreated
  target:
    ref:
      apiVersion: targets.triggermesh.io/v1alpha1
      kind: httptarget
      name: orders-http-target
---
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  labels:
    triggermesh.io/context: triggermesh
    triggermesh.io/role: target
  name: triggermesh-target-service
spec:
  template:
    spec:
      containers:
      - env: []
        image: jmcx/http-order-service
        name: user-container

As expected, the output manifest contains a definition of my Broker, Webhook source, HTTP target, Trigger, and the newly added http-order-service in the form of a Knative service. We're going to use this as a basis for running the example on Kubernetes.

Setup

If you haven’t already done so, you’ll need to install TriggerMesh and Knative Serving on your cluster. I’m using minikube (minikube start) to create a local cluster and I followed the guide for installing TriggerMesh and Knative Serving onto the cluster. The whole process should only take a few minutes.

Once you've installed TriggerMesh (and Knative) on Minikube, apply the previously dumped manifest to the cluster:

kubectl apply -f manifest.yaml
redisbroker.eventing.triggermesh.io/triggermesh created
webhooksource.sources.triggermesh.io/triggermesh-webhooksource created
httptarget.targets.triggermesh.io/orders-http-target created
trigger.eventing.triggermesh.io/triggermesh-trigger-685d47e3 created
service.serving.knative.dev/triggermesh-target-service created

If you view the pods in the default namespace, you'll now see pods corresponding to the different TriggerMesh components and the http-order-service (called triggermesh-target-service). Note that because the Webhook source, the HTTP target, and the http-order-service all run as Knative services, they will scale to 0 when idle, so you might need to send events into the system before you see the pods spin up. In my case, they're spinning up initially before they scale down to 0, so I can see the pods like so:

kubectl get po
NAME                                                              READY   STATUS    RESTARTS   AGE
httptarget-orders-http-target-00001-deployment-54f755b7f8-qhhsn   2/2     Running   0          2m5s
knative-operator-66f5b45fcd-j6k68                                 1/1     Running   0          24m
operator-webhook-9f5487b8f-xtqpf                                  1/1     Running   0          24m
triggermesh-rb-broker-77558fc5c5-c4cdv                            1/1     Running   0          104s
triggermesh-rb-redis-6d5985d478-d9kcx                             1/1     Running   0          2m6s
triggermesh-target-service-00001-deployment-5f5545fc4c-8qqn7      2/2     Running   0          2m5s
webhooksource-triggermesh-webhooksource-00001-deployment-6c2jc8   2/2     Running   0          51s

Lets get the cluster-local address of our http-order-service so that we can update the HTTP target's endpoint to that value:

kubectl get ksvc triggermesh-target-service 
NAME                         URL                                                            LATESTCREATED                      LATESTREADY                        READY   REASON
triggermesh-target-service   http://triggermesh-target-service.default.svc.cluster.local   triggermesh-target-service-00001   triggermesh-target-service-00001   True   

Now update the relevant part of the manifest.yaml, i.e. the HTTP target's endpoint (don't forget the /orders path at the end of the URL):

apiVersion: targets.triggermesh.io/v1alpha1
kind: HTTPTarget
metadata:
  labels:
    triggermesh.io/context: triggermesh
  name: orders-http-target
spec:
  endpoint: http://triggermesh-target-service.default.svc.cluster.local/orders
  headers:
    Content-type: ' application/json'
  method: POST

And apply the manifest again so that the endpoint gets updated:

kubectl apply -f manifest.yaml 
redisbroker.eventing.triggermesh.io/triggermesh unchanged
webhooksource.sources.triggermesh.io/triggermesh-webhooksource unchanged
httptarget.targets.triggermesh.io/orders-http-target configured
trigger.eventing.triggermesh.io/triggermesh-trigger-685d47e3 unchanged
service.serving.knative.dev/triggermesh-target-service configured

Test it out!

Run minikube tunnel in a separate terminal, so that the load balancer service is assigned an IP. That loadbalancer IP is used by the sslip.io config.

Run the command below to get the webhook source’s URL (accessible outside of the cluster thanks to minikube tunnel).

kubectl get webhooksources.sources.triggermesh.io
NAME                        READY   REASON   URL                                                                         SINK                                                     AGE
triggermesh-webhooksource   True             http://webhooksource-triggermesh-webhooksource.default.127.0.0.1.sslip.io   http://triggermesh-rb-broker.default.svc.cluster.local   12m

Then send an event into your webhook source:

curl -d '{"orderid": 11, "ordertime": 1497014121580, "category": "books", "itemid": "331", "brand": "Penguin", "name": "Bonnie Garmus - Lessons in Chemistry"}' \
     -H "Content-Type: application/json" \
     http://webhooksource-triggermesh-webhooksource.default.127.0.0.1.sslip.io/orders

Quickly run kubectl get pods and if all went well, you should see the HTTP target pods, the Webhook source pods, and the http-order-service pods (called triggermesh-target-service) spin up:

kubectl get po                                                                 
NAME                                                              READY   STATUS    RESTARTS   AGE
httptarget-orders-http-target-00005-deployment-6b78ffdfc8-bbsbd   2/2     Running   0          9s
knative-operator-66f5b45fcd-j6k68                                 1/1     Running   0          49m
operator-webhook-9f5487b8f-xtqpf                                  1/1     Running   0          49m
triggermesh-rb-broker-77558fc5c5-c4cdv                            1/1     Running   0          27m
triggermesh-rb-redis-6d5985d478-d9kcx                             1/1     Running   0          27m
triggermesh-target-service-00001-deployment-5f5545fc4c-bt8vq      2/2     Running   0          3s
webhooksource-triggermesh-webhooksource-00001-deployment-6mwcp2   2/2     Running   0          4s

Again, these three services run as Knative services, which is why you’ll only see their pods appear when there are events to process (else they scale down to 0). 

If you look at the pod logs for the http-order-service, you’ll see that it has received the event:

kubectl logs triggermesh-target-service-00001-deployment-5f5545fc4c-bt8vq
Defaulted container "user-container" out of: user-container, queue-proxy
Example app listening on port 8080
{
  orderid: 11,
  ordertime: 1497014121580,
  category: 'books',
  itemid: '331',
  brand: 'Penguin',
  name: 'Bonnie Garmus - Lessons in Chemistry'
}

👏👏👏

Wrapping up

The goal of this post was to show how to trigger any HTTP service such as a cloud function, knative service, or any other service with an HTTP endpoint. We ran through an example with tmctl, and then showed how you can transition this to something similar on Kubernetes. 

Improvements in the 1.25 release have made this much easier, because now by default the HTTP target will take the value of the Data attribute (your event payload), and pass it as the HTTP payload to its destination. Prior to 1.25, some transformation was required before you could do this.

Now that you’ve seen how to do this, you can easily use other event sources to trigger the service other than the Webhook source shown in this demo. For instance, you could try triggering it when an event lands on an AWS SQS queue, a Google Pub/Sub topic, or an Azure Event Hub.

If you got this far, let us know all about it on Slack! We'd love to hear from you, and are always happy to help. 

Create your first event flow in under 5 minutes