Compare commits

..

10 Commits

27 changed files with 499 additions and 503 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea .idea
.vscode .vscode
.DS_Store .DS_Store
xtest

18
app.go
View File

@ -19,8 +19,6 @@ var (
regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
regRemoveRepeatedChar = regexp.MustCompile("/{2,}") regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
mimePlain = []string{"text/plain"}
) )
type App struct { type App struct {
@ -165,8 +163,9 @@ func (a *App) handleHTTPRequest(c *Ctx) {
// Find route in tree // Find route in tree
value := root.getValue(rPath, c.params, c.skippedNodes, unescape) value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil { if value.params != nil {
c.Params = *value.params c.params = value.params
} }
if value.handlers != nil { if value.handlers != nil {
c.handlers = value.handlers c.handlers = value.handlers
c.fullPath = value.fullPath c.fullPath = value.fullPath
@ -175,7 +174,6 @@ func (a *App) handleHTTPRequest(c *Ctx) {
serveError(c, errorHandler) serveError(c, errorHandler)
} }
c.writermem.WriteHeaderNow()
return return
} }
if httpMethod != http.MethodConnect && rPath != "/" { if httpMethod != http.MethodConnect && rPath != "/" {
@ -204,17 +202,19 @@ func (a *App) handleHTTPRequest(c *Ctx) {
} }
if len(allowed) > 0 { if len(allowed) > 0 {
c.handlers = a.combineHandlers() c.handlers = a.combineHandlers(a.config.MethodNotAllowedHandler)
serveError(c, a.config.MethodNotAllowedHandler) _ = c.Next()
return return
} }
} }
c.handlers = a.combineHandlers() c.handlers = a.combineHandlers(a.config.NotFoundHandler)
serveError(c, a.config.NotFoundHandler) _ = c.Next()
return
} }
func errorHandler(c *Ctx) error { func errorHandler(c *Ctx) error {
@ -273,6 +273,6 @@ func redirectRequest(c *Ctx) {
//debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL) //debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
http.Redirect(c.writer, req, rURL, code) http.Redirect(c.Writer, req, rURL, code)
c.writermem.WriteHeaderNow() c.writermem.WriteHeaderNow()
} }

92
ctx.go
View File

@ -3,23 +3,24 @@ package nf
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"github.com/loveuer/nf/internal/sse"
"io" "io"
"mime/multipart" "mime/multipart"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"sync"
) )
type Ctx struct { type Ctx struct {
lock sync.Mutex
writermem responseWriter writermem responseWriter
// origin objects Writer ResponseWriter
writer http.ResponseWriter
Request *http.Request Request *http.Request
// request info
path string path string
method string method string
// response info
StatusCode int StatusCode int
app *App app *App
@ -29,7 +30,6 @@ type Ctx struct {
locals map[string]interface{} locals map[string]interface{}
skippedNodes *[]skippedNode skippedNodes *[]skippedNode
fullPath string fullPath string
Params Params
} }
func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ctx { func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ctx {
@ -38,8 +38,7 @@ func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ct
v := make(Params, 0, app.maxParams) v := make(Params, 0, app.maxParams)
ctx := &Ctx{ ctx := &Ctx{
writer: writer, lock: sync.Mutex{},
writermem: responseWriter{},
Request: request, Request: request,
path: request.URL.Path, path: request.URL.Path,
method: request.Method, method: request.Method,
@ -54,11 +53,13 @@ func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ct
} }
ctx.writermem = responseWriter{ ctx.writermem = responseWriter{
ResponseWriter: ctx.writer, ResponseWriter: writer,
size: -1, size: -1,
status: 0, status: 0,
} }
ctx.Writer = &ctx.writermem
return ctx return ctx
} }
@ -110,12 +111,15 @@ func (c *Ctx) Cookies(key string, defaultValue ...string) string {
func (c *Ctx) Next() error { func (c *Ctx) Next() error {
c.index++ c.index++
if c.index >= len(c.handlers) {
return nil
}
var ( var (
err error err error
handler = c.handlers[c.index] handler = c.handlers[c.index]
) )
//for c.index < len(c.handlers) {
if handler != nil { if handler != nil {
if err = handler(c); err != nil { if err = handler(c); err != nil {
return err return err
@ -123,7 +127,6 @@ func (c *Ctx) Next() error {
} }
c.index++ c.index++
//}
return nil return nil
} }
@ -142,18 +145,39 @@ func (c *Ctx) verify() error {
} }
func (c *Ctx) Param(key string) string { func (c *Ctx) Param(key string) string {
return c.Params.ByName(key) return c.params.ByName(key)
}
func (c *Ctx) SetParam(key, value string) {
c.lock.Lock()
defer c.lock.Unlock()
params := append(*c.params, Param{Key: key, Value: value})
c.params = &params
} }
func (c *Ctx) Form(key string) string { func (c *Ctx) Form(key string) string {
return c.Request.FormValue(key) return c.Request.FormValue(key)
} }
// FormValue fiber ctx function
func (c *Ctx) FormValue(key string) string {
return c.Request.FormValue(key)
}
func (c *Ctx) FormFile(key string) (*multipart.FileHeader, error) { func (c *Ctx) FormFile(key string) (*multipart.FileHeader, error) {
_, fh, err := c.Request.FormFile(key) _, fh, err := c.Request.FormFile(key)
return fh, err return fh, err
} }
func (c *Ctx) MultipartForm() (*multipart.Form, error) {
if err := c.Request.ParseMultipartForm(c.app.config.BodyLimit); err != nil {
return nil, err
}
return c.Request.MultipartForm, nil
}
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)
} }
@ -221,11 +245,6 @@ 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())
} }
@ -234,8 +253,12 @@ func (c *Ctx) QueryParser(out interface{}) error {
=============================================================== */ =============================================================== */
func (c *Ctx) Status(code int) *Ctx { func (c *Ctx) Status(code int) *Ctx {
c.lock.Lock()
defer c.lock.Unlock()
c.writermem.WriteHeader(code) c.writermem.WriteHeader(code)
c.StatusCode = c.writermem.status c.StatusCode = c.writermem.status
return c return c
} }
@ -247,6 +270,12 @@ func (c *Ctx) SetHeader(key string, value string) {
c.writermem.Header().Set(key, value) c.writermem.Header().Set(key, value)
} }
func (c *Ctx) SendStatus(code int) error {
c.Status(code)
c.writermem.WriteHeaderNow()
return nil
}
func (c *Ctx) SendString(data string) error { func (c *Ctx) SendString(data string) error {
c.SetHeader("Content-Type", "text/plain") c.SetHeader("Content-Type", "text/plain")
_, err := c.Write([]byte(data)) _, err := c.Write([]byte(data))
@ -255,7 +284,7 @@ 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.Write([]byte(fmt.Sprintf(format, values...)))
} }
func (c *Ctx) JSON(data interface{}) error { func (c *Ctx) JSON(data interface{}) error {
@ -270,16 +299,29 @@ func (c *Ctx) JSON(data interface{}) error {
return nil return nil
} }
func (c *Ctx) RawWriter() http.ResponseWriter { func (c *Ctx) SSEvent(event string, data interface{}) error {
return c.writer c.Set("Content-Type", "text/event-stream")
c.Set("Cache-Control", "no-cache")
c.Set("Transfer-Encoding", "chunked")
return sse.Encode(c.Writer, sse.Event{Event: event, Data: data})
}
func (c *Ctx) Flush() error {
if f, ok := c.Writer.(http.Flusher); ok {
f.Flush()
return nil
}
return errors.New("http.Flusher is not implemented")
}
func (c *Ctx) HTML(html string) error {
c.SetHeader("Content-Type", "text/html")
_, err := c.Write([]byte(html))
return err
} }
func (c *Ctx) Write(data []byte) (int, error) { func (c *Ctx) Write(data []byte) (int, error) {
return c.writermem.Write(data) return c.writermem.Write(data)
} }
func (c *Ctx) HTML(html string) error {
c.SetHeader("Content-Type", "text/html")
_, err := c.writer.Write([]byte(html))
return err
}

11
go.mod
View File

@ -1,3 +1,14 @@
module github.com/loveuer/nf module github.com/loveuer/nf
go 1.20 go 1.20
require (
github.com/fatih/color v1.17.0
github.com/google/uuid v1.6.0
)
require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.18.0 // indirect
)

13
go.sum
View File

@ -0,0 +1,13 @@
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

106
internal/sse/sse-encoder.go Normal file
View File

@ -0,0 +1,106 @@
package sse
import (
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
"strconv"
"strings"
)
// Server-Sent Events
// W3C Working Draft 29 October 2009
// http://www.w3.org/TR/2009/WD-eventsource-20091029/
const ContentType = "text/event-stream"
var contentType = []string{ContentType}
var noCache = []string{"no-cache"}
var fieldReplacer = strings.NewReplacer(
"\n", "\\n",
"\r", "\\r")
var dataReplacer = strings.NewReplacer(
"\n", "\ndata:",
"\r", "\\r")
type Event struct {
Event string
Id string
Retry uint
Data interface{}
}
func Encode(writer io.Writer, event Event) error {
w := checkWriter(writer)
writeId(w, event.Id)
writeEvent(w, event.Event)
writeRetry(w, event.Retry)
return writeData(w, event.Data)
}
func writeId(w stringWriter, id string) {
if len(id) > 0 {
w.WriteString("id:")
fieldReplacer.WriteString(w, id)
w.WriteString("\n")
}
}
func writeEvent(w stringWriter, event string) {
if len(event) > 0 {
w.WriteString("event:")
fieldReplacer.WriteString(w, event)
w.WriteString("\n")
}
}
func writeRetry(w stringWriter, retry uint) {
if retry > 0 {
w.WriteString("retry:")
w.WriteString(strconv.FormatUint(uint64(retry), 10))
w.WriteString("\n")
}
}
func writeData(w stringWriter, data interface{}) error {
w.WriteString("data:")
switch kindOfData(data) {
case reflect.Struct, reflect.Slice, reflect.Map:
err := json.NewEncoder(w).Encode(data)
if err != nil {
return err
}
w.WriteString("\n")
default:
dataReplacer.WriteString(w, fmt.Sprint(data))
w.WriteString("\n\n")
}
return nil
}
func (r Event) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
return Encode(w, r)
}
func (r Event) WriteContentType(w http.ResponseWriter) {
header := w.Header()
header["Content-Type"] = contentType
if _, exist := header["Cache-Control"]; !exist {
header["Cache-Control"] = noCache
}
}
func kindOfData(data interface{}) reflect.Kind {
value := reflect.ValueOf(data)
valueType := value.Kind()
if valueType == reflect.Ptr {
valueType = value.Elem().Kind()
}
return valueType
}

24
internal/sse/writer.go Normal file
View File

@ -0,0 +1,24 @@
package sse
import "io"
type stringWriter interface {
io.Writer
WriteString(string) (int, error)
}
type stringWrapper struct {
io.Writer
}
func (w stringWrapper) WriteString(str string) (int, error) {
return w.Writer.Write([]byte(str))
}
func checkWriter(writer io.Writer) stringWriter {
if w, ok := writer.(stringWriter); ok {
return w
} else {
return stringWrapper{writer}
}
}

View File

@ -2,9 +2,11 @@ package nf
import ( import (
"fmt" "fmt"
"log" "github.com/google/uuid"
"github.com/loveuer/nf/nft/log"
"os" "os"
"runtime/debug" "runtime/debug"
"strings"
"time" "time"
) )
@ -27,51 +29,44 @@ func NewRecover(enableStackTrace bool) HandlerFunc {
} }
} }
func NewLogger() HandlerFunc { func NewLogger(traceHeader ...string) HandlerFunc {
l := log.New(os.Stdout, "[NF] ", 0) Header := "X-Trace-ID"
if len(traceHeader) > 0 && traceHeader[0] != "" {
durationFormat := func(num int64) string { Header = traceHeader[0]
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("%3d %2s", num, unit)
} }
return func(c *Ctx) error { return func(c *Ctx) error {
start := time.Now() var (
now = time.Now()
trace = c.Get(Header)
logFn func(msg string, data ...any)
ip = c.IP()
)
if trace == "" {
trace = uuid.Must(uuid.NewV7()).String()
}
c.SetHeader(Header, trace)
traces := strings.Split(trace, "-")
shortTrace := traces[len(traces)-1]
err := c.Next() err := c.Next()
duration := time.Since(now)
var ( msg := fmt.Sprintf("NF | %s | %15s | %3d | %s | %6s | %s", shortTrace, ip, c.StatusCode, HumanDuration(duration.Nanoseconds()), c.Method(), c.Path())
duration = time.Now().Sub(start).Nanoseconds()
status = c.StatusCode
path = c.path
method = c.Request.Method
)
l.Printf("%s | %5s | %d | %s | %s", switch {
start.Format("06/01/02T15:04:05"), case c.StatusCode >= 500:
method, logFn = log.Error
status, case c.StatusCode >= 400:
durationFormat(duration), logFn = log.Warn
path, default:
) logFn = log.Info
}
logFn(msg)
return err return err
} }

67
nft/log/default.go Normal file
View File

@ -0,0 +1,67 @@
package log
import (
"fmt"
"os"
"sync"
)
var (
nilLogger = func(prefix, timestamp, msg string, data ...any) {}
normalLogger = func(prefix, timestamp, msg string, data ...any) {
fmt.Printf(prefix+"| "+timestamp+" | "+msg+"\n", data...)
}
panicLogger = func(prefix, timestamp, msg string, data ...any) {
panic(fmt.Sprintf(prefix+"| "+timestamp+" | "+msg+"\n", data...))
}
fatalLogger = func(prefix, timestamp, msg string, data ...any) {
fmt.Printf(prefix+"| "+timestamp+" | "+msg+"\n", data...)
os.Exit(1)
}
defaultLogger = &logger{
Mutex: sync.Mutex{},
timeFormat: "2006-01-02T15:04:05",
writer: os.Stdout,
level: LogLevelInfo,
debug: nilLogger,
info: normalLogger,
warn: normalLogger,
error: normalLogger,
panic: panicLogger,
fatal: fatalLogger,
}
)
func SetTimeFormat(format string) {
defaultLogger.SetTimeFormat(format)
}
func SetLogLevel(level LogLevel) {
defaultLogger.SetLogLevel(level)
}
func Debug(msg string, data ...any) {
defaultLogger.Debug(msg, data...)
}
func Info(msg string, data ...any) {
defaultLogger.Info(msg, data...)
}
func Warn(msg string, data ...any) {
defaultLogger.Warn(msg, data...)
}
func Error(msg string, data ...any) {
defaultLogger.Error(msg, data...)
}
func Panic(msg string, data ...any) {
defaultLogger.Panic(msg, data...)
}
func Fatal(msg string, data ...any) {
defaultLogger.Fatal(msg, data...)
}

115
nft/log/log.go Normal file
View File

@ -0,0 +1,115 @@
package log
import (
"github.com/fatih/color"
"io"
"sync"
"time"
)
type LogLevel uint32
const (
LogLevelDebug = iota
LogLevelInfo
LogLevelWarn
LogLevelError
LogLevelPanic
LogLevelFatal
)
type logger struct {
sync.Mutex
timeFormat string
writer io.Writer
level LogLevel
debug func(prefix, timestamp, msg string, data ...any)
info func(prefix, timestamp, msg string, data ...any)
warn func(prefix, timestamp, msg string, data ...any)
error func(prefix, timestamp, msg string, data ...any)
panic func(prefix, timestamp, msg string, data ...any)
fatal func(prefix, timestamp, msg string, data ...any)
}
var (
red = color.New(color.FgRed)
hired = color.New(color.FgHiRed)
green = color.New(color.FgGreen)
yellow = color.New(color.FgYellow)
white = color.New(color.FgWhite)
)
func (l *logger) SetTimeFormat(format string) {
l.Lock()
defer l.Unlock()
l.timeFormat = format
}
func (l *logger) SetLogLevel(level LogLevel) {
l.Lock()
defer l.Unlock()
if level > LogLevelDebug {
l.debug = nilLogger
} else {
l.debug = normalLogger
}
if level > LogLevelInfo {
l.info = nilLogger
} else {
l.info = normalLogger
}
if level > LogLevelWarn {
l.warn = nilLogger
} else {
l.warn = normalLogger
}
if level > LogLevelError {
l.error = nilLogger
} else {
l.error = normalLogger
}
if level > LogLevelPanic {
l.panic = nilLogger
} else {
l.panic = panicLogger
}
if level > LogLevelFatal {
l.fatal = nilLogger
} else {
l.fatal = fatalLogger
}
}
func (l *logger) Debug(msg string, data ...any) {
l.debug(white.Sprint("Debug "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Info(msg string, data ...any) {
l.info(green.Sprint("Info "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Warn(msg string, data ...any) {
l.warn(yellow.Sprint("Warn "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Error(msg string, data ...any) {
l.error(red.Sprint("Error "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Panic(msg string, data ...any) {
l.panic(hired.Sprint("Panic "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Fatal(msg string, data ...any) {
l.fatal(hired.Sprint("Fatal "), time.Now().Format(l.timeFormat), msg, data...)
}
type WroteLogger interface {
Info(msg string, data ...any)
}

21
nft/log/new.go Normal file
View File

@ -0,0 +1,21 @@
package log
import (
"os"
"sync"
)
func New() *logger {
return &logger{
Mutex: sync.Mutex{},
timeFormat: "2006-01-02T15:04:05",
writer: os.Stdout,
level: LogLevelInfo,
debug: nilLogger,
info: normalLogger,
warn: normalLogger,
error: normalLogger,
panic: panicLogger,
fatal: fatalLogger,
}
}

View File

@ -1 +0,0 @@
package nf

View File

@ -3,6 +3,7 @@ package nf
import ( import (
"bufio" "bufio"
"io" "io"
"log"
"net" "net"
"net/http" "net/http"
) )
@ -41,6 +42,7 @@ type ResponseWriter interface {
type responseWriter struct { type responseWriter struct {
http.ResponseWriter http.ResponseWriter
written bool
size int size int
status int status int
} }
@ -60,7 +62,7 @@ func (w *responseWriter) reset(writer http.ResponseWriter) {
func (w *responseWriter) WriteHeader(code int) { func (w *responseWriter) WriteHeader(code int) {
if code > 0 && w.status != code { if code > 0 && w.status != code {
if w.Written() { if w.Written() {
// todo: debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code) log.Printf("[NF] WARNING: Headers were already written. Wanted to override status code %d with %d", w.status, code)
return return
} }
w.status = code w.status = code
@ -102,7 +104,7 @@ func (w *responseWriter) Size() int {
} }
func (w *responseWriter) Written() bool { func (w *responseWriter) Written() bool {
return w.size != noWritten return w.size != noWritten || w.written
} }
// Hijack implements the http.Hijacker interface. // Hijack implements the http.Hijacker interface.

21
util.go
View File

@ -202,3 +202,24 @@ func bufApp(buf *[]byte, s string, w int, c byte) {
} }
b[w] = c b[w] = c
} }
func HumanDuration(nano int64) string {
duration := float64(nano)
unit := "ns"
if duration >= 1000 {
duration /= 1000
unit = "us"
}
if duration >= 1000 {
duration /= 1000
unit = "ms"
}
if duration >= 1000 {
duration /= 1000
unit = " s"
}
return fmt.Sprintf("%6.2f%s", duration, unit)
}

View File

@ -1,14 +0,0 @@
### basic - get
GET http://127.0.0.1/hello/nf
### test resp error
GET http://127.0.0.1/error
### test basic post
POST http://127.0.0.1/data
Content-Type: application/json
{
"name": "nice"
}

View File

@ -1,17 +0,0 @@
package main
import (
"github.com/loveuer/nf"
"log"
)
func main() {
app := nf.New(nf.Config{EnableNotImplementHandler: true})
api := app.Group("/api")
api.Get("/1", func(c *nf.Ctx) error {
return c.SendString("nice")
})
log.Fatal(app.Run(":80"))
}

View File

@ -1,9 +0,0 @@
### body_limit
POST http://127.0.0.1/data
Content-Type: application/json
{
"name": "zyp",
"age": 19,
"likes": ["2233"]
}

View File

@ -1,50 +0,0 @@
package main
import (
"github.com/loveuer/nf"
"log"
)
func main() {
app := nf.New(nf.Config{BodyLimit: 30})
app.Post("/data", func(c *nf.Ctx) error {
type Req struct {
Name string `json:"name"`
Age int `json:"age"`
Likes []string `json:"likes"`
}
var (
err error
req = new(Req)
)
if err = c.BodyParser(req); err != nil {
return c.JSON(nf.Map{"status": 400, "err": err.Error()})
}
return c.JSON(nf.Map{"status": 200, "data": req})
})
app.Post("/url", func(c *nf.Ctx) error {
type Req struct {
Name string `form:"name"`
Age int `form:"age"`
Likes []string `form:"likes"`
}
var (
err error
req = new(Req)
)
if err = c.BodyParser(req); err != nil {
return c.JSON(nf.Map{"status": 400, "err": err.Error()})
}
return c.JSON(nf.Map{"status": 200, "data": req})
})
log.Fatal(app.Run("0.0.0.0:80"))
}

View File

@ -1,28 +0,0 @@
package main
import (
"github.com/loveuer/nf"
"log"
)
func main() {
app := nf.New(nf.Config{DisableLogger: false})
app.Get("/hello", func(c *nf.Ctx) error {
return c.SendString("world")
})
app.Use(ml())
log.Fatal(app.Run(":80"))
}
func ml() nf.HandlerFunc {
return func(c *nf.Ctx) error {
index := []byte(`<h1>my not found</h1>`)
c.Set("Content-Type", "text/html")
c.Status(403)
_, err := c.Write(index)
return err
}
}

View File

@ -1,34 +0,0 @@
package main
import (
"errors"
"github.com/loveuer/nf"
"github.com/loveuer/nf/nft/resp"
"log"
)
func main() {
app := nf.New()
api := app.Group("/api")
api.Get("/hello",
auth(),
func(c *nf.Ctx) error {
return resp.Resp403(c, errors.New("in hello"))
},
)
log.Fatal(app.Run(":80"))
}
func auth() nf.HandlerFunc {
return func(c *nf.Ctx) error {
token := c.Query("token")
if token != "zyp" {
return resp.Resp401(c, errors.New("no auth"))
}
return c.Next()
}
}

View File

@ -1,28 +0,0 @@
package main
import (
"github.com/loveuer/nf"
"log"
)
func main() {
app := nf.New()
app.Get("/nice", h1, h2)
log.Fatal(app.Run(":80"))
}
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()
return nil
}
func h2(c *nf.Ctx) error {
return c.JSON(nf.Map{"status": 200, "msg": "hello world"})
}

View File

@ -1,5 +0,0 @@
### 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

View File

@ -1,24 +0,0 @@
package main
import (
"github.com/loveuer/nf"
"log"
)
func main() {
app := nf.New(nf.Config{
DisableRecover: false,
})
app.Get("/hello/:name", func(c *nf.Ctx) error {
name := c.Param("name")
if name == "nf" {
panic("name is nf")
}
return c.JSON("nice")
})
log.Fatal(app.Run("0.0.0.0:80"))
}

View File

@ -1,5 +0,0 @@
### panic test
GET http://127.0.0.1/hello/nf
### if covered?
GET http://127.0.0.1/hello/world

View File

@ -1,36 +0,0 @@
package main
import (
"github.com/loveuer/nf"
"log"
)
func main() {
app := nf.New()
app.Get("/hello", func(c *nf.Ctx) error {
type Req struct {
Name string `query:"name"`
Age int `query:"age"`
Likes []string `query:"likes"`
}
var (
err error
req = new(Req)
rm = make(map[string]interface{})
)
//if err = c.QueryParser(req); err != nil {
// 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, "map": rm})
})
log.Fatal(app.Run("0.0.0.0:80"))
}

View File

@ -1,52 +0,0 @@
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})
}

View File

@ -1,119 +0,0 @@
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
}