github.com/ethersphere/bee/v2@v2.2.0/pkg/api/chunk_stream.go (about) 1 // Copyright 2021 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package api 6 7 import ( 8 "context" 9 "errors" 10 "net/http" 11 "time" 12 13 "github.com/ethersphere/bee/v2/pkg/cac" 14 "github.com/ethersphere/bee/v2/pkg/jsonhttp" 15 "github.com/ethersphere/bee/v2/pkg/log" 16 "github.com/ethersphere/bee/v2/pkg/postage" 17 storage "github.com/ethersphere/bee/v2/pkg/storage" 18 storer "github.com/ethersphere/bee/v2/pkg/storer" 19 "github.com/ethersphere/bee/v2/pkg/swarm" 20 "github.com/gorilla/websocket" 21 ) 22 23 const streamReadTimeout = 15 * time.Minute 24 25 var successWsMsg = []byte{} 26 27 func (s *Service) chunkUploadStreamHandler(w http.ResponseWriter, r *http.Request) { 28 logger := s.logger.WithName("chunks_stream").Build() 29 30 headers := struct { 31 BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` 32 SwarmTag uint64 `map:"Swarm-Tag"` 33 }{} 34 if response := s.mapStructure(r.Header, &headers); response != nil { 35 response("invalid header params", logger, w) 36 return 37 } 38 39 var ( 40 tag uint64 41 err error 42 ) 43 if headers.SwarmTag > 0 { 44 tag, err = s.getOrCreateSessionID(headers.SwarmTag) 45 if err != nil { 46 logger.Debug("get or create tag failed", "error", err) 47 logger.Error(nil, "get or create tag failed") 48 switch { 49 case errors.Is(err, storage.ErrNotFound): 50 jsonhttp.NotFound(w, "tag not found") 51 default: 52 jsonhttp.InternalServerError(w, "cannot get or create tag") 53 } 54 return 55 } 56 } 57 58 // if tag not specified use direct upload 59 putter, err := s.newStamperPutter(r.Context(), putterOptions{ 60 BatchID: headers.BatchID, 61 TagID: tag, 62 Deferred: tag != 0, 63 }) 64 if err != nil { 65 logger.Debug("get putter failed", "error", err) 66 logger.Error(nil, "get putter failed") 67 switch { 68 case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable): 69 jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist") 70 case errors.Is(err, postage.ErrNotFound): 71 jsonhttp.NotFound(w, "batch with id not found") 72 case errors.Is(err, errInvalidPostageBatch): 73 jsonhttp.BadRequest(w, "invalid batch id") 74 case errors.Is(err, errUnsupportedDevNodeOperation): 75 jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation) 76 default: 77 jsonhttp.BadRequest(w, nil) 78 } 79 return 80 } 81 82 upgrader := websocket.Upgrader{ 83 ReadBufferSize: swarm.SocMaxChunkSize, 84 WriteBufferSize: swarm.SocMaxChunkSize, 85 CheckOrigin: s.checkOrigin, 86 } 87 88 wsConn, err := upgrader.Upgrade(w, r, nil) 89 if err != nil { 90 logger.Debug("chunk upload: upgrade failed", "error", err) 91 logger.Error(nil, "chunk upload: upgrade failed") 92 jsonhttp.BadRequest(w, "upgrade failed") 93 return 94 } 95 96 s.wsWg.Add(1) 97 go s.handleUploadStream(logger, wsConn, putter) 98 } 99 100 func (s *Service) handleUploadStream( 101 logger log.Logger, 102 conn *websocket.Conn, 103 putter storer.PutterSession, 104 ) { 105 defer s.wsWg.Done() 106 107 ctx, cancel := context.WithCancel(context.Background()) 108 109 var ( 110 gone = make(chan struct{}) 111 err error 112 ) 113 defer func() { 114 cancel() 115 _ = conn.Close() 116 if err = putter.Done(swarm.ZeroAddress); err != nil { 117 logger.Error(err, "chunk upload stream: syncing chunks failed") 118 } 119 }() 120 121 conn.SetCloseHandler(func(code int, text string) error { 122 logger.Debug("chunk upload stream: client gone", "code", code, "message", text) 123 close(gone) 124 return nil 125 }) 126 127 sendMsg := func(msgType int, buf []byte) error { 128 err := conn.SetWriteDeadline(time.Now().Add(writeDeadline)) 129 if err != nil { 130 return err 131 } 132 err = conn.WriteMessage(msgType, buf) 133 if err != nil { 134 return err 135 } 136 return nil 137 } 138 139 sendErrorClose := func(code int, errmsg string) { 140 err := conn.WriteControl( 141 websocket.CloseMessage, 142 websocket.FormatCloseMessage(code, errmsg), 143 time.Now().Add(writeDeadline), 144 ) 145 if err != nil { 146 logger.Error(err, "chunk upload stream: failed sending close message") 147 } 148 } 149 150 for { 151 select { 152 case <-s.quit: 153 // shutdown 154 sendErrorClose(websocket.CloseGoingAway, "node shutting down") 155 return 156 case <-gone: 157 // client gone 158 return 159 default: 160 // if there is no indication to stop, go ahead and read the next message 161 } 162 163 err = conn.SetReadDeadline(time.Now().Add(streamReadTimeout)) 164 if err != nil { 165 logger.Debug("chunk upload stream: set read deadline failed", "error", err) 166 logger.Error(nil, "chunk upload stream: set read deadline failed") 167 return 168 } 169 170 mt, msg, err := conn.ReadMessage() 171 if err != nil { 172 if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 173 logger.Debug("chunk upload stream: read message failed", "error", err) 174 logger.Error(nil, "chunk upload stream: read message failed") 175 } 176 return 177 } 178 179 if mt != websocket.BinaryMessage { 180 logger.Debug("chunk upload stream: unexpected message received from client", "message_type", mt) 181 logger.Error(nil, "chunk upload stream: unexpected message received from client") 182 sendErrorClose(websocket.CloseUnsupportedData, "invalid message") 183 return 184 } 185 186 if len(msg) < swarm.SpanSize { 187 logger.Debug("chunk upload stream: insufficient data") 188 logger.Error(nil, "chunk upload stream: insufficient data") 189 return 190 } 191 192 chunk, err := cac.NewWithDataSpan(msg) 193 if err != nil { 194 logger.Debug("chunk upload stream: create chunk failed", "error", err) 195 logger.Error(nil, "chunk upload stream: create chunk failed") 196 return 197 } 198 199 err = putter.Put(ctx, chunk) 200 if err != nil { 201 logger.Debug("chunk upload stream: write chunk failed", "address", chunk.Address(), "error", err) 202 logger.Error(nil, "chunk upload stream: write chunk failed") 203 switch { 204 case errors.Is(err, postage.ErrBucketFull): 205 sendErrorClose(websocket.CloseInternalServerErr, "batch is overissued") 206 default: 207 sendErrorClose(websocket.CloseInternalServerErr, "chunk write error") 208 } 209 return 210 } 211 212 err = sendMsg(websocket.BinaryMessage, successWsMsg) 213 if err != nil { 214 s.logger.Debug("chunk upload stream: sending success message failed", "error", err) 215 s.logger.Error(nil, "chunk upload stream: sending success message failed") 216 return 217 } 218 } 219 }