Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
9dcf2f8e28 | |||
083b91bfaa | |||
d2d90e6ffd | |||
79e94dfd21 | |||
7b62a82b42 | |||
7057e232e6 | |||
8f4132f131 | |||
340239fdd9 | |||
53ed37a218 | |||
286f010346 |
14
app.go
14
app.go
@ -5,6 +5,8 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@ -42,13 +44,23 @@ func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
func (a *App) run(ln net.Listener) error {
|
func (a *App) run(ln net.Listener) error {
|
||||||
srv := &http.Server{Handler: a}
|
srv := &http.Server{Handler: a}
|
||||||
|
|
||||||
|
if a.config.DisableHttpErrorLog {
|
||||||
|
srv.ErrorLog = log.New(io.Discard, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
a.server = srv
|
a.server = srv
|
||||||
|
|
||||||
if !a.config.DisableBanner {
|
if !a.config.DisableBanner {
|
||||||
fmt.Println(banner + "nf serve at: " + ln.Addr().String() + "\n")
|
fmt.Println(banner + "nf serve at: " + ln.Addr().String() + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.server.Serve(ln)
|
err := a.server.Serve(ln)
|
||||||
|
if !errors.Is(err, http.ErrServerClosed) || a.config.ErrServeClose {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Run(address string) error {
|
func (a *App) Run(address string) error {
|
||||||
|
64
ctx.go
64
ctx.go
@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
type Ctx struct {
|
type Ctx struct {
|
||||||
// origin objects
|
// origin objects
|
||||||
Writer http.ResponseWriter
|
writer http.ResponseWriter
|
||||||
Request *http.Request
|
Request *http.Request
|
||||||
// request info
|
// request info
|
||||||
path string
|
path string
|
||||||
@ -26,24 +26,25 @@ type Ctx struct {
|
|||||||
params map[string]string
|
params map[string]string
|
||||||
index int
|
index int
|
||||||
handlers []HandlerFunc
|
handlers []HandlerFunc
|
||||||
locals map[string]any
|
locals map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ctx {
|
func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ctx {
|
||||||
return &Ctx{
|
return &Ctx{
|
||||||
Writer: writer,
|
writer: writer,
|
||||||
Request: request,
|
Request: request,
|
||||||
path: request.URL.Path,
|
path: request.URL.Path,
|
||||||
Method: request.Method,
|
Method: request.Method,
|
||||||
|
StatusCode: 200,
|
||||||
|
|
||||||
app: app,
|
app: app,
|
||||||
index: -1,
|
index: -1,
|
||||||
locals: map[string]any{},
|
locals: map[string]interface{}{},
|
||||||
handlers: make([]HandlerFunc, 0),
|
handlers: make([]HandlerFunc, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ctx) Locals(key string, value ...any) any {
|
func (c *Ctx) Locals(key string, value ...interface{}) interface{} {
|
||||||
data := c.locals[key]
|
data := c.locals[key]
|
||||||
if len(value) > 0 {
|
if len(value) > 0 {
|
||||||
c.locals[key] = value[0]
|
c.locals[key] = value[0]
|
||||||
@ -61,16 +62,33 @@ func (c *Ctx) Path(overWrite ...string) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ctx) Next() error {
|
func (c *Ctx) Cookies(key string, defaultValue ...string) string {
|
||||||
c.index++
|
var (
|
||||||
s := len(c.handlers)
|
dv = ""
|
||||||
for ; c.index < s; c.index++ {
|
)
|
||||||
if err := c.handlers[c.index](c); err != nil {
|
|
||||||
return err
|
if len(defaultValue) > 0 {
|
||||||
}
|
dv = defaultValue[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
cookie, err := c.Request.Cookie(key)
|
||||||
|
if err != nil || cookie.Value == "" {
|
||||||
|
return dv
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookie.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) Next() error {
|
||||||
|
c.index++
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if c.index < len(c.handlers) {
|
||||||
|
err = c.handlers[c.index](c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===============================================================
|
/* ===============================================================
|
||||||
@ -182,16 +200,16 @@ func (c *Ctx) QueryParser(out interface{}) error {
|
|||||||
|
|
||||||
func (c *Ctx) Status(code int) *Ctx {
|
func (c *Ctx) Status(code int) *Ctx {
|
||||||
c.StatusCode = code
|
c.StatusCode = code
|
||||||
c.Writer.WriteHeader(code)
|
c.writer.WriteHeader(code)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ctx) Set(key string, value string) {
|
func (c *Ctx) Set(key string, value string) {
|
||||||
c.Writer.Header().Set(key, value)
|
c.writer.Header().Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ctx) SetHeader(key string, value string) {
|
func (c *Ctx) SetHeader(key string, value string) {
|
||||||
c.Writer.Header().Set(key, value)
|
c.writer.Header().Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ctx) SendString(data string) error {
|
func (c *Ctx) SendString(data string) error {
|
||||||
@ -202,13 +220,13 @@ func (c *Ctx) SendString(data string) error {
|
|||||||
|
|
||||||
func (c *Ctx) Writef(format string, values ...interface{}) (int, error) {
|
func (c *Ctx) Writef(format string, values ...interface{}) (int, error) {
|
||||||
c.SetHeader("Content-Type", "text/plain")
|
c.SetHeader("Content-Type", "text/plain")
|
||||||
return c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
|
return c.writer.Write([]byte(fmt.Sprintf(format, values...)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ctx) JSON(data interface{}) error {
|
func (c *Ctx) JSON(data interface{}) error {
|
||||||
c.SetHeader("Content-Type", "application/json")
|
c.SetHeader("Content-Type", MIMEApplicationJSON)
|
||||||
|
|
||||||
encoder := json.NewEncoder(c.Writer)
|
encoder := json.NewEncoder(c.writer)
|
||||||
|
|
||||||
if err := encoder.Encode(data); err != nil {
|
if err := encoder.Encode(data); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -217,12 +235,16 @@ func (c *Ctx) JSON(data interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) RawWriter() http.ResponseWriter {
|
||||||
|
return c.writer
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Ctx) Write(data []byte) (int, error) {
|
func (c *Ctx) Write(data []byte) (int, error) {
|
||||||
return c.Writer.Write(data)
|
return c.writer.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ctx) HTML(html string) error {
|
func (c *Ctx) HTML(html string) error {
|
||||||
c.SetHeader("Content-Type", "text/html")
|
c.SetHeader("Content-Type", "text/html")
|
||||||
_, err := c.Writer.Write([]byte(html))
|
_, err := c.writer.Write([]byte(html))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
21
group.go
21
group.go
@ -1,6 +1,7 @@
|
|||||||
package nf
|
package nf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@ -25,8 +26,26 @@ func (group *RouterGroup) Group(prefix string) *RouterGroup {
|
|||||||
return newGroup
|
return newGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) verifyHandlers(path string, handlers ...HandlerFunc) []HandlerFunc {
|
||||||
|
if len(handlers) == 0 {
|
||||||
|
if !group.app.config.EnableNotImplementHandler {
|
||||||
|
panic(fmt.Sprintf("missing handler in route: %s", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers = append(handlers, ToDoHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, handler := range handlers {
|
||||||
|
if handler == nil {
|
||||||
|
panic(fmt.Sprintf("nil handler found in route: %s", path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlers
|
||||||
|
}
|
||||||
|
|
||||||
func (group *RouterGroup) addRoute(method string, comp string, handlers ...HandlerFunc) {
|
func (group *RouterGroup) addRoute(method string, comp string, handlers ...HandlerFunc) {
|
||||||
verifyHandlers(comp, handlers...)
|
handlers = group.verifyHandlers(comp, handlers...)
|
||||||
pattern := group.prefix + comp
|
pattern := group.prefix + comp
|
||||||
log.Printf("Add Route %4s - %s", method, pattern)
|
log.Printf("Add Route %4s - %s", method, pattern)
|
||||||
group.app.router.addRoute(method, pattern, handlers...)
|
group.app.router.addRoute(method, pattern, handlers...)
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
package nf
|
package nf
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
type HandlerFunc func(*Ctx) error
|
type HandlerFunc func(*Ctx) error
|
||||||
|
|
||||||
|
func ToDoHandler(c *Ctx) error {
|
||||||
|
return c.Status(501).SendString(fmt.Sprintf("%s - %s Not Implemented", c.Method, c.Path()))
|
||||||
|
}
|
||||||
|
@ -2,8 +2,10 @@ package nf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRecover(enableStackTrace bool) HandlerFunc {
|
func NewRecover(enableStackTrace bool) HandlerFunc {
|
||||||
@ -21,3 +23,53 @@ func NewRecover(enableStackTrace bool) HandlerFunc {
|
|||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewLogger() HandlerFunc {
|
||||||
|
l := log.New(os.Stdout, "[NF] ", 0)
|
||||||
|
|
||||||
|
durationFormat := func(num int64) string {
|
||||||
|
var (
|
||||||
|
unit = "ns"
|
||||||
|
)
|
||||||
|
|
||||||
|
if num > 1000 {
|
||||||
|
num = num / 1000
|
||||||
|
unit = "µs"
|
||||||
|
}
|
||||||
|
|
||||||
|
if num > 1000 {
|
||||||
|
num = num / 1000
|
||||||
|
unit = "ms"
|
||||||
|
}
|
||||||
|
|
||||||
|
if num > 1000 {
|
||||||
|
num = num / 1000
|
||||||
|
unit = " s"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%v %s", num, unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(c *Ctx) error {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
err := c.Next()
|
||||||
|
|
||||||
|
var (
|
||||||
|
duration = time.Now().Sub(start).Nanoseconds()
|
||||||
|
status = c.StatusCode
|
||||||
|
path = c.path
|
||||||
|
method = c.Request.Method
|
||||||
|
)
|
||||||
|
|
||||||
|
l.Printf("%s | %5s | %d | %s | %s",
|
||||||
|
start.Format("06/01/02T15:04:05"),
|
||||||
|
method,
|
||||||
|
status,
|
||||||
|
durationFormat(duration),
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
23
nf.go
23
nf.go
@ -2,6 +2,7 @@ package nf
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
banner = " _ _ _ ___ _ \n | \\| |___| |_ | __|__ _ _ _ _ __| |\n | .` / _ \\ _| | _/ _ \\ || | ' \\/ _` |\n |_|\\_\\___/\\__| |_|\\___/\\_,_|_||_\\__,_|\n "
|
banner = " _ _ _ ___ _ \n | \\| |___| |_ | __|__ _ _ _ _ __| |\n | .` / _ \\ _| | _/ _ \\ || | ' \\/ _` |\n |_|\\_\\___/\\__| |_|\\___/\\_,_|_||_\\__,_|\n "
|
||||||
|
_404 = "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1\"><meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"><title>Not Found</title><style>body{background:#333;margin:0;color:#ccc;display:flex;align-items:center;max-height:100vh;height:100vh;justify-content:center}textarea{min-height:5rem;min-width:20rem;text-align:center;border:none;background:0 0;color:#ccc;resize:none;user-input:none;user-select:none;cursor:default;-webkit-user-select:none;-webkit-touch-callout:none;-moz-user-select:none;-ms-user-select:none;outline:0}</style></head><body><textarea id=\"banner\" readonly=\"readonly\"></textarea><script type=\"text/javascript\">let htmlCodes = [\n ' _ _ _ ___ _ ',\n '| \\\\| |___| |_ | __|__ _ _ _ _ __| |',\n '| .` / _ \\\\ _| | _/ _ \\\\ || | \\' \\\\/ _` |',\n '|_|\\\\_\\\\___/\\\\__| |_|\\\\___/\\\\_,_|_||_\\\\__,_|'\n].join('\\n');\ndocument.querySelector('#banner').value = htmlCodes</script></body></html>"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Map map[string]interface{}
|
type Map map[string]interface{}
|
||||||
@ -9,14 +10,27 @@ type Map map[string]interface{}
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
// Default: 4 * 1024 * 1024
|
// Default: 4 * 1024 * 1024
|
||||||
BodyLimit int64 `json:"-"`
|
BodyLimit int64 `json:"-"`
|
||||||
|
|
||||||
|
// if report http.ErrServerClosed as run err
|
||||||
|
ErrServeClose bool `json:"-"`
|
||||||
|
|
||||||
DisableBanner bool `json:"-"`
|
DisableBanner bool `json:"-"`
|
||||||
DisableLogger bool `json:"-"`
|
DisableLogger bool `json:"-"`
|
||||||
DisableRecover bool `json:"-"`
|
DisableRecover bool `json:"-"`
|
||||||
|
DisableHttpErrorLog bool `json:"-"`
|
||||||
|
|
||||||
|
EnableNotImplementHandler bool `json:"-"`
|
||||||
|
NotFoundHandler HandlerFunc `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
defaultConfig = &Config{
|
defaultConfig = &Config{
|
||||||
BodyLimit: 4 * 1024 * 1024,
|
BodyLimit: 4 * 1024 * 1024,
|
||||||
|
NotFoundHandler: func(c *Ctx) error {
|
||||||
|
c.Set("Content-Type", MIMETextHTML)
|
||||||
|
_, err := c.Status(404).Write([]byte(_404))
|
||||||
|
return err
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,6 +44,11 @@ func New(config ...Config) *App {
|
|||||||
if app.config.BodyLimit == 0 {
|
if app.config.BodyLimit == 0 {
|
||||||
app.config.BodyLimit = defaultConfig.BodyLimit
|
app.config.BodyLimit = defaultConfig.BodyLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if app.config.NotFoundHandler == nil {
|
||||||
|
app.config.NotFoundHandler = defaultConfig.NotFoundHandler
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
app.config = defaultConfig
|
app.config = defaultConfig
|
||||||
}
|
}
|
||||||
@ -37,6 +56,10 @@ func New(config ...Config) *App {
|
|||||||
app.RouterGroup = &RouterGroup{app: app}
|
app.RouterGroup = &RouterGroup{app: app}
|
||||||
app.groups = []*RouterGroup{app.RouterGroup}
|
app.groups = []*RouterGroup{app.RouterGroup}
|
||||||
|
|
||||||
|
if !app.config.DisableLogger {
|
||||||
|
app.Use(NewLogger())
|
||||||
|
}
|
||||||
|
|
||||||
if !app.config.DisableRecover {
|
if !app.config.DisableRecover {
|
||||||
app.Use(NewRecover(true))
|
app.Use(NewRecover(true))
|
||||||
}
|
}
|
||||||
|
69
nft/resp/error.go
Normal file
69
nft/resp/error.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package resp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/loveuer/nf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
status uint32
|
||||||
|
msg string
|
||||||
|
err error
|
||||||
|
data any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) Error() string {
|
||||||
|
if e.msg != "" {
|
||||||
|
return e.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e.status {
|
||||||
|
case 200:
|
||||||
|
return MSG200
|
||||||
|
case 202:
|
||||||
|
return MSG202
|
||||||
|
case 400:
|
||||||
|
return MSG400
|
||||||
|
case 401:
|
||||||
|
return MSG401
|
||||||
|
case 403:
|
||||||
|
return MSG403
|
||||||
|
case 404:
|
||||||
|
return MSG404
|
||||||
|
case 429:
|
||||||
|
return MSG429
|
||||||
|
case 500:
|
||||||
|
return MSG500
|
||||||
|
case 501:
|
||||||
|
return MSG501
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.err.Error()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(statusCode uint32, msg string, rawErr error, data any) Error {
|
||||||
|
return Error{
|
||||||
|
status: statusCode,
|
||||||
|
msg: msg,
|
||||||
|
err: rawErr,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RespError(c *nf.Ctx, err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return Resp(c, 500, MSG500, "response with nil error", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var re = &Error{}
|
||||||
|
if errors.As(err, re) {
|
||||||
|
if re.err == nil {
|
||||||
|
return Resp(c, re.status, re.msg, re.msg, re.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resp(c, re.status, re.msg, re.err.Error(), re.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resp(c, 500, MSG500, err.Error(), nil)
|
||||||
|
}
|
127
nft/resp/resp.go
Normal file
127
nft/resp/resp.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package resp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/loveuer/nf"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleEmptyMsg(status uint32, msg string) string {
|
||||||
|
if msg == "" {
|
||||||
|
switch status {
|
||||||
|
case 200:
|
||||||
|
msg = MSG200
|
||||||
|
case 202:
|
||||||
|
msg = MSG202
|
||||||
|
case 400:
|
||||||
|
msg = MSG400
|
||||||
|
case 401:
|
||||||
|
msg = MSG401
|
||||||
|
case 403:
|
||||||
|
msg = MSG403
|
||||||
|
case 404:
|
||||||
|
msg = MSG404
|
||||||
|
case 429:
|
||||||
|
msg = MSG429
|
||||||
|
case 500:
|
||||||
|
msg = MSG500
|
||||||
|
case 501:
|
||||||
|
msg = MSG501
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resp(c *nf.Ctx, status uint32, msg string, err string, data any) error {
|
||||||
|
msg = handleEmptyMsg(status, msg)
|
||||||
|
|
||||||
|
c.Set(RealStatusHeader, strconv.Itoa(int(status)))
|
||||||
|
|
||||||
|
if data == nil {
|
||||||
|
return c.JSON(nf.Map{"status": status, "msg": msg, "err": err})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(nf.Map{"status": status, "msg": msg, "err": err, "data": data})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resp200(c *nf.Ctx, data any, msgs ...string) error {
|
||||||
|
msg := MSG200
|
||||||
|
|
||||||
|
if len(msgs) > 0 && msgs[0] != "" {
|
||||||
|
msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resp(c, 200, msg, "", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resp202(c *nf.Ctx, data any, msgs ...string) error {
|
||||||
|
msg := MSG202
|
||||||
|
|
||||||
|
if len(msgs) > 0 && msgs[0] != "" {
|
||||||
|
msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resp(c, 202, msg, "", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resp400(c *nf.Ctx, data any, msgs ...string) error {
|
||||||
|
msg := MSG400
|
||||||
|
err := ""
|
||||||
|
|
||||||
|
if len(msgs) > 0 && msgs[0] != "" {
|
||||||
|
msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
|
||||||
|
err = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resp(c, 400, msg, err, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resp401(c *nf.Ctx, data any, msgs ...string) error {
|
||||||
|
msg := MSG401
|
||||||
|
err := ""
|
||||||
|
|
||||||
|
if len(msgs) > 0 && msgs[0] != "" {
|
||||||
|
msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
|
||||||
|
err = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resp(c, 401, msg, err, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resp403(c *nf.Ctx, data any, msgs ...string) error {
|
||||||
|
msg := MSG403
|
||||||
|
err := ""
|
||||||
|
|
||||||
|
if len(msgs) > 0 && msgs[0] != "" {
|
||||||
|
msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
|
||||||
|
err = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resp(c, 403, msg, err, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resp429(c *nf.Ctx, data any, msgs ...string) error {
|
||||||
|
msg := MSG429
|
||||||
|
err := ""
|
||||||
|
|
||||||
|
if len(msgs) > 0 && msgs[0] != "" {
|
||||||
|
msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
|
||||||
|
err = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resp(c, 429, msg, err, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resp500(c *nf.Ctx, data any, msgs ...string) error {
|
||||||
|
msg := MSG500
|
||||||
|
err := ""
|
||||||
|
|
||||||
|
if len(msgs) > 0 && msgs[0] != "" {
|
||||||
|
msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
|
||||||
|
err = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resp(c, 500, msg, err, data)
|
||||||
|
}
|
17
nft/resp/var.go
Normal file
17
nft/resp/var.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package resp
|
||||||
|
|
||||||
|
const (
|
||||||
|
MSG200 = "请求成功"
|
||||||
|
MSG202 = "请求成功, 请稍后..."
|
||||||
|
MSG400 = "请求参数错误"
|
||||||
|
MSG401 = "登录已过期, 请重新登录"
|
||||||
|
MSG403 = "请求权限不足"
|
||||||
|
MSG404 = "请求资源未找到"
|
||||||
|
MSG429 = "请求过于频繁, 请稍后再试"
|
||||||
|
MSG500 = "服务器开小差了, 请稍后再试"
|
||||||
|
MSG501 = "功能开发中, 尽情期待"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RealStatusHeader = "NF-STATUS"
|
||||||
|
)
|
@ -50,7 +50,7 @@ func handlePost(c *nf.Ctx) error {
|
|||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
req = Req{}
|
req = Req{}
|
||||||
reqMap = make(map[string]any)
|
reqMap = make(map[string]interface{})
|
||||||
)
|
)
|
||||||
|
|
||||||
if err = c.BodyParser(&req); err != nil {
|
if err = c.BodyParser(&req); err != nil {
|
||||||
|
@ -91,8 +91,7 @@ func (r *router) handle(c *Ctx) error {
|
|||||||
key := c.Method + "-" + node.pattern
|
key := c.Method + "-" + node.pattern
|
||||||
c.handlers = append(c.handlers, r.handlers[key]...)
|
c.handlers = append(c.handlers, r.handlers[key]...)
|
||||||
} else {
|
} else {
|
||||||
_, err := c.Writef("404 NOT FOUND: %s\n", c.path)
|
return c.app.config.NotFoundHandler(c)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
|
14
util.go
14
util.go
@ -13,8 +13,6 @@ const (
|
|||||||
MIMETextJavaScript = "text/javascript"
|
MIMETextJavaScript = "text/javascript"
|
||||||
MIMEApplicationXML = "application/xml"
|
MIMEApplicationXML = "application/xml"
|
||||||
MIMEApplicationJSON = "application/json"
|
MIMEApplicationJSON = "application/json"
|
||||||
// Deprecated: use MIMETextJavaScript instead
|
|
||||||
MIMEApplicationJavaScript = "application/javascript"
|
|
||||||
MIMEApplicationForm = "application/x-www-form-urlencoded"
|
MIMEApplicationForm = "application/x-www-form-urlencoded"
|
||||||
MIMEOctetStream = "application/octet-stream"
|
MIMEOctetStream = "application/octet-stream"
|
||||||
MIMEMultipartForm = "multipart/form-data"
|
MIMEMultipartForm = "multipart/form-data"
|
||||||
@ -29,18 +27,6 @@ const (
|
|||||||
MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
|
MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func verifyHandlers(path string, handlers ...HandlerFunc) {
|
|
||||||
if len(handlers) == 0 {
|
|
||||||
panic(fmt.Sprintf("missing handler in route: %s", path))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, handler := range handlers {
|
|
||||||
if handler == nil {
|
|
||||||
panic(fmt.Sprintf("nil handler found in route: %s", path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseVendorSpecificContentType check if content type is vendor specific and
|
// parseVendorSpecificContentType check if content type is vendor specific and
|
||||||
// if it is parsable to any known types. If it's not vendor specific then returns
|
// if it is parsable to any known types. If it's not vendor specific then returns
|
||||||
// the original content type.
|
// the original content type.
|
||||||
|
@ -1,2 +1,6 @@
|
|||||||
### basic - get
|
### basic - get
|
||||||
GET http://127.0.0.1/hello/nf
|
GET http://127.0.0.1/hello/nf
|
||||||
|
|
||||||
|
|
||||||
|
### test resp error
|
||||||
|
GET http://127.0.0.1/error
|
@ -1,18 +1,30 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"github.com/loveuer/nf"
|
"github.com/loveuer/nf"
|
||||||
|
"github.com/loveuer/nf/nft/resp"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := nf.New()
|
app := nf.New(nf.Config{EnableNotImplementHandler: true})
|
||||||
|
|
||||||
app.Get("/hello/:name", func(c *nf.Ctx) error {
|
app.Get("/hello/:name", func(c *nf.Ctx) error {
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
return c.JSON(nf.Map{"status": 200, "data": "hello, " + name})
|
return c.JSON(nf.Map{"status": 200, "data": "hello, " + name})
|
||||||
})
|
})
|
||||||
|
app.Get("/not_impl")
|
||||||
|
app.Patch("/world", func(c *nf.Ctx) error {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
c.Status(404)
|
||||||
|
return c.JSON(nf.Map{"method": c.Method, "status": c.StatusCode})
|
||||||
|
})
|
||||||
|
app.Get("/error", func(c *nf.Ctx) error {
|
||||||
|
return resp.RespError(c, resp.NewError(404, "not found", errors.New("NNNot Found"), nil))
|
||||||
|
})
|
||||||
|
|
||||||
ln, _ := net.Listen("tcp", ":80")
|
ln, _ := net.Listen("tcp", ":80")
|
||||||
log.Fatal(app.RunListener(ln))
|
log.Fatal(app.RunListener(ln))
|
||||||
|
27
xtest/multihandler/main.go
Normal file
27
xtest/multihandler/main.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/loveuer/nf"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := nf.New()
|
||||||
|
|
||||||
|
app.Get("/nice", h1, h2)
|
||||||
|
|
||||||
|
log.Fatal(app.Run(":3333"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func h1(c *nf.Ctx) error {
|
||||||
|
you := c.Query("to")
|
||||||
|
if you == "you" {
|
||||||
|
return c.JSON(nf.Map{"status": 201, "msg": "nice to meet you"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func h2(c *nf.Ctx) error {
|
||||||
|
return c.JSON(nf.Map{"status": 200, "msg": "hello world"})
|
||||||
|
}
|
5
xtest/multihandler/req.http
Normal file
5
xtest/multihandler/req.http
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
### test multi handlers no next
|
||||||
|
GET http://127.0.0.1:3333/nice?to=you
|
||||||
|
|
||||||
|
### test multi handlers do next
|
||||||
|
GET http://127.0.0.1:3333/nice?to=nf
|
@ -18,7 +18,7 @@ func main() {
|
|||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
req = new(Req)
|
req = new(Req)
|
||||||
rm = make(map[string]any)
|
rm = make(map[string]interface{})
|
||||||
)
|
)
|
||||||
|
|
||||||
//if err = c.QueryParser(req); err != nil {
|
//if err = c.QueryParser(req); err != nil {
|
||||||
|
52
xtest/quit/main.go
Normal file
52
xtest/quit/main.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/loveuer/nf"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
app = nf.New()
|
||||||
|
quit = make(chan bool)
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
app.Get("/name", handleGet)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := app.Run(":80")
|
||||||
|
log.Print("run with err=", err)
|
||||||
|
quit <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-quit
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGet(c *nf.Ctx) error {
|
||||||
|
type Req struct {
|
||||||
|
Name string `query:"name"`
|
||||||
|
Addr []string `query:"addr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
req = Req{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = c.QueryParser(&req); err != nil {
|
||||||
|
return nf.NewNFError(400, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name == "quit" {
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
log.Print("app quit = ", app.Shutdown(context.TODO()))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(nf.Map{"req_map": req})
|
||||||
|
}
|
119
xtest/tls/main.go
Normal file
119
xtest/tls/main.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"github.com/loveuer/nf"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := nf.New(nf.Config{
|
||||||
|
DisableHttpErrorLog: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/hello/:name", func(c *nf.Ctx) error {
|
||||||
|
return c.SendString("hello, " + c.Param("name"))
|
||||||
|
})
|
||||||
|
|
||||||
|
st, _, _ := GenerateTlsConfig()
|
||||||
|
log.Fatal(app.RunTLS(":443", st))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateTlsConfig() (serverTLSConf *tls.Config, clientTLSConf *tls.Config, err error) {
|
||||||
|
ca := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(2019),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"Company, INC."},
|
||||||
|
Country: []string{"US"},
|
||||||
|
Province: []string{""},
|
||||||
|
Locality: []string{"San Francisco"},
|
||||||
|
StreetAddress: []string{"Golden Gate Bridge"},
|
||||||
|
PostalCode: []string{"94016"},
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(99, 0, 0),
|
||||||
|
IsCA: true,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
// create our private and public key
|
||||||
|
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// create the CA
|
||||||
|
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// pem encode
|
||||||
|
caPEM := new(bytes.Buffer)
|
||||||
|
pem.Encode(caPEM, &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: caBytes,
|
||||||
|
})
|
||||||
|
caPrivKeyPEM := new(bytes.Buffer)
|
||||||
|
pem.Encode(caPrivKeyPEM, &pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
|
||||||
|
})
|
||||||
|
// set up our server certificate
|
||||||
|
cert := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(2019),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"Company, INC."},
|
||||||
|
Country: []string{"US"},
|
||||||
|
Province: []string{""},
|
||||||
|
Locality: []string{"San Francisco"},
|
||||||
|
StreetAddress: []string{"Golden Gate Bridge"},
|
||||||
|
PostalCode: []string{"94016"},
|
||||||
|
},
|
||||||
|
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||||
|
SubjectKeyId: []byte{1, 2, 3, 4, 6},
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||||
|
}
|
||||||
|
certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
certPEM := new(bytes.Buffer)
|
||||||
|
pem.Encode(certPEM, &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: certBytes,
|
||||||
|
})
|
||||||
|
certPrivKeyPEM := new(bytes.Buffer)
|
||||||
|
pem.Encode(certPrivKeyPEM, &pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
|
||||||
|
})
|
||||||
|
serverCert, err := tls.X509KeyPair(certPEM.Bytes(), certPrivKeyPEM.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
serverTLSConf = &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{serverCert},
|
||||||
|
}
|
||||||
|
certpool := x509.NewCertPool()
|
||||||
|
certpool.AppendCertsFromPEM(caPEM.Bytes())
|
||||||
|
clientTLSConf = &tls.Config{
|
||||||
|
RootCAs: certpool,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
Reference in New Issue
Block a user