0%

数组Array

  • 是同一种数据类型的固定长度的序列。

  • 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值

  • 数组定义:var a [len]T,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。

  • 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
全局:
var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}
局部:
a := [3]int{1, 2} // 未初始化元素值为 0。
b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
}

字符串

如下代码所示:

  • 函数外定义字符串,为全局变量
  • 在函数内定义s3 := “5555”,为局部变量
  • 循环字符串时,单个字符的类型为byte,而字符有两种类型
    • 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符
    • 另一种是 rune 类型,代表一个 UTF-8 字符,如中文,rune 类型等价于 int32 类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 全局定义字符串
var s1 = `
111
222
33
`
var s2 = "4444"

func main() {
// 局部定义
s3 := "5555"
var a = "中"
fmt.Println(s1, s2, s3, a)
s := "pprof.cn博客"
for i := 0; i < len(s); i++ { //byte
fmt.Printf("%v(%c) ", s[i], s[i])
//112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 229(å) 141() 154() 229(å) 174(®) 162(¢)
}
fmt.Println()
// 打印中文,需转换为rune类型
for _, r := range s { //rune
fmt.Printf("%v(%c) ", r, r)
//112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 21338(博) 23458(客)
}
fmt.Println()

s1 := "hello"
// 强制类型转换
byteS1 := []byte(s1)
byteS1[0] = 'H'
fmt.Println(reflect.TypeOf(byteS1[0])) // uint8
fmt.Println(string(byteS1)) //Hello

s2 := "博客"
runeS2 := []rune(s2)
runeS2[0] = '狗'
fmt.Println(string(runeS2)) //狗客

byte

  • 在字符串中代码已经详细说明了byte的定义,就是字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符。

切片Slice

初始化

  • 看如下代码,为声明和初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//1.声明切片,int类型的数组
var s1 []int
if s1 == nil {
fmt.Println("是空")
} else {
fmt.Println("不是空")
}
// 2.:=
s2 := []int{}
// 3.make()
var s3 []int = make([]int, 0)
fmt.Println("s1,s2,s3", s1, s2, s3) // s1,s2,s3 [] [] []
// 4.初始化赋值
var s4 []int = make([]int, 0, 0)
fmt.Println("s4=", s4) // s4= []
s5 := []int{1, 2, 3}
fmt.Println(s5)
// 5.从数组切片
arr := [5]int{1, 2, 3, 4, 5}
var s6 []int
// 前包后不包
s6 = arr[1:4]
fmt.Println("s6=", s6) //s6= [2 3 4]

append

  • 切片追加
1
2
3
4
5
6
7
8
9
10
11
var a = []int{1, 2, 3}
fmt.Printf("slice a : %v\n", a) // slice a : [1 2 3]
var b = []int{4, 5, 6}
fmt.Printf("slice b : %v\n", b) // slice b : [4 5 6]
c := append(a, b...)
fmt.Printf("slice c : %v\n", c) //slice c : [1 2 3 4 5 6]
d := append(c, 7)
fmt.Printf("slice d : %v\n", d) // slice d : [1 2 3 4 5 6 7]
e := append(d, 8, 9, 10)
fmt.Printf("slice e : %v\n", e) // slice e : [1 2 3 4 5 6 7 8 9 10]

  • 向 slice 尾部添加数据,返回新的 slice 对象
1
2
3
4
5
6
7
s1 := make([]int, 0, 5)
fmt.Printf("%p\n", &s1) // 0xc000008078

s2 := append(s1, 1)
fmt.Printf("%p\n", &s2) // 0xc000008090

fmt.Println(s1, s2) // [] [1]

总结

  • 切片是指针类型,数组是值类型
  • 数组的赋值形式为值传递,切片的赋值形式为引用传递
  • 数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组)
  • 数组只有长度一个属性,而切片比数组多了一个容量(cap)属性

map

  • map是一种无序的基于key-value的数据结构
  • 定义 map[KeyType]ValueType,如map[string]int
1
2
3
4
5
6
7
8
9
10
11
func main(){
// 使用make初始化一个map
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["王五"] = 60
delete(scoreMap, "小明")//将小明:100从map中删除
for k,v := range scoreMap{
fmt.Println(k, v)
}
}

元素为map类型的切片

  • 对应于python中的[{"name":"王五","password": "123456", "address":"红旗大街"},{}]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var mapSlice = make([]map[string]string, 3)
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value) //index:0 value:map[]
}
fmt.Println("after init")
// 对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "王五"
mapSlice[0]["password"] = "123456"
mapSlice[0]["address"] = "红旗大街"
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
// index:0 value:map[address:红旗大街 name:王五 password:123456]
//index:1 value:map[]
for k, v := range value {
fmt.Println("k=", k, "v=", v)
//k= name v= 王五
//k= address v= 红旗大
}
}
fmt.Println(mapSlice) // [map[address:红旗大街 name:王五 password:123456] map[] map[]]

值为切片类型的map

  • 对应于python中{“中国”:[“北京”, “上海”]}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap)
fmt.Println("after init")
key := "中国"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2)
}
value = append(value, "北京", "上海")
sliceMap[key] = value
fmt.Println(sliceMap) //map[中国:[北京 上海]]
fmt.Println(sliceMap["中国"]) // [北京 上海]
for _, v := range sliceMap["中国"] {
fmt.Println(v)
// 北京
// 上海
}
fmt.Println(sliceMap) //map[中国:[北京 上海]]

interface

  • interface是一种抽象的类型,可以用来表示任何类型

  • 比如如下场景,不像写结构体,就可以用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js := `{"int":1,"string":"qwertyuiop","float":1.111}`
jsm := make(map[string]interface{})
// 反序列化
err := json.Unmarshal([]byte(js), &jsm)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(jsm) // map[float:1.111 int:1 string:qwertyuiop]
for k, v := range jsm {
fmt.Println("k=", k, "v=", v)
}


类型转换

byte 转换为string

  • data1.json中内容为:
1
2
3
1 2
2 3
中国 4
  • 代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
f, err := os.Open("hello_work\\data1.json")
if err != nil {
fmt.Println("open file eror:", err)
return
}
defer f.Close()
// 读取文件内容,类型为[]byte
byteValue, _ := ioutil.ReadAll(f)
fmt.Println(byteValue) // [49 32 50 13 10 50 32 51 13 10 228 184 173 229 155 189 32 52]
fmt.Println(reflect.TypeOf(byteValue)) // []uint8
s := string(byteValue)
for _, v := range s {
fmt.Println(string(v))
}

string 转换为map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package conver

import (
"encoding/json"
"fmt"
"reflect"
)

type User struct {
Code int
Msg string
}

func StrT() {
js := `{"code":1,"msg":"qwertyuiop"}`
// 定义一个map的key为string,值类型为不确定的结构体
jsm := make(map[string]interface{})
// 反序列化
err := json.Unmarshal([]byte(js), &jsm)

if err != nil {
fmt.Println(err)
return
}
fmt.Println(jsm) // map[code:1 msg:qwertyuiop]

var user User
err1 := json.Unmarshal([]byte(js), &user)
if err1 != nil {
fmt.Println(err1)
return
}
fmt.Println(user) //{1 qwertyuiop}
fmt.Println(reflect.TypeOf(user)) //conver.User
}

byte 转换为map

  • data.json内容
1
{"code": 1, "msg":"success", "data":[{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
f, err := os.Open("hello_work\\data.json")
if err != nil {
fmt.Println("open file eror:", err)
return
}
defer f.Close()
byteValue, _ := ioutil.ReadAll(f)

// 定义一个map的key为string,值类型为不确定的结构体
jsm := make(map[string]interface{})
// 反序列化
err1 := json.Unmarshal(byteValue, &jsm)

if err1 != nil {
fmt.Println(err)
return
}
fmt.Println(jsm) // map[code:1 data:[map[id:1 name:西瓜] map[id:2 name:苹果]] msg:success]

// map转换为string
dataType, _ := json.Marshal(jsm)
dataString := string(dataType)
fmt.Println(dataString)

map转换为string

  • 在byte 转换为map中,最后三行代码就是把map转为string
1
2
3
dataType, _ := json.Marshal(jsm)
dataString := string(dataType)
fmt.Println(dataString)

其他

说明

  • 对mysql的操作采用了两种,第一种是写原生sql,使用如下两个库
1
2
"github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
  • 第二种采用orm,使用了如下库
1
2
"gorm.io/driver/mysql"
"gorm.io/gorm"

准备工作

  • 本地搭建好mysql环境,数据库和表名如下

image-20230608170552494

原生sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# hello_work/tmysql/operateMsql.go
package tmysql

import (
"fmt"

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

//go get github.com/go-sql-driver/mysql
//go get github.com/jmoiron/sqlx

// 对应Class表中的字段
type Class1 struct {
Id int `db:"id"` // 注意这里``的用法,对应数据库的字段
Name string `db:"name"`
Sex *string `db:"sex"` // 表中数据为空,直接取值报错,若是指针则返回nil

}

var Db *sqlx.DB

func InitDb() {
database, err := sqlx.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("connect mysql err", err)
return
}
Db = database
}

func Select() {

var class1 []Class1
err := Db.Select(&class1, "select id,name,sex from class")
if err != nil {
fmt.Println("select error", err)
return
}
fmt.Println("class succ", class1)
for _, v := range class1 {
fmt.Println(v.Id)
fmt.Println(v.Name)
if v.Sex != nil {
fmt.Println(*v.Sex)

}
}
}

func Insert() {
r, err := Db.Exec("insert into class(name) values(?)", "三年级一班")
if err != nil {
fmt.Println(err)
return
}
id, err := r.LastInsertId()
if err != nil {
fmt.Print("exec failed", err)
return
}
fmt.Println("insert success", id)
}

func Update() {
res, err := Db.Exec("update class set name=? where id=?", "三年级二班", 9)
if err != nil {
fmt.Println(err)
return
}
row, err := res.RowsAffected()
if err != nil {
fmt.Println("row failed, error", err)
}
fmt.Println("update success:", row)
}

func Delete() {
res, err := Db.Exec("delete from class where id=?", 10)
if err != nil {
fmt.Println(err)
return
}
row, err := res.RowsAffected()
if err != nil {
fmt.Println("row failed, error", err)
}
fmt.Println("detele success:", row)

}

  • 代码调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# hello_work/main.go
package main

import (
"example.com/hello_work/hello_work/tmysql"
)

func main() {
tmysql.InitDb()
tmysql.Select()
tmysql.Insert()
tmysql.Update()
tmysql.Delete()
}

gorm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package ggorm

import (
"fmt"

"gorm.io/driver/mysql"
"gorm.io/gorm"
)

// 安装gorm
//go get gorm.io/gorm
// 安装mysql驱动
//go get gorm.io/driver/mysql

var Db *gorm.DB

type User struct {
gorm.Model // 内置的结构提
Id uint // 默认为主键
Name string
}

func InitDb() {
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
database, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// database, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
fmt.Println("open mysql error:", err)
}
Db = database
//// 根据User结构体,自动创建表结构,表名为users,如果表存在,不会创建
Db.AutoMigrate(&User{})
}
func Close() {
sqlDB, _ := Db.DB()
sqlDB.Close()
}

func Insert() {
u := User{
Name: "张山1",
}

if err := Db.Create(&u).Error; err != nil {
fmt.Println("create is wrong,error:", err)
return
}
fmt.Println("创建数据成功")
}

func Select() {
var users []User
// SELECT * FROM users;
if err := Db.Find(&users).Error; err != nil {
fmt.Println("find failed, error:", err)
return
}
for k, v := range users {
fmt.Println("k=", k, "v=", v)
fmt.Println(v.Name)
fmt.Println(v.Id)
}
//SELECT * FROM users WHERE name = '张山1';
Db.Where("name=?", "张山1").Find(&users)
if len(users) == 0 {
fmt.Println("find failed")
return
}

for k, v := range users {
fmt.Println("k1=", k, "v1=", v)
fmt.Println(v.Name)
fmt.Println(v.Id)
}

}

func Update() {
user := &User{Id: 1}
d := Db.Model(user).Update("Name", "lisi")
if d.Error != nil {
fmt.Println("update failed")
return
}
fmt.Println("update success")
}

func Detele() {
user := &User{Id: 2}

// 查询不到此数据,就说明无法删除
var users []User
Db.Where("id=?", 2).Find(&users)
if len(users) == 0 {
fmt.Println("del failed")
return
}
// 永久删除
d := Db.Unscoped().Delete(&user)
// 下面的语句有问题无论删除是否成功都成功
if d.Error != nil {
fmt.Println("del failed")
return
}

fmt.Println("del success")
}

  • 代码调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# hello_work/main.go
package main

import (
"example.com/hello_work/hello_work/ggorm"
"example.com/hello_work/hello_work/tmysql"
)

func main() {
tmysql.InitDb()
tmysql.Select()
tmysql.Insert()
tmysql.Update()
tmysql.Delete()

ggorm.InitDb()
ggorm.Insert()
ggorm.Update()
ggorm.Detele()
ggorm.Close()
}

来自

说明

  • 本文主要对json内容的读取

实例

  • data.json文件内容
1
{"code": 1, "msg":"success", "data":[{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]}
  • main.go代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/tidwall/gjson"
)

func strGjson() {
// go get -u github.com/tidwall/gjson
jsonstr := `{"code": 1, "msg":"success", "data":[{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]}`
results := gjson.GetMany(jsonstr, "code", "msg", "data")
fmt.Println(results) //[1 success [{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]]
data := results[2]
fmt.Println(data) //[{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]
if data.Exists() {
re := data.Array()
for _, v := range re {
fmt.Println(v) // {"id": 1, "name":"西瓜"}
fmt.Println(v.Get("name"))
}
}

}

func readByteJson() {
f, err := os.Open("hello_work\\data.json")
if err != nil {
fmt.Println("open file eror:", err)
return
}
defer f.Close()
// 不写结构体,直接用这种方式定义
formData := make(map[string]interface{})
// 解码json
json.NewDecoder(f).Decode(&formData)
// 第一次循环,取map值
for key, value := range formData {
fmt.Println("key:", key, " => value :", value)
// 第二次循环,取数组的值
if _, ok := value.([]interface{}); ok {
for k, v := range value.([]interface{}) {
fmt.Println("key:", k, " => value :", v)
// 第三次循环,取数组中map的值
for j, s := range v.(map[string]interface{}) {
fmt.Printf("%s: %v\n", j, s)

}
}
}
}

}
func byteGjson() {
// go get -u github.com/tidwall/gjson
f, err := os.Open("hello_work\\data.json")
if err != nil {
fmt.Println("open file eror:", err)
return
}
defer f.Close()
// 读取json内容,类型为[]byte
byteValue, _ := ioutil.ReadAll(f)
data := gjson.GetBytes(byteValue, "data")
code := gjson.GetBytes(byteValue, "code")
fmt.Println(data) // [{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]
fmt.Println(code)
if data.Exists() {
re := data.Array()
for _, v := range re {
fmt.Println(v) // {"id": 1, "name":"西瓜"}
fmt.Println(v.Get("name"))
}
}

}

func main() {
// readJson()
// byteGjson()
strGjson()
}

socket

  • Socket又称“套接字”,应用程序通常通过“套接字”向网络发出请求或者应答网络请求

  • 常用的Socket类型有两种:流式Socket和数据报式Socket,流式是一种面向连接的Socket,针对于面向连接的TCP服务应用,数据报式Socket是一种无连接的Socket,针对于无连接的UDP服务应用

  • TCP:比较靠谱,面向连接,比较慢

  • UDP:不是太靠谱,比较快

  • 举个例子:TCP就像货到付款的快递,送到家还必须见到你人才算一整套流程。UDP就像某快递快递柜一扔就走管你收到收不到,一般直播用UDP。

TCP编程

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# hello_work\tcp\server.go
package main

import (
"fmt"
"net"
)

func process(conn net.Conn) {
defer conn.Close()
for {
var buff [128]byte
// 将tcp连接读取到的数据读取到byte数组中, 返回读取到的byte的数目
n, err := conn.Read(buff[:])
if err != nil {
// 从客户端读取数据的过程中发生错误
fmt.Println("read from client failed, err:", err)
break
}
recvStr := string(buff[:n])
fmt.Println("收到client端发来的数据", recvStr)
// conn.Write([]byte(recvStr)) // 发送数据
conn.Write([]byte("这是服务端返回的数据")) // 发送数据

}
}

func main() {
// 监听当前的tcp连接
listen, err := net.Listen("tcp", "127.0.0.1:2000")
if err != nil {
fmt.Println("liston failed, error:", err)
return
}
for {
conn, err := listen.Accept() //建立tcp连接
fmt.Print("建立tcp连接")
if err != nil {
fmt.Println("aceept failed,error", err)
continue
}
// 启动一个新的线程
go process(conn)
}
}

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
"bufio"
"fmt"
"net"
"os"
"strings"
)

func main() {
// 连接tcp服务器
conn, err := net.Dial("tcp", "127.0.0.1:2000")
if err != nil {
fmt.Println("err:", err)
return
}
fmt.Println("连接tcp服务成功")
defer conn.Close()
// 获取一个标准输入的*Reader结构体指针类型的变
inputReader := bufio.NewReader(os.Stdin)
for {
// 读取用户输入
input, _ := inputReader.ReadString('\n')
// 去掉\r\n
inputInfo := strings.Trim(input, "\r\n")
// 用户输入q,就退出
if strings.ToUpper(inputInfo) == "Q" {
return
}
// 发送数据到tcp服务端
_, err := conn.Write([]byte(inputInfo))
if err != nil {
fmt.Println("send data is worng,error:", err)
return
}
buff := [512]byte{}
// var buff [512]byte
// 读取服务端发送的数据
n, err := conn.Read(buff[:])
if err != nil {
fmt.Println("recv failed, err:", err)
return
}
fmt.Println("客户端接受服务端的数据为:", string(buff[:n]))
}
}

运行

  • 分别运行go run hello_work/tcp/server.gogo run hello_work/tcp/client.go

  • 客户端输入数据后,服务端返回数据

1
2
3
4
5
6
7
8
9
10
PS E:\proj\gowork\hello_work\tcp> go run .\server.go
建立tcp连接收到client端发来的数据 22
收到client端发来的数据 333

PS E:\proj\gowork> go run .\hello_work\tcp\client.go
连接tcp服务成功
22
客户端接受服务端的数据为: 这是服务端返回的数据
333
客户端接受服务端的数据为: 这

http

  • HTTP协议通常承载于TCP协议之上
  • 下面的实例包含两部分,一部分是最简单的http服务器和客户端,另外一部分是展示如何使用json传递消息

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# hello_work/http/server.go
package main

import (
"fmt"
"net/http"
)

func myhandler(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.RemoteAddr, "连接成功")
fmt.Println("method:", r.Method)
fmt.Println("url:", r.URL)
fmt.Println("header:", r.Header)
fmt.Println("body:", r.Body)
// 服务器回复内容
w.Write([]byte("hello word!"))
}

func main() {
// 监控客户端发送的请求网址后缀为/go,并调用自定义回调函数
http.HandleFunc("/go", myhandler)
// http.HandleFunc("/to", myhandler1)
http.ListenAndServe("127.0.0.1:8000", nil)
}

  • 启动server
1
go run hello_work/http/server.go

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# hello_work/http/client
package main

import (
"fmt"
"io"
"net/http"
)

func main() {
resp, err := http.Get("http://127.0.0.1:8000/go")
if err != nil {
fmt.Println("连接服务器失败,error", err)
return
}
defer resp.Body.Close()
fmt.Println("code:", resp.StatusCode)
buf := make([]byte, 1024)
for {
// 接受服务器信息
n, err := resp.Body.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("读取服务器数据失败,error:", err)
return
}
fmt.Println("读取服务器数据成功")
res := string(buf[:n])
fmt.Println(res)
break
}

}

  • 运行客户端
1
2
3
4
5
PS E:\proj\gowork\hello_work> go run .\http\client.go
code: 200
读取服务器数据成功
hello word!
PS E:\proj\gowork\hello_work>
  • 实际上的场景肯定复杂的多,比如常见的客户端和服务端都是用json交互,如下面代码

server1

  • 服务器代码进行优化,当请问为post时,接受客户端的json参数,响应结果也是以json字符串进行返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# hello_work/http/server.go
package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
)

type Data struct {
Name string
Age int
}

// 定义返回给客户端的结构体
type Ret struct {
Code int
Param string
Msg string
Data []Data
}

func myHandlerJson(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
err := r.ParseForm()
// 接受客户端传过来的json
formData := make(map[string]interface{})
if err != nil {
fmt.Println("读取客户端的数据失败,error:", err)
return
}
// 调用json包的解析,解析请求body
json.NewDecoder(r.Body).Decode(&formData)
// 打印客户端传过来的json值
for key, value := range formData {
fmt.Println("key:", key, " => value :", value)
}

data := Data{Name: "li", Age: 18}
ret := new(Ret)
ret.Code = 0
ret.Msg = "success"
ret.Param = "1"
ret.Data = append(ret.Data, data)
ret.Data = append(ret.Data, data)
ret.Data = append(ret.Data, data)
// 将结构体转换为切片
ret_json, _ := json.Marshal(ret)
// 传递参数给客户端
io.WriteString(w, string(ret_json))

}
}

func myhandler(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.RemoteAddr, "连接成功")
fmt.Println("method:", r.Method)
fmt.Println("url:", r.URL)
fmt.Println("header:", r.Header)
fmt.Println("body:", r.Body)
// 服务器回复内容
w.Write([]byte("hello word!"))
}

func main() {
// 监控客户端发送的请求网址后缀为/go,并调用自定义回调函数
http.HandleFunc("/go", myhandler)
http.HandleFunc("/bar", myHandlerJson)
http.ListenAndServe("127.0.0.1:8000", nil)
}

client1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#  hello_work/http/client
package main

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)

type Cmd struct {
ReqType int
username string
pwd string
}

func start(s string) {
if strings.ToUpper(s) == "POST" {
contentType := "application/json;charset=utf-8"
url := "http://127.0.0.1:8000/bar"

// 定义传送给服务器的json
cmd := Cmd{ReqType: 1, Username: "test1", Pwd: "123456"}
// 把结构体转换为json
b, err := json.Marshal(cmd)
if err != nil {
fmt.Println("json格式错误,error:", err)
return
}
// 把json参数转换为bytes,传给服务端
body := bytes.NewBuffer(b)
resp, err := http.Post(url, contentType, body)
if err != nil {
fmt.Println("http request is fail,error:", err)
}
defer resp.Body.Close()

// content, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// fmt.Println("Read failed:", err)
// return
// }
// 不写结构体,直接用这种方式定义
formData := make(map[string]interface{})
json.NewDecoder(resp.Body).Decode(&formData)

// jsonErr := json.Unmarshal(content, &formData) // 解码服务端传递的json
// if jsonErr != nil {
// fmt.Println(jsonErr)
// }
// 数据结构体
//map[Code:0 Data:[map[Age:18 Name:li] map[Age:18 Name:li] map[Age:18 Name:li]] Msg:success Param:1]
fmt.Println("读取服务器数据成功", formData)

for key, value := range formData {
fmt.Printf("%s: %v\n", key, value)
if key == "Data" {
// 判断value是否是一个切片,再使用for循环遍历切片中的每个元素
if _, ok := value.([]interface{}); ok {
for _, v := range value.([]interface{}) {
// 循环取map中的值
for j, s := range v.(map[string]interface{}) {
fmt.Printf("%s: %v\n", j, s)

}

}
}

}

}

} else {

resp, err := http.Get("http://127.0.0.1:8000/go")
if err != nil {
fmt.Println("连接服务器失败,error", err)
return
}
defer resp.Body.Close()
fmt.Println("code:", resp.StatusCode)
buf := make([]byte, 1024)
for {
// 接受服务器信息
n, err := resp.Body.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("读取服务器数据失败,error:", err)
return
}
fmt.Println("读取服务器数据成功")
res := string(buf[:n])
fmt.Println(res)
break
}
}
}

func main() {

start("post")

}

  • 运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
S E:\proj\gowork> go run .\hello_work\http\client.go
读取服务器数据成功 map[Code:0 Data:[map[Age:18 Name:li] map[Age:18 Name:li] map[Age:18 Name:li]] Msg:success Param:1]
Code: 0
Param: 1
Msg: success
Data: [map[Age:18 Name:li] map[Age:18 Name:li] map[Age:18 Name:li]]
Name: li
Age: 18
Name: li
Age: 18
Name: li
Age: 18

PS E:\proj\gowork> go run .\hello_work\http\server.go
key: ReqType => value : 1
key: Username => value : test1
key: Pwd => value : 123456
key: ReqType => value : 1

总结

  • 在处理json这种数据结构时,感觉go还是比较复杂,还是python用起来更舒服

  • 网络编程

环境搭建

  • 打开官网,下载win平台下的msi安装文件,环境变量设置E:\app\Go\bin

  • 本地新建目录,E:\proj\gowork作为工作空间

  • 初始化项目

1
2
3
4
5
6
7
8
9
cd E:\proj\gowork
mkdir hello_work
cd hello_work
go mod init example.com/hello_work

$ go mod init example.com/hello_work
go: creating new go.mod: module example.com/hello_work
go: to add module requirements and sums:
go mod tidy

go mod tidy 执行后,把当前项目中不需要用的依赖文件删除,也就是go.mod中添加的依赖文件

  • 当前最新的工作目录如下
1
2
3
4
Administrator@WIN-5TF67LA12I4 MINGW64 /e/proj/gowork
$ ls
go.mod hello_work/

  • 下载并打开Visual Studio Code,作为开发go的工具,扩展中安装GO
  • 按快捷键ctrl+shift+x,打开扩展,安装GO和chinese

image-20230525113031586

  • 安装go tools的依赖插件
1
2
go env -w GO111MODULE=on
go env -w GOPROXY=https://proxy.golang.com.cn,direct
  • Ctrl+Shift+P 然后搜索>Go:Install/Update Tools 然后勾选全部项目即可。

image-20230525114744401

实践

  • vscode 打开E:\proj\gowork目录
  • 在终端中拉取远程的依赖项目代码:go get github.com/georgehao/gomodtestc
  • 拉取远程代码成功后,go.mod文件中生产的代码如下
1
2
3
4
5
6
7
8
module example.com/hello_work

go 1.20

require (
github.com/georgehao/gomodtestc v1.0.1 // indirect
)

源码分析

image-20230531170143420

  • hello_wrok/main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"

"example.com/hello_work/hello_work/util" // 引用本地其他目录下的文件
"github.com/georgehao/gomodtestc" // 引用远程项目地址
)

func main() {
fmt.Println(gomodtestc.PrintStr("Hello", 100)) // 调用远程项目的代码
Print_hello() // 相同文件夹内的函数直接调用,来自于代码hello.go
util.Print_work() //util是包名
}

在编写代码中,比如输入Print_hello,import自动导入依赖包

  • hello_work/hello.go
1
2
3
4
5
6
7
8
package main

import "fmt"

func Print_hello() {
fmt.Println("Print_hello!")
}

  • hello_work/util/work.go
1
2
3
4
5
6
7
8
9
10
11
12
package util

import "fmt"

func init() {
fmt.Println("imp-init() come here.")
}

func Print_work() {
fmt.Println("Hello!")
}

  • 执行命令,如下go run 执行了两个go文件,因为不这样执行无法调用到hello.go中的函数,理论上根目录下只有一个main.go函数,其他函数写到其他目录下
1
2
3
4
5
PS E:\proj\gowork> go run .\hello_work\main.go .\hello_work\hello.go
imp-init() come here.
project C Hello_100
Print_hello!
Hello!

基础语法

总结

常用组件汇总

  • 看如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from tkinter import *
from tkinter import messagebox

root = Tk()
root.title('我的gui程序')
root.geometry('500x300+100+200')
# 500宽度 300高度 距屏幕左侧100像素 顶部200像素
bt = Button(root)
bt['text'] = '点我'
bt.pack()


def click(e):
print(e)
messagebox.showinfo('message', 'give flower') # 提示框

bt.bind('<Button-1>', click) # 绑定点击事件
root.mainloop() # 进入事件循环
  • 查看root = Tk(),查看源码,发现其实就是实例化class Tk(Misc, Wm),发现注释中写明了是Base class,经过搜索发现了6个基类,这6个基类包含了常见的组件

image-20230517161613275

  • 组件汇总如下

image-20230517161741836

image-20230517161833878

面向对象写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from tkinter import *
from tkinter import messagebox


class Application(Frame):
"""
继承Frame容器类
"""
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()

self.createWidget()

def createWidget(self):
# 创建组件
self.btn01 = Button(self)
self.btn01['text'] = '点击送花'
self.btn01.pack()
self.btn01['command'] = self.songhua
# 创建一个退出按钮
self.btnQuit = Button(self, text='退出', command=root.destroy)
self.btnQuit.pack()

def songhua(self):
messagebox.showinfo('送花', '送你99朵玫瑰花')


root = Tk()
root.geometry('400x100+200+300')
root.title('一个经典的GUI程序类的测试')
app = Application(master=root)
root.mainloop()

常见组件用法

label

  • 主要用于显示文本信息,也可以显示图像,常见属性如下

  • width,height
    用于指定区域大小,如果显示是文本,则以单个英文字符大小为单位(一个汉字宽度占 2 个字符位置,高度和英文字符一样);如果显示是图像,则以像素为单位。默认值是根据具体显示的内容动态调整。

  • font
    指定字体和字体大小,如:font =(font_name,size)

  • image
    显示在 Label 上的图像,目前 tkinter 只支持 gif 格式。

  • fgbg
    fg(foreground):前景色、bg(background):背景色

  • justify
    针对多行文字的对齐,可设置 justify 属性,可选值”left” “center” “right”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from tkinter import *


class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
# 创建组件
self.label01 = Label(self, text='关关雎鸠', width=10, height=2,
bg='black', fg='white')
self.label01.pack() # self.label01.pack(side=TOP)
self.label02 = Label(self, text='hebut', width=10, height=2,
bg='blue', fg='white', font=('黑体', 30))
self.label02.pack()
# 显示图像
global photo
# photo = PhotoImage(file='pic01.gif')
# self.label03 = Label(self, image=photo)
# self.label03.pack()
# 显示多行文本
self.label04 = Label(self, text='hebut\n关关雎鸠', borderwidth=1, relief='groove', justify='right')
self.label04.pack()


if __name__ == '__main__':
root = Tk()
root.geometry('400x500+200+300')
app = Application(master=root)
root.mainloop()

上述例子中,注意pack()的用法,是tkinter中一种布局管理的方法,文章后面介绍了三种布局

image-20230518093353160

options

我们可以通过 Options 设置组件的属性,从而控制组件的各种状态。比如:宽度、高度、颜色、位置等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
fred = Label(self, text='什么?', fg="red", bg="blue")
fred["fg"] = "red"
fred["bg"] = "blue"
fred.config(fg="white", bg="blue")
fred.pack()

self.btn01 = Button(self, text="点击送花,变色")
# self.btn01.pack()
self.btn01["command"] = self.click()
def click(self):
messagebox.showinfo("送花","送你99朵花")
self.btn01.config(fg="white", bg="black")
print(self.btn01.config()) # 查看 Options 选项的方法

print(self.btn01.config()) 可查看到options中的常用方法

Button-Entry

  • Button(按钮)用来执行用户的单击操作。Button 可以包含文本,也可以包含图像。按钮被单击后会自动调用对应事件绑定的方法。
  • Entry,是单行文本框
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from tkinter import *
from tkinter import messagebox


class Application(Frame):
def __init__(self, master=None): # super()代表的是父类的定义,而不是父类对象
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
# 创建登录界面的组件
self.label01 = Label(self, text="用户名")
self.label01.pack()

# StringVar 变量绑定到指定的组件。
# StringVar 变量的值发生变化,组件内容也变化;
# 组件内容发生变化,StringVar 变量的值也发生变化。 v1 = StringVar()
v1 = StringVar()
self.entry01 = Entry(self, textvariable=v1)
self.entry01.pack()
v1.set("admin")
print(v1.get())
print(self.entry01.get())

# 创建密码框
self.label02 = Label(self, text="密码")
self.label02.pack()

v2 = StringVar()
self.entry02 = Entry(self, textvariable=v2, show="*")
self.entry02.pack()

Button(self, text="登陆", command=self.login).pack()

def login(self):
username = self.entry01.get()
pwd = self.entry02.get()
print("去数据库比对用户名和密码!")
print("用户名:" + username)
print("密码:" + pwd)
if username == "关关雎鸠" and pwd == "123456":
messagebox.showinfo("学习系统", "登录成功!欢迎开始学习!")
else:
messagebox.showinfo("学习系统", "登录失败!用户名或密码错误!")


if __name__ == '__main__':
root = Tk()
root.geometry("400x130+200+300")
app = Application(master=root)
root.mainloop()

  • 常见的组件比如Text多行文本框、Radiobutton 单选按钮、Checkbutton 复选按钮、canvas 画布等不做演示

布局管理

tkinter 提供了三种管理器:pack、grid、place

grid 布局管理器

grid 表格布局,采用表格结构组织组件。子组件的位置由行和列的单元格来确定,并且可以跨行和跨列,从而实现复杂的布局。

image-20230518094805637

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 测试 Grid 布局管理器的基本用法,使用面向对象的方式
from tkinter import *

class Application(Frame):
def __init__(self, master=None):
# super()代表的是父类的定义,而不是父类对象
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
# 通过 grid 布局实现登录界面
Label(self,text="用户名").grid(row=0,column=0)
Entry(self).grid(row=0,column=1)
Label(self,text="用户名为手机号").grid(row=0,column=2)
Label(self, text="密码").grid(row=1, column=0)
Entry(self, show="*").grid(row=1, column=1)
Button(self, text="登录").grid(row=2, column=1, sticky=EW)
Button(self, text="取消").grid(row=2, column=2, sticky=EW)

if __name__ == '__main__':
root = Tk()
root.geometry("400x90+200+300")
app = Application(master=root)
root.mainloop()

image-20230518100007136

pack布局

  • 三种布局中最简单的
  • pack 按照组件的创建顺序将子组件添加到父组件中,按照垂直或者水平的方向自然排布。如果不指定任何选项,默认在父组件中自顶向下垂直添加组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 测试 pack 布局管理
from tkinter import *

root = Tk()
root.geometry("700x220")
# Frame 是一个矩形区域,就是用来防止其他子组件
f1 = Frame(root)
f1.pack()
f2 = Frame(root)
f2.pack()
btnText = ("流行风", "中国风", "日本风", "重金属", "轻音乐")
for txt in btnText:
Button(f1, text=txt).pack(side="left", padx="10")
for i in range(1, 20):
Button(f2, width=5, height=10, bg="black" if i % 2 == 0 else "white").pack(side="left")
root.mainloop()

  • 常用方法

image-20230518113530055

place 布局管理器

place 布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from tkinter import *

root= Tk()
root.geometry("500x300")
root.title("布局管理 place")
root["bg"]="white"


f1= Frame(root,width=200,height=200,bg="green")
f1.place(x=30,y=30)
Button(root,text="hebut").place(relx=0.5,rely=0,x=100,y=200,relwidth=0.2,relheight=0.2)
Button(f1,text="programmer").place(relx=0.6,rely=0.7)
Button(f1,text="关关雎鸠").place(relx=0.2,rely=0.2)

root.mainloop()

image-20230518114625901

打包

  • 源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

from tkinter import *
from tkinter import messagebox
import requests

class Application(Frame):
def __init__(self, master=None): # super()代表的是父类的定义,而不是父类对象
super().__init__(master)
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
# 创建登录界面的组件
self.label01 = Label(self, text="用户名")
self.label01.pack()

# StringVar 变量绑定到指定的组件。
# StringVar 变量的值发生变化,组件内容也变化;
# 组件内容发生变化,StringVar 变量的值也发生变化。 v1 = StringVar()
v1 = StringVar()
self.entry01 = Entry(self, textvariable=v1)
self.entry01.pack()
v1.set("admin")
print(v1.get())
print(self.entry01.get())

# 创建密码框
self.label02 = Label(self, text="密码")
self.label02.pack()

v2 = StringVar()
self.entry02 = Entry(self, textvariable=v2, show="*")
self.entry02.pack()

Button(self, text="登陆", command=self.login).pack()

def login(self):
username = self.entry01.get()
pwd = self.entry02.get()
print("去数据库比对用户名和密码!")
print("用户名:" + username)
print("密码:" + pwd)
if username == "admin" and pwd == "123456":
messagebox.showinfo("学习系统", "登录成功!欢迎开始学习!")
res = requests.get("http://www.baidu.com")
print(res.status_code)
else:
messagebox.showinfo("学习系统", "登录失败!用户名或密码错误!")


if __name__ == '__main__':
root = Tk()
root.geometry("400x130+200+300")
app = Application(master=root)
root.mainloop()

  • 安装打包依赖文件
1
pip install pyinstaller  -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 打包
1
D:\project\studyTK>pyinstaller -F main.py
  • 执行命令完成后,在dist目录就发现了打包好的exe文件,运行即可

image-20230518151216662

迭代器

  • 内部含有_iter_方法和_next_方法都是迭代器
    • 可迭代对象 都可以通过内置函数 iter转换为迭代器
    • 内置函数 next 进行迭代操作,当所有数据迭代完毕后,再使用 next 迭代,会抛出异常 StopIteration
  • 使用for循环容器取值的都是可迭代的

容器就是将多个元素在一起的单元,并且,是可以迭代的,比如列表,字典,元组,字符串

  • 如下代码
1
2
3
4
5
6
7
8
# 将列表转换为一个迭代器
iter_li = iter([11,22)
# 通过next对迭代器进行迭代操作,每次可以迭代出来一个数据
s1 = next(iter_li)
print('s1:',s1)
s2 = next(iter_li) # s1 :11
print('s2:',s2) # s2 :22
s3 = next(iter_li) # StopIteration 错误

生成器

  • 是一种特殊的迭代器,具备迭代器所有的特性。但其相比与迭代器,占用的内存更少

  • 他本质上是一个函数,只不过函数的return改为了yield语句,正常的for循环语句,会不停的遍历容器的对象,然后返回(如果有return的话),当我们用yield取而代之

  • 每当函数执行时遇到yield时,他会记住此时的一个位置并挂起,等到使用__next__操作时,才由上一次挂起的位置继续执行。生成器不仅“记住”了它的数据状态,生成还记住了程序执行的位置

  • python 中定义生成器,一共有两种方式,一种是生成器表达式,另一种是生成器函数。

生成器表达式

生成器表达式的语法其实就是把列表推导式的中括号改成小括号,如下:

1
2
3
4
5
6
7
gen_ =(item for item in range(2))
print(gen_)
s1 = next(gen_)
print(s1)
next(gen_)
next(gen_) # 报错StopIteration

生成器函数

在函数中使用 yield关键字可以定义一个生成器函数

1
2
3
4
5
6
7
8
9
10
def func():
for i in range(1):
yield i

gen_list = func()
# 调用函数
gen_lsit = func()
s2 = next(gen_list)
print(s2)
next(gen_list) # 报错StopIteration

自定义类生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from collections import Iterable
from collections import Iterator
import time


class Classmate(object):
"""定义一个同学类"""

def __init__(self):
self.name = list()
self.name_num = 0

def add(self, name):
self.name.append(name)

def __iter__(self):
return self # 返回本身

def __next__(self):
if self.name_num < len(self.name):
ret = self.name[self.name_num]
self.name_num += 1
return ret

# 抛出异常,当循环后自动结束
else:
print("-------StopIteration---")
raise StopIteration


class1 = Classmate()
class1.add("张三")
class1.add("李四")
class1.add("王五")

print("判断是否是可迭代的对象:", isinstance(class1, Iterable))

print("判断是否是迭代器:", isinstance(class1, Iterator))

for name in class1:
print(name)
time.sleep(1)

# 结果为
判断是否是可迭代的对象: True
判断是否是迭代器: True
张三
李四
王五
-------StopIteration---

总结两者的区别

  • 生成器就是迭代器,反之则不是
  • 迭代器,在创建的时候已经生成(比如上面迭代器的列子),在需要的时候再去操作迭代器加载元素到内存中
  • 而生成器,本质是一个可以暂时挂起的函数,等到需要时再继续执行去生成元素,这样子,使得生成器比迭代器更省内存。

Dependency-Check

  • 使用 “存在已知漏洞的组件” 已经成为OWASP TOP 10的漏洞之一了,他是一个开源的程序,主要用于识别项目依赖项并检查是否存在已知的,公开披露的漏洞,目前支持Java.NETRubyNode.jsPython等语言。
  • 主要功能是对jar依赖包进行扫描。他的简单工作原理是依靠强大的库,与被扫jar依赖包进行比对,输出jar包详情。所以该工具只能扫描出已经公布的,无法扫描0day

工作原理

  • Dependency-Check工作的方式是通过分析器对文件进行扫描搜集信息,搜集到的信息被叫做迹象。
  • 这边共搜集3种迹象,分时是vendor(供应商),product(产品)和version(版本)。例如,jarAnalyzer将从jar文件包中的Mainfestpom.xml和包名进行信息搜集,然后把各种搜集到的源放到一个或者多个迹象表里。
  • 通过搜集到的迹象和CPE条目(NVD 美国国家通用数据库、CVE数据索引)进行匹配,分析器匹配到了就会给个标志发送到报告。
  • Dependency-Check 目前不使用hash识别文件,因为第三方依赖从源码中的hash值构建通常不会匹配官方发布版本的hash。后续版本中可能会增加一些hash来匹配一些常用的第三方库,例如Spring, Struts等。

使用

  • 本地环境:win10 x64

  • 打开官网,点击command Line下载文件,并解压

image-20230324164847298

  • 进入到bin目录
1
D:\app\dependency-check-8.2.1-release\dependency-check\bin>
  • 常用参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  --advancedHelp              Print the advanced help message.
--enableExperimental Enables the experimental analyzers.
--exclude <pattern> 指定一个排除模式。这个选项可以多次指定,它接受Ant风格的排除”
-f,--format <format> The report format (HTML, XML, CSV, JSON,
JUNIT, SARIF, JENKINS, or ALL). The
default is HTML. Multiple format
parameters can be specified.
--failOnCVSS <score> 指定如果识别到高于指定级别的CVSS评分,是否应该使构建失败。
默认值是11;因为CVSS评分是0-10,所以默认情况下构建永远不会失败

-h,--help Print this message.
--junitFailOnCVSS <score> 指定在生成junit报告时被视为失败的CVSS评分。默认值是0
-l,--log <file> The file path to write verbose logging
information.
-n,--noupdate 禁用NVD-CVE,hosted-suppressions和RetireJS数据的自动更新
-o,--out <path> 指定报告目录.
--prettyPrint When specified the JSON and XML report
formats will be pretty printed.
--project <name> The name of the project being scanned.
-s,--scan <path> 指定被扫描的jar文件目录
recommended to quote the argument value.
--suppression <file> The file path to the suppression XML file.
This can be specified more then once to
utilize multiple suppression files
-v,--version Print the version information.

  • CVE
  • CVSS评分标准: 漏洞的最终得分最大为10,最小为0。得分710的漏洞通常被认为比较严重,得分在46.9之间的是中级漏洞,0~3.9的则是低级漏洞。
  • 扫描本地jar,远程更新这里的漏洞数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dependency-check.bat --disableRetireJS --disableNodeJS -s  D:\project\checkjar  -o D:\project\report

[INFO] NVD CVE requires several updates; this could take a couple of minutes.
[INFO] Download Started for NVD CVE - 2002
[INFO] Download Complete for NVD CVE - 2002 (4454 ms)
[INFO] Processing Started for NVD CVE - 2002
[INFO] Download Started for NVD CVE - 2003
[INFO] Processing Complete for NVD CVE - 2002 (6277 ms)
[INFO] Download Complete for NVD CVE - 2003 (2566 ms)
[INFO] Processing Started for NVD CVE - 2003
[INFO] Processing Complete for NVD CVE - 2003 (1376 ms)
...
INFO] Finished Dependency Bundling Analyzer (0 seconds)
[INFO] Finished Unused Suppression Rule Analyzer (0 seconds)
[INFO] Analysis Complete (8 seconds)
[INFO] Writing report to: D:\project\report\dependency-check-report.html

报告分析

  • 查看这里的测试报告
1
D:\project\report\dependency-check-report.html
  • 比如log4j-core-2.16.0.jar 发现两个中等漏洞

image-20230325101328304

  • 当配置使用JDBC AppenderJNDI LDAP数据源URI时,Apache Log4j2版本2.0-beta72.17.0(不包括安全修复版本2.3.2和2.12.4)容易受到远程代码执行(RCE)攻击,如果攻击者控制了目标LDAP服务器。这个问题通过将JNDI数据源名称限制为java协议在Log4j2版本2.17.1,2.12.42.3.2中得到修复

image-20230325101710088

离线审计

  • 这里没有实践
  • 当然了,如果你是离线审计,也可以将NVD库搭建到本地,这样就会更加的方便,我们可以在本地搭建一个NVD库来提高更新效率,具体可以参考这里
  • 具体命令如下,其中cveUrlModifiedcveUrlModified指定本地NVD
1
2
3
4
dependency-check.bat
--cveUrlModified 本地nvd库的url/nvdcve-1.1-modified.json.gz
--cveUrlBase本地nvd库的url/nvdcve-1.1-2020.json.gz
--project test -s D:\checkjar\ -o D:\report\

集成

  • 此处没有实践,转载这里

与maven集成

  • Dependency-check-maven非常易于使用,可以作为独立插件使用,也可以作为maven site的一部分使用。该插件需要使用Maven 3.1或更高版本,第一次执行时,可能需要20分钟或更长时间,因为它会从NIST托管的国家漏洞数据库下载漏洞数据到本地备份库。第一次批量下载后,只要插件每七天至少执行一次,本地漏洞库就会自动更新,更新只需几秒钟。
  • 集成很简单,只需要在项目的pom文件中增加maven配置即可。

用法一 在target目录中创建dependency-check-report.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>4.0.2</version>
<configuration>
<autoUpdate>true</autoUpdate>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>

用法二 在maven site中创建聚合性的报告

1
2
3
4
5
6
7
8
9
10
11
12
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>4.0.2</version>
<reportSets>
<reportSet>
<reports>
<report>aggregate</report>
</reports>
</reportSet>
</reportSets>
</plugin>

用法三 设置当风险指数(CVSS)大于等于8时(CVSS分数为0-10)则项目编译失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>4.0.2</version>
<configuration>
<failBuildOnCVSS>8</failBuildOnCVSS>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>

用法四 仅更新NVD(漏洞库)数据,而不执行检查

1
2
3
4
5
6
7
8
9
10
11
12
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>4.0.2</version>
<executions>
<execution>
<goals>
<goal>update-only</goal>
</goals>
</execution>
</executions>
</plugin>

更多配置信息,可以根据实际情况在官网查找:参考链接

与Jenkins集成

  • Jenkins中需要安装插件:Static Analysis UtilitiesDependency-Check
    该插件具有执行依赖关系分析和构建后查看检查结果的功能。
    执行依赖分析配置:
    image-20230325103550702

  • 查看检查分析结果配置:

image-20230325103620374

SonarQube 7.x集成

  • 与代码质量管理平台SonarQube 7.x以上的版本集成

  • 将插件(jar文件)复制到$SONAR_INSTALL_DIR/extensions/plugins并重新启动SonarQube
    但需要添加以下配置:

1
2
3
4
sonar.dependencyCheck.reportPath = ${WORKSPACE}/dependency-check-report.xml
##以Jenkins为例报告.xml路径
sonar.dependencyCheck.htmlReportPath = ${WORKSPACE}/dependency-check-report.html
##以Jenkins为例报告.html路径
  • 问题严重性分数设定:
1
2
3
4
sonar.dependencyCheck.severity.blocker = 9.0
sonar.dependencyCheck.severity.critical = 7.0
sonar.dependencyCheck.severity.major = 4.0
sonar.dependencyCheck.severity.minor = 0.0

报告查看

image-20230325103731194

image-20230325103754549

其他

  • 扫描python
  • 测试报告不好看,有能力的话,可以基于开源的代码进行修改

说明

本机环境,python3.7 ,win10 64

操作sqlite

  • 新建一个空的后缀名文件sample.db

  • 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import time
import sqlite3

#打开数据库
def open_db():
#创建SQLite数据库
con=sqlite3.connect(r"D:\project\pythonSql\sample.db")
#创建表book:包含3列,id(主键,学号),name,tel
# con.execute("create table if not exists book(id primary key,name,tel)")
con.execute("create table if not exists book(id INTEGER PRIMARY KEY, name TEXT, tel TEXT, timer TEXT);")

#创建游标对象
cur=con.cursor()
# 涉及到修改,删除等入库的操作必须用con.commit()提交事务最终完成
# cur 主要是进行查询
return con,cur

#查询全部信息
def show_all_db():
print("******通讯录现有数据******")
cur_1=open_db()[1]
cur_1.execute("select id,name,tel from book")
for row in cur_1:
print(row)
print(row[0])



#向数据库中添加内容
def add_db(name,tel):
print("******数据添加功能******")
cur_1=open_db()
# cur_1[1].execute("insert into book(id,name,tel) values(?,?,?)",(id,name,tel))
cur_1[1].execute("insert into book(name,tel) values(?,?)",(name,tel))
cur_1[0].commit()
print("******数据添加成功******")

#删除数据库中的内容
def delete_db(del_id):
print("******数据删除功能******")
cur_1=open_db()
cur_1[1].execute("delete from book where id="+del_id)
cur_1[0].commit()
print("******数据删除成功******")
show_all_db()
#关闭游标对象
cur_1[1].close()

#修改数据库中的内容
def alter_db(id,name,tel):
print("******数据修改功能******")
cur_1=open_db()
#更新数据使用 SQL 语句中的 update
cur_1[1].execute("update book set name = ? ,tel = ? where id ="+id,(name,tel))
#游标事务提交
cur_1[0].commit()
show_all_db()
cur_1[1].close()

#查询数据
def query_data(id):

print("******数据查询功能******")
cur_1=open_db()
cur_1[1].execute("select id,name,tel from book where id ="+id)
print("******查询结果如下******")
for row in cur_1[1]:
print(row)
cur_1[1].close()



if __name__=="__main__":
# add_db("你好", "185111")
# show_all_db()
# query_data("1")
# alter_db("1", "我好", "hehe")
# query_data("1")
# delete_db("1")
show_all_db()

  • 使用工具打开sqlitespy打开sqlite文件

image-20221209174201531

操作mysql

  • 本地搭建的mysql环境,省略
  • 使用HeidiSQL,连接数据库,创建表

image-20221209174414824

  • 设置自增主键

image-20221209174525798

  • 操作代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import pymysql

dbinfo = {
"host":"127.0.0.1",
"user":"root",
"password":"123456",
"port":3306
}

class DB:
def __init__(self,dbinfo):
# self.db 只要修改表,必须用self.db.commit()进行事务提交,如果报错可以用self.db.rollback()进行回滚
self.db = pymysql.connect(cursorclass = pymysql.cursors.DictCursor,
**dbinfo)
# self.cursor 用来查询
self.cursor = self.db.cursor()

def select(self,sql):
self.cursor.execute(sql)
result = self.cursor.fetchall()
return result

def execute(self,sql):

self.cursor.execute(sql)
self.db.commit()

# self.db.rollback()
# try:
# self.cursor.execute(sql)
# self.db.commit()
# except:
# self.db.rollback()

def close(self):
self.cursor.close()
self.db.close()

if __name__ == '__main__':
db = DB(dbinfo)
#查询
sql = "SELECT * from test.class"
result = db.select(sql)
print(result)

#新增
# sql2 = "INSERT into test.class(name) VALUES ('五年一班')"
# db.execute(sql2)
# sql = "SELECT * from test.class"
# result = db.select(sql)
# print(result)

# 修改
# sql2 = "update test.class set name='综艺一般' where id=6"
# db.execute(sql2)
# sql = "SELECT * from test.class"
# result = db.select(sql)
# print(result)

# # 删除
# sql2 = "delete from test.class where id=5"
# db.execute(sql2)
# sql = "SELECT * from test.class"
# result = db.select(sql)
# print(result)

Django mysql

  • django安装pip install Django

  • 创建项目及应用

1
2
3
4
5
# 创建应用
D:\project>django-admin startproject pysql
D:\project>cd pysql
# 创建应用下的一个项目
D:\project\mysite> python manage.py startapp myapi
  • django 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# pysql\pysql\setting.py
import pymysql
pymysql.version_info = (1, 4, 13, "final", 0) #指定版本
pymysql.install_as_MySQLdb()



INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapi' # 注册应用
]


// 设置默认数据库为mysql
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'test', # 数据库名字
'USER': 'root', # 帐号
'PASSWORD': '123456', # 密码
'HOST': '127.0.0.1', # IP
'PORT': '3306', # 端口
}
}
  • 设置model中的表字段
1
2
3
4
5
6
7
8
9
10
11
12
#pysql\myapi\model.py
from django.db import models


class class1(models.Model):
id = models.IntegerField().primary_key
name = models.CharField(max_length=128, unique=True)
tel = models.CharField(max_length=256)
c_time = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.name
  • 让model中的表结构在数据库中生成
1
2
python manage.py makemigrations # 让model生效
python manage.py migrate # 生成表
  • 编写views中代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# pysql/myapi/views.py
import json

from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt

from myapi.models import *

def query_all(request):
res = Class1.objects.all()
resp = []
for i in res:
resp.append({"id": i.id, "name": i.name, "tel": i.tel})
return JsonResponse({"code": 1, "msg": "success", "data": resp})

@csrf_exempt
def delete(request):
data = json.loads(request.body)
id = data.get("id")
if not id:
return JsonResponse({"code": -1, "msg": "id must be fill"})
try:
entry = Class1.objects.get(id=id)
if not entry:
return JsonResponse({"code": -1, "msg": "no effect row"})
entry.delete()
return JsonResponse({"code": 1, "msg": "success"})

except ObjectDoesNotExist:
return JsonResponse({"code": -1, "msg": "no effect row"})

@csrf_exempt
def update(request):
data = json.loads(request.body)
name = data.get('name').strip()
tel = data.get('tel').strip()
ids = data.get("id")
if not name and not ids:
return JsonResponse({"code": -1, "msg": "error"})
try:
entry = Class1.objects.get(id=ids)
if not entry:
return JsonResponse({"code": -1, "msg": "no effect row"})
entry.tel = tel
entry.name = name
entry.save()

return JsonResponse({"code": 1, "msg": "success"})

except ObjectDoesNotExist:
return JsonResponse({"code": -1, "msg": "no effect row"})


def get_class(request, id):
try:
# entry = Class1.objects.filter(id=id)
entry = Class1.objects.get(id=id)
if not entry:
result = {'code': -1, 'msg': 'data is null'}
return JsonResponse(result)
return JsonResponse({"code": 1, "msg": "success", "data": {"name": entry.name, "tel": entry.tel}})

except ObjectDoesNotExist:
result = {'code': -1, 'msg': 'no effect row'}
return JsonResponse(result)

@csrf_exempt
def add(request):
data = json.loads(request.body)
name = data.get("name")
tel = data.get("tel")
if not name or not tel:
res = {'code': -1, 'msg': 'name,tel must be fill'}
return JsonResponse(res)
Class1(name=name, tel=tel).save()
res = {'code': 1, 'msg': 'success'}
return JsonResponse(res)


  • 把views中的对外接口引用到url中
1
2
3
4
5
6
7
8
9
10
11
12
13
# pysql/myapi/urls.py

from django.conf.urls import url
from django.urls import path
from myapi import views

urlpatterns = [
url(r'^query_all/', views.query_all),
url(r'^delete/', views.delete),
url(r'^update/', views.update),
url(r'^add/', views.add),
path('get_class/<int:id>/', views.get_class)
]

把myapi应用的路由url引用到pysql项目中

1
2
3
4
5
6
7
8
9
10
11
12
13
#pysql/pysql/urls.py
from django.conf.urls import url
from django.urls import path

from myapi import views

urlpatterns = [
url(r'^query_all/', views.query_all),
url(r'^delete/', views.delete),
url(r'^update/', views.update),
url(r'^add/', views.add),
path('get_class/<int:id>/', views.get_class)
]
  • 运行Django
1
2
3
4
5
6
7
8
9
10
11
D:\project\pysql>python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
December 09, 2022 - 19:15:56
Django version 3.1.3, using settings 'pysql.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.


  • 模拟前端访问后台接口代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

import requests

resp = requests.post("http://127.0.0.1:8000/myapi/add/", json={"name": "admin1", "tel": "123456"})
print(resp.text)
resp1 = requests.post("http://127.0.0.1:8000/myapi/update/", json={"id": 1,"name": "test", "tel": "7999"})
print(resp1.text)
resp2= requests.post("http://127.0.0.1:8000/myapi/update/", json={"id": 1,"name": "test", "tel": "1111"})
print(resp2.text)
resp3 = requests.post("http://127.0.0.1:8000/myapi/delete/", json={"id": 1})
print(resp3.text)

resp4 = requests.get("http://127.0.0.1:8000/myapi/get_class/1/")
print(resp4.text)

resp4 = requests.get("http://127.0.0.1:8000/myapi/query_all")
print(resp4.text)

  • 得到结果为:
1
2
D:\app\Python37\python.exe D:/project/pysql/test1.py
{"code": 1, "msg": "success", "data": [{"id": 5, "name": "admin1", "tel": "123456"}]}

Django sqlite

  • 由于接入mysql,配置已经改的差不多,只要修改,setting.py的默认数据库引用
1
2
3
4
5
6
7
8
# pysql/pysql/setting.py

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
  • 在pysql文件夹下新建一个空文件db.sqlite3
  • 让model的结构迁移到sqlite3数据库中
1
2
python manage.py makemigrations # 让model生效
python manage.py migrate # 生成表
  • 再次运行django
1
D:\project\pysql>python manage.py runserver
  • 模拟前端访问后台接口代码
1
2
3
4
import requests

resp = requests.post("http://127.0.0.1:8000/myapi/add/", json={"name": "admin1", "tel": "123456"})
print(resp.text)

image-20221209200159234

概述

  • 本文来自这里

  • mitmproxy 相比Charles、fiddler的优点在于,它可以命令行方式或脚本的方式进行mock mitmproxy不仅可以像Charles那样抓包,还可以对请求数据进行二次开发,进入高度二次定制

安装

  • windows下安装,我本机的python版本为3.7
1
pip install mitmproxy
  • 完成后,系统将拥有 mitmproxymitmdumpmitmweb 三个命令,由于 mitmproxy 命令不支持在 windows 系统中运行(这没关系,不用担心),我们可以拿 mitmdump 测试一下安装是否成功,执行:
1
2
3
4
5
6
7
8
C:\Users\Administrator>mitmdump --version
Mitmproxy: 5.3.0
Python: 3.7.9
OpenSSL: OpenSSL 1.1.1h 22 Sep 2020
Platform: Windows-10-10.0.19041-SP0

C:\Users\Administrator>

  • 要启动 mitmproxymitmproxymitmdumpmitmweb 这三个命令中的任意一个即可,这三个命令功能一致,且都可以加载自定义脚本,唯一的区别是交互界面的不同。

生命周期

原文中介绍了HTTPTCP,Websocket 的生命周期,因为用http比较多,只需要用到 http_connectrequestresponse 三个事件就能完成大多数需求了。

实战

  • 启动mitmproxy
1
mitmweb
  • cmd中启动chrome(启动前,需要先关闭所有chrome)
1
"C:\Users\Administrator\AppData\Local\Google\Chrome\Application\chrome.exe " --proxy-server=127.0.0.1:8080 --ignore-certificate-errors

新增三个代码文件分别为:

  • counter.py,这里代码比较简单,就是发送请求前打印信息
1
2
3
4
5
6
7
8
9
10
11
12
13
# counter.py

import mitmproxy.http
from mitmproxy import ctx


class Counter:
def __init__(self):
self.num = 0

def request(self, flow: mitmproxy.http.HTTPFlow):
self.num = self.num + 1
ctx.log.info("We've seen %d flows" % self.num)
  • joker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import mitmproxy.http
from mitmproxy import ctx, http


class Joker:
def request(self, flow: mitmproxy.http.HTTPFlow):
"""
对百度的搜索发送请求进行拦截,对wd参数修改为360搜索
:param flow:
:return:
"""
if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):
return
# for i in flow.request.query.keys():
# print(i)
# 抓取请求参数可以看到wd传的是百度搜索的具体内容
if "wd" not in flow.request.query.keys():
ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)
return

ctx.log.info("catch search word: %s" % flow.request.query.get("wd"))
flow.request.query.set_all("wd", ["360搜索"])

def response(self, flow: mitmproxy.http.HTTPFlow):
"""
点击百度搜索中的so搜索后,把当期网页标题修改
:param flow:
:return:
"""
if flow.request.host != "www.so.com":
return

text = flow.response.get_text()
# 网页标题修改
text = text.replace("搜索", "请使用谷歌")
flow.response.set_text(text)

def http_connect(self, flow: mitmproxy.http.HTTPFlow):
"""
连接的请求为google,则把状态码改为404
:param flow:
:return:
"""

if flow.request.host == "www.google.com":
flow.response = http.HTTPResponse.make(404)
# 打印是否设置成功
print(flow.response.status_code)
  • addons.py,这里代码比较简单,只是把自定义事件加载进来
1
2
3
4
5
6
7
import counter
import joker

addons = [
counter.Counter(),
joker.Joker(),
]
  • 运行mitmproxy
1
mitmweb -s addons.py
  • 输入你好,因为搜索关键字被替换为了so搜索,所以结果查到了so搜索

image-20221014183654649

  • 点击360搜索首页,标题被修改了

image-20221014184000637

image-20221014184051730