联系管理员

开通文章发布权限

扫码 添加微信
微信图片
电话:15897897836 QQ:2106973531

Go + React 实现 Server-Sent Events (SSE) 详细教程

第一部分:Go 后端实现

1. 创建项目目录结构

mkdir go-react-sse
cd go-react-sse
mkdir backend frontend

2. 初始化Go模块

cd backend
go mod init sse-demo

3. 创建主程序文件 main.go

package main

import (
	"fmt"
	"net/http"
	"time"
)

func main() {
	// 设置路由
	http.HandleFunc("/events", sseHandler)
	http.HandleFunc("/", homeHandler)

	// 启动服务器
	fmt.Println("Server running on :8080")
	http.ListenAndServe(":8080", nil)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	fmt.Fprintf(w, `
	<!DOCTYPE html>
	<html>
	<head>
		<title>SSE Demo</title>
	</head>
	<body>
		<h1>Go SSE Server</h1>
		<p>This is the backend server. Open React app to see SSE in action.</p>
	</body>
	</html>
	`)
}

func sseHandler(w http.ResponseWriter, r *http.Request) {
	// 设置SSE所需的响应头
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("Access-Control-Allow-Origin", "*") // 允许跨域

	// 获取flusher接口
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
		return
	}

	// 创建关闭通知通道
	notify := r.Context().Done()

	// 计数器
	eventID := 1

	// 发送事件循环
	for {
		select {
		case <-notify:
			// 客户端断开连接
			fmt.Println("Client disconnected")
			return
		default:
			// 构造消息
			message := fmt.Sprintf("Server time: %s (Event ID: %d)", 
				time.Now().Format("2006-01-02 15:04:05"), eventID)

			// 发送事件 (格式很重要!)
			fmt.Fprintf(w, "id: %d\n", eventID)
			fmt.Fprintf(w, "data: %s\n\n", message)
			flusher.Flush()
			
			eventID++
			time.Sleep(1 * time.Second) // 每秒发送一次
		}
	}
}

4. 运行Go服务器

go run main.go

现在你的Go服务器已经在 http://localhost:8080 运行,SSE端点位于 http://localhost:8080/events

第二部分:React 前端实现

1. 创建React应用

cd ../frontend
npx create-react-app sse-client
cd sse-client

2. 创建SSE组件

src 文件夹下创建 SSEComponent.js

import React, { useState, useEffect } from 'react';

function SSEComponent() {
  const [messages, setMessages] = useState([]);
  const [isConnected, setIsConnected] = useState(false);
  const [eventSource, setEventSource] = useState(null);

  const connectSSE = () => {
    if (eventSource) return; // 避免重复连接
    
    // 创建新的EventSource连接
    const source = new EventSource('http://localhost:8080/events');
    
    setEventSource(source);
    setIsConnected(true);
    
    // 处理消息事件
    source.onmessage = (event) => {
      setMessages(prev => [...prev, event.data]);
    };
    
    // 处理错误事件
    source.onerror = (error) => {
      console.error('SSE Error:', error);
      disconnectSSE();
    };
  };

  const disconnectSSE = () => {
    if (eventSource) {
      eventSource.close();
      setEventSource(null);
      setIsConnected(false);
    }
  };

  // 组件卸载时断开连接
  useEffect(() => {
    return () => {
      disconnectSSE();
    };
  }, []);

  return (
    <div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
      <h2>SSE Client</h2>
      
      <div style={{ marginBottom: '20px' }}>
        <button 
          onClick={connectSSE} 
          disabled={isConnected}
          style={{ 
            padding: '8px 16px',
            marginRight: '10px',
            backgroundColor: isConnected ? '#ccc' : '#4CAF50',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: isConnected ? 'not-allowed' : 'pointer'
          }}
        >
          {isConnected ? 'Connected' : 'Connect'}
        </button>
        
        <button 
          onClick={disconnectSSE} 
          disabled={!isConnected}
          style={{ 
            padding: '8px 16px',
            backgroundColor: !isConnected ? '#ccc' : '#f44336',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: !isConnected ? 'not-allowed' : 'pointer'
          }}
        >
          Disconnect
        </button>
      </div>
      
      <div style={{ 
        height: '300px', 
        overflowY: 'auto', 
        border: '1px solid #ddd', 
        padding: '10px',
        backgroundColor: '#f9f9f9'
      }}>
        {messages.length === 0 ? (
          <p>No messages yet. Click Connect to start receiving events.</p>
        ) : (
          messages.map((msg, index) => (
            <p key={index} style={{ 
              padding: '5px', 
              borderBottom: '1px solid #eee',
              margin: '5px 0'
            }}>
              {msg}
            </p>
          ))
        )}
      </div>
    </div>
  );
}

export default SSEComponent;

3. 修改App.js

替换 src/App.js 内容:

import './App.css';
import SSEComponent from './SSEComponent';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>React SSE Demo</h1>
        <SSEComponent />
      </header>
    </div>
  );
}

export default App;

4. 修改App.css

可以添加一些基本样式到 src/App.css

.App {
  text-align: center;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
}

.App-header {
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

5. 启动React应用

npm start

React应用将在 http://localhost:3000 自动打开

第三部分:测试与验证

1. 测试步骤

  1. 确保Go后端正在运行 (http://localhost:8080)

  2. React应用正在运行 (http://localhost:3000)

  3. 在React页面点击"Connect"按钮

  4. 你应该每秒看到一条新消息

  5. 点击"Disconnect"停止接收消息

2. 验证跨域

由于前端(3000端口)和后端(8080端口)不同源,我们已经在Go后端添加了CORS头:

w.Header().Set("Access-Control-Allow-Origin", "*")

3. 错误处理

  • 如果连接失败,检查控制台错误

  • 确保两个服务器都在运行

  • 检查浏览器控制台的网络请求

第四部分:扩展功能

1. 添加更多事件类型

修改Go后端的sseHandler

// 添加不同类型的事件
fmt.Fprintf(w, "event: status\ndata: {\"cpu\": %.1f, \"memory\": %.1f}\n\n", 
    rand.Float64()*100, rand.Float64()*100)

修改React组件的connectSSE

// 添加特定事件监听
source.addEventListener('status', (event) => {
  const data = JSON.parse(event.data);
  setMessages(prev => [...prev, `CPU: ${data.cpu.toFixed(1)}%, Memory: ${data.memory.toFixed(1)}%`]);
});

2. 添加重连逻辑

在React组件中添加:

useEffect(() => {
  if (!isConnected && autoReconnect) {
    const timer = setTimeout(() => {
      connectSSE();
    }, 5000); // 5秒后重连
    return () => clearTimeout(timer);
  }
}, [isConnected, autoReconnect]);

第五部分:部署说明

1. 生产环境配置

  1. 修改React的.env文件:

    REACT_APP_API_URL=https://your-api-domain.com

  2. 更新EventSource连接:

    const source = new EventSource(`${process.env.REACT_APP_API_URL}/events`);

2. 构建React应用

npm run build

3. 部署Go应用

cd backend
go build -o server
./server

总结

通过这个完整的教程,你已经学会了:

  1. 如何用Go创建SSE服务器

  2. 如何用React连接SSE流

  3. 如何处理连接和断开

  4. 如何显示实时消息

  5. 基本的错误处理和样式设计

这个实现包含了SSE的核心功能,你可以基于此扩展更复杂的功能,如:

  • 用户认证

  • 不同事件类型

  • 更复杂的UI

  • 生产环境部署配置

相关文章

从安装PostgreSQL开始
通过以下步骤,你可以在 CentOS 7 上成功安装和配置 PostgreSQL 14。首先,设置 RPM 仓库并安装 PostgreSQL 14 服务器。然后,创建自定义数据存储目录并初始化数据库。接下来,修改启动脚本以使用自定义数据存储目录,并设置 PostgreSQL 监听所有 IP 地址。为了增强安全性,配置密码认证并修改 postgres 超级用户密码。最后,确保所有配置生效,重启 PostgreSQL 服务。通过这些步骤,你将拥有一个配置良好且安全的 PostgreSQL 数据库环境。如果在过程中遇到任何问题,请参考官方文档或寻求专业支持。
checkpoint是什么东西?一个位置?还是一个操作?
checkpoint是一个操作,执行这个操作的开始时刻,会记录当前开始时刻的 WAL 位置作为重做点,这个位置会被保存在文件中。 然后将该重做点之前所有 shared buffer 中的脏页均被刷入到存储。checkpoint又名检查点,一般checkpoint会将某个时间点之前的脏数据全部刷新到磁盘,以实现数据的一致性与完整性。
PostgreSQL 内参:深入解析运行原理【快照篇】
来源于灿哥的:《PostgreSQL 内参:深入解析运行原理》,非常好的一本书。
PG存在的级联删除(ON DELETE CASCADE)
在 PostgreSQL 中,如果你有两张表:用户表(users)和订单表(orders),并且用户表的主键是订单表的外键,那么你可以通过以下步骤来进行实验,删除订单表中的一条数据,然后删除用户表中的一条数据。

评论

快捷导航

把好文章收藏到微信

打开微信,扫码查看

关闭

还没有账号?立即注册