构建一个即时消息应用(五):实时消息

开发 后端
对于实时消息,我们将使用 服务器发送事件。这是一个打开的连接,我们可以在其中传输数据流。我们会有个端点,用户会在其中订阅发送给他的所有消息。

[[345206]]

本文是该系列的第五篇。

对于实时消息,我们将使用 服务器发送事件Server-Sent Events。这是一个打开的连接,我们可以在其中传输数据流。我们会有个端点,用户会在其中订阅发送给他的所有消息。

消息户端

在 HTTP 部分之前,让我们先编写一个映射map ,让所有客户端都监听消息。 像这样全局初始化:

type MessageClient struct { 
    Messages chan Message 
    UserID   string 

 
var messageClients sync.Map 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

已创建的新消息

还记得在 上一篇文章 中,当我们创建这条消息时,我们留下了一个 “TODO” 注释。在那里,我们将使用这个函数来调度一个 goroutine。

go messageCreated(message) 
  • 1.

把这行代码插入到我们留注释的位置。

func messageCreated(message Message) error { 
    if err := db.QueryRow(` 
        SELECT user_id FROM participants 
        WHERE user_id != $1 and conversation_id = $2 
    `, message.UserID, message.ConversationID). 
    Scan(&message.ReceiverID); err != nil { 
        return err 
    } 
 
    go broadcastMessage(message) 
 
    return nil 

 
func broadcastMessage(message Message) { 
    messageClients.Range(func(key, _ interface{}) bool { 
        client := key.(*MessageClient) 
        if client.UserID == message.ReceiverID { 
            client.Messages <- message 
        } 
        return true 
    }) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

该函数查询接收者 ID(其他参与者 ID),并将消息发送给所有客户端。

订阅消息

让我们转到 main() 函数并添加以下路由:

router.HandleFunc("GET""/api/messages", guard(subscribeToMessages)) 
  • 1.

此端点处理 /api/messages 上的 GET 请求。请求应该是一个 EventSource 连接。它用一个事件流响应,其中的数据是 JSON 格式的。

func subscribeToMessages(w http.ResponseWriter, r *http.Request) { 
    if a := r.Header.Get("Accept"); !strings.Contains(a, "text/event-stream") { 
        http.Error(w, "This endpoint requires an EventSource connection", http.StatusNotAcceptable) 
        return 
    } 
 
    f, ok := w.(http.Flusher) 
    if !ok { 
        respondError(w, errors.New("streaming unsupported")) 
        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"
 
    messages := make(chan Message) 
    defer close(messages) 
 
    client := &MessageClient{Messages: messages, UserID: authUserID} 
    messageClients.Store(client, nil) 
    defer messageClients.Delete(client) 
 
    for { 
        select { 
        case <-ctx.Done(): 
            return 
        case message := <-messages: 
            if b, err := json.Marshal(message); err != nil { 
                log.Printf("could not marshall message: %v\n", err) 
                fmt.Fprintf(w, "event: error\ndata: %v\n\n", err) 
            } else { 
                fmt.Fprintf(w, "data: %s\n\n", b) 
            } 
            f.Flush() 
        } 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.

首先,它检查请求头是否正确,并检查服务器是否支持流式传输。我们创建一个消息通道,用它来构建一个客户端,并将其存储在客户端映射中。每当创建新消息时,它都会进入这个通道,因此我们可以通过 for-select 循环从中读取。

服务器发送事件Server-Sent Events使用以下格式发送数据:

data: some data here\n\n 
  • 1.

我们以 JSON 格式发送:

data: {"foo":"bar"}\n\n 
  • 1.

我们使用 fmt.Fprintf() 以这种格式写入响应写入器writter,并在循环的每次迭代中刷新数据。

这个循环会一直运行,直到使用请求上下文关闭连接为止。我们延迟了通道的关闭和客户端的删除,因此,当循环结束时,通道将被关闭,客户端不会收到更多的消息。

注意,服务器发送事件Server-Sent Events(EventSource)的 JavaScript API 不支持设置自定义请求头😒,所以我们不能设置 Authorization: Bearer <token>。这就是为什么 guard() 中间件也会从 URL 查询字符串中读取令牌的原因。


实时消息部分到此结束。我想说的是,这就是后端的全部内容。但是为了编写前端代码,我将再增加一个登录端点:一个仅用于开发的登录。

 

 

责任编辑:庞桂玉 来源: Linux中国
相关推荐

2020-10-09 12:45:19

创建消息即时消息编程语言

2019-09-29 15:25:13

CockroachDBGoJavaScript

2019-10-28 20:12:40

OAuthGuard中间件编程语言

2020-03-31 12:21:20

JSON即时消息编程语言

2020-10-12 09:20:13

即时消息Access页面编程语言

2020-10-19 16:20:38

即时消息Conversatio编程语言

2020-10-16 14:40:20

即时消息Home页面编程语言

2020-10-10 20:51:10

即时消息编程语言

2021-03-25 08:29:33

SpringBootWebSocket即时消息

2023-08-14 08:01:12

websocket8g用户

2015-03-18 15:37:19

社交APP场景

2011-10-19 09:30:23

jQuery

2024-02-01 12:54:00

RustWebSocket消息代理

2023-11-30 08:34:29

批量消息消息队列

2023-03-27 08:33:32

2021-12-03 00:02:01

通讯工具即时

2010-05-24 09:51:37

System Cent

2021-05-10 15:05:18

消息通信本地网络

2024-04-24 11:42:21

Redis延迟消息数据库

2022-08-30 11:41:53

网络攻击木马
点赞
收藏

51CTO技术栈公众号