github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/tcpserver/tcp_server.go (about)

     1  // Copyright 2021 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package tcpserver
    15  
    16  import (
    17  	"context"
    18  	"crypto/tls"
    19  	"net"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/pingcap/errors"
    24  	"github.com/pingcap/log"
    25  	cerror "github.com/pingcap/tiflow/pkg/errors"
    26  	"github.com/pingcap/tiflow/pkg/security"
    27  	"github.com/soheilhy/cmux"
    28  	"go.uber.org/atomic"
    29  	"go.uber.org/zap"
    30  	"golang.org/x/sync/errgroup"
    31  )
    32  
    33  var cmuxReadTimeout = 10 * time.Second
    34  
    35  // TCPServer provides a muxed socket that can
    36  // serve both plain HTTP and gRPC at the same time.
    37  type TCPServer interface {
    38  	// Run runs the TCPServer.
    39  	// For a given instance of TCPServer, Run is expected
    40  	// to be called only once.
    41  	Run(ctx context.Context) error
    42  	// GrpcListener returns the gRPC listener that
    43  	// can be listened on by a gRPC server.
    44  	GrpcListener() net.Listener
    45  	// HTTP1Listener returns a plain HTTP listener.
    46  	HTTP1Listener() net.Listener
    47  	// IsTLSEnabled returns whether TLS has been enabled.
    48  	IsTLSEnabled() bool
    49  	// Close closed the TCPServer.
    50  	// The listeners returned by GrpcListener and HTTP1Listener
    51  	// will be closed, which will force the consumers of these
    52  	// listeners to stop. This provides a reliable mechanism to
    53  	// cancel all related components.
    54  	Close() error
    55  }
    56  
    57  type tcpServerImpl struct {
    58  	mux cmux.CMux
    59  
    60  	rootListener net.Listener
    61  	// grpc lisener, service as p2p gRPC server.
    62  	grpcListener net.Listener
    63  	// used for HTTP server, service for restful open API.
    64  	http1Listener net.Listener
    65  
    66  	isClosed atomic.Bool
    67  
    68  	credentials  *security.Credential
    69  	isTLSEnabled bool // read only
    70  }
    71  
    72  // NewTCPServer creates a new TCPServer
    73  func NewTCPServer(address string, credentials *security.Credential) (TCPServer, error) {
    74  	lis, err := net.Listen("tcp", address)
    75  	if err != nil {
    76  		return nil, errors.Trace(err)
    77  	}
    78  
    79  	server := &tcpServerImpl{
    80  		credentials: credentials,
    81  	}
    82  
    83  	if credentials.IsTLSEnabled() {
    84  		tlsLis, err := wrapTLSListener(lis, credentials)
    85  		if err != nil {
    86  			return nil, errors.Trace(err)
    87  		}
    88  		server.rootListener = tlsLis
    89  		server.isTLSEnabled = true
    90  	} else {
    91  		server.rootListener = lis
    92  	}
    93  
    94  	server.mux = cmux.New(server.rootListener)
    95  	// We must set a read timeout for cmux, otherwise irresponsive clients
    96  	// may block the server from exiting.
    97  	// ref: https://github.com/pingcap/tidb-binlog/pull/352
    98  	server.mux.SetReadTimeout(cmuxReadTimeout)
    99  
   100  	server.grpcListener = server.mux.MatchWithWriters(
   101  		cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
   102  	server.http1Listener = server.mux.Match(cmux.HTTP1Fast(), cmux.HTTP2())
   103  
   104  	return server, nil
   105  }
   106  
   107  // Run runs the mux. The mux has to be running to accept connections.
   108  func (s *tcpServerImpl) Run(ctx context.Context) error {
   109  	if s.isClosed.Load() {
   110  		return cerror.ErrTCPServerClosed.GenWithStackByArgs()
   111  	}
   112  
   113  	defer func() {
   114  		s.isClosed.Store(true)
   115  		// Closing the rootListener provides a reliable way
   116  		// for telling downstream components to exit.
   117  		_ = s.rootListener.Close()
   118  	}()
   119  	errg, ctx := errgroup.WithContext(ctx)
   120  
   121  	errg.Go(func() error {
   122  		err := s.mux.Serve()
   123  		if err == cmux.ErrServerClosed {
   124  			return cerror.ErrTCPServerClosed.GenWithStackByArgs()
   125  		}
   126  		if err != nil && strings.Contains(err.Error(), "use of closed network connection") {
   127  			return cerror.ErrTCPServerClosed.GenWithStackByArgs()
   128  		}
   129  		return errors.Trace(err)
   130  	})
   131  
   132  	errg.Go(func() error {
   133  		<-ctx.Done()
   134  		log.Debug("cmux has been canceled", zap.Error(ctx.Err()))
   135  		s.mux.Close()
   136  		return nil
   137  	})
   138  
   139  	return errg.Wait()
   140  }
   141  
   142  func (s *tcpServerImpl) GrpcListener() net.Listener {
   143  	return s.grpcListener
   144  }
   145  
   146  func (s *tcpServerImpl) HTTP1Listener() net.Listener {
   147  	return s.http1Listener
   148  }
   149  
   150  func (s *tcpServerImpl) IsTLSEnabled() bool {
   151  	return s.isTLSEnabled
   152  }
   153  
   154  func (s *tcpServerImpl) Close() error {
   155  	if s.isClosed.Swap(true) {
   156  		// ignore double closing
   157  		return nil
   158  	}
   159  	// Closing the rootListener provides a reliable way
   160  	// for telling downstream components to exit.
   161  	return errors.Trace(s.rootListener.Close())
   162  }
   163  
   164  // wrapTLSListener takes a plain Listener and security credentials,
   165  // and returns a listener that handles TLS connections.
   166  func wrapTLSListener(inner net.Listener, credentials *security.Credential) (net.Listener, error) {
   167  	config, err := credentials.ToTLSConfigWithVerify()
   168  	if err != nil {
   169  		return nil, errors.Trace(err)
   170  	}
   171  	// This is a hack to make `ToTLSConfigWithVerify` work with cmux,
   172  	// since cmux does not support ALPN.
   173  	config.NextProtos = nil
   174  
   175  	return tls.NewListener(inner, config), nil
   176  }