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 }