2023-04-27 09:03:40 +08:00
|
|
|
package writer
|
|
|
|
|
|
|
|
import (
|
2024-08-09 03:50:41 +08:00
|
|
|
"bbb-graphql-middleware/internal/common"
|
|
|
|
"bbb-graphql-middleware/internal/msgpatch"
|
2024-03-28 02:29:38 +08:00
|
|
|
"context"
|
2024-06-25 21:27:44 +08:00
|
|
|
"encoding/json"
|
2024-03-28 02:29:38 +08:00
|
|
|
"errors"
|
2024-08-08 22:28:46 +08:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2023-04-27 09:03:40 +08:00
|
|
|
log "github.com/sirupsen/logrus"
|
2024-06-25 21:27:44 +08:00
|
|
|
"nhooyr.io/websocket"
|
2024-03-29 04:17:13 +08:00
|
|
|
"os"
|
2024-01-31 22:37:28 +08:00
|
|
|
"strings"
|
|
|
|
"sync"
|
2023-04-27 09:03:40 +08:00
|
|
|
)
|
|
|
|
|
2024-08-08 22:28:46 +08:00
|
|
|
var ()
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-04-27 09:03:40 +08:00
|
|
|
// HasuraConnectionWriter
|
|
|
|
// process messages (middleware to hasura)
|
2024-07-06 00:35:08 +08:00
|
|
|
func HasuraConnectionWriter(hc *common.HasuraConnection, wg *sync.WaitGroup, initMessage []byte) {
|
2023-04-27 09:03:40 +08:00
|
|
|
log := log.WithField("_routine", "HasuraConnectionWriter")
|
|
|
|
|
2024-03-13 21:35:51 +08:00
|
|
|
browserConnection := hc.BrowserConn
|
2023-04-27 09:03:40 +08:00
|
|
|
|
|
|
|
log = log.WithField("browserConnectionId", browserConnection.Id).WithField("hasuraConnectionId", hc.Id)
|
|
|
|
|
|
|
|
defer wg.Done()
|
|
|
|
defer hc.ContextCancelFunc()
|
2023-09-07 22:54:27 +08:00
|
|
|
defer log.Debugf("finished")
|
2023-04-27 09:03:40 +08:00
|
|
|
|
2024-01-24 07:20:16 +08:00
|
|
|
//Send authentication (init) message at first
|
|
|
|
//It will not use the channel (fromBrowserToHasuraChannel) because this msg must bypass ChannelFreeze
|
2024-05-30 04:43:17 +08:00
|
|
|
if initMessage == nil {
|
|
|
|
log.Errorf("it can't start Hasura Connection because initMessage is null")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
//Send init connection message to Hasura to start
|
2024-06-25 21:27:44 +08:00
|
|
|
err := hc.Websocket.Write(hc.Context, websocket.MessageText, initMessage)
|
2024-05-30 04:43:17 +08:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error on write authentication (init) message (we're disconnected from hasura): %v", err)
|
|
|
|
return
|
2024-01-24 07:20:16 +08:00
|
|
|
}
|
|
|
|
|
2023-04-27 09:03:40 +08:00
|
|
|
RangeLoop:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-hc.Context.Done():
|
|
|
|
break RangeLoop
|
2024-07-06 00:35:08 +08:00
|
|
|
case fromBrowserMessage := <-hc.BrowserConn.FromBrowserToHasuraChannel.ReceiveChannel():
|
2023-04-27 09:03:40 +08:00
|
|
|
{
|
|
|
|
if fromBrowserMessage == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
//var fromBrowserMessageAsMap = fromBrowserMessage.(map[string]interface{})
|
2023-04-27 09:03:40 +08:00
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
var browserMessage common.BrowserSubscribeMessage
|
|
|
|
err := json.Unmarshal(fromBrowserMessage, &browserMessage)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to unmarshal message: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if browserMessage.Type == "subscribe" {
|
|
|
|
var queryId = browserMessage.ID
|
2023-04-27 09:03:40 +08:00
|
|
|
|
2023-05-10 02:37:58 +08:00
|
|
|
//Identify type based on query string
|
|
|
|
messageType := common.Query
|
2024-02-03 01:37:32 +08:00
|
|
|
var lastReceivedDataChecksum uint32
|
2024-01-24 02:28:32 +08:00
|
|
|
streamCursorField := ""
|
|
|
|
streamCursorVariableName := ""
|
|
|
|
var streamCursorInitialValue interface{}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
query := browserMessage.Payload.Query
|
|
|
|
if query != "" {
|
2023-05-10 02:37:58 +08:00
|
|
|
if strings.HasPrefix(query, "subscription") {
|
2024-04-09 01:15:15 +08:00
|
|
|
//Validate if subscription is allowed
|
|
|
|
if allowedSubscriptions := os.Getenv("BBB_GRAPHQL_MIDDLEWARE_ALLOWED_SUBSCRIPTIONS"); allowedSubscriptions != "" {
|
|
|
|
allowedSubscriptionsSlice := strings.Split(allowedSubscriptions, ",")
|
|
|
|
subscriptionAllowed := false
|
|
|
|
for _, s := range allowedSubscriptionsSlice {
|
2024-06-25 21:27:44 +08:00
|
|
|
if s == browserMessage.Payload.OperationName {
|
2024-04-09 01:15:15 +08:00
|
|
|
subscriptionAllowed = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !subscriptionAllowed {
|
2024-06-25 21:27:44 +08:00
|
|
|
log.Infof("Subscription %s not allowed!", browserMessage.Payload.OperationName)
|
2024-06-07 00:49:58 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Validate if subscription is allowed
|
|
|
|
if deniedSubscriptions := os.Getenv("BBB_GRAPHQL_MIDDLEWARE_DENIED_SUBSCRIPTIONS"); deniedSubscriptions != "" {
|
|
|
|
deniedSubscriptionsSlice := strings.Split(deniedSubscriptions, ",")
|
|
|
|
subscriptionAllowed := true
|
|
|
|
for _, s := range deniedSubscriptionsSlice {
|
2024-06-25 21:27:44 +08:00
|
|
|
if s == browserMessage.Payload.OperationName {
|
2024-06-07 00:49:58 +08:00
|
|
|
subscriptionAllowed = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !subscriptionAllowed {
|
2024-06-25 21:27:44 +08:00
|
|
|
log.Infof("Subscription %s not allowed!", browserMessage.Payload.OperationName)
|
2024-04-09 01:15:15 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-10 02:37:58 +08:00
|
|
|
messageType = common.Subscription
|
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
browserConnection.ActiveSubscriptionsMutex.RLock()
|
|
|
|
existingSubscriptionData, queryIdExists := browserConnection.ActiveSubscriptions[queryId]
|
|
|
|
browserConnection.ActiveSubscriptionsMutex.RUnlock()
|
|
|
|
if queryIdExists {
|
2024-02-03 01:37:32 +08:00
|
|
|
lastReceivedDataChecksum = existingSubscriptionData.LastReceivedDataChecksum
|
2024-07-09 01:17:08 +08:00
|
|
|
streamCursorField = existingSubscriptionData.StreamCursorField
|
|
|
|
streamCursorVariableName = existingSubscriptionData.StreamCursorVariableName
|
|
|
|
streamCursorInitialValue = existingSubscriptionData.StreamCursorCurrValue
|
2024-02-02 23:36:27 +08:00
|
|
|
}
|
|
|
|
|
2023-05-10 02:37:58 +08:00
|
|
|
if strings.Contains(query, "_stream(") && strings.Contains(query, "cursor: {") {
|
|
|
|
messageType = common.Streaming
|
2024-01-24 02:28:32 +08:00
|
|
|
if !queryIdExists {
|
2024-06-25 21:27:44 +08:00
|
|
|
streamCursorField, streamCursorVariableName, streamCursorInitialValue = common.GetStreamCursorPropsFromBrowserMessage(browserMessage)
|
2024-01-24 02:28:32 +08:00
|
|
|
|
|
|
|
//It's necessary to assure the cursor field will return in the result of the query
|
|
|
|
//To be able to store the last received cursor value
|
2024-06-25 21:27:44 +08:00
|
|
|
browserMessage.Payload.Query = common.PatchQueryIncludingCursorField(query, streamCursorField)
|
|
|
|
|
|
|
|
newMessageJson, _ := json.Marshal(browserMessage)
|
|
|
|
fromBrowserMessage = newMessageJson
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|
2023-05-10 02:37:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if strings.Contains(query, "_aggregate") && strings.Contains(query, "aggregate {") {
|
|
|
|
messageType = common.SubscriptionAggregate
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasPrefix(query, "mutation") {
|
|
|
|
messageType = common.Mutation
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-25 06:31:31 +08:00
|
|
|
//Identify if the client that requested this subscription expects to receive json-patch
|
|
|
|
//Client append `Patched_` to the query operationName to indicate that it supports
|
|
|
|
jsonPatchSupported := false
|
2024-06-25 21:27:44 +08:00
|
|
|
if strings.HasPrefix(browserMessage.Payload.OperationName, "Patched_") {
|
2023-05-25 06:31:31 +08:00
|
|
|
jsonPatchSupported = true
|
|
|
|
}
|
2024-03-29 04:17:13 +08:00
|
|
|
if jsonPatchDisabled := os.Getenv("BBB_GRAPHQL_MIDDLEWARE_JSON_PATCH_DISABLED"); jsonPatchDisabled != "" {
|
|
|
|
jsonPatchSupported = false
|
|
|
|
}
|
2023-05-25 06:31:31 +08:00
|
|
|
|
2024-01-27 01:42:35 +08:00
|
|
|
browserConnection.ActiveSubscriptionsMutex.Lock()
|
|
|
|
browserConnection.ActiveSubscriptions[queryId] = common.GraphQlSubscription{
|
2024-01-30 05:40:17 +08:00
|
|
|
Id: queryId,
|
2024-06-25 21:27:44 +08:00
|
|
|
Message: fromBrowserMessage,
|
|
|
|
OperationName: browserMessage.Payload.OperationName,
|
2024-01-30 05:40:17 +08:00
|
|
|
StreamCursorField: streamCursorField,
|
|
|
|
StreamCursorVariableName: streamCursorVariableName,
|
|
|
|
StreamCursorCurrValue: streamCursorInitialValue,
|
|
|
|
LastSeenOnHasuraConnection: hc.Id,
|
|
|
|
JsonPatchSupported: jsonPatchSupported,
|
|
|
|
Type: messageType,
|
2024-02-03 01:37:32 +08:00
|
|
|
LastReceivedDataChecksum: lastReceivedDataChecksum,
|
2023-04-27 09:03:40 +08:00
|
|
|
}
|
2024-01-27 01:42:35 +08:00
|
|
|
// log.Tracef("Current queries: %v", browserConnection.ActiveSubscriptions)
|
|
|
|
browserConnection.ActiveSubscriptionsMutex.Unlock()
|
2024-03-28 22:26:56 +08:00
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
common.ActivitiesOverviewStarted(string(messageType) + "-" + browserMessage.Payload.OperationName)
|
2024-03-29 04:17:13 +08:00
|
|
|
common.ActivitiesOverviewStarted("_Sum-" + string(messageType))
|
|
|
|
|
2024-08-08 22:28:46 +08:00
|
|
|
//Add Prometheus Metrics
|
|
|
|
if messageType == common.Subscription {
|
|
|
|
common.GqlSubscriptionCounter.With(prometheus.Labels{"operationName": browserMessage.Payload.OperationName}).Inc()
|
|
|
|
} else if messageType == common.Streaming {
|
|
|
|
common.GqlSubscriptionStreamingCounter.With(prometheus.Labels{"operationName": browserMessage.Payload.OperationName}).Inc()
|
|
|
|
} else if messageType == common.Query {
|
|
|
|
common.GqlQueriesCounter.With(prometheus.Labels{"operationName": browserMessage.Payload.OperationName}).Inc()
|
|
|
|
}
|
|
|
|
|
2024-04-06 22:43:36 +08:00
|
|
|
//Dump of all subscriptions for analysis purpose
|
2024-06-29 03:53:11 +08:00
|
|
|
//queryCounter++
|
|
|
|
//saveItToFile(fmt.Sprintf("%02d-%s-%s", queryCounter, string(messageType), browserMessage.Payload.OperationName), fromBrowserMessage)
|
|
|
|
//saveItToFile(fmt.Sprintf("%s-%s-%02s", string(messageType), operationName, queryId), fromBrowserMessage)
|
2023-04-27 09:03:40 +08:00
|
|
|
}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
if browserMessage.Type == "complete" {
|
2023-07-06 20:34:48 +08:00
|
|
|
browserConnection.ActiveSubscriptionsMutex.RLock()
|
2024-06-25 21:27:44 +08:00
|
|
|
jsonPatchSupported := browserConnection.ActiveSubscriptions[browserMessage.ID].JsonPatchSupported
|
2024-03-28 22:26:56 +08:00
|
|
|
|
|
|
|
//Remove subscriptions from ActivitiesOverview here once Hasura-Reader will ignore "complete" msg for them
|
2024-06-25 21:27:44 +08:00
|
|
|
common.ActivitiesOverviewCompleted(string(browserConnection.ActiveSubscriptions[browserMessage.ID].Type) + "-" + browserConnection.ActiveSubscriptions[browserMessage.ID].OperationName)
|
|
|
|
common.ActivitiesOverviewCompleted("_Sum-" + string(browserConnection.ActiveSubscriptions[browserMessage.ID].Type))
|
2024-03-28 22:26:56 +08:00
|
|
|
|
2023-07-06 20:34:48 +08:00
|
|
|
browserConnection.ActiveSubscriptionsMutex.RUnlock()
|
2023-05-26 04:08:04 +08:00
|
|
|
if jsonPatchSupported {
|
2024-06-25 21:27:44 +08:00
|
|
|
msgpatch.RemoveConnSubscriptionCacheFile(browserConnection.Id, browserConnection.SessionToken, browserMessage.ID)
|
2023-05-10 02:37:58 +08:00
|
|
|
}
|
2023-04-27 09:03:40 +08:00
|
|
|
browserConnection.ActiveSubscriptionsMutex.Lock()
|
2024-06-25 21:27:44 +08:00
|
|
|
delete(browserConnection.ActiveSubscriptions, browserMessage.ID)
|
2023-04-27 09:03:40 +08:00
|
|
|
// log.Tracef("Current queries: %v", browserConnection.ActiveSubscriptions)
|
|
|
|
browserConnection.ActiveSubscriptionsMutex.Unlock()
|
|
|
|
}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
if browserMessage.Type == "connection_init" {
|
2024-05-30 04:43:17 +08:00
|
|
|
//browserConnection.ConnectionInitMessage = fromBrowserMessageAsMap
|
|
|
|
//Skip message once it is handled by ConnInitHandler already
|
|
|
|
continue
|
2023-04-27 09:03:40 +08:00
|
|
|
}
|
|
|
|
|
2024-07-03 02:35:15 +08:00
|
|
|
log.Tracef("sending to hasura: %s", string(fromBrowserMessage))
|
2024-06-25 21:27:44 +08:00
|
|
|
errWrite := hc.Websocket.Write(hc.Context, websocket.MessageText, fromBrowserMessage)
|
|
|
|
if errWrite != nil {
|
|
|
|
if !errors.Is(errWrite, context.Canceled) {
|
|
|
|
log.Errorf("error on write (we're disconnected from hasura): %v", errWrite)
|
2024-03-28 02:29:38 +08:00
|
|
|
}
|
2023-04-27 09:03:40 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-06 22:43:36 +08:00
|
|
|
|
|
|
|
//
|
2024-06-29 03:53:11 +08:00
|
|
|
//var queryCounter = 0
|
|
|
|
//
|
|
|
|
//func saveItToFile(filename string, contentInBytes []byte) {
|
2024-04-06 22:43:36 +08:00
|
|
|
// filePath := fmt.Sprintf("/tmp/%s.txt", filename)
|
2024-06-29 03:53:11 +08:00
|
|
|
// //message, err := json.Marshal(contentInBytes)
|
2024-04-06 22:43:36 +08:00
|
|
|
//
|
|
|
|
// fmt.Printf("Saving %s\n", filePath)
|
|
|
|
//
|
|
|
|
// file, err := os.Create(filePath)
|
|
|
|
// if err != nil {
|
|
|
|
// panic(err)
|
|
|
|
// }
|
|
|
|
// defer file.Close()
|
|
|
|
//
|
2024-06-29 03:53:11 +08:00
|
|
|
// _, err = file.Write(contentInBytes)
|
2024-04-06 22:43:36 +08:00
|
|
|
// if err != nil {
|
|
|
|
// panic(err)
|
|
|
|
// }
|
|
|
|
//}
|