github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/dataexchange/dxhttps/dxhttps.go (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package dxhttps 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "io" 24 "strings" 25 26 "github.com/go-resty/resty/v2" 27 "github.com/kaleido-io/firefly/internal/config" 28 "github.com/kaleido-io/firefly/internal/i18n" 29 "github.com/kaleido-io/firefly/internal/log" 30 "github.com/kaleido-io/firefly/internal/restclient" 31 "github.com/kaleido-io/firefly/internal/wsclient" 32 "github.com/kaleido-io/firefly/pkg/dataexchange" 33 "github.com/kaleido-io/firefly/pkg/fftypes" 34 ) 35 36 type HTTPS struct { 37 ctx context.Context 38 capabilities *dataexchange.Capabilities 39 callbacks dataexchange.Callbacks 40 client *resty.Client 41 wsconn wsclient.WSClient 42 } 43 44 type wsEvent struct { 45 Type msgType `json:"type"` 46 Sender string `json:"sender"` 47 Recipient string `json:"recipient"` 48 RequestID string `json:"requestID"` 49 Path string `json:"path"` 50 Message string `json:"message"` 51 Hash string `json:"hash"` 52 Error string `json:"error"` 53 } 54 55 type msgType string 56 57 const ( 58 messageReceived msgType = "message-received" 59 messageDelivered msgType = "message-delivered" 60 messageFailed msgType = "message-failed" 61 blobReceived msgType = "blob-received" 62 blobDelivered msgType = "blob-delivered" 63 blobFailed msgType = "blob-failed" 64 ) 65 66 type responseWithRequestID struct { 67 RequestID string `json:"requestID"` 68 } 69 70 type sendMessage struct { 71 Message string `json:"message"` 72 Recipient string `json:"recipient"` 73 } 74 75 type transferBlob struct { 76 Path string `json:"path"` 77 Recipient string `json:"recipient"` 78 } 79 80 func (h *HTTPS) Name() string { 81 return "https" 82 } 83 84 func (h *HTTPS) Init(ctx context.Context, prefix config.Prefix, callbacks dataexchange.Callbacks) (err error) { 85 h.ctx = log.WithLogField(ctx, "dx", "https") 86 h.callbacks = callbacks 87 88 if prefix.GetString(restclient.HTTPConfigURL) == "" { 89 return i18n.NewError(ctx, i18n.MsgMissingPluginConfig, "url", "dataexchange.https") 90 } 91 92 h.client = restclient.New(h.ctx, prefix) 93 h.capabilities = &dataexchange.Capabilities{} 94 h.wsconn, err = wsclient.New(ctx, prefix, nil) 95 if err != nil { 96 return err 97 } 98 go h.eventLoop() 99 return nil 100 } 101 102 func (h *HTTPS) Start() error { 103 return h.wsconn.Connect() 104 } 105 106 func (h *HTTPS) Capabilities() *dataexchange.Capabilities { 107 return h.capabilities 108 } 109 110 func (h *HTTPS) GetEndpointInfo(ctx context.Context) (peerID string, endpoint fftypes.JSONObject, err error) { 111 res, err := h.client.R().SetContext(ctx). 112 SetResult(&endpoint). 113 Get("/api/v1/id") 114 if err != nil || !res.IsSuccess() { 115 return peerID, endpoint, restclient.WrapRestErr(ctx, res, err, i18n.MsgDXRESTErr) 116 } 117 return endpoint.GetString("id"), endpoint, nil 118 } 119 120 func (h *HTTPS) AddPeer(ctx context.Context, node *fftypes.Node) (err error) { 121 res, err := h.client.R().SetContext(ctx). 122 SetBody(node.DX.Endpoint). 123 Put(fmt.Sprintf("/api/v1/peers/%s", node.DX.Peer)) 124 if err != nil || !res.IsSuccess() { 125 return restclient.WrapRestErr(ctx, res, err, i18n.MsgDXRESTErr) 126 } 127 return nil 128 } 129 130 func (h *HTTPS) UploadBLOB(ctx context.Context, ns string, id fftypes.UUID, content io.Reader) (err error) { 131 res, err := h.client.R().SetContext(ctx). 132 SetFileReader("file", id.String(), content). 133 Put(fmt.Sprintf("/api/v1/blobs/%s/%s", ns, &id)) 134 if err != nil || !res.IsSuccess() { 135 return restclient.WrapRestErr(ctx, res, err, i18n.MsgDXRESTErr) 136 } 137 return nil 138 } 139 140 func (h *HTTPS) DownloadBLOB(ctx context.Context, ns string, id fftypes.UUID) (content io.ReadCloser, err error) { 141 res, err := h.client.R().SetContext(ctx). 142 SetDoNotParseResponse(true). 143 Get(fmt.Sprintf("/api/v1/blobs/%s/%s", ns, &id)) 144 if err != nil || !res.IsSuccess() { 145 if err == nil { 146 _ = res.RawBody().Close() 147 } 148 return nil, restclient.WrapRestErr(ctx, res, err, i18n.MsgDXRESTErr) 149 } 150 return res.RawBody(), nil 151 } 152 153 func (h *HTTPS) SendMessage(ctx context.Context, node *fftypes.Node, data []byte) (trackingID string, err error) { 154 var responseData responseWithRequestID 155 res, err := h.client.R().SetContext(ctx). 156 SetBody(&sendMessage{ 157 Message: string(data), 158 Recipient: node.DX.Peer, 159 }). 160 SetResult(&responseData). 161 Post("/api/v1/messages") 162 if err != nil || !res.IsSuccess() { 163 return "", restclient.WrapRestErr(ctx, res, err, i18n.MsgDXRESTErr) 164 } 165 return responseData.RequestID, nil 166 } 167 168 func (h *HTTPS) TransferBLOB(ctx context.Context, node *fftypes.Node, ns string, id fftypes.UUID) (trackingID string, err error) { 169 var responseData responseWithRequestID 170 res, err := h.client.R().SetContext(ctx). 171 SetBody(&transferBlob{ 172 Path: fmt.Sprintf("%s/%s", ns, id), 173 Recipient: node.DX.Peer, 174 }). 175 SetResult(&responseData). 176 Post("/api/v1/transfers") 177 if err != nil || !res.IsSuccess() { 178 return "", restclient.WrapRestErr(ctx, res, err, i18n.MsgDXRESTErr) 179 } 180 return responseData.RequestID, nil 181 } 182 183 func (h *HTTPS) extractBlobPath(ctx context.Context, path string) (ns string, id *fftypes.UUID) { 184 parts := strings.Split(path, "/") 185 if len(parts) != 2 { 186 log.L(ctx).Errorf("Invalid blob path: %s", path) 187 return "", nil 188 } 189 ns = parts[0] 190 if err := fftypes.ValidateFFNameField(ctx, ns, "namespace"); err != nil { 191 log.L(ctx).Errorf("Invalid blob namespace: %s", path) 192 return "", nil 193 } 194 id, err := fftypes.ParseUUID(ctx, parts[1]) 195 if err != nil { 196 log.L(ctx).Errorf("Invalid blob UUID: %s", path) 197 return "", nil 198 } 199 return ns, id 200 } 201 202 func (h *HTTPS) eventLoop() { 203 l := log.L(h.ctx).WithField("role", "event-loop") 204 ctx := log.WithLogger(h.ctx, l) 205 ack, _ := json.Marshal(map[string]string{"action": "commit"}) 206 for { 207 select { 208 case <-ctx.Done(): 209 l.Debugf("Event loop exiting (context cancelled)") 210 return 211 case msgBytes, ok := <-h.wsconn.Receive(): 212 if !ok { 213 l.Debugf("Event loop exiting (receive channel closed)") 214 return 215 } 216 217 l.Tracef("DX message: %s", msgBytes) 218 var msg wsEvent 219 err := json.Unmarshal(msgBytes, &msg) 220 if err != nil { 221 l.Errorf("Message cannot be parsed as JSON: %s\n%s", err, string(msgBytes)) 222 continue // Swallow this and move on 223 } 224 l.Debugf("Received %s event from DX sender=%s", msg.Type, msg.Sender) 225 switch msg.Type { 226 case messageFailed: 227 h.callbacks.TransferResult(msg.RequestID, fftypes.OpStatusFailed, msg.Error, nil) 228 case messageDelivered: 229 h.callbacks.TransferResult(msg.RequestID, fftypes.OpStatusSucceeded, "", nil) 230 case messageReceived: 231 h.callbacks.MessageReceived(msg.Sender, fftypes.Byteable(msg.Message)) 232 case blobFailed: 233 h.callbacks.TransferResult(msg.RequestID, fftypes.OpStatusFailed, msg.Error, nil) 234 case blobDelivered: 235 h.callbacks.TransferResult(msg.RequestID, fftypes.OpStatusSucceeded, "", nil) 236 case blobReceived: 237 if ns, id := h.extractBlobPath(ctx, msg.Path); id != nil { 238 h.callbacks.BLOBReceived(msg.Sender, ns, *id) 239 } 240 default: 241 l.Errorf("Message unexpected: %s", msg.Type) 242 } 243 244 // Send the ack - only fails if shutting down 245 err = h.wsconn.Send(ctx, ack) 246 if err != nil { 247 l.Errorf("Event loop exiting: %s", err) 248 return 249 } 250 } 251 } 252 }