feat: 升级路由中间件机制,优化 FlexNum 和 Package 打包/解析

This commit is contained in:
Drunk
2025-05-11 23:16:43 +08:00
parent 56c024fc6b
commit 60b82e8296
13 changed files with 619 additions and 444 deletions
+11 -8
View File
@@ -28,15 +28,18 @@ DCE 致力于打造一个高效、开放、安全的通用路由库,欢迎社
---
**TODO**
- [ ] 优化 JS 版 WebSocket 可路由协议客户端。
- [ ] 升级控制器前后置事件接口,支持与程序接口绑定。
- [x] 优化 JS 版 WebSocket 可路由协议客户端。(暂时搁置,可参见`easy-tools`库的实现)
- [x] 升级控制器前后置事件接口,支持与程序接口绑定。
- [ ] 完善数字路径支持。
- [ ] 调整弹性数字函数为结构方法式。
- [ ] 研究可路由协议中支持自定义业务属性的可能性。
- [ ] 支持文件上传等超大数据包的可路由协议。
- [x] 调整弹性数字函数为结构方法式。(取消)
- [x] 研究可路由协议中支持自定义业务属性的可能性。(已实现)
- [x] 支持文件上传等超大数据包的可路由协议。(暂时搁置,可参见`easy-tools`库的实现)
- [ ] 升级 DCE-RUST 功能版本。
- [ ] 完善各协议的 Golang 客户端实现。
- [x] 完善各协议的 Golang 客户端实现。(取消)
- [ ] 逐步替换 AI 生成的文档为人工编写文档。
- [ ] Api扩展属性支持继承。
*由于工作原因,加上不知此框架有任何实际应用者,作者将不计划新功能开发,包括上述全部TODO。后续若作者自身有需求,或者达到一定数量的用户需求,作者将继续开发,BUG修复将优先于新功能开发。若你认同此项目,想推动其发展,欢迎协作开发,欢迎对外推广。*
---
@@ -146,7 +149,7 @@ func bindServer() {
jc.Response(member)
})
flex.TcpRouter.SetEventHandler(func(c *flex.Tcp) error {
flex.TcpRouter.SetBefore("*", func(c *flex.Tcp) error {
shadow, _ := c.Rp.CtxData("$shadowSession")
rs := shadow.(*session.ShmSession[*Member])
cloned, err := rs.CloneForRequest(c.Rp.Sid())
@@ -171,7 +174,7 @@ func bindServer() {
}
c.Rp.SetSession(se)
return nil
}, nil)
})
}
func bindClient() {
+11 -8
View File
@@ -28,15 +28,18 @@ DCE is committed to building an efficient, open, and secure universal routing li
---
**TODO**:
- [ ] Optimize the JS version of the WebSocket routable protocol client.
- [ ] Upgrade the controller pre- and post-event interfaces to support binding with program interfaces.
- [x] Optimize the JS version of the WebSocket routable protocol client. (Temporarily shelved, refer to the implementation in the `easy-tools` repository)
- [x] Upgrade the controller pre- and post-event interfaces to support binding with program interfaces.
- [ ] Enhance support for digital paths.
- [ ] Refactor elastic numeric functions into structural method styles.
- [ ] Investigate the possibility of supporting custom business attributes in routable protocols.
- [ ] Routable protocol support very large data packets such as file uploads.
- [x] Refactor elastic numeric functions into structural method styles. (Canceled)
- [x] Investigate the possibility of supporting custom business attributes in routable protocols. (Implemented)
- [x] Routable protocol support very large data packets such as file uploads. (Temporarily shelved, refer to the implementation in the `easy-tools` repository)
- [ ] Upgrade the feature version of DCE-RUST.
- [ ] Improve the Golang client implementations for various protocols.
- [x] Improve the Golang client implementations for various protocols. (Canceled)
- [ ] Gradually replace AI-generated documentation with manually written documentation.
- [ ] Api extension attributes support inheritance.
*Due to work reasons and not knowing of any actual users of this framework, the author does not plan to develop new features, including all the TODOs listed above. Development will resume later if the author has personal needs or if there is a certain number of user demands. Bug fixes will be prioritized over new feature development. If you recognize this project and want to promote its development, collaboration and external promotion are welcome.*
---
@@ -146,7 +149,7 @@ func bindServer() {
jc.Response(member)
})
flex.TcpRouter.SetEventHandler(func(c *flex.Tcp) error {
flex.TcpRouter.SetBefore("*", func(c *flex.Tcp) error {
shadow, _ := c.Rp.CtxData("$shadowSession")
rs := shadow.(*session.ShmSession[*Member])
cloned, err := rs.CloneForRequest(c.Rp.Sid())
@@ -171,7 +174,7 @@ func bindServer() {
}
c.Rp.SetSession(se)
return nil
}, nil)
})
}
func bindClient() {
+2 -2
View File
@@ -109,7 +109,7 @@ func flexWebsocketBind(port string) {
_, _ = w.Write([]byte{'1'})
})
flex.WebsocketRouter.SetEventHandler(func(ctx *flex.Websocket) error {
flex.WebsocketRouter.SetBefore("*", func(ctx *flex.Websocket) error {
shadowSession, _ := ctx.Rp.CtxData("$shadowSession")
rs := shadowSession.(*session.ShmSession[*session.SimpleUser])
cloned, _ := rs.CloneForRequest(ctx.Rp.Sid())
@@ -123,7 +123,7 @@ func flexWebsocketBind(port string) {
ctx.Rp.SetRespSid(sess.Id())
}
return nil
}, nil)
})
}
func syncUserList(sess *session.ShmSession[*session.SimpleUser], h *proto.Http, user *session.SimpleUser) {
+118 -122
View File
@@ -95,15 +95,24 @@
}
}
class FlexPackage {
static #flagId = 128
static #flagPath = 64
static #flagSid = 32
static #flagCode = 16
static #flagMsg = 8
static #flagBody = 4
static #flagNumPath = 2
class PackageField {
constructor(field, kind) {
this.field = field
this.kind = kind
}
}
const baseFields = [
new PackageField("id", "uint"),
new PackageField("path", "string"),
new PackageField("numPath", "uint"),
new PackageField("sid", "string"),
new PackageField("code", "int"),
new PackageField("message", "string"),
new PackageField("body", "bytes"),
];
class FlexPackage {
static #reqId = 0
id
@@ -128,120 +137,107 @@
}
serialize() {
const buffer = [0]
const lenSeqInfo = []
const textBuffer = []
let seq
if ((this.path || "").length > 0) {
buffer[0] |= FlexPackage.#flagPath
textBuffer.push(seq = new TextEncoder().encode(this.path))
lenSeqInfo.push(FlexNum.non0LenPackHead(seq.length))
let flag = 0;
const totalFields = baseFields.length;
const numHeadVec = [];
const textBuffer = [];
const bodyBuffer = [];
for (const [i, fc] of baseFields.entries()) {
let nh;
let textSeq;
const prop = this[fc.field];
if (prop === undefined) {
continue;
} else if (fc.kind === "int") {
nh = FlexNum.intPackHead(prop);
} else if (fc.kind === "uint") {
nh = FlexNum.uintPackHead(prop);
} else if (fc.kind === "string") {
textSeq = new TextEncoder().encode(prop);
nh = FlexNum.non0LenPackHead(textSeq.length);
} else if (fc.kind === "bytes") {
textSeq = new TextEncoder().encode(prop);
nh = FlexNum.non0LenPackHead(textSeq.length);
} else {
continue;
}
flag |= 1 << i;
numHeadVec.push(nh);
if (fc.kind === "bytes") {
textSeq && bodyBuffer.push(textSeq);
} else if (textSeq) {
textBuffer.push(textSeq);
}
}
if ((this.sid || "").length > 0) {
buffer[0] |= FlexPackage.#flagSid
textBuffer.push(seq = new TextEncoder().encode(this.sid))
lenSeqInfo.push(FlexNum.non0LenPackHead(seq.length))
const flagSeq = FlexNum.uintSerialize(flag);
const flagLen = flagSeq.length;
const buffer = [...flagSeq];
buffer[buffer.length + flagLen - 1] = undefined;
for (const [i, nh] of numHeadVec.entries()) {
buffer[flagLen+i] = nh[0];
buffer.push(...FlexNum.packBody(nh));
}
if ((this.message || "").length > 0) {
buffer[0] |= FlexPackage.#flagMsg
textBuffer.push(seq = new TextEncoder().encode(this.message))
lenSeqInfo.push(FlexNum.non0LenPackHead(seq.length))
for (const part of textBuffer) {
buffer.push(...part);
}
if ((this.body || "").length > 0) {
buffer[0] |= FlexPackage.#flagBody
textBuffer.push(seq = new TextEncoder().encode(this.body))
lenSeqInfo.push(FlexNum.non0LenPackHead(seq.length))
for (const part of bodyBuffer) {
buffer.push(...part);
}
if (this.id > 0) {
buffer[0] |= FlexPackage.#flagId
lenSeqInfo.push(FlexNum.non0LenPackHead(this.id))
}
if (this.code) {
buffer[0] |= FlexPackage.#flagCode
lenSeqInfo.push(FlexNum.intPackHead(this.code))
}
if (this.numPath > 0) {
buffer[0] |= FlexPackage.#flagNumPath
lenSeqInfo.push(FlexNum.non0LenPackHead(this.numPath))
}
buffer.push(...Array(lenSeqInfo.length).fill(0))
for (let i=0; i<lenSeqInfo.length; i++) {
buffer[1+i] = lenSeqInfo[i][0]
buffer.push(...FlexNum.packBody(lenSeqInfo[i][3], lenSeqInfo[i][2], lenSeqInfo[i][1]))
}
for (let i=0; i<textBuffer.length; i++) {
buffer.push(...textBuffer[i])
}
return Uint8Array.from(buffer)
return Uint8Array.from(buffer);
}
static deserialize(seq) {
const fp = new FlexPackage
const flag = seq.splice(0, 1)?.[0]
if (! flag) {
return fp
const fp = new FlexPackage;
const head = seq.splice(0, 1)?.[0];
if (!head) {
return fp;
}
const onesCount = FlexNum.onesCount(flag)
const numHeadSeq = seq.splice(0, onesCount)
if (numHeadSeq.length < onesCount) {
return fp
const nh = FlexNum.parseHead(head, false);
let flag = nh[0];
if (nh[1] > 0) {
const flagBodySeq = seq.splice(0, nh[1]);
flagBodySeq.unshift(nh[3]);
flag = FlexNum.parse(flagBodySeq);
}
const numInfoSeq = new Array(onesCount).fill(0)
for (let i=0; i<onesCount; i++) {
const tuple = FlexNum.parseHead(numHeadSeq[i], true)
const numBodySeq = tuple[1] > 0 ? seq.splice(0, tuple[1]) : []
if (numBodySeq.length < tuple[1]) {
return fp
const onesCount = FlexNum.onesCount(flag);
const numHeadList = seq.splice(0, onesCount)
if (numHeadList.length < onesCount) {
return fp;
}
const numInfoList = new Array(onesCount).fill(0)
let nhi = 0;
let bitsLen = FlexNum.bitsLen(flag);
for (let i=0; i<bitsLen; i++) {
if (((1 << i) & flag) === 0) {
continue;
}
numInfoSeq[i] = [tuple, numBodySeq]
const nh = FlexNum.parseHead(numHeadList[nhi], true);
const numBodySeq = seq.splice(0, nh[1]);
numInfoList[nhi] = [i, nh, numBodySeq];
nhi ++;
}
if ((flag & this.#flagPath) > 0) {
const [numInfo, numBodySeq] = numInfoSeq.shift()
const len = FlexNum.non0LenParse([numInfo[3], ...numBodySeq])
const sq = seq.splice(0, len)
if (sq.length < len) {
return fp
if (bitsLen > baseFields.length) {
return fp;
}
for (const ni of numInfoList) {
const fc = baseFields[ni[0]];
if (fc.kind === "string" || fc.kind === "bytes") {
const len = FlexNum.non0LenParse([ni[1][3], ...ni[2]]);
const sq = seq.splice(0, len);
if (sq.length < len) {
return fp;
}
fp[fc.field] = new TextDecoder().decode(Uint8Array.from(sq));
} else if (fc.kind === "int") {
const val = FlexNum.intParse([ni[1][0], ...ni[2]], ni[1][2]);
fp[fc.field] = val;
} else if (fc.kind === "uint") {
const val = FlexNum.parse([ni[1][3], ...ni[2]]);
fp[fc.field] = val;
}
fp.path = new TextDecoder().decode(Uint8Array.from(sq))
}
if ((flag & this.#flagSid) > 0) {
const [numInfo, numBodySeq] = numInfoSeq.shift()
const len = FlexNum.non0LenParse([numInfo[3], ...numBodySeq])
const sq = seq.splice(0, len)
if (sq.length < len) {
return fp
}
fp.sid = new TextDecoder().decode(Uint8Array.from(sq))
}
if ((flag & this.#flagMsg) > 0) {
const [numInfo, numBodySeq] = numInfoSeq.shift()
const len = FlexNum.non0LenParse([numInfo[3], ...numBodySeq])
const sq = seq.splice(0, len)
if (sq.length < len) {
return fp
}
fp.message = new TextDecoder().decode(Uint8Array.from(sq))
}
if ((flag & this.#flagBody) > 0) {
const [numInfo, numBodySeq] = numInfoSeq.shift()
const len = FlexNum.non0LenParse([numInfo[3], ...numBodySeq])
const sq = seq.splice(0, len)
if (sq.length < len) {
return fp
}
fp.body = new TextDecoder().decode(Uint8Array.from(sq))
}
if ((flag & this.#flagId) > 0) {
const [numInfo, numBodySeq] = numInfoSeq.shift()
fp.id = FlexNum.non0LenParse([numInfo[3], ...numBodySeq])
}
if ((flag & this.#flagCode) > 0) {
const [numInfo, numBodySeq] = numInfoSeq.shift()
fp.code = FlexNum.intParse([numInfo[0], ...numBodySeq], numInfo[2])
}
if ((flag & this.#flagNumPath) > 0) {
const [numInfo, numBodySeq] = numInfoSeq.shift()
fp.numPath = FlexNum.non0LenParse([numInfo[3], ...numBodySeq])
}
return fp
}
@@ -249,22 +245,22 @@
class FlexNum {
static uintSerialize(unsigned) {
return this.#serialize(...this.#uintPackHead(unsigned))
return this.#serialize(...this.uintPackHead(unsigned));
}
static intSerialize(integer) {
return this.#serialize(...this.intPackHead(integer))
return this.#serialize(...this.intPackHead(integer));
}
static non0LenPackHead(unsigned) {
return this.#uintPackHead(unsigned - 1)
return this.uintPackHead(unsigned - 1);
}
static #uintPackHead(unsigned) {
const usize = Math.abs(unsigned)
const bitsLen = this.bitsLen(usize)
const [head, bytesLen] = this.#packHead(usize, bitsLen)
return [head, bytesLen, bitsLen, usize]
static uintPackHead(unsigned) {
const usize = Math.abs(unsigned);
const bitsLen = this.bitsLen(usize);
const [head, bytesLen] = this.#packHead(usize, bitsLen);
return [head, bytesLen, bitsLen, usize];
}
static intPackHead(integer) {
@@ -317,7 +313,7 @@
static uintDeserialize(seq) {
[seq[0]] = this.parseHead(seq[0], false)
return this.#parse(seq)
return this.parse(seq)
}
static intDeserialize(seq) {
@@ -358,7 +354,7 @@
}
static intParse(seq, negative) {
const u64 = this.#parse(seq)
const u64 = this.parse(seq)
if (negative) {
return -u64
}
@@ -366,10 +362,10 @@
}
static non0LenParse(seq) {
return this.#parse(seq) + 1
return this.parse(seq) + 1
}
static #parse(seq) {
static parse(seq) {
let u64 = 0
for (let i = 0; i < seq.length; i++) {
if (seq[i] > 0) {
+4 -5
View File
@@ -30,16 +30,15 @@ func HttpStart(c *proto.Cli) {
Method: proto.HttpGet,
Omission: true,
}, home).
Raw().SetEventHandler(func(ctx *router.Context[*proto.HttpProtocol]) error {
if ctx.Api.Path == "session/{username?}" {
Raw().
SetBefore("session/{username?}", func(ctx *router.Context[*proto.HttpProtocol]) error {
if username := ctx.Param("username"); len(username) > 0 {
ctx.Rp.SetCtxData("hello", "Inject from [BeforeController]")
} else {
return util.Openly(401, "Need to login")
}
}
return nil
}, nil)
return nil
})
port := c.Rp.ArgOr("-p", "2046")
fmt.Printf("Http server is starting on port %s\n", port)
+4 -2
View File
@@ -128,7 +128,8 @@ func bind() {
},
)
proto.HttpRouter.Raw().SetEventHandler(func(ctx *router.Context[*proto.HttpProtocol]) error {
proto.HttpRouter.Raw().
SetBefore("*", func(ctx *router.Context[*proto.HttpProtocol]) error {
sess, err := redises.NewSession[*Member](Rdb, []string{ctx.Rp.Sid()}, session.DefaultTtlMinutes)
if err != nil {
return err
@@ -148,7 +149,8 @@ func bind() {
}
ctx.Rp.SetSession(sess)
return nil
}, func(ctx *router.Context[*proto.HttpProtocol]) error {
}).
SetAfter("*", func(ctx *router.Context[*proto.HttpProtocol]) error {
if newSid := ctx.Rp.RespSid(); newSid != "" {
_, _ = ctx.Rp.WriteString(fmt.Sprintf("\n\nGot new sid, you can use it to access private page:\n%s", newSid))
}
+187 -260
View File
@@ -3,11 +3,14 @@ package flex
import (
"bufio"
"io"
"log"
"math"
"math/bits"
"net"
"reflect"
"slices"
"sync/atomic"
"unsafe"
"go.drunkce.com/dce/router"
"go.drunkce.com/dce/util"
@@ -54,15 +57,78 @@ func NewPackageProtocolWithMeta[Req any](reader *bufio.Reader, meta router.Meta[
return &PackageProtocol[Req]{meta, pkg}, nil
}
const (
flagId uint8 = 128 >> iota
flagPath
flagSid
flagCode
flagMsg
flagBody
flagNumPath
)
type PackageField struct {
Field string
Kind reflect.Kind
Get func(fc *PackageField, pkg *reflect.Value) (numHead *NumHead, textSeq []byte)
Set func(fc *PackageField, pkg *reflect.Value, nh *NumHead, nbSeq []byte, reader io.Reader) (err error)
}
func DefaultPropertyGetter(fc *PackageField, pkg *reflect.Value) (numHead *NumHead, textSeq []byte) {
val := pkg.FieldByName(fc.Field)
if val.IsZero() {
// if (field.Required) {
// log.Panicf("%s is required in Package.", field.Field)
// }
return
}
if fc.Kind >= reflect.Int && fc.Kind < reflect.Uint { // int
numHead = IntPackHead(val.Int())
} else if fc.Kind >= reflect.Uint && fc.Kind <= reflect.Uint64 { // uint
numHead = UintPackHead(val.Uint())
} else if fc.Kind == reflect.String { // string
str := val.String()
numHead = Non0LenPackHead(uint(len(str)))
textSeq = []byte(str)
} else if bts, ok := val.Interface().([]byte); ok { // []byte
numHead = Non0LenPackHead(uint(len(bts)))
textSeq = bts
}
return
}
func DefaultPropertySetter(fc *PackageField, pkg *reflect.Value, nh *NumHead, nbSeq []byte, reader io.Reader) (err error) {
tyRef := pkg.Type()
f, ok := tyRef.FieldByName(fc.Field)
if !ok {
log.Fatalf(`Field "%s" not defined in Package`, fc.Field)
}
field := pkg.FieldByIndex(f.Index)
switch fc.Kind {
case reflect.String, reflect.Slice:
len := Non0LenParse(nh.Original, nbSeq)
if fc.Kind == reflect.String {
seq := make([]byte, len)
if _, err = io.ReadFull(reader, seq); err != nil {
return err
}
field.SetString(string(seq))
} else if fc.Field == "Body" { // Hard-coding for `Body`
blf := pkg.FieldByName("bodyLen")
ptr := unsafe.Pointer(blf.UnsafeAddr())
realPtr := (*uint64)(ptr)
*realPtr = len
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val := IntParse(nh.Negative, nh.Unsigned, nbSeq)
field.SetInt(val)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val := UintParse(nh.Original, nbSeq)
field.SetUint(val)
}
return nil
}
var baseFields = []*PackageField{
{"Id", reflect.Uint32, DefaultPropertyGetter, DefaultPropertySetter},
{"Path", reflect.String, DefaultPropertyGetter, DefaultPropertySetter},
{"NumPath", reflect.Uint32, DefaultPropertyGetter, DefaultPropertySetter},
{"Sid", reflect.String, DefaultPropertyGetter, DefaultPropertySetter},
{"Code", reflect.Int32, DefaultPropertyGetter, DefaultPropertySetter},
{"Message", reflect.String, DefaultPropertyGetter, DefaultPropertySetter},
{"Body", reflect.Slice, DefaultPropertyGetter, DefaultPropertySetter},
}
type Package struct {
Id uint32
@@ -77,53 +143,61 @@ type Package struct {
}
func (p *Package) Serialize() []byte {
// Init a seq buffer and set flag byte to 0
buffer := make([]byte, 1, 512)
textBuffer := make([][]byte, 0, 4)
lenSeqInfoVec := make([]util.Tuple4[byte, int, int, uint], 0, 8)
// Fill protocol headFlags and pack FlexNum head, cache text contents
if length := len(p.Path); length > 0 {
buffer[0] |= flagPath
lenSeqInfoVec = append(lenSeqInfoVec, util.NewTuple4(Non0LenPackHead(uint16(length))))
textBuffer = append(textBuffer, []byte(p.Path))
return p.SerializeWith(nil, nil)
}
func (p *Package) mergeFields(fields []*PackageField, pkg *reflect.Value) []*util.Tuple2[*PackageField, *reflect.Value] {
pe := reflect.ValueOf(p).Elem()
fullFields := make([]*util.Tuple2[*PackageField, *reflect.Value], 0, len(baseFields))
for _, f := range baseFields {
fullFields = append(fullFields, util.NewTuple2(f, &pe))
}
if length := len(p.Sid); length > 0 {
buffer[0] |= flagSid
lenSeqInfoVec = append(lenSeqInfoVec, util.NewTuple4(Non0LenPackHead(uint16(length))))
textBuffer = append(textBuffer, []byte(p.Sid))
if fields != nil && pkg != nil {
for _, f := range fields {
fullFields = append(fullFields, util.NewTuple2(f, pkg))
}
}
if length := len(p.Message); length > 0 {
buffer[0] |= flagMsg
lenSeqInfoVec = append(lenSeqInfoVec, util.NewTuple4(Non0LenPackHead(uint16(length))))
textBuffer = append(textBuffer, []byte(p.Message))
}
if length := len(p.Body); length > 0 {
buffer[0] |= flagBody
lenSeqInfoVec = append(lenSeqInfoVec, util.NewTuple4(Non0LenPackHead(uint(length))))
textBuffer = append(textBuffer, p.Body)
}
if p.Id > 0 {
buffer[0] |= flagId
lenSeqInfoVec = append(lenSeqInfoVec, util.NewTuple4(Non0LenPackHead(p.Id)))
}
if p.Code != 0 {
buffer[0] |= flagCode
lenSeqInfoVec = append(lenSeqInfoVec, util.NewTuple4(IntPackHead(p.Code)))
}
if p.NumPath > 0 {
buffer[0] |= flagNumPath
lenSeqInfoVec = append(lenSeqInfoVec, util.NewTuple4(Non0LenPackHead(p.NumPath)))
return fullFields
}
func (p *Package) SerializeWith(fields []*PackageField, pkg *reflect.Value) []byte {
fullFields := p.mergeFields(fields, pkg)
flag := 0
totalFields := len(fullFields)
numHeadVec := make([]*NumHead, 0, totalFields + 1)
textBuffer := make([][]byte, 0, totalFields - 4) // 容量为减掉至少4个非文本的字段数
bodyBuffer := make([][]byte, 0, 1)
for i, t2 := range fullFields {
numHead, textSeq := t2.A.Get(t2.A, t2.B);
if numHead == nil {
continue
}
flag |= 1 << i
numHeadVec = append(numHeadVec, numHead)
if t2.A.Kind == reflect.Slice {
bodyBuffer = append(bodyBuffer, textSeq)
} else if len(textSeq) > 0 {
textBuffer = append(textBuffer, textSeq)
}
}
flagSeq := UintSerialize(uint(flag))
flagLen := len(flagSeq)
buffer := make([]byte, flagLen, 512)
copy(buffer, flagSeq)
// Fill FlexNum heads and pack and fill FlexNum body
buffer = buffer[:1+len(lenSeqInfoVec)]
for i, lenSeqInfo := range lenSeqInfoVec {
buffer[1+i] = lenSeqInfo.A
buffer = append(buffer, NumPackBody(lenSeqInfo.D, lenSeqInfo.C, lenSeqInfo.B)...)
buffer = buffer[:flagLen+len(numHeadVec)]
for i, nh := range numHeadVec {
buffer[flagLen+i] = nh.Head
buffer = append(buffer, NumPackBody(nh)...)
}
// Append the text contents
for _, part := range textBuffer {
buffer = append(buffer, part...)
}
for _, part := range bodyBuffer {
buffer = append(buffer, part...)
}
return buffer
}
@@ -135,84 +209,6 @@ func (p *Package) parseBody() ([]byte, error) {
return body, nil
}
func PackageDeserializeHead(reader *bufio.Reader) (*Package, error) {
// Try read flag
flag, err := reader.ReadByte()
if err != nil {
return nil, err
}
// Count the FlexNum and init a numHead bytes container
var numHeadSeq = make([]byte, bits.OnesCount8(flag))
if _, err = reader.Read(numHeadSeq); err != nil {
return nil, err
}
var numInfoSeq = make([]util.Tuple5[byte, uint8, bool, byte, []byte], len(numHeadSeq))
// Parse the FlexNum head and total the FlexNum body len
for i, numHead := range numHeadSeq {
unsignedBits, bytesLen, negative, originalBits := NumParseHead(numHead, true)
// Read FlexNum bodies
var numBodySeq = make([]byte, bytesLen)
if _, err = reader.Read(numBodySeq); err != nil {
return nil, err
}
numInfoSeq[i] = util.NewTuple5(unsignedBits, bytesLen, negative, originalBits, numBodySeq)
}
pkg := Package{reader: reader}
// Finally number parse and read text seq
if flag&flagPath > 0 {
numInfo, _ := util.SliceDeleteGet(&numInfoSeq, 0, 1)
seq := make([]byte, Non0LenParse(numInfo[0].D, numInfo[0].E))
if _, err = io.ReadFull(reader, seq); err != nil {
return nil, err
}
pkg.Path = string(seq)
}
if flag&flagSid > 0 {
numInfo, _ := util.SliceDeleteGet(&numInfoSeq, 0, 1)
seq := make([]byte, Non0LenParse(numInfo[0].D, numInfo[0].E))
if _, err = io.ReadFull(reader, seq); err != nil {
return nil, err
}
pkg.Sid = string(seq)
}
if flag&flagMsg > 0 {
numInfo, _ := util.SliceDeleteGet(&numInfoSeq, 0, 1)
seq := make([]byte, Non0LenParse(numInfo[0].D, numInfo[0].E))
if _, err = io.ReadFull(reader, seq); err != nil {
return nil, err
}
pkg.Message = string(seq)
}
if flag&flagBody > 0 {
numInfo, _ := util.SliceDeleteGet(&numInfoSeq, 0, 1)
pkg.bodyLen = Non0LenParse(numInfo[0].D, numInfo[0].E)
}
if flag&flagId > 0 {
numInfo, _ := util.SliceDeleteGet(&numInfoSeq, 0, 1)
pkg.Id = uint32(Non0LenParse(numInfo[0].D, numInfo[0].E))
}
if flag&flagCode > 0 {
numInfo, _ := util.SliceDeleteGet(&numInfoSeq, 0, 1)
pkg.Code = IntParse[int32](numInfo[0].C, numInfo[0].A, numInfo[0].E)
}
if flag&flagNumPath > 0 {
numInfo, _ := util.SliceDeleteGet(&numInfoSeq, 0, 1)
pkg.NumPath = uint32(Non0LenParse(numInfo[0].D, numInfo[0].E))
}
return &pkg, nil
}
func PackageDeserialize(reader *bufio.Reader) (*Package, error) {
sp, err := PackageDeserializeHead(reader)
if err != nil {
return nil, err
} else if b, e := sp.parseBody(); e != nil {
return nil, e
} else {
sp.Body = b
return sp, nil
}
}
var reqId atomic.Uint32
@@ -230,142 +226,73 @@ func NewNumPackage(numPath uint32, body []byte, sid string, id int, path string)
return &Package{Id: uint32(id), Path: path, NumPath: numPath, Sid: sid, Body: body}
}
func UintSerialize[U uint | uint64 | uint32 | uint16 | uint8](unsigned U) []byte {
return numSerialize(UintPackHead(unsigned))
func PackageDeserializeHead(reader *bufio.Reader) (*Package, error) {
return PackageDeserializeHeadWith(reader, nil, nil)
}
func IntSerialize[I int | int64 | int32 | int16 | int8](integer I) []byte {
return numSerialize(IntPackHead(integer))
func PackageDeserializeHeadWith(reader *bufio.Reader, fields []*PackageField, pkg *reflect.Value) (*Package, error) {
// Try read flagHead
head, err := reader.ReadByte()
if err != nil {
return nil, err
}
nh := NumParseHead(head, false)
flag := uint64(nh.Original)
if nh.BytesLen > 0 {
var flagBodySeq = make([]byte, nh.BytesLen)
if _, err = reader.Read(flagBodySeq); err != nil {
return nil, err
}
flagBodySeq = slices.Insert(flagBodySeq, 0, nh.Original)
flag = NumParse(flagBodySeq)
}
// Count the FlexNum and init a numHead bytes container
headOnesCount := bits.OnesCount64(flag)
numHeadList := make([]byte, headOnesCount)
if _, err = reader.Read(numHeadList); err != nil {
return nil, err
}
numInfoList := make([]*util.Tuple3[int, *NumHead, []byte], headOnesCount)
nhi := 0
bitsLen := bits.Len64(flag)
for i := range bitsLen {
if 1 << i & flag == 0 {
continue // Directly continue if current bit is zero
}
nh := NumParseHead(numHeadList[nhi], true)
// Read FlexNum bodies
var numBodySeq = make([]byte, nh.BytesLen)
if _, err = reader.Read(numBodySeq); err != nil {
return nil, err
}
numInfoList[nhi] = util.NewTuple3(i, nh, numBodySeq)
nhi ++
}
p := &Package{reader: reader}
pkgRef := reflect.ValueOf(p).Elem()
fullFields := p.mergeFields(fields, &pkgRef)
if bitsLen > len(fullFields) {
return nil, util.Closed0(`Packet exception, flag overflow`)
}
for _, ni := range numInfoList {
fc := fullFields[ni.A]
fc.A.Set(fc.A, fc.B, ni.B, ni.C, reader)
}
return p, nil
}
// Non0LenPackHead can package 128 into uint7 to represent the length of sha512 in hexadecimal
func Non0LenPackHead[U uint | uint64 | uint32 | uint16 | uint8](unsigned U) (head byte, bytesLen int, bitsLen int, usize uint) {
return UintPackHead(unsigned - 1)
}
func UintPackHead[U uint | uint64 | uint32 | uint16 | uint8](unsigned U) (head byte, bytesLen int, bitsLen int, usize uint) {
usize = uint(unsigned)
bitsLen = bits.Len(usize)
head, bytesLen = numPackHead(usize, bitsLen)
return head, bytesLen, bitsLen, usize
}
func IntPackHead[I int | int64 | int32 | int16 | int8](integer I) (byte, int, int, uint) {
var unsigned uint
if integer < 0 {
// use -int to resolve the edge case, eg. int8(-128) to int8(128) is illegal, but to int(128) is legal.
// but the int(math.MinInt) to -int(math.MinInt) may still illegal, but it is unlikely to be used.
unsigned = uint(-int(integer))
func PackageDeserialize(reader *bufio.Reader) (*Package, error) {
sp, err := PackageDeserializeHead(reader)
if err != nil {
return nil, err
} else if b, e := sp.parseBody(); e != nil {
return nil, e
} else {
unsigned = uint(integer)
sp.Body = b
return sp, nil
}
// add the width of the sign
bitsLen := bits.Len(unsigned) + 1
head, bytesLen := numPackHead(unsigned, bitsLen)
if integer < 0 {
var negative uint8 = 1
if bytesLen < 7 {
negative = 1 << (6 - bytesLen)
}
// handle the negative situation to mark the sign bit
head |= negative
}
return head, bytesLen, bitsLen, unsigned
}
func numPackHead[U uint | uint64 | uint32 | uint16 | uint8](u64 U, bitsLen int) (byte, int) {
bytesLen := int(math.Floor(float64(bitsLen) / 8))
headMaskShift := 8 - bytesLen
var headBits uint8
if bytesLen > 5 {
// Directly alloc 8bytes if the requirement greater than 5
bytesLen = 8
headMaskShift = 2
} else if bitsLen%8 > 7-bytesLen {
// If the remains bits width than the headBits, then need to increase the bytes length,
// and decrease the maskShift to match the body byte length.
// When bytesLen updated, the head no longer needs to be stored any bits, just need to keep the default
bytesLen++
headMaskShift--
} else {
// Otherwise, right shift the bodyBytesLen to calc out the headBits
headBits |= uint8(u64 >> (bytesLen * 8))
}
return 255<<headMaskShift&255 | headBits, bytesLen
}
func numSerialize[U uint | uint64 | uint32 | uint16 | uint8](head uint8, bytesLen int, bitsLen int, u64 U) []byte {
units := make([]byte, bytesLen+1)
units[0] = head
copy(units[1:], NumPackBody(u64, bitsLen, bytesLen))
return units
}
func NumPackBody[U uint | uint64 | uint32 | uint16 | uint8](u64 U, bitsLen int, bytesLen int) []byte {
units := make([]byte, bytesLen)
for i := 0; i < bytesLen && i*8 < bitsLen; i++ {
units[bytesLen-i-1] = uint8(u64 >> (i * 8) & 255)
}
return units
}
func UintDeserialize[U uint | uint64 | uint32 | uint16 | uint8](seq []byte) U {
seq[0], _, _, _ = NumParseHead(seq[0], false)
return U(NumParse(seq))
}
func IntDeserialize[I int | int64 | int32 | int16 | int8](seq []byte) I {
headBits, _, negative, _ := NumParseHead(seq[0], true)
return IntParse[I](negative, headBits, seq[1:])
}
func NumParseHead(head byte, sign bool) (unsignedBits byte, bytesLen uint8, negative bool, originalBits byte) {
for i := 0; i < 8; i++ {
if 128>>i&head == 0 {
if bytesLen = uint8(i); bytesLen > 5 {
bytesLen = 8
originalBits = 1 & head
} else {
originalBits = 127 >> bytesLen & head
}
break
}
}
unsignedBits = originalBits
if sign {
if bytesLen == 8 {
negative = 1&head == 1
} else {
signShift := 0
if negative = 64>>bytesLen&head > 0; negative {
signShift = 1
}
unsignedBits = 127 >> bytesLen >> signShift & head
}
}
return unsignedBits, bytesLen, negative, originalBits
}
func IntParse[I int | int64 | int32 | int16 | int8](negative bool, head uint8, seq []byte) I {
u64 := NumParse(slices.Insert(seq, 0, head))
if negative {
return I(-int64(u64))
}
return I(u64)
}
func Non0LenParse(head uint8, seq []byte) uint64 {
return NumParse(slices.Insert(seq, 0, head)) + 1
}
func NumParse(seq []byte) uint64 {
var u64 uint64 = 0
for i, b := range seq {
if b > 0 {
u64 |= uint64(b) << ((len(seq) - i - 1) * 8)
}
}
return u64
}
func StreamRead(conn net.Conn) ([]byte, error) {
@@ -374,12 +301,12 @@ func StreamRead(conn net.Conn) ([]byte, error) {
if err != nil {
return nil, err
}
_, bytesLen, _, headBits := NumParseHead(numHead, false)
numBody := make([]byte, bytesLen)
nh := NumParseHead(numHead, false)
numBody := make([]byte, nh.BytesLen)
if _, err = reader.Read(numBody); err != nil {
return nil, err
}
var data = make([]byte, Non0LenParse(headBits, numBody))
var data = make([]byte, Non0LenParse(nh.Head, numBody))
if _, err = io.ReadFull(reader, data); err != nil {
return data, err
}
@@ -387,6 +314,6 @@ func StreamRead(conn net.Conn) ([]byte, error) {
}
func StreamPack(bytes []byte) []byte {
head, bytesLen, bitsLen, usize := Non0LenPackHead(uint(len(bytes)))
return slices.Insert(bytes, 0, slices.Insert(NumPackBody(usize, bitsLen, bytesLen), 0, head)...)
nh := Non0LenPackHead(uint(len(bytes)))
return slices.Insert(bytes, 0, slices.Insert(NumPackBody(nh), 0, nh.Head)...)
}
+3 -3
View File
@@ -13,12 +13,12 @@ import (
)
func TestPackage_Serialize(t *testing.T) {
Body := []byte(strings.Repeat("Hello world!你好,世界!", rand.IntN(1000)))
body := []byte(strings.Repeat("Hello world!你好,世界!", rand.IntN(1000)))
hash := md5.New()
hash.Write(Body)
hash.Write(body)
srcHash := hex.EncodeToString(hash.Sum(nil))
t.Logf("Source content hash: %s", srcHash)
pkg := NewPackage("home", Body, srcHash, -1)
pkg := NewPackage("home", body, srcHash, -1)
seq := pkg.Serialize()
dePkg, _ := PackageDeserialize(bufio.NewReader(bytes.NewReader(seq)))
hash2 := md5.New()
+162
View File
@@ -0,0 +1,162 @@
package flex
import (
"math"
"math/bits"
"slices"
)
type NumHead struct {
Unsigned byte // 整数除开符号位的头部
BytesLen uint8 // 数字躯体部分字节长度
Negative bool // 是否负数
Original byte // 整数头部(若为有符号整数,将符号位也视为数字一部分)
Head byte // 弹性数字序列头
BitsLen uint8 // 数字比特长度
Positive uint64 // 正整数
}
func UintSerialize[U uint | uint64 | uint32 | uint16 | uint8](unsigned U) []byte {
return numSerialize(UintPackHead(unsigned))
}
func IntSerialize[I int | int64 | int32 | int16 | int8](integer I) []byte {
return numSerialize(IntPackHead(integer))
}
func numSerialize(nh *NumHead) []byte {
units := make([]byte, nh.BytesLen+1)
units[0] = nh.Head
copy(units[1:], NumPackBody(nh))
return units
}
func NumPackBody(nh *NumHead) []byte {
units := make([]byte, nh.BytesLen)
for i := uint8(0); i < nh.BytesLen && i*8 < nh.BitsLen; i++ {
units[nh.BytesLen-i-1] = uint8(int(nh.Positive) >> (i * 8) & 255)
}
return units
}
// Non0LenPackHead can package 128 into uint7 to represent the length of sha512 in hexadecimal
func Non0LenPackHead[U uint | uint64 | uint32 | uint16 | uint8](unsigned U) *NumHead {
return UintPackHead(unsigned - 1)
}
func UintPackHead[U uint | uint64 | uint32 | uint16 | uint8](unsigned U) *NumHead {
nh := &NumHead{}
nh.Positive = uint64(unsigned)
nh.BitsLen = uint8(bits.Len64(nh.Positive))
nh.Head, nh.BytesLen = numPackHead(nh.Positive, nh.BitsLen)
return nh
}
func IntPackHead[I int | int64 | int32 | int16 | int8](integer I) *NumHead {
nh := &NumHead{}
if integer < 0 {
// use -int to resolve the edge case, eg. int8(-128) to int8(128) is illegal, but to int(128) is legal.
// but the int(math.MinInt) to -int(math.MinInt) may still illegal, but it is unlikely to be used.
nh.Positive = uint64(-int64(integer))
} else {
nh.Positive = uint64(integer)
}
// add the width of the sign
nh.BitsLen = uint8(bits.Len64(nh.Positive)) + 1
nh.Head, nh.BytesLen = numPackHead(nh.Positive, nh.BitsLen)
if integer < 0 {
var negative uint8 = 1
if nh.BytesLen < 7 {
negative = 1 << (6 - nh.BytesLen)
}
// handle the negative situation to mark the sign bit
nh.Head |= negative
}
return nh
}
func numPackHead(u64 uint64, bitsLen uint8) (byte, uint8) {
bytesLen := uint8(math.Floor(float64(bitsLen) / 8))
headMaskShift := 8 - bytesLen
var headBits uint8
if bytesLen > 5 {
// Directly alloc 8bytes if the requirement greater than 5
bytesLen = 8
headMaskShift = 2
} else if bitsLen%8 > 7-bytesLen {
// If the remains bits width than the headBits, then need to increase the bytes length,
// and decrease the maskShift to match the body byte length.
// When bytesLen updated, the head no longer needs to be stored any bits, just need to keep the default
bytesLen++
headMaskShift--
} else {
// Otherwise, right shift the bodyBytesLen to calc out the headBits
headBits |= uint8(u64 >> (bytesLen * 8))
}
return 255<<headMaskShift&255 | headBits, bytesLen
}
func UintDeserialize[U uint | uint64 | uint32 | uint16 | uint8](seq []byte) U {
nh := NumParseHead(seq[0], false)
seq[0] = nh.Unsigned
return U(NumParse(seq))
}
func IntDeserialize[I int | int64 | int32 | int16 | int8](seq []byte) I {
nh := NumParseHead(seq[0], true)
return I(IntParse(nh.Negative, nh.Unsigned, seq[1:]))
}
func NumParseHead(head byte, sign bool) *NumHead {
nh := &NumHead{}
for i := range 8 {
if 128>>i&head == 0 {
if nh.BytesLen = uint8(i); nh.BytesLen > 5 {
nh.BytesLen = 8
nh.Original = 1 & head
} else {
nh.Original = 127 >> nh.BytesLen & head
}
break
}
}
nh.Unsigned = nh.Original
if sign {
if nh.BytesLen == 8 {
nh.Negative = 1&head == 1
} else {
signShift := 0
if nh.Negative = 64>>nh.BytesLen&head > 0; nh.Negative {
signShift = 1
}
nh.Unsigned = 127 >> nh.BytesLen >> signShift & head
}
}
return nh
}
func IntParse(negative bool, head uint8, seq []byte) int64 {
i64 := int64(NumParse(slices.Insert(seq, 0, head)))
if negative {
i64 = -i64
}
return i64
}
func UintParse(head uint8, seq []byte) uint64 {
return NumParse(slices.Insert(seq, 0, head))
}
func Non0LenParse(head uint8, seq []byte) uint64 {
return UintParse(head, seq) + 1
}
func NumParse(seq []byte) uint64 {
var u64 uint64 = 0
for i, b := range seq {
if b > 0 {
u64 |= uint64(b) << ((len(seq) - i - 1) * 8)
}
}
return u64
}
+3 -3
View File
@@ -48,10 +48,10 @@ func (w *ConnectorMappingManager[Rp, C]) ConnMapping() map[string]C {
return cm
}
func (w *ConnectorMappingManager[Rp, C]) ListBy(filter func(s string) bool) []util.Tuple2[string, C] {
return util.MapSeq2From[string, C, util.Tuple2[string, C]](w.ConnMapping()).Filter2(func(s string, _ C) bool {
func (w *ConnectorMappingManager[Rp, C]) ListBy(filter func(s string) bool) []*util.Tuple2[string, C] {
return util.MapSeq2From[string, C, *util.Tuple2[string, C]](w.ConnMapping()).Filter2(func(s string, _ C) bool {
return filter(s)
}).Map2(func(addr string, conn C) util.Tuple2[string, C] {
}).Map2(func(addr string, conn C) *util.Tuple2[string, C] {
return util.NewTuple2(addr, conn)
}).Collect()
}
+1 -1
View File
@@ -17,7 +17,7 @@ type Context[Rp RoutableProtocol] struct {
Rp Rp
Api *RpApi[Rp]
router *Router[Rp]
suffix util.Tuple2[*Suffix, bool]
suffix *util.Tuple2[*Suffix, bool]
params map[string]Param
}
+101 -18
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"log"
"log/slog"
"maps"
"reflect"
"slices"
"strings"
@@ -34,8 +35,10 @@ type Router[Rp RoutableProtocol] struct {
apisMapping map[string][]*RpApi[Rp]
apisTree util.Tree[ApiBranch[Rp], string]
apiMatcher func(rp Rp, apis []*Api) (index int)
beforeController func(ctx *Context[Rp]) error
afterController func(ctx *Context[Rp]) error
beforeMapping map[string] func(ctx *Context[Rp]) error
afterMapping map[string] func(ctx *Context[Rp]) error
pathBeforeMapping map[string]string
pathAfterMapping map[string]string
mu sync.Mutex
}
@@ -45,6 +48,10 @@ func NewRouter[Rp RoutableProtocol]() *Router[Rp] {
suffixBoundary: MarkSuffixBoundary,
idApiMapping: make(map[string]*RpApi[Rp]),
apisMapping: make(map[string][]*RpApi[Rp]),
beforeMapping: make(map[string]func(ctx *Context[Rp]) error),
afterMapping: make(map[string]func(ctx *Context[Rp]) error),
pathBeforeMapping: make(map[string]string),
pathAfterMapping: make(map[string]string),
apisTree: util.NewTree(newApiBranch("", make([]*RpApi[Rp], 0))),
}
}
@@ -55,12 +62,51 @@ func (r *Router[Rp]) SetSeparator(pps string, sb string) *Router[Rp] {
return r
}
func (r *Router[Rp]) SetEventHandler(before func(ctx *Context[Rp]) error, after func(ctx *Context[Rp]) error) *Router[Rp] {
r.beforeController = before
r.afterController = after
// SetBefore sets the pre-controller middleware, which can be used for tasks such as authentication,
// validation, etc. The path parameter works similarly to that in the Push method,
// with the following differences: a trailing + indicates matching child APIs excluding the current one,
// while a trailing * includes the current API as well.
// Path omission is not supported—if the path was omitted in the Push configuration, it must be fully specified here.
//
// SetBefore("member", func(ctx *Context[Rp]) error {}) // Intercept "/member"
// SetBefore("member*", func(ctx *Context[Rp]) error {}) // Intercept "/member" and its sub-APIs
// SetBefore("member+", func(ctx *Context[Rp]) error {}) // Intercept sub-APIs of "/member"
// SetBefore("member/{mid}+", func(ctx *Context[Rp]) error {}) // Intercept sub-APIs of "/member/{mid}"
//
// Returning an error can interrupt and clear the processing flow.
func (r *Router[Rp]) SetBefore(path string, handler func(ctx *Context[Rp]) error) *Router[Rp] {
hookOverrideWarn(path, r.beforeMapping, true)
r.beforeMapping[path] = handler
return r
}
func (r *Router[Rp]) SetBeforePaths(paths []string, handler func(ctx *Context[Rp]) error) *Router[Rp] {
for _, path := range paths { r.SetBefore(path, handler) }
return r
}
func (r *Router[Rp]) SetAfter(path string, handler func(ctx *Context[Rp]) error) *Router[Rp] {
hookOverrideWarn(path, r.beforeMapping, false)
r.afterMapping[path] = handler
return r
}
func (r *Router[Rp]) SetAfterPaths(paths []string, handler func(ctx *Context[Rp]) error) *Router[Rp] {
for _, path := range paths { r.SetAfter(path, handler) }
return r
}
func hookOverrideWarn[T any](path string, mapping map[string]T, pre bool) {
if _, ok := mapping[path]; !ok {
return
}
hook := "preprocessor"
if !pre {
hook = "postprocesser"
}
slog.Warn(fmt.Sprintf(`Path "%s" already has a %s; reassigning it will overwrite the current one.`, path, hook))
}
func (r *Router[Rp]) SetApiMatcher(apiMatcher func(rp Rp, apis []*Api) (index int)) *Router[Rp] {
r.apiMatcher = apiMatcher
return r
@@ -145,12 +191,8 @@ func (r *Router[Rp]) ready() {
return rp.MatchApi(apis)
}
}
if r.beforeController == nil {
r.beforeController = func(ctx *Context[Rp]) error { return nil }
}
if r.afterController == nil {
r.afterController = func(ctx *Context[Rp]) error { return nil }
}
r.mapMiddleware(r.beforeMapping, r.pathBeforeMapping, true)
r.mapMiddleware(r.afterMapping, r.pathAfterMapping, true)
}
func (r *Router[Rp]) omittedPath(path string) string {
@@ -191,19 +233,19 @@ func (r *Router[Rp]) buildTree() {
}).Collect()
// 2. init the apisTree
r.apisTree.Build(apiBranches, func(tree *util.Tree[ApiBranch[Rp], string], remains []ApiBranch[Rp]) {
var fills []util.Tuple2[string, ApiBranch[Rp]]
var fills []*util.Tuple2[string, ApiBranch[Rp]]
for _, remain := range remains {
paths := strings.Split(remain.Path, MarkPathPartSeparator)
for i := 0; i < len(paths)-1; i++ {
path := strings.Join(paths[:i+1], MarkPathPartSeparator)
if _, ok := tree.ChildByPath(paths[:i+1]); !ok && !util.MapSeqFrom[util.Tuple2[string, ApiBranch[Rp]], string](fills).Map(func(f util.Tuple2[string, ApiBranch[Rp]]) string {
if _, ok := tree.ChildByPath(paths[:i+1]); !ok && !util.MapSeqFrom[*util.Tuple2[string, ApiBranch[Rp]], string](fills).Map(func(f *util.Tuple2[string, ApiBranch[Rp]]) string {
return f.A
}).Contains(path, util.Equal) {
fills = append(fills, util.NewTuple2(path, newApiBranch(path, []*RpApi[Rp]{})))
}
}
// If the API already exists in `fills` and `.Apis` is empty, then need to replace with the valid API.
if index := slices.IndexFunc(fills, func(tuple util.Tuple2[string, ApiBranch[Rp]]) bool {
if index := slices.IndexFunc(fills, func(tuple *util.Tuple2[string, ApiBranch[Rp]]) bool {
return tuple.A == remain.Path && len(tuple.B.Apis) == 0
}); index > -1 {
fills[index] = util.NewTuple2(remain.Path, remain)
@@ -242,6 +284,37 @@ func (r *Router[Rp]) buildTree() {
})
}
var middlewarePathSuffixes = []string{"+", "*"}
func (r *Router[Rp]) mapMiddleware(handlerMapping map[string]func(ctx *Context[Rp]) error, pathMapping map[string]string, pre bool) {
apisPaths := slices.Collect(maps.Keys(r.apisMapping))
for k := range handlerMapping {
path := k
suffix := ""
matchChildren := len(k) > 0 && slices.Contains(middlewarePathSuffixes, k[len(k) - 1:])
if matchChildren {
suffix = k[len(k) - 1:]
path = k[:len(k)-1]
}
for i := len(apisPaths) - 1; i >= 0; i -- {
apiPath := apisPaths[i]
matched := false
if suffix != middlewarePathSuffixes[0] && path == apiPath {
matched = true
} else if matchChildren {
// If child matching is enabled and the path is empty, the middleware is treated as a global wildcard;
// otherwise, it applies to sub-paths only.
matched = path == "" || strings.Contains(apiPath, path + MarkPathPartSeparator)
}
if matched {
hookOverrideWarn(apiPath, pathMapping, pre)
pathMapping[apiPath] = k
apisPaths = slices.Delete(apisPaths, i, i + 1)
}
}
}
}
func (r *Router[Rp]) locate(path string, apiFinder func([]*RpApi[Rp]) (*RpApi[Rp], bool)) (*RpApi[Rp], map[string]Param, *Suffix, error) {
var api *RpApi[Rp]
var suffix *Suffix
@@ -281,7 +354,7 @@ func (r *Router[Rp]) locate(path string, apiFinder func([]*RpApi[Rp]) (*RpApi[Rp
func (r *Router[Rp]) matchVarPath(path string) (string, map[string]Param, *Suffix, bool) {
pathParts := strings.Split(path, r.pathPartSeparator)
loopItems := []util.Tuple2[*util.Tree[ApiBranch[Rp], string], int]{util.NewTuple2(&r.apisTree, 0)}
loopItems := []*util.Tuple2[*util.Tree[ApiBranch[Rp], string], int]{util.NewTuple2(&r.apisTree, 0)}
pathParams := map[string]Param{}
var targetApiBranch *util.Tree[ApiBranch[Rp], string]
var suffix *Suffix
@@ -421,11 +494,21 @@ func (r *Router[Rp]) Route(context *Context[Rp]) {
func (r *Router[Rp]) routedHandle(api *RpApi[Rp], pathParams map[string]Param, suffix *Suffix, context *Context[Rp]) error {
context.SetRoutes(r, api, pathParams, suffix)
if err := r.beforeController(context); err != nil {
return err
if bk, ok := r.pathBeforeMapping[api.Path]; ok {
if before, ok := r.beforeMapping[bk]; ok {
if err := before(context); err != nil {
return err
}
}
}
api.Controller(context)
return r.afterController(context)
var err error = nil
if ak, ok := r.pathAfterMapping[api.Path]; ok {
if after, ok := r.afterMapping[ak]; ok {
err = after(context)
}
}
return err
}
func (r *Router[Rp]) idLocate(id string) (*RpApi[Rp], error) {
+12 -12
View File
@@ -5,12 +5,12 @@ type Tuple2[A, B any] struct {
B B
}
func (t Tuple2[A, B]) Values() (A, B) {
func (t *Tuple2[A, B]) Values() (A, B) {
return t.A, t.B
}
func NewTuple2[A, B any](a A, b B) Tuple2[A, B] {
return Tuple2[A, B]{a, b}
func NewTuple2[A, B any](a A, b B) *Tuple2[A, B] {
return &Tuple2[A, B]{a, b}
}
type Tuple3[A, B, C any] struct {
@@ -19,12 +19,12 @@ type Tuple3[A, B, C any] struct {
C C
}
func (t Tuple3[A, B, C]) Values() (A, B, C) {
func (t *Tuple3[A, B, C]) Values() (A, B, C) {
return t.A, t.B, t.C
}
func NewTuple3[A, B, C any](a A, b B, c C) Tuple3[A, B, C] {
return Tuple3[A, B, C]{a, b, c}
func NewTuple3[A, B, C any](a A, b B, c C) *Tuple3[A, B, C] {
return &Tuple3[A, B, C]{a, b, c}
}
type Tuple4[A, B, C, D any] struct {
@@ -34,12 +34,12 @@ type Tuple4[A, B, C, D any] struct {
D D
}
func (t Tuple4[A, B, C, D]) Values() (A, B, C, D) {
func (t *Tuple4[A, B, C, D]) Values() (A, B, C, D) {
return t.A, t.B, t.C, t.D
}
func NewTuple4[A, B, C, D any](a A, b B, c C, d D) Tuple4[A, B, C, D] {
return Tuple4[A, B, C, D]{a, b, c, d}
func NewTuple4[A, B, C, D any](a A, b B, c C, d D) *Tuple4[A, B, C, D] {
return &Tuple4[A, B, C, D]{a, b, c, d}
}
type Tuple5[A, B, C, D, E any] struct {
@@ -50,10 +50,10 @@ type Tuple5[A, B, C, D, E any] struct {
E E
}
func (t Tuple5[A, B, C, D, E]) Values() (A, B, C, D, E) {
func (t *Tuple5[A, B, C, D, E]) Values() (A, B, C, D, E) {
return t.A, t.B, t.C, t.D, t.E
}
func NewTuple5[A, B, C, D, E any](a A, b B, c C, d D, e E) Tuple5[A, B, C, D, E] {
return Tuple5[A, B, C, D, E]{a, b, c, d, e}
func NewTuple5[A, B, C, D, E any](a A, b B, c C, d D, e E) *Tuple5[A, B, C, D, E] {
return &Tuple5[A, B, C, D, E]{a, b, c, d, e}
}