feat: initial implementation of go-kite

Core framework:
- Kite router with full HTTP method support (GET, POST, PUT, DELETE, HEAD, OPTIONS, CONNECT, TRACE)
- Nestable route groups with prefix and middleware inheritance
- Global, group, and route-level middleware with onion ordering
- Centralized error, not-found, and method-not-allowed handlers
- Graceful shutdown on SIGTERM/SIGINT with configurable timeout
- Configurable server read, write, and idle timeouts
- Context with typed request/response helpers, value store, cookie, form, and body binding support
- Response writer wrapper for status code tracking

Middleware:
- Logger with configurable output, format, and skip function
- Recovery with configurable panic handler
- RequestID with configurable header and generator, forwards incoming IDs
- CORS with configurable origins, methods, headers, credentials, and max age
- MaxBodySize with configurable byte limit

Docs:
- README with quickstart, routing, middleware, context API reference, and TLS guide
This commit is contained in:
2026-04-23 20:25:13 +02:00
parent 23ae03a92b
commit dd92f23e0b
18 changed files with 1126 additions and 10 deletions

165
context.go Normal file
View File

@@ -0,0 +1,165 @@
package kite
import (
"context"
"encoding/json"
"encoding/xml"
"mime/multipart"
"net"
"net/http"
"net/url"
"strings"
"github.com/julienschmidt/httprouter"
)
type contextKey string
type Context struct {
ctx context.Context
w *responseWriter
r *http.Request
p httprouter.Params
}
func (c *Context) GetRequest() *http.Request {
return c.r
}
func (c *Context) GetResponse() http.ResponseWriter {
return c.w
}
func (c *Context) GetStatusCode() int {
return c.w.statusCode
}
func (c *Context) GetContext() context.Context {
return c.ctx
}
func (c *Context) GetPathParam(key string) string {
return c.p.ByName(key)
}
func (c *Context) GetQueryParam(key string) string {
return c.r.URL.Query().Get(key)
}
func (c *Context) SetValue(key string, v any) {
c.ctx = context.WithValue(c.ctx, contextKey(key), v)
}
func (c *Context) GetValue(key string) any {
return c.ctx.Value(contextKey(key))
}
func (c *Context) SetHeader(key, v string) {
c.w.Header().Set(key, v)
}
func (c *Context) GetHeader(key string) string {
return c.r.Header.Get(key)
}
func (c *Context) SetCookie(cookie *http.Cookie) {
http.SetCookie(c.w, cookie)
}
func (c *Context) GetCookie(key string) (*http.Cookie, error) {
return c.r.Cookie(key)
}
func (c *Context) GetIP() string {
if ip := c.r.Header.Get("X-Forwarded-For"); ip != "" {
return strings.Split(ip, ",")[0]
}
if ip := c.r.Header.Get("X-Real-IP"); ip != "" {
return ip
}
ip, _, _ := net.SplitHostPort(c.r.RemoteAddr)
return ip
}
func (c *Context) GetMethod() string {
return c.r.Method
}
func (c *Context) GetPath() string {
return c.r.URL.Path
}
func (c *Context) GetForm() (url.Values, error) {
if err := c.r.ParseForm(); err != nil {
return nil, err
}
return c.r.Form, nil
}
func (c *Context) GetFormValue(key string) string {
return c.r.FormValue(key)
}
func (c *Context) GetMultipartForm(maxMemory int64) (*multipart.Form, error) {
if err := c.r.ParseMultipartForm(maxMemory); err != nil {
return nil, err
}
return c.r.MultipartForm, nil
}
func (c *Context) GetMultipartFormFile(key string) (multipart.File, *multipart.FileHeader, error) {
return c.r.FormFile(key)
}
func (c *Context) IsMethod(method string) bool {
return c.r.Method == method
}
func (c *Context) WriteBytes(statusCode int, v []byte) error {
c.w.WriteHeader(statusCode)
_, err := c.w.Write(v)
return err
}
func (c *Context) WriteString(statusCode int, v string) error {
c.w.Header().Set("Content-Type", "text/plain")
c.w.WriteHeader(statusCode)
_, err := c.w.Write([]byte(v))
return err
}
func (c *Context) WriteJSON(statusCode int, v any) error {
c.w.Header().Set("Content-Type", "application/json")
c.w.WriteHeader(statusCode)
return json.NewEncoder(c.w).Encode(v)
}
func (c *Context) WriteXML(statusCode int, v any) error {
c.w.Header().Set("Content-Type", "application/xml")
c.w.WriteHeader(statusCode)
return xml.NewEncoder(c.w).Encode(v)
}
func (c *Context) WriteNoContent() error {
c.w.WriteHeader(http.StatusNoContent)
return nil
}
func (c *Context) Redirect(statusCode int, url string) error {
http.Redirect(c.w, c.r, url, statusCode)
return nil
}
func (c *Context) BindJSON(v any) error {
defer c.r.Body.Close()
return json.NewDecoder(c.r.Body).Decode(v)
}
func (c *Context) BindXML(v any) error {
defer c.r.Body.Close()
return xml.NewDecoder(c.r.Body).Decode(v)
}