Skip to content

02 goweb学习笔记

Jinxin Chen edited this page Dec 11, 2019 · 1 revision

简介

参考网址:

一个简单的http web

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Hello world!", r.URL.Path)
}

func main() {
    // 路由
    // HandleFunc会帮忙建立handler
	http.HandleFunc("/", handler)

    // 监听端口
    // 如果地址为空,表示默认80端口
    // nil 代表使用默认的多路复用器
	http.ListenAndServe(":8080", nil) 
}

方法2,实现ServeHTTP接口

type myHandler struct {
}

func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Hello world!", r.URL.Path)
}

func main() {
	myHandler := myHandler{}
	http.Handle("/", &myHandler)

    http.ListenAndServe(":8080", nil)
    
    // 方法3:使用http.Server
    server := http.Server{
		Addr:        ":8080",
		Handler:     &myHandler,
		ReadTimeout: 3 * time.Second,
	}
    server.ListenAndServe()
    
    // 方法4:自定义mux
    mux := http.NewServeMux()
	mux.HandleFunc("/", handler)

	http.ListenAndServe(":8080", mux)
}

方法3:使用http.Server,上面

方法4:自定义mux,上面

http版本

http1.1允许浏览器拿到当前页面请求的所有资源以后才断开连接,提高了效率,1.0每个资源请求都需要建立一个单独的连接

报文格式

报文头部

空行(回车+换行,16进制)

报文主体

请求报文

响应报文

  • 1xx:请求已被成功接收,继续处理
  • 2xx:请求已经成功接收
  • 3xx:重定向
  • 4xx:客户端错误
  • 5xx:服务器端错误

数据库

包:github.com/go-sql-driver/mysql

type DB

  • 自动维护连接
  • 自动维护连接池
  • 自动将事务绑定到单个连接

示例代码

// utils.go
package utils

import (
	"database/sql"

	_ "github.com/go-sql-driver/mysql"
)

var (
	DB  *sql.DB
	err error
)

func init() {
	DB, err = sql.Open("mysql", "root:example@tcp(xxxxx:3306)/Test_Go")
	if err != nil {
		panic(err.Error())
	}
}
// utils.go
package utils

import (
	"database/sql"

	_ "github.com/go-sql-driver/mysql"
)

var (
	DB  *sql.DB
	err error
)

func init() {
	DB, err = sql.Open("mysql", "root:example@tcp(xxxxx:3306)/Test_Go")
	if err != nil {
		panic(err.Error())
	}
}
// main.go
// 方法1:使用Prepare
func insertUser1() {
	str := "insert into Users  (Name, Age) values (?,?)"

	cmd, err := utils.DB.Prepare(str)
	if err != nil {
		fmt.Printf(err.Error())
	}

	_, err = cmd.Exec("Jinxin", 33)
	if err != nil {
		fmt.Printf(err.Error())
	}

}
// 方法2:直接调用db的Exec
func insertUser2() {
	str := "insert into Users (Name, Age)  values (?,?)"

	_, err := utils.DB.Exec(str, "Huabing", 29)

	if err != nil {
		fmt.Printf(err.Error())
	}

}

查询db

注意,row.Scan中传递的参数个数和顺序必须和select语句里面返回的字段个数和顺序相同,否则会报错

func (u *User) QueryUserByID(id int) (*User, error) {
	str := "select Name, Age from Users where id=?"

	row := utils.DB.QueryRow(str, id)

	user := &User{}
	var (
		Name string
		Age  int
	)
	err := row.Scan(&Name, &Age)
	if err != nil {
		fmt.Printf(err.Error())
		return nil, err
	}

	user.ID = id
	user.Name = Name
	user.Age = Age

	return user, nil
}

测试

  • 文件名必须以 _test.go 结尾,前缀一般写为需要测试的文件名
  • 测死方法必须满足格式: TestXxx,注意大小写

可以用 t.Run 来执行子测试,用TestMain方法来执行一些初始化操作

func TestMain(t *testing.M) {
	fmt.Printf("Initial test")
	t.Run()
}

func TestUser(t *testing.T) {
	t.Run("Test add user", testAddUser)
	t.Run("Test delete user", testDeleteUser)
}

func testAddUser(t *testing.T) {
	user := &User{}

	user.InsertUser1()
	user.InsertUser2()
}

func testDeleteUser(t *testing.T) {
	...
}

执行测试:

go test [-v]

处理请求

net/http包

type Request

PostForm字段,获取Post的参数, url.Values类型,必须先调用Request.ParseForm方法

如果enctype=multipart/form-data(文件上传),则需要通过MultipartForm字段

FormValue和PostFormValue来直接获取参数,必要时会隐式调用ParseForm/ParseMultipartForm方法

响应客户端

  • w.Write([]byte("hello world"))
  • w.Write([]byte(<header>...</header>))
  • 返回json,w.Header().Set("Content-Type", "application/json")

重定向:

//必须先设置header再设置302
w.Header().Set("Location", "http://www.baidu.com")
w.WriteHeader(302)

模板引擎

text/template, html/template

package main

import "http/template"
import "net/http"

func handler(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("index.html")
	// template.ParseGlob("*.html") // 解析多个文件
	t.Execute(w, "")
	// t.ExecuteTemplate(w, "index2.html") // 执行index2.html模板
}

func main() {
	// 处理静态资源
	http.HandleFunc("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("views/static/"))))
	// 直接去html页面
	http.HandleFunc("/pages/", http.StripPrefix("/pages/", http.FileServer(http.Dir("views/pages/"))))
	http.HandleFunc("/", &handler)
    http.ListenAndServe(":8080", nil)
}

可以用template.Must方法来处理模板的错误

action

// index.html
	}
		传过来的是true
	{{else}}
		传过来的不是true
	{{end}}
{{range .}}
	// 这里的.和上面的.不同
	遍历的元素是 {{.}} </br>
		值有 {{.ID}} </br>
		值有 {{.Name}} </br>
{{else}}
	没有元素
{{end}}

// 另一种遍历方式,设置变量
{{range $k, $v := .}}
	键是{{$k}} 值是{{$v}}
{{end}}

设置动作,在with作用域,.变成with设置的值

{{with "修改一下"}}
	修改后的值 {{.}}
{{end}}


{{with ""}}
	修改后的值 {{.}}
{{else}}
	原来的值 {{.}}
{{end}}

包含动作

一个模板里面包含另外一个模板

{{template: "share.html"}}
{{template: "details.html", arg}}

定义动作

定义网页布局,抽象出相同内容

{{define "_layout"}}
	<html>
		<head></head>
		<body>
		{{template "content"}}
		{{end}}
		</body>
	</html>
{{end}}

{{define "content"}}
{{end}}

块动作

定义默认模板

{{define "_layout"}}
	<html>
		<head></head>
		<body>
		{{block "content"}}
			这是默认的模板可以被覆盖
		{{end}}
		</body>
	</html>
{{end}}

Cookie

设置Cookie

cookie1 = http.Cookie{
	Name: "User",
	Value: "Admin",
	MaxAge: 60, // 60秒过期
}
// 没有设置过期,默认会话级别
cookie2 = http.Cookie{
	Name: "User",
	Value: "Operator",
}

// 方法1
w.Header().Set("Set-Cookie", cookie1.String())
// 方法2
http.SetCookie(w, &cookie2)

获取Cookie

// 方法1
cookies := r.Header["cookie"]
// 方法2
cookie, _ := r.Cookie("User")

删除Cookie

cookie.MaxAge = -1 // 任何小于0的值
http.SetCookie(w, &cookie)
Clone this wiki locally