From f804f175e0d1730557b7177fcb86b6e0853540ab Mon Sep 17 00:00:00 2001 From: Muyao CHEN Date: Thu, 5 Sep 2024 22:48:57 +0200 Subject: [PATCH] Context: create a customized Context type. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To package the internal context and other common structs, especially http.Request and http.ResponseWriter. Provides helper functìons. --- framework/context.go | 254 ++++++++++++++++++++++++++++++++++++++++ framework/controller.go | 3 + 2 files changed, 257 insertions(+) create mode 100644 framework/context.go create mode 100644 framework/controller.go diff --git a/framework/context.go b/framework/context.go new file mode 100644 index 0000000..ccfec52 --- /dev/null +++ b/framework/context.go @@ -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 +} + +// }}} +// }}} diff --git a/framework/controller.go b/framework/controller.go new file mode 100644 index 0000000..6ed9890 --- /dev/null +++ b/framework/controller.go @@ -0,0 +1,3 @@ +package framework + +type ControllerHandler func(c *Context) error