istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/hbone/util.go (about)

     1  // Copyright Istio Authors
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package hbone
    16  
    17  import (
    18  	"io"
    19  	"net"
    20  	"net/http"
    21  	"sync"
    22  	"time"
    23  
    24  	istiolog "istio.io/istio/pkg/log"
    25  )
    26  
    27  // createBuffer to get a buffer. io.Copy uses 32k.
    28  // experimental use shows ~20k max read with Firefox.
    29  var bufferPoolCopy = sync.Pool{New: func() any {
    30  	return make([]byte, 0, 32*1024)
    31  }}
    32  
    33  func copyBuffered(dst io.Writer, src io.Reader, log *istiolog.Scope) {
    34  	buf1 := bufferPoolCopy.Get().([]byte)
    35  	// nolint: staticcheck
    36  	defer bufferPoolCopy.Put(buf1)
    37  	bufCap := cap(buf1)
    38  	buf := buf1[0:bufCap:bufCap]
    39  
    40  	// For netstack: src is a gonet.Conn, doesn't implement WriterTo. Dst is a net.TcpConn - and implements ReadFrom.
    41  	// CopyBuffered is the actual implementation of Copy and CopyBuffer.
    42  	// if buf is nil, one is allocated.
    43  	// Duplicated from io
    44  
    45  	// This will prevent stats from working.
    46  	// If the reader has a WriteTo method, use it to do the copy.
    47  	// Avoids an allocation and a copy.
    48  	//if wt, ok := src.(io.WriterTo); ok {
    49  	//	return wt.WriteTo(dst)
    50  	//}
    51  	// Similarly, if the writer has a ReadFrom method, use it to do the copy.
    52  	//if rt, ok := dst.(io.ReaderFrom); ok {
    53  	//	return rt.ReadFrom(src)
    54  	//}
    55  	for {
    56  		if srcc, ok := src.(net.Conn); ok {
    57  			// Best effort
    58  			_ = srcc.SetReadDeadline(time.Now().Add(15 * time.Minute))
    59  		}
    60  		nr, err := src.Read(buf)
    61  		log.Debugf("read %v/%v", nr, err)
    62  		if nr > 0 { // before dealing with the read error
    63  			nw, ew := dst.Write(buf[0:nr])
    64  			log.Debugf("write %v/%v", nw, ew)
    65  			if f, ok := dst.(http.Flusher); ok {
    66  				f.Flush()
    67  			}
    68  			if nr != nw { // Should not happen
    69  				ew = io.ErrShortWrite
    70  			}
    71  			if ew != nil {
    72  				return
    73  			}
    74  		}
    75  		if err != nil {
    76  			// read is already closed - we need to close out
    77  			_ = closeWriter(dst)
    78  			return
    79  		}
    80  	}
    81  }
    82  
    83  // CloseWriter is one of possible interfaces implemented by Out to send a FIN, without closing
    84  // the input. Some writers only do this when Close is called.
    85  type CloseWriter interface {
    86  	CloseWrite() error
    87  }
    88  
    89  func closeWriter(dst io.Writer) error {
    90  	if cw, ok := dst.(CloseWriter); ok {
    91  		return cw.CloseWrite()
    92  	}
    93  	if c, ok := dst.(io.Closer); ok {
    94  		return c.Close()
    95  	}
    96  	if rw, ok := dst.(http.ResponseWriter); ok {
    97  		// Server side HTTP stream. For client side, FIN can be sent by closing the pipe (or
    98  		// request body). For server, the FIN will be sent when the handler returns - but
    99  		// this only happen after request is completed and body has been read. If server wants
   100  		// to send FIN first - while still reading the body - we are in trouble.
   101  
   102  		// That means HTTP2 TCP servers provide no way to send a FIN from server, without
   103  		// having the request fully read.
   104  
   105  		// This works for H2 with the current library - but very tricky, if not set as trailer.
   106  		rw.Header().Set("X-Close", "0")
   107  		rw.(http.Flusher).Flush()
   108  		return nil
   109  	}
   110  	log.Infof("Server out not Closer nor CloseWriter nor ResponseWriter: %v", dst)
   111  	return nil
   112  }