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