wechat icon indicating copy to clipboard operation
wechat copied to clipboard

对于 officialAccount.GetServer() 的建议

Open ywanbing opened this issue 3 years ago • 19 comments

问题及现象


func serveWechat(rw http.ResponseWriter, req *http.Request) {

	// 传入request和responseWriter
	server := officialAccount.GetServer(req, rw)
	fmt.Printf("new server %T, %p",server,server)
	//设置接收消息的处理方法
	server.SetMessageHandler(func(msg message.MixMessage) *message.Reply {
		return nil
	})

	//处理消息接收以及回复
	err := server.Serve()
	if err != nil {
		fmt.Println(err)
		return
	}
	//发送回复的消息
	server.Send()
}

func main() {
	http.HandleFunc("/wechat", serveWechat)
	fmt.Println("wechat server listener at", ":80")
	err := http.ListenAndServe(":80", nil)
	if err != nil {
		fmt.Printf("start server error , err=%v", err)
	}
}

每次 http 请求过来都会去调用 officialAccount.GetServer,然而该方法每次都会新建一个 server 对象,但事实上只有 W 和 R 是不一样的。

建议

  1. 如果不想大幅度的改动代码,可以加一个 server 的对象池 (sync.pool )
  2. 如果想更好的性能,可以参考其他框架的 中间件设计。可以避免大量新建对象。

ywanbing avatar Nov 26 '20 03:11 ywanbing

感谢您的建议;

这块的实现可以通过实现 http.Handler 来做。

silenceper avatar Nov 26 '20 03:11 silenceper

感谢您的建议;

这块的实现可以通过实现http.Handler来做。

通过实现 http.Handler 自己来做 那么就用不到你的 server 封装了。

如果使 server 来实现 http.Handler 还是面临 W 和 R 的问题,因为 server 中 使用了 server.Requestserver.Writer


//Server struct
type Server struct {
	*context.Context
	Writer  http.ResponseWriter
	Request *http.Request

	skipValidate bool

	openID string

	messageHandler func(message.MixMessage) *message.Reply

	RequestRawXMLMsg  []byte
	RequestMsg        message.MixMessage
	ResponseRawXMLMsg []byte
	ResponseMsg       interface{}

	isSafeMode bool
	random     []byte
	nonce      string
	timestamp  int64
}

// GetServer 消息管理:接收事件,被动回复消息管理
func (officialAccount *OfficialAccount) GetServer(req *http.Request, writer http.ResponseWriter) *server.Server {
	srv := server.NewServer(officialAccount.ctx)
	srv.Request = req
	srv.Writer = writer
	return srv
}


在使用同一个 server 处理事件的时候 W R 可能被后来的覆盖,所以只能新建 server 来保证隔离性。

所以这又回到了最开始的问题,一直新建 server 来处理。

要同一个 server 处理,那么就不能有修改 server W 和 R 的操作。

ywanbing avatar Nov 26 '20 03:11 ywanbing

是的,所以Request和Write就可能不能通过GetServer 来进行传入了。

silenceper avatar Nov 26 '20 04:11 silenceper

可以把 Request 和 Write 提出来通过参数的方式传递,或者是 context 参数(类似gin.context ) ,这样就可以使用一个全局的server 来处理这一类的事件。

当然为了 兼容性,只能新增一个 server 实现。

ywanbing avatar Nov 26 '20 04:11 ywanbing

我也觉得这样每次都创建一个server方式不太好

owen-gxz avatar Aug 31 '21 15:08 owen-gxz

@ywanbing 新人能请教一下
gin 如何中集成 微信开发的入口吗?

`r.GET("/wechat/index",Windex)

func Windex() { wc := w.NewWechat() redisOpts := &cache.RedisOpts{ Host: "127.0.0.1", Database: 0, MaxActive: 10, MaxIdle: 10, IdleTimeout: 60, } redisCache := cache.NewRedis(redisOpts) oa := wc.GetOfficialAccount(cfg) s := oa.GetServer(req, rw) } ` req, rw 怎么定义呢?

feng003 avatar Oct 24 '21 06:10 feng003

@ywanbing 新人能请教一下 gin 如何中集成 微信开发的入口吗?

`r.GET("/wechat/index",Windex)

func Windex() { wc := w.NewWechat() redisOpts := &cache.RedisOpts{ Host: "127.0.0.1", Database: 0, MaxActive: 10, MaxIdle: 10, IdleTimeout: 60, } redisCache := cache.NewRedis(redisOpts) oa := wc.GetOfficialAccount(cfg) s := oa.GetServer(req, rw) } ` req, rw 怎么定义呢?

对于gin的框架其实也是把 req 和 resp 都封装在 gin.context 中了

gin中的源码:

// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
	writermem responseWriter
	Request   *http.Request    // req 
	Writer    ResponseWriter  // resp
        ...
}

你把 这两个参数拿出来 传入到 GetServer(req, rw) 中即可。

并且在这个 例子 中也存在gin的集成。

ywanbing avatar Oct 24 '21 07:10 ywanbing

@ywanbing 谢谢了

feng003 avatar Oct 24 '21 07:10 feng003

感谢您的建议; 这块的实现可以通过实现http.Handler来做。

通过实现 http.Handler 自己来做 那么就用不到你的 server 封装了。

如果使 server 来实现 http.Handler 还是面临 W 和 R 的问题,因为 server 中 使用了 server.Requestserver.Writer

//Server struct
type Server struct {
	*context.Context
	Writer  http.ResponseWriter
	Request *http.Request

	skipValidate bool

	openID string

	messageHandler func(message.MixMessage) *message.Reply

	RequestRawXMLMsg  []byte
	RequestMsg        message.MixMessage
	ResponseRawXMLMsg []byte
	ResponseMsg       interface{}

	isSafeMode bool
	random     []byte
	nonce      string
	timestamp  int64
}

// GetServer 消息管理:接收事件,被动回复消息管理
func (officialAccount *OfficialAccount) GetServer(req *http.Request, writer http.ResponseWriter) *server.Server {
	srv := server.NewServer(officialAccount.ctx)
	srv.Request = req
	srv.Writer = writer
	return srv
}

在使用同一个 server 处理事件的时候 W R 可能被后来的覆盖,所以只能新建 server 来保证隔离性。

所以这又回到了最开始的问题,一直新建 server 来处理。

要同一个 server 处理,那么就不能有修改 server W 和 R 的操作。

不就是实现单例模式吗?那更方便

ihaiyan avatar Nov 05 '21 21:11 ihaiyan

感谢您的建议; 这块的实现可以通过实现http.Handler来做。

通过实现 http.Handler 自己来做 那么就用不到你的 server 封装了。 如果使 server 来实现 http.Handler 还是面临 W 和 R 的问题,因为 server 中 使用了 server.Requestserver.Writer

//Server struct
type Server struct {
	*context.Context
	Writer  http.ResponseWriter
	Request *http.Request

	skipValidate bool

	openID string

	messageHandler func(message.MixMessage) *message.Reply

	RequestRawXMLMsg  []byte
	RequestMsg        message.MixMessage
	ResponseRawXMLMsg []byte
	ResponseMsg       interface{}

	isSafeMode bool
	random     []byte
	nonce      string
	timestamp  int64
}

// GetServer 消息管理:接收事件,被动回复消息管理
func (officialAccount *OfficialAccount) GetServer(req *http.Request, writer http.ResponseWriter) *server.Server {
	srv := server.NewServer(officialAccount.ctx)
	srv.Request = req
	srv.Writer = writer
	return srv
}

在使用同一个 server 处理事件的时候 W R 可能被后来的覆盖,所以只能新建 server 来保证隔离性。 所以这又回到了最开始的问题,一直新建 server 来处理。 要同一个 server 处理,那么就不能有修改 server W 和 R 的操作。

不就是实现单例模式吗?那更方便

在高并发的情况下,单例模式会导致后面的请求覆盖上一个请求,这是完全错误的想法。

waro163 avatar Jul 31 '22 10:07 waro163

我当前也是想实现连接池,看到已经有pr了,但是目前没有合并,可能是实现方面有一些问题,很想和各位讨论下打算如何做? 我的想法是将连接池放在OfficialAccount结构体内部,当连接放回的时候,需要Server提供reset方法重置。

waro163 avatar Jul 31 '22 10:07 waro163

我现在的想法是采用一个server 启动,注册一个handler 接受 req和resp 在内部采用对象池,用户无感知。

ywanbing avatar Aug 27 '22 06:08 ywanbing

晚一点出一个demo 来讨论一下

ywanbing avatar Aug 27 '22 06:08 ywanbing


// GetServerHandler 消息管理:接收事件,被动回复消息管理
func (officialAccount *OfficialAccount) GetServerHandler(messageHandler func(*message.MixMessage) *message.Reply) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		srv := server.NewServer(officialAccount.ctx)
		srv.Writer = writer
		srv.Request = request
		// 回收 server 对象
		defer srv.Close()
		
		srv.SetMessageHandler(messageHandler)
		srv.Serve()
		srv.Send()
	}
}

采用注册 handler 的方式在内部管理 server 对象的创建,不知道这种方式是否可行; 毕竟距离上次已经有一段时间了,暂时还不清楚 server 是否可以封装在项目内部。

@silenceper

ywanbing avatar Aug 27 '22 11:08 ywanbing

这个有最新的想法了吗?@ywanbing

tiantianlikeu avatar May 08 '23 09:05 tiantianlikeu

我现在是自己复写了这个类,这个类只用来获取消息,不去新建server了。 https://github.com/tiantianlikeu/wechat-go-gin

tiantianlikeu avatar May 09 '23 06:05 tiantianlikeu

我现在是自己复写了这个类,这个类只用来获取消息,不去新建server了。 https://github.com/tiantianlikeu/wechat-go-gin

赞~

GitHub
wechat-go-gin. Contribute to tiantianlikeu/wechat-go-gin development by creating an account on GitHub.

gzlboy avatar May 31 '23 08:05 gzlboy

不知道作者出于什么考虑每次请求都要创建用于处理消息回复的server对象呢?逻辑上就不大对,对于微信发给服务端的请求,只有get/post两种类型,服务端收到请求做好消息体的解析并做相应的处理就可以了,没必要创建大量的server对象处理,参考WxJava库的实现

20230531173225

gzlboy avatar May 31 '23 09:05 gzlboy

目前没有太多精力再开源项目上面;

如果针对这个问题有更好的建议,可以提供一个方案以及一些demo; 做一些可行性和易用性的验证,讨论之后 可以共同实现;

silenceper avatar May 31 '23 13:05 silenceper