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 }