Compare commits

..

No commits in common. "master" and "v0.0.9" have entirely different histories.

43 changed files with 236 additions and 1882 deletions

View File

@ -4,13 +4,8 @@ import (
"context"
"embed"
"flag"
httpkg "net/http"
"time"
"git.nobla.cn/golang/kos/entry/cli"
"git.nobla.cn/golang/kos/entry/http"
"git.nobla.cn/golang/kos"
"git.nspix.com/golang/kos"
)
//go:embed web
@ -19,39 +14,8 @@ var webDir embed.FS
type subServer struct {
}
type users struct {
Name string `json:"name"`
Age int `json:"age"`
Tags []string `json:"tags"`
}
func (s *subServer) Start(ctx context.Context) (err error) {
kos.Http().Root("/web", httpkg.FS(webDir))
kos.Http().Handle(httpkg.MethodGet, "/hello", func(ctx *http.Context) (err error) {
return ctx.Success("Hello World")
})
kos.Command().Handle("/test", "test command", func(ctx *cli.Context) (err error) {
return ctx.Success([][]string{
{"NAME", "AGE"},
{"SSS", "aaa"},
})
})
kos.Command().Handle("/users", "test command", func(ctx *cli.Context) (err error) {
return ctx.Success([]*users{
{Name: "Zhan", Age: 10, Tags: []string{"a", "b"}},
{Name: "Lisi", Age: 15, Tags: []string{"c", "d"}},
})
})
kos.Command().Handle("/ctx", "context test", func(ctx *cli.Context) (err error) {
select {
case <-ctx.Context().Done():
case <-time.After(time.Second * 2):
}
return ctx.Success("OK")
})
kos.Http().Embed("/ui/web", "web", webDir)
return
}
@ -62,8 +26,9 @@ func (s *subServer) Stop() (err error) {
func main() {
flag.Parse()
svr := kos.Init(
kos.WithName("git.nobla.cn/golang/test", "0.0.1"),
kos.WithName("git.nspix.com/golang/test", "0.0.1"),
kos.WithServer(&subServer{}),
kos.WithDirectHttp(),
)
svr.Run()
}

View File

@ -6,7 +6,7 @@
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<h1>Hello</h1>

View File

@ -5,7 +5,6 @@ const (
EnvAppVersion = "VOX_VERSION"
EnvAppPort = "VOX_PORT"
EnvAppAddress = "VOX_ADDRESS"
EnvAppDebug = "VOX_DEBUG"
)
const (

View File

@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"git.nobla.cn/golang/kos/util/env"
"github.com/peterh/liner"
"io"
"math"
@ -142,7 +141,7 @@ func (client *Client) completer(str string) (ss []string) {
)
ss = make([]string, 0)
seq = client.getSequence()
if err = writeFrame(client.conn, newFrame(PacketTypeCompleter, FlagComplete, seq, client.Timeout, []byte(str))); err != nil {
if err = writeFrame(client.conn, newFrame(PacketTypeCompleter, FlagComplete, seq, []byte(str))); err != nil {
return
}
select {
@ -167,10 +166,10 @@ func (client *Client) Execute(s string) (err error) {
}()
go client.ioLoop(client.conn)
seq = client.getSequence()
if err = writeFrame(client.conn, newFrame(PacketTypeCommand, FlagComplete, seq, client.Timeout, []byte(s))); err != nil {
if err = writeFrame(client.conn, newFrame(PacketTypeCommand, FlagComplete, seq, []byte(s))); err != nil {
return err
}
client.waitResponse(seq, client.Timeout)
client.waitResponse(seq, time.Second*30)
return
}
@ -186,7 +185,7 @@ func (client *Client) Shell() (err error) {
defer func() {
_ = client.Close()
}()
if err = writeFrame(client.conn, newFrame(PacketTypeHandshake, FlagComplete, client.getSequence(), client.Timeout, nil)); err != nil {
if err = writeFrame(client.conn, newFrame(PacketTypeHandshake, FlagComplete, client.getSequence(), nil)); err != nil {
return
}
go client.ioLoop(client.conn)
@ -217,7 +216,7 @@ func (client *Client) Shell() (err error) {
continue
}
seq = client.getSequence()
if err = writeFrame(client.conn, newFrame(PacketTypeCommand, FlagComplete, seq, client.Timeout, []byte(line))); err != nil {
if err = writeFrame(client.conn, newFrame(PacketTypeCommand, FlagComplete, seq, []byte(line))); err != nil {
break
}
client.liner.AppendHistory(line)
@ -241,22 +240,14 @@ func (client *Client) Close() (err error) {
}
func NewClient(ctx context.Context, addr string) *Client {
var (
err error
timeout time.Duration
)
if ctx == nil {
ctx = context.Background()
}
duration := env.Get("VOX_TIMEOUT", "30s")
if timeout, err = time.ParseDuration(duration); err != nil {
timeout = time.Second * 30
}
return &Client{
ctx: ctx,
address: addr,
name: filepath.Base(os.Args[0]),
Timeout: timeout,
Timeout: time.Second * 30,
liner: liner.NewLiner(),
readyChan: make(chan struct{}, 1),
exitChan: make(chan struct{}),

View File

@ -1,32 +1,25 @@
package cli
import (
"context"
"fmt"
"io"
"math"
"sync"
)
type Context struct {
Id int64
seq uint16
ctx context.Context
wc io.WriteCloser
params map[string]string
locker sync.RWMutex
variables map[string]any
args []string
Id int64
seq uint16
wc io.WriteCloser
params map[string]string
args []string
}
func (ctx *Context) reset(id int64, wc io.WriteCloser) {
ctx.Id = id
ctx.wc = wc
ctx.seq = 0
ctx.ctx = context.Background()
ctx.args = make([]string, 0)
ctx.params = make(map[string]string)
ctx.variables = make(map[string]any)
}
func (ctx *Context) setArgs(args []string) {
@ -41,14 +34,6 @@ func (ctx *Context) Bind(v any) (err error) {
return
}
func (ctx *Context) setContext(c context.Context) {
ctx.ctx = c
}
func (ctx *Context) Context() context.Context {
return ctx.ctx
}
func (ctx *Context) Argument(index int) string {
if index >= len(ctx.args) || index < 0 {
return ""
@ -63,22 +48,6 @@ func (ctx *Context) Param(s string) string {
return ""
}
func (ctx *Context) SetValue(name string, value any) {
ctx.locker.Lock()
if ctx.variables == nil {
ctx.variables = make(map[string]any)
}
ctx.variables[name] = value
ctx.locker.Unlock()
}
func (ctx *Context) GetValue(name string) (val any, ok bool) {
ctx.locker.RLock()
defer ctx.locker.RUnlock()
val, ok = ctx.variables[name]
return
}
func (ctx *Context) Success(v any) (err error) {
return ctx.send(responsePayload{Type: PacketTypeCommand, Data: v})
}
@ -124,12 +93,12 @@ __END:
chunkSize := math.MaxInt16 - 1
n := len(buf) / chunkSize
for i := 0; i < n; i++ {
if err = writeFrame(ctx.wc, newFrame(res.Type, FlagPortion, ctx.seq, 0, buf[offset:chunkSize+offset])); err != nil {
if err = writeFrame(ctx.wc, newFrame(res.Type, FlagPortion, ctx.seq, buf[offset:chunkSize+offset])); err != nil {
return
}
offset += chunkSize
}
err = writeFrame(ctx.wc, newFrame(res.Type, FlagComplete, ctx.seq, 0, buf[offset:]))
err = writeFrame(ctx.wc, newFrame(res.Type, FlagComplete, ctx.seq, buf[offset:]))
return
}

View File

@ -27,7 +27,6 @@ type (
Seq uint16 `json:"seq"`
Data []byte `json:"data"`
Error string `json:"error"`
Timeout int64 `json:"timeout"`
Timestamp int64 `json:"timestamp"`
}
)
@ -56,9 +55,6 @@ func readFrame(r io.Reader) (frame *Frame, err error) {
if err = binary.Read(r, binary.LittleEndian, &frame.Seq); err != nil {
return
}
if err = binary.Read(r, binary.LittleEndian, &frame.Timeout); err != nil {
return
}
if err = binary.Read(r, binary.LittleEndian, &frame.Timestamp); err != nil {
return
}
@ -120,9 +116,6 @@ func writeFrame(w io.Writer, frame *Frame) (err error) {
if err = binary.Write(w, binary.LittleEndian, frame.Seq); err != nil {
return
}
if err = binary.Write(w, binary.LittleEndian, frame.Timeout); err != nil {
return
}
if err = binary.Write(w, binary.LittleEndian, frame.Timestamp); err != nil {
return
}
@ -149,14 +142,13 @@ func writeFrame(w io.Writer, frame *Frame) (err error) {
return
}
func newFrame(t, f byte, seq uint16, timeout time.Duration, data []byte) *Frame {
func newFrame(t, f byte, seq uint16, data []byte) *Frame {
return &Frame{
Feature: Feature,
Type: t,
Flag: f,
Seq: seq,
Data: data,
Timeout: int64(timeout),
Timestamp: time.Now().Unix(),
}
}

View File

@ -97,9 +97,6 @@ func (r *Router) Handle(path string, command Command) {
name = path
path = ""
}
if name == "-" {
name = "app"
}
children := r.getChildren(name)
if children == nil {
children = newRouter(name)

View File

@ -4,8 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"git.nobla.cn/golang/kos/util/arrays"
"git.nobla.cn/golang/kos/util/pool"
"git.nspix.com/golang/kos/util/pool"
"github.com/mattn/go-runewidth"
"reflect"
"strconv"
@ -171,7 +170,7 @@ func serializeArray(val []any) (buf []byte, err error) {
if rv.Kind() == reflect.Array || rv.Kind() == reflect.Slice {
row := make([]any, 0, rv.Len())
for i := 0; i < rv.Len(); i++ {
if isNormalKind(rv.Index(i).Kind()) || rv.Index(i).Interface() == nil {
if isNormalKind(rv.Index(i).Elem().Kind()) || rv.Index(i).Interface() == nil {
row = append(row, rv.Index(i).Interface())
} else {
goto __END
@ -185,7 +184,6 @@ func serializeArray(val []any) (buf []byte, err error) {
}
if isStructElement {
vs = make([][]any, 0, len(val))
indexes := make([]int, 0)
for i, v := range val {
rv := reflect.Indirect(reflect.ValueOf(v))
if rv.Kind() == reflect.Struct {
@ -193,26 +191,16 @@ func serializeArray(val []any) (buf []byte, err error) {
row := make([]any, 0, rv.Type().NumField())
for j := 0; j < rv.Type().NumField(); j++ {
st := rv.Type().Field(j).Tag
if columnName, ok = st.Lookup("kos"); !ok {
if columnName, ok = st.Lookup("name"); !ok {
columnName = strings.ToUpper(rv.Type().Field(j).Name)
} else {
if columnName == "-" {
continue
}
}
if !rv.Type().Field(j).IsExported() {
continue
}
indexes = append(indexes, j)
row = append(row, columnName)
}
vs = append(vs, row)
}
row := make([]any, 0, rv.Type().NumField())
for j := 0; j < rv.Type().NumField(); j++ {
if arrays.Exists(j, indexes) {
row = append(row, rv.Field(j).Interface())
}
row = append(row, rv.Field(j).Interface())
}
vs = append(vs, row)
} else {

View File

@ -10,10 +10,9 @@ import (
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"git.nobla.cn/golang/kos/util/env"
"git.nspix.com/golang/kos/util/env"
"github.com/sourcegraph/conc"
)
@ -30,7 +29,6 @@ type Server struct {
middleware []Middleware
router *Router
l net.Listener
exitFlag int32
}
func (svr *Server) applyContext() *Context {
@ -46,8 +44,9 @@ func (svr *Server) releaseContext(ctx *Context) {
ctxPool.Put(ctx)
}
func (svr *Server) execute(ctx *Context, frame *Frame) (err error) {
func (svr *Server) handle(ctx *Context, frame *Frame) {
var (
err error
params map[string]string
tokens []string
args []string
@ -55,15 +54,6 @@ func (svr *Server) execute(ctx *Context, frame *Frame) (err error) {
)
cmd := string(frame.Data)
tokens = strings.Fields(cmd)
if frame.Timeout > 0 {
childCtx, cancelFunc := context.WithTimeout(svr.ctx, time.Duration(frame.Timeout))
ctx.setContext(childCtx)
defer func() {
cancelFunc()
}()
} else {
ctx.setContext(svr.ctx)
}
if r, args, err = svr.router.Lookup(tokens); err != nil {
if errors.Is(err, ErrNotFound) {
err = ctx.Error(errNotFound, fmt.Sprintf("Command %s not found", cmd))
@ -85,7 +75,6 @@ func (svr *Server) execute(ctx *Context, frame *Frame) (err error) {
ctx.setParam(params)
err = r.command.Handle(ctx)
}
return
}
func (svr *Server) nextSequence() int64 {
@ -141,9 +130,7 @@ func (svr *Server) process(conn net.Conn) {
break
}
case PacketTypeCommand:
if err = svr.execute(ctx, frame); err != nil {
break
}
svr.handle(ctx, frame)
default:
break
}
@ -202,17 +189,11 @@ func (svr *Server) Serve(l net.Listener) (err error) {
return ctx.Success(svr.router.String())
})
svr.serve()
atomic.StoreInt32(&svr.exitFlag, 0)
return
}
func (svr *Server) Shutdown() (err error) {
if !atomic.CompareAndSwapInt32(&svr.exitFlag, 0, 1) {
return
}
if svr.l != nil {
err = svr.l.Close()
}
err = svr.l.Close()
svr.ctxMap.Range(func(key, value any) bool {
if ctx, ok := value.(*Context); ok {
err = ctx.Close()

View File

@ -28,27 +28,16 @@ type Context struct {
req *http.Request
res http.ResponseWriter
params map[string]string
user *Userinfo
statusCode int
}
func (ctx *Context) reset(req *http.Request, res http.ResponseWriter, ps map[string]string) {
ctx.statusCode = http.StatusOK
ctx.user = nil
ctx.req, ctx.res, ctx.params = req, res, ps
}
func (ctx *Context) User() *Userinfo {
return ctx.user
}
func (ctx *Context) SetUser(ui *Userinfo) {
ctx.user = ui
}
func (ctx *Context) RealIp() string {
var (
s string
pos int
ipaddr string
)
@ -59,18 +48,10 @@ func (ctx *Context) RealIp() string {
}
ipaddr, _, _ = net.SplitHostPort(ctx.Request().RemoteAddr)
__end:
for {
if pos = strings.IndexByte(ipaddr, ','); pos > -1 {
s = strings.TrimSpace(ipaddr[:pos])
if netAddr := net.ParseIP(s); netAddr != nil && !netAddr.IsPrivate() {
return s
}
ipaddr = ipaddr[pos+1:]
} else {
break
}
if pos = strings.LastIndexByte(ipaddr, ','); pos > -1 {
ipaddr = ipaddr[:pos]
}
return strings.TrimSpace(ipaddr)
return ipaddr
}
func (ctx *Context) Request() *http.Request {
@ -93,36 +74,23 @@ func (ctx *Context) Bind(v any) (err error) {
}
func (ctx *Context) Query(k string) string {
qs := ctx.Request().URL.Query()
if qs == nil {
return ""
}
return qs.Get(k)
}
func (ctx *Context) Form(k string) string {
return ctx.Request().FormValue(k)
}
func (ctx *Context) Param(k string) string {
var (
ok bool
s string
v string
)
if s, ok = ctx.params[k]; ok {
return s
if v, ok = ctx.params[k]; ok {
return v
}
s = ctx.Query(k)
if s == "" {
s = ctx.Form(k)
}
return s
return ctx.Request().FormValue(k)
}
func (ctx *Context) json(res responsePayload) (err error) {
ctx.Response().Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(ctx.Response())
encoder.SetEscapeHTML(false)
if strings.HasPrefix(ctx.Request().Header.Get("User-Agent"), "curl") {
encoder.SetIndent("", "\t")
}
@ -143,7 +111,7 @@ func (ctx *Context) Error(code int, reason string) (err error) {
func (ctx *Context) Redirect(url string, code int) {
if code != http.StatusFound && code != http.StatusMovedPermanently {
code = http.StatusFound
code = http.StatusMovedPermanently
}
http.Redirect(ctx.Response(), ctx.Request(), url, code)
}
@ -152,44 +120,6 @@ func (ctx *Context) SetCookie(cookie *http.Cookie) {
http.SetCookie(ctx.Response(), cookie)
}
func (ctx *Context) GetCookie(name string) (*http.Cookie, error) {
return ctx.Request().Cookie(name)
}
func (ctx *Context) DeleteCookie(name string) {
cookie, err := ctx.GetCookie(name)
if err == nil {
cookie.MaxAge = -1
ctx.SetCookie(cookie)
}
}
func (ctx *Context) SetCookieValue(name, value, domain string) {
if domain == "" {
domain = ctx.Request().URL.Hostname()
}
if name == "" || value == "" {
return
}
ctx.SetCookie(&http.Cookie{
Name: name,
Value: value,
Path: "/",
Domain: domain,
})
}
func (ctx *Context) GetCookieValue(name string) string {
if name == "" {
return ""
}
cookie, err := ctx.GetCookie(name)
if err == nil {
return cookie.Value
}
return ""
}
func (ctx *Context) SendFile(filename string) (err error) {
var (
fi os.FileInfo

View File

@ -1,28 +0,0 @@
package http
import (
"net"
"strings"
"testing"
)
func TestContext_RealIp(t *testing.T) {
var (
pos int
s string
)
ipaddr := "192.168.6.76, 192.168.6.76, 116.24.65.173,192.168.6.76"
for {
if pos = strings.IndexByte(ipaddr, ','); pos > -1 {
s = strings.TrimSpace(ipaddr[:pos])
if netip := net.ParseIP(s); netip != nil && !netip.IsPrivate() {
t.Log(s)
break
}
ipaddr = ipaddr[pos+1:]
} else {
break
}
}
t.Log(ipaddr)
}

View File

@ -1,21 +1,13 @@
package http
import "git.nobla.cn/golang/kos/pkg/types"
const (
ErrAccessDenied = types.ErrAccessDenied //拒绝访问
ErrPermissionDenied = types.ErrPermissionDenied //没有权限
ErrIllegalRequest = types.ErrIllegalRequest //非法请求
ErrInvalidPayload = types.ErrInvalidPayload //请求数据无效
ErrResourceCreate = types.ErrResourceCreate //资源创建失败
ErrResourceUpdate = types.ErrResourceUpdate //资源更新失败
ErrResourceDelete = types.ErrResourceDelete //资源删除失败
ErrResourceNotFound = types.ErrResourceNotFound //资源未找到
ErrResourceEmpty = types.ErrResourceEmpty //资源为空
ErrResourceExpired = types.ErrResourceExpired //资源已失效
ErrResourceUnavailable = types.ErrResourceUnavailable //资源无法使用
ErrResourceLocked = types.ErrResourceLocked //资源已被锁定
ErrServerUnreachable = types.ErrServerUnreachable //服务不可用
ErrTemporaryUnavailable = types.ErrTemporaryUnavailable //临时性失败
ErrFatal = types.ErrFatal //致命错误
ErrAccessDenied = 4003 //拒绝访问
ErrPermissionDenied = 4004 //没有权限
ErrInvalidRequest = 4005 //请求无效或不合法
ErrInvalidPayload = 4006 //请求数据无效
ErrResourceCreate = 8001 //资源创建失败
ErrResourceUpdate = 8002 //资源更新失败
ErrResourceDelete = 8003 //资源删除失败
ErrResourceNotFound = 8004 //资源未找到
ErrTemporaryUnavailable = 8006 //临时性不可用
)

View File

@ -3,19 +3,13 @@ package http
import (
"io/fs"
"net/http"
"os"
"path"
"strings"
"time"
)
type (
FS struct {
fs http.FileSystem
modtime time.Time
prefix string
indexFile string
denyDirectory bool
fs http.FileSystem
modtime time.Time
}
File struct {
@ -80,54 +74,11 @@ func (file *File) Stat() (fs.FileInfo, error) {
return newFileInfo(fi, file.modtime), nil
}
func (fs *FS) DenyAccessDirectory() {
fs.denyDirectory = true
}
func (fs *FS) SetPrefix(prefix string) {
if prefix != "" {
if prefix[0] != '/' {
prefix = "/" + prefix
}
prefix = strings.TrimRight(prefix, "/")
fs.prefix = prefix
}
}
func (fs *FS) SetIndexFile(indexFile string) {
fs.indexFile = indexFile
}
func (fs *FS) Open(name string) (http.File, error) {
var (
needRetry bool
)
if name == "" || name == "/" {
needRetry = true
}
if fs.prefix != "" {
if !strings.HasPrefix(name, fs.prefix) {
name = path.Join(fs.prefix, name)
}
}
fp, err := fs.fs.Open(name)
if err != nil {
return nil, err
}
if fs.denyDirectory {
state, err := fp.Stat()
if err != nil {
return nil, err
}
if state.IsDir() {
if needRetry {
if fs.indexFile != "" {
return fs.Open(path.Join(name, fs.indexFile))
}
}
return nil, os.ErrPermission
}
}
return &File{fp: fp, modtime: fs.modtime}, nil
}

View File

@ -3,13 +3,12 @@ package http
import (
"context"
"embed"
"git.nobla.cn/golang/kos/entry/http/router"
"git.nspix.com/golang/kos/entry/http/router"
"net"
"net/http"
"path"
"strings"
"sync"
"sync/atomic"
"time"
)
@ -18,16 +17,12 @@ var (
)
type Server struct {
ctx context.Context
serve *http.Server
router *router.Router
middleware []Middleware
uptime time.Time
enableDocumentRoot bool
fileSystem http.FileSystem
beforeRequests []HandleFunc
anyRequests map[string]http.Handler
exitFlag int32
ctx context.Context
serve *http.Server
router *router.Router
middleware []Middleware
uptime time.Time
anyRequests map[string]http.Handler
}
func (svr *Server) applyContext() *Context {
@ -46,23 +41,14 @@ func (svr *Server) releaseContext(ctx *Context) {
func (svr *Server) wrapHandle(cb HandleFunc, middleware ...Middleware) router.Handle {
return func(writer http.ResponseWriter, request *http.Request, params router.Params) {
ctx := svr.applyContext()
ps := make(map[string]string, 4)
defer func() {
svr.releaseContext(ctx)
ps = make(map[string]string, 0)
}()
ps := make(map[string]string)
for _, v := range params {
ps[v.Key] = v.Value
}
ctx.reset(request, writer, ps)
if len(svr.beforeRequests) > 0 {
for i := len(svr.beforeRequests) - 1; i >= 0; i-- {
if err := svr.beforeRequests[i](ctx); err != nil {
ctx.Status(http.StatusServiceUnavailable)
return
}
}
}
for i := len(svr.middleware) - 1; i >= 0; i-- {
cb = svr.middleware[i](cb)
}
@ -75,10 +61,6 @@ func (svr *Server) wrapHandle(cb HandleFunc, middleware ...Middleware) router.Ha
}
}
func (svr *Server) Before(cb ...HandleFunc) {
svr.beforeRequests = append(svr.beforeRequests, cb...)
}
func (svr *Server) Use(middleware ...Middleware) {
svr.middleware = append(svr.middleware, middleware...)
}
@ -109,15 +91,6 @@ func (svr *Server) Group(prefix string, routes []Route, middleware ...Middleware
}
}
func (svr *Server) Root(prefix string, fs http.FileSystem) {
svr.enableDocumentRoot = true
s := newFS(svr.uptime, fs)
s.SetPrefix(prefix)
s.DenyAccessDirectory()
s.SetIndexFile("/index.html")
svr.fileSystem = s
}
func (svr *Server) Embed(prefix string, root string, embedFs embed.FS) {
routePath := prefix
if !strings.HasSuffix(routePath, "/*filepath") {
@ -186,24 +159,12 @@ func (svr *Server) handleRequest(res http.ResponseWriter, req *http.Request) {
}
func (svr *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
var (
err error
file http.File
)
for prefix, handle := range svr.anyRequests {
if strings.HasPrefix(request.URL.Path, prefix) {
handle.ServeHTTP(writer, request)
return
}
}
if svr.enableDocumentRoot && request.Method == http.MethodGet {
uri := path.Clean(request.URL.Path)
if file, err = svr.fileSystem.Open(uri); err == nil {
http.ServeContent(writer, request, path.Base(uri), svr.uptime, file)
err = file.Close()
return
}
}
switch request.Method {
case http.MethodOptions:
svr.handleOption(writer, request)
@ -218,14 +179,10 @@ func (svr *Server) Serve(l net.Listener) (err error) {
}
svr.router.NotFound = NotFound{}
svr.router.MethodNotAllowed = NotAllowed{}
atomic.StoreInt32(&svr.exitFlag, 0)
return svr.serve.Serve(l)
}
func (svr *Server) Shutdown() (err error) {
if !atomic.CompareAndSwapInt32(&svr.exitFlag, 0, 1) {
return
}
if svr.serve != nil {
err = svr.serve.Shutdown(svr.ctx)
}
@ -234,12 +191,11 @@ func (svr *Server) Shutdown() (err error) {
func New(ctx context.Context) *Server {
svr := &Server{
ctx: ctx,
uptime: time.Now(),
router: router.New(),
beforeRequests: make([]HandleFunc, 0, 10),
anyRequests: make(map[string]http.Handler),
middleware: make([]Middleware, 0, 10),
ctx: ctx,
uptime: time.Now(),
router: router.New(),
anyRequests: make(map[string]http.Handler),
middleware: make([]Middleware, 0, 10),
}
return svr
}

View File

@ -1,30 +0,0 @@
package http
type Userinfo struct {
ID string
Name string
variables map[string]string
}
func (ui *Userinfo) Set(k, v string) {
if ui.variables == nil {
ui.variables = make(map[string]string)
}
ui.variables[k] = v
}
func (ui *Userinfo) Get(k string) string {
if ui.variables == nil {
return ""
}
return ui.variables[k]
}
func (ui *Userinfo) Reset(id, name string) {
ui.ID = id
ui.Name = name
// clear the variables
for k, _ := range ui.variables {
delete(ui.variables, k)
}
}

View File

@ -1,12 +0,0 @@
package http
import "testing"
func TestUserinfo_Set(t *testing.T) {
ui := &Userinfo{}
ui.Set("name", "xxx")
ui.Set("lost", "xxx")
if ui.Get("lost") != "xxx" {
t.Error("error")
}
}

2
go.mod
View File

@ -1,4 +1,4 @@
module git.nobla.cn/golang/kos
module git.nspix.com/golang/kos
go 1.20

View File

@ -1,83 +1,48 @@
package kos
import (
"git.nobla.cn/golang/kos/entry/cli"
"git.nobla.cn/golang/kos/entry/http"
_ "git.nobla.cn/golang/kos/pkg/request"
_ "git.nobla.cn/golang/kos/util/arrays"
_ "git.nobla.cn/golang/kos/util/bs"
_ "git.nobla.cn/golang/kos/util/fetch"
_ "git.nobla.cn/golang/kos/util/humanize"
_ "git.nobla.cn/golang/kos/util/random"
_ "git.nobla.cn/golang/kos/util/reflection"
_ "git.nobla.cn/golang/kos/util/sys"
"git.nspix.com/golang/kos/entry/cli"
"git.nspix.com/golang/kos/entry/http"
_ "git.nspix.com/golang/kos/pkg/request"
_ "git.nspix.com/golang/kos/util/bs"
_ "git.nspix.com/golang/kos/util/fetch"
_ "git.nspix.com/golang/kos/util/random"
_ "git.nspix.com/golang/kos/util/reflection"
"sync"
)
var (
once sync.Once
app Application
std *application
)
func initialization(cbs ...Option) {
func initApplication(cbs ...Option) {
once.Do(func() {
app = New(cbs...)
std = New(cbs...)
})
}
func Init(cbs ...Option) Application {
initialization(cbs...)
return app
}
func Name() string {
initialization()
return app.Info().Name
}
func ShortName() string {
initialization()
if entry, ok := app.(*application); ok {
return entry.opts.ShortName()
}
return app.Info().Name
}
func Version() string {
initialization()
return app.Info().Version
}
func Debug(args ...any) bool {
initialization()
if entry, ok := app.(*application); ok {
if len(args) <= 0 {
return entry.opts.EnableDebug
}
if b, ok := args[0].(bool); ok {
entry.opts.EnableDebug = b
}
return entry.opts.EnableDebug
}
return false
func Init(cbs ...Option) *application {
initApplication(cbs...)
return std
}
func Node() *Info {
initialization()
return app.Info()
initApplication()
return std.Info()
}
func Http() *http.Server {
initialization()
return app.Http()
initApplication()
return std.Http()
}
func Command() *cli.Server {
initialization()
return app.Command()
initApplication()
return std.Command()
}
func Handle(method string, cb HandleFunc) {
initialization()
app.Handle(method, cb)
initApplication()
std.Handle(method, cb)
}

View File

@ -2,27 +2,25 @@ package kos
import (
"context"
"git.nobla.cn/golang/kos/util/env"
"git.nobla.cn/golang/kos/util/ip"
"git.nobla.cn/golang/kos/util/sys"
"git.nspix.com/golang/kos/util/env"
"git.nspix.com/golang/kos/util/ip"
"git.nspix.com/golang/kos/util/sys"
"os"
"strconv"
"strings"
"syscall"
)
type (
Options struct {
Name string //名称
Version string //版本号
Address string //绑定地址
Port int //端口
Name string
Version string
Address string
Port int
EnableDebug bool //开启调试模式
DisableGateway bool //禁用HTTP和COMMAND入口
DisableHttp bool //禁用HTTP入口
EnableDirectHttp bool //启用HTTP直连模式
DisableCommand bool //禁用COMMAND入口
EnableDirectCommand bool //启用COMMAND直连模式
DisableCommand bool //禁用命令行入口
EnableDirectCommand bool //启用命令行直连模式
DisableStateApi bool //禁用系统状态接口
Metadata map[string]string //原数据
Context context.Context
@ -32,12 +30,6 @@ type (
}
Option func(o *Options)
HandleOptions struct {
description string
}
HandleOption func(o *HandleOptions)
)
func (o *Options) ShortName() string {
@ -52,12 +44,6 @@ func (o *Options) ShortName() string {
return o.shortName
}
func WithHandleDescription(s string) HandleOption {
return func(o *HandleOptions) {
o.description = s
}
}
func WithName(name string, version string) Option {
return func(o *Options) {
o.Name = name
@ -65,12 +51,6 @@ func WithName(name string, version string) Option {
}
}
func WithoutGateway() Option {
return func(o *Options) {
o.DisableGateway = true
}
}
func WithPort(port int) Option {
return func(o *Options) {
o.Port = port
@ -103,7 +83,7 @@ func WithDirectCommand() Option {
}
}
func NewOptions(cbs ...Option) *Options {
func NewOptions() *Options {
opts := &Options{
Name: env.Get(EnvAppName, sys.Hostname()),
Version: env.Get(EnvAppVersion, "0.0.1"),
@ -113,17 +93,5 @@ func NewOptions(cbs ...Option) *Options {
}
opts.Port = int(env.Integer(18080, EnvAppPort, "HTTP_PORT", "KOS_PORT"))
opts.Address = env.Getter(ip.Internal(), EnvAppAddress, "KOS_ADDRESS")
opts.EnableDebug, _ = strconv.ParseBool(env.Getter("false", EnvAppDebug, "KOS_DEBUG"))
for _, cb := range cbs {
cb(opts)
}
return opts
}
func newHandleOptions(cbs ...HandleOption) *HandleOptions {
opts := &HandleOptions{}
for _, cb := range cbs {
cb(opts)
}
return opts
}

10
pkg/cache/cache.go vendored
View File

@ -6,13 +6,13 @@ import (
)
type (
LoadFunc func(ctx context.Context) ([]byte, error)
LoadFunc func(ctx context.Context) (any, error)
)
type Cache interface {
Set(ctx context.Context, key string, buf []byte)
SetEx(ctx context.Context, key string, buf []byte, expire time.Duration)
Get(ctx context.Context, key string) (buf []byte, ok bool)
Try(ctx context.Context, key string, cb LoadFunc) (buf []byte, err error)
Set(ctx context.Context, key string, value any)
SetEx(ctx context.Context, key string, value any, expire time.Duration)
Get(ctx context.Context, key string) (value any, ok bool)
Try(ctx context.Context, key string, cb LoadFunc) (value any, err error)
Del(ctx context.Context, key string)
}

49
pkg/cache/instance.go vendored
View File

@ -2,8 +2,6 @@ package cache
import (
"context"
"encoding/json"
"os"
"time"
)
@ -23,57 +21,22 @@ func GetCache() Cache {
return std
}
// Set 设置缓存数据
func Set(ctx context.Context, key string, buf []byte) {
std.Set(ctx, key, buf)
func Set(ctx context.Context, key string, value any) {
std.Set(ctx, key, value)
}
// SetEx 设置一个有效时间的缓存数据
func SetEx(ctx context.Context, key string, buf []byte, expire time.Duration) {
std.SetEx(ctx, key, buf, expire)
func SetEx(ctx context.Context, key string, value any, expire time.Duration) {
std.SetEx(ctx, key, value, expire)
}
// Try 尝试获取缓存数据,获取不到就设置
func Try(ctx context.Context, key string, cb LoadFunc) (buf []byte, err error) {
func Try(ctx context.Context, key string, cb LoadFunc) (value any, err error) {
return std.Try(ctx, key, cb)
}
// Get 获取缓存数据
func Get(ctx context.Context, key string) (buf []byte, ok bool) {
func Get(ctx context.Context, key string) (value any, ok bool) {
return std.Get(ctx, key)
}
// Del 删除缓存数据
func Del(ctx context.Context, key string) {
std.Del(ctx, key)
}
// Store 存储缓存数据
func Store(ctx context.Context, key string, val any) (err error) {
return StoreEx(ctx, key, val, 0)
}
// StoreEx 存储缓存数据
func StoreEx(ctx context.Context, key string, val any, expire time.Duration) (err error) {
var (
buf []byte
)
if buf, err = json.Marshal(val); err != nil {
return
}
SetEx(ctx, key, buf, expire)
return
}
// Load 加载指定的缓存数据
func Load(ctx context.Context, key string, val any) (err error) {
var (
ok bool
buf []byte
)
if buf, ok = Get(ctx, key); !ok {
return os.ErrNotExist
}
err = json.Unmarshal(buf, val)
return
}

30
pkg/cache/memcache.go vendored
View File

@ -2,7 +2,7 @@ package cache
import (
"context"
"git.nobla.cn/golang/kos/util/env"
"git.nspix.com/golang/kos/util/env"
"github.com/patrickmn/go-cache"
"os"
"time"
@ -30,38 +30,32 @@ type MemCache struct {
engine *cache.Cache
}
func (cache *MemCache) Try(ctx context.Context, key string, cb LoadFunc) (buf []byte, err error) {
func (cache *MemCache) Try(ctx context.Context, key string, cb LoadFunc) (value any, err error) {
var (
ok bool
)
if buf, ok = cache.Get(ctx, key); ok {
return buf, nil
if value, ok = cache.engine.Get(key); ok {
return value, nil
}
if cb == nil {
return nil, os.ErrNotExist
}
if buf, err = cb(ctx); err == nil {
cache.Set(ctx, key, buf)
if value, err = cb(ctx); err == nil {
cache.engine.Set(key, value, 0)
}
return
}
func (cache *MemCache) Set(ctx context.Context, key string, buf []byte) {
cache.engine.Set(key, buf, 0)
func (cache *MemCache) Set(ctx context.Context, key string, value any) {
cache.engine.Set(key, value, 0)
}
func (cache *MemCache) SetEx(ctx context.Context, key string, buf []byte, expire time.Duration) {
cache.engine.Set(key, buf, expire)
func (cache *MemCache) SetEx(ctx context.Context, key string, value any, expire time.Duration) {
cache.engine.Set(key, value, expire)
}
func (cache *MemCache) Get(ctx context.Context, key string) (buf []byte, ok bool) {
var (
val any
)
if val, ok = cache.engine.Get(key); ok {
buf, ok = val.([]byte)
}
return
func (cache *MemCache) Get(ctx context.Context, key string) (value any, ok bool) {
return cache.engine.Get(key)
}
func (cache *MemCache) Del(ctx context.Context, key string) {

View File

@ -2,7 +2,7 @@ package log
import (
"fmt"
"git.nobla.cn/golang/kos/util/env"
"git.nspix.com/golang/kos/util/env"
"strconv"
"time"
)

View File

@ -1,13 +1,10 @@
package request
import (
"crypto/tls"
"io"
"net"
"net/http"
"net/http/cookiejar"
"strings"
"time"
)
type (
@ -24,44 +21,6 @@ type (
}
)
var (
DefaultClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
ForceAttemptHTTP2: true,
MaxIdleConns: 64,
MaxIdleConnsPerHost: 8,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
Timeout: time.Second * 30,
}
UnsafeClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
ForceAttemptHTTP2: true,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 64,
MaxIdleConnsPerHost: 8,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
Timeout: time.Second * 30,
}
)
func (client *Client) stashUri(urlPath string) string {
var (
pos int
@ -180,7 +139,7 @@ func (client *Client) execute(r *Request) (res *http.Response, err error) {
func New() *Client {
client := &Client{
client: DefaultClient,
client: http.DefaultClient,
interceptorRequest: make([]BeforeRequest, 0, 10),
interceptorResponse: make([]AfterRequest, 0, 10),
}

View File

@ -1,19 +0,0 @@
package types
const (
ErrAccessDenied = 4003 //拒绝访问
ErrPermissionDenied = 4004 //没有权限
ErrIllegalRequest = 4005 //非法请求
ErrInvalidPayload = 4006 //请求数据无效
ErrResourceCreate = 4101 //资源创建失败
ErrResourceUpdate = 4102 //资源更新失败
ErrResourceDelete = 4103 //资源删除失败
ErrResourceNotFound = 4104 //资源未找到
ErrResourceEmpty = 4105 //资源为空
ErrResourceExpired = 4107 //资源已失效
ErrResourceUnavailable = 4108 //资源无法使用
ErrResourceLocked = 4109 //资源已被锁定
ErrServerUnreachable = 4201 //服务不可用
ErrTemporaryUnavailable = 4202 //临时性失败
ErrFatal = 4204 //致命错误
)

View File

@ -16,12 +16,12 @@ import (
"syscall"
"time"
"git.nobla.cn/golang/kos/entry"
"git.nobla.cn/golang/kos/entry/cli"
"git.nobla.cn/golang/kos/entry/http"
_ "git.nobla.cn/golang/kos/pkg/cache"
"git.nobla.cn/golang/kos/pkg/log"
"git.nobla.cn/golang/kos/util/env"
"git.nspix.com/golang/kos/entry"
"git.nspix.com/golang/kos/entry/cli"
"git.nspix.com/golang/kos/entry/http"
_ "git.nspix.com/golang/kos/pkg/cache"
"git.nspix.com/golang/kos/pkg/log"
"git.nspix.com/golang/kos/util/env"
"github.com/sourcegraph/conc"
)
@ -76,15 +76,14 @@ func (app *application) Command() *cli.Server {
return app.command
}
func (app *application) Handle(path string, cb HandleFunc, cbs ...HandleOption) {
opts := newHandleOptions(cbs...)
func (app *application) Handle(path string, cb HandleFunc) {
if app.http != nil {
app.http.Handle(http.MethodPost, path, func(ctx *http.Context) (err error) {
return cb(ctx)
})
}
if app.command != nil {
app.command.Handle(path, opts.description, func(ctx *cli.Context) (err error) {
app.command.Handle(path, "", func(ctx *cli.Context) (err error) {
return cb(ctx)
})
}
@ -94,6 +93,7 @@ func (app *application) httpServe() (err error) {
var (
l net.Listener
)
app.http = http.New(app.ctx)
if l, err = app.gateway.Apply(
entry.Feature(http.MethodGet),
entry.Feature(http.MethodHead),
@ -140,6 +140,7 @@ func (app *application) commandServe() (err error) {
var (
l net.Listener
)
app.command = cli.New(app.ctx)
if l, err = app.gateway.Apply(
cli.Feature,
); err != nil {
@ -211,26 +212,23 @@ func (app *application) preStart() (err error) {
app.Log().Infof("server starting")
env.Set(EnvAppName, app.opts.ShortName())
env.Set(EnvAppVersion, app.opts.Version)
app.http = http.New(app.ctx)
app.command = cli.New(app.ctx)
if !app.opts.DisableGateway {
addr = net.JoinHostPort(app.opts.Address, strconv.Itoa(app.opts.Port))
app.Log().Infof("server listen on: %s", addr)
app.gateway = entry.New(addr)
if err = app.gateway.Start(app.ctx); err != nil {
addr = net.JoinHostPort(app.opts.Address, strconv.Itoa(app.opts.Port))
app.Log().Infof("server listen on: %s", addr)
app.gateway = entry.New(addr)
if err = app.gateway.Start(app.ctx); err != nil {
return
}
if !app.opts.DisableHttp {
if err = app.httpServe(); err != nil {
return
}
if !app.opts.DisableHttp {
if err = app.httpServe(); err != nil {
return
}
}
if !app.opts.DisableCommand {
if err = app.commandServe(); err != nil {
return
}
}
if !app.opts.DisableCommand {
if err = app.commandServe(); err != nil {
return
}
}
app.plugins.Range(func(key, value any) bool {
if plugin, ok := value.(Plugin); ok {
if err = plugin.BeforeStart(); err != nil {
@ -254,24 +252,10 @@ func (app *application) preStart() (err error) {
Uptime: time.Now().Sub(app.uptime).String(),
Gateway: app.gateway.State(),
})
}, WithHandleDescription("Display application state"))
})
app.Handle("/-/healthy", func(ctx Context) (err error) {
return ctx.Success(app.Healthy())
}, WithHandleDescription("Display application healthy"))
if !app.opts.DisableCommand {
if app.command != nil {
app.command.Handle("/-/debug", "Toggle debug model", func(ctx *cli.Context) (err error) {
var (
bv bool
)
if bv, err = strconv.ParseBool(ctx.Argument(0)); err == nil {
Debug(bv)
}
return ctx.Success(Debug())
})
}
}
})
}
app.plugins.Range(func(key, value any) bool {
if plugin, ok := value.(Plugin); ok {
@ -304,21 +288,19 @@ func (app *application) preStop() (err error) {
}
return true
})
if !app.opts.DisableGateway {
if app.http != nil {
if err = app.http.Shutdown(); err != nil {
app.Log().Warnf("server http shutdown error: %s", err.Error())
}
if app.http != nil {
if err = app.http.Shutdown(); err != nil {
app.Log().Warnf("server http shutdown error: %s", err.Error())
}
if app.command != nil {
if err = app.command.Shutdown(); err != nil {
app.Log().Warnf("server command shutdown error: %s", err.Error())
}
}
if err = app.gateway.Stop(); err != nil {
app.Log().Warnf("server gateway shutdown error: %s", err.Error())
}
if app.command != nil {
if err = app.command.Shutdown(); err != nil {
app.Log().Warnf("server command shutdown error: %s", err.Error())
}
}
if err = app.gateway.Stop(); err != nil {
app.Log().Warnf("server gateway shutdown error: %s", err.Error())
}
app.plugins.Range(func(key, value any) bool {
if plugin, ok := value.(Plugin); ok {
if err = plugin.AfterStop(); err != nil {
@ -362,8 +344,11 @@ func (app *application) Run() (err error) {
return app.preStop()
}
func New(cbs ...Option) Application {
opts := NewOptions(cbs...)
func New(cbs ...Option) *application {
opts := NewOptions()
for _, cb := range cbs {
cb(opts)
}
app := &application{
opts: opts,
uptime: time.Now(),

View File

@ -2,9 +2,9 @@ package kos
import (
"context"
"git.nobla.cn/golang/kos/entry"
"git.nobla.cn/golang/kos/entry/cli"
"git.nobla.cn/golang/kos/entry/http"
"git.nspix.com/golang/kos/entry"
"git.nspix.com/golang/kos/entry/cli"
"git.nspix.com/golang/kos/entry/http"
)
type (
@ -22,8 +22,7 @@ type (
Info() *Info
Http() *http.Server
Command() *cli.Server
Handle(method string, cb HandleFunc, opts ...HandleOption)
Run() (err error)
Handle(method string, cb HandleFunc)
}
// Info application information

View File

@ -1,43 +0,0 @@
package arrays
func IndexOf[T comparable](a T, vs []T) int {
for i, v := range vs {
if v == a {
return i
}
}
return -1
}
func Exists[T comparable](a T, vs []T) bool {
return IndexOf(a, vs) > -1
}
func Fill[T any](startIndex int, num uint, value T) map[int]T {
m := make(map[int]T)
var i uint
for i = 0; i < num; i++ {
m[startIndex] = value
startIndex++
}
return m
}
func Reverse[T comparable](s []T) []T {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
func Merge[T comparable](ss ...[]T) []T {
n := 0
for _, v := range ss {
n += len(v)
}
s := make([]T, 0, n)
for _, v := range ss {
s = append(s, v...)
}
return s
}

View File

@ -1,73 +0,0 @@
package arrays
import (
"reflect"
"testing"
)
func TestIndexOf(t *testing.T) {
type args[T comparable] struct {
a T
vs []T
}
type testCase[T comparable] struct {
name string
args args[T]
want int
}
tests := []testCase[string]{
{"exists", args[string]{a: "a", vs: []string{"a", "b"}}, 0},
{"not exists", args[string]{a: "a", vs: []string{"c", "b"}}, -1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IndexOf(tt.args.a, tt.args.vs); got != tt.want {
t.Errorf("IndexOf() = %v, want %v", got, tt.want)
}
})
}
}
func TestExists(t *testing.T) {
type args[T comparable] struct {
a T
vs []T
}
type testCase[T comparable] struct {
name string
args args[T]
want bool
}
tests := []testCase[int]{
{"exists", args[int]{a: 1, vs: []int{1, 2}}, true},
{"not exists", args[int]{a: 2, vs: []int{3, 4}}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Exists(tt.args.a, tt.args.vs); got != tt.want {
t.Errorf("Exists() = %v, want %v", got, tt.want)
}
})
}
}
func TestReverse(t *testing.T) {
type args[T comparable] struct {
s []T
}
type testCase[T comparable] struct {
name string
args args[T]
want []T
}
tests := []testCase[int]{
{"one", args[int]{s: []int{1, 2, 3}}, []int{3, 2, 1}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Reverse(tt.args.s); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Reverse() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -1,4 +1,4 @@
package aes
package crypto
import (
"bytes"

View File

@ -14,13 +14,11 @@ import (
"path"
"strings"
"time"
"git.nobla.cn/golang/kos/util/env"
)
var (
httpClient = http.Client{
Timeout: time.Second * 30,
Timeout: time.Second * 15,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
@ -31,7 +29,7 @@ var (
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: false,
MaxIdleConns: 48,
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
@ -39,15 +37,6 @@ var (
}
)
func init() {
httpDefaultTimeout := env.Get("HTTP_CLIENT_TIMEOUT", "30s")
if httpDefaultTimeout != "" {
if duration, err := time.ParseDuration(httpDefaultTimeout); err == nil {
httpClient.Timeout = duration
}
}
}
func encode(data any) (r io.Reader, contentType string, err error) {
var (
buf []byte
@ -68,7 +57,6 @@ func encode(data any) (r io.Reader, contentType string, err error) {
return
}
// Get performs a GET request to the specified URL with optional parameters and headers.
func Get(ctx context.Context, urlString string, cbs ...Option) (res *http.Response, err error) {
var (
uri *url.URL
@ -88,7 +76,7 @@ func Get(ctx context.Context, urlString string, cbs ...Option) (res *http.Respon
}
uri.RawQuery = qs.Encode()
}
if req, err = http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil); err != nil {
if req, err = http.NewRequest(http.MethodGet, uri.String(), nil); err != nil {
return
}
if opts.Header != nil {
@ -96,10 +84,9 @@ func Get(ctx context.Context, urlString string, cbs ...Option) (res *http.Respon
req.Header.Set(k, v)
}
}
return do(req, opts)
return do(ctx, req, opts)
}
// Post performs a POST request to the specified URL with optional parameters, headers, and data.
func Post(ctx context.Context, urlString string, cbs ...Option) (res *http.Response, err error) {
var (
uri *url.URL
@ -126,7 +113,7 @@ func Post(ctx context.Context, urlString string, cbs ...Option) (res *http.Respo
return
}
}
if req, err = http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), reader); err != nil {
if req, err = http.NewRequest(http.MethodPost, uri.String(), reader); err != nil {
return
}
if opts.Header != nil {
@ -137,17 +124,17 @@ func Post(ctx context.Context, urlString string, cbs ...Option) (res *http.Respo
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
return do(req, opts)
return do(ctx, req, opts)
}
// Request is a generic HTTP request function that can handle GET, POST, PUT, DELETE, etc.
func Request(ctx context.Context, urlString string, result any, cbs ...Option) (err error) {
func Request(ctx context.Context, urlString string, response any, cbs ...Option) (err error) {
var (
contentType string
reader io.Reader
buf []byte
uri *url.URL
res *http.Response
req *http.Request
contentType string
reader io.Reader
)
opts := newOptions()
for _, cb := range cbs {
@ -168,7 +155,7 @@ func Request(ctx context.Context, urlString string, result any, cbs ...Option) (
return
}
}
if req, err = http.NewRequestWithContext(ctx, opts.Method, uri.String(), reader); err != nil {
if req, err = http.NewRequest(opts.Method, uri.String(), reader); err != nil {
return
}
if opts.Header != nil {
@ -179,41 +166,41 @@ func Request(ctx context.Context, urlString string, result any, cbs ...Option) (
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
if res, err = do(req, opts); err != nil {
if res, err = do(ctx, req, opts); err != nil {
return
}
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
err = fmt.Errorf("ubexpected HTTP status code: %d", res.StatusCode)
if buf, err = io.ReadAll(res.Body); err == nil && len(buf) > 0 {
err = fmt.Errorf("remote server response %s(%d): %s", res.Status, res.StatusCode, string(buf))
} else {
err = fmt.Errorf("remote server response %d: %s", res.StatusCode, res.Status)
}
return
}
//don't care response
if result == nil {
return nil
}
contentType = strings.ToLower(res.Header.Get("Content-Type"))
extName := path.Ext(req.URL.String())
if strings.Contains(contentType, JSON) || extName == ".json" {
err = json.NewDecoder(res.Body).Decode(result)
err = json.NewDecoder(res.Body).Decode(response)
} else if strings.Contains(contentType, XML) || extName == ".xml" {
err = xml.NewDecoder(res.Body).Decode(result)
err = xml.NewDecoder(res.Body).Decode(response)
} else {
err = fmt.Errorf("unsupported content type: %s", contentType)
}
return
}
func Do(req *http.Request, cbs ...Option) (res *http.Response, err error) {
func Do(ctx context.Context, req *http.Request, cbs ...Option) (res *http.Response, err error) {
opts := newOptions()
for _, cb := range cbs {
cb(opts)
}
return do(req, opts)
return do(ctx, req, opts)
}
func do(req *http.Request, opts *Options) (res *http.Response, err error) {
func do(ctx context.Context, req *http.Request, opts *Options) (res *http.Response, err error) {
if opts.Human {
if req.Header.Get("User-Agent") == "" {
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54")
@ -221,6 +208,9 @@ func do(req *http.Request, opts *Options) (res *http.Response, err error) {
if req.Header.Get("Referer") == "" {
req.Header.Set("Referer", req.URL.String())
}
if req.Header.Get("Accept") == "" {
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
}
}
return httpClient.Do(req)
return httpClient.Do(req.WithContext(ctx))
}

View File

@ -1,6 +1,7 @@
package fs
import (
"errors"
"os"
)
@ -10,26 +11,22 @@ func IsDir(filename string) (bool, error) {
if err != nil {
return false, err
}
return fd.Mode().IsDir(), nil
fm := fd.Mode()
return fm.IsDir(), nil
}
// IsFile Tells whether the filename is a file
func IsFile(filename string) (bool, error) {
fd, err := os.Stat(filename)
if err != nil {
return false, err
}
return !fd.Mode().IsDir(), nil
}
// Mkdir checking directory, is not exists will create
func Mkdir(dirname string, perm os.FileMode) error {
// DirectoryOrCreate checking directory, is not exists will create
func DirectoryOrCreate(dirname string) error {
if fi, err := os.Stat(dirname); err != nil {
return os.MkdirAll(dirname, perm)
if errors.Is(err, os.ErrNotExist) {
return os.MkdirAll(dirname, 0755)
} else {
return err
}
} else {
if fi.IsDir() {
return nil
}
return os.ErrPermission
return errors.New("file not directory")
}
}

1
util/fs/file.go 100644
View File

@ -0,0 +1 @@
package fs

View File

@ -1,154 +0,0 @@
package humanize
import (
"bytes"
"git.nobla.cn/golang/kos/util/bs"
"time"
)
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
type Duration int64
// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
// tail of buf, omitting trailing zeros. It omits the decimal
// point too when the fraction is 0. It returns the index where the
// output bytes begin and the value v/10**prec.
func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
// Omit trailing zeros up to and including decimal point.
w := len(buf)
print := false
for i := 0; i < prec; i++ {
digit := v % 10
print = print || digit != 0
if print {
w--
buf[w] = byte(digit) + '0'
}
v /= 10
}
if print {
w--
buf[w] = '.'
}
return w, v
}
// fmtInt formats v into the tail of buf.
// It returns the index where the output begins.
func fmtInt(buf []byte, v uint64) int {
w := len(buf)
if v == 0 {
w--
buf[w] = '0'
} else {
for v > 0 {
w--
buf[w] = byte(v%10) + '0'
v /= 10
}
}
return w
}
func (d Duration) String() string {
// Largest time is 2540400h10m10.000000000s
var buf [32]byte
w := len(buf)
u := uint64(d)
neg := d < 0
if neg {
u = -u
}
if u < uint64(time.Second) {
// Special case: if duration is smaller than a second,
// use smaller units, like 1.2ms
var prec int
w--
buf[w] = 's'
w--
switch {
case u == 0:
return "0s"
case u < uint64(time.Microsecond):
// print nanoseconds
prec = 0
buf[w] = 'n'
case u < uint64(time.Millisecond):
// print microseconds
prec = 3
// U+00B5 'µ' micro sign == 0xC2 0xB5
w-- // Need room for two bytes.
copy(buf[w:], "µ")
default:
// print milliseconds
prec = 6
buf[w] = 'm'
}
w, u = fmtFrac(buf[:w], u, prec)
w = fmtInt(buf[:w], u)
} else {
w--
buf[w] = 's'
w, u = fmtFrac(buf[:w], u, 9)
// u is now integer seconds
w = fmtInt(buf[:w], u%60)
u /= 60
// u is now integer minutes
if u > 0 {
w--
buf[w] = 'm'
w = fmtInt(buf[:w], u%60)
u /= 60
// u is now integer hours
// Stop at hours because days can be different lengths.
if u > 0 {
w--
buf[w] = 'h'
w = fmtInt(buf[:w], u)
}
}
}
if neg {
w--
buf[w] = '-'
}
return string(buf[w:])
}
func (d *Duration) UnmarshalJSON(b []byte) (err error) {
var n time.Duration
b = bytes.TrimFunc(b, func(r rune) bool {
if r == '"' {
return true
}
return false
})
if n, err = time.ParseDuration(bs.BytesToString(b)); err == nil {
*d = Duration(n)
}
return err
}
func (d Duration) MarshalJSON() ([]byte, error) {
return bs.StringToBytes(`"` + d.String() + `"`), nil
}
func (d Duration) Duration() time.Duration {
return time.Duration(d)
}

View File

@ -1,184 +0,0 @@
package humanize
import (
"math"
"strconv"
)
var (
renderFloatPrecisionMultipliers = [...]float64{
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
}
renderFloatPrecisionRounders = [...]float64{
0.5,
0.05,
0.005,
0.0005,
0.00005,
0.000005,
0.0000005,
0.00000005,
0.000000005,
0.0000000005,
}
)
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
// * thousands separator
// * decimal separator
// * decimal precision
//
// Usage: s := RenderFloat(format, n)
// The format parameter tells how to render the number n.
//
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
//
// Examples of format strings, given n = 12345.6789:
// "#,###.##" => "12,345.67"
// "#,###." => "12,345"
// "#,###" => "12345,678"
// "#\u202F###,##" => "12345,68"
// "#.###,###### => 12.345,678900
// "" (aka default format) => 12,345.67
//
// The highest precision allowed is 9 digits after the decimal symbol.
// There is also a version for integer number, FormatInteger(),
// which is convenient for calls within template.
func FormatFloat(format string, n float64) string {
// Special cases:
// NaN = "NaN"
// +Inf = "+Infinity"
// -Inf = "-Infinity"
if math.IsNaN(n) {
return "NaN"
}
if n > math.MaxFloat64 {
return "Infinity"
}
if n < (0.0 - math.MaxFloat64) {
return "-Infinity"
}
// default format
precision := 2
decimalStr := "."
thousandStr := ","
positiveStr := ""
negativeStr := "-"
if len(format) > 0 {
format := []rune(format)
// If there is an explicit format directive,
// then default values are these:
precision = 9
thousandStr = ""
// collect indices of meaningful formatting directives
formatIndx := []int{}
for i, char := range format {
if char != '#' && char != '0' {
formatIndx = append(formatIndx, i)
}
}
if len(formatIndx) > 0 {
// Directive at index 0:
// Must be a '+'
// Raise an error if not the case
// index: 0123456789
// +0.000,000
// +000,000.0
// +0000.00
// +0000
if formatIndx[0] == 0 {
if format[formatIndx[0]] != '+' {
panic("RenderFloat(): invalid positive sign directive")
}
positiveStr = "+"
formatIndx = formatIndx[1:]
}
// Two directives:
// First is thousands separator
// Raise an error if not followed by 3-digit
// 0123456789
// 0.000,000
// 000,000.00
if len(formatIndx) == 2 {
if (formatIndx[1] - formatIndx[0]) != 4 {
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
}
thousandStr = string(format[formatIndx[0]])
formatIndx = formatIndx[1:]
}
// One directive:
// Directive is decimal separator
// The number of digit-specifier following the separator indicates wanted precision
// 0123456789
// 0.00
// 000,0000
if len(formatIndx) == 1 {
decimalStr = string(format[formatIndx[0]])
precision = len(format) - formatIndx[0] - 1
}
}
}
// generate sign part
var signStr string
if n >= 0.000000001 {
signStr = positiveStr
} else if n <= -0.000000001 {
signStr = negativeStr
n = -n
} else {
signStr = ""
n = 0.0
}
// split number into integer and fractional parts
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
// generate integer part string
intStr := strconv.FormatInt(int64(intf), 10)
// add thousand separator if required
if len(thousandStr) > 0 {
for i := len(intStr); i > 3; {
i -= 3
intStr = intStr[:i] + thousandStr + intStr[i:]
}
}
// no fractional part, we can leave now
if precision == 0 {
return signStr + intStr
}
// generate fractional part
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
// may need padding
if len(fracStr) < precision {
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
}
return signStr + intStr + decimalStr + fracStr
}
// FormatInteger produces a formatted number as string.
// See FormatFloat.
func FormatInteger(format string, n int) string {
return FormatFloat(format, float64(n))
}

View File

@ -1,173 +0,0 @@
package humanize
import (
"bytes"
"fmt"
"git.nobla.cn/golang/kos/util/bs"
"math"
"strconv"
"strings"
"unicode"
)
type Size uint64
// IEC Sizes.
// kibis of bits
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
// SI Sizes.
const (
IByte = 1
KByte = IByte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var bytesSizeTable = map[string]uint64{
"b": Byte,
"kib": KiByte,
"kb": KByte,
"mib": MiByte,
"mb": MByte,
"gib": GiByte,
"gb": GByte,
"tib": TiByte,
"tb": TByte,
"pib": PiByte,
"pb": PByte,
"eib": EiByte,
"eb": EByte,
// Without suffix
"": Byte,
"ki": KiByte,
"k": KByte,
"mi": MiByte,
"m": MByte,
"gi": GiByte,
"g": GByte,
"ti": TiByte,
"t": TByte,
"pi": PiByte,
"p": PByte,
"ei": EiByte,
"e": EByte,
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
// Bytes produces a human readable representation of an SI size.
//
// See also: ParseBytes.
//
// Bytes(82854982) -> 83 MB
func Bytes(s uint64) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
return humanateBytes(s, 1000, sizes)
}
// IBytes produces a human readable representation of an IEC size.
//
// See also: ParseBytes.
//
// IBytes(82854982) -> 79 MiB
func IBytes(s uint64) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
return humanateBytes(s, 1024, sizes)
}
// ParseBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// See Also: Bytes, IBytes.
//
// ParseBytes("42 MB") -> 42000000, nil
// ParseBytes("42 mib") -> 44040192, nil
func ParseBytes(s string) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := bytesSizeTable[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}
func (b *Size) UnmarshalJSON(buf []byte) error {
var (
n uint64
err error
)
buf = bytes.TrimFunc(buf, func(r rune) bool {
if r == '"' {
return true
}
return false
})
if n, err = ParseBytes(bs.BytesToString(buf)); err == nil {
*b = Size(n)
}
return err
}
func (b Size) MarshalJSON() ([]byte, error) {
s := `"` + IBytes(uint64(b)) + `"`
return bs.StringToBytes(s), nil
}
func (b Size) String() string {
return IBytes(uint64(b))
}

View File

@ -1,177 +0,0 @@
package humanize
import (
"bytes"
"fmt"
"git.nobla.cn/golang/kos/util/bs"
"math"
"sort"
"time"
)
const (
Day = 24 * time.Hour
Week = 7 * Day
Month = 30 * Day
Year = 12 * Month
LongTime = 37 * Year
)
// A RelTimeMagnitude struct contains a relative time point at which
// the relative format of time will switch to a new format string. A
// slice of these in ascending order by their "D" field is passed to
// CustomRelTime to format durations.
//
// The Format field is a string that may contain a "%s" which will be
// replaced with the appropriate signed label (e.g. "ago" or "from
// now") and a "%d" that will be replaced by the quantity.
//
// The DivBy field is the amount of time the time difference must be
// divided by in order to display correctly.
//
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
// DivBy should be time.Minute so whatever the duration is will be
// expressed in minutes.
type RelTimeMagnitude struct {
D time.Duration
Format string
DivBy time.Duration
}
var defaultMagnitudes = []RelTimeMagnitude{
{time.Second, "now", time.Second},
{2 * time.Second, "1 second %s", 1},
{time.Minute, "%d seconds %s", time.Second},
{2 * time.Minute, "1 minute %s", 1},
{time.Hour, "%d minutes %s", time.Minute},
{2 * time.Hour, "1 hour %s", 1},
{Day, "%d hours %s", time.Hour},
{2 * Day, "1 day %s", 1},
{Week, "%d days %s", Day},
{2 * Week, "1 week %s", 1},
{Month, "%d weeks %s", Week},
{2 * Month, "1 month %s", 1},
{Year, "%d months %s", Month},
{18 * Month, "1 year %s", 1},
{2 * Year, "2 years %s", 1},
{LongTime, "%d years %s", Year},
{math.MaxInt64, "a long while %s", 1},
}
// RelTime formats a time into a relative string.
//
// It takes two times and two labels. In addition to the generic time
// delta string (e.g. 5 minutes), the labels are used applied so that
// the label corresponding to the smaller time is applied.
//
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
func RelTime(a, b time.Time, albl, blbl string) string {
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
}
// CustomRelTime formats a time into a relative string.
//
// It takes two times two labels and a table of relative time formats.
// In addition to the generic time delta string (e.g. 5 minutes), the
// labels are used applied so that the label corresponding to the
// smaller time is applied.
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
lbl := albl
diff := b.Sub(a)
if a.After(b) {
lbl = blbl
diff = a.Sub(b)
}
n := sort.Search(len(magnitudes), func(i int) bool {
return magnitudes[i].D > diff
})
if n >= len(magnitudes) {
n = len(magnitudes) - 1
}
mag := magnitudes[n]
args := []interface{}{}
escaped := false
for _, ch := range mag.Format {
if escaped {
switch ch {
case 's':
args = append(args, lbl)
case 'd':
args = append(args, diff/mag.DivBy)
}
escaped = false
} else {
escaped = ch == '%'
}
}
return fmt.Sprintf(mag.Format, args...)
}
type Time struct {
tm time.Time
}
func Now() Time {
return Time{tm: time.Now()}
}
func WrapTime(t time.Time) Time {
return Time{tm: t}
}
func (t Time) Add(d Duration) Time {
t.tm = t.tm.Add(d.Duration())
return t
}
func (t Time) AddDuration(d time.Duration) Time {
t.tm = t.tm.Add(d)
return t
}
func (t Time) After(u Time) bool {
return t.tm.After(u.tm)
}
func (t Time) AfterTime(u time.Time) bool {
return t.tm.After(u)
}
func (t Time) Sub(u Time) Duration {
return Duration(t.tm.Sub(u.tm))
}
func (t Time) SubTime(u time.Time) Duration {
return Duration(t.tm.Sub(u))
}
func (t Time) Time() time.Time {
return t.tm
}
func (t Time) String() string {
return t.tm.Format(time.DateTime)
}
func (t Time) MarshalJSON() ([]byte, error) {
s := `"` + t.tm.Format(time.DateTime) + `"`
return bs.StringToBytes(s), nil
}
func (t Time) Ago() string {
return RelTime(t.tm, time.Now(), "ago", "from now")
}
func (t *Time) UnmarshalJSON(buf []byte) (err error) {
buf = bytes.TrimFunc(buf, func(r rune) bool {
if r == '"' {
return true
}
return false
})
t.tm, err = time.ParseInLocation(time.DateTime, bs.BytesToString(buf), time.Local)
return err
}

View File

@ -1,26 +0,0 @@
package humanize
import (
"encoding/json"
"testing"
)
type test struct {
Time Time
}
func TestNow(t *testing.T) {
tm := Now().Add(-1 * Hour * 223)
t.Log(tm.Ago())
ts := &test{Time: Now()}
buf, err := json.Marshal(ts)
if err != nil {
t.Error(err)
}
t.Log(string(buf))
vv := &test{}
if err = json.Unmarshal(buf, vv); err != nil {
t.Error(err)
}
t.Log(vv.Time)
}

View File

@ -1,7 +1,6 @@
package reflect
import (
"errors"
"fmt"
"reflect"
"strconv"
@ -12,10 +11,6 @@ var (
allowTags = []string{"json", "yaml", "xml", "name"}
)
var (
ErrValueAssociated = errors.New("value cannot be associated")
)
func findField(v reflect.Value, field string) reflect.Value {
var (
pos int
@ -42,10 +37,6 @@ func findField(v reflect.Value, field string) reflect.Value {
return v.FieldByName(field)
}
func Assign(variable reflect.Value, value interface{}) (err error) {
return safeAssignment(variable, value)
}
func safeAssignment(variable reflect.Value, value interface{}) (err error) {
var (
n int64
@ -60,45 +51,8 @@ func safeAssignment(variable reflect.Value, value interface{}) (err error) {
return
}
switch kind {
case reflect.Bool:
switch rv.Kind() {
case reflect.Bool:
variable.SetBool(rv.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if rv.Int() != 0 {
variable.SetBool(true)
} else {
variable.SetBool(false)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if rv.Uint() != 0 {
variable.SetBool(true)
} else {
variable.SetBool(false)
}
case reflect.Float32, reflect.Float64:
if rv.Float() != 0 {
variable.SetBool(true)
} else {
variable.SetBool(false)
}
case reflect.String:
var tv bool
tv, err = strconv.ParseBool(rv.String())
if err == nil {
variable.SetBool(tv)
}
default:
err = fmt.Errorf("boolean value can not assign %s", rv.Kind())
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch rv.Kind() {
case reflect.Bool:
if rv.Bool() {
variable.SetInt(1)
} else {
variable.SetInt(0)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
variable.SetInt(rv.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
@ -110,16 +64,10 @@ func safeAssignment(variable reflect.Value, value interface{}) (err error) {
variable.SetInt(n)
}
default:
err = fmt.Errorf("integer value can not assign %s", rv.Kind())
err = fmt.Errorf("unsupported kind %s", rv.Kind())
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
switch rv.Kind() {
case reflect.Bool:
if rv.Bool() {
variable.SetUint(1)
} else {
variable.SetUint(0)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
variable.SetUint(uint64(rv.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
@ -131,16 +79,10 @@ func safeAssignment(variable reflect.Value, value interface{}) (err error) {
variable.SetUint(un)
}
default:
err = fmt.Errorf("unsigned integer value can not assign %s", rv.Kind())
err = fmt.Errorf("unsupported kind %s", rv.Kind())
}
case reflect.Float32, reflect.Float64:
switch rv.Kind() {
case reflect.Bool:
if rv.Bool() {
variable.SetFloat(1)
} else {
variable.SetFloat(0)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
variable.SetFloat(float64(rv.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
@ -152,16 +94,10 @@ func safeAssignment(variable reflect.Value, value interface{}) (err error) {
variable.SetFloat(fn)
}
default:
err = fmt.Errorf("decimal value can not assign %s", rv.Kind())
err = fmt.Errorf("unsupported kind %s", rv.Kind())
}
case reflect.String:
switch rv.Kind() {
case reflect.Bool:
if rv.Bool() {
variable.SetString("true")
} else {
variable.SetString("false")
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
variable.SetString(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
@ -173,8 +109,6 @@ func safeAssignment(variable reflect.Value, value interface{}) (err error) {
default:
variable.SetString(fmt.Sprint(value))
}
case reflect.Interface:
variable.Set(rv)
default:
err = fmt.Errorf("unsupported kind %s", kind)
}
@ -204,135 +138,20 @@ func Set(hacky interface{}, field string, value interface{}) (err error) {
return
}
switch fieldKind {
case reflect.Struct:
if rv.Kind() != reflect.Map {
return ErrValueAssociated
}
keys := rv.MapKeys()
subVal := reflect.New(refField.Type())
for _, key := range keys {
pv := rv.MapIndex(key)
if key.Kind() == reflect.String {
if err = Set(subVal.Interface(), key.String(), pv.Interface()); err != nil {
return err
}
}
}
refField.Set(subVal.Elem())
case reflect.Ptr:
elemType := refField.Type()
if elemType.Elem().Kind() != reflect.Struct {
return ErrValueAssociated
} else {
if rv.Kind() != reflect.Map {
return ErrValueAssociated
}
keys := rv.MapKeys()
subVal := reflect.New(elemType.Elem())
for _, key := range keys {
pv := rv.MapIndex(key)
if key.Kind() == reflect.String {
if err = Set(subVal.Interface(), key.String(), pv.Interface()); err != nil {
return err
}
}
}
refField.Set(subVal)
}
case reflect.Map:
if rv.Kind() != reflect.Map {
return ErrValueAssociated
}
targetValue := reflect.MakeMap(refField.Type())
keys := rv.MapKeys()
for _, key := range keys {
pv := rv.MapIndex(key)
kVal := reflect.New(refField.Type().Key())
eVal := reflect.New(refField.Type().Elem())
if err = safeAssignment(kVal.Elem(), key.Interface()); err != nil {
return ErrValueAssociated
}
if refField.Type().Elem().Kind() == reflect.Struct {
if pv.Elem().Kind() != reflect.Map {
return ErrValueAssociated
}
subKeys := pv.Elem().MapKeys()
for _, subKey := range subKeys {
subVal := pv.Elem().MapIndex(subKey)
if subKey.Kind() == reflect.String {
if err = Set(eVal.Interface(), subKey.String(), subVal.Interface()); err != nil {
return err
}
}
}
targetValue.SetMapIndex(kVal.Elem(), eVal.Elem())
} else {
if err = safeAssignment(eVal.Elem(), pv.Interface()); err != nil {
return ErrValueAssociated
}
targetValue.SetMapIndex(kVal.Elem(), eVal.Elem())
}
}
refField.Set(targetValue)
case reflect.Array, reflect.Slice:
n = 0
innerType := refField.Type().Elem()
if rv.Kind() == reflect.Array || rv.Kind() == reflect.Slice {
if innerType.Kind() == reflect.Struct {
sliceVar := reflect.MakeSlice(refField.Type(), rv.Len(), rv.Len())
for i := 0; i < rv.Len(); i++ {
srcVal := rv.Index(i)
if srcVal.Kind() != reflect.Map {
return ErrValueAssociated
}
dstVal := reflect.New(innerType)
keys := srcVal.MapKeys()
for _, key := range keys {
kv := srcVal.MapIndex(key)
if key.Kind() == reflect.String {
if err = Set(dstVal.Interface(), key.String(), kv.Interface()); err != nil {
return
}
}
}
sliceVar.Index(n).Set(dstVal.Elem())
n++
}
refField.Set(sliceVar.Slice(0, n))
} else if innerType.Kind() == reflect.Ptr {
sliceVar := reflect.MakeSlice(refField.Type(), rv.Len(), rv.Len())
for i := 0; i < rv.Len(); i++ {
srcVal := rv.Index(i)
if srcVal.Kind() != reflect.Map {
return ErrValueAssociated
}
dstVal := reflect.New(innerType.Elem())
keys := srcVal.MapKeys()
for _, key := range keys {
kv := srcVal.MapIndex(key)
if key.Kind() == reflect.String {
if err = Set(dstVal.Interface(), key.String(), kv.Interface()); err != nil {
return
}
}
}
sliceVar := reflect.MakeSlice(refField.Type(), rv.Len(), rv.Len())
n = 0
for i := 0; i < rv.Len(); i++ {
srcVal := rv.Index(i)
dstVal := reflect.New(innerType).Elem()
if err = safeAssignment(dstVal, srcVal); err == nil {
sliceVar.Index(n).Set(dstVal)
n++
}
refField.Set(sliceVar.Slice(0, n))
} else {
sliceVar := reflect.MakeSlice(refField.Type(), rv.Len(), rv.Len())
for i := 0; i < rv.Len(); i++ {
srcVal := rv.Index(i)
dstVal := reflect.New(innerType).Elem()
if err = safeAssignment(dstVal, srcVal.Interface()); err != nil {
return
}
sliceVar.Index(n).Set(dstVal)
n++
}
refField.Set(sliceVar.Slice(0, n))
}
refField.Set(sliceVar.Slice(0, n))
}
default:
err = safeAssignment(refField, value)

View File

@ -1,18 +0,0 @@
package reflect
import (
"testing"
"time"
)
func TestSet(t *testing.T) {
type hack struct {
Duration time.Duration
Enable bool
}
h := &hack{}
Set(h, "Duration", "1111111111111111")
Set(h, "Enable", "T")
t.Log(h.Duration)
t.Log(h.Enable)
}

View File

@ -1,11 +1,8 @@
package reflection
import (
"git.nobla.cn/golang/kos/util/reflect"
reflectpkg "reflect"
)
import "git.nspix.com/golang/kos/util/reflect"
func Setter[T string | int | int64 | float64 | any](hacky any, variables map[string]T) (err error) {
func Setter(hacky any, variables map[string]any) (err error) {
for k, v := range variables {
if err = Set(hacky, k, v); err != nil {
return err
@ -14,10 +11,6 @@ func Setter[T string | int | int64 | float64 | any](hacky any, variables map[str
return
}
func Assign(s reflectpkg.Value, v any) error {
return reflect.Assign(s, v)
}
func Set(hacky any, field string, value any) (err error) {
func Set(hacky any, field string, value interface{}) (err error) {
return reflect.Set(hacky, field, value)
}

View File

@ -1,65 +0,0 @@
package reflection
import (
"fmt"
"testing"
)
type Fakeb struct {
In int `json:"in"`
BS map[string]string `json:"bs"`
}
type Ab struct {
Name string `json:"name"`
}
type fake struct {
Name string `json:"name"`
Age int `json:"age"`
Usage []Fakeb `json:"usage"`
XX Fakeb `json:"xx"`
AX *Fakeb `json:"ax"`
SS []string `json:"ss"`
DS []int `json:"ds"`
Ms map[string]int `json:"ms"`
AB map[string]Ab `json:"ab"`
}
func TestSetter(t *testing.T) {
dst := &fake{}
vs := map[string]any{
"name": "xxx",
}
vvs := []map[string]any{
{
"in": 15,
"bs": map[string]any{
"aa": "vv",
},
},
}
ms := map[string]any{
"name": "aa",
"age": "5",
"usage": vvs,
"xx": map[string]any{"in": 45},
"ax": map[string]any{"in": 55},
"ss": []string{"11", "ss"},
"ds": []int{55, 55, 34},
"ms": map[string]any{"aa": "23"},
"ab": map[string]any{
"xx": vs,
},
}
err := Setter(dst, ms)
if err != nil {
t.Error(err)
return
}
if dst.Age != 5 {
t.Errorf("setter failed")
} else {
fmt.Printf("%+v", dst)
}
}

View File

@ -2,15 +2,19 @@ package sys
import (
"os"
"path/filepath"
"runtime"
)
// HomeDir return user home directory
func HomeDir() string {
if dirname, err := os.UserHomeDir(); err == nil {
return dirname
if runtime.GOOS == "windows" {
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
}
return os.TempDir()
if h := os.Getenv("HOME"); h != "" {
return h
}
return "/"
}
// HiddenFile get hidden file prefix
@ -25,12 +29,20 @@ func HiddenFile(name string) string {
// CacheDir return user cache directory
func CacheDir() string {
if dirname, err := os.UserCacheDir(); err == nil {
return dirname
switch runtime.GOOS {
case "darwin":
return filepath.Join(HomeDir(), "Library", "Caches")
case "windows":
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
if v := os.Getenv(ev); v != "" {
return v
}
}
// Worst case:
return HomeDir()
}
return os.TempDir()
}
func TempFile() (*os.File, error) {
return os.CreateTemp(os.TempDir(), "kos_*")
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
return xdg
}
return filepath.Join(HomeDir(), ".cache")
}