blob: faf0fb66ea78476e08e501e3e163c474f96768e7 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hbone
import (
"io"
"net"
"net/http"
"sync"
"time"
)
import (
istiolog "istio.io/pkg/log"
)
// createBuffer to get a buffer. io.Copy uses 32k.
// experimental use shows ~20k max read with Firefox.
var bufferPoolCopy = sync.Pool{New: func() any {
return make([]byte, 0, 32*1024)
}}
func copyBuffered(dst io.Writer, src io.Reader, log *istiolog.Scope) {
buf1 := bufferPoolCopy.Get().([]byte)
// nolint: staticcheck
defer bufferPoolCopy.Put(buf1)
bufCap := cap(buf1)
buf := buf1[0:bufCap:bufCap]
// For netstack: src is a gonet.Conn, doesn't implement WriterTo. Dst is a net.TcpConn - and implements ReadFrom.
// CopyBuffered is the actual implementation of Copy and CopyBuffer.
// if buf is nil, one is allocated.
// Duplicated from io
// This will prevent stats from working.
// If the reader has a WriteTo method, use it to do the copy.
// Avoids an allocation and a copy.
//if wt, ok := src.(io.WriterTo); ok {
// return wt.WriteTo(dst)
//}
// Similarly, if the writer has a ReadFrom method, use it to do the copy.
//if rt, ok := dst.(io.ReaderFrom); ok {
// return rt.ReadFrom(src)
//}
for {
if srcc, ok := src.(net.Conn); ok {
// Best effort
_ = srcc.SetReadDeadline(time.Now().Add(15 * time.Minute))
}
nr, err := src.Read(buf)
log.Debugf("read %v/%v", nr, err)
if nr > 0 { // before dealing with the read error
nw, ew := dst.Write(buf[0:nr])
log.Debugf("write %v/%v", nw, ew)
if f, ok := dst.(http.Flusher); ok {
f.Flush()
}
if nr != nw { // Should not happen
ew = io.ErrShortWrite
}
if ew != nil {
return
}
}
if err != nil {
// read is already closed - we need to close out
_ = closeWriter(dst)
return
}
}
}
// CloseWriter is one of possible interfaces implemented by Out to send a FIN, without closing
// the input. Some writers only do this when Close is called.
type CloseWriter interface {
CloseWrite() error
}
func closeWriter(dst io.Writer) error {
if cw, ok := dst.(CloseWriter); ok {
return cw.CloseWrite()
}
if c, ok := dst.(io.Closer); ok {
return c.Close()
}
if rw, ok := dst.(http.ResponseWriter); ok {
// Server side HTTP stream. For client side, FIN can be sent by closing the pipe (or
// request body). For server, the FIN will be sent when the handler returns - but
// this only happen after request is completed and body has been read. If server wants
// to send FIN first - while still reading the body - we are in trouble.
// That means HTTP2 TCP servers provide no way to send a FIN from server, without
// having the request fully read.
// This works for H2 with the current library - but very tricky, if not set as trailer.
rw.Header().Set("X-Close", "0")
rw.(http.Flusher).Flush()
return nil
}
log.Infof("Server out not Closer nor CloseWriter nor ResponseWriter: %v", dst)
return nil
}