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  }