github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/quic/gquic-go/h2quic/response_writer.go (about) 1 package h2quic 2 3 import ( 4 "bytes" 5 "net/http" 6 "strconv" 7 "strings" 8 "sync" 9 10 quic "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/quic/gquic-go" 11 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/quic/gquic-go/internal/protocol" 12 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/quic/gquic-go/internal/utils" 13 "golang.org/x/net/http2" 14 "golang.org/x/net/http2/hpack" 15 ) 16 17 type responseWriter struct { 18 dataStreamID protocol.StreamID 19 dataStream quic.Stream 20 21 headerStream quic.Stream 22 headerStreamMutex *sync.Mutex 23 24 header http.Header 25 status int // status code passed to WriteHeader 26 headerWritten bool 27 28 logger utils.Logger 29 } 30 31 func newResponseWriter( 32 headerStream quic.Stream, 33 headerStreamMutex *sync.Mutex, 34 dataStream quic.Stream, 35 dataStreamID protocol.StreamID, 36 logger utils.Logger, 37 ) *responseWriter { 38 return &responseWriter{ 39 header: http.Header{}, 40 headerStream: headerStream, 41 headerStreamMutex: headerStreamMutex, 42 dataStream: dataStream, 43 dataStreamID: dataStreamID, 44 logger: logger, 45 } 46 } 47 48 func (w *responseWriter) Header() http.Header { 49 return w.header 50 } 51 52 func (w *responseWriter) WriteHeader(status int) { 53 if w.headerWritten { 54 return 55 } 56 w.headerWritten = true 57 w.status = status 58 59 var headers bytes.Buffer 60 enc := hpack.NewEncoder(&headers) 61 enc.WriteField(hpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)}) 62 63 for k, v := range w.header { 64 for index := range v { 65 enc.WriteField(hpack.HeaderField{Name: strings.ToLower(k), Value: v[index]}) 66 } 67 } 68 69 w.logger.Infof("Responding with %d", status) 70 w.headerStreamMutex.Lock() 71 defer w.headerStreamMutex.Unlock() 72 h2framer := http2.NewFramer(w.headerStream, nil) 73 err := h2framer.WriteHeaders(http2.HeadersFrameParam{ 74 StreamID: uint32(w.dataStreamID), 75 EndHeaders: true, 76 BlockFragment: headers.Bytes(), 77 }) 78 if err != nil { 79 w.logger.Errorf("could not write h2 header: %s", err.Error()) 80 } 81 } 82 83 func (w *responseWriter) Write(p []byte) (int, error) { 84 if !w.headerWritten { 85 w.WriteHeader(200) 86 } 87 if !bodyAllowedForStatus(w.status) { 88 return 0, http.ErrBodyNotAllowed 89 } 90 return w.dataStream.Write(p) 91 } 92 93 func (w *responseWriter) Flush() {} 94 95 // This is a NOP. Use http.Request.Context 96 func (w *responseWriter) CloseNotify() <-chan bool { return make(<-chan bool) } 97 98 // test that we implement http.Flusher 99 var _ http.Flusher = &responseWriter{} 100 101 // copied from http2/http2.go 102 // bodyAllowedForStatus reports whether a given response status code 103 // permits a body. See RFC 2616, section 4.4. 104 func bodyAllowedForStatus(status int) bool { 105 switch { 106 case status >= 100 && status <= 199: 107 return false 108 case status == 204: 109 return false 110 case status == 304: 111 return false 112 } 113 return true 114 }