Golang笔记

本文记录一些Golang web容易踩的坑

http.ResponseWriter的写入顺序

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
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
values := request.URL.Query()
subLink := values.Get("sub")
subLinkURL, _ := url.Parse(subLink)
if subLink == "" {
http.Error(writer, "param \"sub\" missing", http.StatusInternalServerError)
return
}
res, err := http.Get(subLink)
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
if res.StatusCode != 200 {
http.Error(writer, "upstream server returns error", res.StatusCode)
}
var remote sub.ClashSub
err = yaml.NewDecoder(res.Body).Decode(&remote)
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
}
var body = bytes.NewBuffer(nil)

// omitted...

// this goes before writer.Write, or the content has no effect
for k, v := range res.Header {
switch k {
case "Subscription-Userinfo":
writer.Header().Set(k, v[0])
case "Content-Disposition":
if subLinkURL != nil {
writer.Header().Set(k, "attachment;filename="+subLinkURL.Host)
}
}
}
fmt.Fprintf(writer, body.String())
log.Printf("fetch clash sub %s and transformed, requester: %s", subLink, request.RemoteAddr)
})

fmt.Fprintf(writer, body.String())之前,必须写入所有Header值,后续writer.Header().Set是无效的。

writer.Header的文档: Changing the header map after a call to WriteHeader (or Write) has no effect unless the modified headers are trailers.

其原因是,writer.Write or writer.WriteHeader会把当前的Header()返回对象写入HTTP报文里,后续对Header()返回的对象进行修改,仅仅是修改这个map而已,并不会反映在实际发送出去的报文中。

Break to label

得益于Golang中独特的并发编程模式,借助Channel和select语句可以方便的管理多个Go routine的协同。在实际开发中我们经常处理需要deadline的业务逻辑,一般是这样的代码模式

1
2
3
4
5
6
7
8
9
10
11
12
deadline := time.After(1 * time.Minute)
for {
// do something
select {
case <-deadline:
// stop loop
break
default:
// continue
}
}
fmt.Println("deadline exceeded")

问题是在deadline截至的时候,怎么停止循环呢。复习一下break关键字,在Go中,break会跳出scope中最近的for, select或者switch,所以第七行的break,是从select里跳出了,for loop还是继续进行,上面这段代码永远无法跳出循环。

那么如何使用select语句判断合适的条件满足时(比如channel返回了数据),从外面的for循环里跳出呢?这里要用到一个新语法:label。

1
2
3
4
5
6
7
8
9
10
11
12
13
deadline := time.After(1 * time.Minute)
Loop:
for {
// do something
select {
case <-deadline:
// stop loop
break Loop
default:
// continue
}
}
fmt.Println("deadline exceeded")

修改之后的这段代码,在for关键字之前加了一个名字叫Loop的label,然后配合break Loop,实现”break to label”。触发break Loop的时候,跳到Loop标签后面对应的整个语义单元的下一行,也就是for loop之后的第一行fmt.Println("...")