src.dualinventive.com/go/nbiot-interface/internal/middleware/middleware.go

121 lines
3.1 KiB
Go

package middleware
import (
"bytes"
"context"
"encoding/json"
"net/http"
"time"
"src.dualinventive.com/go/lib/dilog"
"src.dualinventive.com/go/nbiot-interface/internal/cdp"
)
//Middleware represents something to wrap a message handler with.
//A middleware should provide both flavors, UDP and CDP.
type Middleware interface {
UDP(func([]byte)) func([]byte)
CDP(cdp.UplinkMsgReportHandlerFunc) cdp.UplinkMsgReportHandlerFunc
}
//EchoMiddleware implements the Middleware interface.
//It echoes incoming messages to a configured endpoint.
//The given headers will be used in the resulting communication.
type EchoMiddleware struct {
Logger dilog.Logger
Endpoint string
Headers map[string]string
client *http.Client
ch chan cdp.EndpointMessageReport
}
//CDP wraps a CDP message handler, and echos the incoming message after handling.
func (m *EchoMiddleware) CDP(handler cdp.UplinkMsgReportHandlerFunc) cdp.UplinkMsgReportHandlerFunc {
return func(emp *cdp.EndpointMessageReport) {
handler(emp)
m.queue(emp)
}
}
//UDP wraps a UDP message handler, and echos the incoming message after handling.
func (m *EchoMiddleware) UDP(handler func(payload []byte)) func(payload []byte) {
return func(payload []byte) {
handler(payload)
emp := cdp.NewEndpointMessage(payload, time.Now())
m.queue(emp)
}
}
//queue queues given message report in the echo channel.
//Messages in this channel will be sent over http post to configured endpoint/
func (m *EchoMiddleware) queue(emp *cdp.EndpointMessageReport) {
select {
case m.ch <- *emp:
m.Logger.Debug("echo message queued")
default:
m.Logger.Warning("echo buffer full, dropping message")
}
}
//send actually echoes the payload to the configured endpoint using a http request following CDP messaging.
func (m *EchoMiddleware) send(emp cdp.EndpointMessageReport) {
logger := m.Logger.WithField("emp", emp)
body, err := json.Marshal(emp)
if err != nil {
logger.WithError(err).Error("failed to marshal echo request body")
}
req, err := http.NewRequest("POST", m.Endpoint, bytes.NewBuffer(body))
if err != nil {
logger.WithError(err).Error("failed to create echo request")
}
req.Header.Add("Content-Type", "application/json")
for key, value := range m.Headers {
req.Header.Add(key, value)
}
resp, err := m.client.Do(req)
if err != nil {
logger.WithError(err).Error("failed to send echo request")
}
if resp.StatusCode != http.StatusOK {
logger.WithField("status", resp.StatusCode).Warning("echo response is not 200 OK")
}
}
//NewEchoMiddleware returns a new struct
func NewEchoMiddleware(
ctx context.Context,
logger dilog.Logger,
endpoint string,
headers map[string]string,
buffer int) *EchoMiddleware {
client := &http.Client{}
ch := make(chan cdp.EndpointMessageReport, buffer)
mw := &EchoMiddleware{
Logger: logger,
Endpoint: endpoint,
Headers: headers,
client: client,
ch: ch,
}
go func(ctx context.Context, m *EchoMiddleware, ch chan cdp.EndpointMessageReport) {
for {
select {
case emp := <-ch:
m.Logger.WithField("emp", emp).Debug("sending echo request")
m.send(emp)
case <-ctx.Done():
return
}
}
}(ctx, mw, ch)
return mw
}