目录
前言
Go原生支持http,直接使用import("net/http")即可,http服务性能和nginx非常接近,都具备高并发支持的能力,代码实现起来比较简单。
1、服务器配置
package main
import (
"fmt"
"net/http"
)
//HTTP服务端配置
//业务请求相应处理
func hello(res http.ResponseWriter, req *http.Request) {
fmt.Println("hello")
fmt.Fprintln(res, "<h1>welcome</h1>")
}
func main() {
//路由到指定站点位置跳转相关函数处理
http.HandleFunc("/", hello)
err := http.ListenAndServe("127.0.0.1:8088", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
启动服务端
这时候用浏览器验证
2、客户端配置
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
//http客户端配置
func main() {
/*
http.Get()
传入值为 url,是string类型
返回值有两个,
一个是请求,结构体类型,该结构体的Body字段是一个接口
第二个是err
*/
//在Get函数中,放入完整合格域名(FQDN)
res, err := http.Get("https://www.baidu.com/")
if err != nil {
fmt.Println("获取信息失败,err:", err)
return
}
/*
接收 io.reader,这是一个接口
返回字节切片和 err
*/
//response结构体中读取信息
buf, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println("读取信息失败,err:", err)
return
}
fmt.Println(string(buf))
}
运行客户端
3、请求方法
Get请求:提交的数据都是追加在URL之后,限制点是URL的长度不能超过8K,不适用于提交量大的数据,适用于请求数据,get请求从服务器中读取资源,以明文形式发送请求。
Post请求:数据存放在包中,数据量不受限制,可以提交上传量大的数据信息,post用于更新服务器资源。
Put请求:用于在服务器上创建资源。
Delete请求:用户在服务器上删除资源。
Head请求:向服务器请求头部信息,用于监控服务状态。
示例:
package main
import (
"fmt"
"net/http"
)
//请求头部信息
var url []string = []string{
"https://www.baidu.com/",
"https://www.taobao.com/",
"https://www.google.com/",
}
func main() {
for _, v := range url {
req, err := http.Head(v)
if err != nil {
fmt.Println("获取请求失败,err", err)
return
}
fmt.Printf("来自%s的网站,状态是%s\n", v, req.Status)
}
}
运行结果为
使用自建客户端进行超时时间优化
package main
import (
"fmt"
"net"
"net/http"
"time"
)
//请求头部信息
var url []string = []string{
"https://www.baidu.com/",
"https://www.taobao.com/",
"https://www.google.com/",
}
func main() {
for _, v := range url {
//自建client做测试,优化超时时间
client := http.Client{
Transport: &http.Transport{
//连接服务端
Dial: func(network, addr string) (net.Conn, error) {
//设置超时时间2秒
timeout := time.Second * 2
return net.DialTimeout(network, addr, timeout)
},
},
}
//使用自建客户端测试
req, err := client.Head(v)
if err != nil {
fmt.Println("获取请求失败,err", err)
return
}
fmt.Printf("来自%s的网站,状态是%s\n", v, req.Status)
}
}
再次运行结果为
常见的状态码
http.StatusContinue =100客户端上传数据需要请求服务端同意才可以上传显示的状态码。
http.StatusOK= 200成功连接访问。
http.StatusFound = 302页面跳转的状态码。
http.StatusBadRequest = 400非法请求,服务端无法解析。
http.StatusUnauthorized = 401 权限受限,未通过。
http.StatusForbidden = 403禁止访问。
http.StatusNotFound = 404请求页面不存在。
http.StatusInternalServerError = 500服务器内部错误。
示例:表单业务。
package main
import (
"fmt"
"io"
"net/http"
"os"
)
//定义页面表单html
const form = `<html><head></head><body>
<form action="#" method="post" name="bar">
<input type="test" name="in"/>
<input type="test" name="in"/>
<input type="submit" value="submit"/>
</form></body></html>`
//业务请求相应处理
func hello(res http.ResponseWriter, req *http.Request) {
fmt.Println("hello") //提交给服务器
fmt.Fprintln(res, "<h1>welcome China</h1>")
}
//form表单处理
func formServer(res http.ResponseWriter, req *http.Request) {
//头部申明
res.Header().Set("Content-Type", "text/html")
//类型判断
switch req.Method {
case "GET":
io.WriteString(res, form)
case "POST":
req.ParseForm()
//读取第一个框体内容给respomse做页面回应
io.WriteString(os.Stdout, req.Form["in"][0])
io.WriteString(res, "\n")
io.WriteString(res, req.FormValue("in"))
}
}
func main() {
//路由到指定位置,跳转相关函数处理
http.HandleFunc("/test1", hello)
http.HandleFunc("/test2", formServer)
if err := http.ListenAndServe("127.0.0.1:8088", nil); err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
启动服务
再用浏览器查看效果
4、panic宕机恢复
web 服务为了防止 goroutine 运行 panic 而终止程序,提出了宕机恢复解决方案。
Recover是一个 Go 语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer中有效,在正常的执行过程中,调用 recover 会返回nil并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
通常来说,不应该对进入 panic 宕机的程序做任何处理,但有时,需要我们可以从宕机中恢复,至少我们可以在程序崩溃前,做一些操作,举个例子,当web服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭,如果不做任何处理,会使得客户端─直处于等待状态,如果web服务器还在开发阶段,服务器甚至可以将异常信息反馈到客户端,帮助调试。
在其他语言里,宕机往往以异常的形式存在,底层抛出异常,上层逻辑通过try/catch机制
捕获异常,没有被捕获的严重异常会导致宕机,捕获的异常可以被忽略,让代码继续运行。
Go语言没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,recover的宕机恢复机制就对应其他语言中的try/catch机制。
panic和recover的组合有如下特性:
有 panic 没 recover,程序宕机;
有 panic 也有 recOver,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。
//异常捕捉
package main
import (
"fmt"
"io"
"log"
"net/http"
)
//页面表单
const form = `<html><head><title>web</title></head><body>
<form action="#" method="post" name="bar">
<input type="text" name="in"/>
<input type="text" name="in"/>
<input type="submit" name="提交">
</form></body></html>`
//HTTP服务端配置
//test1业务请求响应处理
func hello(res http.ResponseWriter, req *http.Request) {
fmt.Fprintln(res, "<h1>welcome</h1>") //客户端访问浏览器显示
panic("test1 panic")
}
//form表单处理,使用ResponseWriter在浏览器输出
func formServer(res http.ResponseWriter, req *http.Request) {
//头部声明,表明传的是html的信息
res.Header().Set("Content-Type", "text/html")
//提交类型判断
switch req.Method {
case "GET":
io.WriteString(res, form)
case "POST":
req.ParseForm()
/*
WriteString第一个参数是一个接口,所以不仅可以输出到浏览器,还可以将读取的信息输出到任意地方
也可以将内容输出到控制台 os.stdout,也可以写入到文件等
*/
//读取第一个框体内容给response 做页面回应.连个框体名字都是in,
//如果读取第二个,则把下标替换为1
io.WriteString(res, req.Form["in"][0])
//两种方式输出,第一个框体中的信息提交后将会输出两次,第二个框体没有输出
io.WriteString(res, "\n")
io.WriteString(res, req.FormValue("in"))
}
}
func main() {
//将要捕获的函数,放入到异常捕捉函数里
http.HandleFunc("/test1", logPanic(hello))
http.HandleFunc("/test2", logPanic(formServer))
err := http.ListenAndServe("127.0.0.1:8088", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
//宕机恢复recovere,注意,recover 使用在defer中
//将函数http.HandlerFunc作为值传入,返回值也是
func logPanic(handle http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//宕机恢复,这里使用匿名函数,recover 必须在defer 里使用
defer func() {
if x := recover(); x != nil {
log.Printf("%v,捕捉异常:%v", r.RemoteAddr, x)
}
}()
handle(w, r)
}
}
启动服务
再用浏览器验证效果
5、模板
模板是用于动态生成页面或者用于代码生成器的编写。
示例:
main.go
package main
import (
"fmt"
"os"
"text/template"
)
type Persion struct {
Name string
Age int
Title string
}
//映射key:string value不限制类型
// var arr map[string]interface{}
//template模板引用
func main() {
t, err := template.ParseFiles("index.html")
if err != nil {
fmt.Println("模板读取失败,err:", err)
return
}
//实例Persion
persion := Persion{Name: "tom", Age: 20, Title: "个人网站"}
if err := t.Execute(os.Stdout, persion); err != nil {
fmt.Println("输出错误,err:", err.Error())
}
}
index.html
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
<h2>{{.Name}}</h2>
<h2>{{.Age}}</h2>
</body>
</html>
运行结果为
示例:页面展示。
main.go
package main
import (
"fmt"
"net/http"
"text/template"
)
var myTemplate *template.Template
func initTemplate(filename string) (err error) {
myTemplate, err = template.ParseFiles(filename)
if err != nil {
fmt.Println("模板加载失败,err", err)
return
}
return
}
//业务请求相应处理
func userinfo(res http.ResponseWriter, req *http.Request) {
//参数结构体
type Person struct {
Name string
Age int
Title string
}
p := Person{Name: "json", Age: 20, Title: "智慧城市"}
myTemplate.Execute(res, p)
}
func main() {
//路由到指定位置,跳转相关函数处理
initTemplate("index.html")
http.HandleFunc("/user/info", userinfo)
// http.HandleFunc("/test2", formSever)
err := http.ListenAndServe("127.0.0.1:8091", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
index.html
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
<h1>Person Template</h1>
<h2>{{.Name}}</h2>
<h2>{{.Age}}</h2>
</body>
</html>
启动服务
浏览器验证
输出到文本中
main.go
package main
import (
"fmt"
"net/http"
"os"
"text/template"
)
var myTemplate *template.Template
func initTemplate(filename string) (err error) {
myTemplate, err = template.ParseFiles(filename)
if err != nil {
fmt.Println("模板加载失败,err", err)
return
}
return
}
//业务请求相应处理
func userinfo(res http.ResponseWriter, req *http.Request) {
//参数结构体
type Person struct {
Name string
Age int
Title string
}
p := Person{Name: "json", Age: 20, Title: "智慧城市"}
//写入文件
file, err := os.OpenFile("log.txt", os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
fmt.Println("文件加载失败,err", err)
return
}
myTemplate.Execute(file, p)
//写入浏览器
myTemplate.Execute(res, p)
}
func main() {
//路由到指定位置,跳转相关函数处理
initTemplate("index.html")
http.HandleFunc("/user/info", userinfo)
// http.HandleFunc("/test2", formSever)
err := http.ListenAndServe("127.0.0.1:8091", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
运行服务
逻辑模板
main.go
package main
import (
"fmt"
"net/http"
"os"
"text/template"
)
var myTemplate *template.Template
func initTemplate(filename string) (err error) {
myTemplate, err = template.ParseFiles(filename)
if err != nil {
fmt.Println("模板加载失败,err", err)
return
}
return
}
//业务请求相应处理
func userinfo(res http.ResponseWriter, req *http.Request) {
//参数结构体
type Person struct {
Name string
Age int
Title string
}
p := Person{Name: "json", Age: 40, Title: "个人网站"}
//写入文件
file, err := os.OpenFile("log.txt", os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
fmt.Println("文件加载失败,err", err)
return
}
myTemplate.Execute(file, p)
//写入浏览器
myTemplate.Execute(res, p)
}
func main() {
//路由到指定位置,跳转相关函数处理
initTemplate("index.html")
http.HandleFunc("/user/info", userinfo)
// http.HandleFunc("/test2", formSever)
err := http.ListenAndServe("127.0.0.1:8091", nil)
if err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
index.html
在模板里可以做判断。设置超过30,显示this is old man:不超过30,会显示this is young man。
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
{<h1>Persion Template</h1>
{{if gt .Age 30}}
<h2>this is old man,姓名:{{.Name}},年龄:{{.Age}}</h2>
{{else}}
<h2>this is young man,姓名:{{.Name}},年龄:{{.Age}}</h2>
{{end}}
</body>
</html>
启动服务
浏览器验证
6、MYSQL
在本地安装mysql5.7
安装完成后设置环境变量
配置 Navicat
终端测试
创建库
创建表
安装 Git
设置环境变量
在Go工作目录下安装go的mysql连接驱动
go get github.com/go-sql-driver/mysql
go get github.com/jmoiron/sqlx
验证插件是否安装好
如果安装失败把下面的参数进行修改
示例:添加数据。
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Person struct {
UserId int `db:"user_id"`
UserName string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
//数据库结构体
var DB *sqlx.DB
func init() {
//连接数据库,"用户名:密码@协议(地址:端口)/数据库名"
database, err := sqlx.Open("mysql", "root:abc123@tcp(127.0.0.1:3306)/school")
if err != nil {
fmt.Println("打开数据库失败,err:", err)
return
}
DB = database
}
func main() {
//执行数据库数据插入
result, err := DB.Exec("insert into person (username,sex,email) values (?,?,?)", "tom", "boy", "tom@qq.com")
if err != nil {
fmt.Println("数据写入失败,err:", err)
return
}
num, err := result.LastInsertId()
if err != nil {
fmt.Println("结果获取失败,err:", err)
return
}
fmt.Println("data insert succ", num)
if num != 0 {
fmt.Println("数据写入成功")
}
}
运行结果为:
回到数据库界面展示
示例:查询信息。
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Person struct {
UserId int `db:"user_id"`
UserName string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
//数据库结构体
var DB *sqlx.DB
func init() {
//连接数据库,"用户名:密码@协议(地址:端口)/数据库名"
database, err := sqlx.Open("mysql", "root:abc123@tcp(127.0.0.1:3306)/school")
if err != nil {
fmt.Println("打开数据库失败,err:", err)
return
}
DB = database
}
func main() {
//定义获取数据的切片,用来装载查询结果
var person []Person
err := DB.Select(&person, "select user_id,username,sex,email from person where user_id=?", 1)
if err != nil {
fmt.Println("查询数据失败,err:", err)
return
}
fmt.Println(person)
}
在终端查询结果
示例:在web页面展示数据。
package main
import (
"fmt"
"io"
"net/http"
"strconv"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
//定义页面表单HTML
const form = `<html><body><form action="#" method="post" name="bar">
<input type="text" name="in"/>
<input type="submit" value="Submit"/>
</form></body></html>`
type Person struct {
UserId int `db:"user_id"`
UserName string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
//数据库结构体
var DB *sqlx.DB
func init() {
//连接数据库,"用户名:密码@协议(地址:端口)/数据库名"
database, err := sqlx.Open("mysql", "root:abc123@tcp(127.0.0.1:3306)/school")
if err != nil {
fmt.Println("打开数据库失败,err:", err)
return
}
DB = database
}
//form表单处理
func formServer(res http.ResponseWriter, req *http.Request) {
//头部申明
res.Header().Set("Content-Type", "text/html")
//提交类型判断
switch req.Method {
case "GET":
io.WriteString(res, form)
case "POST":
req.ParseForm()
//获取from框体中的数据,转换成int类型
id, err := strconv.Atoi(req.Form["in"][0])
if err != nil {
fmt.Println("数据类型转换失败,err:", err)
return
}
//定义获取数据的切片,用来装载查询结果
var person []Person
err = DB.Select(&person, "select user_id,username,sex,email from person where user_id=?", id)
if err != nil {
fmt.Println("查询数据失败,err:", err)
return
}
//获取查询结果信息
for _, v := range person {
io.WriteString(res, strconv.Itoa(v.UserId))
io.WriteString(res, "\n")
io.WriteString(res, v.UserName)
io.WriteString(res, "\n")
io.WriteString(res, v.Sex)
io.WriteString(res, "\n")
io.WriteString(res, v.Email)
io.WriteString(res, "\n")
}
}
}
func main() {
http.HandleFunc("/mysql", formServer)
if err := http.ListenAndServe("127.0.0.1:8088", nil); err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
启动服务后,在浏览器查看
示例:更新数据。
package main
import (
"fmt"
"io"
"net/http"
"strconv"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
//定义页面表单HTML
const form = `<html><body><form action="#" method="post" name="bar">
<p>ID<input type="text" name="in"/></p>
<p>UserName<input type="text" name="in"/></p>
<input type="submit" value="Submit"/>
</form></body></html>`
type Person struct {
UserId int `db:"user_id"`
UserName string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
//数据库结构体
var DB *sqlx.DB
func init() {
//连接数据库,"用户名:密码@协议(地址:端口)/数据库名"
database, err := sqlx.Open("mysql", "root:abc123@tcp(127.0.0.1:3306)/school")
if err != nil {
fmt.Println("打开数据库失败,err:", err)
return
}
DB = database
}
//form表单处理
func formServer(res http.ResponseWriter, req *http.Request) {
//头部申明
res.Header().Set("Content-Type", "text/html")
//提交类型判断
switch req.Method {
case "GET":
io.WriteString(res, form)
case "POST":
req.ParseForm()
//获取from框体中的数据,转换成int类型
id, err := strconv.Atoi(req.Form["in"][0])
if err != nil {
fmt.Println("数据类型转换失败,err:", err)
return
}
rename := req.Form["in"][1]
//更新数据
_, err = DB.Exec("update person set username=? where user_id=?", rename, id)
if err != nil {
fmt.Println("更新数据失败,err:", err)
return
} else {
io.WriteString(res, "update succ")
}
}
}
func main() {
http.HandleFunc("/mysql", formServer)
if err := http.ListenAndServe("127.0.0.1:8088", nil); err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
启动服务后在浏览器修改信息
数据库查看结果
示例:删除数据。
package main
import (
"fmt"
"io"
"net/http"
"strconv"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
//定义页面表单HTML
const form = `<html><body><form action="#" method="post" name="bar">
ID<input type="text" name="in"/>
<input type="submit" value="Submit"/>
</form></body></html>`
type Person struct {
UserId int `db:"user_id"`
UserName string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}
//数据库结构体
var DB *sqlx.DB
func init() {
//连接数据库,"用户名:密码@协议(地址:端口)/数据库名"
database, err := sqlx.Open("mysql", "root:abc123@tcp(127.0.0.1:3306)/school")
if err != nil {
fmt.Println("打开数据库失败,err:", err)
return
}
DB = database
}
//form表单处理
func formServer(res http.ResponseWriter, req *http.Request) {
//头部申明
res.Header().Set("Content-Type", "text/html")
//提交类型判断
switch req.Method {
case "GET":
io.WriteString(res, form)
case "POST":
req.ParseForm()
//获取from框体中的数据,转换成int类型
id, err := strconv.Atoi(req.Form["in"][0])
if err != nil {
fmt.Println("数据类型转换失败,err:", err)
return
}
//删除数据
_, err = DB.Exec("delete from person where user_id=?", id)
if err != nil {
fmt.Println("del data,err:", err)
return
} else {
io.WriteString(res, "del succ")
}
}
}
func main() {
http.HandleFunc("/mysql", formServer)
if err := http.ListenAndServe("127.0.0.1:8088", nil); err != nil {
fmt.Println("启动监听失败,err:", err)
return
}
}
启动服务后在web界面进行删除
数据库查看信息