0


通过示例学习 Go 中的 Web 编程

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}}

Image

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}}

Image

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}}

Image

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 时,用 SecureHttpOnly 标志,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)}

Image

Websockets

我们将构建一个简单的服务器,它会回显我们发送给它的所有内容。

安装网络套接字包

go get -u github.com/gorilla/websocket

网络套接字

WebSocket

是一种在单个 TCP 连接上进行全双工通信的协议。

websocket.Upgrader

用于将 HTTP 连接升级为 WebStocket 连接,它提供了一些配置选项来控制升级过程。

  1. HandshakeTimeout:握手完成的超时时间。
  2. ReadBufferSize:读取缓冲区的大小(以字节为单位)。
  3. WriteBufferSize:写入缓冲区的大小(以字节为单位)。
  4. WriteBufferPool:用于写入缓冲区的池。
  5. Subprotocols:客户端支持的子协议列表。
  6. Error:在握手失败时调用的函数。
  7. CheckOrigin:用于检查请求来源的函数,防止跨站点请求伪造(CSRF)攻击。
  8. 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)}
标签: 学习 golang 后端

本文转载自: https://blog.csdn.net/zk1147756506/article/details/142208490
版权归原作者 此子不可久留丶 所有, 如有侵权,请联系我们删除。

“通过示例学习 Go 中的 Web 编程”的评论:

还没有评论