github.com/Jeffail/benthos/v3@v3.65.0/lib/input/socket.go (about)

     1  package input
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/Jeffail/benthos/v3/internal/codec"
    14  	"github.com/Jeffail/benthos/v3/internal/docs"
    15  	"github.com/Jeffail/benthos/v3/lib/input/reader"
    16  	"github.com/Jeffail/benthos/v3/lib/log"
    17  	"github.com/Jeffail/benthos/v3/lib/message"
    18  	"github.com/Jeffail/benthos/v3/lib/metrics"
    19  	"github.com/Jeffail/benthos/v3/lib/types"
    20  )
    21  
    22  //------------------------------------------------------------------------------
    23  
    24  func init() {
    25  	Constructors[TypeSocket] = TypeSpec{
    26  		constructor: fromSimpleConstructor(NewSocket),
    27  		Summary: `
    28  Connects to a tcp or unix socket and consumes a continuous stream of messages.`,
    29  		FieldSpecs: docs.FieldSpecs{
    30  			docs.FieldCommon("network", "A network type to assume (unix|tcp).").HasOptions(
    31  				"unix", "tcp",
    32  			),
    33  			docs.FieldCommon("address", "The address to connect to.", "/tmp/benthos.sock", "127.0.0.1:6000"),
    34  			codec.ReaderDocs.AtVersion("3.42.0"),
    35  			docs.FieldDeprecated("delimiter"),
    36  			docs.FieldDeprecated("multipart"),
    37  			docs.FieldAdvanced("max_buffer", "The maximum message buffer size. Must exceed the largest message to be consumed."),
    38  		},
    39  		Categories: []Category{
    40  			CategoryNetwork,
    41  		},
    42  	}
    43  }
    44  
    45  //------------------------------------------------------------------------------
    46  
    47  // SocketConfig contains configuration values for the Socket input type.
    48  type SocketConfig struct {
    49  	Network   string `json:"network" yaml:"network"`
    50  	Address   string `json:"address" yaml:"address"`
    51  	Codec     string `json:"codec" yaml:"codec"`
    52  	MaxBuffer int    `json:"max_buffer" yaml:"max_buffer"`
    53  	// TODO: V4 remove these fields.
    54  	Multipart bool   `json:"multipart" yaml:"multipart"`
    55  	Delim     string `json:"delimiter" yaml:"delimiter"`
    56  }
    57  
    58  // NewSocketConfig creates a new SocketConfig with default values.
    59  func NewSocketConfig() SocketConfig {
    60  	return SocketConfig{
    61  		Network:   "unix",
    62  		Address:   "/tmp/benthos.sock",
    63  		Codec:     "lines",
    64  		Multipart: false,
    65  		MaxBuffer: 1000000,
    66  		Delim:     "",
    67  	}
    68  }
    69  
    70  //------------------------------------------------------------------------------
    71  
    72  // NewSocket creates a new Socket input type.
    73  func NewSocket(conf Config, mgr types.Manager, log log.Modular, stats metrics.Type) (Type, error) {
    74  	rdr, err := newSocketClient(conf.Socket, log)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	// TODO: Consider removing the async cut off here. It adds an overhead and
    79  	// we can get the same results by making sure that the async readers forward
    80  	// CloseAsync all the way through. We would need it to be configurable as it
    81  	// wouldn't be appropriate for inputs that have real acks.
    82  	return NewAsyncReader(TypeSocket, true, reader.NewAsyncCutOff(reader.NewAsyncPreserver(rdr)), log, stats)
    83  }
    84  
    85  //------------------------------------------------------------------------------
    86  
    87  type socketClient struct {
    88  	log log.Modular
    89  
    90  	conf      SocketConfig
    91  	codecCtor codec.ReaderConstructor
    92  
    93  	codecMut sync.Mutex
    94  	codec    codec.Reader
    95  }
    96  
    97  func newSocketClient(conf SocketConfig, logger log.Modular) (*socketClient, error) {
    98  	switch conf.Network {
    99  	case "tcp", "unix":
   100  	default:
   101  		return nil, fmt.Errorf("socket network '%v' is not supported by this input", conf.Network)
   102  	}
   103  
   104  	if len(conf.Delim) > 0 {
   105  		conf.Codec = "delim:" + conf.Delim
   106  	}
   107  	if conf.Multipart && !strings.HasSuffix(conf.Codec, "/multipart") {
   108  		conf.Codec += "/multipart"
   109  	}
   110  
   111  	codecConf := codec.NewReaderConfig()
   112  	codecConf.MaxScanTokenSize = conf.MaxBuffer
   113  	ctor, err := codec.GetReader(conf.Codec, codecConf)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	return &socketClient{
   119  		log:       logger,
   120  		conf:      conf,
   121  		codecCtor: ctor,
   122  	}, nil
   123  }
   124  
   125  // ConnectWithContext attempts to establish a connection to the target S3 bucket
   126  // and any relevant queues used to traverse the objects (SQS, etc).
   127  func (s *socketClient) ConnectWithContext(ctx context.Context) error {
   128  	s.codecMut.Lock()
   129  	defer s.codecMut.Unlock()
   130  
   131  	if s.codec != nil {
   132  		return nil
   133  	}
   134  
   135  	conn, err := net.Dial(s.conf.Network, s.conf.Address)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	if s.codec, err = s.codecCtor("", conn, func(ctx context.Context, err error) error {
   141  		return nil
   142  	}); err != nil {
   143  		conn.Close()
   144  		return err
   145  	}
   146  
   147  	s.log.Infof("Consuming from socket at '%v://%v'\n", s.conf.Network, s.conf.Address)
   148  	return nil
   149  }
   150  
   151  // ReadWithContext attempts to read a new message from the target S3 bucket.
   152  func (s *socketClient) ReadWithContext(ctx context.Context) (types.Message, reader.AsyncAckFn, error) {
   153  	s.codecMut.Lock()
   154  	codec := s.codec
   155  	s.codecMut.Unlock()
   156  
   157  	if codec == nil {
   158  		return nil, nil, types.ErrNotConnected
   159  	}
   160  
   161  	parts, codecAckFn, err := codec.Next(ctx)
   162  	if err != nil {
   163  		if errors.Is(err, context.Canceled) ||
   164  			errors.Is(err, context.DeadlineExceeded) {
   165  			err = types.ErrTimeout
   166  		}
   167  		if err != types.ErrTimeout {
   168  			s.codecMut.Lock()
   169  			if s.codec != nil && s.codec == codec {
   170  				s.codec.Close(ctx)
   171  				s.codec = nil
   172  			}
   173  			s.codecMut.Unlock()
   174  		}
   175  		if errors.Is(err, io.EOF) {
   176  			return nil, nil, types.ErrTimeout
   177  		}
   178  		return nil, nil, err
   179  	}
   180  
   181  	// We simply bounce rejected messages in a loop downstream so there's no
   182  	// benefit to aggregating acks.
   183  	_ = codecAckFn(context.Background(), nil)
   184  
   185  	msg := message.New(nil)
   186  	msg.Append(parts...)
   187  
   188  	if msg.Len() == 0 {
   189  		return nil, nil, types.ErrTimeout
   190  	}
   191  
   192  	return msg, func(rctx context.Context, res types.Response) error {
   193  		return nil
   194  	}, nil
   195  }
   196  
   197  // CloseAsync begins cleaning up resources used by this reader asynchronously.
   198  func (s *socketClient) CloseAsync() {
   199  	s.codecMut.Lock()
   200  	if s.codec != nil {
   201  		s.codec.Close(context.Background())
   202  		s.codec = nil
   203  	}
   204  	s.codecMut.Unlock()
   205  }
   206  
   207  // WaitForClose will block until either the reader is closed or a specified
   208  // timeout occurs.
   209  func (s *socketClient) WaitForClose(time.Duration) error {
   210  	return nil
   211  }