<commit_before>// Package compressor implements GZIP compression. This is highly based on
// https://github.com/phyber/negroni-gzip, with minor changes.
package compressor

import (
	"compress/gzip"
	"net/http"
	"strings"

	"github.com/c4milo/handlers/internal"
)

const (
	gzipEncoding    = "gzip"
	deflateEncoding = "deflate"

	acceptEncoding  = "Accept-Encoding"
	contentEncoding = "Content-Encoding"
	contentLength   = "Content-Length"
	contentType     = "Content-Type"
	vary            = "Vary"
	secWebSocketKey = "Sec-WebSocket-Key"

	BestCompression    = gzip.BestCompression
	BestSpeed          = gzip.BestSpeed
	DefaultCompression = gzip.DefaultCompression
	NoCompression      = gzip.NoCompression
)

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*handler)

// Internal handler
type handler struct {
	compressionLevel int
}

// GzipLevel allows configuring GZIP compression level.
// Options:
// * compressor.BestCompression
// * compressor.BestSpeed
// * compressor.DefaultCompression
// * compressor.NoCompression
func GzipLevel(l int) option {
	return func(h *handler) {
		h.compressionLevel = l
	}
}

// GZIP response writer wrapper
type responseWriter struct {
	internal.ResponseWriter
	gzipWriter *gzip.Writer
}

// Write writes bytes to the gzip.Writer. It will also set the Content-Type
// header using the net/http library content type detection if the Content-Type
// header was not set yet.
func (rw responseWriter) Write(b []byte) (int, error) {
	if !rw.Written() {
		// The status will be StatusOK if WriteHeader has not been called yet
		rw.WriteHeader(http.StatusOK)
	}

	if rw.Header().Get(contentType) == "" {
		rw.Header().Set(contentType, http.DetectContentType(b))
	}

	size, err := rw.gzipWriter.Write(b)
	return size, err
}

// GzipHandler applies GZIP compression to the response body, except in the following
// scenarios:
// * The response body is already compressed using gzip or deflate
// * The request's Accept-Encoding header does not announce gzip support
// * The request is upgrading to a websocket connection.
func GzipHandler(h http.Handler, opts ...option) http.Handler {
	// Default options
	handler := &handler{
		compressionLevel: gzip.DefaultCompression,
	}

	for _, opt := range opts {
		opt(handler)
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hdr := r.Header
		if !strings.Contains(hdr.Get(acceptEncoding), gzipEncoding) {
			h.ServeHTTP(w, r)
			return
		}

		// Skip compression if body comes compressed already.
		curEncoding := hdr.Get(contentEncoding)
		if curEncoding == gzipEncoding ||
			curEncoding == deflateEncoding {
			h.ServeHTTP(w, r)
			return
		}

		// This handler does not support websockets compression
		if hdr.Get(secWebSocketKey) != "" {
			h.ServeHTTP(w, r)
			return
		}

		gz, err := gzip.NewWriterLevel(w, handler.compressionLevel)
		if err != nil {
			h.ServeHTTP(w, r)
			return
		}<commit_msg>Do not compress if response body is already compressed.<commit_after>// Package compressor implements GZIP compression. This is highly based on
// https://github.com/phyber/negroni-gzip, with minor changes.
package compressor

import (
	"compress/gzip"
	"net/http"
	"strings"

	"github.com/c4milo/handlers/internal"
)

const (
	gzipEncoding    = "gzip"
	deflateEncoding = "deflate"

	acceptEncoding  = "Accept-Encoding"
	contentEncoding = "Content-Encoding"
	contentLength   = "Content-Length"
	contentType     = "Content-Type"
	vary            = "Vary"
	secWebSocketKey = "Sec-WebSocket-Key"

	BestCompression    = gzip.BestCompression
	BestSpeed          = gzip.BestSpeed
	DefaultCompression = gzip.DefaultCompression
	NoCompression      = gzip.NoCompression
)

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*handler)

// Internal handler
type handler struct {
	compressionLevel int
}

// GzipLevel allows configuring GZIP compression level.
// Options:
// * compressor.BestCompression
// * compressor.BestSpeed
// * compressor.DefaultCompression
// * compressor.NoCompression
func GzipLevel(l int) option {
	return func(h *handler) {
		h.compressionLevel = l
	}
}

// GZIP response writer wrapper
type responseWriter struct {
	internal.ResponseWriter
	gzipWriter *gzip.Writer
}

// Write writes bytes to the gzip.Writer. It will also set the Content-Type
// header using the net/http library content type detection if the Content-Type
// header was not set yet.
func (rw responseWriter) Write(b []byte) (int, error) {
	if !rw.Written() {
		// The status will be StatusOK if WriteHeader has not been called yet
		rw.WriteHeader(http.StatusOK)
	}

	if rw.Header().Get(contentType) == "" {
		rw.Header().Set(contentType, http.DetectContentType(b))
	}

	size, err := rw.gzipWriter.Write(b)
	return size, err
}

// GzipHandler applies GZIP compression to the response body, except in the following
// scenarios:
// * The response body is already compressed using gzip or deflate
// * The request's Accept-Encoding header does not announce gzip support
// * The request is upgrading to a websocket connection.
func GzipHandler(h http.Handler, opts ...option) http.Handler {
	// Default options
	handler := &handler{
		compressionLevel: gzip.DefaultCompression,
	}

	for _, opt := range opts {
		opt(handler)
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hdr := r.Header
		if !strings.Contains(hdr.Get(acceptEncoding), gzipEncoding) {
			h.ServeHTTP(w, r)
			return
		}

		// Skip compression if response body is compressed already.
		curEncoding := w.Header().Get(contentEncoding)
		if curEncoding == gzipEncoding ||
			curEncoding == deflateEncoding {
			h.ServeHTTP(w, r)
			return
		}

		// This handler does not support websockets compression
		if hdr.Get(secWebSocketKey) != "" {
			h.ServeHTTP(w, r)
			return
		}

		gz, err := gzip.NewWriterLevel(w, handler.compressionLevel)
		if err != nil {
			h.ServeHTTP(w, r)
			return
		}
{'fraction_non_alphanumeric': 0.10782840928058755, 'fraction_numerical': 0.0023368385912201635, 'mean_word_length': 3.7330173775671405, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '66257', 'n_tokens_mistral': 2000, 'n_tokens_neox': 1865, 'n_words': 523}