Context: create a customized Context type.

To package the internal context and other common structs, especially
http.Request and http.ResponseWriter.

Provides helper functìons.
This commit is contained in:
Muyao CHEN 2024-09-05 22:48:57 +02:00
parent 63d5d0dc59
commit f804f175e0
2 changed files with 257 additions and 0 deletions

254
framework/context.go Normal file
View File

@ -0,0 +1,254 @@
package framework
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"strconv"
"sync"
"time"
)
// Context type is the customized context of Araneae framework
//
// It packages the internal context.Context with basic "wr" couple.
type Context struct {
ctx context.Context
request *http.Request
responseWriter http.ResponseWriter
handler ControllerHandler
hasTimeout bool
writerMux *sync.Mutex
}
// NewContext create a new context
func NewContext(w http.ResponseWriter, r *http.Request) *Context {
return &Context{
request: r,
responseWriter: w,
}
}
// {{{ Basic functions
// WriterMux returns the writer mutex
func (ctx *Context) WriterMux() *sync.Mutex {
return ctx.writerMux
}
// GetRequest returns the original request
func (ctx *Context) GetRequest() *http.Request {
return ctx.request
}
// GetResponseWriter returns the original response writer
func (ctx *Context) GetResponseWriter() http.ResponseWriter {
return ctx.responseWriter
}
// SetHasTimeout indicates that the context has timeout.
func (ctx *Context) SetHasTimeout() {
ctx.hasTimeout = true
}
// HasTimeout returns whether the context has timeout.
func (ctx *Context) HasTimeout() bool {
return ctx.hasTimeout
}
// }}}
// {{{ Implements context interface
// BaseContext return a request default Context
func (ctx *Context) BaseContext() context.Context {
return ctx.request.Context()
}
// Done calls the base function
func (ctx *Context) Done() <-chan struct{} {
return ctx.BaseContext().Done()
}
// Deadline calls the base function
func (ctx *Context) Deadline() (deadline time.Time, ok bool) {
return ctx.BaseContext().Deadline()
}
// Err calls the base function
func (ctx *Context) Err() error {
return ctx.BaseContext().Err()
}
// Value calls the base function
func (ctx *Context) Value(key any) any {
return ctx.BaseContext().Value(key)
}
// }}}
// {{{ Implements request functions
// {{{ Request URI
// QueryInt gets an int value from the query request
func (ctx *Context) QueryInt(key string, defval int) (int, error) {
params, err := ctx.QueryAll()
if err != nil {
return defval, err
}
if vals, ok := params[key]; ok {
len := len(vals)
if len > 0 {
intval, err := strconv.Atoi(vals[len-1]) // return the last elem
if err != nil {
return defval, err
}
return intval, nil
}
}
return defval, errors.New("key not found")
}
// QueryString gets a string value from the query request
func (ctx *Context) QueryString(key string, defval string) (string, error) {
params, err := ctx.QueryAll()
if err != nil {
return defval, err
}
if vals, ok := params[key]; ok {
len := len(vals)
if len > 0 {
return vals[len-1], nil // return the last elem
}
}
return defval, errors.New("key not found")
}
// QueryArray gets an array of string values from the query request
func (ctx *Context) QueryArray(key string, defval []string) ([]string, error) {
params, err := ctx.QueryAll()
if err != nil {
return defval, err
}
if vals, ok := params[key]; ok {
return vals, nil // return the last elem
}
return defval, errors.New("key not found")
}
// QueryAll returns all queries in a request URL
func (ctx *Context) QueryAll() (url.Values, error) {
if ctx.request != nil {
return map[string][]string(ctx.request.URL.Query()), nil
}
return url.Values{}, errors.New("missing request in the context")
}
// }}}
// {{{ Post form
// FormInt gets an int value from the submitted form
func (ctx *Context) FormInt(key string, defval int) (int, error) {
vals, err := ctx.FormAll()
if err != nil {
return defval, err
}
valStrs, ok := vals[key]
if !ok {
return defval, errors.New("key not found")
}
valInt, err := strconv.Atoi(valStrs[0]) // Get the first one as result
if err != nil {
return defval, err
}
return valInt, nil
}
// FormString gets a string value from the submitted form
func (ctx *Context) FormString(key string, defval string) (string, error) {
vals, err := ctx.FormAll()
if err != nil {
return defval, err
}
valStrs, ok := vals[key]
if !ok {
return defval, errors.New("key not found")
}
return valStrs[0], nil
}
// FormArray gets an array of string values from the submitted form
func (ctx *Context) FormArray(key string, defval []string) ([]string, error) {
vals, err := ctx.FormAll()
if err != nil {
return defval, err
}
valStrs, ok := vals[key]
if !ok {
return defval, errors.New("key not found")
}
return valStrs, nil
}
// FormAll gets everything from the submitted form
func (ctx *Context) FormAll() (url.Values, error) {
if ctx.request != nil {
err := ctx.request.ParseForm()
if err != nil {
return url.Values{}, err
}
return ctx.request.PostForm, err
}
return url.Values{}, errors.New("missing request in the context")
}
// }}}
// {{{ application/json
// ReadJSON binds the request JSON body to an object.
//
// A pointer of obj should be passed.
func (ctx *Context) ReadJSON(obj any) error {
if ctx.request == nil {
return errors.New("missing request in the context")
}
dec := json.NewDecoder(ctx.request.Body)
err := dec.Decode(obj)
if err != nil {
return err
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must have only a single JSON value")
}
return nil
}
// WriteJSON send back an object in JSON format with the status code
func (ctx *Context) WriteJSON(status int, obj any) error {
data, err := json.Marshal(obj)
if err != nil {
return err
}
ctx.responseWriter.Header().Set("Content-type", "application/json")
ctx.responseWriter.WriteHeader(status)
_, err = ctx.responseWriter.Write(data)
if err != nil {
return err
}
return nil
}
// }}}
// }}}

3
framework/controller.go Normal file
View File

@ -0,0 +1,3 @@
package framework
type ControllerHandler func(c *Context) error