Hello World
Go 语言标准库中的
net/http
包就包含了有关 HTTP 协议的一些功能。我们将通过该包内函数,去实现一个简单的网络服务器,能够在本地浏览器来访问网页。
注册请求处理程序
首先我们需要创建一个方法来处理 HTTP 请求,这个方法有两个入参,类型如下。
http.ResponseWriter
:我们通过这个接口来去设置 HTTP 响应的内容,这个接口要求实现三个方法。
Header
方法用于设置或获取响应头信息。Write
方法用于将数据写入响应体。WriteHeader
用于设置响应状态码,如果不调用则默认设置为 200 OK。
type ResponseWriter interface{Header() Header
Write([]byte)(int,error)WriteHeader(statusCode int)}
http.Request
:Request 表示服务器接收到的或客户端发送的 HTTP 请求,包含了请求中的字段,如 Method、URL、Header(http.Header)、Body(io.ReadCLoser)、ContentLength、Host、Form、PostForm、MultipartForm、RemoteAddr、RequestURI。
FormValue
方法可以获取表单中指定键的值。PostForm
方法可以获取 POST 表单中指定键的值。FormFile
方法可以获取上传的文件。ParseForm
方法解析 URL 查询参数和 POST 表单数据。ParseMultiparForm
方法解析多部分表单数据。
type Request struct{
Method string
URL *url.URL
Proto string// "HTTP/1.0"
ProtoMajor int// 1
ProtoMinor int// 0
Header Header
Body io.ReadCloser
GetBody func()(io.ReadCloser,error)
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
Cancel <-chanstruct{}
Response *Response
Pattern string}
我们用
http.HandleFunc
方法把匹配路径的请求处理注册到服务器上。
http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){_, err := fmt.Fprintf(w,"Hello, you've requested: %s\n", r.URL.Path)if err !=nil{return}})
监听客户端发送的请求
我们注册好对应的处理方法后,需要打开监听器,对服务器暴露的端口进行监听。
http.ListenAndServe
:该方法会监听 TCP 网络地址,然后用 handler 方法调用服务器来去处理请求,如果 handler 方法为 nil 则会自动在服务器查询已经注册的处理函数。
完整代码示例如下:
package main
import("fmt""net/http")funcmain(){
http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){_, err := fmt.Fprintf(w,"Hello, you've requested: %s\n", r.URL.Path)if err !=nil{return}})
err := http.ListenAndServe(":8080",nil)if err !=nil{return}}
HTTP Server
这里我们将要搭建一个 HTTP 服务器,它需要处理这么几个任务。
Process dynamic requets
:处理来自用户的请求。Serve static assets
:给用户提供静态资源。Accept connections
:监听指定端口接收请求。
处理动态请求
在上面的内容我们其实已经介绍了如何处理来自用户的请求,主要是通过
http.HandleFunc
方法在服务器中注册匹配项的处理。对于用户发送的请求,我们可以用
r.URL.Query().Get("token")
或
r.FormValue("email")
来获取参数进行处理。
http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w,"Welcome to my website!")})
服务器静态资源
我们可以通过内置的
http.FileServer
来指向静态资源的路径,该方法会返回一个实现了
http.Handler
接口的处理器,可以将其挂载到指定的路径上。
这里,我们通过
http.Dir
将指定目录
static
作为根目录创建文件服务器
fs := http.FileServer(http.Dir("static/"))
接下来,我们就将该文件系统挂载到
/static/
路径上,并且通过
http.StripPrefix
方法去除了根目录前缀。假设,我们
static
目录中有一个文件
example.txt
内容为
This is an example file.
那么我们访问
http://localhost/static/example.txt
时,服务器会去除
/static/
前缀,询问文件服务器获取
example.txt
,那么查找返回的就是
static/example.txt
文件的内容,如果不去掉就会查找
static/static/example.txt
了。
http.Handle("/static/", http.StripPrefix("/static/", fs))
监听请求
与我们上个案例介绍的一样,通过
http.ListenAndServe
监听端口。
完整代码如下:
package main
import("fmt""net/http")funcmain(){
http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){_, err := fmt.Fprintf(w,"Hello, you've requested: %s\n", r.URL.Path)if err !=nil{return}})
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
err := http.ListenAndServe(":8080",nil)if err !=nil{return}}
Routing
我们已经使用过 Go 语言提供的一些关于 HTTP 协议的方法了。但是我们会发现,对于 URL 多参数的划分处理其实并不太方面。
那么,这里我们会用
gorilla/mux
来去路由。
安装路由器包
gorilla/mux
是一个适应 Go 默认 HTTP 路由器的包,它提供了许多方法来提高编写 Web 应用程序的效率。
go get -u github.com/gorilla/mux
创建新的路由器
路由器是 Web 应用程序的主路由器,稍后将作为参数传递给服务器,它负责接收所有 HTTP 链接并传递给注册的处理程序。
r := mux.NewRouter()
注册请求处理函数
与之前的区别是,因为我们使用
gorilla/mux
包,所以要用
r.HandleFunc
方法来去注册到路由器上。
资源定位参数
举个例子,在我们的应用有这样一条 URL:
/books/go-programming-blueprint/page/10
Book title
:go-programming-blueprint;Page
:10。
这两个字段都是动态字段,在注册处理函数的时候,匹配 URL 模式中要替换为占位符,我们再通过
mux.Vars
方法来获取参数。
r.HandleFunc("/books/{title}/page/{page}",func(w http.ResponseWriter, r *http.Request){
vars := mux.Vars(r)
vars["title"]// the book title slug
vars["page"]// the page})
设置服务器路由器
在我们之前的例子中,监听器都是没有设置的路由,也就是第二个参数为
nil
,此时会自动检索默认的服务器。现在,我们有了路由器就可以设置了。
http.ListenServe(":8080", r)
完整代码如下:
package main
import("fmt""github.com/gorilla/mux""net/http")funcmain(){
r := mux.NewRouter()
r.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){_, err := fmt.Fprintf(w,"Hello, you've requested: %s\n", r.URL.Path)if err !=nil{return}})
r.HandleFunc("/books/{title}/page/{page}",func(w http.ResponseWriter, r *http.Request){
vars := mux.Vars(r)
title := vars["title"]
page := vars["page"]_, err := fmt.Fprintf(w,"You've requested the book: %s on page %s\n", title, page)if err !=nil{return}})
err := http.ListenAndServe(":8080", r)if err !=nil{return}}
MySQL DataBase
当我们需要加载动态数据的时候,就需要用到数据库,比如说用户的登录与注册。
安装数据库驱动
Go 编程语言附带了一个名为
database/sql
的包来查询各种 SQL 数据库,此外还需要不同数据库的驱动来去获得对数据库的连接。
go get -u github.com/go-sql-driver/mysql
连接数据库
检查我们是否可以连接到数据库,导入
database/sql
和
go-sql-driver/mysql
包并打开一个连接,
import"database/sql"import_"go-sql-driver/mysql"
db, err := sql.Open("mysql","username:password@(127.0.0.1:3306)/dbname?parseTime=true")
err := db.Ping()
创建数据库表
假设我们要创建如下的表,就要编写如下的 SQL 语句。
idusernamepasswordcreated_at1johndoesecret2019-08-10 12:30:00
CREATETABLE users(
id INTAUTO_INCREMENT,
username TEXTNOTNULL,
password TEXTNOTNULL,
create_at DATETIME,PRIMARYKEY(id));
现在我们在 GO 语言中,通过
database/sql
包来去执行 SQL 语句。
query :=`
CREATE TABLE users IF NOT EXIST(
id INT AUTO_INCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
created_at DATETIME,
PRIMARY KEY (id)
);`_, err := db.Exec(query)
插入数据到表
问号告诉 SQL 驱动程序,它们是实际数据的占位符。
INSERTINTO users(username, password, created_at)VALUES(?, ?, ?)
现在,我们在 Go 中往数据库中插入数据。
import"time"
username :="Yikuanzz"
password :="secret"
createdAt := time.Now()
result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`,
username, password, createdAt
)
获取最新的用户 ID,可以通过下面的方法。
userID, err := result.LastInsertId()
查询数据库表
通过
db.Query
可以进查询出多行数据,
db.QueryRow
可以查询特定的行。
SELECT id, username, password, created_at FROM users WHERE id = ?
在 GO 中我们可以声明一些变量来存储要查询出的数据。
var(
id int
username string
password string
createdAt time.time
)
query :=`SELECT id, username, password, created_at FROM user WHERE id = ?`
err := db.QueryRow(query,1).Scan(&id,&username,&password,&createdAt)
查询所有用户数据
我们通过下面的 SQL 语句来查询全部用户的数据。
SELECT id, username, password, created_at FROM users
不同的是,我们可以把实体的属性写到结构体中,然后迭代返回用户信息。
type user struct{
id int
username string
password string
createdAt time.Time
}
rows, err := db.Query(`SELECT id, username, password, created_at FROM users`)defer rows.Close()var users []user
for rows.Next(){var u user
err := rows.Scan(&u.id,&u.username,&u.password,&u.createdAt)
users =append(users, u)}
err := rows.Err()
从表中删除数据
与前面执行的 SQL 语句没有什么区别。
_, err := db.Exec(`DELETE FROM users WHRE id = ?`,1)
完整代码如下:
package main
import("database/sql""fmt"_"github.com/go-sql-driver/mysql""log""time")funcmain(){var queryID int64
db, err := sql.Open("mysql","user:password@(127.0.0.1:3306)/dbname?parseTime=true")if err !=nil{
log.Fatal(err)}{// Create a new Table
query :=`
CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT, username TEXT NOT NULL, password TEXT NOT NULL, created_at DATETIME, PRIMARY KEY (id) );`if_, err := db.Exec(query); err !=nil{
log.Fatal(err)}}{// Insert a new user
username :="Yikuanzz"
password :="secret"
createAt := time.Now()
res, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES ( ?, ?, ?)`,
username, password, createAt)if err !=nil{
log.Fatal(err)}
id, err := res.LastInsertId()if err !=nil{
log.Fatal(err)}else{
queryID = id
fmt.Printf("id: %d\n", id)}}{// Query a single user var(
id int
username string
password string
createAt time.Time
)
query :="SELECT id, username, password, created_at FROM users WHERE id = ?"if err := db.QueryRow(query, queryID).Scan(&id,&username,&password,&createAt); err !=nil{
log.Fatal(err)}else{
fmt.Println(id, username, password, createAt)}}{// Query all users type User struct{
id int
username string
password string
createAt time.Time
}
rows, err := db.Query("SELECT id, username, password, created_at FROM users")if err !=nil{
log.Fatal(err)}deferfunc(rows *sql.Rows){
err := rows.Close()if err !=nil{
log.Fatal(err)}}(rows)var users []User
for rows.Next(){var user User
if err := rows.Scan(&user.id,&user.username,&user.password,&user.createAt); err !=nil{
log.Fatal(err)}
users =append(users, user)}if err := rows.Err(); err !=nil{
log.Fatal(err)}
fmt.Printf("%#v\n", users)}{// Delete data _, err := db.Exec(`DELETE FROM users WHERE id = ?`,1)if err !=nil{
log.Fatal(err)}}}
Templates
GO 语言的
html/template
包提供了丰富的 HTML 模版,能够让我们方便地在 Web 应用中展示数据。
第一个模板
我们写一个 TODO List 用 HTML 的
<ul>
,渲染模版时,传入的数据可以是任意的数据结构。我们用
{{.}}
来访问模版上的数据,其中的
.
是数据的根元素。
data := TodoPageData{
PageTitle:"My TODO list",
Todos:[]Todo{{Title:"Task 1", Done:false},{Title:"Task 2", Done:true},{Title:"Task 3", Done:true},},}
<h1>{{.PageTitle}}</h1><ul>
{{range .Todos}}
{{if .Done}}
<liclass="done">{{.Title}}</li>
{{else}}
<li>{{.Title}}</li>
{{end}}
{{end}}
</ul>
控制结构
模版语言包含丰富的控制结构来渲染 HTML。
Control StructureDefinition
{{/* a comment */}}
定义注释
{{.}}
渲染根元素
{{.Title}}
在嵌套元素中渲染标题字段
{{if .Done}} {{else}} {{end}}
定义条件语句
{{range .Todos}} {{.}} {{end}}
循环所有的 “Todos” 并用
{{.}}
渲染每个 “Todos”
{{block "content" .}} {{end}}
定义名为 “content” 的块
从文件中解析模版
模版可以从字符串或磁盘上的文件中解析,其中
template.Must
方法是用来确保模板解析过程中没有错误,如果有错误会触发
panic
。
tmpl, err := template.ParseFiles("layout.html")// or
tmp := template.Must(template.ParseFiles("layout.html"))
请求处理函数中执行模板
Execute
函数接收一个
io.Writer
来去写入模版,通过接口将数据传入模板,此时方法会调用
http.ResponseWriter
进行写入,也自动在 HTTP 响应中写入
Content-Type: text/html;charset=utf-8
。
func(w http.ResponseWrtier, r *http.Request){
tmpl.Execute(w,"data goes here")}
完整代码如下:
// main.gopackage main
import("html/template""net/http")type Todo struct{
Title string
Done bool}type TodoPageDate struct{
PageTitle string
Todos []Todo
}funcmain(){
tmpl := template.Must(template.ParseFiles("./static/layout.html"))
http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){
data := TodoPageDate{
PageTitle:"My TODO list",
Todos:[]Todo{{Title:"Task 1", Done:false},{Title:"Task 2", Done:true},{Title:"Task 3", Done:true},},}
err := tmpl.Execute(w, data)if err !=nil{return}})
err := http.ListenAndServe(":8080",nil)if err !=nil{return}}
<!-- layout.html --><h1>{{.PageTitle}}</h1><ul>
{{range .Todos}}
{{if .Done}}
<liclass="done">{{.Title}}</li>
{{else}}
<li>{{.Title}}</li>
{{end}}
{{end}}
</ul>
Assets and Form
这里我们会展示静态资源的渲染和表单的信息传递。
静态资源
比如,我们将资源给到
assets
目录下,也可以将其处理与
static
进行匹配。
// static-files.gopackage main
import"net/http"funcmain(){
fs := http.FileServer(http.Dir("assets/"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.ListenAndServe(":8080",nil)}
$ tree assets/
assets/
└── css
└── styles.css
$ go run static-files.go
$ curl-s http://localhost:8080/static/css/styles.css
body {
background-color: black;}
表单提交
如果客户端发送的是
request
请求,那么就渲染表单模板,而当发送的是
post
请求,就把数据接收,并返回成功。
// forms.gopackage main
import("html/template""net/http")type ContactDetails struct{
Email string
Subject string
Message string}funcmain(){
tmpl := template.Must(template.ParseFiles("forms.html"))
http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){if r.Method != http.MethodPost {
tmpl.Execute(w,nil)return}
details := ContactDetails{
Email: r.FormValue("email"),
Subject: r.FormValue("subject"),
Message: r.FormValue("message"),}// do something with details_= details
tmpl.Execute(w,struct{ Success bool}{true})})
http.ListenAndServe(":8080",nil)}
<!-- forms.html -->
{{if .Success}}
<h1>Thanks for your message!</h1>
{{else}}
<h1>Contact</h1><formmethod="POST"><label>Email:</label><br/><inputtype="text"name="email"><br/><label>Subject:</label><br/><inputtype="text"name="subject"><br/><label>Message:</label><br/><textareaname="message"></textarea><br/><inputtype="submit"></form>
{{end}}
Middleware
这里我们,创建一个基础的日志中间件,它将
http.HandlerFunc
作为其参数,包装返回一个新的
http.HandlerFunc
给服务器调用。
简单的中间件
通过简单的封装之后,我们就可以记录每次的访问路径情况了,以此为拓展,我们还可以记录很多其他信息。
// basic-middleware.gopackage main
import("fmt""log""net/http")funclogging(f http.HandlerFunc) http.HandlerFunc {returnfunc(w http.ResponseWriter, r *http.Request){
log.Println(r.URL.Path)f(w, r)}}funcfoo(w http.ResponseWriter, r *http.Request){
fmt.Fprintln(w,"foo")}funcbar(w http.ResponseWriter, r *http.Request){
fmt.Fprintln(w,"bar")}funcmain(){
http.HandleFunc("/foo",logging(foo))
http.HandleFunc("/bar",logging(bar))
http.ListenAndServe(":8080",nil)}
中间件进阶
在这里,我们定义了一种新类型的中间件,它可以轻松地将多个中间件链接在一起。
package main
import("fmt""log""net/http""time")type Middleware func(http.HandlerFunc) http.HandlerFunc
funcLogging() Middleware{// 创建一个新的中间件returnfunc(f http.HandlerFunc) http.HandlerFunc{// 定义处理函数returnfunc(w http.ResponseWriter, r *http.Request){
start := time.Now()deferfunc(){
log.Println(r.URL.Path, time.Since(start))}f(w, r)}}}funcMethod(m string) Middleware{// 创建新的中间件returnfunc(f http.HandlerFunc) http.HandlerFunc{// 定义处理函数returnfunc(w http.ResponseWriter, r *http.Request){if r.Method != m{
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)}f(w, r)}}}funcChain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc{for_, m :=range middlewares{
f =m(f)}return f
}funcHello(w http.ResponseWriter, r *http.Request){
fmt.Fprintln(w,"Hello World")}funcmain(){
http.HandleFunc("/",Chain(Hello,Method("GET"),Logging()))
http.ListenAndServe(":8080",nil)}
Sessions
这里我们一起看看怎么通过
gorilla/sessions
来在
session
和
cookie
中存储数据。
会话定义
Cookie
是存储在用户浏览器中的小数据片段,并根据每个请求发送到我们的服务器。通常用于保存用户的登录状态、偏好设置、跟踪用户行为等等,大小通常为 4KB。
http.SetCookie(w,&http.Cookie{
Name:"username",
Value:"john_doe",
Path:"/",
MaxAge:3600,// 1 hour})
Session
是存储在服务器上的数据,用于跟踪用户的回话状态。每个
Session
都有一个唯一的
Session ID
,客户端通过
Cookie
或
URL
参数将这个 ID 传递给服务器。
session,_:= store.Get(r,"session-name")
session.Values["username"]="john_doe"
session.Save(r, w)
会话常用函数
session
常用的函数有:
sessions.NewCookieStore()
:创建新的 Cookie 存储对象,通常传入一个随机字符串作为秘钥来加密和验证Session
数据。store.Get()
:获取或创建一个Session
。session.Save()
:保存Session
数据。session.Values
:用于存储和获取Session
中的数据。session.Options.MaxAge
:设置Session
的最大存储时间,负值表示删除Session
。
一般来说,当客户端发送请求的的时候,我们用
store.Get
检查请求中的 Cookie 看看是否有名为
session-name
的 Cookie。如果没有我们就在程序内存中创建一个新的 Session 并且它具有唯一的 Session ID。然后,在我们完成一些数据操作之后,我们会用
store.Save
来把 Session 存到内存上或者持久化,并在给客户端的响应中设置一个 Cookie,包含这个 Session ID。这个 Cookie 会在后续请求中被客户端发送回服务器,以便服务器识别会话。
此外,可能要注意的问题是会话劫持(Session Hijacking)的安全风险,就是说攻击者截获合法用户的
session ID
并利用它冒充用户访问服务器资源。为了防止这种情况,我们可采取一下安全措施:
- 用 HTTPS 加密通信,防止
session ID
在传输过程中被截获,确保所有的请求和响应都通过 HTTPS 进行传输。 - 在设置 Cookie 时,用
Secure
和HttpOnly
标志,Secure
可以确保 Cookie 只能通过 HTTPS 传输,而HttpOnly
可以防止 JavaScript 访问 Cookie,减少 XSS 攻击风险。
package main
import("fmt""net/http""github.com/gorilla/sessions")var(// key must be 16, 24 or 32 bytes long (AES-128, AES-192 or AES-256)
key =[]byte("super-secret-key")
store = session.NewCookieStore(key))funcsecret(w http.ResponseWriter, r *http.Request){
session,_:= store.Get(r,"cookie-name")if auth, ok := session.Values["authenticated"].(bool);!ok ||!auth {
http.Error(w,"Forbidden", http.StatusForbidden)return}
fmt.Fprintln(w,"The cake is a lie!")}funclogin(w http.ResponseWriter, r *http.Request){
session,_:= store.Get(r,"cookie-name")
session.Value["authenticated"]=true
session.Save(r, w)}funclogout(w http.ResponseWriter, r *http.Request){
session,_:= store.Get(r,"cookie-name")
session.Values["authenticated"]=false
session.Save(r, w)}funcmain(){
http.HandleFunc("/secret", sercet)
http.HandleFunc("/login", login)
http.HandleFunc("/logout", logout)
http.ListenAndServe(":8080",nil)}
JSON
这里会展示的是如何用
encoding/json
来编解码 JSON 数据。
编码序列化
json.Marshal
将 Go 结构体编码为 JSON 字符串,我们会用字符标识在结构体字段中标注要转换成的 JSON 名。
package main
import("encoding/json""fmt")type User struct{
Name string`json:"name"`
Age int`json:"age"`}funcmain(){
user := User{Name:"Alice", Age:30}
jsonData, err := json.Marshal(user)if err !=nil{
fmt.Println(err)return}
fmt.Println(string(jsonData))}
解码反序列化
json.Unmarshal
将 JSON 字符串解码为 GO 结构体。
package main
import("encoding/json""fmt")type User struct{
Name string`json:"name"`
Age int`json:"age"`}funcmain(){
jsonString :=`{"name":"Alice","age":30}`var user User
err := json.Unmarshal([]byte(jsonString),&user)if err !=nil{
fmt.Println(err)return}
fmt.Println(user)}
编码和解码使用
// json.gopackage main
import("encoding/json""fmt""net/http")type User struct{
Firstname string`json:"firstname"`
Lastname string`json:"lastname"`
Age int`json:"age"`}funcmain(){
http.HandleFunc("/decode",func(w http.ResponseWriter, r *http.Request){var user User
json.NewDecoder(r.Body).Decode(&user)
fmt.Fprintf(w,"%s %s is %d years old!", user.Firstname, user.Lastname, user.Age)})
http.HandleFunc("/encode",func(w http.ResponseWriter, r *http.Request){
peter := User{
Firstname:"John",
Lastname:"Doe",
Age:25,}
json.NewEncoder(w).Encode(peter)})
http.ListenAndServe(":8080",nil)}
Websockets
我们将构建一个简单的服务器,它会回显我们发送给它的所有内容。
安装网络套接字包
go get -u github.com/gorilla/websocket
网络套接字
WebSocket
是一种在单个 TCP 连接上进行全双工通信的协议。
websocket.Upgrader
用于将 HTTP 连接升级为 WebStocket 连接,它提供了一些配置选项来控制升级过程。
- HandshakeTimeout:握手完成的超时时间。
- ReadBufferSize:读取缓冲区的大小(以字节为单位)。
- WriteBufferSize:写入缓冲区的大小(以字节为单位)。
- WriteBufferPool:用于写入缓冲区的池。
- Subprotocols:客户端支持的子协议列表。
- Error:在握手失败时调用的函数。
- CheckOrigin:用于检查请求来源的函数,防止跨站点请求伪造(CSRF)攻击。
- EnableCompression:是否启用 WebSocket 压缩扩展。
type Upgrader struct{
HandshakeTimeout time.Duration
ReadBufferSize int
WriteBufferSize int
WriteBufferPool BufferPool
Subprotocols []string
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
CheckOrigin func(r *http.Request)bool
EnableCompression bool}
websocket.Upgrader.Upgrage
是 WebSocket 服务器实现的核心部分,它传入 HTTP 的请求和响应,可选
http.Header
设置额外的头部信息,返回一个
*Conn
WebSocket 连接对象。
*wensocket.Conn
类型常用的方法:
ReadMessage() (messageType int, p []byte, err error)
:读取消息,返回小李类型、消息内容和错误信息。WriteMessage(messageType int, data []byte) error
:发送消息,指定消息类型和消息内容。Close() error
:关闭连接。SetReadDeadline(t time.Timer) error
:设置读取消息的超时时间。SetWriteDeadline(t time.Timer) error
:设置写入消息的超时时间。
回显服务器
// websockets.gopackage main
import("fmt""net/http""github.com/gorilla/websocket")var upgrader = websocket.Upgrader{
ReadBufferSize:1024,
WriteBufferSize:1024,}funcmain(){
http.HandleFunc("/echo",func(w http.ResponseWriter, r *http.Request){
conn,_:= upgrader.Upgrade(w, r,nil)// error ignored for sake of simplicityfor{// Read message from browser
msgType, msg, err := conn.ReadMessage()if err !=nil{return}// Print the message to the console
fmt.Printf("%s sent: %s\n", conn.RemoteAddr(),string(msg))// Write message back to browserif err = conn.WriteMessage(msgType, msg); err !=nil{return}}})
http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){
http.ServeFile(w, r,"websockets.html")})
http.ListenAndServe(":8080",nil)}
<!-- websockets.html --><inputid="input"type="text"/><buttononclick="send()">Send</button><preid="output"></pre><script>var input = document.getElementById("input");var output = document.getElementById("output");var socket =newWebSocket("ws://localhost:8080/echo");
socket.onopen=function(){
output.innerHTML +="Status: Connected\n";};
socket.onmessage=function(e){
output.innerHTML +="Server: "+ e.data +"\n";};functionsend(){
socket.send(input.value);
input.value ="";}</script>
Password Hashing
在这个示例中,我们会用
bcrypt
来对密码进行哈希处理。
go get -u golang.org/x/crypto/bcrypt
密码哈希算法
在 Go 中,
bcrypt
是一种常用的密码哈希算法,用于安全地存储和验证密码。
- 安全性高:使用了随机盐值和多次哈希,增加了破解难度。
- 适应性强:通过增加迭代次数提高安全性,适应不断提升的计算能力。
- 不可逆:单向哈希,无法通过哈希值反推出原始密码。
bcrypt.GenerateFromPassword
方法用于生成哈希密码。
bcrypt.CompareHashAndPassword
方法用于验证用户输入的密码是否正确。
package utils
import("golang.org/x/crypto/bcrypt")// GenerateFromPassword 生成哈希密码funcGenerateFromPassword(password string)(string,error){
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)if err !=nil{return"", err
}returnstring(hashedPassword),nil}// CompareHashAndPassword 验证密码funcCompareHashAndPassword(hashPassword, password string)bool{
err := bcrypt.CompareHashAndPassword([]byte(hashPassword),[]byte(password))return err ==nil}
加密和解密
// passwords.gopackage main
import("fmt""golang.org/x/crypto/bcrypt")funcHashPassword(password string)(string,error){
bytes, err := bcrypt.GenerateFromPassword([]byte(password),14)returnstring(bytes), err
}funcCheckPasswordHash(password, hash string)bool{
err := bcrypt.CompareHashAndPassword([]byte(hash),[]byte(password))return err ==nil}funcmain(){
password :="secret"
hash,_:=HashPassword(password)// ignore error for the sake of simplicity
fmt.Println("Password:", password)
fmt.Println("Hash: ", hash)
match :=CheckPasswordHash(password, hash)
fmt.Println("Match: ", match)}
版权归原作者 此子不可久留丶 所有, 如有侵权,请联系我们删除。