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  }