mirror of
https://github.com/idrunk/dce-go.git
synced 2025-09-26 19:01:12 +08:00
1. Split the converter into a request converter and a response converter.
2. Added a protobuf converter. 3. Removed all sub-`go.mod` files and switched to a `go package` modular structure. 4. Migrated the module prefix from `github.com` to `go.drunkce.com`.
This commit is contained in:
265
README-zh.md
265
README-zh.md
@@ -1,28 +1,259 @@
|
||||
中文 | [English](README.md)
|
||||
|
||||
DCE-GO是一个通用路由库,除了能路由http协议外,还能路由cli、websocket、tcp/udp等非标准可路由协议的协议。
|
||||
---
|
||||
|
||||
DCE-GO按功能类型不同,划分了[路由器](router)、[可路由协议](proto)、[转换器](converter)、[会话管理器](session)及[工具](util)等模块。
|
||||
**DCE-GO** 是一个功能强大的通用路由库,不仅支持 HTTP 协议,还能路由 CLI、WebSocket、TCP/UDP 等非标准协议。它采用模块化设计,按功能划分为以下核心模块:
|
||||
|
||||
路由器模块下,定义了api、上下文及路由器库,以及转换器、可路由协议等接口,它是DCE的核心模块。
|
||||
1. **路由器模块**
|
||||
作为 DCE 的核心模块,定义了 API、上下文及路由器库,同时提供了转换器、可路由协议等接口,确保灵活性和扩展性。
|
||||
|
||||
可路由协议模块实现了一些常见协议的可路由协议封装,包括http、cli、websocket、tcp、udp、quic等。
|
||||
2. **可路由协议模块**
|
||||
封装了多种常见协议的可路由实现,包括 HTTP、CLI、WebSocket、TCP、UDP、QUIC 等,满足多样化场景需求。
|
||||
|
||||
转换器内置实现了json与template转换器,用于序列化及反序列化串行数据、双向转换传输对象与实体对象等。
|
||||
3. **转换器模块**
|
||||
内置 JSON 和模板转换器,支持串行数据的序列化与反序列化,以及传输对象与实体对象的双向转换。
|
||||
|
||||
会话管理器模块定义了基础会话、用户会话、连接会话及自重生会话接口,并内置了Redis、共享内存版这些接口的实现类库。
|
||||
4. **会话管理器模块**
|
||||
定义了基础会话、用户会话、连接会话及自重生会话接口,并提供了 Redis 和共享内存的实现类库,方便开发者快速集成。
|
||||
|
||||
所有功能特性,都有相应用例,位于[_examples](_examples)目录下。它的路由性能非常高,与gin相当,可在[这里](_examples/attachs/report/ab-test-result.txt)查看ab测试报告,端口`2046`的为DCE结果。
|
||||
5. **工具模块**
|
||||
提供了一系列实用工具,简化开发流程。
|
||||
|
||||
DCE-GO源自于[DCE-RUST](https://github.com/idrunk/dce-rust),它们都源自于[DCE-PHP](https://github.com/idrunk/dce-php)。DCE-PHP是一个完整的网络编程框架,已停止更新,它的核心路由模块,已升级并迁移到DCE-RUST与DCE-GO中,目前DCE-GO功能版本较新,后续会同步两个语言的功能版本。
|
||||
DCE-GO 的所有功能特性均配有详细用例,位于 [_examples](_examples) 目录下。其路由性能与 Gin 相当,具体性能测试报告可查看 [ab 测试结果](_examples/attachs/report/ab-test-result.txt),其中端口 `2046` 为 DCE 的测试结果。
|
||||
|
||||
DCE致力于成为一个高效、开放、安全的通用路由库,欢迎你来贡献,使其更加高效、开放、安全。
|
||||
DCE-GO 源自 [DCE-RUST](https://github.com/idrunk/dce-rust),而两者均基于 [DCE-PHP](https://github.com/idrunk/dce-php) 的核心路由模块升级而来。DCE-PHP 是一个完整的网络编程框架,现已停止更新,其核心功能已迁移至 DCE-RUST 和 DCE-GO。目前,DCE-GO 的功能版本较新,未来 DCE-RUST 将与之同步。
|
||||
|
||||
TODO:
|
||||
- 优化JS版Websocket可路由协议客户端,完善各协议通信GOLANG版客户端
|
||||
- 升级控制器前后置事件接口,可与程序接口绑定
|
||||
- 完善数字路径支持
|
||||
- 尝试调整弹性数字函数,改为结构方法式
|
||||
- 研究可路由协议中支持自定义业务属性的可能性
|
||||
- 同步DCE-RUST
|
||||
- 逐步替换AI生成的文档为人工文档
|
||||
DCE 致力于打造一个高效、开放、安全的通用路由库,欢迎社区贡献,共同推动其发展。
|
||||
|
||||
---
|
||||
|
||||
**TODO**:
|
||||
- [ ] 优化 JS 版 WebSocket 可路由协议客户端。
|
||||
- [ ] 升级控制器前后置事件接口,支持与程序接口绑定。
|
||||
- [ ] 完善数字路径支持。
|
||||
- [ ] 调整弹性数字函数为结构方法式。
|
||||
- [ ] 研究可路由协议中支持自定义业务属性的可能性。
|
||||
- [ ] 支持文件上传等超大数据包的可路由协议。
|
||||
- [ ] 升级 DCE-RUST 功能版本。
|
||||
- [ ] 完善各协议的 Golang 客户端实现。
|
||||
- [ ] 逐步替换 AI 生成的文档为人工编写文档。
|
||||
|
||||
---
|
||||
|
||||
**使用示例**
|
||||
|
||||
```golang
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"go.drunkce.com/dce/converter"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/router"
|
||||
"go.drunkce.com/dce/session"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// go run main.go tcp start
|
||||
proto.CliRouter.Push("tcp/start/{address?}", func(c *proto.Cli) {
|
||||
bindServer()
|
||||
|
||||
addr := c.ParamOr("address", ":2048")
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
fmt.Printf("tcp server start at %s\n", addr)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
slog.Warn(fmt.Sprintf("accept error: %s", err))
|
||||
continue
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
// Connection sessions are used to store the connection information for sending message across hosts to clients in a distributed environment.
|
||||
shadow, err := session.NewShmSession[*Member](nil, session.DefaultTtlMinutes)
|
||||
if err != nil {
|
||||
slog.Warn(fmt.Sprintf("new session error: %s", err))
|
||||
return
|
||||
}
|
||||
shadow.Connect(conn.LocalAddr().String(), conn.RemoteAddr().String())
|
||||
defer shadow.Disconnect()
|
||||
for {
|
||||
if !flex.TcpRouter.Route(conn, map[string]any{"$shadowSession": shadow}) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
})
|
||||
bindClient()
|
||||
proto.CliRoute(1)
|
||||
}
|
||||
|
||||
func bindServer() {
|
||||
flex.TcpRouter.Push("sign", func(c *flex.Tcp) {
|
||||
signInfo, ok := converter.JsonRawRequester[*flex.TcpProtocol, *Member](c).Parse()
|
||||
jsr := converter.JsonStatusResponser(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if (len(signInfo.Name) == 0 || len(signInfo.Password) == 0) && jsr.Fail("name or password is empty", 0) {
|
||||
return
|
||||
}
|
||||
member, ok := members[signInfo.Name]
|
||||
if !ok {
|
||||
// Notfound then auto register
|
||||
memberId++
|
||||
member = signInfo
|
||||
member.Id = memberId
|
||||
member.Role = 1
|
||||
members[member.Name] = member
|
||||
}
|
||||
if member.Password != signInfo.Password && jsr.Fail("password error", 0) {
|
||||
return
|
||||
}
|
||||
// Must be have a session obj after `BeforeController` event, so we no need to check nil
|
||||
se := c.Rp.Session().(*session.ShmSession[*Member])
|
||||
if err := se.Login(member, 0); err != nil && jsr.Fail(err.Error(), 0) {
|
||||
return
|
||||
}
|
||||
// Must be have a new session id after `UserSession.Login()`
|
||||
c.Rp.SetRespSid(se.Id())
|
||||
jsr.Success(nil)
|
||||
})
|
||||
|
||||
// Bind an api with Path: signer, roles: [1]
|
||||
flex.TcpRouter.PushApi(router.Path("signer").Append("roles", 1), func(c *flex.Tcp) {
|
||||
jc := converter.JsonResponser[*flex.TcpProtocol, *Member, *Signer](c)
|
||||
sess := c.Rp.Session().(*session.ShmSession[*Member])
|
||||
// Member info can be obtained here, so there is no need to check
|
||||
member, _ := sess.User()
|
||||
// Response the member, it can be convert to Signer struct automatically
|
||||
jc.Response(member)
|
||||
})
|
||||
|
||||
flex.TcpRouter.SetEventHandler(func(c *flex.Tcp) error {
|
||||
shadow, _ := c.Rp.CtxData("$shadowSession")
|
||||
rs := shadow.(*session.ShmSession[*Member])
|
||||
cloned, err := rs.CloneForRequest(c.Rp.Sid())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
se := cloned.(*session.ShmSession[*Member])
|
||||
if roles := util.MapSeqFrom[any, uint16](c.Api.ExtrasBy("roles")).Map(func(i any) uint16 {
|
||||
return uint16(i.(int))
|
||||
}).Collect(); len(roles) > 0 {
|
||||
// Roles configured means need to login
|
||||
if member, ok := se.User(); !ok {
|
||||
return util.Openly(401, "need to login")
|
||||
} else if !slices.Contains(roles, member.Role) {
|
||||
return util.Openly(403, "no permission")
|
||||
} else if newer, err := session.NewAutoRenew(se).TryRenew(); err != nil {
|
||||
return err
|
||||
} else if newer {
|
||||
// Logged session need to auto renew to enhance security
|
||||
c.Rp.SetRespSid(se.Id())
|
||||
}
|
||||
}
|
||||
c.Rp.SetSession(se)
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func bindClient() {
|
||||
// go run main.go sign
|
||||
proto.CliRouter.Push("sign", func(c *proto.Cli) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
signInfo := Member{}
|
||||
fmt.Print("Enter username: ")
|
||||
username, _ := reader.ReadString('\n')
|
||||
signInfo.Name = strings.TrimSpace(username)
|
||||
fmt.Print("Enter password: ")
|
||||
password, _ := reader.ReadString('\n')
|
||||
signInfo.Password = strings.TrimSpace(password)
|
||||
reqBody, err := json.Marshal(signInfo)
|
||||
if err != nil && c.SetError(err) {
|
||||
return
|
||||
} else if resp := request(c, "sign", reqBody, ""); resp != nil {
|
||||
c.Rp.SetRespSid(resp.Sid)
|
||||
c.WriteString("Signed in successfully")
|
||||
}
|
||||
})
|
||||
|
||||
// go run main.go signer $SESSION_ID
|
||||
proto.CliRouter.Push("signer/{sid?}", func(c *proto.Cli) {
|
||||
sid := c.Param("sid")
|
||||
if len(sid) == 0 {
|
||||
panic("Session ID is required")
|
||||
}
|
||||
if resp := request(c, "signer", nil, sid); resp == nil {
|
||||
c.SetError(util.Closed0("Request failed"))
|
||||
} else if resp.Code == 0 {
|
||||
var signer Signer
|
||||
if err := json.Unmarshal(resp.Body, &signer); err != nil && c.SetError(err) {
|
||||
return
|
||||
} else {
|
||||
// Just response the signer info if the session is logged in
|
||||
c.WriteString(fmt.Sprintf("Signer: %v", signer))
|
||||
}
|
||||
} else {
|
||||
c.SetError(util.Openly(int(resp.Code), resp.Message))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// It's a simple example, need to mapping request id and the response callback if the server is async
|
||||
func request(c *proto.Cli, path string, reqBody []byte, sid string) *flex.Package {
|
||||
pkg := flex.NewPackage(path, reqBody, sid, -1)
|
||||
conn, _ := net.Dial("tcp", "127.0.0.1:"+c.Rp.ArgOr("port", "2048"))
|
||||
defer conn.Close()
|
||||
if _, err := conn.Write(pkg.Serialize()); err != nil && c.SetError(err) {
|
||||
return nil
|
||||
}
|
||||
resp, err := flex.PackageDeserialize(bufio.NewReader(conn))
|
||||
if err != nil && c.SetError(err) {
|
||||
return nil
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
var memberId uint64 = 0
|
||||
|
||||
var members map[string]*Member = make(map[string]*Member)
|
||||
|
||||
type Member struct {
|
||||
Id uint64
|
||||
Role uint16
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (m Member) Uid() uint64 {
|
||||
return m.Id
|
||||
}
|
||||
|
||||
type Signer struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Member entity converted to transfer object desensitization
|
||||
func (m *Signer) From(member *Member) (*Signer, error) {
|
||||
m = util.NewStruct[*Signer]()
|
||||
m.Name = member.Name
|
||||
return m, nil
|
||||
}
|
||||
```
|
265
README.md
265
README.md
@@ -1,28 +1,259 @@
|
||||
[中文](README-zh.md) | English
|
||||
|
||||
DCE-GO is a universal routing library that can route not only HTTP protocols but also non-standard routable protocols such as CLI, WebSocket, TCP/UDP, and more.
|
||||
---
|
||||
|
||||
DCE-GO is divided into several modules based on functional types, including [Router](router), [Routable Protocols](proto), [Converters](converter), [Session Managers](session), and [Utilities](util).
|
||||
**DCE-GO** is a powerful universal routing library that not only supports the HTTP protocol but also routes non-standard protocols such as CLI, WebSocket, TCP/UDP, and more. It adopts a modular design, divided into the following core modules based on functionality:
|
||||
|
||||
The Router module defines APIs, contexts, and router libraries, as well as interfaces for converters and routable protocols. It is the core module of DCE.
|
||||
1. **Router Module**
|
||||
As the core module of DCE, it defines APIs, contexts, and the router library, while providing interfaces for converters and routable protocols, ensuring flexibility and extensibility.
|
||||
|
||||
The Routable Protocols module implements routable protocol encapsulations for common protocols, including HTTP, CLI, WebSocket, TCP, UDP, QUIC, and more.
|
||||
2. **Routable Protocol Module**
|
||||
Encapsulates routable implementations of various common protocols, including HTTP, CLI, WebSocket, TCP, UDP, QUIC, etc., to meet diverse scenario requirements.
|
||||
|
||||
The Converter module includes built-in implementations of JSON and template converters, used for serializing and deserializing data, as well as bidirectional conversion between transport objects and entity objects.
|
||||
3. **Converter Module**
|
||||
Built-in JSON and template converters support serialization and deserialization of serial data, as well as bidirectional conversion between transport objects and entity objects.
|
||||
|
||||
The Session Manager module defines interfaces for basic sessions, user sessions, connection sessions, and self-regenerating sessions. It also includes built-in implementations of these interfaces using Redis and shared memory.
|
||||
4. **Session Manager Module**
|
||||
Defines interfaces for basic sessions, user sessions, connection sessions, and self-regenerating sessions, and provides implementation libraries for Redis and shared memory, facilitating rapid integration for developers.
|
||||
|
||||
All features come with corresponding examples, located in the [_examples](_examples) directory. Its routing performance is very high, comparable to Gin. You can view the ab test report [here](_examples/attachs/report/ab-test-result.txt). The result for port `2046` is from DCE.
|
||||
5. **Utility Module**
|
||||
Provides a series of practical tools to simplify the development process.
|
||||
|
||||
DCE-GO originates from [DCE-RUST](https://github.com/idrunk/dce-rust), and both originate from [DCE-PHP](https://github.com/idrunk/dce-php). DCE-PHP was a complete network programming framework that has been discontinued. Its core routing module has been upgraded and migrated to DCE-RUST and DCE-GO. Currently, DCE-GO has a more advanced feature set, and future updates will synchronize the features between the two languages.
|
||||
All features of DCE-GO come with detailed usage examples, located in the [_examples](_examples) directory. Its routing performance is comparable to Gin, and specific performance test reports can be viewed in the [ab test results](_examples/attachs/report/ab-test-result.txt), where port `2046` represents DCE's test results.
|
||||
|
||||
DCE aims to be an efficient, open, and secure universal routing library. Contributions are welcome to make it even more efficient, open, and secure.
|
||||
DCE-GO originates from [DCE-RUST](https://github.com/idrunk/dce-rust), and both are based on the core routing module of [DCE-PHP](https://github.com/idrunk/dce-php). DCE-PHP is a complete network programming framework that has ceased updates, with its core functionalities migrated to DCE-RUST and DCE-GO. Currently, DCE-GO has a newer feature version, and DCE-RUST will be synchronized with it in the future.
|
||||
|
||||
TODO:
|
||||
- Optimize the JS version of the WebSocket routable protocol client and improve the communication clients for various protocols in the Golang version.
|
||||
- Upgrade the pre- and post-event interfaces of the controller to bind with program interfaces.
|
||||
- Improve support for numeric paths.
|
||||
- Attempt to refactor the elastic numeric function into a structural method style.
|
||||
- Explore the possibility of supporting custom business attributes in routable protocols.
|
||||
- Synchronize to DCE-RUST.
|
||||
- Gradually replace AI-generated documentation with human-written documentation.
|
||||
DCE is committed to building an efficient, open, and secure universal routing library, and welcomes community contributions to drive its development.
|
||||
|
||||
---
|
||||
|
||||
**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.
|
||||
- [ ] 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.
|
||||
- [ ] Upgrade the feature version of DCE-RUST.
|
||||
- [ ] Improve the Golang client implementations for various protocols.
|
||||
- [ ] Gradually replace AI-generated documentation with manually written documentation.
|
||||
|
||||
---
|
||||
|
||||
**Usage Example**
|
||||
|
||||
```golang
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"go.drunkce.com/dce/converter"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/router"
|
||||
"go.drunkce.com/dce/session"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// go run main.go tcp start
|
||||
proto.CliRouter.Push("tcp/start/{address?}", func(c *proto.Cli) {
|
||||
bindServer()
|
||||
|
||||
addr := c.ParamOr("address", ":2048")
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
fmt.Printf("tcp server start at %s\n", addr)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
slog.Warn(fmt.Sprintf("accept error: %s", err))
|
||||
continue
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
// Connection sessions are used to store the connection information for sending message across hosts to clients in a distributed environment.
|
||||
shadow, err := session.NewShmSession[*Member](nil, session.DefaultTtlMinutes)
|
||||
if err != nil {
|
||||
slog.Warn(fmt.Sprintf("new session error: %s", err))
|
||||
return
|
||||
}
|
||||
shadow.Connect(conn.LocalAddr().String(), conn.RemoteAddr().String())
|
||||
defer shadow.Disconnect()
|
||||
for {
|
||||
if !flex.TcpRouter.Route(conn, map[string]any{"$shadowSession": shadow}) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
})
|
||||
bindClient()
|
||||
proto.CliRoute(1)
|
||||
}
|
||||
|
||||
func bindServer() {
|
||||
flex.TcpRouter.Push("sign", func(c *flex.Tcp) {
|
||||
signInfo, ok := converter.JsonRawRequester[*flex.TcpProtocol, *Member](c).Parse()
|
||||
jsr := converter.JsonStatusResponser(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if (len(signInfo.Name) == 0 || len(signInfo.Password) == 0) && jsr.Fail("name or password is empty", 0) {
|
||||
return
|
||||
}
|
||||
member, ok := members[signInfo.Name]
|
||||
if !ok {
|
||||
// Notfound then auto register
|
||||
memberId++
|
||||
member = signInfo
|
||||
member.Id = memberId
|
||||
member.Role = 1
|
||||
members[member.Name] = member
|
||||
}
|
||||
if member.Password != signInfo.Password && jsr.Fail("password error", 0) {
|
||||
return
|
||||
}
|
||||
// Must be have a session obj after `BeforeController` event, so we no need to check nil
|
||||
se := c.Rp.Session().(*session.ShmSession[*Member])
|
||||
if err := se.Login(member, 0); err != nil && jsr.Fail(err.Error(), 0) {
|
||||
return
|
||||
}
|
||||
// Must be have a new session id after `UserSession.Login()`
|
||||
c.Rp.SetRespSid(se.Id())
|
||||
jc.Success(nil)
|
||||
})
|
||||
|
||||
// Bind an api with Path: signer, roles: [1]
|
||||
flex.TcpRouter.PushApi(router.Path("signer").Append("roles", 1), func(c *flex.Tcp) {
|
||||
jc := converter.JsonResponser[*flex.TcpProtocol, *Member, *Signer](c)
|
||||
sess := c.Rp.Session().(*session.ShmSession[*Member])
|
||||
// Member info can be obtained here, so there is no need to check
|
||||
member, _ := sess.User()
|
||||
// Response the member, it can be convert to Signer struct automatically
|
||||
jc.Response(member)
|
||||
})
|
||||
|
||||
flex.TcpRouter.SetEventHandler(func(c *flex.Tcp) error {
|
||||
shadow, _ := c.Rp.CtxData("$shadowSession")
|
||||
rs := shadow.(*session.ShmSession[*Member])
|
||||
cloned, err := rs.CloneForRequest(c.Rp.Sid())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
se := cloned.(*session.ShmSession[*Member])
|
||||
if roles := util.MapSeqFrom[any, uint16](c.Api.ExtrasBy("roles")).Map(func(i any) uint16 {
|
||||
return uint16(i.(int))
|
||||
}).Collect(); len(roles) > 0 {
|
||||
// Roles configured means need to login
|
||||
if member, ok := se.User(); !ok {
|
||||
return util.Openly(401, "need to login")
|
||||
} else if !slices.Contains(roles, member.Role) {
|
||||
return util.Openly(403, "no permission")
|
||||
} else if newer, err := session.NewAutoRenew(se).TryRenew(); err != nil {
|
||||
return err
|
||||
} else if newer {
|
||||
// Logged session need to auto renew to enhance security
|
||||
c.Rp.SetRespSid(se.Id())
|
||||
}
|
||||
}
|
||||
c.Rp.SetSession(se)
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func bindClient() {
|
||||
// go run main.go sign
|
||||
proto.CliRouter.Push("sign", func(c *proto.Cli) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
signInfo := Member{}
|
||||
fmt.Print("Enter username: ")
|
||||
username, _ := reader.ReadString('\n')
|
||||
signInfo.Name = strings.TrimSpace(username)
|
||||
fmt.Print("Enter password: ")
|
||||
password, _ := reader.ReadString('\n')
|
||||
signInfo.Password = strings.TrimSpace(password)
|
||||
reqBody, err := json.Marshal(signInfo)
|
||||
if err != nil && c.SetError(err) {
|
||||
return
|
||||
} else if resp := request(c, "sign", reqBody, ""); resp != nil {
|
||||
c.Rp.SetRespSid(resp.Sid)
|
||||
c.WriteString("Signed in successfully")
|
||||
}
|
||||
})
|
||||
|
||||
// go run main.go signer $SESSION_ID
|
||||
proto.CliRouter.Push("signer/{sid?}", func(c *proto.Cli) {
|
||||
sid := c.Param("sid")
|
||||
if len(sid) == 0 {
|
||||
panic("Session ID is required")
|
||||
}
|
||||
if resp := request(c, "signer", nil, sid); resp == nil {
|
||||
c.SetError(util.Closed0("Request failed"))
|
||||
} else if resp.Code == 0 {
|
||||
var signer Signer
|
||||
if err := json.Unmarshal(resp.Body, &signer); err != nil && c.SetError(err) {
|
||||
return
|
||||
} else {
|
||||
// Just response the signer info if the session is logged in
|
||||
c.WriteString(fmt.Sprintf("Signer: %v", signer))
|
||||
}
|
||||
} else {
|
||||
c.SetError(util.Openly(int(resp.Code), resp.Message))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// It's a simple example, need to mapping request id and the response callback if the server is async
|
||||
func request(c *proto.Cli, path string, reqBody []byte, sid string) *flex.Package {
|
||||
pkg := flex.NewPackage(path, reqBody, sid, -1)
|
||||
conn, _ := net.Dial("tcp", "127.0.0.1:"+c.Rp.ArgOr("port", "2048"))
|
||||
defer conn.Close()
|
||||
if _, err := conn.Write(pkg.Serialize()); err != nil && c.SetError(err) {
|
||||
return nil
|
||||
}
|
||||
resp, err := flex.PackageDeserialize(bufio.NewReader(conn))
|
||||
if err != nil && c.SetError(err) {
|
||||
return nil
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
var memberId uint64 = 0
|
||||
|
||||
var members map[string]*Member = make(map[string]*Member)
|
||||
|
||||
type Member struct {
|
||||
Id uint64
|
||||
Role uint16
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (m Member) Uid() uint64 {
|
||||
return m.Id
|
||||
}
|
||||
|
||||
type Signer struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Member entity converted to transfer object desensitization
|
||||
func (m *Signer) From(member *Member) (*Signer, error) {
|
||||
m = util.NewStruct[*Signer]()
|
||||
m.Name = member.Name
|
||||
return m, nil
|
||||
}
|
||||
```
|
@@ -3,8 +3,8 @@ package apis
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
func BindCli() {
|
||||
|
@@ -7,16 +7,17 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/proto/flex"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"github.com/quic-go/quic-go"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
const alpn = "dce-quic-example"
|
||||
|
@@ -4,9 +4,6 @@ import (
|
||||
"bufio"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/proto/flex"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
@@ -14,6 +11,10 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
func FlexTcpStart(c *proto.Cli) {
|
||||
|
@@ -4,15 +4,16 @@ import (
|
||||
"bufio"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/proto/flex"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
func FlexUdpStart(c *proto.Cli) {
|
||||
|
@@ -5,12 +5,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/coder/websocket"
|
||||
"github.com/idrunk/dce-go/converter"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/proto/flex"
|
||||
"github.com/idrunk/dce-go/session"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"log"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
@@ -18,9 +12,17 @@ import (
|
||||
"os"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"go.drunkce.com/dce/converter"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/session"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// go run . websocket start
|
||||
proto.CliRouter.Push("websocket/start", FlexWebsocketStart)
|
||||
}
|
||||
|
||||
@@ -36,8 +38,8 @@ func flexWebsocketBind(port string) {
|
||||
converter.TplConfig.SetRoot(wd + "/apis/")
|
||||
|
||||
proto.HttpRouter.Get("", func(h *proto.Http) {
|
||||
t := converter.FileTemplate[*proto.HttpProtocol, struct{ ServerAddr string }](h, "flex-websocket.html")
|
||||
t.Response(struct{ ServerAddr string }{
|
||||
t := converter.FileTemplate[*proto.HttpProtocol, *struct{ ServerAddr string }](h, "flex-websocket.html")
|
||||
t.Response(&struct{ ServerAddr string }{
|
||||
ServerAddr: "ws://127.0.0.1:" + port + "/ws",
|
||||
})
|
||||
})
|
||||
@@ -48,7 +50,7 @@ func flexWebsocketBind(port string) {
|
||||
slog.Warn(err.Error())
|
||||
return
|
||||
}
|
||||
shadowSess, err := session.NewShmSession[*session.SimpleUser]([]string{h.Param("sid")}, session.DefaultTtlMinutes)
|
||||
shadowSess, _ := session.NewShmSession[*session.SimpleUser]([]string{h.Param("sid")}, session.DefaultTtlMinutes)
|
||||
flex.WebsocketRouter.SetMapping(h.Rp.Req.RemoteAddr, c)
|
||||
defer func() {
|
||||
flex.WebsocketRouter.Unmapping(h.Rp.Req.RemoteAddr)
|
||||
@@ -56,7 +58,7 @@ func flexWebsocketBind(port string) {
|
||||
go syncUserList(shadowSess, h, nil)
|
||||
}()
|
||||
if shadowSess != nil {
|
||||
shadowSess.Connect(":2047", h.Rp.Req.RemoteAddr, true)
|
||||
shadowSess.Connect(":2047", h.Rp.Req.RemoteAddr)
|
||||
defer shadowSess.Disconnect()
|
||||
if _, ok := shadowSess.User(); !ok {
|
||||
// auto register and login
|
||||
@@ -125,7 +127,7 @@ func flexWebsocketBind(port string) {
|
||||
}
|
||||
|
||||
func syncUserList(sess *session.ShmSession[*session.SimpleUser], h *proto.Http, user *session.SimpleUser) {
|
||||
var userList []session.SimpleUser
|
||||
var userList []*session.SimpleUser
|
||||
connList := make(map[string]*websocket.Conn)
|
||||
for addr, uid := range flex.WebsocketRouter.UidMapping() {
|
||||
if uses, _ := sess.ListByUid(uid); len(uses) > 0 {
|
||||
@@ -133,16 +135,16 @@ func syncUserList(sess *session.ShmSession[*session.SimpleUser], h *proto.Http,
|
||||
if u, o := us.User(); o {
|
||||
conn, _ := flex.WebsocketRouter.ConnBy(addr)
|
||||
connList[addr] = conn
|
||||
if !slices.ContainsFunc(userList, func(ru session.SimpleUser) bool {
|
||||
if !slices.ContainsFunc(userList, func(ru *session.SimpleUser) bool {
|
||||
return ru.Id == u.Id
|
||||
}) {
|
||||
userList = append(userList, *u)
|
||||
userList = append(userList, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resp := struct {
|
||||
UserList []session.SimpleUser `json:"userList"`
|
||||
UserList []*session.SimpleUser `json:"userList"`
|
||||
SessionUser *session.SimpleUser `json:"sessionUser,omitempty"`
|
||||
}{
|
||||
UserList: userList,
|
||||
|
@@ -1,5 +0,0 @@
|
||||
module github.com/idrunk/dce-go/_examples/apis
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require github.com/coder/websocket v1.8.12
|
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"go.drunkce.com/dce/proto"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@@ -4,10 +4,10 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/idrunk/dce-go/converter"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"go.drunkce.com/dce/converter"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/router"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -52,13 +52,13 @@ func HttpStart(c *proto.Cli) {
|
||||
func var1(c *proto.Http) {
|
||||
user := c.Param("var1")
|
||||
c.Rp.Req.Header.Set("Content-Type", "text/xml")
|
||||
te := converter.TextTemplate[*proto.HttpProtocol, Greeting](c, `<?xml version="1.0" encoding="UTF-8"?>
|
||||
te := converter.TextTemplate[*proto.HttpProtocol, *Greeting](c, `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<greeting>
|
||||
<user>{{.User}}</user>
|
||||
<age>{{.Age}}</age>
|
||||
<welcome>{{.Welcome}}</welcome>
|
||||
</greeting>`)
|
||||
te.Response(Greeting{
|
||||
te.Response(&Greeting{
|
||||
User: user,
|
||||
Age: 0,
|
||||
Welcome: "Welcome to DCE-GO",
|
||||
@@ -98,7 +98,7 @@ func var6(c *proto.Http) {
|
||||
// curl http://127.0.0.1:2046/session/drunk
|
||||
// curl -I http://127.0.0.1:2046/session
|
||||
func sessionApi(c *proto.Http) {
|
||||
t := converter.EmptyTemplate(c)
|
||||
t := converter.StatusTemplate(c)
|
||||
if username := c.Param("username"); username == "dce" {
|
||||
msg, _ := c.Rp.CtxData("hello")
|
||||
fmt.Println(msg)
|
||||
@@ -117,21 +117,21 @@ func hello(c *proto.Http) {
|
||||
// curl -H "Content-Type: application/json" -d "{""user"":""Drunk"",""age"":18}" http://127.0.0.1:2046/hello
|
||||
func helloPost(c *proto.Http) {
|
||||
var legalAge uint8 = 18
|
||||
jc := converter.JsonConverter[*proto.HttpProtocol, GreetingReq, Greeting, Greeting, GreetingResp](c)
|
||||
body, _ := jc.Parse()
|
||||
jc := converter.JsonResponser[*proto.HttpProtocol, *Greeting, *GreetingResp](c)
|
||||
body, _ := converter.JsonRequester[*proto.HttpProtocol, *GreetingReq, *Greeting](c).Parse()
|
||||
fmt.Println(body)
|
||||
if body.Age >= legalAge {
|
||||
body.Welcome = fmt.Sprintf("Hello %s, welcome", body.User)
|
||||
jc.Response(body)
|
||||
} else {
|
||||
jc.Fail(fmt.Sprintf("Sorry, only service for over %d years old peoples", legalAge), 0)
|
||||
jc.Fail(fmt.Sprintf("Sorry, only service for over %d years old peoples", legalAge), 403)
|
||||
}
|
||||
}
|
||||
|
||||
// curl http://127.0.0.1:2046/
|
||||
func home(c *proto.Http) {
|
||||
jc := converter.JsonConverterNoParse[*proto.HttpProtocol, Greeting, GreetingResp](c)
|
||||
jc.Response(Greeting{
|
||||
jc := converter.JsonResponser[*proto.HttpProtocol, *Greeting, *GreetingResp](c)
|
||||
jc.Response(&Greeting{
|
||||
User: "Dce",
|
||||
Age: 18,
|
||||
Welcome: "Welcome to Golang",
|
||||
@@ -153,16 +153,16 @@ type GreetingResp struct {
|
||||
Welcome string `json:"welcome"`
|
||||
}
|
||||
|
||||
func (gr *GreetingReq) Into() (Greeting, error) {
|
||||
return Greeting{
|
||||
func (gr *GreetingReq) Into() (*Greeting, error) {
|
||||
return &Greeting{
|
||||
User: gr.User,
|
||||
Age: gr.Age,
|
||||
Welcome: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (gr *GreetingResp) From(g Greeting) (GreetingResp, error) {
|
||||
return GreetingResp{
|
||||
func (gr *GreetingResp) From(g *Greeting) (*GreetingResp, error) {
|
||||
return &GreetingResp{
|
||||
Welcome: g.Welcome,
|
||||
}, nil
|
||||
}
|
||||
|
@@ -4,10 +4,6 @@ import (
|
||||
"bufio"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/proto/flex"
|
||||
"github.com/idrunk/dce-go/proto/json"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
@@ -15,6 +11,11 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/proto/json"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
func JsonTcpStart(c *proto.Cli) {
|
||||
|
@@ -4,10 +4,6 @@ import (
|
||||
"bufio"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/proto/flex"
|
||||
"github.com/idrunk/dce-go/proto/pb"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
@@ -15,6 +11,11 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/proto/pb"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
func PbTcpStart(c *proto.Cli) {
|
||||
|
@@ -2,16 +2,17 @@ package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/converter"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"github.com/idrunk/dce-go/session"
|
||||
"github.com/idrunk/dce-go/session/redises"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.drunkce.com/dce/converter"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/router"
|
||||
"go.drunkce.com/dce/session"
|
||||
"go.drunkce.com/dce/session/redises"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -37,8 +38,8 @@ func bind() {
|
||||
// curl http://127.0.0.1:2050/login -d "{""name"":""Drunk""}" // role 1
|
||||
// curl http://127.0.0.1:2050/login -d "{""name"":""Dce""}" // role 2
|
||||
proto.HttpRouter.Post("login", func(h *proto.Http) {
|
||||
jc := converter.JsonMapConverter(h)
|
||||
u, ok := jc.Parse()
|
||||
jc := converter.JsonMapResponser(h)
|
||||
u, ok := converter.JsonMapRequester(h).Parse()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -47,7 +48,7 @@ func bind() {
|
||||
return
|
||||
}
|
||||
member, ok := util.SeqFrom(members()).Find(func(m Member) bool {
|
||||
return strings.ToLower(m.Name) == strings.ToLower(name)
|
||||
return strings.EqualFold(m.Name, name)
|
||||
})
|
||||
if !ok && jc.Fail("Wrong name", 1001) {
|
||||
return
|
||||
@@ -68,7 +69,7 @@ func bind() {
|
||||
// curl http://127.0.0.1:2050/manage/profile -H "X-Session-Id: $session_id" // pass sid on header, can access if sid is valid
|
||||
// curl http://127.0.0.1:2050/manage/profile -b "session_id=$session_id" // pass sid in cookies, can access if sid is valid
|
||||
// curl http://127.0.0.1:2050/manage/profile?autologin=1 -H "X-Session-Id: $session_id" // use long life sid to do auto login, will get new sid and the old will destroy
|
||||
proto.HttpRouter.PushApi(router.Path("manage/profile").ByMethod(proto.HttpGet).Append("roles", 1).BindHosts("2050"), func(h *proto.Http) {
|
||||
proto.HttpRouter.PushApi(router.Path("manage/profile").ByMethod(proto.HttpGet).Append("roles", 1, 2).BindHosts("2050"), func(h *proto.Http) {
|
||||
se := h.Rp.Session()
|
||||
if se == nil && h.SetError(util.Openly0("Invalid session")) {
|
||||
return
|
||||
@@ -81,8 +82,8 @@ func bind() {
|
||||
// curl -X PATCH http://127.0.0.1:2050/manage/profile -H "X-Session-Id: $session_id" -d "{}" // none required fields, got openly err response
|
||||
// curl -X PATCH http://127.0.0.1:2050/manage/profile -H "X-Session-Id: $session_id" -d "{""name"":""Foo"",""role_id"":2}" // with required, curren session user will update to role 2
|
||||
proto.HttpRouter.PushApi(router.Path("manage/profile").ByMethod(proto.HttpPatch).Append("roles", 1), func(h *proto.Http) {
|
||||
jc := converter.JsonMapConverter(h)
|
||||
u, ok := jc.Parse()
|
||||
jc := converter.JsonMapResponser(h)
|
||||
u, ok := converter.JsonMapRequester(h).Parse()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -221,7 +222,7 @@ type Member struct {
|
||||
RoleId uint16
|
||||
}
|
||||
|
||||
func (m *Member) Uid() uint64 {
|
||||
func (m Member) Uid() uint64 {
|
||||
return m.Id
|
||||
}
|
||||
|
||||
|
@@ -1,3 +0,0 @@
|
||||
module github.com/idrunk/dce-go/_examples
|
||||
|
||||
go 1.23.3
|
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/idrunk/dce-go/_examples/apis"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"go.drunkce.com/dce/_examples/apis"
|
||||
"go.drunkce.com/dce/proto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@@ -1,3 +0,0 @@
|
||||
module github.com/idrunk/dce-go/converter
|
||||
|
||||
go 1.23.3
|
@@ -2,103 +2,54 @@ package converter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type JsonRequestProcessor[Rp router.RoutableProtocol, ReqDto, Req, Resp, RespDto any] struct {
|
||||
*router.Context[Rp]
|
||||
func JsonRequester[Rp router.RoutableProtocol, ReqDto, Req any](ctx *router.Context[Rp]) *router.Requester[Rp, ReqDto, Req] {
|
||||
return &router.Requester[Rp, ReqDto, Req]{Context: ctx, Deserializer: JsonDeserializer[ReqDto](0)}
|
||||
}
|
||||
|
||||
func (j *JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto]) Serialize(dto RespDto) ([]byte, error) {
|
||||
return json.Marshal(dto)
|
||||
func JsonRawRequester[Rp router.RoutableProtocol, Req any](ctx *router.Context[Rp]) *router.Requester[Rp, Req, Req] {
|
||||
return JsonRequester[Rp, Req, Req](ctx)
|
||||
}
|
||||
|
||||
func (j *JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto]) Deserialize(seq []byte) (ReqDto, error) {
|
||||
var obj ReqDto
|
||||
func JsonStatusRequester[Rp router.RoutableProtocol](ctx *router.Context[Rp]) *router.Requester[Rp, *router.Status, *router.Status] {
|
||||
return JsonRawRequester[Rp, *router.Status](ctx)
|
||||
}
|
||||
|
||||
func JsonMapRequester[Rp router.RoutableProtocol](ctx *router.Context[Rp]) *router.Requester[Rp, map[string]any, map[string]any] {
|
||||
return JsonRawRequester[Rp, map[string]any](ctx)
|
||||
}
|
||||
|
||||
|
||||
func JsonResponser[Rp router.RoutableProtocol, Resp, RespDto any](ctx *router.Context[Rp]) *router.Responser[Rp, Resp, RespDto] {
|
||||
return &router.Responser[Rp, Resp, RespDto]{Context: ctx, Serializer: JsonSerializer[RespDto](0)}
|
||||
}
|
||||
|
||||
func JsonRawResponser[Rp router.RoutableProtocol, Resp any](ctx *router.Context[Rp]) *router.Responser[Rp, Resp, Resp] {
|
||||
return JsonResponser[Rp, Resp, Resp](ctx)
|
||||
}
|
||||
|
||||
func JsonStatusResponser[Rp router.RoutableProtocol](ctx *router.Context[Rp]) *router.Responser[Rp, *router.Status, *router.Status] {
|
||||
return JsonRawResponser[Rp, *router.Status](ctx)
|
||||
}
|
||||
|
||||
func JsonMapResponser[Rp router.RoutableProtocol](ctx *router.Context[Rp]) *router.Responser[Rp, map[string]any, map[string]any] {
|
||||
return JsonRawResponser[Rp, map[string]any](ctx)
|
||||
}
|
||||
|
||||
|
||||
type JsonDeserializer[T any] uint8
|
||||
|
||||
func (s JsonDeserializer[T]) Deserialize(seq []byte) (T, error) {
|
||||
var obj T
|
||||
err := json.Unmarshal(seq, &obj)
|
||||
return obj, err
|
||||
}
|
||||
|
||||
func (j *JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto]) Parse() (Req, bool) {
|
||||
bytes, err := j.Rp.Body()
|
||||
if err == nil {
|
||||
dto, err2 := j.Deserialize(bytes)
|
||||
if err2 == nil {
|
||||
obj, err3 := router.DtoInto[ReqDto, Req](dto)
|
||||
if err3 == nil {
|
||||
return obj, true
|
||||
}
|
||||
err = err3
|
||||
} else {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
j.Rp.SetError(err)
|
||||
var obj Req
|
||||
return obj, false
|
||||
}
|
||||
type JsonSerializer[T any] uint8
|
||||
|
||||
func (j *JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto]) Response(obj Resp) bool {
|
||||
if dto, err := router.DtoFrom[Resp, RespDto](obj); err != nil {
|
||||
j.Rp.SetError(err)
|
||||
} else if seq, err := j.Serialize(dto); err != nil {
|
||||
j.Rp.SetError(err)
|
||||
} else if _, err := j.Rp.Write(seq); err != nil {
|
||||
j.Rp.SetError(err)
|
||||
}
|
||||
j.Rp.SetCtxData(router.HttpContentTypeKey, "application/json; charset=utf-8")
|
||||
return true
|
||||
}
|
||||
|
||||
func (j *JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto]) Error(err error) bool {
|
||||
j.Rp.SetError(err)
|
||||
code, msg := util.ResponseUnits(err)
|
||||
return j.Status(false, msg, code, nil)
|
||||
}
|
||||
|
||||
func (j *JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto]) Success(data any) bool {
|
||||
return j.Status(true, "", 0, data)
|
||||
}
|
||||
|
||||
func (j *JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto]) Fail(msg string, code int) bool {
|
||||
return j.Status(false, msg, code, nil)
|
||||
}
|
||||
|
||||
func (j *JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto]) Status(status bool, msg string, code int, data any) bool {
|
||||
if seq, err := json.Marshal(router.Status{Status: status, Msg: msg, Code: code, Data: data}); err != nil {
|
||||
j.Rp.SetError(err)
|
||||
} else if _, err := j.Rp.Write(seq); err != nil {
|
||||
j.Rp.SetError(err)
|
||||
}
|
||||
j.Rp.SetCtxData(router.HttpContentTypeKey, "application/json; charset=utf-8")
|
||||
return true
|
||||
}
|
||||
|
||||
func JsonConverterNoConvert[Rp router.RoutableProtocol](c *router.Context[Rp]) JsonRequestProcessor[Rp, router.DoNotConvert, router.DoNotConvert, router.DoNotConvert, router.DoNotConvert] {
|
||||
return JsonRequestProcessor[Rp, router.DoNotConvert, router.DoNotConvert, router.DoNotConvert, router.DoNotConvert]{c}
|
||||
}
|
||||
|
||||
func JsonConverterNoParseSame[Rp router.RoutableProtocol, Resp any](c *router.Context[Rp]) JsonRequestProcessor[Rp, router.DoNotConvert, router.DoNotConvert, Resp, Resp] {
|
||||
return JsonRequestProcessor[Rp, router.DoNotConvert, router.DoNotConvert, Resp, Resp]{c}
|
||||
}
|
||||
|
||||
func JsonConverterNoParse[Rp router.RoutableProtocol, Resp, RespDto any](c *router.Context[Rp]) JsonRequestProcessor[Rp, router.DoNotConvert, router.DoNotConvert, Resp, RespDto] {
|
||||
return JsonRequestProcessor[Rp, router.DoNotConvert, router.DoNotConvert, Resp, RespDto]{c}
|
||||
}
|
||||
|
||||
func JsonConverterSame[Rp router.RoutableProtocol, Req, Resp any](c *router.Context[Rp]) JsonRequestProcessor[Rp, Req, Req, Resp, Resp] {
|
||||
return JsonRequestProcessor[Rp, Req, Req, Resp, Resp]{c}
|
||||
}
|
||||
|
||||
func JsonMapConverter[Rp router.RoutableProtocol](c *router.Context[Rp]) JsonRequestProcessor[Rp, map[string]any, map[string]any, map[string]any, map[string]any] {
|
||||
return JsonRequestProcessor[Rp, map[string]any, map[string]any, map[string]any, map[string]any]{c}
|
||||
}
|
||||
|
||||
func JsonMapConverterNoParse[Rp router.RoutableProtocol](c *router.Context[Rp]) JsonRequestProcessor[Rp, router.DoNotConvert, router.DoNotConvert, map[string]any, map[string]any] {
|
||||
return JsonRequestProcessor[Rp, router.DoNotConvert, router.DoNotConvert, map[string]any, map[string]any]{c}
|
||||
}
|
||||
|
||||
func JsonConverter[Rp router.RoutableProtocol, ReqDto, Req, Resp, RespDto any](c *router.Context[Rp]) JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto] {
|
||||
return JsonRequestProcessor[Rp, ReqDto, Req, Resp, RespDto]{c}
|
||||
func (s JsonSerializer[T]) Serialize(obj T) ([]byte, error) {
|
||||
return json.Marshal(obj)
|
||||
}
|
||||
|
113
converter/protobuf.go
Normal file
113
converter/protobuf.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"go.drunkce.com/dce/router"
|
||||
"go.drunkce.com/dce/util"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func PbRequester[Rp router.RoutableProtocol, ReqDto proto.Message, Req any](ctx *router.Context[Rp]) *router.Requester[Rp, ReqDto, Req] {
|
||||
return &router.Requester[Rp, ReqDto, Req]{Context: ctx, Deserializer: ProtobufDeserializer[ReqDto](0)}
|
||||
}
|
||||
|
||||
func PbRawRequester[Rp router.RoutableProtocol, Req proto.Message](ctx *router.Context[Rp]) *router.Requester[Rp, Req, Req] {
|
||||
return PbRequester[Rp, Req, Req](ctx)
|
||||
}
|
||||
|
||||
func PbStatusRequester[Rp router.RoutableProtocol](ctx *router.Context[Rp]) *router.Requester[Rp, *Status, router.Status] {
|
||||
return PbRequester[Rp, *Status, router.Status](ctx)
|
||||
}
|
||||
|
||||
|
||||
func PbResponser[Rp router.RoutableProtocol, Resp any, RespDto proto.Message](ctx *router.Context[Rp]) *router.Responser[Rp, Resp, RespDto] {
|
||||
return &router.Responser[Rp, Resp, RespDto]{Context: ctx, Serializer: ProtobufSerializer[RespDto](0)}
|
||||
}
|
||||
|
||||
func PbRawResponser[Rp router.RoutableProtocol, Resp proto.Message](ctx *router.Context[Rp]) *router.Responser[Rp, Resp, Resp] {
|
||||
return PbResponser[Rp, Resp, Resp](ctx)
|
||||
}
|
||||
|
||||
func PbStatusResponser[Rp router.RoutableProtocol](ctx *router.Context[Rp]) *router.Responser[Rp, router.Status, *Status] {
|
||||
return PbResponser[Rp, router.Status, *Status](ctx)
|
||||
}
|
||||
|
||||
|
||||
type ProtobufDeserializer[T proto.Message] uint8
|
||||
|
||||
func (p ProtobufDeserializer[T]) Deserialize(seq []byte) (T, error) {
|
||||
t := util.NewStruct[T]()
|
||||
err := proto.Unmarshal(seq, t)
|
||||
return t, err
|
||||
}
|
||||
|
||||
type ProtobufSerializer[T proto.Message] uint8
|
||||
|
||||
func (p ProtobufSerializer[T]) Serialize(t T) ([]byte, error) {
|
||||
return proto.Marshal(t)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func PbJsonRequester[Rp router.RoutableProtocol, ReqDto proto.Message, Req any](ctx *router.Context[Rp]) *router.Requester[Rp, ReqDto, Req] {
|
||||
return &router.Requester[Rp, ReqDto, Req]{Context: ctx, Deserializer: ProtobufJsonDeserializer[ReqDto](0)}
|
||||
}
|
||||
|
||||
func PbJsonRawRequester[Rp router.RoutableProtocol, Req proto.Message](ctx *router.Context[Rp]) *router.Requester[Rp, Req, Req] {
|
||||
return PbJsonRequester[Rp, Req, Req](ctx)
|
||||
}
|
||||
|
||||
func PbJsonStatusRequester[Rp router.RoutableProtocol](ctx *router.Context[Rp]) *router.Requester[Rp, *Status, *router.Status] {
|
||||
return PbJsonRequester[Rp, *Status, *router.Status](ctx)
|
||||
}
|
||||
|
||||
|
||||
func PbJsonResponser[Rp router.RoutableProtocol, Resp any, RespDto proto.Message](ctx *router.Context[Rp]) *router.Responser[Rp, Resp, RespDto] {
|
||||
return &router.Responser[Rp, Resp, RespDto]{Context: ctx, Serializer: ProtobufJsonSerializer[RespDto](0)}
|
||||
}
|
||||
|
||||
func PbJsonRawResponser[Rp router.RoutableProtocol, Resp proto.Message](ctx *router.Context[Rp]) *router.Responser[Rp, Resp, Resp] {
|
||||
return PbJsonResponser[Rp, Resp, Resp](ctx)
|
||||
}
|
||||
|
||||
func PbJsonStatusResponser[Rp router.RoutableProtocol](ctx *router.Context[Rp]) *router.Responser[Rp, *router.Status, *Status] {
|
||||
return PbJsonResponser[Rp, *router.Status, *Status](ctx)
|
||||
}
|
||||
|
||||
type ProtobufJsonDeserializer[T proto.Message] uint8
|
||||
|
||||
func (p ProtobufJsonDeserializer[T]) Deserialize(seq []byte) (T, error) {
|
||||
t := util.NewStruct[T]()
|
||||
err := protojson.Unmarshal(seq, t)
|
||||
return t, err
|
||||
}
|
||||
|
||||
type ProtobufJsonSerializer[T proto.Message] uint8
|
||||
|
||||
func (p ProtobufJsonSerializer[T]) Serialize(t T) ([]byte, error) {
|
||||
return protojson.Marshal(t)
|
||||
}
|
||||
|
||||
|
||||
func (ps *Status) Into() (*router.Status, error) {
|
||||
s := router.Status{Data: ps.Data}
|
||||
if ps.Status != nil {
|
||||
s.Status = *ps.Status
|
||||
}
|
||||
if ps.Code != nil {
|
||||
s.Code = int(*ps.Code)
|
||||
}
|
||||
if ps.Msg != nil {
|
||||
s.Msg = *ps.Msg
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (ps *Status) From(s *router.Status) (*Status, error) {
|
||||
p := &Status{}
|
||||
p.Status = &s.Status
|
||||
p.Code = util.Ref(int64(s.Code))
|
||||
p.Msg = &s.Msg
|
||||
p.Data = s.Data
|
||||
return p, nil
|
||||
}
|
156
converter/status.pb.go
Normal file
156
converter/status.pb.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.1
|
||||
// protoc v5.29.2
|
||||
// source: status.proto
|
||||
|
||||
package converter
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Status *bool `protobuf:"varint,1,opt,name=status,proto3,oneof" json:"status,omitempty"`
|
||||
Code *int64 `protobuf:"varint,2,opt,name=code,proto3,oneof" json:"code,omitempty"`
|
||||
Msg *string `protobuf:"bytes,3,opt,name=msg,proto3,oneof" json:"msg,omitempty"`
|
||||
Data *string `protobuf:"bytes,4,opt,name=data,proto3,oneof" json:"data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Status) Reset() {
|
||||
*x = Status{}
|
||||
mi := &file_status_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Status) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Status) ProtoMessage() {}
|
||||
|
||||
func (x *Status) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_status_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Status.ProtoReflect.Descriptor instead.
|
||||
func (*Status) Descriptor() ([]byte, []int) {
|
||||
return file_status_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Status) GetStatus() bool {
|
||||
if x != nil && x.Status != nil {
|
||||
return *x.Status
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Status) GetCode() int64 {
|
||||
if x != nil && x.Code != nil {
|
||||
return *x.Code
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Status) GetMsg() string {
|
||||
if x != nil && x.Msg != nil {
|
||||
return *x.Msg
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Status) GetData() string {
|
||||
if x != nil && x.Data != nil {
|
||||
return *x.Data
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_status_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_status_proto_rawDesc = []byte{
|
||||
0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x93,
|
||||
0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x06, 0x73, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x12,
|
||||
0x15, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x03,
|
||||
0x6d, 0x73, 0x67, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42,
|
||||
0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x63,
|
||||
0x6f, 0x64, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6d, 0x73, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x5f,
|
||||
0x64, 0x61, 0x74, 0x61, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72,
|
||||
0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_status_proto_rawDescOnce sync.Once
|
||||
file_status_proto_rawDescData = file_status_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_status_proto_rawDescGZIP() []byte {
|
||||
file_status_proto_rawDescOnce.Do(func() {
|
||||
file_status_proto_rawDescData = protoimpl.X.CompressGZIP(file_status_proto_rawDescData)
|
||||
})
|
||||
return file_status_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_status_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_status_proto_goTypes = []any{
|
||||
(*Status)(nil), // 0: Status
|
||||
}
|
||||
var file_status_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_status_proto_init() }
|
||||
func file_status_proto_init() {
|
||||
if File_status_proto != nil {
|
||||
return
|
||||
}
|
||||
file_status_proto_msgTypes[0].OneofWrappers = []any{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_status_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_status_proto_goTypes,
|
||||
DependencyIndexes: file_status_proto_depIdxs,
|
||||
MessageInfos: file_status_proto_msgTypes,
|
||||
}.Build()
|
||||
File_status_proto = out.File
|
||||
file_status_proto_rawDesc = nil
|
||||
file_status_proto_goTypes = nil
|
||||
file_status_proto_depIdxs = nil
|
||||
}
|
10
converter/status.proto
Normal file
10
converter/status.proto
Normal file
@@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "./converter";
|
||||
|
||||
message Status {
|
||||
optional bool status = 1;
|
||||
optional int64 code = 2;
|
||||
optional string msg = 3;
|
||||
optional string data = 4;
|
||||
}
|
@@ -4,29 +4,29 @@ import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
type TemplateEngine[Rp router.RoutableProtocol, Resp any] struct {
|
||||
*router.Context[Rp]
|
||||
tpl *template.Template
|
||||
func TemplateResponser[Rp router.RoutableProtocol, D any](ctx *router.Context[Rp], tmpl *template.Template) *router.Responser[Rp, D, D] {
|
||||
return &router.Responser[Rp, D, D]{ Context: ctx, Serializer: TemplateEngine[D]{tmpl}}
|
||||
}
|
||||
|
||||
func FileTemplate[Rp router.RoutableProtocol, Resp any](c *router.Context[Rp], tplPath string) TemplateEngine[Rp, Resp] {
|
||||
return TemplateEngine[Rp, Resp]{c, fileTemplate(tplPath, "")}
|
||||
func FileTemplate[Rp router.RoutableProtocol, D any](c *router.Context[Rp], tplPath string) *router.Responser[Rp, D, D] {
|
||||
return TemplateResponser[Rp, D](c, fileTemplate(tplPath, ""))
|
||||
}
|
||||
|
||||
func TextTemplate[Rp router.RoutableProtocol, Resp any](c *router.Context[Rp], text string) TemplateEngine[Rp, Resp] {
|
||||
return TemplateEngine[Rp, Resp]{c, textTemplate(text, "")}
|
||||
func TextTemplate[Rp router.RoutableProtocol, D any](c *router.Context[Rp], text string) *router.Responser[Rp, D, D] {
|
||||
return TemplateResponser[Rp, D](c, textTemplate(text, ""))
|
||||
}
|
||||
|
||||
func EmptyTemplate[Rp router.RoutableProtocol](c *router.Context[Rp]) TemplateEngine[Rp, router.DoNotConvert] {
|
||||
return TemplateEngine[Rp, router.DoNotConvert]{Context: c}
|
||||
func StatusTemplate[Rp router.RoutableProtocol](c *router.Context[Rp]) *router.Responser[Rp, *router.Status, *router.Status] {
|
||||
return TemplateResponser[Rp, *router.Status](c, nil)
|
||||
}
|
||||
|
||||
func fileTemplate(tplPath string, key string) *template.Template {
|
||||
@@ -61,56 +61,29 @@ func textTemplate(text string, key string) *template.Template {
|
||||
})
|
||||
}
|
||||
|
||||
func (t *TemplateEngine[Rp, Resp]) Serialize(resp Resp) ([]byte, error) {
|
||||
type TemplateEngine[D any] struct {
|
||||
*template.Template
|
||||
}
|
||||
|
||||
func (t TemplateEngine[D]) Serialize(resp D) ([]byte, error) {
|
||||
tpl := t.Template
|
||||
if status, ok := any(resp).(*router.Status); ok {
|
||||
if status.Code == 0 {
|
||||
status.Code = util.ServiceUnavailable
|
||||
}
|
||||
if status.Code == 404 {
|
||||
tpl = statusTemplate(NotfoundTplId)
|
||||
} else {
|
||||
tpl = statusTemplate(StatusTplId)
|
||||
}
|
||||
}
|
||||
buff := new(bytes.Buffer)
|
||||
if err := t.tpl.Execute(buff, resp); err != nil {
|
||||
if err := tpl.Execute(buff, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
func (t *TemplateEngine[Rp, Resp]) Response(resp Resp) bool {
|
||||
if bs, err := t.Serialize(resp); err != nil {
|
||||
t.Rp.SetError(err)
|
||||
} else if _, err := t.Rp.Write(bs); err != nil {
|
||||
t.Rp.SetError(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *TemplateEngine[Rp, Resp]) Error(err error) bool {
|
||||
code, msg := util.ResponseUnits(err)
|
||||
return t.Status(false, msg, code, nil)
|
||||
}
|
||||
|
||||
func (t *TemplateEngine[Rp, Resp]) Success(data any) bool {
|
||||
return t.Status(true, "", 0, data)
|
||||
}
|
||||
|
||||
func (t *TemplateEngine[Rp, Resp]) Fail(msg string, code int) bool {
|
||||
return t.Status(false, msg, code, nil)
|
||||
}
|
||||
|
||||
func (t *TemplateEngine[Rp, Resp]) Status(status bool, msg string, code int, data any) bool {
|
||||
if code == 0 {
|
||||
code = util.ServiceUnavailable
|
||||
}
|
||||
s := router.Status{Status: status, Msg: msg, Code: code, Data: data}
|
||||
var tpl *template.Template
|
||||
if code == 404 {
|
||||
tpl = statusTemplate(NotfoundTplId)
|
||||
} else {
|
||||
tpl = statusTemplate(StatusTplId)
|
||||
}
|
||||
buff := new(bytes.Buffer)
|
||||
if err := tpl.Execute(buff, s); err != nil {
|
||||
t.Rp.SetError(err)
|
||||
} else if _, err := t.Write(buff.Bytes()); err != nil {
|
||||
t.Rp.SetError(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func statusTemplate(tplId string) *template.Template {
|
||||
if tplId == StatusTplId {
|
||||
if path, text := TplConfig.statusTpl(); len(path) > 0 {
|
||||
|
26
go.mod
Normal file
26
go.mod
Normal file
@@ -0,0 +1,26 @@
|
||||
module go.drunkce.com/dce
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require (
|
||||
github.com/coder/websocket v1.8.12
|
||||
github.com/quic-go/quic-go v0.49.0
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
google.golang.org/protobuf v1.36.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
)
|
14
go.work
14
go.work
@@ -1,15 +1,3 @@
|
||||
go 1.23.3
|
||||
|
||||
use (
|
||||
./converter
|
||||
./_examples
|
||||
./_examples/apis
|
||||
./proto
|
||||
./proto/flex
|
||||
./proto/json
|
||||
./proto/pb
|
||||
./router
|
||||
./session
|
||||
./session/redises
|
||||
./util
|
||||
)
|
||||
use .
|
@@ -3,12 +3,13 @@ package proto
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@@ -8,12 +8,12 @@ Protocol format:
|
||||
0 1 2 3 4 5 6 7 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
+-+-+-+-+-+-+-+-+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
||||
|I|P|S|C|M|L|N|R| LEN of| LEN of| LEN of| LEN of| ID | CODE |NumPath| same |
|
||||
|D|A|I|O|S|O|P|S| Path | Sid | Msg |Payload|FlexNum|FlexNum|FlexNum| order |
|
||||
|D|A|I|O|S|O|P|S| Path | Sid | Msg | Body |FlexNum|FlexNum|FlexNum| order |
|
||||
|E|T|D|D|G|A|A|V|FlexNum|FlexNum|FlexNum|FlexNum| HEAD | HEAD | HEAD |FlexNum|
|
||||
|N|H| |E| |D|T| | HEAD | HEAD | HEAD | HEAD | | | | BODY |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - - - - - - - - - - - - - - - - - - - - - - |
|
||||
| | | | |
|
||||
| Path | Sid | Msg | Payload Data ... |
|
||||
| Path | Sid | Msg | Body Data ... |
|
||||
| | | | |
|
||||
+-+-------------+-+-------------+-+-------------+-------------------------------+
|
||||
|
||||
|
@@ -2,14 +2,15 @@ package flex
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"io"
|
||||
"math"
|
||||
"math/bits"
|
||||
"net"
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
type PackageProtocol[Req any] struct {
|
||||
|
@@ -1,5 +0,0 @@
|
||||
module github.com/idrunk/dce-go/proto/flex
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require github.com/quic-go/quic-go v0.48.2
|
@@ -2,9 +2,10 @@ package flex
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Quic = router.Context[*QuicProtocol]
|
||||
|
@@ -2,10 +2,11 @@ package flex
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log/slog"
|
||||
"net"
|
||||
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Tcp = router.Context[*TcpProtocol]
|
||||
|
@@ -4,8 +4,9 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"net"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Udp = router.Context[*UdpProtocol]
|
||||
|
@@ -2,11 +2,12 @@ package flex
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/coder/websocket"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Websocket = router.Context[*WebsocketProtocol]
|
||||
|
@@ -1,3 +0,0 @@
|
||||
module github.com/idrunk/dce-go/proto
|
||||
|
||||
go 1.23.3
|
@@ -2,14 +2,15 @@ package proto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -26,7 +27,7 @@ var (
|
||||
|
||||
type Http = router.Context[*HttpProtocol]
|
||||
|
||||
const headerSidKey = "X-Session-Id"
|
||||
const HeaderSidKey = "X-Session-Id"
|
||||
|
||||
type HttpProtocol struct {
|
||||
router.Meta[*http.Request]
|
||||
@@ -65,13 +66,15 @@ func (h *HttpProtocol) Body() ([]byte, error) {
|
||||
return io.ReadAll(h.Req.Body)
|
||||
}
|
||||
|
||||
var headerSidKey string = strings.ToLower(HeaderSidKey)
|
||||
|
||||
func (h *HttpProtocol) Sid() string {
|
||||
if headerSid := h.Req.Header.Get(headerSidKey); len(headerSid) > 0 {
|
||||
if headerSid := h.Req.Header.Get(HeaderSidKey); len(headerSid) > 0 {
|
||||
return headerSid
|
||||
} else if cookies := h.Req.Cookies(); len(cookies) > 0 {
|
||||
if cookie, ok := util.SeqFrom(cookies).Find(func(c *http.Cookie) bool {
|
||||
lower := strings.ToLower((*c).Name)
|
||||
return lower == "session_id" || lower == "session-id" || lower == "x-session-sid"
|
||||
return lower == "session_id" || lower == "session-id" || lower == headerSidKey
|
||||
}); ok {
|
||||
return (*cookie).Value
|
||||
}
|
||||
@@ -153,10 +156,7 @@ func (h *WrappedHttpRouter) PushApi(api router.Api, controller func(c *Http)) *W
|
||||
}
|
||||
|
||||
func (h *WrappedHttpRouter) Route(writer http.ResponseWriter, request *http.Request) {
|
||||
hp := &HttpProtocol{
|
||||
Meta: router.NewMeta(request, nil, false),
|
||||
Writer: writer,
|
||||
}
|
||||
hp := NewHttpProtocol(writer, request)
|
||||
context := router.NewContext(hp)
|
||||
h.Raw().Route(context)
|
||||
hp.TryPrintErr()
|
||||
@@ -165,14 +165,14 @@ func (h *WrappedHttpRouter) Route(writer http.ResponseWriter, request *http.Requ
|
||||
}
|
||||
if hp.Error() != nil {
|
||||
var e util.Error
|
||||
if errors.As(hp.Error(), &e) && e.IsOpenly() && e.Code < 600 {
|
||||
hp.Writer.WriteHeader(e.Code)
|
||||
} else {
|
||||
if !errors.As(hp.Error(), &e) {
|
||||
hp.Writer.WriteHeader(util.ServiceUnavailable)
|
||||
} else if ! e.IsOpenly() || hp.ResponseEmpty() {
|
||||
hp.Writer.WriteHeader(util.Iif(e.Code > 0 && e.Code < 600, e.Code, util.ServiceUnavailable))
|
||||
}
|
||||
}
|
||||
if sid := hp.RespSid(); sid != "" {
|
||||
hp.Writer.Header().Set(headerSidKey, sid)
|
||||
hp.Writer.Header().Set(HeaderSidKey, sid)
|
||||
}
|
||||
bytes := hp.ClearBuffer()
|
||||
if _, err := hp.Writer.Write(bytes); err != nil {
|
||||
@@ -180,6 +180,13 @@ func (h *WrappedHttpRouter) Route(writer http.ResponseWriter, request *http.Requ
|
||||
}
|
||||
}
|
||||
|
||||
func NewHttpProtocol(writer http.ResponseWriter, request *http.Request) *HttpProtocol {
|
||||
return &HttpProtocol{
|
||||
Meta: router.NewMeta(request, nil, false),
|
||||
Writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
var HttpRouter *WrappedHttpRouter
|
||||
|
||||
func init() {
|
||||
|
@@ -1,3 +0,0 @@
|
||||
module github.com/idrunk/dce-go/proto/json
|
||||
|
||||
go 1.23.3
|
@@ -2,9 +2,10 @@ package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"math"
|
||||
"sync/atomic"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type PackageProtocol[Req any] struct {
|
||||
|
@@ -1,11 +1,12 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/proto/flex"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log/slog"
|
||||
"net"
|
||||
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Tcp = router.Context[*TcpProtocol]
|
||||
|
@@ -2,8 +2,9 @@ package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"net"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Udp = router.Context[*UdpProtocol]
|
||||
|
@@ -1,11 +1,12 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"github.com/coder/websocket"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Websocket = router.Context[*WebsocketProtocol]
|
||||
|
@@ -1,5 +0,0 @@
|
||||
module github.com/idrunk/dce-go/proto/pb
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require google.golang.org/protobuf v1.36.1
|
@@ -1,11 +1,12 @@
|
||||
package pb
|
||||
|
||||
import (
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"log/slog"
|
||||
"math"
|
||||
"sync/atomic"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type PackageProtocol[Req any] struct {
|
||||
|
@@ -1,11 +1,12 @@
|
||||
package pb
|
||||
|
||||
import (
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/proto/flex"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log/slog"
|
||||
"net"
|
||||
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/proto/flex"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Tcp = router.Context[*TcpProtocol]
|
||||
|
@@ -2,8 +2,9 @@ package pb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"net"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Udp = router.Context[*UdpProtocol]
|
||||
|
@@ -1,11 +1,12 @@
|
||||
package pb
|
||||
|
||||
import (
|
||||
"github.com/coder/websocket"
|
||||
"github.com/idrunk/dce-go/proto"
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"go.drunkce.com/dce/proto"
|
||||
"go.drunkce.com/dce/router"
|
||||
)
|
||||
|
||||
type Websocket = router.Context[*WebsocketProtocol]
|
||||
|
@@ -1,10 +1,12 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"github.com/idrunk/dce-go/router"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"go.drunkce.com/dce/router"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
func NewConnectorMappingManager[Rp router.RoutableProtocol, C any](routerId string) ConnectorMappingManager[Rp, C] {
|
||||
@@ -28,7 +30,7 @@ func (w *ConnectorMappingManager[Rp, C]) Unmapping(addr string) {
|
||||
|
||||
func (w *ConnectorMappingManager[Rp, C]) Except(addr string, err error) bool {
|
||||
w.Unmapping(addr)
|
||||
slog.Debug("Client disconnected with: %s", err.Error())
|
||||
slog.Debug(fmt.Sprintf("Client disconnected with: %s", err.Error()))
|
||||
return false
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,7 @@ import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -80,7 +80,7 @@ type Api struct {
|
||||
Responsive bool
|
||||
Redirect string
|
||||
Name string
|
||||
Extras map[string]any
|
||||
extras map[string]any
|
||||
}
|
||||
|
||||
func (a Api) ByMethod(method Method) Api {
|
||||
@@ -88,27 +88,17 @@ func (a Api) ByMethod(method Method) Api {
|
||||
return a
|
||||
}
|
||||
|
||||
func (a Api) BySuffix(suffixes Suffix) Api {
|
||||
a.Suffixes = append(a.Suffixes, suffixes)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a Api) BySuffixes(suffixes ...Suffix) Api {
|
||||
a.Suffixes = suffixes
|
||||
return a
|
||||
}
|
||||
|
||||
func (a Api) IsOmission() Api {
|
||||
func (a Api) AsOmission() Api {
|
||||
a.Omission = true
|
||||
return a
|
||||
}
|
||||
|
||||
func (a Api) IsResponsive() Api {
|
||||
func (a Api) AsResponsive() Api {
|
||||
a.Responsive = true
|
||||
return a
|
||||
}
|
||||
|
||||
func (a Api) Unresponsive() Api {
|
||||
func (a Api) AsUnresponsive() Api {
|
||||
a.Responsive = false
|
||||
return a
|
||||
}
|
||||
@@ -134,10 +124,10 @@ func (a Api) ByName(name string) Api {
|
||||
// Returns:
|
||||
// - The modified `Api` instance with the updated `Extras` map.
|
||||
func (a Api) With(key string, val any) Api {
|
||||
if a.Extras == nil {
|
||||
a.Extras = make(map[string]interface{})
|
||||
if a.extras == nil {
|
||||
a.extras = make(map[string]interface{})
|
||||
}
|
||||
a.Extras[key] = val
|
||||
a.extras[key] = val
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -153,15 +143,15 @@ func (a Api) With(key string, val any) Api {
|
||||
// Returns:
|
||||
// - The modified `Api` instance with the updated `Extras` map.
|
||||
func (a Api) Append(key string, items ...any) Api {
|
||||
if val := a.Extras[key]; val == nil {
|
||||
if a.Extras == nil {
|
||||
a.Extras = make(map[string]interface{})
|
||||
if val := a.extras[key]; val == nil {
|
||||
if a.extras == nil {
|
||||
a.extras = make(map[string]interface{})
|
||||
}
|
||||
a.Extras[key] = new([]any)
|
||||
a.extras[key] = new([]any)
|
||||
}
|
||||
val := a.Extras[key]
|
||||
if vec, ok := val.([]any); ok || vec == nil {
|
||||
a.Extras[key] = append(vec, items...)
|
||||
val := a.extras[key]
|
||||
if vec, ok := val.([]any); ok || len(vec) == 0 {
|
||||
a.extras[key] = append(vec, items...)
|
||||
} else {
|
||||
log.Panicf("Api with path \"%s\" was already has an extra keyd by \"%s\", but is not a slice value.", a.Path, key)
|
||||
}
|
||||
@@ -169,14 +159,14 @@ func (a Api) Append(key string, items ...any) Api {
|
||||
}
|
||||
|
||||
func (a Api) ExtraBy(key string) any {
|
||||
if val, ok := a.Extras[key]; ok {
|
||||
if val, ok := a.extras[key]; ok {
|
||||
return val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a Api) ExtrasBy(key string) []any {
|
||||
if val, ok := a.Extras[key]; ok {
|
||||
if val, ok := a.extras[key]; ok {
|
||||
if vec, ok := val.([]any); ok {
|
||||
return vec
|
||||
}
|
||||
@@ -206,7 +196,7 @@ func (a Api) Hosts() []string {
|
||||
}
|
||||
|
||||
func Path(path string) Api {
|
||||
return Api{Path: path}
|
||||
return Api{Path: path, Responsive: true}
|
||||
}
|
||||
|
||||
const extraServeAddrKey = "$#BIND-HOSTS#"
|
||||
|
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
// Context is a generic struct that encapsulates the state and functionality for handling RoutableProtocol requests.
|
||||
@@ -49,6 +49,13 @@ func (c *Context[Rp]) Param(key string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Context[Rp]) ParamOr(key string, def string) string {
|
||||
if param, ok := c.params[key]; ok {
|
||||
return param.Value()
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func (c *Context[Rp]) Params(key string) []string {
|
||||
if param, ok := c.params[key]; ok {
|
||||
return param.Values()
|
||||
|
@@ -3,47 +3,97 @@ package router
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
// RequestProcessor is a generic interface that defines the contract for processing requests.
|
||||
// It is parameterized with two types: Obj and Dto.
|
||||
// - Obj represents the type of the object that will be processed.
|
||||
// - Dto represents the type of the Data Transfer Object (DTO) that will be used for serialization or deserialization.
|
||||
// Implementations of this interface are expected to handle the processing logic for converting between Obj and Dto,
|
||||
// and for managing the request lifecycle, including error handling and response generation.
|
||||
type RequestProcessor[Obj, Dto any] interface {
|
||||
// Response processes the given response object of type Obj and returns a boolean indicating success or failure.
|
||||
// This method is typically used to handle the final output of a request processing pipeline.
|
||||
Response(resp Obj) bool
|
||||
|
||||
// Error handles an error encountered during request processing and returns a boolean indicating whether the error was successfully handled.
|
||||
// This method is used to manage error states and ensure proper error reporting.
|
||||
Error(err error) bool
|
||||
|
||||
// Success processes a successful response with the provided data and returns a boolean indicating success.
|
||||
// This method is used to handle successful outcomes and generate appropriate responses.
|
||||
Success(data any) bool
|
||||
|
||||
// Fail processes a failure response with the provided error message and status code, and returns a boolean indicating failure.
|
||||
// This method is used to handle failed outcomes and generate appropriate error responses.
|
||||
Fail(msg string, code int) bool
|
||||
|
||||
// Status processes a response with the provided status, message, status code, and data, and returns a boolean indicating success or failure.
|
||||
// This method is a more generalized version of Success and Fail, allowing for custom status handling.
|
||||
Status(status bool, msg string, code int, data any) bool
|
||||
type Requester[Rp RoutableProtocol, ReqDto, Req any] struct {
|
||||
Context *Context[Rp]
|
||||
Deserializer Deserializer[ReqDto]
|
||||
}
|
||||
|
||||
type Parser[Obj any] interface {
|
||||
Parse() (Obj, bool)
|
||||
func (r *Requester[Rp, ReqDto, Req]) Deserialize(seq []byte) (Req, error) {
|
||||
dto, err := r.Deserializer.Deserialize(seq)
|
||||
if err != nil {
|
||||
return util.NewStruct[Req](), err
|
||||
}
|
||||
return DtoInto[ReqDto, Req](dto)
|
||||
}
|
||||
|
||||
type Serializer[Dto any] interface {
|
||||
Serialize(dto Dto) ([]byte, error)
|
||||
func (r *Requester[Rp, ReqDto, Req]) Parse() (Req, bool) {
|
||||
if body, err := r.Context.Body(); err != nil {
|
||||
r.Context.Rp.SetError(err)
|
||||
} else if req, err := r.Deserialize(body); err != nil {
|
||||
r.Context.Rp.SetError(err)
|
||||
} else {
|
||||
return req, true
|
||||
}
|
||||
return util.NewStruct[Req](), false
|
||||
}
|
||||
|
||||
type Deserializer[Dto any] interface {
|
||||
Deserialize(bytes []byte) (Dto, error)
|
||||
|
||||
type Responser[Rp RoutableProtocol, Resp, RespDto any] struct {
|
||||
Context *Context[Rp]
|
||||
Serializer Serializer[RespDto]
|
||||
}
|
||||
|
||||
func (r *Responser[Rp, Resp, RespDto]) Serialize(obj Resp) ([]byte, error) {
|
||||
dto, err := DtoFrom[Resp, RespDto](obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.Serializer.Serialize(dto)
|
||||
}
|
||||
|
||||
func (r *Responser[Rp, Resp, RespDto]) Response(obj Resp) bool {
|
||||
if seq, err := r.Serialize(obj); err != nil {
|
||||
r.Context.Rp.SetError(err)
|
||||
} else if _, err := r.Context.Write(seq); err != nil {
|
||||
r.Context.Rp.SetError(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *Responser[Rp, Resp, RespDto]) Error(err error) bool {
|
||||
r.Context.Rp.SetError(err)
|
||||
code, msg := util.ResponseUnits(err)
|
||||
return r.Status(false, msg, code, nil)
|
||||
}
|
||||
|
||||
func (r *Responser[Rp, Resp, RespDto]) Success(data *string) bool {
|
||||
return r.Status(true, "", 0, data)
|
||||
}
|
||||
|
||||
func (r *Responser[Rp, Resp, RespDto]) Fail(msg string, code int) bool {
|
||||
return r.Status(false, msg, code, nil)
|
||||
}
|
||||
|
||||
func (r *Responser[Rp, Resp, RespDto]) Status(status bool, msg string, code int, data *string) bool {
|
||||
if ! status && r.Context.Rp.Error() == nil {
|
||||
r.Context.Rp.SetError(util.Openly(code, "%s", msg))
|
||||
}
|
||||
if reflect.TypeFor[Resp]() == reflect.TypeFor[*Status]() {
|
||||
obj := &Status{Status: status, Msg: msg, Code: code, Data: data}
|
||||
return r.Response(any(obj).(Resp))
|
||||
} else if data != nil {
|
||||
r.Context.WriteString(*data)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Status bool `json:"status,omitempty"`
|
||||
Code int `json:"code,omitempty"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
Data *string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
type Serializer[T any] interface {
|
||||
Serialize(obj T) ([]byte, error)
|
||||
}
|
||||
|
||||
type Deserializer[T any] interface {
|
||||
Deserialize(bytes []byte) (T, error)
|
||||
}
|
||||
|
||||
type Into[T any] interface {
|
||||
@@ -55,30 +105,21 @@ type From[S, T any] interface {
|
||||
}
|
||||
|
||||
func DtoInto[Dto, Obj any](dto Dto) (Obj, error) {
|
||||
if d, ok := any(dto).(Obj); ok {
|
||||
return d, nil
|
||||
} else if d2, ok2 := any(&dto).(Into[Obj]); ok2 {
|
||||
return d2.Into()
|
||||
if obj, ok := any(dto).(Obj); ok {
|
||||
return obj, nil
|
||||
} else if dto, ok := any(dto).(Into[Obj]); ok {
|
||||
return dto.Into()
|
||||
}
|
||||
var obj Obj
|
||||
return obj, util.Closed0(`Type "%s" doesn't implement the "%s" interface`, reflect.TypeFor[Dto](), reflect.TypeFor[Into[Obj]]())
|
||||
return util.NewStruct[Obj](), util.Closed0(`Type "%s" doesn't implement the "%s" interface`, reflect.TypeFor[Dto](), reflect.TypeFor[Into[Obj]]())
|
||||
}
|
||||
|
||||
func DtoFrom[Obj, Dto any](obj Obj) (Dto, error) {
|
||||
dto := new(Dto)
|
||||
if d, ok := any(obj).(Dto); ok {
|
||||
return d, nil
|
||||
} else if d2, ok2 := any(dto).(From[Obj, Dto]); ok2 {
|
||||
return d2.From(obj)
|
||||
if dto, ok := any(obj).(Dto); ok {
|
||||
return dto, nil
|
||||
}
|
||||
return *dto, util.Closed0(`Type "%s" doesn't implement the "%s" interface`, reflect.TypeFor[Dto](), reflect.TypeFor[From[Obj, Dto]]())
|
||||
}
|
||||
|
||||
type DoNotConvert uint8
|
||||
|
||||
type Status struct {
|
||||
Status bool `json:"status,omitempty"`
|
||||
Code int `json:"code,omitempty"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
Data any `json:"data,omitempty"`
|
||||
var emp Dto
|
||||
if dto, ok := any(emp).(From[Obj, Dto]); ok {
|
||||
return dto.From(obj)
|
||||
}
|
||||
return emp, util.Closed0(`Type "%s" doesn't implement the "%s" interface`, reflect.TypeFor[Dto](), reflect.TypeFor[From[Obj, Dto]]())
|
||||
}
|
||||
|
@@ -1,3 +0,0 @@
|
||||
module github.com/idrunk/dce-go/router
|
||||
|
||||
go 1.23.3
|
@@ -6,8 +6,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/idrunk/dce-go/session"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"go.drunkce.com/dce/session"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,7 +56,7 @@ func (m *Meta[Req]) ClearBuffer() []byte {
|
||||
return bs
|
||||
}
|
||||
|
||||
func (m *Meta[Req]) NotResponse() bool {
|
||||
func (m *Meta[Req]) ResponseEmpty() bool {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.respBuffer.Len() == 0
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
const CodeNotFound = 404
|
||||
@@ -167,7 +167,7 @@ func (r *Router[Rp]) suffixPath(path string, suffix *Suffix) string {
|
||||
if suffix == nil || len(*suffix) == 0 {
|
||||
return path
|
||||
}
|
||||
return fmt.Sprintf("%s%s%s", path, MarkSuffixBoundary, suffix)
|
||||
return fmt.Sprintf("%s%s%s", path, MarkSuffixBoundary, *suffix)
|
||||
}
|
||||
|
||||
func (r *Router[Rp]) buildTree() {
|
||||
@@ -202,8 +202,15 @@ func (r *Router[Rp]) buildTree() {
|
||||
fills = append(fills, util.NewTuple2(path, newApiBranch(path, []*RpApi[Rp]{})))
|
||||
}
|
||||
}
|
||||
// original remain should directly insert
|
||||
fills = append(fills, util.NewTuple2(remain.Path, remain))
|
||||
// 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 {
|
||||
return tuple.A == remain.Path && len(tuple.B.Apis) == 0
|
||||
}); index > -1 {
|
||||
fills[index] = util.NewTuple2(remain.Path, remain)
|
||||
} else {
|
||||
// Original remain should directly insert
|
||||
fills = append(fills, util.NewTuple2(remain.Path, remain))
|
||||
}
|
||||
}
|
||||
for _, fill := range fills {
|
||||
_, _ = tree.SetByPath(strings.Split(fill.A, MarkPathPartSeparator), fill.B)
|
||||
@@ -268,13 +275,13 @@ func (r *Router[Rp]) locate(path string, apiFinder func([]*RpApi[Rp]) (*RpApi[Rp
|
||||
}
|
||||
return nil, nil, nil, util.Openly(CodeNotFound, `path "%s" route failed, could not matched by Router`, path)
|
||||
}
|
||||
slog.Debug(`%s: path "%s" matched api "%s"`, reflect.TypeFor[Rp](), reqPath, api.Path)
|
||||
slog.Debug(fmt.Sprintf(`%s: path "%s" matched api "%s"`, reflect.TypeFor[Rp](), reqPath, api.Path))
|
||||
return api, pathParams, suffix, nil
|
||||
}
|
||||
|
||||
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]{{&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
|
||||
@@ -423,7 +430,7 @@ func (r *Router[Rp]) routedHandle(api *RpApi[Rp], pathParams map[string]Param, s
|
||||
|
||||
func (r *Router[Rp]) idLocate(id string) (*RpApi[Rp], error) {
|
||||
if api, ok := r.idApiMapping[id]; ok {
|
||||
slog.Debug(`%s: Uid "%s" matched api "%s"`, reflect.TypeFor[Rp](), id, api.Path)
|
||||
slog.Debug(fmt.Sprintf(`%s: Uid "%s" matched api "%s"`, reflect.TypeFor[Rp](), id, api.Path))
|
||||
return api, nil
|
||||
}
|
||||
return nil, util.Openly(CodeNotFound, `Uid "%s" route failed, could not matched by Router`, id)
|
||||
|
@@ -4,7 +4,7 @@ import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
const DefaultNewSidField = "$newid"
|
||||
|
@@ -1,18 +1,10 @@
|
||||
package session
|
||||
|
||||
import "maps"
|
||||
|
||||
const (
|
||||
DefaultServerField = "$server"
|
||||
DefaultClientField = "$client"
|
||||
)
|
||||
|
||||
const (
|
||||
// typeInfoShadow = 0
|
||||
typeEntryShadow = iota + 1
|
||||
typeRequest
|
||||
)
|
||||
|
||||
// ConnectionSession represents a session associated with a connection. It extends the BasicSession
|
||||
// and implements the IfConnection interface. This struct is used to manage session information
|
||||
// for both server and client connections, including handling session updates, disconnections,
|
||||
@@ -23,7 +15,7 @@ type ConnectionSession struct {
|
||||
// shadow is a connection level session used to update the connection session through this attribute when the sid
|
||||
// is updated under the request session, in order to update session information when the connection is disconnected
|
||||
shadow *ConnectionSession
|
||||
ty int
|
||||
request bool
|
||||
serverField string
|
||||
clientField string
|
||||
// log the connection info to let the request session to store it
|
||||
@@ -42,12 +34,10 @@ func (c *ConnectionSession) CloneConnection(cloned IfConnection, basic *BasicSes
|
||||
return &connection
|
||||
}
|
||||
|
||||
func (c *ConnectionSession) Connect(server string, client string, entry bool) *ConnectionSession {
|
||||
// Connect bind the addresses of server and client to the session.
|
||||
func (c *ConnectionSession) Connect(server string, client string) *ConnectionSession {
|
||||
c.serverToBind = server
|
||||
c.clientToBind = client
|
||||
if entry {
|
||||
c.ty = typeEntryShadow
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -61,7 +51,7 @@ func (c *ConnectionSession) Disconnect() error {
|
||||
}
|
||||
|
||||
func (c *ConnectionSession) Request() bool {
|
||||
return c.ty == typeRequest
|
||||
return c.request
|
||||
}
|
||||
|
||||
func (c *ConnectionSession) UpdateShadow(newSid string) error {
|
||||
@@ -81,21 +71,11 @@ func (c *ConnectionSession) CloneForRequest(sid string) (any, error) {
|
||||
func (c *ConnectionSession) handleClonedRequest(original *ConnectionSession) {
|
||||
c.newborn = false
|
||||
c.shadow = original
|
||||
c.ty = typeRequest
|
||||
c.request = true
|
||||
if len(original.serverToBind) > 0 {
|
||||
c.serverToBind, c.clientToBind = original.serverToBind, original.clientToBind
|
||||
data := map[string]interface{}{}
|
||||
// for some protocol could connect with session id, could be had information stored
|
||||
if original.ty == typeEntryShadow {
|
||||
if raw, err := original.Raw(); err == nil {
|
||||
data = raw
|
||||
}
|
||||
}
|
||||
maps.Copy(data, map[string]any{
|
||||
original.serverField: original.serverToBind,
|
||||
original.clientField: original.clientToBind,
|
||||
})
|
||||
if err := c.Load(data); err == nil {
|
||||
if err := c.SilentSet(c.serverField, c.serverToBind); err == nil {
|
||||
c.SilentSet(c.clientField, c.clientToBind)
|
||||
original.serverToBind, original.clientToBind = "", ""
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +0,0 @@
|
||||
module github.com/idrunk/dce-go/session
|
||||
|
||||
go 1.23.3
|
@@ -1,5 +0,0 @@
|
||||
module github.com/idrunk/dce-go/session/redises
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.7.0
|
@@ -6,9 +6,9 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/idrunk/dce-go/session"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.drunkce.com/dce/session"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
// Session is a generic struct that represents a session in a Redis-backed session management system.
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
const DefaultIdName = "dcesid"
|
||||
@@ -99,7 +99,7 @@ func GenSid(ttlMinutes uint16) (sid string, createStamp int64) {
|
||||
now := time.Now()
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(fmt.Sprintf("%d-%d", now.UnixNano(), rand.Uint())))
|
||||
return fmt.Sprintf("%X%04X%X", hash.Sum(nil), ttlMinutes, now.Unix()), now.Unix()
|
||||
return fmt.Sprintf("%x%04x%x", hash.Sum(nil), ttlMinutes, now.Unix()), now.Unix()
|
||||
}
|
||||
|
||||
func (b *BasicSession) CloneBasic(cloned IfSession, id string) (*BasicSession, error) {
|
||||
@@ -142,10 +142,10 @@ func (b *BasicSession) Set(field string, value any) error {
|
||||
val, err := TryMarshal(value, b.IfSession.NeedSerial())
|
||||
if err != nil {
|
||||
return err
|
||||
} else if err := b.TryTouch(); err != nil {
|
||||
} else if err := b.SilentSet(field, val); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.SilentSet(field, val)
|
||||
return b.TryTouch()
|
||||
}
|
||||
|
||||
func (b *BasicSession) Get(field string, target any) error {
|
||||
|
@@ -2,12 +2,13 @@ package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
// sessionMapping map[string]*shmMeta
|
||||
|
@@ -3,7 +3,7 @@ package session
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/idrunk/dce-go/util"
|
||||
"go.drunkce.com/dce/util"
|
||||
)
|
||||
|
||||
const DefaultUserPrefix = "dceusmap"
|
||||
|
@@ -1,3 +0,0 @@
|
||||
module github.com/idrunk/dce-go/util
|
||||
|
||||
go 1.23.3
|
@@ -3,6 +3,7 @@ package util
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@@ -83,5 +84,14 @@ func Iif[T any](test bool, trueVal T, falseVal T) T {
|
||||
|
||||
func NewStruct[T any]() T {
|
||||
var t T
|
||||
return t
|
||||
ty := reflect.TypeOf(t)
|
||||
if ty.Kind() != reflect.Ptr {
|
||||
return t
|
||||
}
|
||||
v := reflect.New(ty.Elem())
|
||||
return v.Interface().(T)
|
||||
}
|
||||
|
||||
func Ref[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
Reference in New Issue
Block a user