【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.rhkb.cn/news/468750.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UnixBench和Geekbench进行服务器跑分

1 概述 服务器的基准测试&#xff0c;常见的测试工具有UnixBench、Geekbench、sysbench等。本文主要介绍UnixBench和Geekbench。 1.1 UnixBench UnixBench是一款开源的测试UNIX系统基本性能的工具&#xff08;https://github.com/kdlucas/byte-unixbench&#xff09;&#x…

打造个性化时钟应用:结合视觉与听觉的创新实践

​ 在数字时代&#xff0c;虽然手机、电脑等设备已经能够非常方便地显示时间&#xff0c;但一款融合了视觉艺术和声音效果的桌面时钟仍能给我们的日常生活带来不一样的体验。本文将引导读者通过Python语言及其强大的库支持来创建一个具有整点及半点报时功能的美观时钟界面。该项…

ASMR助眠声音视频素材去哪找 吃播助眠素材网站分享

在快节奏的现代生活中&#xff0c;越来越多的人感到压力山大&#xff0c;许多人开始寻求助眠和放松的方式。而ASMR&#xff08;自发性知觉经络反应&#xff09;助眠声音视频&#xff0c;凭借其独特的声音刺激和放松效果&#xff0c;成为了睡前的“神器”。如果你是一位内容创作…

Ente: 我们的 Monorepo 经验

原文&#xff1a;manav - 2024.10.29 九个月前&#xff0c;我们切换到了 monorepo。在此&#xff0c;我将介绍我们迄今为止的切换经验。 这并不是一份规范性的建议&#xff0c;而是一个经验的分享&#xff0c;目的是希望能够帮助其他团队做出明智的决策。 与大多数岔路不同&…

css:还是语法

emmet的使用 emmet是一个插件&#xff0c;Emmet 是 Zen Coding 的升级版&#xff0c;由 Zen Coding 的原作者进行开发&#xff0c;可以快速的编写 HTML、CSS 以及实现其他的功能。很多文本编辑器都支持&#xff0c;我们只是学会使用它&#xff1a; 生成html结构 <!-- emme…

常见计算机网络知识整理(未完,整理中。。。)

TCP和UDP区别 TCP是面向连接的协议&#xff0c;发送数据前要先建立连接&#xff1b;UDP是无连接的协议&#xff0c;发送数据前不需要建立连接&#xff0c;是没有可靠性&#xff1b; TCP只支持点对点通信&#xff0c;UDP支持一对一、一对多、多对一、多对多&#xff1b; TCP是…

javascript实现国密sm4算法(支持微信小程序)

概述&#xff1a; 本人前端需要实现sm4计算的功能&#xff0c;最好是能做到分多次计算。 本文所写的代码在现有sm4的C代码&#xff0c;反复测试对比计算过程参数&#xff0c;成功改造成sm4的javascript代码&#xff0c;并成功验证好分多次计算sm4数据 测试平台&#xff1a; …

深度解读AI在数字档案馆中的创新应用:高效识别与智能档案管理

一、项目背景介绍 在信息化浪潮推动下&#xff0c;基于OCR技术的纸质档案电子化方案成为解决档案管理难题的有效途径。该方案通过先进的OCR技术&#xff0c;能够统一采集各类档案数据&#xff0c;无论是手写文件、打印文件、复古文档还是照片或扫描的历史资料&#xff0c;都能实…

C++ | Leetcode C++题解之第554题砖墙

题目&#xff1a; 题解&#xff1a; class Solution { public:int leastBricks(vector<vector<int>>& wall) {unordered_map<int, int> cnt;for (auto& widths : wall) {int n widths.size();int sum 0;for (int i 0; i < n - 1; i) {sum wi…

【机器学习】强化学习(1)——强化学习原理浅析(区分强化学习、监督学习和启发式算法)

文章目录 强化学习介绍强化学习和监督学习比较监督学习强化学习 强化学习的数学和过程表达动作空间序列决策策略&#xff08;policy&#xff09;价值函数&#xff08;value function&#xff09;模型&#xff08;model&#xff09; 强化学习和启发式算法比较强化学习步骤代码走…

模糊搜索:在不确定性中寻找精确结果

目录 模糊搜索&#xff1a;在不确定性中寻找精确结果 一、引言 二、模糊搜索的背景 三、模糊搜索的原理 1、编辑距离&#xff08;Levenshtein Distance&#xff09;&#xff1a; 2、Jaccard 相似系数&#xff1a; 3、Soundex 算法&#xff1a; 4、TF-IDF&#xff08;词…

MyBatis5-缓存

目录 一级缓存 二级缓存 MyBatis缓存查询的顺序 整合第三方缓存EHCache 一级缓存 一级缓存是 SqlSession 级别的&#xff0c;通过同一个 SqlSession 查询的数据会被缓存&#xff0c;下次查询相同的数据&#xff0c;就会从缓存中直接获取&#xff0c;不会从数据库重新访问 一…

95.【C语言】数据结构之双向链表的头插,头删,查找,中间插入,中间删除和销毁函数

目录 1.双向链表的头插 方法一 方法二 2.双向链表的头删 3.双向链表的销毁 4.双向链表的某个节点的数据查找 5.双向链表的中间插入 5.双向链表的中间删除 6.对比顺序表和链表 承接94.【C语言】数据结构之双向链表的初始化,尾插,打印和尾删文章 1.双向链表的头插 方法…

24-11-9-读书笔记(三十二)-《契诃夫文集》(六)上([俄] 契诃夫 [译] 汝龙)药品是甜的,真理是美的,咖啡是苦的,生活是什么啊?

文章目录 《契诃夫文集》&#xff08;六&#xff09;上&#xff08;[俄] 契诃夫 [译] 汝龙&#xff09;药品是甜的&#xff0c;真理是美的&#xff0c;咖啡是苦的&#xff0c;生活是什么啊&#xff1f;目录阅读笔记1. 新年的苦难2. 香槟3. 乞丐4. 仇敌5.薇罗琪卡6.在家里7. 太早…

【从零开始鸿蒙开发:01】自定义闪屏页

文章目录 大体介绍文件介绍各部分代码SplashPage.etsIndex.etsHomePage.etsroute_map.jsonmodule.json5 流程 大体介绍 文件介绍 其中&#xff1a; pages为我们的页面内容&#xff08;我个人理解功能性小于activity但是大于fragment&#xff09;route_map.json 为自定义的路由…

【Spring】获取Cookie和Session(@CookieValue()和@SessionAttribute())

文章目录 获取 Cookie传统获取 Cookie简洁获取 Cookie&#xff08;注解&#xff09; 获取 SessionSession 存储和获取简洁获取 Session (1)简洁获取 Session (2) 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给…

【机器学习】任务十:从函数分析到机器学习应用与BP神经网络

目录 1.从函数分析到机器学习应用 1.1 3D曲面图可视化报告 1.1.1 目标 1.1.2 代码分析 1.1.3 结果分析 1.1.4 观察与总结 1.1.5 结论 1.2 一元函数梯度计算报告 1.2.1 目标 1.2.2 代码分析 1.2.4 计算结果 1.2.5 优势与意义 1.2.6 结论 1.3 一元函数梯度和二阶导…

ios打包文件上传App Store windows工具

在苹果开发者中心上架IOS APP的时候&#xff0c;在苹果开发者中心不能直接上传打包文件&#xff0c;需要下载mac的xcode这些工具进行上传&#xff0c;但这些工具无法安装在windows或linux电脑上。 这里&#xff0c;我们可以不用xcode这些工具来上传&#xff0c;可以用国内的香…

Rust @绑定(Rust@绑定)(在模式匹配的同时将值绑定到变量)

文章目录 Rust中的绑定基础概念示例&#xff1a;基本模式匹配 绑定的使用示例&#xff1a;范围匹配并绑定变量 深入探索绑定的好处示例&#xff1a;复杂数据结构中的应用 总结 附加 Rust中的绑定 Rust 语言以其强类型系统和内存安全的特性著称。在进行模式匹配时&#xff0c;R…

JVM知识点大全(未完...)

JVM运行时数据区域 堆 堆是Java虚拟机中用于存储对象的主要区域&#xff0c;包括字符串常量池。绝大多数对象都是在堆中创建的&#xff08;少部分对象可能会在栈上分配&#xff09;。为了更好地进行垃圾回收&#xff0c;堆被划分为年轻代和老年代两部分。年轻代又被进一步分为E…