想了解下区块链相关的东西,从头开始学习go 语法实在是耐不下心,稍微看了下
还是直接做web来学吧,主要材料如下
web应用的流程如图所示,goweb使用默认的多路服用去转发请求到处理器,如果要使用模板,处理器解析并渲染返回响应,和数据交互通过模型完成
不论怎样,首先写个hello world的demo,跑起来再说
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":8888", nil)
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "hello wolrd")
}
一个简单的web页面就启动起来了,相对于javaee的web工程简直太棒了
go语言的标准库放在这里:go标准库
上面的handlerFunc实际上是一个适配器,将func转换为handler
handler是一个接口,实现了handler的ServeHTTP方法,就能够为特定路径提供服务
上面传入的nil实际上是指定了默认的ServeMux,可以自己实现
下面自己实现一个handler
func main() {
http.HandleFunc("/", hello)
myhandler := Myhandler{}
http.Handle("/demo", &myhandler)
http.ListenAndServe(":8888", nil)
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "hello wolrd", r.URL.Path)
}
type Myhandler struct{}
func (h *Myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "demo")
}
上面的Myhandler struct时间了Handler接口,因此可以注册到Mux中处理请求了
此外可以通过定义Server结构体自定义server的各种参数
func main() {
myhandler := Myhandler{}
server := http.Server{
Addr: ":8888",
Handler: &myhandler,
ReadTimeout: 2 * time.Second,
}
server.ListenAndServe()
}
type Myhandler struct{}
func (h *Myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "server")
}
这个时候还是使用的默认mux,可以创建自己的mux
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", hello)
myhandler := Myhandler{}
mux.Handle("/demo", &myhandler)
server := http.Server{
Addr: ":8888",
Handler: mux,
ReadTimeout: 2 * time.Second,
}
server.ListenAndServe()
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "hello")
}
type Myhandler struct{}
func (h *Myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "server")
}
这里使用vscode 的rest client插件进行http请求,so easy
接下来调试下go链接数据库的的后端
和java一样,golang中的database/sql定义了database的操作,安装相应的驱动即可(这些驱动是按照go的接口标准开发的),sql包统一了database的操作
这里用最常用的mysql数据库来实验,在本地安装好mariadb数据库即可(驱动和mysql貌似都一样,毕竟是亲兄弟)
文档:https://studygolang.com/static/pkgdoc/pkg/database_sql.htm
驱动下载:https://github.com/golang/go/wiki/SQLDrivers
go get github.com/go-sql-driver/mysql
然后再MySQL中创建数据库和表
CREATE DATABASE webdemo;
use webdemo;
CREATE TABLE IF NOT EXISTS `student`(
`id` INT UNSIGNED AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`age` INT,
`tele` VARCHAR(40),
`birth` DATE,
PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO student (name, age, tele, birth) VALUES ("sam", 22, "12304772837",NOW());
INSERT INTO student (name, age, tele, birth) VALUES ("bob", 16, "321314431321",NOW());
INSERT INTO student(name, age, tele, birth) VALUES ("mary", 22, "7389217391",NOW());
数据库链接工具使用vsvode插件database client,表结构如下
尝试链接并查询studnet表
文档中sql模块中对curd的接口介绍的比较简洁,但是也足够了
Query 查询多行
QueryRow 查询一行
Exec 增删改
Prepare 预编译语句
这里首先获取Db链接
var (
Db *sql.DB
err error
)
func init() {
Db, err = sql.Open("mysql", "root:zhaojie@tcp(localhost:3306)/webdemo")
if err != nil {
panic(err.Error())
}
}
然后创建User结构,实现crud方法
package model
type User struct {
Id int
Name string
Age int
Tele string
Birth time.Time
}
func (u *User) Adduser() error {
sqlStr := "insert into student (name,age,tele,birth) values(?,?,?,?)"
inStmt, err := dbutils.Db.Prepare(sqlStr)
if err != nil {
log.Fatal("prepare is wrong", err)
return err
}
u.Birth = time.Now()
_, inerr := inStmt.Exec(u.Name, u.Age, u.Tele, u.Birth)
if inerr != nil {
log.Fatal("insert is wrong", err)
return err
}
return nil
}
为了方便进行测试,看一下go的单元测试方法,参考testing包
测试文件以test_开头,和被测在一个包中,测试函数以Test开头并且参数按照不同的用途进行了分类。
此外如果不以Test开头则默认不执行,可以设置成子测试函数
TestMain可以在测试方法之前执行一段逻辑
package model
func TestMain(t *testing.M) {
fmt.Println("start testing")
}
func TestUser(t *testing.T) {
fmt.Println("start test user func")
t.Run("test add user", TestAdduser)
}
func TestAdduser(t *testing.T) {
fmt.Println("start test add user:")
user := &User{
Name: "test1",
Age: 12,
Tele: "12313213123",
Birth: time.Now(),
}
user.Adduser()
}
在model模块打开控制台运行go test即可,运行之后显示数据库插入成功
$ go test
start testing
start test user func
start test add user:
PASS
ok beegodemo/model 0.005s
查询操作如下
func (u *User) GetUserByID() (*User, error) {
sqlStr := "select * from student where id = ?"
row := dbutils.Db.QueryRow(sqlStr, u.Id)
var (
id int
name string
age int
tele string
birth_str string
)
err := row.Scan(&id, &name, &age, &tele, &birth_str)
if err != nil {
return nil, err
}
DefaultTimeLoc := time.Local
birth, err := time.ParseInLocation("2006-01-02", birth_str, DefaultTimeLoc)
user := &User{
Id: id,
Age: age,
Name: name,
Tele: tele,
Birth: birth,
}
return user, err
}
测试方法
func TestGetUserByID(t *testing.T) {
fmt.Println("start test get user by id:")
user := &User{
Id: 2,
}
u, err := user.GetUserByID()
if err != nil {
fmt.Println("wrong get user", err)
}
json, _ := json.Marshal(u)
fmt.Print(string(json))
}
测试结果如下
start testing
=== RUN TestGetUserByID
start test get user by id:
{"id":2,"name":"bob","age":16,"tele":"321314431321","Birth":"2022-01-29T00:00:00Z"}
--- PASS: TestGetUserByID (0.00s)
PASS
ok beegodemo/model 0.004s
后面的写法大同小异,参考godoc即可
只不过Query方法得到rows,需要便利rows进行scan到每个user对象中
for rows.Next(){
rows.Scan(....)
user := &User(....)
append(users,user)
}
直接去看net/http包
requesu是一个struct,根据字段写获取就行
func hello(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, r.Header["Accept-Encoding"])
fmt.Fprintln(w, r.UserAgent())
}
解析request请求
看一下post请求的解析
request中的Body字段是 io.ReadCloser类型
readcloser有两个子接口reader和closer实现了read和lclose方法,read方法将数据读入byte切片
func (h *Myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
length := r.ContentLength
body := make([]byte, length)
r.Body.Read(body)
fmt.Fprintln(w, string(body))
}
不知道为什么rest client没有codelens,有点麻烦,懒得查了将就用吧
当然form表单中的数据有专门的获取方式
func form(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintln(w, r.Form)
fmt.Fprintln(w, r.PostForm)
}
form字段只有在调用parsefrom之后才有效,并且post请求优先于url查询字符串
注意:postform只支持x-www-form-urlencoded,如果是mutilpart/form-data,需要使用mutilpartform
如果想快速获得某个请求参数那就用formvalue和postformvalue,好处是会自动parseform
上传文件用解析mutilpartform即可,得到一个file指针读取就行了,大同小异
这部分也比较简单,大体上就是设置响应头和响应体
func resp(w http.ResponseWriter, r *http.Request) {
str := `
go web
hello world
`
w.Write([]byte(str))
}
wtiteheader能够设置响应header code,但是调用之后就不能继续修改header了
func resp(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
fmt.Fprintln(w, "not found")
}
重定向
func resp(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "http://bing.com")
w.WriteHeader(302)
}
返回json
type Post struct {
User string `json:"user"`
Threads []string `json:"threads"`
}
func resp(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
post := &Post{
User: "zhaojie",
Threads: []string{"first", "second", "third"},
}
json, _ := json.Marshal(post)
w.Write(json)
}
模板引擎能够将模板和数据结合起来生成网页
模板引擎有两种
go模板引擎使用了text/template库,介于两种引擎特性之间
简单的模板 demo
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
{{ . }}
body>
html>
替换占位符
func tmpl(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("html/index.html")
t.Execute(w, "hello world")
}
写一个模板的demo
func main() {
mux := http.NewServeMux()
templates := loadTemplates()
mux.HandleFunc("/", newtemp(templates))
server := http.Server{
Addr: ":8888",
Handler: mux,
ReadTimeout: 2 * time.Second,
}
mux.Handle("/css/", http.FileServer(http.Dir("html/wwwroot")))
mux.Handle("/img/", http.FileServer(http.Dir("html/wwwroot")))
server.ListenAndServe()
}
//创建模板集
func loadTemplates() *template.Template {
result := template.New("templates")
template.Must(result.ParseGlob("html/template/*.html"))
return result
}
//handler函数
func newtemp(templates *template.Template) func(w http.ResponseWriter, r *http.Request) {
temp := func(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Path[1:]
t := templates.Lookup(filename)
if t != nil {
err := t.Execute(w, nil)
if err != nil {
log.Fatalln(err.Error())
}
} else {
w.WriteHeader(http.StatusNotFound)
}
}
return temp
}
请求结果和项目结构在下面,注意
有图片和css,效果在网页看比较好
这里有个坑点,注意自己使用的多路复用器是自定义的还是默认的,加载静态文件的时候一定不要弄错了
搭建好了基本的股价,下面来看模板中的action,一共五种
这里随便写几个demo
func tmplaction(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("html/template/tmpl.html")
rand.Seed(time.Now().Unix())
t.Execute(w, rand.Intn(10) > 5)
}
gotemplate-syntax插件
<body>
{{ if . }}
number os greater than 5
{{ else }}
number is 5 or less
{{ end }}
body>
前面的逻辑处理放在了main函数当中,但是实际上main函数应该只负责设置类的工作,将handle逻辑全部放在conrtoller中
现在的项目结构如图所示:
用RegisterRoute导出handlefunc
//controller.go
func RegisterRoutes() {
//这里还可以加入一些static resource
registerAboutRoute()
registerHomeRoute()
registerIndex()
}
下面是未导出的route
func registerHomeRoute() {
http.HandleFunc("/test", handleHome)
}
func handleHome(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, r.Header["Accept-Encoding"])
fmt.Fprintln(w, r.UserAgent())
}
如果要解析urlpath中的信息,可以使用regex匹配
还有一些第三方的路由器
中间件middleware在请求和响应冲handler中进出之前对消息进行处理
middleware实际上也是一个handler,在正式的handler之前形成一个链式结构,逻辑就像下面这样
中间件的用途通常包括
type MyMiddleware struct{
Next http.Handler
}
func(m MyMiddleware)ServeHTTP(w http.ResponseWriter, r *http.Request){
//在next handle之前的处理
m.Next.ServeHTTP(w,r)
//在next handle之后的处理
}
写个dmeo
type AuthMiddleware struct {
Next http.Handler
}
func (am AuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if am.Next == nil {
am.Next = http.DefaultServeMux
}
auth := r.Header.Get("Authorization")
if auth != "" {
am.Next.ServeHTTP(w, r)
} else {
w.WriteHeader(http.StatusUnauthorized)
}
}
可以看到没有带authorization头的请求被拦截了,middle将请求和相应参数都传递给next,如果next为空则设置为默认mux
为了在一次会话过程中保存一些数据,可以使用context环境变量,这个用到在查吧
众所周知,https使用tls对通信进行了加密
go可以使用https的linsten进行监听
我们测试环境没有证书,但是好在go为我们提供了脚本生成证书
$ go run /usr/lib/golang/src/crypto/tls/generate_cert.go -h
Usage of /tmp/go-build083634091/b001/exe/generate_cert:
-ca
whether this cert should be its own Certificate Authority
-duration duration
Duration that certificate is valid for (default 8760h0m0s)
-ecdsa-curve string
ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521
-ed25519
Generate an Ed25519 key
-host string
Comma-separated hostnames and IPs to generate a certificate for
-rsa-bits int
Size of RSA key to generate. Ignored if --ecdsa-curve is set (default 2048)
-start-date string
Creation date formatted as Jan 1 15:04:05 2011
$ go run /usr/lib/golang/src/crypto/tls/generate_cert.go -host localhost
2022/01/31 07:52:28 wrote cert.pem
2022/01/31 07:52:28 wrote key.pem
使用上面的两个文件监听即可
func main() {
controller.RegisterRoutes()
server := http.Server{
Addr: ":8888",
Handler: new(middleware.AuthMiddleware),
ReadTimeout: 2 * time.Second,
}
http.Handle("/css/", http.FileServer(http.Dir("html/wwwroot")))
http.Handle("/img/", http.FileServer(http.Dir("html/wwwroot")))
server.ListenAndServeTLS("cert.pem","key.pem")
}
https请求的header要复杂很多,具体可以查资料
此外go还支持使用http2协议进行server push
go支持了对于web相应的request测试,这里就不展示了,用到再说
go自带的性能分析工具还是很强大的,用起来也很简单
这个貌似目前也用不太到,需要的话转移到
https://www.bilibili.com/video/BV1Xv411k7Xn?p=28&spm_id_from=pageDriver