4

WebSockets 与 NextJS 和 Golang 结合使用的案例源码

 1 year ago
source link: https://www.jdon.com/63948.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

WebSockets 与 NextJS 和 Golang 结合使用的案例源码


大多数应用程序使用 CRUD(创建/读取/更新/删除)API——前端将更改发送到后端,但反之亦然。

CRUD 应用程序允许您将更改发送到服务器,并允许其他用户请求这些更改。

在实时应用程序中,所有客户端都与后端保持持久的 WebSocket 连接,并在更新发生时接收更新,而不需要等待用户刷新页面。

架构图:

Frame-122.svg

这种架构意味着您可以在创建任何 websocket 之前立即查看网页内容(使用 NextJS 进行服务器端呈现),同时通过直接连接到 Go 服务器来保持更新以了解更改。
这也意味着我们可以将所有实际代码保留在 Go 中,并避免在后端使用 Javascript 或 Typescript,同时仍然能够使用 React。

从这个现成代码开始:

git clone https://github.com/webappio/golang-nextjs-example.git

main函数:

func main() {
    handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
        var resp []byte
        if req.URL.Path == "/handler-initial-data" {
            resp = []byte(`{"text": "initial"}`)
        } else if req.URL.Path == "/handler" {
            time.Sleep(time.Second) //TODO HACK: sleep a second to check everything is working properly

            resp = []byte(`{"text": "updated"}`)
            
            //this line is required because the browser will check permissions to avoid getting hacked
            rw.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
        } else {
            rw.WriteHeader(http.StatusNotFound)
            return
        }

        rw.Header().Set("Content-Type", "application/json")
        rw.Header().Set("Content-Length", fmt.Sprint(len(resp)))
        rw.Write(resp)
    })

    log.Println("Server is available at http://localhost:8000")
    log.Fatal(http.ListenAndServe(":8000", handler))
}

现在我们有两个端点,http://localhost:8000/handler-initial-dataNextJS 最初将发送给浏览器的内容(作为 HTML,由于服务器端呈现),然后http://localhost:8000/handler是我们的应用程序将从中提取数据的内容。

在前端添加一个异步更新
接下来,更新前端,在index.js中引用我们的/handler-initial-data和/handler路径。

import Head from 'next/head'
import styles from '../styles/Home.module.css'
import {useEffect, useState} from "react";

export async function getServerSideProps() {
    const initialData = await fetch("http://localhost:8000/handler-initial-data").then(x => x.json());
    return {props: {data: initialData}}
}

export default function Home(props) {
    const [data, setData] = useState(props.data);
    useEffect(() => {
        fetch("http://localhost:8000/handler")
            .then(x => x.json())
            .then(x => setData(x));
    }, [])
    return (
        <div className={styles.container}>
            <Head>
                <title>OSS Docs</title>
                <meta name="description" content="Fast like SSR, Powerful like WebSockets"/>
                <link rel="icon" href="/favicon.ico"/>
            </Head>

            <main className={styles.main}>
                <h1 className={styles.title}>
                    {props.title || "Untitled Document"}
                </h1>
                <div>Data is: {JSON.stringify(data)}</div>
            </main>
        </div>
    )
}

如果你用go run main.go运行后端,用npm run dev运行前端,你最初应该看到Data is:{"text": "initial"},然后一秒钟后看到数据是。{"text": "uped"}- 所发生的操作是。

  • 你的浏览器向NextJS询问网站的HTML内容
  • NextJS调用getServerSideProps()和函数Home(props)来生成该HTML,导致HTTP请求到http://localhost:8000/handler-initial-data
  • 你的浏览器收到HTML和JavaScript,并在本地运行函数Home
  • 一秒钟后,useEffect钩子完成了从你的浏览器中的/handler获取数据,并更新数据,然后在本地改变你的网站

添加一个websocket
为了建立像Google Docs这样的东西,我们希望能够将我们的修改发送到后台,同时也能接收其他人的修改,因为他们进来了。这意味着我们需要双向通信,这正是WebSocket的作用。

Go并没有自带WebSocket库,所以我们来安装一个。

# NOTE: if you have a GitHub or GitLab repository, change this line 
user@computer:my-project/services/backend$ go mod init github.com/webappio/golang-nextjs-example

user@computer:my-project/services/backend$ go get github.com/gobwas/ws

并在/handler处更新我们的后端以处理WebSockets。

if req.URL.Path == "/handler" {
    conn, _, _, err := ws.UpgradeHTTP(req, rw)
    if err != nil {
        log.Println("Error with WebSocket: ", err)
        rw.WriteHeader(http.StatusMethodNotAllowed)
        return
    }
    go func() {
        defer conn.Close()

        time.Sleep(time.Second) //TODO HACK: sleep a second to check everything is working properly
        err = wsutil.WriteServerMessage(conn, ws.OpText, []byte(`{"text": "from-websocket"}`))
        if err != nil {
            log.Println("Error writing WebSocket data: ", err)
            return
        }
    }()
    return
}

将已安装的库添加到我们的导入中。

import (
    "fmt"
    "github.com/gobwas/ws"
    "github.com/gobwas/ws/wsutil"
    "log"
    "net/http"
    "time"
)

最后,我们可以改变我们的前端,以使用WebSocket而不是fetch调用。

export default function Home(props) {
    const [data, setData] = useState(props.data);
    const [ws, setWS] = useState(null);
    useEffect(() => {
        const newWS = new WebSocket("ws://localhost:8000/handler")
        newWS.onerror = err => console.error(err);
        newWS.onopen = () => setWS(newWS);
        newWS.onmessage = msg => setData(JSON.parse(msg.data));
    }, [])
    return (
        <div className={styles.container}>
            <Head>
                <title>OSS Docs</title>
                <meta name="description" content="Fast like SSR, Powerful like WebSockets"/>
                <link rel="icon" href="/favicon.ico"/>
            </Head>

            <main className={styles.main}>
                <h1 className={styles.title}>
                    {props.title || "Untitled Document"}
                </h1>
                <div>Data is: {JSON.stringify(data)}</div>
            </main>
        </div>
    )
}

重新启动后台(ctrl + c,向上箭头,回车)后,你应该看到文本迅速从Data is:{"text": "initial"}变为Data is:{"text": "from-websocket"}。- 与我们的获取实现结果相同,但现在我们有两个新方法。

  • 我们前端的ws.send(...)将向我们的后端发送数据,而
  • 后台的wsutil.WriteServerMessage将发送数据到前台。

项目源码点击标题


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK