ironhook

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2022 License: MIT Imports: 21 Imported by: 0

README

tests

iron hook

You're looking at a simple plug-in webhook service for your Golang application.

It's agnostic of your wider architectural choices and doesnt make too many assumptions about your application.

It persists state like registered endpoints and all sent notifications to a database controlled by the Gorm ORM, but the database can be as simpe as an in-memory sqlite if you don't require persistence between processes.

How it works

The service runs around two structs: WebhookEndpoint and a WebhookNotification.

You can create the service like this:

service, err := ironhook.NewWebhookService(nil)

Then we can create a new WebhookEndpoint like this:

e := ironhook.WebhookEndpoint{
    URL: "http://localhost:8080"
}

endpoint := service.Create(e)

At first, the endpoint is Unverified, but we can fix that quickly by Verifying it:

verified_endpoint, err := service.Verify(endpoint)

You may want to persist the UUID of this endpoint to be able to reference it in the future

endpoint_id := verified_endpoint.UUID // save to your database

And with that out of the way, we can send notifications

uuid_trace := uuid.Must(uuid.NewV4())

notification := ironhook.NewNotification(
    event: uuid_trace,
    topic: "Batch processing #1 complete",
    body: "All batches have been processed"
)

err := service.Notify(verified_endpoint, notification)

Returning customer

If you want to send another notification to the same endpoint, the starting point will be the UUID of the endpoint.

endpoint_id := verified_endpoint.UUID // or read from your DB

With that, we can create a new notification and send it in one go:

endpoint := ironhook.WebhookEndpoint{
    UUID: endpoint_id,
}

notification := ironhook.NewNotification(
    event: uuid_trace,
    topic: "Batch processing #2 complete",
    body: "All batches have been processed"
)

err := service.Notify(endpoint, notification)

Verification

Every Endpoint has to be verified before it can be used for notifications.

The verification process is relatively simple and best shown with an example.

Given an endpoint like this:

webhooks.example.com

A verification request will be sent to

webhooks.example.com/verification

with an id query parameter cointaining a uuid identifier of the endpoint, like this: id=6551e000-947a-40a4-948d-18d01e3660d4

Putting it together, a request like this will be made:

webhooks.example.com/verification?id=6551e000-947a-40a4-948d-18d01e3660d4

And in response to this request, we expect to see the uuid as body of the response.

In Golang, one of the simpler approaches to the verification would be something like this:

func VerificationHandler(w http.ResponseWriter, r *http.Request) {

    qID, ok := r.URL.Query()["id"]
    
    if !ok || len(qID[0]) < 1 {
		fmt.Fprintf(w, "URL Parameter <id> is missing")
		return
	}
    
    fmt.Fprint(w, qID[0])
}

Which you can also see in the examples section: _examples/receiver/http_handlers.go

Configuration

Database

By default, if no environment variables are specified, the service will persist state to an in-memory sqlite database.

If you want to have it stored in a file, you might want to specify the below:

HOOK_DB_DSN=webhooks.db

Available database engine options are:

  • postgres
    • cockroach
  • mysql
  • sqlserver
  • sqlite

Naturally, you can use databases like cockroachdb since they may be compatible with one of the supported engines. In this example, cockroachdb is compatible with postgres.

In order to specify an engine you can use:

HOOK_DB_ENGINE=postgres

And specify the connection string for it:

HOOK_DB_DSN=postgres://postgres:123456@localhost:5432/mywebapp
Logging

The service uses zap for logging, at the moment, you can configure the logging level:

HOOK_LOG_LEVEL=warn

By default, info level is used.

If you specify an unsupported log level, the configuration will default to info.

HTTP Client

You can also tell ironhook to use your pre-configured http client if the default isn't what you're after.

http_client = &http.Client{
    Timeout: time.Second * 3,
}
service, err := ironhook.NewWebhookService(http_client)

The above is also the default configuration.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrEmptyEndpointURL error = errors.New(
	`
	cant accept an empty URL in an Endpoint declaration. 
	Recover by retrying with a non-empty URL in your Endpoint declaration
	`,
)
View Source
var ErrEmptyEndpointURLScheme error = errors.New(
	`
	cant accept a URL without http/s.
	you can recover by retrying with "https://"+endpoint
	`,
)
View Source
var ErrEmptyEndpointUUID error = errors.New(
	`
	cant accept an empty UUID in an Endpoint. 
	Recover by retrying with a non-empty UUID in your Endpoint
	`,
)
View Source
var ErrEndpointNotYetActivated error = errors.New(
	`the endpoint youre trying to notify hasnt yet been activated.
	recover by running service.Verify(endpoint) first`,
)
View Source
var ErrFailedEndpointVerification error = errors.New(
	"failed to verify an endpoint",
)
View Source
var ErrFailedNotifyingTheEndpoint error = errors.New(
	`the endpoint youre trying to notify returned an error.
	This is perhaps an external issue, recovering might require 
	checking if the request body is parsed correctly and on both ends`,
)
View Source
var ErrIncorrectEndpointURL error = errors.New(
	"cant accept a URL like this for verification",
)
View Source
var ErrIncorrectVerificationResponse error = errors.New(
	"expected a different endpoint verification response",
)
View Source
var ErrInternalProcessingError error = errors.New(
	`an internal error occured that shouldn't have happened.
	Please submit an issue with as much detail as possible`,
)
View Source
var ErrRecordNotFound = errors.New(
	`
	Cant find the requested record,
	Recover by retrying with a different identifier
	or, where applicable, by enabling persistence.
	`,
)
View Source
var ErrUnsupportedDatabaseEngine error = errors.New(
	`
	unsupported database engine.
	Recover by retrying with either one of the documented database engines
	`,
)
View Source
var ErrUnsupportedEndpointURLScheme error = errors.New(
	"cant accept a URL without http/s for verification",
)

Functions

func VerificationHandler

func VerificationHandler(w http.ResponseWriter, r *http.Request)

Handles incoming Verification requests. To be used from the perspective of the webhook receiver.

Types

type WebhookEndpoint

type WebhookEndpoint struct {
	UUID   uuid.UUID             `json:"uuid"`
	URL    string                `json:"url"`
	Status WebhookEndpointStatus `json:"status"`
}

type WebhookEndpointDB

type WebhookEndpointDB struct {
	gorm.Model
	UUID   uuid.UUID             `gorm:"type:uuid"`
	URL    string                `gorm:"not null"`
	Status WebhookEndpointStatus `gorm:"not null"`
}

type WebhookEndpointService

type WebhookEndpointService interface {
	Create(WebhookEndpoint) (WebhookEndpoint, error)
	UpdateURL(WebhookEndpoint) (WebhookEndpoint, error)
	Verify(WebhookEndpoint) (WebhookEndpoint, error)
	Get(WebhookEndpoint) (WebhookEndpoint, error)
	Delete(WebhookEndpoint) error
	Notify(WebhookEndpoint, WebhookNotification) error
	LastNotificationSent(WebhookEndpoint) (WebhookNotification, error)
	ListEndpoints() (*[]WebhookEndpoint, error)
}

func NewWebhookService

func NewWebhookService(custom_http_client *http.Client) (WebhookEndpointService, error)

Creates a new Webhook service, connects to a database and applies migrations

type WebhookEndpointServiceImpl

type WebhookEndpointServiceImpl struct {
	WebhookEndpointService
	// contains filtered or unexported fields
}

func (*WebhookEndpointServiceImpl) Create

Creates a new unverified Webhook Endpoint. The next step would be to run the verification process on this endpoint.

verified_endpoint, err := service.Verify(endpoint)

Otherwise you will not be able to send Notifiations to the endpoint.

func (*WebhookEndpointServiceImpl) Delete

func (s *WebhookEndpointServiceImpl) Delete(endpoint WebhookEndpoint) error

Deletes the indicated Endpoint

endpoint.UUID is used to find the webhook in the database.

func (*WebhookEndpointServiceImpl) Get

Fetches the indicated Endpoint from the database

endpoint.UUID is used to find the webhook in the database.

func (*WebhookEndpointServiceImpl) LastNotificationSent

func (s *WebhookEndpointServiceImpl) LastNotificationSent(endpoint WebhookEndpoint) (WebhookNotification, error)

func (*WebhookEndpointServiceImpl) ListEndpoints

func (s *WebhookEndpointServiceImpl) ListEndpoints() (*[]WebhookEndpoint, error)

func (*WebhookEndpointServiceImpl) Notify

func (s *WebhookEndpointServiceImpl) Notify(endpoint WebhookEndpoint, notification WebhookNotification) error

Notify sends a Notification to a verified Endpoint.

Notification's Topic and Body can be empty

func (*WebhookEndpointServiceImpl) UpdateURL

Updates the endpoint with a new URL. The endpoint is then switched to Unverified so you will have to verify it again.

verified_endpoint, err := service.Verify(endpoint)

Otherwise you will not be able to send Notifiations to the endpoint.

endpoint.UUID is used to find the webhook in the database.

func (*WebhookEndpointServiceImpl) Verify

Verifies user's control over the provided endpoint. Runs a simple check to see if the endpoint responds with an expected answer.

given a request like below

<endpoint.URL>/verification?id=abcd

The verification process expects to see "abcd" in the response body.

type WebhookEndpointStatus

type WebhookEndpointStatus int
const (
	Unverified WebhookEndpointStatus = iota
	Suspended
	Verified
	Healthy
)

type WebhookNotification

type WebhookNotification struct {
	EventUUID uuid.UUID `json:"event_uuid"`
	Topic     string    `json:"topic"`
	Body      string    `json:"body"`
}

type WebhookNotificationDB

type WebhookNotificationDB struct {
	gorm.Model
	EventUUID    uuid.UUID `gorm:"type:uuid"`
	Topic        string    `gorm:"not null"`
	Body         string    `gorm:"not null"`
	EndpointUUID uuid.UUID `gorm:"type:uuid"`
}

Directories

Path Synopsis
_examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL