1package restful 2 3// Copyright 2013 Ernest Micklei. All rights reserved. 4// Use of this source code is governed by a license 5// that can be found in the LICENSE file. 6 7import ( 8 "bufio" 9 "compress/gzip" 10 "compress/zlib" 11 "errors" 12 "io" 13 "net" 14 "net/http" 15 "strings" 16) 17 18// OBSOLETE : use restful.DefaultContainer.EnableContentEncoding(true) to change this setting. 19var EnableContentEncoding = false 20 21// CompressingResponseWriter is a http.ResponseWriter that can perform content encoding (gzip and zlib) 22type CompressingResponseWriter struct { 23 writer http.ResponseWriter 24 compressor io.WriteCloser 25 encoding string 26} 27 28// Header is part of http.ResponseWriter interface 29func (c *CompressingResponseWriter) Header() http.Header { 30 return c.writer.Header() 31} 32 33// WriteHeader is part of http.ResponseWriter interface 34func (c *CompressingResponseWriter) WriteHeader(status int) { 35 c.writer.WriteHeader(status) 36} 37 38// Write is part of http.ResponseWriter interface 39// It is passed through the compressor 40func (c *CompressingResponseWriter) Write(bytes []byte) (int, error) { 41 if c.isCompressorClosed() { 42 return -1, errors.New("Compressing error: tried to write data using closed compressor") 43 } 44 return c.compressor.Write(bytes) 45} 46 47// CloseNotify is part of http.CloseNotifier interface 48func (c *CompressingResponseWriter) CloseNotify() <-chan bool { 49 return c.writer.(http.CloseNotifier).CloseNotify() 50} 51 52// Close the underlying compressor 53func (c *CompressingResponseWriter) Close() error { 54 if c.isCompressorClosed() { 55 return errors.New("Compressing error: tried to close already closed compressor") 56 } 57 58 c.compressor.Close() 59 if ENCODING_GZIP == c.encoding { 60 currentCompressorProvider.ReleaseGzipWriter(c.compressor.(*gzip.Writer)) 61 } 62 if ENCODING_DEFLATE == c.encoding { 63 currentCompressorProvider.ReleaseZlibWriter(c.compressor.(*zlib.Writer)) 64 } 65 // gc hint needed? 66 c.compressor = nil 67 return nil 68} 69 70func (c *CompressingResponseWriter) isCompressorClosed() bool { 71 return nil == c.compressor 72} 73 74// Hijack implements the Hijacker interface 75// This is especially useful when combining Container.EnabledContentEncoding 76// in combination with websockets (for instance gorilla/websocket) 77func (c *CompressingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 78 hijacker, ok := c.writer.(http.Hijacker) 79 if !ok { 80 return nil, nil, errors.New("ResponseWriter doesn't support Hijacker interface") 81 } 82 return hijacker.Hijack() 83} 84 85// WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested. 86func wantsCompressedResponse(httpRequest *http.Request) (bool, string) { 87 header := httpRequest.Header.Get(HEADER_AcceptEncoding) 88 gi := strings.Index(header, ENCODING_GZIP) 89 zi := strings.Index(header, ENCODING_DEFLATE) 90 // use in order of appearance 91 if gi == -1 { 92 return zi != -1, ENCODING_DEFLATE 93 } else if zi == -1 { 94 return gi != -1, ENCODING_GZIP 95 } else { 96 if gi < zi { 97 return true, ENCODING_GZIP 98 } 99 return true, ENCODING_DEFLATE 100 } 101} 102 103// NewCompressingResponseWriter create a CompressingResponseWriter for a known encoding = {gzip,deflate} 104func NewCompressingResponseWriter(httpWriter http.ResponseWriter, encoding string) (*CompressingResponseWriter, error) { 105 httpWriter.Header().Set(HEADER_ContentEncoding, encoding) 106 c := new(CompressingResponseWriter) 107 c.writer = httpWriter 108 var err error 109 if ENCODING_GZIP == encoding { 110 w := currentCompressorProvider.AcquireGzipWriter() 111 w.Reset(httpWriter) 112 c.compressor = w 113 c.encoding = ENCODING_GZIP 114 } else if ENCODING_DEFLATE == encoding { 115 w := currentCompressorProvider.AcquireZlibWriter() 116 w.Reset(httpWriter) 117 c.compressor = w 118 c.encoding = ENCODING_DEFLATE 119 } else { 120 return nil, errors.New("Unknown encoding:" + encoding) 121 } 122 return c, err 123} 124