GoWeb
搭建服务器
package main
import (
"fmt"
"net/http"
)
//创建处理器函数
func handler(w http.ResponseWriter,r*http.Request) { //这里面的参数是不能变的
fmt.Fprintln(w,"Hello world","abc",r.URL.Path,"def") //fprintln函数可以随意拼接自己想要的字符
}
func main() {
http.HandleFunc("/abc",handler) //定义一个函数类型,就可以把函数作为参数传入,handlerfunc函数当访问根目录时就会自动执行handler函数
//handlerfunc函数会将指定的url拼接到后面,当拼接了指定的url时,会自动执行handler函数
//创建路由
http.ListenAndServe(":8088",nil) //ListenAndServer函数会映射指定的端口,第一个参数就是映射到哪个端口,第二个参数是
//ListenAndServer函数需要传入两个参数都需要监听的端口和handler,第一个是监听的端口,第二个是处理请求的接口,
}
handler函数:是一个接口,接口名随便起,参数是固定的,必须是w http.ResponseWriter 和 r*http.Request,自定义的,所以不需要写http.包的名字
HandlerFunc,是调用http包的函数,所以必须调用http.来说明是包内的函数,第一个参数是后面拼接的后缀(映射的地址)(url为string类型),第二个参数是一个处理器,说明映射的地址交给哪个处理器去完成
ListenAndServe函数第一个参数是监听TCP地址addr(端口),并且会使用handler参数调用Serve函数处理接收到的链接,handler参数一般设置为nil,此时会使用DefaultServeMux
-
package main import ( "fmt" "net/http" ) type MyHandler struct {} func (m *MyHandler) ServeHTTP(w http.ResponseWriter , r *http.Request) { fmt.Fprintln(w,"通过自己创建的处理器处理请求!") } func main() { myHandler := MyHandler{} http.Handle("/myHandler",&myHandler) //Handle函数的第二个参数是myHandler的地址,实例对象的地址 http.ListenAndServe(":8088",nil) } //这个是比较麻烦的自己创建处理器的方法,不推荐使用
如果调用的是HandleFunc函数会自动转换成处理器,不需要一个struct来实现接口
如果调用的是Handle函数,则必须要实现ServeHTTP方法(一个接口)
HTTP报文格式
请求报文
第一行是请求首行,包含请求方式,请求地址 和 请求协议 第二行开始是请求头信息,就是请求的属性信息 后面紧跟一个空行 空行后面是请求体
get请求没有请求体,post请求才有请求体
可以通过浏览器的network来查看报文信息,其中view-source选项可以查看具有报文结构的报文信息
-
package main import ( "fmt" "net/http" ) //创建处理器函数 func handler(w http.ResponseWriter , r *http.Request){ fmt.Fprintln(w , "测试http协议") } func main() { //调用处理器处理请求 http.HandleFunc("/http",handler) //路由 http.ListenAndServe(":8080",nil) }
<html> <head> <meta charset="UTF-8"/> </head> <body> <form action="http://localhost:8080/http" method="POST" > <!--form标签用于为用户输入创建HTML表单,可以包含input元素,比如文本字段等,表单用于向服务器传输数据--> 用户名:<input type="text" name="username" /><br/> 密码: <input type="text" name="password" /><br/> <input type="submit" /> </body> </html>
通过go语言链接数据库
- 查看如何拼接sql语句官方文档看database/sql库中的
- Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国 (studygolang.com)
- 使用database/sql包来操作数据库
- 因为go语言没有提供任何官方的数据库驱动,所以我们需要导入第三方的数据库驱动,“github.com/go-sql-driver/mysql”
- 放在 gomodcache 指向的目录下,有一个github.com目录
链接数据库
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:20030729a@tcp(localhost:3306)/ctfshow")
if err != nil {
panic(err.Error())
}
DB.SetConnMaxLifetime(10)
DB.SetMaxIdleConns(5)
if err := DB.Ping() ; err != nil {
fmt.Println("open database fail")
return
}
}
adduser添加用户的方法
func (user *User) AddUser() error {
//写sql语句
sqlStr := "insert into users(username,password,email) values(?,?,?)"
//预编译
inStmt , err := utils.Db.Prepare()
}
Exec执行一次命令,(包括查询,删除,更新,插入等),不返回任何执行结果,参数args表示query中的占位参数
func (db *DB) Exec(query string,args ...interface()) {Result,error}
Query执行一次查询,返回多行结果,(即Rows),一般用于执行select命令,参数args表示query中的占位参数
func (db *DB) Query(query string,args ...interface()) {*Rows,error}
QueryRow执行一次查询,并期望返回最多一行结果(即Row),QueryRow总是返回非nil的值,查到返回值的Scan方法被调用时,才会返回被延迟的错误
func (db *DB) QueryRow(query string,args ...interface()) *Row
总:AddUser 添加User的方法一,需要预编译
// AddUser 添加用户的方法一
func (user *User) AddUser() error {
//写sql语句
sqlStr := "insert into users(username,password,email) values(?,?,?)"
//预编译
inStmt , err := utils.Db.Prepare(sqlStr) //预编译得到的是inStmt,通过操作inStmt得到不同的结果
if err != nil {
fmt.Println("预编译出现异常",err)
return err
}
//3.执行
_,err2 := inStmt.Exec("admin","123456","admin@atguigu.com")
if err2 != nil {
fmt.Println("执行出现异常",err2)
return err2
}
return nil
}
单元测试
单元测试就是为了验证单元的正确性而设置的自动化测试,一个单元就是程序中的一个模块化部分
一般来说,一个单元通常会和程序中的一个函数或者一个方法对应
go的单元测试需要用到testing包以及go test命令,而且对测试文件也有以下要求
1. 被测试的源文件和测试文件必须位于同一个包下 2. 测试文件必须以 _test.go结尾 3. 虽然go对测试文件_test.go的前缀没有强制要求,不过一般都设置魏被测试文件的文件名,对user.go测试,名字一般设置为user_test.go 4. 测试文件中的测试函数为 TestXXX(*test.T) 其中,XXX的首字母必须是大写的英文字母 函数参数必须是test.T的指针类型 5. Test测试函数的参数必须是 t *test.T
package model import ( "fmt" "testing" ) func TestAddUser(t *testing.T) { fmt.Println("测试添加用户:") user := &User{} //调用添加用户的方法 user.AddUser() user.AddUser2() }
如果函数名不是以Test开头,那么函数默认不执行,我们可以将它设置成为一个子测试程序
在主Test函数中调用子测试程序,可以将声明的test.T指针对象指向子测试函数
t.Run("测试添加用户",testAddUser) //第一个参数是自己写的string类型,在调用时自动输出,第二个参数是要调用哪个子测试程序
预处理SQL
预编译语句是将需要反复调用的某一条sql语句的值用占位符代替,可以视为将sql语句模板化或者参数化,这类语句即为prepared statements,预编译语句
优势在于:一次编译,多次运行,省去了解析优化的过程,此外预编译语句能够防止sql注入
# 定义预处理语句 prepare state_string from prepareable_state_ment; # 执行预处理语句 execute state_string [using @var_name , @var_name] # 删除(释放)定义 {deallocate|drop} prepare state_ment;
处理一条语句
QueryRow函数返回的是一行结果的指针,row的内容隐藏或非导出字段,代表单行查询结果
实际查询代码
func (user *User) GetUserById() (*User,error) { //写sql语句 sqlStr := "select id,username,password,email from users where id = ?" //执行 row := utils.Db.QueryRow(sqlStr,user.ID) //声明 var id int var username string var password string var email string err := row.Scan(&id,&username,&password,&email) if err != nil { return nil,err } u := &User { ID : id, Username : username, Password: password, Email: email, } return u , nil }
单元测试代码
//测试获取一个User func testGetUserById(t *testing.T){ fmt.Println("测试一条查询数据") user := User { ID : 3, } //调用获取User的方法 u,err2 := user.GetUserById() if err2 != nil{ fmt.Println(err2) } else { fmt.Println("得到的User信息是:",u) } }
处理多条语句
实际查询代码
//GetUsers 获取数据库中的所有记录 func (user *User) GetUsers() ([]*User,error) { //在一个切片上存储指针或带指针的值,典型的例子是[]*string //写sql语句 sqlStr := "select id,username,password,email from users" //执行 rows ,_ := utils.Db.Query(sqlStr) // if err3 != nil { // return err3,nil // } //创建User切片 var users []*User for rows.Next(){ var id int var username string var password string var email string err := rows.Scan(&id,&username,&password,&email) if err != nil { return nil,err } u := &User { //要被添加的值是地址 ID : id, Username : username, Password: password, Email: email, } users = append(users,u) //append函数的第一个值是被添加到的slice的地址,第二个值是添加的值,因为这个地方是一个存储地址的数组,所以添加的值也是地址 } return users,nil }
单元测试代码
//测试获取所有的User func testGetUsers(t *testing.T){ fmt.Println("测试查询所有记录:") user := &User{} //调用获取所有User的方法 rows,_ := user.GetUsers() //遍历输出切片中的内容 for k , v := range rows{ fmt.Printf("第%v个用户是%v:",k+1,v) fmt.Println() } }
处理请求
切片上存储指针或带指针的值
- 在一个切片上存储指针或带指针的值,典型的例子是[]*string
获取请求行中的信息(url和传递的参数)
这个地方的Request类型和handler处理器中的参数是一个类型
其中Request数据类型中的URL属性,也是一个结构体type
-
package main import ( "fmt" "net/http" ) //创建处理器函数 func handler(w http.ResponseWriter,r *http.Request){ fmt.Fprintln(w,"你发送的请求地址是:",r.URL.Path) fmt.Fprintln(w,"你发送的请求地址后的查询字符串是:",r.URL.RawQuery) } func main(){ http.HandleFunc("/hello",handler) http.ListenAndServe(":8080",nil) }
Request类型的变量r,r.URL.Path获得到的是请求地址,r.URL.RawQuery获得到的是传递的参数
获取请求行和请求体
Request类型中的Header字段即为请求头
Request变量r中的Header字段,代表了请求头中的所有信息
如果想要获取Header字段中的某个信息,可以通过中括号取下标的方式来获取
r.Header["Accept-Encodeing"] //这样可以获取请求头中的报文编码格式
获取Header字段中某个信息的属性值用Get方法
r.Header.Get("Accept-Encoding")
Get返回键对应的第一个值,如果键不存在会返回””,如果获取该键对应的值切片,请直接用规范格式的键访问map
package main import ( "fmt" "net/http" ) //创建处理器函数 func handler(w http.ResponseWriter,r *http.Request){ fmt.Fprintln(w,"你发送的请求地址是:",r.URL.Path) fmt.Fprintln(w,"你发送的请求地址后的查询字符串是:",r.URL.RawQuery) fmt.Fprintln(w,"请求头中所有的信息有:",r.Header) fmt.Fprintln(w,"请求头中Accept-Encoding的信息是:",r.Header["Accept-Encoding"]) } func main(){ http.HandleFunc("/hello",handler) http.ListenAndServe(":8080",nil) }
其中不同的是:r.Header[属性]获取到的是map
r.Header.Get()获取到的是值,没有大括号,Get函数中的参数是string类型的属性,和大括号取值中大括号中的内容相同
refer属性
- Referer属性可以起到防盗链和广告计费的作用,防盗链:如果不是特定的页面跳转过来的权限不是完全开放;广告计费:在页面中加广告,当从特定的Referer跳转过来的时候,访问流量达到多少会计费
获取请求体中的信息
- 请求和响应的主体都是由Request结构中的Body字段表示,这个字段是io.ReadClose接口
获取请求参数
通过net/http库中的Request结构的字段以及方法获取请求URL后面的query参数和POST或PUT的表单数据
如果想要获取postform字段中的数据,需要特定enctype的属性值为application/x-www-form-urlencoded,指定编码方式,如果编码方式为multipart/form-data的属性值,则使用postform字段无法获取表单中的数据
form表单的enctype属性的默认值时application/x-www-form-urlencode编码,实现文件上传时需要将该属性的值设置为multipart/form-data的编码格式
Request的type中,ContentLength属性记录相关内容的长度,在客户端,如果Body非nil而该字段为0,则表示不知道Body的长度
//获取请求体中内容的长度 len := r.ContentLength //ContentLength属性在Request对象中
将Body中的内容读到body中
//将Body中的内容读到body中 r.Body.Read(body)
通过直接调用FormValue方法和PostFormValue方法直接获取请求参数的值
- FormValue函数传递的参数是键key,string类型的参数
- From是解析好的表单数据,包括URL的query参数和POST或PUT传递的表单数据
处理客户端响应
handler处理器的第一个参数,w http.ResponseWriter类型的对象w
例子
func handler(w http.ResponseWriter , r *http.Request){ w.write([]byte("你的请求我已经收到")) }
给客户端响应改变为json格式
记住,一定要导入encoding/json的包
func testJsonRes(w http.ResponseWriter,r *http.Request){ //设置响应内容的类型 w.Header().Set("Content-Type","application/json") //创建User user := model.User{ ID:1, Username:"admin", Password:"123456", Email:"admin@atguigu.com", } //将User转换为Json格式 json,_ := json.Marshal(user) //将json格式的数据相应给客户端 w.Write(json) } func main(){ http.HandleFunc("/hello",handler) http.HandleFunc("/testJson",testJsonRes) http.ListenAndServe(":8080",nil) }
让客户端重定向
处理器端代码
func handler(w http.ResponseWriter,r *http.Request){ //以下操作必须得在WriteHeader之前运行 w.Header().Set("Location","https://www.baidu.com") //第一个参数是表明Location地址,第二个参数指定重定向位置 w.WriteHeader(302) //设置响应的状态码 }
可以看出,*http.Request 参数是用来处理用户的请求
http.ReponseWriter用来给用户响应
模板引擎,处理响应数据
-
func testTemplate(w http.ResponseWriter,r *http.Request) { //解析模板 t,_ := template.ParseFiles("index.html") t.Execute(w,"") }