Documentation
¶
Overview ¶
Package pirca provides a lightweight HTTP middleware that enhances net/http with a rich Context, response helpers, and request utilities without replacing the standard library.
Index ¶
- func New(cfg ...*Config) func(http.Handler) http.Handler
- type Config
- type Context
- func (c *Context) Bind(obj any, binder func(data []byte, obj any) error) error
- func (c *Context) BindBodyWith(obj any, binder func(data []byte, obj any) error) error
- func (c *Context) BindJSON(obj any) error
- func (c *Context) BindJSONStrict(obj any) error
- func (c *Context) BindJSONStrictWith(obj any) error
- func (c *Context) BindJSONWith(obj any) error
- func (c *Context) BindXML(obj any) error
- func (c *Context) BindXMLWith(obj any) error
- func (c *Context) BytesWritten() int
- func (c *Context) Cookie(name string) (string, error)
- func (c *Context) Data(code int, data []byte) (err error)
- func (c *Context) Deadline() (deadline time.Time, ok bool)
- func (c *Context) DefaultFormValue(key, defaultValue string) string
- func (c *Context) DefaultQuery(key, defaultValue string) string
- func (c *Context) Delete(key string)
- func (c *Context) Done() <-chan struct{}
- func (c *Context) Err() error
- func (c *Context) File(filepath string)
- func (c *Context) FileAttachment(filepath, filename string)
- func (c *Context) FileFromFS(filepath string, fs http.FileSystem)
- func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
- func (c *Context) FormValue(key string) (val string)
- func (c *Context) Get(key string) (value any, exists bool)
- func (c *Context) GetBodyBytes() ([]byte, error)
- func (c *Context) GetBodyWith() ([]byte, error)
- func (c *Context) GetFormValue(key string) (string, bool)
- func (c *Context) GetHeader(key string) string
- func (c *Context) GetQuery(key string) (string, bool)
- func (c *Context) GetQueryArray(key string) (values []string, ok bool)
- func (c *Context) GetStatus() int
- func (c *Context) Header(key, value string)
- func (c *Context) JSON(code int, obj any) error
- func (c *Context) MultipartForm() (*multipart.Form, error)
- func (c *Context) Param(key string) string
- func (c *Context) Query(key string) (val string)
- func (c *Context) QueryArray(key string) (values []string)
- func (c *Context) Redirect(code int, location string)
- func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm ...fs.FileMode) error
- func (c *Context) Set(key string, value any)
- func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
- func (c *Context) SetCookieData(cookie *http.Cookie)
- func (c *Context) SetSameSite(samesite http.SameSite)
- func (c *Context) Status(code int)
- func (c *Context) String(code int, message string) (err error)
- func (c *Context) Value(key any) any
- func (c *Context) XML(code int, obj any) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func New ¶
New returns an HTTP middleware that initializes a pirca Context for each incoming request and makes it available via Ctx. It must wrap the outermost handler in the middleware chain. Optionally accepts a *Config to override default settings.
Example:
mux := http.NewServeMux()
// With defaults
http.ListenAndServe(":8080", pirca.New()(mux))
// With custom config
http.ListenAndServe(":8080", pirca.New(&pirca.Config{
MaxBodySize: 1 << 20,
})(mux))
Types ¶
type Config ¶
type Config struct {
// MaxBodySize sets the maximum size of the request body in bytes.
// Requests exceeding this limit will return an error on read.
// Defaults to 0 (no limit).
MaxBodySize int64
// MaxMultipartMemory sets the maximum memory used when parsing multipart forms.
// The rest is written to temporary files on disk.
// Defaults to 32MB.
MaxMultipartMemory int64
}
Config holds the configuration for the Pirca middleware. All fields are optional — unset fields use their default values.
Defaults:
MaxBodySize: 0 (no limit) MaxMultipartMemory: 32MB
type Context ¶
type Context struct {
// Writer is the underlying http.ResponseWriter, wrapped to capture
// status code and bytes written. It can be used directly if needed.
Writer http.ResponseWriter
// Request is the incoming HTTP request. It can be used directly
// alongside Context methods interchangeably.
Request *http.Request
// contains filtered or unexported fields
}
Context holds the request and response state for a single HTTP request. It implements context.Context, so it can be passed directly to any function that accepts a context, such as database drivers, HTTP clients, or tracers.
Context is created by Wrap for each incoming request and retrieved via Ctx.
func Ctx ¶
Ctx retrieves the pirca Context from the request. It must be called within a handler wrapped by Wrap, otherwise it will panic.
Example:
mux.HandleFunc("GET /ip/{ip}", func(w http.ResponseWriter, r *http.Request) {
ctx := pirca.Ctx(r)
_ = ctx.JSON(http.StatusOK, map[string]string{"msg": "hello, world!"})
}
func (*Context) Bind ¶
Bind reads the entire request body into memory and passes the raw bytes to the provided binder function for deserialization.
Unlike BindJSON or BindXML which stream directly from the request body, Bind loads the full body into a []byte first. This makes it suitable for custom formats (TOML, YAML, MessagePack, etc.) or when you need the raw bytes before deserializing.
The body can only be read once — use BindBodyWith if the body needs to be read multiple times across middlewares and handlers.
For JSON use BindJSON. For XML use BindXML. For cacheable reads use BindBodyWith.
Example:
// Custom format
ctx.Bind(&obj, func(data []byte, obj any) error {
return toml.Unmarshal(data, obj)
})
// With validation before deserializing
ctx.Bind(&obj, func(data []byte, obj any) error {
if !json.Valid(data) {
return errors.New("invalid json")
}
return json.Unmarshal(data, obj)
})
func (*Context) BindBodyWith ¶
BindBodyWith reads the request body once and caches it in the Context store, allowing subsequent calls to reuse the same body bytes across middlewares and handlers without hitting EOF.
bind is a function that deserializes the body bytes into obj. Use BindJSONWith or BindXMLWith for the most common cases.
Example:
ctx.BindBodyWith(&obj, func(data []byte, obj any) error {
return json.Unmarshal(data, obj)
})
func (*Context) BindJSON ¶
BindJSON decodes the request body as JSON into obj. Returns an error if the body is nil, the JSON is malformed, or the types are incompatible with obj.
The body can only be read once. Use BindJSONWith if the body needs to be read multiple times across middlewares and handlers.
Example:
var payload MyStruct
if err := ctx.BindJSON(&payload); err != nil {
_ = ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
func (*Context) BindJSONStrict ¶
BindJSONStrict is like BindJSON but returns an error if the request body contains fields that are not present in obj. Useful for strict API validation where unknown fields should be rejected.
Example:
var payload MyStruct
if err := ctx.BindJSONStrict(&payload); err != nil {
_ = ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
func (*Context) BindJSONStrictWith ¶
BindJSONStrictWith is like BindJSONWith but returns an error if the request body contains fields not present in obj. The body is cached for reuse.
Example:
var payload MyStruct
if err := ctx.BindJSONStrictWith(&payload); err != nil {
_ = ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
func (*Context) BindJSONWith ¶
BindJSONWith decodes the request body as JSON into obj, caching the body so it can be read multiple times. See BindBodyWith for details.
Example:
var payload MyStruct
if err := ctx.BindJSONWith(&payload); err != nil {
_ = ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
func (*Context) BindXML ¶
BindXML decodes the request body as XML into obj. Returns an error if the body is nil, the XML is malformed, or the types are incompatible with obj.
The body can only be read once. Use BindXMLWith if the body needs to be read multiple times across middlewares and handlers.
Example:
var payload MyStruct
if err := ctx.BindXML(&payload); err != nil {
_ = ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
func (*Context) BindXMLWith ¶
BindXMLWith decodes the request body as XML into obj, caching the body so it can be read multiple times. See BindBodyWith for details.
Example:
var payload MyStruct
if err := ctx.BindXMLWith(&payload); err != nil {
_ = ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
func (*Context) BytesWritten ¶
BytesWritten returns the total number of bytes written to the response body.
func (*Context) Cookie ¶
Cookie returns the value of the named cookie from the request. The value is automatically URL-decoded to reverse the encoding applied by SetCookie. Returns http.ErrNoCookie if the cookie is not present.
func (*Context) Data ¶
Data writes raw bytes to the response with the given status code. Set the Content-Type header beforehand with Header if needed.
Example:
ctx.Header("Content-Type", "application/pdf")
ctx.Data(http.StatusOK, pdfBytes)
func (*Context) Deadline ¶
Deadline implements context.Context. It delegates to the underlying request context, which is canceled when the client disconnects.
func (*Context) DefaultFormValue ¶
DefaultFormValue returns the value of a form field from the request body, or defaultValue if the key is not present. If the key exists but is empty, returns empty string — not defaultValue. For query params use DefaultQuery instead.
func (*Context) DefaultQuery ¶
DefaultQuery returns the value of the URL query param with the given key, or defaultValue if not present.
Example:
// GET /search?q=golang
page := ctx.DefaultQuery("page", "1") // "1" — not in URL, uses default
func (*Context) Done ¶
func (c *Context) Done() <-chan struct{}
Done implements context.Context. The returned channel is closed when the request context is canceled — typically when the client disconnects.
func (*Context) Err ¶
Err implements context.Context. It returns the error from the underlying request context, or nil if the context has not been canceled.
func (*Context) File ¶
File serves the file at the given filepath using http.ServeFile. It handles Range requests, ETags, and Last-Modified headers automatically.
func (*Context) FileAttachment ¶
FileAttachment serves the file at filepath as a downloadable attachment. The filename parameter sets the suggested filename in the browser's save dialog.
ASCII filenames are quoted and escaped per RFC 2183. Non-ASCII filenames are encoded using RFC 5987 (UTF-8 with URL encoding) to support characters such as accents, ñ, or CJK characters.
Example:
ctx.FileAttachment("./files/report.pdf", "reporte_2026.pdf")
ctx.FileAttachment("./files/report.pdf", "reporte_año_2026.pdf")
func (*Context) FileFromFS ¶
func (c *Context) FileFromFS(filepath string, fs http.FileSystem)
FileFromFS serves a file from the given http.FileSystem at the given filepath. Unlike File, it allows serving from any FileSystem implementation, including embedded files via embed.FS.
Example:
//go:embed static
var staticFiles embed.FS
ctx.FileFromFS("static/style.css", http.FS(staticFiles))
func (*Context) FormFile ¶
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
FormFile returns the first file uploaded with the given form key. Parses the multipart form if not already parsed, using MaxMultipartMemory from Config.
Returns the file metadata only — the internal file handle is closed automatically. Use SaveUploadedFile to save the file to disk, or call file.Open() to read its contents directly.
Note: if reading file contents manually via file.Open(), always close the returned reader after use.
Example:
file, err := ctx.FormFile("avatar")
if err != nil {
_ = ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
ctx.SaveUploadedFile(file, "./uploads/"+file.Filename)
func (*Context) FormValue ¶
FormValue returns the value of a form field from the request body. Works with application/x-www-form-urlencoded and multipart/form-data. For query params use Query instead.
func (*Context) Get ¶
Get retrieves a value previously stored with Set. Returns the value and true if the key exists, or nil and false otherwise. It is safe for concurrent use.
Example:
if userID, ok := ctx.Get("userID"); ok {
fmt.Println(userID)
}
func (*Context) GetBodyBytes ¶
GetBodyBytes reads and returns the raw request body as bytes. The body can only be read once — subsequent calls will return empty bytes. For cacheable reads use BindBodyWith instead.
Returns an error if the body is nil.
Example:
body, err := ctx.GetBodyBytes()
if err != nil {
_ = ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
func (*Context) GetBodyWith ¶ added in v1.2.0
GetBodyWith reads the request body and caches it, returning the raw bytes. Subsequent calls return the cached bytes without reading the body again.
It is a convenience wrapper around BindBodyWith for cases where you need the raw bytes without deserializing — for example in HMAC verification or custom parsing middlewares.
Example:
// Middleware
body, err := ctx.GetBodyWith()
if err != nil {
ctx.String(http.StatusInternalServerError, "internal error")
return
}
// verify HMAC with body...
// Handler — body already cached
var payload MyStruct
ctx.BindJSONWith(&payload)
func (*Context) GetFormValue ¶
GetFormValue returns the value of a form field plus a bool indicating if the key exists. Unlike FormValue, it distinguishes between an empty value and a missing key.
func (*Context) GetQuery ¶
GetQuery returns the value of the URL query param with the given key, plus a boolean indicating whether the key exists. Unlike Query, it distinguishes between a missing key and an empty value.
Example:
// GET /?name=jesus&empty=
ctx.GetQuery("name") // ("gopher", true)
ctx.GetQuery("empty") // ("", true) — exists but empty
ctx.GetQuery("wtf") // ("", false) — does not exist
func (*Context) GetQueryArray ¶
GetQueryArray returns all values for the given query key as a slice, plus a boolean indicating whether the key exists.
Example:
// GET /search?color=red&color=blue
values, ok := ctx.GetQueryArray("color") // (["red", "blue"], true)
values, ok := ctx.GetQueryArray("wtf") // ([], false)
func (*Context) Header ¶
Header sets a response header key to value. If value is empty, the header is deleted.
func (*Context) JSON ¶
JSON serializes obj as JSON and writes it to the response with the given status code. Sets Content-Type to "application/json".
JSON uses encoding/json Marshal, which means it will fail for types that cannot be serialized such as channels, functions, or circular references. For well-defined structs and maps with basic types, it will never fail.
Example:
_ = ctx.JSON(http.StatusOK, map[string]string{"message": "ok"})
func (*Context) MultipartForm ¶
MultipartForm parses and returns the full multipart form data, including all fields and uploaded files. Uses MaxMultipartMemory from Config.
Use FormFile for single file uploads. Use MultipartForm when you need access to multiple files or all form fields at once.
Note: if reading file contents manually via file.Open(), always close the returned reader after use.
Example:
form, err := ctx.MultipartForm()
if err != nil {
_ = ctx.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
files := form.File["avatars"]
for _, f := range files {
ctx.SaveUploadedFile(f, "./uploads/"+f.Filename)
}
func (*Context) Param ¶
Param returns the value of the URL path param with the given key. Requires Go 1.22+ net/http pattern matching.
Example:
// GET /user/{id}
id := ctx.Param("id") // "123"
func (*Context) Query ¶
Query returns the value of the URL query param with the given key. Returns empty string if not present.
Example:
// GET /search?q=golang&page=1
q := ctx.Query("q") // "golang"
page := ctx.Query("page") // "1"
func (*Context) QueryArray ¶
QueryArray returns all values for the given query key as a slice. Useful when the same key appears multiple times in the URL.
Example:
// GET /search?color=red&color=blue&color=green
colors := ctx.QueryArray("color") // ["red", "blue", "green"]
func (*Context) Redirect ¶
Redirect replies to the request with a redirect to the given location. The code must be a valid HTTP redirect status code (301-308) or 201.
Panics if an invalid status code is provided, as this indicates a bug in the caller's code rather than a runtime error.
func (*Context) SaveUploadedFile ¶
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm ...fs.FileMode) error
SaveUploadedFile saves a multipart file to the given destination path. Creates any necessary parent directories automatically. The optional perm parameter sets the directory permissions, defaulting to 0o750.
The uploaded file is opened and closed internally — the caller does not need to manage the file lifecycle.
Example:
file, _ := ctx.FormFile("avatar")
if err := ctx.SaveUploadedFile(file, "./uploads/"+file.Filename); err != nil {
_ = ctx.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
func (*Context) Set ¶
Set stores a key-value pair in the Context, making it available to subsequent handlers and middlewares within the same request lifecycle. It is safe for concurrent use.
Example:
ctx.Set("userID", "123")
ctx.Set("role", "admin")
func (*Context) SetCookie ¶
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
SetCookie writes a Set-Cookie header to the response. The value is automatically URL-encoded and decoded by Cookie. If path is empty, it defaults to "/".
func (*Context) SetCookieData ¶
SetCookieData writes a Set-Cookie header using a pre-built http.Cookie. If Path is empty, it defaults to "/". If SameSite is http.SameSiteDefaultMode, it uses the value set by SetSameSite.
func (*Context) SetSameSite ¶
SetSameSite sets the SameSite attribute used for cookies set via SetCookie. Defaults to http.SameSiteDefaultMode if not called.
func (*Context) Status ¶
Status writes the HTTP status code to the response header. Must be called before writing the response body.
func (*Context) String ¶
String writes a plain string message to the response with the given status code. Unlike JSON or XML, it does not set a Content-Type header — the caller is responsible for setting it beforehand if needed.
Example:
ctx.Header("Content-Type", "text/html; charset=utf-8")
_ = ctx.String(http.StatusOK, "<h1>Hello</h1>")
func (*Context) Value ¶
Value implements context.Context. It looks up key in the following order:
- If key is a string, searches in the keys stored via Set.
- Delegates to the underlying request context.
This allows *Context to be passed directly to any function that accepts a context.Context, including database drivers, HTTP clients, and tracers.
Note: only string keys are searched in the local store. Non-string keys are delegated to the request context, where external libraries store their own values.