Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
9b7c8d9d24 | |||
c13263fe0d | |||
d4fe4e0112 | |||
16541e377c | |||
479c4eef57 | |||
436264117c |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
xtest
|
15
app.go
15
app.go
@ -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 {
|
||||||
@ -176,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 != "/" {
|
||||||
@ -205,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 {
|
||||||
@ -274,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()
|
||||||
}
|
}
|
||||||
|
63
ctx.go
63
ctx.go
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +149,9 @@ func (c *Ctx) Param(key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ctx) SetParam(key, value string) {
|
func (c *Ctx) SetParam(key, value string) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
params := append(*c.params, Param{Key: key, Value: value})
|
params := append(*c.params, Param{Key: key, Value: value})
|
||||||
c.params = ¶ms
|
c.params = ¶ms
|
||||||
}
|
}
|
||||||
@ -241,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())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +271,8 @@ func (c *Ctx) SetHeader(key string, value string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ctx) SendStatus(code int) error {
|
func (c *Ctx) SendStatus(code int) error {
|
||||||
c.writermem.WriteHeader(code)
|
c.Status(code)
|
||||||
|
c.writermem.WriteHeaderNow()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,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 {
|
||||||
@ -295,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
11
go.mod
@ -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
13
go.sum
@ -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
106
internal/sse/sse-encoder.go
Normal 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
24
internal/sse/writer.go
Normal 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}
|
||||||
|
}
|
||||||
|
}
|
@ -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
67
nft/log/default.go
Normal 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
115
nft/log/log.go
Normal 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
21
nft/log/new.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
@ -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
21
util.go
@ -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)
|
||||||
|
}
|
||||||
|
@ -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"
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/loveuer/nf"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := nf.New(nf.Config{})
|
|
||||||
|
|
||||||
app.Get("/ok", func(c *nf.Ctx) error {
|
|
||||||
return c.SendStatus(200)
|
|
||||||
})
|
|
||||||
|
|
||||||
api := app.Group("/api")
|
|
||||||
api.Use(func(c *nf.Ctx) error {
|
|
||||||
c.SetParam("age", "18")
|
|
||||||
return c.Next()
|
|
||||||
})
|
|
||||||
|
|
||||||
api.Get("/:name", func(c *nf.Ctx) error {
|
|
||||||
name := c.Param("name")
|
|
||||||
age := c.Param("age")
|
|
||||||
return c.SendString(name + "@" + age)
|
|
||||||
})
|
|
||||||
|
|
||||||
log.Fatal(app.Run(":80"))
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
### body_limit
|
|
||||||
POST http://127.0.0.1/data
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "zyp",
|
|
||||||
"age": 19,
|
|
||||||
"likes": ["2233"]
|
|
||||||
}
|
|
@ -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"))
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"})
|
|
||||||
}
|
|
@ -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
|
|
@ -1,33 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/loveuer/nf"
|
|
||||||
"github.com/loveuer/nf/nft/resp"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := nf.New(nf.Config{BodyLimit: 10 * 1024 * 1024})
|
|
||||||
|
|
||||||
app.Post("/upload", func(c *nf.Ctx) error {
|
|
||||||
fs, err := c.MultipartForm()
|
|
||||||
if err != nil {
|
|
||||||
return resp.Resp400(c, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
fm := make(map[string][]string)
|
|
||||||
for key := range fs.File {
|
|
||||||
if _, exist := fm[key]; !exist {
|
|
||||||
fm[key] = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
for f := range fs.File[key] {
|
|
||||||
fm[key] = append(fm[key], fs.File[key][f].Filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.Resp200(c, nf.Map{"value": fs.Value, "files": fm})
|
|
||||||
})
|
|
||||||
|
|
||||||
log.Fatal(app.Run(":13322"))
|
|
||||||
}
|
|
@ -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"))
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
### panic test
|
|
||||||
GET http://127.0.0.1/hello/nf
|
|
||||||
|
|
||||||
### if covered?
|
|
||||||
GET http://127.0.0.1/hello/world
|
|
@ -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"))
|
|
||||||
}
|
|
@ -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})
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
Reference in New Issue
Block a user