Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compressor): added gzip/deflate compression support on request #12

Merged
merged 5 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Xun [ʃʊn] (pronounced 'shoon'), derived from the Chinese character 迅, signif
[![Go Reference](https://pkg.go.dev/badge/github.com/yaitoo/xun.svg)](https://pkg.go.dev/github.com/yaitoo/xun)
[![Codecov](https://codecov.io/gh/yaitoo/xun/branch/main/graph/badge.svg)](https://codecov.io/gh/yaitoo/xun)
[![GitHub Release](https://img.shields.io/github/v/release/yaitoo/xun)](https://github.com/yaitoo/xun/blob/main/CHANGELOG.md)
[![Go Report Card](https://goreportcard.com/badge/yaitoo/xun)](http://goreportcard.com/report/yaitoo/xun)
[![Go Report Card](https://goreportcard.com/badge/github.com/yaitoo/xun)](https://goreportcard.com/report/github.com/yaitoo/xun)

## Features
- Works with Go's built-in `net/http.ServeMux` router. that was introduced in 1.22. [Routing Enhancements for Go 1.22](https://go.dev/blog/routing-enhancements).
cnlangzi marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
54 changes: 39 additions & 15 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
watch bool
watcher *fsnotify.Watcher
interceptor Interceptor
compressors []Compressor
}

// New allocates an App instance and loads all view engines.
Expand All @@ -60,6 +61,13 @@
app.mux = http.DefaultServeMux
}

if app.compressors == nil {
app.compressors = []Compressor{
&GzipCompressor{},
&DeflateCompressor{},
}
}

if app.viewEngines == nil {
app.viewEngines = []ViewEngine{
&StaticViewEngine{},
Expand Down Expand Up @@ -249,12 +257,14 @@
app.routes[pattern] = r

app.mux.HandleFunc(pattern, func(w http.ResponseWriter, req *http.Request) {
rw := app.createWriter(req, w)
defer rw.Close()

ctx := &Context{
req: req,
rw: w,
Routing: *r,
app: app,
interceptor: app.interceptor,
req: req,
rw: rw,
Routing: *r,
app: app,
}

err := r.Next(ctx)
Expand Down Expand Up @@ -319,12 +329,14 @@
app.routes[pattern] = r

app.mux.HandleFunc(pattern, func(w http.ResponseWriter, req *http.Request) {
rw := app.createWriter(req, w)
defer rw.Close()

ctx := &Context{
req: req,
rw: w,
Routing: *r,
app: app,
interceptor: app.interceptor,
req: req,
rw: rw,
Routing: *r,
app: app,
}

err := r.Next(ctx)
Expand All @@ -342,6 +354,16 @@

}

func (app *App) createWriter(req *http.Request, w http.ResponseWriter) ResponseWriter {
acceptEncoding := req.Header.Get("Accept-Encoding")
for _, compressor := range app.compressors {
if strings.Contains(acceptEncoding, compressor.AcceptEncoding()) {
return compressor.New(w)
}
}
return &responseWriter{ResponseWriter: w}

Check warning on line 364 in app.go

View check run for this annotation

Codecov / codecov/patch

app.go#L364

Added line #L364 was not covered by tests
}

// HandleFile registers a route handler for serving a file.
//
// This function associates a FileViewer with a given file name
Expand Down Expand Up @@ -379,12 +401,14 @@
r.Viewers[v.MimeType()] = v

app.mux.HandleFunc(pat, func(w http.ResponseWriter, req *http.Request) {
rw := app.createWriter(req, w)
defer rw.Close()

ctx := &Context{
req: req,
rw: w,
Routing: *r,
app: app,
interceptor: app.interceptor,
req: req,
rw: rw,
Routing: *r,
app: app,
}

err := r.Next(ctx)
Expand Down
15 changes: 15 additions & 0 deletions compressor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package xun

import "net/http"

// Compressor is an interface that defines methods for handling HTTP response compression.
// Implementations of this interface should provide the following methods:
//
// AcceptEncoding returns the encoding type that the compressor supports.
//
// WriteTo takes an http.ResponseWriter and returns a wrapped http.ResponseWriter
// that compresses the response data.
type Compressor interface {
AcceptEncoding() string
New(rw http.ResponseWriter) ResponseWriter
}
23 changes: 23 additions & 0 deletions compressor_deflate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package xun

import (
"compress/flate"
"net/http"
)

type DeflateCompressor struct {
}

func (c *DeflateCompressor) AcceptEncoding() string {
return "deflate"

Check warning on line 12 in compressor_deflate.go

View check run for this annotation

Codecov / codecov/patch

compressor_deflate.go#L11-L12

Added lines #L11 - L12 were not covered by tests
}

func (c *DeflateCompressor) New(rw http.ResponseWriter) ResponseWriter {
cnlangzi marked this conversation as resolved.
Show resolved Hide resolved
rw.Header().Set("Content-Encoding", "deflate")
w, _ := flate.NewWriter(rw, flate.DefaultCompression)
return &deflateResponseWriter{
w: w,
ResponseWriter: rw,
}

Check warning on line 22 in compressor_deflate.go

View check run for this annotation

Codecov / codecov/patch

compressor_deflate.go#L15-L22

Added lines #L15 - L22 were not covered by tests
}
23 changes: 23 additions & 0 deletions compressor_gzip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package xun

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

type GzipCompressor struct {
}

func (c *GzipCompressor) AcceptEncoding() string {
return "gzip"
}

func (c *GzipCompressor) New(rw http.ResponseWriter) ResponseWriter {
rw.Header().Set("Content-Encoding", "gzip")

return &gzipResponseWriter{
w: gzip.NewWriter(rw),
ResponseWriter: rw,
}

}
9 changes: 4 additions & 5 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

writtenStatus bool
values map[string]any
interceptor Interceptor
}

// Writer returns the http.ResponseWriter associated with the current context.
Expand Down Expand Up @@ -112,8 +111,8 @@
// It uses the given status code. If the status code is not provided,
// it uses http.StatusFound (302).
func (c *Context) Redirect(url string, statusCode ...int) {
if c.interceptor != nil {
if c.interceptor.Redirect(c, url, statusCode...) {
if c.app.interceptor != nil {
if c.app.interceptor.Redirect(c, url, statusCode...) {

Check warning on line 115 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L115

Added line #L115 was not covered by tests
return
}

Expand Down Expand Up @@ -168,8 +167,8 @@
// RequestReferer returns the referer of the request.
func (c *Context) RequestReferer() *url.URL {
var v string
if c.interceptor != nil {
v = c.interceptor.RequestReferer(c)
if c.app.interceptor != nil {
v = c.app.interceptor.RequestReferer(c)

Check warning on line 171 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L170-L171

Added lines #L170 - L171 were not covered by tests
}

if v == "" {
Expand Down
26 changes: 26 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,34 @@
}
}

// WithInterceptor returns an Option that sets the provided Interceptor
// to the App. This allows customization of the App's behavior by
// intercepting and potentially modifying requests or responses.
//
// Parameters:
// - i: An Interceptor instance to be set in the App.
//
// Returns:
// - Option: A function that takes an App pointer and sets its interceptor
// to the provided Interceptor.
func WithInterceptor(i Interceptor) Option {
return func(app *App) {
app.interceptor = i
}
}

// WithCompressor is an option function that sets the compressors for the application.
// It takes a variadic parameter of Compressor type and assigns it to the app's compressors field.
//
// Parameters:
//
// c ...Compressor - A variadic list of Compressor instances to be used by the application.
//
// Returns:
//
// Option - A function that takes an App pointer and sets its compressors field.
func WithCompressor(c ...Compressor) Option {
return func(app *App) {
app.compressors = c
}

Check warning on line 86 in option.go

View check run for this annotation

Codecov / codecov/patch

option.go#L83-L86

Added lines #L83 - L86 were not covered by tests
}
19 changes: 19 additions & 0 deletions response_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package xun

import (
"net/http"
)

type ResponseWriter interface {
http.ResponseWriter

Close()
}

type responseWriter struct {
http.ResponseWriter
}

func (w *responseWriter) Close() {

Check warning on line 18 in response_writer.go

View check run for this annotation

Codecov / codecov/patch

response_writer.go#L17-L18

Added lines #L17 - L18 were not covered by tests
}
19 changes: 19 additions & 0 deletions response_writer_deflate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package xun

import (
"compress/flate"
"net/http"
)

type deflateResponseWriter struct {
w *flate.Writer
http.ResponseWriter
}

func (w *deflateResponseWriter) Write(p []byte) (int, error) {
return w.w.Write(p)

Check warning on line 14 in response_writer_deflate.go

View check run for this annotation

Codecov / codecov/patch

response_writer_deflate.go#L13-L14

Added lines #L13 - L14 were not covered by tests
}

func (w *deflateResponseWriter) Close() {
w.w.Close()

Check warning on line 18 in response_writer_deflate.go

View check run for this annotation

Codecov / codecov/patch

response_writer_deflate.go#L17-L18

Added lines #L17 - L18 were not covered by tests
}
19 changes: 19 additions & 0 deletions response_writer_gzip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package xun

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

type gzipResponseWriter struct {
w *gzip.Writer
http.ResponseWriter
}

func (w *gzipResponseWriter) Write(p []byte) (int, error) {
return w.w.Write(p)
}

func (w *gzipResponseWriter) Close() {
w.w.Close()
}
Loading