搭建服务器

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,第一个是监听的端口,第二个是处理请求的接口,
}
  1. handler函数:是一个接口,接口名随便起,参数是固定的,必须是w http.ResponseWriter 和 r*http.Request,自定义的,所以不需要写http.包的名字

  2. HandlerFunc,是调用http包的函数,所以必须调用http.来说明是包内的函数,第一个参数是后面拼接的后缀(映射的地址)(url为string类型),第二个参数是一个处理器,说明映射的地址交给哪个处理器去完成

  3. ListenAndServe函数第一个参数是监听TCP地址addr(端口),并且会使用handler参数调用Serve函数处理接收到的链接,handler参数一般设置为nil,此时会使用DefaultServeMux

  4. 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)
    }
    //这个是比较麻烦的自己创建处理器的方法,不推荐使用
  5. 如果调用的是HandleFunc函数会自动转换成处理器,不需要一个struct来实现接口

  6. 如果调用的是Handle函数,则必须要实现ServeHTTP方法(一个接口)

HTTP报文格式

  1. 请求报文

    第一行是请求首行,包含请求方式,请求地址 和 请求协议
    第二行开始是请求头信息,就是请求的属性信息
    后面紧跟一个空行
    空行后面是请求体
  2. get请求没有请求体,post请求才有请求体

  3. 可以通过浏览器的network来查看报文信息,其中view-source选项可以查看具有报文结构的报文信息

  4. 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语言链接数据库

  1. 查看如何拼接sql语句官方文档看database/sql库中的
  2. Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国 (studygolang.com)
  3. 使用database/sql包来操作数据库
  4. 因为go语言没有提供任何官方的数据库驱动,所以我们需要导入第三方的数据库驱动,“github.com/go-sql-driver/mysql”
  5. 放在 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
}

单元测试

  1. 单元测试就是为了验证单元的正确性而设置的自动化测试,一个单元就是程序中的一个模块化部分

  2. 一般来说,一个单元通常会和程序中的一个函数或者一个方法对应

  3. 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()
    }
  4. 如果函数名不是以Test开头,那么函数默认不执行,我们可以将它设置成为一个子测试程序

  5. 在主Test函数中调用子测试程序,可以将声明的test.T指针对象指向子测试函数

    t.Run("测试添加用户",testAddUser) //第一个参数是自己写的string类型,在调用时自动输出,第二个参数是要调用哪个子测试程序

预处理SQL

  1. 预编译语句是将需要反复调用的某一条sql语句的值用占位符代替,可以视为将sql语句模板化或者参数化,这类语句即为prepared statements,预编译语句

  2. 优势在于:一次编译,多次运行,省去了解析优化的过程,此外预编译语句能够防止sql注入

    # 定义预处理语句
    prepare state_string from prepareable_state_ment;
    # 执行预处理语句
    execute state_string [using @var_name , @var_name]
    # 删除(释放)定义
    {deallocate|drop} prepare state_ment;

处理一条语句

  1. QueryRow函数返回的是一行结果的指针,row的内容隐藏或非导出字段,代表单行查询结果

  2. 实际查询代码

    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
    }
  3. 单元测试代码

    //测试获取一个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)
    	}
    }

处理多条语句

  1. 实际查询代码

    //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
    }
  2. 单元测试代码

    //测试获取所有的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()
    	}
    }

处理请求

切片上存储指针或带指针的值

  1. 在一个切片上存储指针或带指针的值,典型的例子是[]*string

获取请求行中的信息(url和传递的参数)

  1. 这个地方的Request类型和handler处理器中的参数是一个类型

  2. 其中Request数据类型中的URL属性,也是一个结构体type

  3. 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)
    }
  4. Request类型的变量r,r.URL.Path获得到的是请求地址,r.URL.RawQuery获得到的是传递的参数

获取请求行和请求体

  1. Request类型中的Header字段即为请求头

  2. Request变量r中的Header字段,代表了请求头中的所有信息

  3. 如果想要获取Header字段中的某个信息,可以通过中括号取下标的方式来获取

    r.Header["Accept-Encodeing"] //这样可以获取请求头中的报文编码格式
  4. 获取Header字段中某个信息的属性值用Get方法

    r.Header.Get("Accept-Encoding")
  5. 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)
    }
  6. 其中不同的是:r.Header[属性]获取到的是map

  7. r.Header.Get()获取到的是值,没有大括号,Get函数中的参数是string类型的属性,和大括号取值中大括号中的内容相同

refer属性

  1. Referer属性可以起到防盗链和广告计费的作用,防盗链:如果不是特定的页面跳转过来的权限不是完全开放;广告计费:在页面中加广告,当从特定的Referer跳转过来的时候,访问流量达到多少会计费

获取请求体中的信息

  1. 请求和响应的主体都是由Request结构中的Body字段表示,这个字段是io.ReadClose接口

获取请求参数

  1. 通过net/http库中的Request结构的字段以及方法获取请求URL后面的query参数和POST或PUT的表单数据

  2. 如果想要获取postform字段中的数据,需要特定enctype的属性值为application/x-www-form-urlencoded,指定编码方式,如果编码方式为multipart/form-data的属性值,则使用postform字段无法获取表单中的数据

  3. form表单的enctype属性的默认值时application/x-www-form-urlencode编码,实现文件上传时需要将该属性的值设置为multipart/form-data的编码格式

  4. Request的type中,ContentLength属性记录相关内容的长度,在客户端,如果Body非nil而该字段为0,则表示不知道Body的长度

    //获取请求体中内容的长度
    	len := r.ContentLength //ContentLength属性在Request对象中
  5. 将Body中的内容读到body中

    //将Body中的内容读到body中
    	r.Body.Read(body)

通过直接调用FormValue方法和PostFormValue方法直接获取请求参数的值

  1. FormValue函数传递的参数是键key,string类型的参数
  2. From是解析好的表单数据,包括URL的query参数和POST或PUT传递的表单数据

处理客户端响应

  1. handler处理器的第一个参数,w http.ResponseWriter类型的对象w

  2. 例子

    func handler(w http.ResponseWriter , r *http.Request){
    	w.write([]byte("你的请求我已经收到"))
    }

给客户端响应改变为json格式

  1. 记住,一定要导入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)
    }

让客户端重定向

  1. 处理器端代码

    func handler(w http.ResponseWriter,r *http.Request){
    	//以下操作必须得在WriteHeader之前运行
    	w.Header().Set("Location","https://www.baidu.com") //第一个参数是表明Location地址,第二个参数指定重定向位置
    	w.WriteHeader(302) //设置响应的状态码
    }
  2. 可以看出,*http.Request 参数是用来处理用户的请求

  3. http.ReponseWriter用来给用户响应

模板引擎,处理响应数据

  1. func testTemplate(w http.ResponseWriter,r *http.Request) {
    	//解析模板
    	t,_ := template.ParseFiles("index.html")
    	t.Execute(w,"")
    }

处理静态文件