Добавлление gzip

Теперь ответы сжимаются , что увеличивает скорость работы.
This commit is contained in:
2026-02-09 01:14:40 +07:00
parent cff7ef1bd1
commit cb19d0b132
15 changed files with 227 additions and 7 deletions

View File

@@ -0,0 +1,109 @@
package webserver
import (
"compress/gzip"
"io"
"net/http"
"strings"
"sync"
)
var compressibleTypes = map[string]bool{
"text/html": true,
"text/css": true,
"text/javascript": true,
"text/plain": true,
"text/xml": true,
"text/csv": true,
"application/javascript": true,
"application/json": true,
"application/xml": true,
"application/wasm": true,
"application/xhtml+xml": true,
"application/rss+xml": true,
"application/atom+xml": true,
"application/manifest+json": true,
"image/svg+xml": true,
}
var gzipWriterPool = sync.Pool{
New: func() interface{} {
w, _ := gzip.NewWriterLevel(io.Discard, gzip.DefaultCompression)
return w
},
}
type gzipResponseWriter struct {
http.ResponseWriter
gzWriter *gzip.Writer
headerWritten bool
compressed bool
}
func (g *gzipResponseWriter) Write(data []byte) (int, error) {
if !g.headerWritten {
g.detectAndSetHeaders()
}
if g.compressed {
return g.gzWriter.Write(data)
}
return g.ResponseWriter.Write(data)
}
func (g *gzipResponseWriter) WriteHeader(statusCode int) {
if !g.headerWritten {
g.detectAndSetHeaders()
}
g.ResponseWriter.WriteHeader(statusCode)
}
func (g *gzipResponseWriter) detectAndSetHeaders() {
g.headerWritten = true
contentType := g.ResponseWriter.Header().Get("Content-Type")
if contentType == "" {
return
}
mimeType := strings.SplitN(contentType, ";", 2)[0]
mimeType = strings.TrimSpace(mimeType)
if compressibleTypes[mimeType] {
g.ResponseWriter.Header().Set("Content-Encoding", "gzip")
g.ResponseWriter.Header().Add("Vary", "Accept-Encoding")
g.ResponseWriter.Header().Del("Content-Length")
g.compressed = true
}
}
func (g *gzipResponseWriter) Flush() {
if g.compressed {
g.gzWriter.Flush()
}
if flusher, ok := g.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
}
func (g *gzipResponseWriter) close() {
if g.compressed {
g.gzWriter.Close()
gzipWriterPool.Put(g.gzWriter)
}
}
func clientAcceptsGzip(r *http.Request) bool {
return strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
}
func isAlreadyCompressed(header http.Header) bool {
return header.Get("Content-Encoding") != ""
}
func newGzipResponseWriter(w http.ResponseWriter) *gzipResponseWriter {
gz := gzipWriterPool.Get().(*gzip.Writer)
gz.Reset(w)
return &gzipResponseWriter{
ResponseWriter: w,
gzWriter: gz,
}
}

View File

@@ -170,10 +170,19 @@ func isRootFileRoutingEnabled(host string) bool {
return site.Root_file_routing
}
}
// По умолчанию роутинг выключен
return false
}
// Проверяет включено ли сжатие для сайта
func isSiteCompressionEnabled(host string) bool {
for _, site := range config.ConfigData.Site_www {
if site.Host == host {
return site.IsCompressionEnabled()
}
}
return true
}
// Проверка vAccess с обработкой ошибки
// Возвращает true если доступ разрешён, false если заблокирован
func checkVAccessAndHandle(w http.ResponseWriter, r *http.Request, filePath string, host string) bool {
@@ -246,6 +255,13 @@ func handler(w http.ResponseWriter, r *http.Request) {
}
// Сжатие ответа (gzip)
if isSiteCompressionEnabled(host) && clientAcceptsGzip(r) {
gzw := newGzipResponseWriter(w)
defer gzw.close()
w = gzw
}
// Проверяем существование директории сайта
if _, err := os.Stat("WebServer/www/" + host + "/public_www"); err != nil {
http.ServeFile(w, r, "WebServer/tools/error_page/index.html")

View File

@@ -306,26 +306,30 @@ func StartHandlerProxy(w http.ResponseWriter, r *http.Request) (valid bool) {
}
}
// Сжатие ответа (gzip) — только если бэкенд не сжал сам
var gzw *gzipResponseWriter
if proxyConfig.IsCompressionEnabled() && clientAcceptsGzip(r) && !isAlreadyCompressed(resp.Header) {
gzw = newGzipResponseWriter(w)
defer gzw.close()
w = gzw
}
// Устанавливаем статус код
w.WriteHeader(resp.StatusCode)
// Копируем тело ответа с поддержкой streaming (SSE, chunked responses)
// Используем буферизированное копирование с принудительной отправкой данных
flusher, canFlush := w.(http.Flusher)
// Буфер для чанков (32KB - оптимальный размер для баланса производительности)
buffer := make([]byte, 32*1024)
for {
n, err := resp.Body.Read(buffer)
if n > 0 {
// Записываем прочитанные данные
if _, writeErr := w.Write(buffer[:n]); writeErr != nil {
log.Printf("Ошибка записи тела ответа: %v", writeErr)
break
}
// Принудительно отправляем данные клиенту (критично для SSE)
if canFlush {
flusher.Flush()
}