update
commit
894dbc1bbf
|
@ -0,0 +1,34 @@
|
|||
# ksrv
|
||||
|
||||
A extended http.Server with logging and panic recovery
|
||||
|
||||
## Examples
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"kumoly.io/core/log"
|
||||
"kumoly.io/lib/ksrv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.PROD = false
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { rw.Write([]byte("ok")) })
|
||||
mux.HandleFunc("/err", func(rw http.ResponseWriter, r *http.Request) { ksrv.Abort(rw, errors.New("small err")) })
|
||||
mux.HandleFunc("/panic", func(rw http.ResponseWriter, r *http.Request) { panic(500) })
|
||||
mux.HandleFunc("/out", func(rw http.ResponseWriter, r *http.Request) { arr := []int{0, 1}; fmt.Print(arr[9]) })
|
||||
log.Info("start")
|
||||
|
||||
err := ksrv.New().Listen("0.0.0.0:8080").Serve()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
|
@ -0,0 +1,10 @@
|
|||
module kumoly.io/lib/ksrv
|
||||
|
||||
go 1.17
|
||||
|
||||
require kumoly.io/core/log v0.1.7
|
||||
|
||||
require (
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
kumoly.io/core/log v0.1.7 h1:eAOitZUYyeivFlqcdNsITZT79vVXCzRckuZozqNwx44=
|
||||
kumoly.io/core/log v0.1.7/go.mod h1:DrKjL3xGEEJ7OC+NHuuv0JSMV7ZqDSw2GiSFFWxilT4=
|
|
@ -0,0 +1,95 @@
|
|||
package ksrv
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"kumoly.io/core/log"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_INFO = `[{{"KMUX"|cyan}}] {{Time}} {{with .Fields}}|{{printf " %3d " .Status|statcol .Status}}| ` +
|
||||
`{{printf "%15s" .IP}} {{printf " %-7s " .Method|methcol .Method}} {{.URL}}{{"\n"}}{{end}}`
|
||||
DEFAULT_ERR = `[{{"KMUX"|cyan}}] {{Time}} {{with .Fields}}|{{printf " %3d " .Status|statcol .Status}}| ` +
|
||||
`{{printf "%15s" .IP}} {{printf " %-7s " .Method|methcol .Method}} {{.URL}}{{end}} {{.Message|red}}{{"\n"}}` +
|
||||
`{{if .Stack}}{{.Stack|redl}}{{end}}`
|
||||
)
|
||||
|
||||
func (k *kserver) SetLogger(l *log.Logger) {
|
||||
k.l = l
|
||||
}
|
||||
|
||||
func (k *kserver) GetLogger() *log.Logger {
|
||||
return k.l
|
||||
}
|
||||
|
||||
// New returns a extended http.Server
|
||||
func New() *kserver {
|
||||
l := log.Sub("KSRV")
|
||||
tmpl := log.NewLogFormater()
|
||||
tmpl.InfoTmplStr = DEFAULT_INFO
|
||||
tmpl.ErrTmplStr = DEFAULT_ERR
|
||||
|
||||
fMap := log.DefaultFuncMap()
|
||||
fMap["statcol"] = func(code int, s string) string {
|
||||
switch {
|
||||
case code >= http.StatusOK && code < http.StatusMultipleChoices:
|
||||
return l.M(s, 97, 42)
|
||||
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
|
||||
return l.M(s, 90, 47)
|
||||
case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
|
||||
return l.M(s, 90, 43)
|
||||
default:
|
||||
return l.M(s, 97, 41)
|
||||
}
|
||||
}
|
||||
fMap["methcol"] = func(method string, s string) string {
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
return l.M(s, 97, 44)
|
||||
case http.MethodPost:
|
||||
return l.M(s, 97, 46)
|
||||
case http.MethodPut:
|
||||
return l.M(s, 90, 43)
|
||||
case http.MethodDelete:
|
||||
return l.M(s, 97, 41)
|
||||
case http.MethodPatch:
|
||||
return l.M(s, 97, 42)
|
||||
case http.MethodHead:
|
||||
return l.M(s, 97, 45)
|
||||
case http.MethodOptions:
|
||||
return l.M(s, 90, 47)
|
||||
default:
|
||||
return l.M(s, 0)
|
||||
}
|
||||
}
|
||||
l.SetTmpl(tmpl, fMap)
|
||||
err := l.Reload()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
k := &kserver{}
|
||||
k.l = l
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *kserver) Middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
rw := &responseWriter{w, 0, ""}
|
||||
defer k.catch(rw, r)
|
||||
next.ServeHTTP(rw, r)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (k *kserver) catch(rw *responseWriter, r *http.Request) {
|
||||
ex := recover()
|
||||
if ex != nil {
|
||||
Abort(rw, ex)
|
||||
k.l.ErrorF(log.H{"Status": rw.StatueCode, "IP": GetIP(r), "Method": r.Method, "URL": r.URL.Path}, ex)
|
||||
} else if rw.StatueCode >= 500 {
|
||||
k.l.ErrorF(log.H{"Status": rw.StatueCode, "IP": GetIP(r), "Method": r.Method, "URL": r.URL.Path}, rw.err)
|
||||
} else {
|
||||
k.l.InfoF(log.H{"Status": rw.StatueCode, "IP": GetIP(r), "Method": r.Method, "URL": r.URL.Path})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package ksrv
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
StatueCode int
|
||||
err string
|
||||
}
|
||||
|
||||
func (w *responseWriter) WriteHeader(statusCode int) {
|
||||
if w.StatueCode != 0 {
|
||||
return
|
||||
}
|
||||
w.StatueCode = statusCode
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (w *responseWriter) Write(body []byte) (int, error) {
|
||||
if w.StatueCode >= 500 {
|
||||
w.err = string(body)
|
||||
}
|
||||
if w.StatueCode == 0 {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
return w.ResponseWriter.Write(body)
|
||||
}
|
||||
|
||||
// Response shorthand for set status code and write body
|
||||
func Response(w http.ResponseWriter, status int, body []byte) (int, error) {
|
||||
w.WriteHeader(status)
|
||||
return w.Write(body)
|
||||
}
|
||||
|
||||
// Abort shorthand for aborting with error, strings and status code could also be passed
|
||||
func Abort(w http.ResponseWriter, errs ...interface{}) (int, error) {
|
||||
code := 500
|
||||
msg := []byte{}
|
||||
for _, err := range errs {
|
||||
switch v := err.(type) {
|
||||
case int:
|
||||
if v >= 100 || v < 600 {
|
||||
code = v
|
||||
}
|
||||
case string:
|
||||
msg = []byte(v)
|
||||
case error:
|
||||
msg = []byte(v.Error())
|
||||
}
|
||||
}
|
||||
if len(msg) == 0 {
|
||||
msg = []byte(strconv.Itoa(code))
|
||||
}
|
||||
|
||||
w.WriteHeader(code)
|
||||
return w.Write(msg)
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package ksrv
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"kumoly.io/core/log"
|
||||
)
|
||||
|
||||
type kserver struct {
|
||||
http.Server
|
||||
l *log.Logger
|
||||
}
|
||||
|
||||
// Listen to addr
|
||||
func (s *kserver) Listen(addr string) *kserver {
|
||||
s.Addr = addr
|
||||
return s
|
||||
}
|
||||
|
||||
// Serve alias to ListenAndServe
|
||||
func (s *kserver) Serve() error {
|
||||
return s.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *kserver) ListenAndServe() error {
|
||||
s.Handler = s.Middleware(s.Handler)
|
||||
return s.Server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *kserver) ListenAndServeTLS(certFile string, keyFile string) error {
|
||||
s.Handler = s.Middleware(s.Handler)
|
||||
return s.Server.ListenAndServeTLS(certFile, keyFile)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"kumoly.io/core/log"
|
||||
"kumoly.io/lib/ksrv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.PROD = false
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { rw.Write([]byte("ok")) })
|
||||
mux.HandleFunc("/err", func(rw http.ResponseWriter, r *http.Request) { ksrv.Abort(rw, errors.New("small err")) })
|
||||
mux.HandleFunc("/panic", func(rw http.ResponseWriter, r *http.Request) { panic(500) })
|
||||
mux.HandleFunc("/out", func(rw http.ResponseWriter, r *http.Request) { arr := []int{0, 1}; fmt.Print(arr[9]) })
|
||||
log.Info("start")
|
||||
|
||||
err := ksrv.New().Listen("0.0.0.0:8080").Serve()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package ksrv
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MatchIPGlob match ip to glob pattern, ex. * 192.168.* 192.* 192.168.51.*2*
|
||||
func MatchIPGlob(ip, pattern string) bool {
|
||||
parts := strings.Split(pattern, ".")
|
||||
seg := strings.Split(ip, ".")
|
||||
for i, part := range parts {
|
||||
|
||||
// normalize pattern to 3 digits
|
||||
switch len(part) {
|
||||
case 1:
|
||||
if part == "*" {
|
||||
part = "***"
|
||||
} else {
|
||||
part = "00" + part
|
||||
}
|
||||
case 2:
|
||||
if strings.HasPrefix(part, "*") {
|
||||
part = "*" + part
|
||||
} else if strings.HasSuffix(part, "*") {
|
||||
part = part + "*"
|
||||
} else {
|
||||
part = "0" + part
|
||||
}
|
||||
}
|
||||
|
||||
// normalize ip to 3 digits
|
||||
switch len(seg[i]) {
|
||||
case 1:
|
||||
seg[i] = "00" + seg[i]
|
||||
case 2:
|
||||
seg[i] = "0" + seg[i]
|
||||
}
|
||||
|
||||
for j := range part {
|
||||
if string(part[j]) == "*" {
|
||||
continue
|
||||
}
|
||||
if part[j] != seg[i][j] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetIP gets the real ip (could still be tricked by proxy)
|
||||
func GetIP(r *http.Request) string {
|
||||
ip := r.Header.Get("X-Real-Ip")
|
||||
if ip == "" {
|
||||
ips := r.Header.Get("X-Forwarded-For")
|
||||
ipArr := strings.Split(ips, ",")
|
||||
ip = strings.Trim(ipArr[len(ipArr)-1], " ")
|
||||
}
|
||||
if ip == "" {
|
||||
var err error
|
||||
ip, _, err = net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
ip = r.RemoteAddr
|
||||
}
|
||||
}
|
||||
return ip
|
||||
}
|
Loading…
Reference in New Issue