github.com/avenga/couper@v1.12.2/server/writer/response.go (about) 1 package writer 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "net" 8 "net/http" 9 "net/textproto" 10 "strconv" 11 12 "github.com/avenga/couper/errors" 13 "github.com/avenga/couper/eval" 14 "github.com/avenga/couper/logging" 15 "github.com/hashicorp/hcl/v2" 16 ) 17 18 type writer interface { 19 http.Flusher 20 http.Hijacker 21 http.ResponseWriter 22 } 23 24 type modifier interface { 25 AddModifier(*hcl.EvalContext, ...hcl.Body) 26 } 27 28 var ( 29 _ writer = &Response{} 30 _ modifier = &Response{} 31 _ logging.RecorderInfo = &Response{} 32 33 endOfHeader = []byte("\r\n\r\n") 34 endOfLine = []byte("\r\n") 35 ) 36 37 // Response wraps the http.ResponseWriter. 38 type Response struct { 39 hijackedConn net.Conn 40 httpHeaderBuffer []byte 41 rw http.ResponseWriter 42 secureCookies string 43 statusWritten bool 44 // logging info 45 statusCode int 46 rawBytesWritten int 47 bytesWritten int 48 // modifier 49 evalCtx *hcl.EvalContext 50 modifier []hcl.Body 51 // security 52 addPrivateCC bool 53 } 54 55 // NewResponseWriter creates a new Response object. 56 func NewResponseWriter(rw http.ResponseWriter, secureCookies string) *Response { 57 return &Response{ 58 rw: rw, 59 secureCookies: secureCookies, 60 } 61 } 62 63 // Header wraps the Header method of the <http.ResponseWriter>. 64 func (r *Response) Header() http.Header { 65 return r.rw.Header() 66 } 67 68 // Write wraps the Write method of the <http.ResponseWriter>. 69 func (r *Response) Write(p []byte) (int, error) { 70 l := len(p) 71 r.rawBytesWritten += l 72 if !r.statusWritten { // buffer all until end-of-header chunk: '\r\n' 73 r.httpHeaderBuffer = append(r.httpHeaderBuffer, p...) 74 idx := bytes.Index(r.httpHeaderBuffer, endOfHeader) 75 if idx == -1 { 76 return l, nil 77 } 78 79 r.flushHeader() 80 81 bufLen := len(r.httpHeaderBuffer) 82 // More than http header related bytes? Write body. 83 if !bytes.HasSuffix(r.httpHeaderBuffer, endOfLine) && bufLen > idx+4 { 84 n, writeErr := r.rw.Write(r.httpHeaderBuffer[idx+4:]) // len(endOfHeader) -> 4 85 r.bytesWritten += n 86 return l, writeErr 87 } 88 return l, nil 89 } 90 91 n, writeErr := r.rw.Write(p) 92 r.bytesWritten += n 93 return n, writeErr 94 } 95 96 func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { 97 hijack, ok := r.rw.(http.Hijacker) 98 if !ok { 99 return nil, nil, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", r.rw) 100 } 101 102 conn, brw, err := hijack.Hijack() 103 r.hijackedConn = conn 104 if brw != nil { 105 brw.Writer.Reset(r) 106 } 107 return conn, brw, err 108 } 109 110 func (r *Response) IsHijacked() bool { 111 return r.hijackedConn != nil 112 } 113 114 // Flush implements the <http.Flusher> interface. 115 func (r *Response) Flush() { 116 if rw, ok := r.rw.(http.Flusher); ok { 117 rw.Flush() 118 } 119 } 120 121 func (r *Response) flushHeader() { 122 reader := textproto.NewReader(bufio.NewReader(bytes.NewBuffer(r.httpHeaderBuffer))) 123 headerLine, _ := reader.ReadLineBytes() 124 header, _ := reader.ReadMIMEHeader() 125 for k := range header { 126 r.rw.Header()[k] = header.Values(k) 127 } 128 r.WriteHeader(r.parseStatusCode(headerLine)) 129 } 130 131 // WriteHeader wraps the WriteHeader method of the <http.ResponseWriter>. 132 func (r *Response) WriteHeader(statusCode int) { 133 if r.statusWritten { 134 return 135 } 136 137 r.configureHeader() 138 r.applyModifier() 139 140 // !!! Execute after modifier !!! 141 if r.addPrivateCC { 142 r.Header().Add("Cache-Control", "private") 143 } 144 145 if statusCode == 0 { 146 r.rw.Header().Set(errors.HeaderErrorCode, errors.Server.Error()) 147 statusCode = errors.Server.HTTPStatus() 148 } 149 150 if r.hijackedConn != nil { 151 r1 := &http.Response{ 152 ProtoMajor: 1, 153 ProtoMinor: 1, 154 Header: r.rw.Header(), 155 StatusCode: statusCode, 156 } 157 if err := r1.Write(r.hijackedConn); err != nil { 158 panic(err) 159 } 160 } else { 161 r.rw.WriteHeader(statusCode) 162 } 163 164 r.statusWritten = true 165 r.statusCode = statusCode 166 } 167 168 func (r *Response) configureHeader() { 169 r.rw.Header().Set("Server", "couper.io") 170 171 if r.secureCookies == SecureCookiesStrip { 172 stripSecureCookies(r.rw.Header()) 173 } 174 } 175 176 func (r *Response) parseStatusCode(p []byte) int { 177 if len(p) < 12 { 178 return 0 179 } 180 code, _ := strconv.Atoi(string(p[9:12])) 181 return code 182 } 183 184 func (r *Response) StatusCode() int { 185 return r.statusCode 186 } 187 188 func (r *Response) WrittenBytes() int { 189 return r.bytesWritten 190 } 191 192 func (r *Response) AddPrivateCC() { 193 r.addPrivateCC = true 194 } 195 196 func (r *Response) AddModifier(evalCtx *hcl.EvalContext, modifier ...hcl.Body) { 197 r.evalCtx = evalCtx 198 r.modifier = append(r.modifier, modifier...) 199 } 200 201 func (r *Response) applyModifier() { 202 if r.evalCtx == nil || r.modifier == nil { 203 return 204 } 205 206 for _, body := range r.modifier { 207 _ = eval.ApplyResponseHeaderOps(r.evalCtx, body, r.Header()) 208 } 209 }