0


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

Hello World

Go 语言标准库中的

  1. net/http

包就包含了有关 HTTP 协议的一些功能。我们将通过该包内函数,去实现一个简单的网络服务器,能够在本地浏览器来访问网页。

注册请求处理程序

首先我们需要创建一个方法来处理 HTTP 请求,这个方法有两个入参,类型如下。

  1. http.ResponseWriter

:我们通过这个接口来去设置 HTTP 响应的内容,这个接口要求实现三个方法。

  • Header 方法用于设置或获取响应头信息。
  • Write 方法用于将数据写入响应体。
  • WriteHeader 用于设置响应状态码,如果不调用则默认设置为 200 OK。
  1. type ResponseWriter interface{Header() Header
  2. Write([]byte)(int,error)WriteHeader(statusCode int)}
  1. 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 方法解析多部分表单数据。
  1. type Request struct{
  2. Method string
  3. URL *url.URL
  4. Proto string// "HTTP/1.0"
  5. ProtoMajor int// 1
  6. ProtoMinor int// 0
  7. Header Header
  8. Body io.ReadCloser
  9. GetBody func()(io.ReadCloser,error)
  10. ContentLength int64
  11. TransferEncoding []string
  12. Close bool
  13. Host string
  14. Form url.Values
  15. PostForm url.Values
  16. MultipartForm *multipart.Form
  17. Trailer Header
  18. RemoteAddr string
  19. RequestURI string
  20. TLS *tls.ConnectionState
  21. Cancel <-chanstruct{}
  22. Response *Response
  23. Pattern string}

我们用

  1. http.HandleFunc

方法把匹配路径的请求处理注册到服务器上。

  1. 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}})

监听客户端发送的请求

我们注册好对应的处理方法后,需要打开监听器,对服务器暴露的端口进行监听。

  1. http.ListenAndServe

:该方法会监听 TCP 网络地址,然后用 handler 方法调用服务器来去处理请求,如果 handler 方法为 nil 则会自动在服务器查询已经注册的处理函数。

完整代码示例如下:

  1. package main
  2. import("fmt""net/http")funcmain(){
  3. 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}})
  4. err := http.ListenAndServe(":8080",nil)if err !=nil{return}}

Image

HTTP Server

这里我们将要搭建一个 HTTP 服务器,它需要处理这么几个任务。

  • Process dynamic requets:处理来自用户的请求。
  • Serve static assets:给用户提供静态资源。
  • Accept connections:监听指定端口接收请求。

处理动态请求

在上面的内容我们其实已经介绍了如何处理来自用户的请求,主要是通过

  1. http.HandleFunc

方法在服务器中注册匹配项的处理。对于用户发送的请求,我们可以用

  1. r.URL.Query().Get("token")

  1. r.FormValue("email")

来获取参数进行处理。

  1. http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){
  2. fmt.Fprint(w,"Welcome to my website!")})

服务器静态资源

我们可以通过内置的

  1. http.FileServer

来指向静态资源的路径,该方法会返回一个实现了

  1. http.Handler

接口的处理器,可以将其挂载到指定的路径上。

这里,我们通过

  1. http.Dir

将指定目录

  1. static

作为根目录创建文件服务器

  1. fs := http.FileServer(http.Dir("static/"))

接下来,我们就将该文件系统挂载到

  1. /static/

路径上,并且通过

  1. http.StripPrefix

方法去除了根目录前缀。假设,我们

  1. static

目录中有一个文件

  1. example.txt

内容为

  1. This is an example file.

那么我们访问

  1. http://localhost/static/example.txt

时,服务器会去除

  1. /static/

前缀,询问文件服务器获取

  1. example.txt

,那么查找返回的就是

  1. static/example.txt

文件的内容,如果不去掉就会查找

  1. static/static/example.txt

了。

  1. http.Handle("/static/", http.StripPrefix("/static/", fs))

监听请求

与我们上个案例介绍的一样,通过

  1. http.ListenAndServe

监听端口。

完整代码如下:

  1. package main
  2. import("fmt""net/http")funcmain(){
  3. 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}})
  4. fs := http.FileServer(http.Dir("static"))
  5. http.Handle("/static/", http.StripPrefix("/static/", fs))
  6. err := http.ListenAndServe(":8080",nil)if err !=nil{return}}

Image

Routing

我们已经使用过 Go 语言提供的一些关于 HTTP 协议的方法了。但是我们会发现,对于 URL 多参数的划分处理其实并不太方面。

那么,这里我们会用

  1. gorilla/mux

来去路由。

安装路由器包

  1. gorilla/mux

是一个适应 Go 默认 HTTP 路由器的包,它提供了许多方法来提高编写 Web 应用程序的效率。

  1. go get -u github.com/gorilla/mux

创建新的路由器

路由器是 Web 应用程序的主路由器,稍后将作为参数传递给服务器,它负责接收所有 HTTP 链接并传递给注册的处理程序。

  1. r := mux.NewRouter()

注册请求处理函数

与之前的区别是,因为我们使用

  1. gorilla/mux

包,所以要用

  1. r.HandleFunc

方法来去注册到路由器上。

资源定位参数

举个例子,在我们的应用有这样一条 URL:

  1. /books/go-programming-blueprint/page/10
  • Book title:go-programming-blueprint;
  • Page:10。

这两个字段都是动态字段,在注册处理函数的时候,匹配 URL 模式中要替换为占位符,我们再通过

  1. mux.Vars

方法来获取参数。

  1. r.HandleFunc("/books/{title}/page/{page}",func(w http.ResponseWriter, r *http.Request){
  2. vars := mux.Vars(r)
  3. vars["title"]// the book title slug
  4. vars["page"]// the page})

设置服务器路由器

在我们之前的例子中,监听器都是没有设置的路由,也就是第二个参数为

  1. nil

,此时会自动检索默认的服务器。现在,我们有了路由器就可以设置了。

  1. http.ListenServe(":8080", r)

完整代码如下:

  1. package main
  2. import("fmt""github.com/gorilla/mux""net/http")funcmain(){
  3. r := mux.NewRouter()
  4. 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}})
  5. r.HandleFunc("/books/{title}/page/{page}",func(w http.ResponseWriter, r *http.Request){
  6. vars := mux.Vars(r)
  7. title := vars["title"]
  8. page := vars["page"]_, err := fmt.Fprintf(w,"You've requested the book: %s on page %s\n", title, page)if err !=nil{return}})
  9. err := http.ListenAndServe(":8080", r)if err !=nil{return}}

Image

MySQL DataBase

当我们需要加载动态数据的时候,就需要用到数据库,比如说用户的登录与注册。

安装数据库驱动

Go 编程语言附带了一个名为

  1. database/sql

的包来查询各种 SQL 数据库,此外还需要不同数据库的驱动来去获得对数据库的连接。

  1. go get -u github.com/go-sql-driver/mysql

连接数据库

检查我们是否可以连接到数据库,导入

  1. database/sql

  1. go-sql-driver/mysql

包并打开一个连接,

  1. import"database/sql"import_"go-sql-driver/mysql"
  2. db, err := sql.Open("mysql","username:password@(127.0.0.1:3306)/dbname?parseTime=true")
  3. err := db.Ping()

创建数据库表

假设我们要创建如下的表,就要编写如下的 SQL 语句。
idusernamepasswordcreated_at1johndoesecret2019-08-10 12:30:00

  1. CREATETABLE users(
  2. id INTAUTO_INCREMENT,
  3. username TEXTNOTNULL,
  4. password TEXTNOTNULL,
  5. create_at DATETIME,PRIMARYKEY(id));

现在我们在 GO 语言中,通过

  1. database/sql

包来去执行 SQL 语句。

  1. query :=`
  2. CREATE TABLE users IF NOT EXIST(
  3. id INT AUTO_INCREMENT,
  4. username TEXT NOT NULL,
  5. password TEXT NOT NULL,
  6. created_at DATETIME,
  7. PRIMARY KEY (id)
  8. );`_, err := db.Exec(query)

插入数据到表

问号告诉 SQL 驱动程序,它们是实际数据的占位符。

  1. INSERTINTO users(username, password, created_at)VALUES(?, ?, ?)

现在,我们在 Go 中往数据库中插入数据。

  1. import"time"
  2. username :="Yikuanzz"
  3. password :="secret"
  4. createdAt := time.Now()
  5. result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`,
  6. username, password, createdAt
  7. )

获取最新的用户 ID,可以通过下面的方法。

  1. userID, err := result.LastInsertId()

查询数据库表

通过

  1. db.Query

可以进查询出多行数据,

  1. db.QueryRow

可以查询特定的行。

  1. SELECT id, username, password, created_at FROM users WHERE id = ?

在 GO 中我们可以声明一些变量来存储要查询出的数据。

  1. var(
  2. id int
  3. username string
  4. password string
  5. createdAt time.time
  6. )
  7. query :=`SELECT id, username, password, created_at FROM user WHERE id = ?`
  8. err := db.QueryRow(query,1).Scan(&id,&username,&password,&createdAt)

查询所有用户数据

我们通过下面的 SQL 语句来查询全部用户的数据。

  1. SELECT id, username, password, created_at FROM users

不同的是,我们可以把实体的属性写到结构体中,然后迭代返回用户信息。

  1. type user struct{
  2. id int
  3. username string
  4. password string
  5. createdAt time.Time
  6. }
  7. rows, err := db.Query(`SELECT id, username, password, created_at FROM users`)defer rows.Close()var users []user
  8. for rows.Next(){var u user
  9. err := rows.Scan(&u.id,&u.username,&u.password,&u.createdAt)
  10. users =append(users, u)}
  11. err := rows.Err()

从表中删除数据

与前面执行的 SQL 语句没有什么区别。

  1. _, err := db.Exec(`DELETE FROM users WHRE id = ?`,1)

完整代码如下:

  1. package main
  2. import("database/sql""fmt"_"github.com/go-sql-driver/mysql""log""time")funcmain(){var queryID int64
  3. db, err := sql.Open("mysql","user:password@(127.0.0.1:3306)/dbname?parseTime=true")if err !=nil{
  4. log.Fatal(err)}{// Create a new Table
  5. query :=`
  6. 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{
  7. log.Fatal(err)}}{// Insert a new user
  8. username :="Yikuanzz"
  9. password :="secret"
  10. createAt := time.Now()
  11. res, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES ( ?, ?, ?)`,
  12. username, password, createAt)if err !=nil{
  13. log.Fatal(err)}
  14. id, err := res.LastInsertId()if err !=nil{
  15. log.Fatal(err)}else{
  16. queryID = id
  17. fmt.Printf("id: %d\n", id)}}{// Query a single user var(
  18. id int
  19. username string
  20. password string
  21. createAt time.Time
  22. )
  23. query :="SELECT id, username, password, created_at FROM users WHERE id = ?"if err := db.QueryRow(query, queryID).Scan(&id,&username,&password,&createAt); err !=nil{
  24. log.Fatal(err)}else{
  25. fmt.Println(id, username, password, createAt)}}{// Query all users type User struct{
  26. id int
  27. username string
  28. password string
  29. createAt time.Time
  30. }
  31. rows, err := db.Query("SELECT id, username, password, created_at FROM users")if err !=nil{
  32. log.Fatal(err)}deferfunc(rows *sql.Rows){
  33. err := rows.Close()if err !=nil{
  34. log.Fatal(err)}}(rows)var users []User
  35. for rows.Next(){var user User
  36. if err := rows.Scan(&user.id,&user.username,&user.password,&user.createAt); err !=nil{
  37. log.Fatal(err)}
  38. users =append(users, user)}if err := rows.Err(); err !=nil{
  39. log.Fatal(err)}
  40. fmt.Printf("%#v\n", users)}{// Delete data _, err := db.Exec(`DELETE FROM users WHERE id = ?`,1)if err !=nil{
  41. log.Fatal(err)}}}

Templates

GO 语言的

  1. html/template

包提供了丰富的 HTML 模版,能够让我们方便地在 Web 应用中展示数据。

第一个模板

我们写一个 TODO List 用 HTML 的

  1. <ul>

,渲染模版时,传入的数据可以是任意的数据结构。我们用

  1. {{.}}

来访问模版上的数据,其中的

  1. .

是数据的根元素。

  1. data := TodoPageData{
  2. PageTitle:"My TODO list",
  3. Todos:[]Todo{{Title:"Task 1", Done:false},{Title:"Task 2", Done:true},{Title:"Task 3", Done:true},},}
  1. <h1>{{.PageTitle}}</h1><ul>
  2. {{range .Todos}}
  3. {{if .Done}}
  4. <liclass="done">{{.Title}}</li>
  5. {{else}}
  6. <li>{{.Title}}</li>
  7. {{end}}
  8. {{end}}
  9. </ul>

控制结构

模版语言包含丰富的控制结构来渲染 HTML。
Control StructureDefinition

  1. {{/* a comment */}}

定义注释

  1. {{.}}

渲染根元素

  1. {{.Title}}

在嵌套元素中渲染标题字段

  1. {{if .Done}} {{else}} {{end}}

定义条件语句

  1. {{range .Todos}} {{.}} {{end}}

循环所有的 “Todos” 并用

  1. {{.}}

渲染每个 “Todos”

  1. {{block "content" .}} {{end}}

定义名为 “content” 的块

从文件中解析模版

模版可以从字符串或磁盘上的文件中解析,其中

  1. template.Must

方法是用来确保模板解析过程中没有错误,如果有错误会触发

  1. panic

  1. tmpl, err := template.ParseFiles("layout.html")// or
  2. tmp := template.Must(template.ParseFiles("layout.html"))

请求处理函数中执行模板

  1. Execute

函数接收一个

  1. io.Writer

来去写入模版,通过接口将数据传入模板,此时方法会调用

  1. http.ResponseWriter

进行写入,也自动在 HTTP 响应中写入

  1. Content-Type: text/html;charset=utf-8

  1. func(w http.ResponseWrtier, r *http.Request){
  2. tmpl.Execute(w,"data goes here")}

完整代码如下:

  1. // main.gopackage main
  2. import("html/template""net/http")type Todo struct{
  3. Title string
  4. Done bool}type TodoPageDate struct{
  5. PageTitle string
  6. Todos []Todo
  7. }funcmain(){
  8. tmpl := template.Must(template.ParseFiles("./static/layout.html"))
  9. http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){
  10. data := TodoPageDate{
  11. PageTitle:"My TODO list",
  12. Todos:[]Todo{{Title:"Task 1", Done:false},{Title:"Task 2", Done:true},{Title:"Task 3", Done:true},},}
  13. err := tmpl.Execute(w, data)if err !=nil{return}})
  14. err := http.ListenAndServe(":8080",nil)if err !=nil{return}}
  1. <!-- layout.html --><h1>{{.PageTitle}}</h1><ul>
  2. {{range .Todos}}
  3. {{if .Done}}
  4. <liclass="done">{{.Title}}</li>
  5. {{else}}
  6. <li>{{.Title}}</li>
  7. {{end}}
  8. {{end}}
  9. </ul>

Assets and Form

这里我们会展示静态资源的渲染和表单的信息传递。

静态资源

比如,我们将资源给到

  1. assets

目录下,也可以将其处理与

  1. static

进行匹配。

  1. // static-files.gopackage main
  2. import"net/http"funcmain(){
  3. fs := http.FileServer(http.Dir("assets/"))
  4. http.Handle("/static/", http.StripPrefix("/static/", fs))
  5. http.ListenAndServe(":8080",nil)}
  1. $ tree assets/
  2. assets/
  3. └── css
  4. └── styles.css
  1. $ go run static-files.go
  2. $ curl-s http://localhost:8080/static/css/styles.css
  3. body {
  4. background-color: black;}

表单提交

如果客户端发送的是

  1. request

请求,那么就渲染表单模板,而当发送的是

  1. post

请求,就把数据接收,并返回成功。

  1. // forms.gopackage main
  2. import("html/template""net/http")type ContactDetails struct{
  3. Email string
  4. Subject string
  5. Message string}funcmain(){
  6. tmpl := template.Must(template.ParseFiles("forms.html"))
  7. http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){if r.Method != http.MethodPost {
  8. tmpl.Execute(w,nil)return}
  9. details := ContactDetails{
  10. Email: r.FormValue("email"),
  11. Subject: r.FormValue("subject"),
  12. Message: r.FormValue("message"),}// do something with details_= details
  13. tmpl.Execute(w,struct{ Success bool}{true})})
  14. http.ListenAndServe(":8080",nil)}
  1. <!-- forms.html -->
  2. {{if .Success}}
  3. <h1>Thanks for your message!</h1>
  4. {{else}}
  5. <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>
  6. {{end}}

Middleware

这里我们,创建一个基础的日志中间件,它将

  1. http.HandlerFunc

作为其参数,包装返回一个新的

  1. http.HandlerFunc

给服务器调用。

简单的中间件

通过简单的封装之后,我们就可以记录每次的访问路径情况了,以此为拓展,我们还可以记录很多其他信息。

  1. // basic-middleware.gopackage main
  2. import("fmt""log""net/http")funclogging(f http.HandlerFunc) http.HandlerFunc {returnfunc(w http.ResponseWriter, r *http.Request){
  3. log.Println(r.URL.Path)f(w, r)}}funcfoo(w http.ResponseWriter, r *http.Request){
  4. fmt.Fprintln(w,"foo")}funcbar(w http.ResponseWriter, r *http.Request){
  5. fmt.Fprintln(w,"bar")}funcmain(){
  6. http.HandleFunc("/foo",logging(foo))
  7. http.HandleFunc("/bar",logging(bar))
  8. http.ListenAndServe(":8080",nil)}

中间件进阶

在这里,我们定义了一种新类型的中间件,它可以轻松地将多个中间件链接在一起。

  1. package main
  2. import("fmt""log""net/http""time")type Middleware func(http.HandlerFunc) http.HandlerFunc
  3. funcLogging() Middleware{// 创建一个新的中间件returnfunc(f http.HandlerFunc) http.HandlerFunc{// 定义处理函数returnfunc(w http.ResponseWriter, r *http.Request){
  4. start := time.Now()deferfunc(){
  5. 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{
  6. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)}f(w, r)}}}funcChain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc{for_, m :=range middlewares{
  7. f =m(f)}return f
  8. }funcHello(w http.ResponseWriter, r *http.Request){
  9. fmt.Fprintln(w,"Hello World")}funcmain(){
  10. http.HandleFunc("/",Chain(Hello,Method("GET"),Logging()))
  11. http.ListenAndServe(":8080",nil)}

Sessions

这里我们一起看看怎么通过

  1. gorilla/sessions

来在

  1. session

  1. cookie

中存储数据。

会话定义

  1. Cookie

是存储在用户浏览器中的小数据片段,并根据每个请求发送到我们的服务器。通常用于保存用户的登录状态、偏好设置、跟踪用户行为等等,大小通常为 4KB。

  1. http.SetCookie(w,&http.Cookie{
  2. Name:"username",
  3. Value:"john_doe",
  4. Path:"/",
  5. MaxAge:3600,// 1 hour})
  1. Session

是存储在服务器上的数据,用于跟踪用户的回话状态。每个

  1. Session

都有一个唯一的

  1. Session ID

,客户端通过

  1. Cookie

  1. URL

参数将这个 ID 传递给服务器。

  1. session,_:= store.Get(r,"session-name")
  2. session.Values["username"]="john_doe"
  3. session.Save(r, w)

会话常用函数

  1. session

常用的函数有:

  • sessions.NewCookieStore():创建新的 Cookie 存储对象,通常传入一个随机字符串作为秘钥来加密和验证 Session 数据。
  • store.Get():获取或创建一个 Session
  • session.Save():保存 Session 数据。
  • session.Values:用于存储和获取 Session 中的数据。
  • session.Options.MaxAge:设置 Session 的最大存储时间,负值表示删除 Session

一般来说,当客户端发送请求的的时候,我们用

  1. store.Get

检查请求中的 Cookie 看看是否有名为

  1. session-name

的 Cookie。如果没有我们就在程序内存中创建一个新的 Session 并且它具有唯一的 Session ID。然后,在我们完成一些数据操作之后,我们会用

  1. store.Save

来把 Session 存到内存上或者持久化,并在给客户端的响应中设置一个 Cookie,包含这个 Session ID。这个 Cookie 会在后续请求中被客户端发送回服务器,以便服务器识别会话。

此外,可能要注意的问题是会话劫持(Session Hijacking)的安全风险,就是说攻击者截获合法用户的

  1. session ID

并利用它冒充用户访问服务器资源。为了防止这种情况,我们可采取一下安全措施:

  • 用 HTTPS 加密通信,防止 session ID 在传输过程中被截获,确保所有的请求和响应都通过 HTTPS 进行传输。
  • 在设置 Cookie 时,用 SecureHttpOnly 标志,Secure 可以确保 Cookie 只能通过 HTTPS 传输,而 HttpOnly 可以防止 JavaScript 访问 Cookie,减少 XSS 攻击风险。
  1. package main
  2. 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)
  3. key =[]byte("super-secret-key")
  4. store = session.NewCookieStore(key))funcsecret(w http.ResponseWriter, r *http.Request){
  5. session,_:= store.Get(r,"cookie-name")if auth, ok := session.Values["authenticated"].(bool);!ok ||!auth {
  6. http.Error(w,"Forbidden", http.StatusForbidden)return}
  7. fmt.Fprintln(w,"The cake is a lie!")}funclogin(w http.ResponseWriter, r *http.Request){
  8. session,_:= store.Get(r,"cookie-name")
  9. session.Value["authenticated"]=true
  10. session.Save(r, w)}funclogout(w http.ResponseWriter, r *http.Request){
  11. session,_:= store.Get(r,"cookie-name")
  12. session.Values["authenticated"]=false
  13. session.Save(r, w)}funcmain(){
  14. http.HandleFunc("/secret", sercet)
  15. http.HandleFunc("/login", login)
  16. http.HandleFunc("/logout", logout)
  17. http.ListenAndServe(":8080",nil)}

JSON

这里会展示的是如何用

  1. encoding/json

来编解码 JSON 数据。

编码序列化

  1. json.Marshal

将 Go 结构体编码为 JSON 字符串,我们会用字符标识在结构体字段中标注要转换成的 JSON 名。

  1. package main
  2. import("encoding/json""fmt")type User struct{
  3. Name string`json:"name"`
  4. Age int`json:"age"`}funcmain(){
  5. user := User{Name:"Alice", Age:30}
  6. jsonData, err := json.Marshal(user)if err !=nil{
  7. fmt.Println(err)return}
  8. fmt.Println(string(jsonData))}

解码反序列化

  1. json.Unmarshal

将 JSON 字符串解码为 GO 结构体。

  1. package main
  2. import("encoding/json""fmt")type User struct{
  3. Name string`json:"name"`
  4. Age int`json:"age"`}funcmain(){
  5. jsonString :=`{"name":"Alice","age":30}`var user User
  6. err := json.Unmarshal([]byte(jsonString),&user)if err !=nil{
  7. fmt.Println(err)return}
  8. fmt.Println(user)}

编码和解码使用

  1. // json.gopackage main
  2. import("encoding/json""fmt""net/http")type User struct{
  3. Firstname string`json:"firstname"`
  4. Lastname string`json:"lastname"`
  5. Age int`json:"age"`}funcmain(){
  6. http.HandleFunc("/decode",func(w http.ResponseWriter, r *http.Request){var user User
  7. json.NewDecoder(r.Body).Decode(&user)
  8. fmt.Fprintf(w,"%s %s is %d years old!", user.Firstname, user.Lastname, user.Age)})
  9. http.HandleFunc("/encode",func(w http.ResponseWriter, r *http.Request){
  10. peter := User{
  11. Firstname:"John",
  12. Lastname:"Doe",
  13. Age:25,}
  14. json.NewEncoder(w).Encode(peter)})
  15. http.ListenAndServe(":8080",nil)}

Image

Websockets

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

安装网络套接字包

  1. go get -u github.com/gorilla/websocket

网络套接字

  1. WebSocket

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

  1. websocket.Upgrader

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

  1. HandshakeTimeout:握手完成的超时时间。
  2. ReadBufferSize:读取缓冲区的大小(以字节为单位)。
  3. WriteBufferSize:写入缓冲区的大小(以字节为单位)。
  4. WriteBufferPool:用于写入缓冲区的池。
  5. Subprotocols:客户端支持的子协议列表。
  6. Error:在握手失败时调用的函数。
  7. CheckOrigin:用于检查请求来源的函数,防止跨站点请求伪造(CSRF)攻击。
  8. EnableCompression:是否启用 WebSocket 压缩扩展。
  1. type Upgrader struct{
  2. HandshakeTimeout time.Duration
  3. ReadBufferSize int
  4. WriteBufferSize int
  5. WriteBufferPool BufferPool
  6. Subprotocols []string
  7. Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
  8. CheckOrigin func(r *http.Request)bool
  9. EnableCompression bool}
  1. websocket.Upgrader.Upgrage

是 WebSocket 服务器实现的核心部分,它传入 HTTP 的请求和响应,可选

  1. http.Header

设置额外的头部信息,返回一个

  1. *Conn

WebSocket 连接对象。

  1. *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:设置写入消息的超时时间。

回显服务器

  1. // websockets.gopackage main
  2. import("fmt""net/http""github.com/gorilla/websocket")var upgrader = websocket.Upgrader{
  3. ReadBufferSize:1024,
  4. WriteBufferSize:1024,}funcmain(){
  5. http.HandleFunc("/echo",func(w http.ResponseWriter, r *http.Request){
  6. conn,_:= upgrader.Upgrade(w, r,nil)// error ignored for sake of simplicityfor{// Read message from browser
  7. msgType, msg, err := conn.ReadMessage()if err !=nil{return}// Print the message to the console
  8. fmt.Printf("%s sent: %s\n", conn.RemoteAddr(),string(msg))// Write message back to browserif err = conn.WriteMessage(msgType, msg); err !=nil{return}}})
  9. http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){
  10. http.ServeFile(w, r,"websockets.html")})
  11. http.ListenAndServe(":8080",nil)}
  1. <!-- 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");
  2. socket.onopen=function(){
  3. output.innerHTML +="Status: Connected\n";};
  4. socket.onmessage=function(e){
  5. output.innerHTML +="Server: "+ e.data +"\n";};functionsend(){
  6. socket.send(input.value);
  7. input.value ="";}</script>

Password Hashing

在这个示例中,我们会用

  1. bcrypt

来对密码进行哈希处理。

  1. go get -u golang.org/x/crypto/bcrypt

密码哈希算法

在 Go 中,

  1. bcrypt

是一种常用的密码哈希算法,用于安全地存储和验证密码。

  • 安全性高:使用了随机盐值和多次哈希,增加了破解难度。
  • 适应性强:通过增加迭代次数提高安全性,适应不断提升的计算能力。
  • 不可逆:单向哈希,无法通过哈希值反推出原始密码。
  1. bcrypt.GenerateFromPassword

方法用于生成哈希密码。

  1. bcrypt.CompareHashAndPassword

方法用于验证用户输入的密码是否正确。

  1. package utils
  2. import("golang.org/x/crypto/bcrypt")// GenerateFromPassword 生成哈希密码funcGenerateFromPassword(password string)(string,error){
  3. hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)if err !=nil{return"", err
  4. }returnstring(hashedPassword),nil}// CompareHashAndPassword 验证密码funcCompareHashAndPassword(hashPassword, password string)bool{
  5. err := bcrypt.CompareHashAndPassword([]byte(hashPassword),[]byte(password))return err ==nil}

加密和解密

  1. // passwords.gopackage main
  2. import("fmt""golang.org/x/crypto/bcrypt")funcHashPassword(password string)(string,error){
  3. bytes, err := bcrypt.GenerateFromPassword([]byte(password),14)returnstring(bytes), err
  4. }funcCheckPasswordHash(password, hash string)bool{
  5. err := bcrypt.CompareHashAndPassword([]byte(hash),[]byte(password))return err ==nil}funcmain(){
  6. password :="secret"
  7. hash,_:=HashPassword(password)// ignore error for the sake of simplicity
  8. fmt.Println("Password:", password)
  9. fmt.Println("Hash: ", hash)
  10. match :=CheckPasswordHash(password, hash)
  11. fmt.Println("Match: ", match)}
标签: 学习 golang 后端

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

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

还没有评论