This package provides some methods that go-chi/chi lacks, such as binding and rendering.
A lot of the code in this package comes from Fiber, the last synchronized version: 82facfdd6e89b005925fbd74b7ae3de5936a143f.
go get github.com/libtnb/chix/v2chix.JSONEncoder/JSONDecoder/XMLEncoder/XMLDecoderare replaced bychix.JSONMarshal/JSONUnmarshal/XMLMarshal/XMLUnmarshal, which accept any implementation matching the standardMarshal/Unmarshalsignatures (e.g. sonic, go-json).- Query/form/header binding no longer joins repeated keys with commas:
?a=1&a=2now binds to["1", "2"]instead of["1,2"], and values containing commas are no longer split unless splitting is enabled. - Repeated keys bound to a scalar (non-slice) field resolve to the last value, matching gofiber/schema and Fiber:
?role=user&role=adminbindsroleas"admin". If duplicate parameters are security-sensitive for you, validate or reject them before binding. Bind.JSON/Bind.XML/Bind.Bodyread at most 32MB of the request body by default; pass a custom size to override. A larger body fails with an*http.MaxBytesError, which you can map to HTTP 413.Render.JSON/JSONPno longer write a trailing newline.Render.Hijacknow returns(net.Conn, *bufio.ReadWriter, error)directly.Render.WithoutCookiedeletes the cookie withPath=/by default and accepts an optional path.Bind.Bodyreturnschix.ErrUnsupportedMediaTypefor unknown content types, andBind.URIreturnschix.ErrNoRouteContextinstead of panicking when the request was not routed by chi.renderer.SSEventEncodesplits multi-line payloads into onedata:field per line per the SSE spec, andDatamay be nil.
Package chix supports custom JSON/XML marshal and unmarshal functions, so you can plug in any drop-in replacement of the standard library:
import (
"github.com/bytedance/sonic"
"github.com/libtnb/chix/v2"
)
func init() {
chix.JSONMarshal = sonic.Marshal
chix.JSONUnmarshal = sonic.Unmarshal
}Chix supports binding request data directly into a struct using gofiber/schema. Here's an example:
// Field names must start with an uppercase letter
type Person struct {
Name string `json:"name" xml:"name" form:"name"`
Pass string `json:"pass" xml:"pass" form:"pass"`
}
router.Post("/", func(w http.ResponseWriter, r *http.Request) {
p := new(Person)
bind := chix.NewBind(r)
defer bind.Release()
if err := bind.Body(p); err != nil {
return err
}
log.Println(p.Name) // Output: john
log.Println(p.Pass) // Output: doe
// Additional logic...
})
// Run tests with the following curl commands:
// JSON
curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000
// XML
curl -X POST -H "Content-Type: application/xml" --data "<login><name>john</name><pass>doe</pass></login>" localhost:3000
// URL-Encoded Form
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000
// Multipart Form
curl -X POST -F name=john -F pass=doe http://localhost:3000
// Query Parameters
curl -X POST "http://localhost:3000/?name=john&pass=doe"Chix allows binding request data into a map[string]string or map[string][]string. Here's an example:
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
params := make(map[string][]string)
bind := chix.NewBind(r)
defer bind.Release()
if err := bind.Query(params); err != nil {
return err
}
log.Println(params["name"]) // Output: [john]
log.Println(params["pass"]) // Output: [doe]
log.Println(params["products"]) // Output: [shoe hat]
// Additional logic...
})
// Run tests with the following curl command:
curl "http://localhost:3000/?name=john&pass=doe&products=shoe&products=hat"- ContentType
- Status
- Header
- Cookie
- WithoutCookie
- Redirect
- RedirectPermanent
- PlainText
- Data
- HTML
- JSON
- JSONP
- XML
- NoContent
- Stream
- EventStream
- SSEvent
- File
- Download
- Flush
- Hijack
- Release
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.ContentType("application/json")
// Your code...
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.Status(http.StatusOK)
// Your code...
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.Header("X-Custom-Header", "value")
// Your code...
})Always set Path explicitly, so the cookie can be reliably removed later:
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.Cookie(&http.Cookie{
Name: "token",
Value: "your-token",
Path: "/",
})
// Your code...
})Browsers only remove a cookie when the deletion Path matches the Path it was set with (cookies set without a Path get a default path derived from the request URL, see RFC 6265):
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.WithoutCookie("token") // deletes with Path=/
render.WithoutCookie("token", "/admin") // deletes with a custom path
// Your code...
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w, r)
render.Redirect("/new-location")
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w, r)
render.RedirectPermanent("/new-location")
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.PlainText("Hello, World!")
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.Data([]byte("Hello, World!"))
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.HTML("<h1>Hello, World!</h1>")
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.JSON(chix.M{
"hello": "world",
})
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.JSONP("callback", chix.M{
"hello": "world",
})
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
type Person struct {
Name string `xml:"name"`
Age int `xml:"age"`
}
render.XML(Person{Name: "John", Age: 30})
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
defer render.Release()
render.NoContent()
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w, r)
defer render.Release()
clientDisconnected := render.Stream(func(w io.Writer) bool {
_, _ = w.Write([]byte("chunk of data\n"))
time.Sleep(100 * time.Millisecond)
return true // continue streaming
})
if clientDisconnected {
// Handle client disconnect
}
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w, r)
ch := make(chan map[string]string)
// In another goroutine
go func() {
ch <- map[string]string{"message": "hello"}
time.Sleep(time.Second)
ch <- map[string]string{"message": "world"}
close(ch)
}()
render.EventStream(ch)
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w, r)
event := renderer.SSEvent{
Event: "message",
Data: strings.NewReader("Hello, World!"),
ID: "1",
Retry: 3000,
}
render.SSEvent(event)
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w, r)
render.File("path/to/file.txt")
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w, r)
render.Download("path/to/file.txt", "download.txt")
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
// Write some data
w.Data([]byte("Some data"))
// Flush immediately to client
render.Flush()
// Write more data later...
})router.Get("/", func(w http.ResponseWriter, r *http.Request) {
render := chix.NewRender(w)
conn, bufrw, err := render.Hijack()
if err != nil {
// Hijacking not supported or failed
return
}
defer conn.Close()
// Use the hijacked connection
bufrw.WriteString("HTTP/1.1 200 OK\r\n\r\n")
bufrw.WriteString("Hello from hijacked connection")
bufrw.Flush()
})