当前位置: 首页 > 科技观察

构建一个即时通讯应用(五):实时通讯

时间:2023-03-19 22:42:06 科技观察

本文是系列文章的第五篇。第1部分:模式第2部分:OAuth第3部分:对话框第4部分:消息对于实时消息,我们将使用Server-SentEvents。这是一个开放的连接,我们可以在其中传输数据。我们将有一个端点,用户将在其中订阅发送给他的所有消息。消息客户端在HTTP部分之前,我们先写一个映射map,让所有客户端监听消息。全局初始化像这样:typeMessageClientstruct{MessageschanMessageUserIDstring}varmessageClientssync.Mapcreatednewmessage还记得在上一篇文章中,当我们创建这条消息时,我们留下了“TODO”注释。在那里,我们将使用这个函数来安排一个goroutine。gomessageCreated(message)在我们留下评论的地方插入这行代码。funcmessageCreated(messageMes??sage)error{iferr:=db.QueryRow(`SELECTuser_idFROMparticipantsWHEREuser_id!=$1andconversation_id=$2`,message.UserID,message.ConversationID).Scan(&message.ReceiverID);err!=nil{returnerr}gobroadcastMessage(消息返回nil这函数查询接收者ID(其他参与者ID),并将消息发送给所有客户端。订阅消息让我们转到main()函数并添加以下路由:router.HandleFunc("GET","/api/messages",guard(subscribeToMessages))此端点处理/api/messages上的GET请求。该请求应该是一个EventSource连接。它使用数据为JSON格式的事件流进行响应。funcsubscribeToMessages(whttp.ResponseWriter,r*http.Request){ifa:=r.Header.Get("Accept");!strings.Contains(a,"text/event-stream"){http.Error(w,"ThisendpointrequiresanEventSourceconnection",http.StatusNotAcceptable)return}f,ok:=w.(http.Flusher)if!ok{respondError(w,errors.New("streamingunsupported"))return}ctx:=r.Context()authUserID:=ctx.Value(keyAuthUserID).(string)h:=w.Header()h.Set("Cache-Control","no-cache")h.Set("Connection","keep-alive")h.Set("Content-Type","text/event-stream")消息:=make(chanMessage)deferclose(messages)client:=&MessageClient{Messages:messages,UserID:authUserID}messageClients.Store(client,nil)defermessageClients.Delete(client)for{select{case<-ctx.Done():returncasemessage:=<-messages:ifb,err:=json.Marshal(message);err!=nil{log.Printf("couldnotmarshallmessage:%v\n",err)fmt.Fprintf(w,"事件:错误\n数据:%v\n\n",err)}else{fmt.Fprintf(w,"data:%s\n\n",b)}f.Flush()}}}首先检查请求头是否是正确,为了检查服务器是否支持流式传输,我们创建了一个消息通道,用它来构建客户端,并将其存储在客户端映射中。每当创建新消息时,它都会进入此通道,因此我们可以通过for-selectloopreadsfrom.Server-SentEventsServer-SentEvents使用以下格式发送数据:data:somedatahere\n\n我们以JSON格式发送:data:{"foo":"bar"}\n\n我们使用fmt.Fprintf()以这种格式写入响应编写器,并在循环的每次迭代中刷新数据。这个循环一直运行,直到使用请求上下文关闭连接。我们延迟关闭通道和客户端,所以当循环结束,通道将关闭,客户端将不再接收消息。请注意,用于服务器发送事件(EventSource)的JavaScriptAPI不支持设置自定义请求标头😒,因此我们无法设置作者化:不记名。这就是guard()中间件还从URL查询字符串中读取令牌的原因。实时消息部分到此结束。我会说,这就是后端的全部。但是为了编写前端代码,我将再添加一个登录端点:一个仅用于开发的登录。源代码