├── README.md ├── gosetdemo ├── README.md ├── bitset │ ├── bitset.go │ ├── bitset_test.go │ └── functions.go ├── go.mod ├── main.go ├── mapset │ └── mapset.go └── mode │ └── mode.go └── httpdemo ├── basic └── main.go ├── cookie ├── main.go └── server │ └── main.go ├── download └── main.go ├── go.mod ├── go.sum ├── post ├── NOTE.md ├── main.go ├── server │ ├── form.html │ ├── main.go │ └── poloxue.txt ├── uploadfile1 └── uploadfile2 ├── proxy └── main.go ├── redirect └── main.go ├── request └── main.go ├── response └── main.go └── timeout └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # Go 语言学习笔记(视频) 2 | 3 | 纯粹个人爱好,随机录制 Go 语言学习视频。目标是以一个个小系列逐步学习 Go 语言。 4 | 5 | 系列名称 | 视频地址 | 演示代码 6 | ------------------- | ----------- | --------- 7 | Go 中使用 set | [B 站](https://www.bilibili.com/video/av75078755) | [gosetdemo](./gosetdemo) 8 | Go 模块依赖可视化小工具 | [B 站](https://www.bilibili.com/video/av74105474) | [poloxue/modv](https://github.com/poloxue/modv) 9 | Go 如何发起 http 请求 | [B 站](https://www.bilibili.com/video/av79633877) | [httpdemo](./httpdemo) 10 | Go 如何灵活地处理 JSON | 待开始 | 暂无代码 11 | 12 | ## 说明 13 | 14 | 如果大家有任何的问题或想法欢迎提 issue,持续关注,欢迎 star。 15 | 16 | 目前还处在想到啥就录啥的状态,我会尽力为大家提供有价值的内容。 17 | 18 | 19 | ## 目录 20 | 21 | **如何使用 Set** 22 | 23 | - [概要介绍两种实现方式](https://www.bilibili.com/video/av75078755?p=1) 24 | - [为什么可以用 map 实现 set](https://www.bilibili.com/video/av75078755?p=2) 25 | - [编写 mapset 实现代码](https://www.bilibili.com/video/av75078755?p=3) 26 | - [bitset 的基本实现原理](https://www.bilibili.com/video/av75078755?p=4) 27 | - [标志位的使用](https://www.bilibili.com/video/av75078755?p=5) 28 | - [bitset 的准备了解](https://www.bilibili.com/video/av75078755?p=6) 29 | - [bitset 的增删查](https://www.bilibili.com/video/av75078755?p=7) 30 | - [bitset 的交并差](https://www.bilibili.com/video/av75078755?p=8) 31 | 32 | **Go 模块依赖关系可视化** 33 | 34 | - [为什么要开发这个小工具](https://www.bilibili.com/video/av74105474?p=1) 35 | - [如何实现的思路](https://www.bilibili.com/video/av74105474?p=2) 36 | - [处理流程搭建](https://www.bilibili.com/video/av74105474?p=3) 37 | - [模块关系解析](https://www.bilibili.com/video/av74105474?p=4) 38 | - [输出格式渲染](https://www.bilibili.com/video/av74105474?p=5) 39 | 40 | **Go 如何发起 http 请求** 41 | 42 | - [前奏说明](https://www.bilibili.com/video/av79633877?p=1) 43 | - [基本方法请求](https://www.bilibili.com/video/av79633877?p=2) 44 | - [request 设置](https://www.bilibili.com/video/av79633877?p=3) 45 | - [响应信息](https://www.bilibili.com/video/av79633877?p=4) 46 | - [响应编码信息](https://www.bilibili.com/video/av79633877?p=5) 47 | - [文件下载和进度显示](https://www.bilibili.com/video/av79633877?p=6) 48 | - [POST 提交 form 表单和 json](https://www.bilibili.com/video/av79633877?p=7) 49 | - [POST 上传文件](https://www.bilibili.com/video/av79633877?p=8) 50 | - [如何处理重定向](https://www.bilibili.com/video/av79633877?p=9) 51 | - [Cookie 处理](https://www.bilibili.com/video/av79633877?p=10) 52 | - [持久化 Cookie](https://www.bilibili.com/video/av79633877?p=11) 53 | - [超时和代理](https://www.bilibili.com/video/av79633877?p=11) 54 | 55 | net/http 客户端部分的基本使用,视情况考虑是否录制一些其他高级用法。 -------------------------------------------------------------------------------- /gosetdemo/README.md: -------------------------------------------------------------------------------- 1 | # go-set-example 2 | 介绍 Go 如何使用 set 的示例代码 3 | -------------------------------------------------------------------------------- /gosetdemo/bitset/bitset.go: -------------------------------------------------------------------------------- 1 | package bitset 2 | 3 | import "math/bits" 4 | 5 | // 状态标志位 元素个数固定 一个整数的就足以表示左右的状态 6 | // 集合场景 元素个数不确定 一个整数不足以表示 7 | type BitSet struct { 8 | data []uint64 9 | size int // 用于存放集合元素的个数 10 | } 11 | 12 | func NewBitSet(ns ...int) *BitSet { 13 | if len(ns) == 0 { 14 | return new(BitSet) 15 | } 16 | 17 | // 确定给 bitset 分配多大存放集合元素 18 | max := ns[0] 19 | for _, n := range ns { 20 | if n > max { 21 | max = n 22 | } 23 | } 24 | 25 | if max < 0 { 26 | return new(BitSet) 27 | } 28 | 29 | s := &BitSet{ 30 | data: make([]uint64, index(max)+1), 31 | } 32 | 33 | // 添加元素 34 | for _, n := range ns { 35 | if n >= 0 { 36 | s.data[index(n)] |= posVal(n) 37 | s.size++ 38 | } 39 | } 40 | 41 | return s 42 | } 43 | 44 | func (set *BitSet) Contains(n int) bool { 45 | // 应该存在什么位置 46 | i := index(n) 47 | if i >= len(set.data) { 48 | return false 49 | } 50 | 51 | return set.data[i]&posVal(n) != 0 52 | } 53 | 54 | func (set *BitSet) Clear(n int) *BitSet { 55 | if n < 0 { 56 | return set 57 | } 58 | 59 | i := index(n) 60 | if i >= len(set.data) { 61 | return set 62 | } 63 | 64 | if set.data[i]&posVal(n) != 0 { 65 | set.data[i] &^= posVal(n) 66 | set.size-- 67 | } 68 | 69 | return set 70 | } 71 | 72 | func (set *BitSet) Add(n int) *BitSet { 73 | if n < 0 { 74 | return set 75 | } 76 | 77 | i := index(n) 78 | if i >= len(set.data) { 79 | ndata := make([]uint64, i+1) 80 | copy(ndata, set.data) 81 | set.data = ndata 82 | } 83 | 84 | if set.data[i]&posVal(n) == 0 { 85 | set.data[i] |= posVal(n) 86 | } 87 | 88 | return set 89 | } 90 | 91 | func (set *BitSet) Size() int { 92 | return set.size 93 | } 94 | 95 | func (set *BitSet) Intersect(other *BitSet) *BitSet { 96 | minLen := min(len(set.data), len(other.data)) 97 | 98 | intersectSet := &BitSet{ 99 | data: make([]uint64, minLen), 100 | } 101 | 102 | for i := 0; i < minLen; i++ { 103 | intersectSet.data[i] = set.data[i] & other.data[i] 104 | } 105 | 106 | intersectSet.size = set.computeSize() 107 | 108 | return intersectSet 109 | } 110 | 111 | func (set *BitSet) Union(other *BitSet) *BitSet { 112 | var maxSet, minSet *BitSet 113 | if len(set.data) > len(other.data) { 114 | maxSet, minSet = set, other 115 | } else { 116 | maxSet, minSet = other, set 117 | } 118 | 119 | unionSet := &BitSet{ 120 | data: make([]uint64, len(maxSet.data)), 121 | } 122 | 123 | minLen := len(minSet.data) 124 | copy(unionSet.data[minLen:], maxSet.data[minLen:]) 125 | 126 | for i := 0; i < minLen; i++ { 127 | unionSet.data[i] = set.data[i] | other.data[i] 128 | } 129 | unionSet.size = unionSet.computeSize() 130 | return unionSet 131 | } 132 | 133 | func (set *BitSet) Difference(other *BitSet) *BitSet { 134 | setLen := len(set.data) 135 | otherLen := len(other.data) 136 | 137 | differenceSet := &BitSet{ 138 | data: make([]uint64, setLen), 139 | } 140 | 141 | minLen := setLen 142 | if setLen > otherLen { 143 | copy(differenceSet.data[otherLen:], set.data[otherLen:]) 144 | minLen = otherLen 145 | } 146 | 147 | for i := 0; i < minLen; i++ { 148 | differenceSet.data[i] = set.data[i] &^ other.data[i] 149 | } 150 | 151 | differenceSet.size = differenceSet.computeSize() 152 | 153 | return differenceSet 154 | } 155 | 156 | func (set *BitSet) Visit(do func(int) (skip bool)) (aborted bool) { 157 | d := set.data 158 | for i, len := 0, len(d); i < len; i++ { 159 | w := d[i] 160 | if w == 0 { 161 | continue 162 | } 163 | 164 | n := i << shift // 0 << 6,还是 0,1 << 6 就是 64,2 << 6 的就是 128 165 | for w != 0 { 166 | // 000.....000100 64~128 的话,表示 66,即 64 + 2,这个 2 可以由结尾 0 的个数确定 167 | // 那怎么获取结果 0 的个数呢?可以使用 bits.TrailingZeros64 函数 168 | b := bits.TrailingZeros64(w) 169 | if do(n + b) { 170 | return true 171 | } 172 | w &^= 1 << uint64(b) // 将已经检查的位清零 173 | } 174 | } 175 | return false 176 | } 177 | 178 | func (set *BitSet) computeSize() int { 179 | d := set.data 180 | n := 0 181 | for i, l := 0, len(d); i < l; i++ { 182 | if w := d[i]; w != 0 { 183 | n += bits.OnesCount64(w) 184 | } 185 | } 186 | 187 | return n 188 | } 189 | -------------------------------------------------------------------------------- /gosetdemo/bitset/bitset_test.go: -------------------------------------------------------------------------------- 1 | package bitset 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestContains(t *testing.T) { 9 | args := []int{1, 2, 3, 8, 100, 99} 10 | set := NewBitSet(args[:len(args)-1]...) 11 | for _, n := range args { 12 | if set.Contains(n) { 13 | fmt.Printf("%d 存在\n", n) 14 | } else { 15 | fmt.Printf("%d 不存在\n", n) 16 | } 17 | } 18 | 19 | set.Add(99) 20 | if set.Contains(99) { 21 | fmt.Println("99 存在") 22 | } 23 | 24 | set.Clear(100) 25 | if set.Contains(100) { 26 | fmt.Println("100 存在") 27 | } else { 28 | fmt.Println("100 不存在") 29 | } 30 | } 31 | 32 | func TestIntersect(t *testing.T) { 33 | set1 := NewBitSet(1, 2, 3) 34 | set2 := NewBitSet(1, 4, 3) 35 | 36 | set3 := set1.Intersect(set2) 37 | fmt.Println(set3.data) 38 | } 39 | 40 | func TestUnion(t *testing.T) { 41 | set := NewBitSet(1, 2, 10, 99) 42 | set.Visit(func(n int) bool { 43 | fmt.Println(n) 44 | return false 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /gosetdemo/bitset/functions.go: -------------------------------------------------------------------------------- 1 | package bitset 2 | 3 | const ( 4 | shift = 6 // 2^n = 64 中的 n, 即 6 5 | mask = 0x3f // 2^n - 1,即 63,即 0x3f 6 | ) 7 | 8 | // 所在的索引 9 | func index(n int) int { 10 | return n >> shift 11 | } 12 | 13 | func posVal(n int) uint64 { 14 | return 1 << uint64(n&mask) 15 | } 16 | 17 | func min(a, b int) int { 18 | if a < b { 19 | return a 20 | } else { 21 | return b 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gosetdemo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/poloxue/gset 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /gosetdemo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/bits" 6 | ) 7 | 8 | func main() { 9 | // 本文件是用于测试使用 10 | fmt.Println(bits.TrailingZeros(0b00110)) 11 | } 12 | -------------------------------------------------------------------------------- /gosetdemo/mapset/mapset.go: -------------------------------------------------------------------------------- 1 | package mapset 2 | 3 | // 这里面 key 用 interface 的好处,集合中的元素可以是任何类型 4 | type MapSet map[interface{}]struct{} 5 | 6 | // 数据结构经常会遇到一些方法,比如,增删查, 7 | // 除了一些数据结构相关的操作,还有一些集合特有的方法,比如交并差 8 | 9 | func NewMapSet(elements ...interface{}) *MapSet { 10 | set := make(MapSet) 11 | for _, e := range elements { 12 | set.Add(e) 13 | } 14 | 15 | return &set 16 | } 17 | 18 | func (set *MapSet) Add(e interface{}) bool { 19 | if _, found := (*set)[e]; found { 20 | return false 21 | } 22 | 23 | (*set)[e] = struct{}{} 24 | return true 25 | } 26 | 27 | func (set *MapSet) Remove(e interface{}) { 28 | delete(*set, e) 29 | } 30 | 31 | func (set *MapSet) Contains(e interface{}) bool { 32 | _, found := (*set)[e] 33 | return found 34 | } 35 | 36 | func (set *MapSet) Cardinatity() int { 37 | return len(*set) 38 | } 39 | 40 | func (set *MapSet) Intersect(other *MapSet) *MapSet { 41 | newSet := NewMapSet() 42 | 43 | // 为了高效的进行交集操作,先判断执行集合运算的两个集合的长度 44 | if set.Cardinatity() < other.Cardinatity() { 45 | for _, e := range *set { 46 | if _, found := (*other)[e]; found { 47 | newSet.Add(e) 48 | } 49 | } 50 | } else { 51 | for _, e := range *other { 52 | if _, found := (*set)[e]; found { 53 | newSet.Add(e) 54 | } 55 | } 56 | } 57 | 58 | return newSet 59 | } 60 | 61 | func (set *MapSet) Union(other *MapSet) *MapSet { 62 | unionSet := new(MapSet) 63 | 64 | for _, e := range *set { 65 | unionSet.Add(e) 66 | } 67 | 68 | for _, e := range *other { 69 | unionSet.Add(e) 70 | } 71 | 72 | return unionSet 73 | } 74 | 75 | func (set *MapSet) Difference(other *MapSet) *MapSet { 76 | // set - other 77 | differenceSet := NewMapSet() 78 | for _, e := range *set { 79 | if _, found := (*other)[e]; !found { 80 | differenceSet.Add(e) 81 | } 82 | } 83 | 84 | return differenceSet 85 | } 86 | -------------------------------------------------------------------------------- /gosetdemo/mode/mode.go: -------------------------------------------------------------------------------- 1 | package mode 2 | 3 | type Mode uint8 4 | 5 | const ( 6 | F1 = 1 << iota // 0b00000001 7 | F2 // 0b00000010 8 | F3 // 0b00000100 9 | F4 // 0b00001000 10 | F5 // 0b00010000 11 | F6 // 0b00100000 12 | F7 // 0b01000000 13 | F8 // 0b10000000 14 | ) 15 | 16 | func Set(m, f Mode) Mode { 17 | return m | f 18 | } 19 | 20 | func Has(m, f Mode) bool { 21 | return m&f != 0 22 | } 23 | 24 | func Clear(m, f Mode) Mode { 25 | return m &^ f 26 | } 27 | 28 | func Toggle(m, f Mode) Mode { 29 | return m ^ f 30 | } 31 | -------------------------------------------------------------------------------- /httpdemo/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | ) 8 | 9 | func get() { 10 | r, err := http.Get("http://httpbin.org/get") 11 | if err != nil { 12 | panic(err) 13 | } 14 | defer func() {_ = r.Body.Close()}() 15 | 16 | content, err := ioutil.ReadAll(r.Body) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | fmt.Printf("%s", content) 22 | } 23 | 24 | func post() { 25 | r, err := http.Post("http://httpbin.org/post", "", nil) 26 | if err != nil { 27 | panic(err) 28 | } 29 | defer func() {_ = r.Body.Close()}() 30 | 31 | content, err := ioutil.ReadAll(r.Body) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | fmt.Printf("%s", content) 37 | } 38 | 39 | func put() { 40 | request, err := http.NewRequest(http.MethodPut, "http://httpbin.org/put", nil) 41 | if err != nil { 42 | panic(err) 43 | } 44 | r, err := http.DefaultClient.Do(request) // enter 键 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | defer func() {_ = r.Body.Close()}() 50 | 51 | content, err := ioutil.ReadAll(r.Body) 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | fmt.Printf("%s", content) 57 | } 58 | 59 | func del() { 60 | request, err := http.NewRequest(http.MethodDelete, "http://httpbin.org/delete", nil) 61 | if err != nil { 62 | panic(err) 63 | } 64 | r, err := http.DefaultClient.Do(request) // enter 键 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | defer func() {_ = r.Body.Close()}() 70 | 71 | content, err := ioutil.ReadAll(r.Body) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | fmt.Printf("%s", content) 77 | } 78 | 79 | func main() { 80 | // client request 和 response 81 | 82 | // head 83 | // options 84 | del() 85 | } 86 | -------------------------------------------------------------------------------- /httpdemo/cookie/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/cookiejar" 9 | "net/url" 10 | "os" 11 | 12 | cookiejar2 "github.com/juju/persistent-cookiejar" 13 | ) 14 | 15 | func rrCookie() { 16 | // 模拟完成一个登录 17 | // 请求一个页面,传递基本的登录信息,将响应的 cookie 设置到下一次之上重新请求 18 | // 请求 http://httpbin.org/cookies/set?name=poloxue&password=123456 19 | // 返回 set-cookie: 20 | // 再一次请求呢携带上 cookie, 21 | // 首页 http://httpbin.org/cookies 就会通过 body 打印出已经设置 cookie 22 | // http://httpbin.org/cookies/set? => response 23 | // request => http://httpbin.org/cookies 24 | client := &http.Client{ 25 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 26 | return http.ErrUseLastResponse 27 | }, 28 | } 29 | 30 | firstRequest, _ := http.NewRequest( 31 | http.MethodGet, 32 | "http://httpbin.org/cookies/set?name=poloxue&password=123456", 33 | nil, 34 | ) 35 | firstResponse, _ := client.Do(firstRequest) 36 | defer func() { _ = firstResponse.Body.Close() }() 37 | 38 | secondRequest, _ := http.NewRequest( 39 | http.MethodGet, 40 | "http://httpbin.org/cookies", 41 | nil, 42 | ) 43 | 44 | for _, cookie := range firstResponse.Cookies() { 45 | secondRequest.AddCookie(cookie) 46 | } 47 | 48 | secondResponse, _ := client.Do(secondRequest) 49 | defer func() { _ = secondResponse.Body.Close() }() 50 | 51 | content, _ := ioutil.ReadAll(secondResponse.Body) 52 | fmt.Printf("%s\n", content) 53 | } 54 | 55 | func jarCookie() { 56 | jar, _ := cookiejar.New(nil) 57 | client := &http.Client{ 58 | Jar: jar, 59 | } 60 | r, _ := client.Get("http://httpbin.org/cookies/set?username=poloxue&password=123456") 61 | defer func() { _ = r.Body.Close() }() 62 | 63 | _, _ = io.Copy(os.Stdout, r.Body) 64 | } 65 | 66 | func login(jar http.CookieJar) { 67 | 68 | client := &http.Client{ 69 | Jar: jar, 70 | } 71 | r, _ := client.PostForm( 72 | "http://localhost:8080/login", 73 | url.Values{"username": {"poloxue"}, "password": {"poloxue123"}}, 74 | ) 75 | defer func() { _ = r.Body.Close() }() 76 | fmt.Println(r.Cookies()) 77 | 78 | _, _ = io.Copy(os.Stdout, r.Body) 79 | } 80 | 81 | func center(jar http.CookieJar) { 82 | client := &http.Client{ 83 | Jar: jar, 84 | } 85 | r, _ := client.Get("http://localhost:8080/center") 86 | defer func() { _ = r.Body.Close() }() 87 | 88 | _, _ = io.Copy(os.Stdout, r.Body) 89 | } 90 | 91 | func main() { 92 | // rrCookie() 93 | // jarCookie() 94 | // cookie 的分类有两种 一种是会话期 cookie 一种是持久性 cookie 95 | // jar, _ := cookiejar.New(nil) 96 | jar, _ := cookiejar2.New(nil) 97 | // login(jar) 98 | center(jar) 99 | _ = jar.Save() 100 | } 101 | -------------------------------------------------------------------------------- /httpdemo/cookie/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | func login(w http.ResponseWriter, req *http.Request) { 9 | username := req.PostFormValue("username") 10 | password := req.PostFormValue("password") 11 | 12 | if username == "poloxue" && password == "poloxue123" { 13 | http.SetCookie(w, &http.Cookie{ 14 | Name: "isLogin", 15 | Value: "1", 16 | Expires: time.Now().Add(3 * time.Hour), 17 | }) 18 | _, _ = w.Write([]byte("登录成功\n")) 19 | } else { 20 | _, _ = w.Write([]byte("登录失败")) 21 | } 22 | 23 | return 24 | } 25 | 26 | func center(w http.ResponseWriter, r *http.Request) { 27 | isLogin, err := r.Cookie("isLogin") 28 | if err == http.ErrNoCookie { 29 | _, _ = w.Write([]byte("无法访问")) 30 | w.WriteHeader(http.StatusUnauthorized) 31 | return 32 | } 33 | if isLogin.Value != "1" { 34 | _, _ = w.Write([]byte("无法访问")) 35 | w.WriteHeader(http.StatusUnauthorized) 36 | return 37 | } 38 | 39 | _, _ = w.Write([]byte("个人主页\n")) 40 | } 41 | 42 | func main() { 43 | http.HandleFunc("/login", login) 44 | http.HandleFunc("/center", center) 45 | _ = http.ListenAndServe(":8080", nil) 46 | } 47 | -------------------------------------------------------------------------------- /httpdemo/download/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | func downloadFile(url, filename string) { 11 | r, err := http.Get(url) 12 | if err != nil { 13 | panic(err) 14 | } 15 | defer func() {_ = r.Body.Close()}() 16 | 17 | f, err := os.Create(filename) 18 | if err != nil { 19 | panic(err) 20 | } 21 | defer func() {_ = f.Close()}() 22 | 23 | n, err := io.Copy(f, r.Body) 24 | fmt.Println(n, err) 25 | } 26 | 27 | type Reader struct { 28 | io.Reader 29 | Total int64 30 | Current int64 31 | } 32 | 33 | func (r *Reader) Read(p []byte) (n int, err error){ 34 | n, err = r.Reader.Read(p) 35 | 36 | r.Current += int64(n) 37 | fmt.Printf("\r进度 %.2f%%", float64(r.Current * 10000/ r.Total)/100) 38 | 39 | return 40 | } 41 | 42 | func DownloadFileProgress(url, filename string) { 43 | r, err := http.Get(url) 44 | if err != nil { 45 | panic(err) 46 | } 47 | defer func() {_ = r.Body.Close()}() 48 | 49 | f, err := os.Create(filename) 50 | if err != nil { 51 | panic(err) 52 | } 53 | defer func() {_ = f.Close()}() 54 | 55 | reader := &Reader{ 56 | Reader: r.Body, 57 | Total: r.ContentLength, 58 | } 59 | 60 | _, _ = io.Copy(f, reader) 61 | } 62 | 63 | func main() { 64 | // 自动文件下载,比如自动下载图片、压缩包 65 | url := "https://user-gold-cdn.xitu.io/2019/6/30/16ba8cb6465a6418?w=826&h=782&f=png&s=279620" 66 | filename := "poloxue.png" 67 | DownloadFileProgress(url, filename) 68 | } 69 | -------------------------------------------------------------------------------- /httpdemo/go.mod: -------------------------------------------------------------------------------- 1 | module httpdemo 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/juju/go4 v0.0.0-20160222163258-40d72ab9641a // indirect 7 | github.com/juju/persistent-cookiejar v0.0.0-20171026135701-d5e5a8405ef9 // indirect 8 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a 9 | golang.org/x/text v0.3.2 10 | gopkg.in/errgo.v1 v1.0.1 // indirect 11 | gopkg.in/retry.v1 v1.0.3 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /httpdemo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= 2 | github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 3 | github.com/juju/go4 v0.0.0-20160222163258-40d72ab9641a h1:45JtCyuNYE+QN9aPuR1ID9++BQU+NMTMudHSuaK0Las= 4 | github.com/juju/go4 v0.0.0-20160222163258-40d72ab9641a/go.mod h1:RVHtZuvrpETIepiNUrNlih2OynoFf1eM6DGC6dloXzk= 5 | github.com/juju/persistent-cookiejar v0.0.0-20171026135701-d5e5a8405ef9 h1:5g7E7WtWRslad/0fq68VHgHhFRvTWo30VDaEJ5KEvEc= 6 | github.com/juju/persistent-cookiejar v0.0.0-20171026135701-d5e5a8405ef9/go.mod h1:zrbmo4nBKaiP/Ez3F67ewkMbzGYfXyMvRtbOfuAwG0w= 7 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 8 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 9 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 10 | github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ= 11 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= 12 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 13 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 14 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 15 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 16 | gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso= 17 | gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo= 18 | gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs= 19 | gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g= 20 | -------------------------------------------------------------------------------- /httpdemo/post/NOTE.md: -------------------------------------------------------------------------------- 1 | words=123&uploadfile1=uploadfile1 2 | 3 | POST http://httpbin.org/post HTTP/1.1 4 | Content-Type: multipart/form-data; boundary=285fa365bd76e6378f91f09f4eae20877246bbba4d31370d3c87b752d350 5 | 6 | multipart/form-data; boundary=285fa365bd76e6378f91f09f4eae20877246bbba4d31370d3c87b752d350 7 | --285fa365bd76e6378f91f09f4eae20877246bbba4d31370d3c87b752d350 8 | Content-Disposition: form-data; name="uploadFile1"; filename="uploadfile1.txt" 9 | Content-Type: application/octet-stream 10 | 11 | upload file1 12 | --285fa365bd76e6378f91f09f4eae20877246bbba4d31370d3c87b752d350 13 | Content-Disposition: form-data; name="uploadFile1"; filename="uploadfile2.txt" 14 | Content-Type: application/octet-stream 15 | 16 | upload file2 17 | --285fa365bd76e6378f91f09f4eae20877246bbba4d31370d3c87b752d350 18 | Content-Disposition: form-data; name="words" 19 | 20 | 123 21 | --285fa365bd76e6378f91f09f4eae20877246bbba4d31370d3c87b752d350-- 22 | -------------------------------------------------------------------------------- /httpdemo/post/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "mime/multipart" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "strings" 14 | ) 15 | 16 | func postForm() { 17 | // form data 形式 query string,类似于 name=poloxue&age=18 18 | data := make(url.Values) 19 | data.Add("name", "poloxue") 20 | data.Add("age", "18") 21 | payload := data.Encode() 22 | 23 | r, _ := http.Post( 24 | "http://httpbin.org/post", 25 | "application/x-www-form-urlencoded", 26 | strings.NewReader(payload), 27 | ) 28 | defer func() { _ = r.Body.Close() }() 29 | 30 | content, _ := ioutil.ReadAll(r.Body) 31 | fmt.Printf("%s", content) 32 | } 33 | 34 | func postJson() { 35 | u := struct { 36 | Name string `json:"name"` 37 | Age int `json:"age"` 38 | }{ 39 | Name: "poloxue", 40 | Age: 18, 41 | } 42 | payload, _ := json.Marshal(u) 43 | r, _ := http.Post( 44 | "http://httpbin.org/post", 45 | "application/json", 46 | bytes.NewReader(payload), 47 | ) 48 | defer func() { _ = r.Body.Close() }() 49 | 50 | content, _ := ioutil.ReadAll(r.Body) 51 | fmt.Printf("%s", content) 52 | } 53 | 54 | func postFile() { 55 | body := &bytes.Buffer{} 56 | 57 | writer := multipart.NewWriter(body) 58 | _ = writer.WriteField("words", "123") 59 | 60 | // 一个是输入表单的 name,一个上传的文件名称 61 | upload1Writer, _ := writer.CreateFormFile("uploadfile1", "uploadfile1") 62 | 63 | uploadFile1, _ := os.Open("uploadfile1") 64 | defer func() {_ = uploadFile1.Close()}() 65 | 66 | _, _ = io.Copy(upload1Writer, uploadFile1) 67 | 68 | // 一个是输入表单的 name,一个上传的文件名称 69 | upload2Writer, _ := writer.CreateFormFile("uploadfile2", "uploadfile2") 70 | 71 | uploadFile2, _ := os.Open("uploadfile2") 72 | defer func() {_ = uploadFile2.Close()}() 73 | 74 | _, _ = io.Copy(upload2Writer, uploadFile2) 75 | 76 | _ = writer.Close() 77 | 78 | fmt.Println(writer.FormDataContentType()) 79 | fmt.Println(body.String()) 80 | r, _ := http.Post("http://httpbin.org/post", 81 | writer.FormDataContentType(), 82 | body, 83 | ) 84 | defer func() {_ = r.Body.Close()}() 85 | 86 | content, _ := ioutil.ReadAll(r.Body) 87 | 88 | fmt.Printf("%s", content) 89 | } 90 | 91 | func main() { 92 | // post 请求的本质,它是 request body 提交,相对于 get 请求(urlencoded 提交查询参数, 提交内容有大小限制,好像 2kb) 93 | // post 不同的形式也就是 body 的格式不同 94 | // post form 表单,body 就是 urlencoded 的形式,比如 name=poloxue&age=18 95 | // post json,提交的 json 格式 96 | // post 文件,其实也是组织 body 数据 97 | // postJson() 98 | postFile() 99 | } 100 | -------------------------------------------------------------------------------- /httpdemo/post/server/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /httpdemo/post/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "html/template" 7 | "io" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | const maxBytes = 2 * 1024 * 1024 // 2 MB 14 | const uploadPath = "./tmp" 15 | 16 | func randToken(len int) string { 17 | b := make([]byte, len) 18 | _, _ = rand.Read(b) 19 | return fmt.Sprintf("%x", b) 20 | } 21 | 22 | func uploadHandler() http.HandlerFunc { 23 | return func(w http.ResponseWriter, req *http.Request) { 24 | http.MaxBytesReader(w, req.Body, maxBytes) 25 | if err := req.ParseMultipartForm(maxBytes); err != nil { 26 | log.Printf("req.ParseMultipartForm: %v", err) 27 | return 28 | } 29 | 30 | file, _, err := req.FormFile("uploadFile") 31 | if err != nil { 32 | log.Printf("req.FormFile: %v", err) 33 | return 34 | } 35 | defer func() { _ = file.Close()}() 36 | 37 | f, _ := os.Create("poloxue.txt") 38 | defer func() {_ = f.Close()}() 39 | _, _ = io.Copy(f, file) 40 | 41 | fmt.Println(req.FormValue("words")) 42 | } 43 | } 44 | 45 | func main() { 46 | http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 47 | tpl, err := template.ParseFiles("./form.html") 48 | if err != nil { 49 | log.Printf("template.New: %v", err) 50 | return 51 | } 52 | 53 | if err := tpl.Execute(w, nil); err != nil { 54 | log.Printf("tpl.Execute: %v", err) 55 | return 56 | } 57 | }) 58 | http.HandleFunc("/upload", uploadHandler()) 59 | 60 | fs := http.FileServer(http.Dir(uploadPath)) 61 | http.Handle("/files", http.StripPrefix("/files", fs)) 62 | 63 | log.Println("Server started on localhost:8080, use /upload for uploading files and /files/{filename} for downloading files.") 64 | 65 | log.Fatal(http.ListenAndServe(":8080", nil)) 66 | } 67 | -------------------------------------------------------------------------------- /httpdemo/post/server/poloxue.txt: -------------------------------------------------------------------------------- 1 | package gopinery 2 | 3 | type Swi struct{} 4 | 5 | func (*Swi) Industries() { 6 | } 7 | -------------------------------------------------------------------------------- /httpdemo/post/uploadfile1: -------------------------------------------------------------------------------- 1 | upload file1 -------------------------------------------------------------------------------- /httpdemo/post/uploadfile2: -------------------------------------------------------------------------------- 1 | upload file2 -------------------------------------------------------------------------------- /httpdemo/proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/url" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | // proxyUrl, _ := url.Parse("socks5://127.0.0.1:1080") 12 | proxyUrl, _ := url.Parse("http://127.0.0.1:8087") 13 | t := &http.Transport{ 14 | Proxy: http.ProxyURL(proxyUrl), 15 | } 16 | // 一般主要两种,http 代理 和 shadowsocks 的代码, socks5 17 | client := http.Client{Transport: t} 18 | r, _ := client.Get("https://google.com") 19 | defer func() { _ = r.Body.Close() }() 20 | 21 | _, _ = io.Copy(os.Stdout, r.Body) 22 | // session 23 | // session.Get 24 | // session.Post 25 | // session.Put 26 | // session.Delete 27 | } 28 | -------------------------------------------------------------------------------- /httpdemo/redirect/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | func redirectLimitTimes() { 10 | // 限制重定向的次数 11 | client := &http.Client{ 12 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 13 | if len(via) > 10 { 14 | return errors.New("redirect too times") 15 | } 16 | return nil 17 | }, 18 | } 19 | 20 | request, _ := http.NewRequest( 21 | http.MethodGet, 22 | "http://httpbin.org/redirect/20", 23 | nil, 24 | ) 25 | _, err := client.Do(request) 26 | if err != nil { 27 | panic(err) 28 | } 29 | } 30 | 31 | func redirectForbidden() { 32 | // 禁止重定向 33 | // 登录请求,防止重定向到首页 34 | client := &http.Client{ 35 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 36 | return http.ErrUseLastResponse 37 | }, 38 | } 39 | 40 | request, _ := http.NewRequest( 41 | http.MethodGet, 42 | "http://httpbin.org/cookies/set?name=poloxue", 43 | nil, 44 | ) 45 | r, err := client.Do(request) 46 | if err != nil { 47 | panic(err) 48 | } 49 | defer func() {_ = r.Body.Close()}() 50 | fmt.Println(r.Request.URL) 51 | } 52 | 53 | func main() { 54 | // 重定向 55 | // 返回一个状态码,3xx 301 302 303 307 308 56 | redirectForbidden() 57 | } 58 | -------------------------------------------------------------------------------- /httpdemo/request/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | ) 9 | 10 | func printBody(r *http.Response) { 11 | defer func() {_ = r.Body.Close()}() 12 | 13 | content, err := ioutil.ReadAll(r.Body) 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | fmt.Printf("%s", content) 19 | } 20 | 21 | func requestByParams() { 22 | request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | params := make(url.Values) 28 | params.Add("name", "poloxue") 29 | params.Add("age", "18") 30 | 31 | request.URL.RawQuery = params.Encode() 32 | 33 | r , err:= http.DefaultClient.Do(request) 34 | if err != nil { 35 | panic(err) 36 | } 37 | printBody(r) 38 | } 39 | 40 | func requestByHead() { 41 | request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | request.Header.Add("user-agent", "chrome") 47 | r , err:= http.DefaultClient.Do(request) 48 | if err != nil { 49 | panic(err) 50 | } 51 | printBody(r) 52 | } 53 | 54 | func main() { 55 | // 如何设置请求的查询参数,http://httpbin.org/get?name=poloxue&age=18 56 | // 如何定制请求头,比如修改 user-agent 57 | // requestByParams() 58 | requestByHead() 59 | } 60 | -------------------------------------------------------------------------------- /httpdemo/response/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "golang.org/x/net/html/charset" 7 | "golang.org/x/text/transform" 8 | "io/ioutil" 9 | "net/http" 10 | ) 11 | 12 | func responseBody(r *http.Response) { 13 | content, _ := ioutil.ReadAll(r.Body) 14 | fmt.Printf("%s", content) 15 | } 16 | 17 | func status(r *http.Response) { 18 | fmt.Println(r.StatusCode) // 状态吗 19 | fmt.Println(r.Status) // 状态描述 20 | } 21 | 22 | func header(r *http.Response) { 23 | // content-type Content-Type 是都可以获取到内容 24 | fmt.Println(r.Header.Get("content-type")) 25 | } 26 | 27 | func encoding(r *http.Response) { 28 | // content-type 中会提供编码,比如 content-type="text/html;charset=utf-8" 29 | // html head meta 获取编码, 30 | //