【GoWeb示例】通过示例学习 Go 的 Web 编程

文章目录

  • 你好世界
  • HTTP 服务器
  • 路由(使用 gorilla/mux)
  • 连接到 MySQL 数据库
  • MySQL 数据库简单操作
  • 模板
  • 静态资源和文件操作
  • 表单处理
  • 中间件(基础)
  • 中间件(高级)
  • 会话
  • JSON
  • Websockets
  • 密码哈希

你好世界

Go语言创建可在浏览器中查看的 Web 服务器

代码如下:

package mainimport ("fmt""net/http" //用于创建 HTTP 服务器和处理 HTTP 请求
)// 将处理请求的函数注册到 HTTP 服务器上
func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { //注册一个处理器,用于指定特定路径(在这里是 /)的请求。// 使用 Fprintf 写入响应,并检查错误 ,用于格式化字符串并将其写入到指定的 io.Writer 接口(例如 HTTP 响应、文件等)if _, err := fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path); err != nil {// 如果发生错误,设置HTTP状态码并记录错误http.Error(w, "Internal Server Error", http.StatusInternalServerError)fmt.Println("Error writing response:", err) // 记录错误信息}})//处理函数(http.HandleFunc的第二个参数)接收两个参数:w(用于写入响应)和 r(包含请求信息)// 启动 HTTP 服务器,监听端口 80err := http.ListenAndServe(":80", nil)if err != nil {// 如果发生错误,输出错误信息fmt.Println("Error starting server:", err)}
}//通过访问 http://localhost/,可向正在本地运行的 HTTP 服务器发送请求
//操作系统会自动将 localhost 映射到 127.0.0.1
//localhost: 这是一个特殊的域名,通常用于指向当前计算机。无论你在哪个操作系统上,访问 http://localhost 都会将请求发送到本机。
//127.0.0.1: 这是一个回环地址(loopback address),是计算机内部的 IP 地址,也表示本机。它和 localhost 是等价的,访问这两个地址会得到相同的结果。

执行流程如下:
在这里插入图片描述
测试服务器:
在常用的网页浏览器地址栏输入 http://localhost/,然后按下回车键:
在这里插入图片描述

HTTP 服务器

package mainimport ("fmt""net/http"
)func main() {// 处理根路径的请求http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {// 当访问根路径时,发送欢迎消息,并检查错误if _, err := fmt.Fprintf(w, "Welcome to my website!"); err != nil {// 如果发生错误,设置 HTTP 状态码并记录错误http.Error(w, "Internal Server Error", http.StatusInternalServerError)fmt.Println("Error writing response:", err) // 记录错误信息}})// 设置静态文件服务器fs := http.FileServer(http.Dir("static/"))                // 创建一个文件服务器,指向 static/ 目录http.Handle("/static/", http.StripPrefix("/static/", fs)) // 处理 /static/ 路径的请求// 启动 HTTP 服务器,监听 80 端口,并处理错误if err := http.ListenAndServe(":80", nil); err != nil {// 如果发生错误,输出错误信息fmt.Println("Error starting server:", err)}
}

路由(使用 gorilla/mux)

package mainimport ("fmt""net/http""github.com/gorilla/mux"
)func main() {// 创建一个新的路由器r := mux.NewRouter()// 定义路由,支持动态路径变量r.HandleFunc("/books/{title}/page/{page}", func(w http.ResponseWriter, r *http.Request) {vars := mux.Vars(r)    // 从请求中提取变量title := vars["title"] // 获取书名page := vars["page"]   // 获取页码// 写入响应fmt.Fprintf(w, "You've requested the book: %s on page %s\n", title, page)})// 启动 HTTP 服务器,监听 80 端口http.ListenAndServe(":80", r)
}

可以在浏览器中访问以下 URL 来测试此服务器:
http://localhost/books/Go%20Programming/page/10
在这里插入图片描述

连接到 MySQL 数据库

使用docker:

  • 安装docker
  • 在命令行启动MySql容器
    创建一个名为 my-mysql 的容器,并设置根用户密码为 password,同时创建一个名为 mydb 的数据库:
    docker run --name my-mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=mydb -p 3306:3306 -d mysql:latest
  • 检查MySql容器是否运行
    docker ps
  • 在GoLand中配置数据库连接:
package mainimport ("database/sql"          // 用于数据库操作"fmt"                    // 用于打印输出"log"                    // 用于错误处理_ "github.com/go-sql-driver/mysql" // MySQL 驱动,使用匿名导入,确保驱动被加载
)func main() {// 配置数据库连接dsn := "root:rootpassword@tcp(127.0.0.1:3306)/mydb?parseTime=true"db, err := sql.Open("mysql", dsn)if err != nil {log.Fatal(err)}defer db.Close()// 初始化数据库连接,确保连接成功err = db.Ping()if err != nil {log.Fatal(err)}fmt.Println("Successfully connected to the database!")
}

如果不打算继续使用 MySQL 容器,可以使用以下命令停止:

docker stop mysql-container

如果将来不再需要该容器,可以使用以下命令删除:

docker rm mysql-container

MySQL 数据库简单操作

package mainimport ("database/sql" // 导入数据库操作相关包"fmt"          // 导入格式化输出包"log"          // 导入日志包,用于错误处理"time"         // 导入时间包,用于获取当前时间_ "github.com/go-sql-driver/mysql" // 导入 MySQL 驱动(使用匿名导入)
)func main() {// 连接到 MySQL 数据库,使用 root 用户,密码为 root,数据库为 root,支持解析时间格式db, err := sql.Open("mysql", "root:root@(127.0.0.1:3306)/root?parseTime=true")if err != nil {log.Fatal(err) // 如果连接失败,打印错误并退出}// 测试数据库连接是否正常if err := db.Ping(); err != nil {log.Fatal(err) // 如果数据库连接不正常,打印错误并退出}// --- 创建一个新的数据库表 ---{query := `CREATE TABLE users (  -- SQL 查询,创建 users 表id INT AUTO_INCREMENT,  -- 自动增长的 IDusername TEXT NOT NULL,  -- 用户名字段,不允许为空password TEXT NOT NULL,  -- 密码字段,不允许为空created_at DATETIME,     -- 创建时间字段,类型为 DATETIMEPRIMARY KEY (id)         -- 设置 ID 为主键);`// 执行创建表的 SQL 查询if _, err := db.Exec(query); err != nil {log.Fatal(err) // 如果执行查询出错,打印错误并退出}}// --- 插入一个新用户 ---{username := "johndoe"   // 新用户的用户名password := "secret"    // 新用户的密码createdAt := time.Now() // 获取当前时间作为用户的创建时间// 执行插入用户的 SQL 查询result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt)if err != nil {log.Fatal(err) // 如果插入出错,打印错误并退出}// 获取插入后生成的 IDid, err := result.LastInsertId()if err != nil {log.Fatal(err) // 如果获取插入 ID 出错,打印错误并退出}fmt.Println(id) // 打印新用户的 ID}// --- 查询单个用户 ---{var (id        intusername  stringpassword  stringcreatedAt time.Time)// 查询 ID 为 1 的用户query := "SELECT id, username, password, created_at FROM users WHERE id = ?"if err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt); err != nil {log.Fatal(err) // 如果查询或扫描数据出错,打印错误并退出}// 打印查询结果fmt.Println(id, username, password, createdAt)}// --- 查询所有用户 ---{type user struct { // 定义一个用户结构体id        intusername  stringpassword  stringcreatedAt time.Time}// 执行查询所有用户的 SQL 查询rows, err := db.Query(`SELECT id, username, password, created_at FROM users`)if err != nil {log.Fatal(err) // 如果查询出错,打印错误并退出}defer rows.Close() // 在函数退出时关闭 rows 资源var users []user // 定义一个切片用于存储所有用户// 遍历查询结果for rows.Next() {var u user// 扫描每一行数据并填充到用户结构体中err := rows.Scan(&u.id, &u.username, &u.password, &u.createdAt)if err != nil {log.Fatal(err) // 如果扫描出错,打印错误并退出}// 将用户结构体添加到 users 切片中users = append(users, u)}// 检查是否有其他查询错误if err := rows.Err(); err != nil {log.Fatal(err) // 如果有错误,打印错误并退出}// 打印所有用户的详细信息fmt.Printf("%#v", users)}// --- 删除指定 ID 的用户 ---{// 执行删除 ID 为 1 的用户_, err := db.Exec(`DELETE FROM users WHERE id = ?`, 1)if err != nil {log.Fatal(err) // 如果删除出错,打印错误并退出}}
}

模板

主程序,负责启动 Web 服务器、加载模板并渲染 HTML 页面:

package mainimport ("html/template" // 导入 HTML 模板包"log""net/http" // 导入 HTTP 包,用于处理 HTTP 请求和响应
)// Todo 结构体定义待办事项的数据
type Todo struct {Title string // 任务的标题Done  bool   // 任务是否完成
}// TodoPageData 结构体定义整个页面的数据结构
type TodoPageData struct {PageTitle string // 页面标题Todos     []Todo // 待办事项列表
}func main() {// 解析模板文件 layout.html,模板用于展示 HTML 页面tmpl := template.Must(template.ParseFiles("layout.html"))// 设置路由,当访问根路径(/)时,调用处理函数http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {// 定义待办事项的数据data := TodoPageData{PageTitle: "My TODO list", // 页面标题Todos: []Todo{{Title: "Task 1", Done: false}, // 未完成的任务{Title: "Task 2", Done: true},  // 已完成的任务{Title: "Task 3", Done: true},  // 已完成的任务},}// 渲染模板并传递数据,模板将把数据渲染到 HTML 中tmpl.Execute(w, data)})// 启动 HTTP 服务器并监听 80 端口err := http.ListenAndServe(":80", nil)if err != nil {log.Fatal("Server failed to start: ", err) // 如果发生错误,打印错误并退出}
}

HTML 模板文件:layout.html,用于动态生成 HTML 内容:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8">  <!-- 定义字符集为 UTF-8 --><meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 响应式设计 --><title>{{.PageTitle}}</title> <!-- 渲染页面标题 --><style>.done {text-decoration: line-through; /* 已完成的任务加删除线 */color: gray; /* 已完成的任务显示为灰色 */}</style>
</head>
<body>
<h1>{{.PageTitle}}</h1> <!-- 渲染页面标题 -->
<ul>{{range .Todos}} <!-- 遍历待办事项列表 -->{{if .Done}} <!-- 如果任务已完成 --><li class="done">{{.Title}}</li> <!-- 使用 'done' 类,渲染为灰色和删除线 -->{{else}} <!-- 如果任务未完成 --><li>{{.Title}}</li> <!-- 普通任务项 -->{{end}} <!-- if-else 语句结束 -->{{end}} <!-- range 循环结束 -->
</ul>
</body>
</html>

打开浏览器,访问 http://localhost,会看到待办事项列表的页面。任务完成的项会显示为灰色且带有删除线,未完成的任务则显示为正常文本。
在这里插入图片描述

静态资源和文件操作

结构:

project-directory/
│
├── main.go       // Go 文件
└── assets/└── css/└── styles.css    // 静态 CSS 文件

main.go:

package mainimport "net/http"func main() {// 创建文件服务器,映射 "assets/" 目录中的静态文件fs := http.FileServer(http.Dir("assets/"))// 设置路由,将 "/static/" 映射到 "assets/" 目录http.Handle("/static/", http.StripPrefix("/static/", fs))http.ListenAndServe(":8080", nil)
}

styles.css:

body {background-color: lightblue;
}h1 {color: darkblue;
}

访问静态文件:
打开浏览器,访问 http://localhost:8080/static/css/styles.css,能够看到 styles.css 文件的内容。
在这里插入图片描述

表单处理

main.go

package mainimport ("html/template" // 导入模板包,用于渲染 HTML 页面"net/http"      // 导入 HTTP 包,用于处理 Web 请求
)type ContactDetails struct {Email   string // 表单字段:EmailSubject string // 表单字段:SubjectMessage string // 表单字段:Message
}func main() {tmpl := template.Must(template.ParseFiles("forms.html")) // 解析 forms.html 模板http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {// 处理根路径的请求if r.Method != http.MethodPost { // 如果请求方法不是 POSTtmpl.Execute(w, nil) // 渲染表单return}// 如果是 POST 请求(表单提交)details := ContactDetails{Email:   r.FormValue("email"),   // 获取表单中 email 字段的值Subject: r.FormValue("subject"), // 获取表单中 subject 字段的值Message: r.FormValue("message"), // 获取表单中 message 字段的值}// 这里可以进一步处理 `details`,例如保存数据或发送邮件_ = details // 目前只是将其赋值给一个变量,实际没有做处理tmpl.Execute(w, struct{ Success bool }{true}) // 渲染成功页面})// 启动 Web 服务器,监听 8080 端口http.ListenAndServe(":8080", nil)
}

forms.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Contact Form</title>
</head>
<body>{{if .Success}}
<h1>Thanks for your message!</h1> <!-- 如果提交成功,显示感谢消息 -->
{{else}}
<h1>Contact</h1> <!-- 如果还没有提交,显示表单 -->
<form method="POST"><label for="email">Email:</label><br /><input type="text" id="email" name="email" required><br /> <!-- 用户输入 email --><label for="subject">Subject:</label><br /><input type="text" id="subject" name="subject" required><br /> <!-- 用户输入 subject --><label for="message">Message:</label><br /><textarea id="message" name="message" required></textarea><br /> <!-- 用户输入 message --><input type="submit" value="Submit"> <!-- 提交按钮 -->
</form>
{{end}}</body>
</html>

在浏览器中访问 http://localhost:8080,(get请求),会看到一个表单页面,其中包含邮箱、主题和消息字段:
在这里插入图片描述
点击“Submit”按钮,(post请求),提交表单数据,提交成功后,页面会显示“Thanks for your message!”的成功消息:
在这里插入图片描述

中间件(基础)

功能:可以在请求到达最终处理函数之前或之后执行一些操作(如日志记录、权限验证、请求参数检查等)。
示例中的功能:每次有请求到达时,都会在终端(命令行)打印出请求的路径。

package mainimport ("fmt"      // 用于格式化输出"log"      // 用于记录日志"net/http" // 用于处理 HTTP 请求
)// logging 中间件,接收一个 http.HandlerFunc 并返回一个新的 http.HandlerFunc
func logging(f http.HandlerFunc) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {// 记录访问的 URL 路径log.Println(r.URL.Path)// 调用原始的处理函数f(w, r)}
}// foo 路径的处理函数,返回 "foo" 字符串
func foo(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "foo")
}// bar 路径的处理函数,返回 "bar" 字符串
func bar(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "bar")
}func main() {// 为 "/foo" 路径设置 logging 中间件和 foo 处理函数http.HandleFunc("/foo", logging(foo))// 为 "/bar" 路径设置 logging 中间件和 bar 处理函数http.HandleFunc("/bar", logging(bar))// 启动 HTTP 服务器,监听 8080 端口log.Println("Starting server on :8080")http.ListenAndServe(":8080", nil)
}

访问 http://localhost:8080/foo,日志输出会是:
在这里插入图片描述

访问 http://localhost:8080/bar,日志输出会是:
在这里插入图片描述
在浏览器中,看到的是由 foo 或 bar 函数返回的字符串:

在这里插入图片描述

中间件(高级)

package mainimport ("fmt""log""net/http""time"
)// Middleware 定义了一个中间件类型,实际上是一个函数,这个函数接收一个 http.HandlerFunc(处理请求的函数),并返回一个新的 http.HandlerFunc(经过中间件处理后的函数)
type Middleware func(http.HandlerFunc) http.HandlerFunc// Logging 中间件用于记录请求的路径以及处理请求的时间
func Logging() Middleware {// 创建一个新的中间件return func(f http.HandlerFunc) http.HandlerFunc {// 返回一个新的处理函数,它是对原始处理函数的包装return func(w http.ResponseWriter, r *http.Request) {// 中间件的逻辑:记录请求的时间start := time.Now() // 记录请求开始的时间// 使用 defer 延迟执行,确保请求处理完成后记录下处理时间defer func() {// 打印请求的路径和处理时间log.Println(r.URL.Path, time.Since(start))}()// 调用下一个中间件或处理函数f(w, r)}}
}// Method 中间件确保只有特定的 HTTP 请求方法(如 GET、POST 等)才能通过,否则返回 400 错误
func Method(m string) Middleware {// 创建一个新的中间件return func(f http.HandlerFunc) http.HandlerFunc {// 返回一个新的处理函数,它是对原始处理函数的包装return func(w http.ResponseWriter, r *http.Request) {// 中间件的逻辑:检查请求方法是否匹配if r.Method != m {// 如果请求方法不符合要求,返回 400 错误http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)return}// 调用下一个中间件或处理函数f(w, r)}}
}// Chain 函数将中间件应用到一个处理函数上,返回一个新的处理函数
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {for _, m := range middlewares {// 对每个中间件,使用它来包装当前的处理函数f = m(f)}// 返回最终的处理函数return f
}// Hello 是一个简单的 HTTP 处理函数,返回 "hello world"
func Hello(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "hello world")
}func main() {// 设置路由 "/"// Chain 函数应用了两个中间件:Method("GET") 和 Logging()// 这样请求 "/"" 路径时,首先会验证 HTTP 方法是否是 GET,然后会记录请求的路径和处理时间http.HandleFunc("/", Chain(Hello, Method("GET"), Logging()))// 启动服务器,监听 8080 端口http.ListenAndServe(":8080", nil)
}

运行结果:
如果请求方法是 GET,浏览器将显示 “hello world”:
打开浏览器,访问 http://localhost:8080/,会看到响应 hello world:
在这里插入图片描述

此时控制台会显示日志:
(表示请求的路径 / 和处理该请求所花费的时间(例如 1.234ms))
在这里插入图片描述

如果请求方法不是 GET(例如 POST),服务器会返回 400 错误。
在命令行发起 POST 请求,会收到 HTTP 400 错误响应:
在这里插入图片描述
同时,控制台会输出错误日志:
在这里插入图片描述

会话

用户登录时,服务器生成一个会话数据(比如用户 ID),并使用密钥 key 加密后保存在浏览器的 cookie 中。每次用户访问时,服务器通过这个 cookie 获取加密的会话信息,解密后识别用户身份。

// sessions.go
package mainimport ("fmt""net/http""github.com/gorilla/sessions" // 导入第三方库:gorilla/sessions,用于管理用户会话
)var (// key 必须是 16, 24 或 32 字节长 (AES-128, AES-192 或 AES-256)key   = []byte("super-secret-key")   // 会话加密的密钥store = sessions.NewCookieStore(key) // 创建一个新的 Cookie 存储器,用于存储和检索会话数据
)// secret 处理 "/secret" 路由,要求用户必须认证才能访问
func secret(w http.ResponseWriter, r *http.Request) {session, _ := store.Get(r, "cookie-name") // 获取当前请求的会话对象// 检查用户是否已认证,session.Values 存储了用户的数据if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {http.Error(w, "Forbidden", http.StatusForbidden) // 如果没有认证,返回 403 Forbiddenreturn}// 如果用户已认证,显示秘密消息fmt.Fprintln(w, "The cake is a lie!")
}// login 处理 "/login" 路由,模拟用户登录并设置认证标志
func login(w http.ResponseWriter, r *http.Request) {session, _ := store.Get(r, "cookie-name") // 获取会话对象// 这里可以添加用户认证的逻辑,比如用户名和密码验证// 假设认证成功,设置用户为已认证session.Values["authenticated"] = true // 将用户设置为已认证session.Save(r, w)                     // 保存会话数据到浏览器的 Cookie 中
}// logout 处理 "/logout" 路由,注销用户,撤销认证
func logout(w http.ResponseWriter, r *http.Request) {session, _ := store.Get(r, "cookie-name") // 获取会话对象// 撤销认证session.Values["authenticated"] = falsesession.Save(r, w) // 保存会话数据
}func main() {// 定义 HTTP 路由与对应的处理函数http.HandleFunc("/secret", secret) // 访问 /secret 路由时调用 secret 函数http.HandleFunc("/login", login)   // 访问 /login 路由时调用 login 函数http.HandleFunc("/logout", logout) // 访问 /logout 路由时调用 logout 函数// 启动 HTTP 服务器,监听 8080 端口http.ListenAndServe(":8080", nil)
}

打开浏览器,访问 http://localhost:8080/login。
这会模拟登录,设置认证标志。不会看到页面内容,因为它只是设置了会话(并不会显示任何东西)。
如果没有问题,浏览器会保存一个 cookie-name 的 cookie。
测试访问秘密页面:
访问 http://localhost:8080/secret。
如果已经成功登录(并且浏览器中保存了有效的会话 cookie),将看到输出:
在这里插入图片描述

测试登出:
访问 http://localhost:8080/logout。
这将登出并撤销认证。
如果再次访问 http://localhost:8080/secret,会看到 Forbidden 错误,因为会话中的认证标志已经被设置为 false。
在这里插入图片描述

JSON

package mainimport ("encoding/json" // 导入 Go 标准库中的 encoding/json 包,用于处理 JSON 编码和解码"fmt""net/http"
)// 定义一个结构体 User,用于表示用户信息
// 结构体字段使用 JSON 标签,指定字段在 JSON 中的对应键名
type User struct {Firstname string `json:"firstname"` // "firstname" 字段会映射到 JSON 中的 "firstname" 键Lastname  string `json:"lastname"`  // "lastname" 字段会映射到 JSON 中的 "lastname" 键Age       int    `json:"age"`       // "age" 字段会映射到 JSON 中的 "age" 键
}func main() {// 设置 HTTP 路由和处理函数http.HandleFunc("/decode", func(w http.ResponseWriter, r *http.Request) {// 创建一个空的 User 结构体,用于存储解码后的 JSON 数据var user User// 使用 json.NewDecoder 解码请求体中的 JSON 数据并填充到 user 变量json.NewDecoder(r.Body).Decode(&user)// 格式化并返回用户信息,输出类似 "Elon Musk is 48 years old!"fmt.Fprintf(w, "%s %s is %d years old!", user.Firstname, user.Lastname, user.Age)})// 设置另一个 HTTP 路由用于编码响应数据为 JSONhttp.HandleFunc("/encode", func(w http.ResponseWriter, r *http.Request) {// 创建一个 User 实例并填充数据peter := User{Firstname: "John",Lastname:  "Doe",Age:       25,}// 使用 json.NewEncoder 将 peter 结构体编码为 JSON 格式并返回给客户端json.NewEncoder(w).Encode(peter)})// 启动 HTTP 服务器并监听 8080 端口http.ListenAndServe(":8080", nil)
}

访问 /decode 路由:

通过 POST 请求传递 JSON 数据:

curl -s -XPOST -d'{"firstname":"Elon","lastname":"Musk","age":48}' http://localhost:8080/decode

输出:Elon Musk is 48 years old!
在这里插入图片描述

访问 /encode 路由:

通过 GET 请求获取 JSON 数据:

curl -s http://localhost:8080/encode

输出:{“firstname”:“John”,“lastname”:“Doe”,“age”:25}
在这里插入图片描述

Websockets

package mainimport ("fmt""github.com/gorilla/websocket""net/http"
)var upgrader = websocket.Upgrader{ReadBufferSize:  1024, // 设置读取缓冲区大小WriteBufferSize: 1024, // 设置写入缓冲区大小
}func main() {// 处理 WebSocket 连接http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {conn, _ := upgrader.Upgrade(w, r, nil) // 将 HTTP 升级为 WebSocket 连接,错误被忽略for {// 从 WebSocket 连接中读取消息msgType, msg, err := conn.ReadMessage()if err != nil {return // 如果发生错误,退出}// 打印收到的消息fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg))// 将收到的消息返回给客户端(实现回显)if err = conn.WriteMessage(msgType, msg); err != nil {return // 如果发生错误,退出}}})// 处理 HTML 页面请求http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {http.ServeFile(w, r, "websockets.html") // 发送 websockets.html 文件给浏览器})// 启动 HTTP 服务器并监听 8080 端口http.ListenAndServe(":8080", nil)
}

打开浏览器,访问 http://localhost:8080/,将加载 websockets.html 页面。
在这里插入图片描述

在输入框中输入内容,然后点击 Send 按钮。
浏览器会将输入的内容通过 WebSocket 发送到服务器。
服务器会接收到这个消息并将其打印到控制台,同时返回相同的消息给浏览器。
浏览器接收到服务器的消息后,会将其显示出来。
在这里插入图片描述

密码哈希

package mainimport ("fmt""golang.org/x/crypto/bcrypt"
)// HashPassword 对密码进行哈希处理
func HashPassword(password string) (string, error) {// bcrypt.GenerateFromPassword 函数将密码哈希为字符串// 第二个参数是加密的难度(工作因子),越大越安全,但也越慢bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) // 14 是工作因子return string(bytes), err
}// CheckPasswordHash 检查密码是否与给定的哈希值匹配
func CheckPasswordHash(password, hash string) bool {// bcrypt.CompareHashAndPassword 比较给定的密码与哈希值是否匹配err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))return err == nil // 如果没有错误,表示匹配
}func main() {// 这是我们要加密的原始密码password := "secret"// 调用 HashPassword 函数对密码进行加密(哈希)hash, _ := HashPassword(password) // 忽略错误处理,为了简单起见// 打印原始密码和哈希值fmt.Println("Password:", password)fmt.Println("Hash:    ", hash)// 使用 CheckPasswordHash 函数验证原始密码和哈希值是否匹配match := CheckPasswordHash(password, hash)fmt.Println("Match:   ", match) // 如果匹配,将输出 true
}

查看输出:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/11125.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于Multisim的烟雾报警电路设计与仿真

设计任务和要求&#xff1a; &#xff08; 1 &#xff09;需要有低浓度、中浓度和高浓度 3 个浓度范围。 &#xff08; 2 &#xff09;需要用电压比较器设置不同浓度的阈值。 &#xff08; 3 &#xff09;用蜂鸣器和二极管指示灯&#xff08; 3 个浓度范围的指示灯用 3 …

旅行是过于梦幻的镜月【西域之旅】

旅行的意义就是几个定格的“瞬间” 短暂的相遇 恰如涉水而过 每条河终究是 奔向属于它的海看到一句话&#xff1a;一个人的行走范围&#xff0c;就是他的世界。 快节奏的社会里&#xff0c;旅行也许不值得被歌颂&#xff0c;但它却实实在在拓宽一个人的世界。 当我没灵感时&…

Simulink中Matlab function使用全局变量

目录 一. 引言二. 普通Matlab function使用全局变量三. Simulink中的Matlab function使用全局变量四. 如何利用Matlab function的全局变量施加随机噪声 一. 引言 最近发现了之前仿真中的一个问题&#xff0c;记录一下备忘。 Matlab function中有时候需要用到全局变量&#xf…

架构篇(04理解架构的演进)

目录 学习前言 一、架构演进 1. 初始阶段的网站架构 2. 应用服务和数据服务分离 3. 使用缓存改善网站性能 4. 使用应用服务器集群改善网站的并发处理能力 5. 数据库读写分离 6. 使用反向代理和CDN加上网站相应 7. 使用分布式文件系统和分布式数据库系统 8. 使用NoSQL和…

zabbix前台界面配置

点击下一步 如上异常错误解决方法代码如下&#xff0c;安装缺失的软包&#xff0c;并修改php.ini对应参数的值即可 yum install php-mbstring php-bcmath php-gd php72w-xml -y yum install gd gd-devel -y 修改配置文件 sed -i /post_max_size/s/8/16/g;/max_ex…

数据库去O搞了个寂寞!甲骨文股价翻倍,市值突破5200亿美金!

随着川建国同志的当选&#xff0c;到账最近几天美股科技股都有不小的涨幅&#xff0c;涨幅最高的当tesla莫属&#xff0c;然而据传甲骨文老板也是川普的金主&#xff01; 或许正因为如此&#xff0c;甲骨文股价也创新高了&#xff0c;最近几天涨了10%&#xff0c;市值突破5200…

[强网杯 2019]随便注 1

[强网杯 2019]随便注 1 审题 观察题目&#xff0c;判断可能是SQL注入&#xff0c;或者Linux命令执行&#xff0c;结合题目就是注入了 知识点 堆叠注入&#xff0c;handler命令执行&#xff0c;更改表名,预编译 知识点解析 堆叠注入 简单来说&#xff0c;堆叠注入就是按…

三菱QD77MS定位模块紧急停止功能

“紧急停止功能” 是通过简单运动模块的外部输入连接用连接器上连接的紧急停止输入&#xff0c;对同服放大器的全部轴进行批量停止的功能。(初始值为“0:有效”。)通过“[r.82]紧急停止有效/无效设置”可以选择紧急停止输入的有效/无效。 [1]控制内容 将“[r82]紧急停止有效/无…

计算机组成原理(指令格式)

(1)指令:是用来表示控制信息的一组二进制代码 (2)指令由操作码和地址码组成&#xff0c;操作码表示操作的性质和功能&#xff0c;地址码用来存放操作数的地址。地址码不为1。 (3)程序计数器(PC):专门存放当前要执行的指令地址。 (4)累加器AC:是一种寄存器&#xff0c;用来存储…

时序预测 | 改进图卷积+informer时间序列预测,pytorch架构

时序预测 | 改进图卷积informer时间序列预测&#xff0c;pytorch架构 目录 时序预测 | 改进图卷积informer时间序列预测&#xff0c;pytorch架构预测效果基本介绍参考资料 预测效果 基本介绍 改进图卷积informer时间序列预测代码 CTR-GC卷积,informer&#xff0c;CTR-GC 图卷积…

docker desktop es windows解决vm.max_map_count [65530] is too low 问题

如果你使用windows上的docker desktop 搭建es相关的应用&#xff0c;大概率会遇到vm.vm.max_map_count [65530] is too low这个错误&#xff0c;本篇文章分享下怎么解决这个问题&#xff0c;主要分享长期解决的方法&#xff0c;重启机器之后也能生效的方法。 这个错误的详细信息…

ssm072基于bs模式的医院在线挂号预约系统的设计与实现+jsp(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;医院在线挂号预约系统的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以…

Web实时消息推送

Web实时消息推送 消息推送 web端消息推送移动端推送 消息推送&#xff1a;推Push 和 拉Pull 消息推送常见方案 短轮询 轮询&#xff1a;由浏览器向服务器发出HTTP请求&#xff0c;服务器实时返回未读消息数据给客户端&#xff0c;浏览器再做渲染显示。 JS请求 setInter…

ReactPress数据库表结构设计全面分析

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 ReactPress是一个基于React框架开发的开源发布平台和内容管理系统&#xff08;CMS&#xff09;。它不仅支持用户在支持React和MySQL数据库的服务器上搭建自己的博客和网站&#…

无线振动传感器的安装方法

lora无线温振一体传感器即传感器的采集时间&#xff0c;采集方式完全有主机通过命令实现。其主要特点是&#xff1a;传感器平时处在低功耗状态、传感器可以随时响应远程主机控制命令、传感器可采集特征值或者原始加速度数据 lora 技术&#xff0c;提高了传输速率多振动&#xf…

GESP4级考试语法知识(暴力枚举(三))

古堡算式代码&#xff1a; # include <stdio.h> int main(){int a, b, c, d, e;int x;int left, right;left a * 10000 b * 1000 c * 100 d * 10 e * 1;right e * 10000 d * 1000 c * 100 b * 10 a * 1;for(a 0; a < 9; a){for(b 0; b < 9; b){for(c …

CSS学习

目录 一、CSS概述 二、CSS的三种引入方式 (一)直接使用style标签编辑样式(调试样式代码时使用) (二)使用link标签引入CSS文件(上线时使用) (三)内嵌式(尽量不用&#xff0c;后期维护麻烦) 三、CSS常用选择器 (一)标签选择器(通过html标签选择元素) (二)class选择器(通…

数字IC实践项目(10)—基于System Verilog的DDR4 Model/Tb 及基础Verification IP的设计与验证(付费项目)

数字IC实践项目&#xff08;10&#xff09;—基于System Verilog的DDR4 Model/Tb 及基础Verification IP的设计与验证&#xff08;付费项目&#xff09; 前言项目框图1&#xff09;DDR4 Verification IP2&#xff09;DDR4 JEDEC Model & Tb 项目文件1&#xff09;DDR4 Veri…

NLP论文速读|ScPO:自我一致性的偏好优化(Self-Consistency Preference Optimization)

论文速读|Self-Consistency Preference Optimization 论文信息&#xff1a; 简介&#xff1a; 这篇论文试图解决的问题是如何在没有人类标注数据的情况下&#xff0c;提高大型语言模型&#xff08;LLMs&#xff09;在复杂推理任务上的性能。现有的自我对齐技术往往因为难以分配…