Compare commits

..

12 Commits

18 changed files with 743 additions and 90 deletions

54
app.go
View File

@ -1,8 +1,13 @@
package nf package nf
import ( import (
"context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io"
"log"
"net"
"net/http" "net/http"
"strings" "strings"
) )
@ -12,6 +17,7 @@ type App struct {
config *Config config *Config
router *router router *router
groups []*RouterGroup groups []*RouterGroup
server *http.Server
} }
func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) { func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
@ -36,9 +42,51 @@ func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
} }
} }
func (a *App) Run(address string) error { func (a *App) run(ln net.Listener) error {
srv := &http.Server{Handler: a}
if a.config.DisableHttpErrorLog {
srv.ErrorLog = log.New(io.Discard, "", 0)
}
a.server = srv
if !a.config.DisableBanner { if !a.config.DisableBanner {
fmt.Println(banner + "nf serve at: " + address + "\n") fmt.Println(banner + "nf serve at: " + ln.Addr().String() + "\n")
} }
return http.ListenAndServe(address, a)
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 {
ln, err := net.Listen("tcp", address)
if err != nil {
return err
}
return a.run(ln)
}
func (a *App) RunTLS(address string, tlsConfig *tls.Config) error {
ln, err := tls.Listen("tcp", address, tlsConfig)
if err != nil {
return err
}
return a.run(ln)
}
func (a *App) RunListener(ln net.Listener) error {
a.server = &http.Server{Addr: ln.Addr().String()}
return a.run(ln)
}
func (a *App) Shutdown(ctx context.Context) error {
return a.server.Shutdown(ctx)
} }

104
ctx.go
View File

@ -3,15 +3,18 @@ package nf
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"log" "log"
"mime/multipart"
"net"
"net/http" "net/http"
"strings" "strings"
) )
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
@ -23,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]
@ -58,6 +62,23 @@ func (c *Ctx) Path(overWrite ...string) string {
return path return path
} }
func (c *Ctx) Cookies(key string, defaultValue ...string) string {
var (
dv = ""
)
if len(defaultValue) > 0 {
dv = defaultValue[0]
}
cookie, err := c.Request.Cookie(key)
if err != nil || cookie.Value == "" {
return dv
}
return cookie.Value
}
func (c *Ctx) Next() error { func (c *Ctx) Next() error {
c.index++ c.index++
s := len(c.handlers) s := len(c.handlers)
@ -91,6 +112,11 @@ func (c *Ctx) Form(key string) string {
return c.Request.FormValue(key) return c.Request.FormValue(key)
} }
func (c *Ctx) FormFile(key string) (*multipart.FileHeader, error) {
_, fh, err := c.Request.FormFile(key)
return fh, err
}
func (c *Ctx) Query(key string) string { func (c *Ctx) Query(key string) string {
return c.Request.URL.Query().Get(key) return c.Request.URL.Query().Get(key)
} }
@ -104,6 +130,14 @@ func (c *Ctx) Get(key string, defaultValue ...string) string {
return value return value
} }
func (c *Ctx) IP() string {
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
if err != nil {
return ""
}
return ip
}
func (c *Ctx) BodyParser(out interface{}) error { func (c *Ctx) BodyParser(out interface{}) error {
var ( var (
err error err error
@ -152,5 +186,65 @@ func (c *Ctx) BodyParser(out interface{}) error {
} }
func (c *Ctx) QueryParser(out interface{}) error { func (c *Ctx) QueryParser(out interface{}) error {
//v := reflect.ValueOf(out)
//
//if v.Kind() == reflect.Ptr && v.Elem().Kind() != reflect.Map {
//}
return parseToStruct("query", out, c.Request.URL.Query()) return parseToStruct("query", out, c.Request.URL.Query())
} }
/* ===============================================================
|| Handle Ctx Response Part
=============================================================== */
func (c *Ctx) Status(code int) *Ctx {
c.StatusCode = code
c.writer.WriteHeader(code)
return c
}
func (c *Ctx) Set(key string, value string) {
c.writer.Header().Set(key, value)
}
func (c *Ctx) SetHeader(key string, value string) {
c.writer.Header().Set(key, value)
}
func (c *Ctx) SendString(data string) error {
c.SetHeader("Content-Type", "text/plain")
_, err := c.Write([]byte(data))
return err
}
func (c *Ctx) Writef(format string, values ...interface{}) (int, error) {
c.SetHeader("Content-Type", "text/plain")
return c.writer.Write([]byte(fmt.Sprintf(format, values...)))
}
func (c *Ctx) JSON(data interface{}) error {
c.SetHeader("Content-Type", MIMEApplicationJSON)
encoder := json.NewEncoder(c.writer)
if err := encoder.Encode(data); err != nil {
return err
}
return nil
}
func (c *Ctx) RawWriter() http.ResponseWriter {
return c.writer
}
func (c *Ctx) Write(data []byte) (int, error) {
return c.writer.Write(data)
}
func (c *Ctx) HTML(html string) error {
c.SetHeader("Content-Type", "text/html")
_, err := c.writer.Write([]byte(html))
return err
}

View File

@ -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...)

View File

@ -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()))
}

View File

@ -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
View File

@ -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
View 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
View 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
View File

@ -0,0 +1,17 @@
package resp
const (
MSG200 = "请求成功"
MSG202 = "请求成功, 请稍后..."
MSG400 = "请求参数错误"
MSG401 = "登录已过期, 请重新登录"
MSG403 = "请求权限不足"
MSG404 = "请求资源未找到"
MSG429 = "请求过于频繁, 请稍后再试"
MSG500 = "服务器开小差了, 请稍后再试"
MSG501 = "功能开发中, 尽情期待"
)
const (
RealStatusHeader = "NF-STATUS"
)

67
readme.md Normal file
View File

@ -0,0 +1,67 @@
# NF Web Framework
### Usage
##### basic usage
- get param
```go
func main() {
app := nf.New()
app.Get("/hello/:name", func(c *nf.Ctx) error {
name := c.Param("name")
return c.JSON(nf.Map{"status": 200, "data": "hello, " + name})
})
log.Fatal(app.Run("0.0.0.0:80"))
}
```
- parse request query
```go
func handleQuery(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())
}
return c.JSON(nf.Map{"query": req})
}
```
- parse application/json body
```go
func handlePost(c *nf.Ctx) error {
type Req struct {
Name string `json:"name"`
Addr []string `json:"addr"`
}
var (
err error
req = Req{}
reqMap = make(map[string]interface{})
)
if err = c.BodyParser(&req); err != nil {
return nf.NewNFError(400, err.Error())
}
// can parse body multi times
if err = c.BodyParser(&reqMap); err != nil {
return nf.NewNFError(400, err.Error())
}
return c.JSON(nf.Map{"struct": req, "map": reqMap})
}
```

48
resp.go
View File

@ -1,49 +1 @@
package nf package nf
import (
"encoding/json"
"fmt"
)
func (c *Ctx) Status(code int) *Ctx {
c.StatusCode = code
c.Writer.WriteHeader(code)
return c
}
func (c *Ctx) SetHeader(key string, value string) {
c.Writer.Header().Set(key, value)
}
func (c *Ctx) SendString(data string) error {
c.SetHeader("Content-Type", "text/plain")
_, err := c.Write([]byte(data))
return err
}
func (c *Ctx) Writef(format string, values ...interface{}) (int, error) {
c.SetHeader("Content-Type", "text/plain")
return c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}
func (c *Ctx) JSON(data interface{}) error {
c.SetHeader("Content-Type", "application/json")
encoder := json.NewEncoder(c.Writer)
if err := encoder.Encode(data); err != nil {
return err
}
return nil
}
func (c *Ctx) Write(data []byte) (int, error) {
return c.Writer.Write(data)
}
func (c *Ctx) HTML(html string) error {
c.SetHeader("Content-Type", "text/html")
_, err := c.Writer.Write([]byte(html))
return err
}

View File

@ -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
View File

@ -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.

View File

@ -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

View File

@ -1,17 +1,31 @@
package main package main
import ( import (
"errors"
"github.com/loveuer/nf" "github.com/loveuer/nf"
"github.com/loveuer/nf/nft/resp"
"log" "log"
"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))
})
log.Fatal(app.Run("0.0.0.0:80")) ln, _ := net.Listen("tcp", ":80")
log.Fatal(app.RunListener(ln))
} }

View File

@ -18,13 +18,18 @@ func main() {
var ( var (
err error err error
req = new(Req) req = new(Req)
rm = make(map[string]interface{})
) )
if err = c.QueryParser(req); err != nil { //if err = c.QueryParser(req); err != nil {
return nf.NewNFError(400, err.Error()) // return nf.NewNFError(400, "1:"+err.Error())
//}
if err = c.QueryParser(&rm); err != nil {
return nf.NewNFError(400, "2:"+err.Error())
} }
return c.JSON(nf.Map{"status": 200, "data": req}) return c.JSON(nf.Map{"status": 200, "data": req, "map": rm})
}) })
log.Fatal(app.Run("0.0.0.0:80")) log.Fatal(app.Run("0.0.0.0:80"))

52
xtest/quit/main.go Normal file
View 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
View 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
}