【Go语言】《GO语言入门经典》阅读笔记
未归档知识点
字符串末尾可以追加其他数据,但是不能修改原来的值
使用reflect包可以输出变量的类型
浮点数不指定默认是float64
package main
import ( "fmt";"reflect" )
func main() {
s := "aaa"
i := 10
f := 1.2
fmt.Println(reflect.TypeOf(s))
fmt.Println(reflect.TypeOf(i))
fmt.Println(reflect.TypeOf(f))
}
数据转换 资料
string ↔ bool
package main
import ( "fmt";"reflect";"strconv")
func main() {
var s string = strconv.FormatBool(true) // 举例:将bool类型转换成string
b,err := strconv.ParseBool(s) //举例:将string转化成bool, 会有两个返回值
fmt.Println(reflect.TypeOf(b),b,err)
}
output:
bool true <nil>
string ↔ int
package main
import ( "fmt";"strconv")
func main() {
s := strconv.Itoa(32)) // int -> string
i,_ := strconv.Atoi("3") // string -> int 成功
i,err := strconv.Atoi("a") // string -> int 失败
if err != nil { //判断是否成功
fmt.Println("converted failed", i) //i必须使用,这里的值为 0
}
}
Parse类函数
用于转换字符串为给定类型的值:ParseBool()、ParseFloat()、ParseInt()、ParseUint()
由于字符串转换为其它类型可能会失败,所以这些函数都有两个返回值,第一个返回值保存转换后的值,第二个返回值判断是否转换成功。
b, err := strconv.ParseBool("true")
f, err := strconv.ParseFloat("3.1415", 64) //ParseFloat()只能接收float64类型的浮点数。
i, err := strconv.ParseInt("-42", 10, 64) // func ParseInt(s string, base int, bitSize int) (i int64, err error)
u, err := strconv.ParseUint("42", 10, 64) // func ParseUint(s string, base int, bitSize int) (uint64, error)
bitSize
参数表示转换为什么位的int/uint,有效值为0、8、16、32、64。
当bitSize=0的时候,表示转换为int或uint类型。例如bitSize=8表示转换后的值的类型为int8或uint8。
base
参数表示以什么进制的方式去解析给定的字符串,有效值为0、2-36。
当base=0的时候,表示根据string的前缀来判断以什么进制去解析:0x开头的以16进制的方式去解析,0开头的以8进制方式去解析,其它的以10进制方式解析。
// 以10进制方式解析"-42",保存为int64类型
i, _ := strconv.ParseInt("-42", 10, 64)
// 以5进制方式解析"23",保存为int64类型
i, _ := strconv.ParseInt("23", 5, 64)
// 以16进制解析23,保存为int64类型
i, _ := strconv.ParseInt("23", 16, 64)
// 以15进制解析23,保存为int64类型
i, _ := strconv.ParseInt("23", 15, 64)
Format类函数
将给定类型格式化为string类型:FormatBool()、FormatFloat()、FormatInt()、FormatUint()
s := strconv.FormatBool(true)
s := strconv.FormatFloat(3.1415, 'E', -1, 64) //func FormatFloat(f float64, fmt byte, prec, bitSize int) string
s := strconv.FormatInt(-42, 16) //func FormatInt(i int64, base int) string
s := strconv.FormatUint(42, 16) //func FormatUint(i uint64, base int) string
FormatInt的第二个参数base
指定将第一个参数转换为多少进制,有效值为2<=base<=36。当指定的进制位大于10的时候,超出10的数值以a-z字母表示。
FormatFloat()的参数
bitSize
表示f的来源类型(32:float32、64:float64),会据此进行舍入fmt
表示格式:'f'(-ddd.dddd)、'b'(-ddddp±ddd,指数为二进制)、'e'(-d.dddde±dd,十进制指数)、'E'(-d.ddddE±dd,十进制指数)、'g'(指数很大时用'e'格式,否则'f'格式)、'G'(指数很大时用'E'格式,否则'f'格式)prec
控制精度(排除指数部分):对'f'、'e'、'E',它表示小数点后的数字个数;对'g'、'G',它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。
Append类函数
AppendTP类函数用于将TP转换成字符串后append到一个slice中:AppendBool()、AppendFloat()、AppendInt()、AppendUint()。
Append类的函数和Format类的函数工作方式类似,只不过是将转换后的结果追加到一个slice中
// 声明一个slice
b10 := []byte("int (base 10):")
// 将转换为10进制的string,追加到slice中
b10 = strconv.AppendInt(b10, -42, 10)
fmt.Println(string(b10))
b16 := []byte("int (base 16):")
b16 = strconv.AppendInt(b16, -42, 16)
fmt.Println(string(b16))
函数
函数可以有多个返回值
不定参数函数
// 实现求最大值函数
package main
import ( "fmt" )
func max(numbers ... int) int{
res := numbers[0]
for _,it := range numbers {
if res < it {
res = it
}
}
return res
}
func main() {
result := max(1,3,5,2,6)
fmt.Println(result)
}
具名返回值
指定返回值的名称,并在return前进行赋值,return时只需要裸return语句,可以增加函数的可读性
func sayHi() (x,y string) {
x = "Hello"
y = "world"
return
}
func main() {
fmt.Println(sayHi())
}
/*
output:
Hello world
*/
将函数作为值传递
package main
import ( "fmt")
func B(f func() string) string {
return f()
}
func main() {
fn := func() string {
return "ok"
}
fmt.Println(B(fn))
}
/*
output:
ok
*/
B接受一个签名类型为func() string
的函数作为函数参数f
,在B里面调用这个参数的函数f()
,就执行了这个函数,返回了一个string,再把这个函数作为返回值返回到main.
它同样也可以写成下面这种形式
func B(f func() string) string {
return f()
}
func A() string {
return "ok"
}
func main() {
fn := A
fmt.Println(B(fn))
}
流程控制
Golang中没有while关键词,while循环可以用for实现
// 打印1~10
func main() {
i:=0
for i<10 {
i++
fmt.Println(i)
}
}
类似C语言的迭代遍历,golang可以用for遍历数据结构
func main() {
a :=[]int{1,2,3,4}
for id,num := range a { //如果不需要序号'id',可以用'_'代替
fmt.Println(id,"::",num)
}
}
/*
OUTPUT:
0 :: 1
1 :: 2
2 :: 3
3 :: 4
*/
defer语句
被defer指定的语句将在函数返回前执行,无论它被写在哪个位置
func main() {
defer fmt.Println("this is a defer function")
fmt.Println("hello world")
}
/*
OUTPUT:
hello world
this is a defer function
*/
如果程序里有多条defer语句,将从后往前执行
func main() {
defer fmt.Println("defer A")
defer fmt.Println("defer B")
fmt.Println("hello world")
defer fmt.Println("defer C")
}
/*
OUTPUT:
hello world
defer C
defer B
defer A
*/
一些需要使用defer语句的例子:
- 在读取文件后将其关闭
- 收到来自Web服务器的响应后对其进行处理以及建立连接后向数据库请求数据
- 需要在某项工作完成后执行特定的函数
数组,切片
切片类似于数组,但可以改变长度
append函数
使用append函数可以增大切片长度,也可以删除元素,不能将delete用于切片元素
func main() {
var d = make([]string, 2)
d[0] = "aaa"
d[1] = "bbb"
d = append(d,"ccc","ddd","eee") //向d的末尾添加多个元素
d = append(d[:2],d[3:]...) //删除d[2],将后面所有元素前移
for id,it := range d {
fmt.Println(id,it)
}
}
/*
OUTPUT:
0 aaa
1 bbb
2 ddd
3 eee
*/
copy函数
copy可以在新切片中创建元素的副本,但是不能改变新切片的长度
func main() {
a := []int {1,2,3,4}
a = append(a,5) //a的长度为5
b := make([]int, 2) //b的长度为2
copy(b,a)
fmt.Println(b)
}
/*
OUTPUT:
[1 2]
*/
映射
类似于c++的map
声明string → int空映射
var mp = make(map[string]int)
添加元素
mp["cook"] = 32
结构体
简单的例子
type Movie struct {
Name string
Year int
Rating float32
}
func main() {
m := Movie{Rating: 10, Name: "name", Year: 1999}
fmt.Println(m)// 直接打印是按照结构体定义成员变量的顺序打印
}
/*
OUTPUT:
{name 1999 10}
*/
还可以用new方法创建实例,但是创建的是指针变量
但是在golang中指针变量访问成员变量还是用'.'
type Movie struct {
Name string
Rating float32
}
func main() {
a := new(int)
//var a int
*a = 2333
fmt.Println(reflect.TypeOf(a))
m := new(Movie)
*m = Movie{
Name:"aaa",
Rating:9.9,
}
fmt.Println(reflect.TypeOf(m))
fmt.Println(m.Name, m.Rating)
}
/*
*int
*main.Movie
aaa 9.9
*/
结构体也是可以嵌套的,当作一个普通的数据类型,类似c++
创建结构体时,如果没有给数据字段指定值,那么为golang默认的初始值
golang没有提供自定义默认值的内置方法,但可以使用自定义函数来实现这个目标
方法、接口
类似于c语言的结构体,我们可以为结构体添加成员方法,在golang要写在结构体外面,在func之后,函数名之前添加“接收者”变量,传递指针还是传递值根据是否需要改变原值来决定。
type Movie struct {
Name string
Rating float64
}
func (m *Movie) summary() string {
r := strconv.FormatFloat(m.Rating, 'f', 1, 64) //将float64转化成string
return m.Name + ", " + r
}
func main() {
m := Movie{
Name: "Spiderman",
Rating: 3.2,
}
fmt.Println(m.summary())
}
/*
OUTPUT:
Spiderman, 3.2
*/
接口描述了方法集中的所有方法,制定了它们的签名,但并没有实现他们。
要使用接口必须先实现它,接口定义了方法的规范,有助于代码理解
// 编写启动机器人的接口
type Robot interface {
PowerOn() error //不接受任何参数,返回一种错误类型
}
//编写两种机器人的启动方法
type T850 struct {
Name string
}
func (a *T850) PowerOn() error {
return nil
}
type R2D2 struct {
Broken bool
}
func (r *R2D2) PowerOn() error {
if r.Broken {
return errors.New("R2D2 is broken")
} else {
0 return nil
}
}
//编写一个可用于启动任何机器人的函数, 这个函数将接口的实现作为参数
func Boot(r Robot) error {
return r.PowerOn()
}
//各种调用方法
func main() {
t := T850{Name: "The Terminator"}
r := R2D2{Broken: true}
err := Boot(&r)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Robot is powered on!")
}
err = Boot(&t)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Robot is powered on!")
}
}
/*
OUTPUT:
R2D2 is broken
Robot is powered on!
*/
接口有助于代码重用,还能完全更换实现,在golang中,接口一声明的方式提供了多态
字符串
使用缓冲区拼接字符串,速度比直接+起来快得多
package main
import (
"bytes"
"fmt"
)
func main() {
var buffer bytes.Buffer
for i := 0; i < 500; i++ {
buffer.WriteString("z")
}
fmt.Println(buffer.String())
}
/* OUTPUT:
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
*/
将整个字符串转换为小写,使用ToLower
函数
将整个字符串转换为小写,使用ToUpper
函数
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.ToLower("AAA bbb"))
fmt.Println(strings.ToUpper("AAA bbb"))
}
/*
OUTPUT:
aaa bbb
AAA BBB
*/
字符串查找
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Index("surface", "face"))
fmt.Println(strings.Index("moon", "aer"))
}
其中Index函数的实现如下,时间复杂度应该是O(n^2)的,具体一些函数的实现待考证
// Index returns the index of the first instance of substr in s, or -1 if substr is not present in s.
func Index(s, substr string) int {
n := len(substr) //先获取substr的长度 赋给n
switch {
case n == 0: //如果 substr的长度为0 ,则返回0,
return 0
case n == 1:
return IndexByte(s, substr[0]) // 后面再看一下IndexByte()的源码
case n == len(s): // 如果 s和substr长度相等,直接判断俩字符串是否一模一样
if substr == s {
return 0
}
return -1
case n > len(s): // 如果 substr的长度大于s的长度,那肯定不存在了,返回-1,说明substr不存在于s中
return -1
case n <= bytealg.MaxLen: // 后面得看bytealg.MaxLen
// Use brute force when s and substr both are small
if len(s) <= bytealg.MaxBruteForce { // const型 :const MaxBruteForce = 64
return bytealg.IndexString(s, substr)
}
c0 := substr[0]
c1 := substr[1]
i := 0
t := len(s) - n + 1
fails := 0
for i < t {
if s[i] != c0 {
// IndexByte is faster than bytealg.IndexString, so use it as long as
// we're not getting lots of false positives.
o := IndexByte(s[i:t], c0) //这个的实现?
if o < 0 {
return -1
}
i += o
}
if s[i+1] == c1 && s[i:i+n] == substr {
return i
}
fails++
i++
// Switch to bytealg.IndexString when IndexByte produces too many false positives.
if fails > bytealg.Cutover(i) { //这个的实现?
r := bytealg.IndexString(s[i:], substr)
if r >= 0 {
return r + i
}
return -1
}
}
return -1
}
c0 := substr[0]
c1 := substr[1]
i := 0
t := len(s) - n + 1
fails := 0
for i < t {
if s[i] != c0 {
o := IndexByte(s[i:t], c0)
if o < 0 {
return -1
}
i += o
}
if s[i+1] == c1 && s[i:i+n] == substr {
return i
}
i++
fails++
if fails >= 4+i>>4 && i < t {
// See comment in ../bytes/bytes_generic.go.
j := indexRabinKarp(s[i:], substr)
if j < 0 {
return -1
}
return i + j
}
}
return -1
}
删除字符串开头和末尾的空白字符可以用TrimSpace
函数
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.TrimSpace(" I don't need all this space "))
}
/*
OUTPUT:
I don't need all this space
*/
错误处理
不同于java等其他语言,go语言不支持传统的try-catch-finally控制结构,go语言将错误作为返回值。
读取文件时处理错误
package main
import (
"fmt"
"io/ioutil"
)
func main() {
var file []byte
var err error
file, err = ioutil.ReadFile("foo.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s", file)
}
/*
OUTPUT:
open foo.txt: The system cannot find the file specified.
*/
自定义一个错误类型
import (
"errors"
"fmt"
)
func main() {
err := errors.New("Something went wrong")
if err != nil {
fmt.Println(err)
}
}
/*
OUTPUT:
Something went wrong
*/
fmt包提供了方法Errorf,可用于设置返回的错误字符串的格式,可以创建更有意义的错误字符串
import (
"fmt"
)
func main() {
name := "name_aaa"
err := fmt.Errorf("The %v quit",name)
if err != nil {
fmt.Println(err)
}
}
/*
The name_aaa quit
*/
自定义从函数返回错误类型
// returns half the value
func Half(numberToHalf int) (int, error) {
if numberToHalf%2 != 0 {
return -1, fmt.Errorf("Cannot half %v", numberToHalf)
}
return numberToHalf / 2, nil
}
func main() {
n, err := Half(19)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(n)
}
/*
OUTPUT:
Cannot half 19
*/
// returns half the value
func Half(numberToHalf int) (int, error) {
if numberToHalf%2 != 0 {
return -1, fmt.Errorf("Cannot half %v", numberToHalf)
}
return numberToHalf / 2, nil
}
func main() {
n, err := Half(18)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(n)
}
/*
OUTPUT:
9
*/
panic
函数可以让程序直接崩溃推出,通常不建议使用,如果继续运行程序会有更坏的结果可以使用(拔电源效果?)
- 程序处于无法恢复的状态
- 发生了无法处理的错误
package main
import "fmt"
func main() {
fmt.Println("aaaaa")
panic("Oh no. I can do no more. Goodbye.")
fmt.Println("bbbbb")
}
/*
OUTPUT:
aaaaa
panic: Oh no. I can do no more. Goodbye.
goroutine 1 [running]:
main.main()
C:/Users/..../...:7 +0x9c
exit status 2
*/
并发编成
通过time.sleep
函数可以使程序暂停指定的时间
time.S``leep(time.Second*2) //让程序暂停两秒
go 语言提供了Goroutine,可以处理并发操作。通过使用Goroutine,可在调用函数print1后立即执行main函数中的第二行代码print2,在这种情况下,函数print1依然会执行,但不会阻塞程序中其他代码的执行,于是print1和print2并发执行,交错打印"aaa"和"bbb"
package main
import (
"fmt"
"time"
)
func print1() {
for i:=1;i<=5;i++ {
fmt.Println("aaa")
time.Sleep(time.Second)
}
}
func print2() {
for i:=1;i<=5;i++ {
fmt.Println("bbb")
time.Sleep(time.Second)
}
}
func main() {
go print1()
print2()
}
/*
OUTPUT:
bbb
aaa
bbb
aaa
bbb
aaa
aaa
bbb
aaa
bbb
*/
通道
使用通道进行通信
c:=make(chan,string) // 创建通道c,可以接受字符串数据
c <- "Hello World" //向通道发送消息
msg := <- c
下面这个例子,两秒后会输出msg字符串
给通道指定消息接收者是一个阻塞操作,它将阻止函数返回,直到收到一条消息为止
程序并发执行main
函数和slowFunc
函数时,会先执行到msg:= <- c
,此时该进程被阻塞,直到c <- "slowFunc() finished"
被执行,msg立即收到通道的消息。
func slowFunc(c chan string) {
time.Sleep(time.Second * 2)
c <- "slowFunc() finished"
}
func main() {
c := make(chan string)
slowFunc(c)
msg := <- c
fmt.Println(msg)
}
可以在创建通道时指定容量,这样可以将消息暂时缓存在通道中,但是不能超过通道容量
使用close
函数关闭通道,不再接收消息,这里不关闭通道就会产生协程死锁的错误(原因待查)
func receiver(c chan string) {
for msg := range c {
fmt.Println(msg)
}
}
func main() {
messages := make(chan string, 2)
messages <- "hello"
messages <- "world"
close(messages)
fmt.Println("Pushed two messages onto channel with no receivers")
receiver(messages)
}
/*
OUTPUT:
Pushed two messages onto channel with no receivers
hello
world
*/
将通道函数用作函数参数,可以指定它为只读,只写或读写的
//通道c只读,<-位于chan左边
func OR(c <- chan string) string{
msg := <-c
return msg
}
//通道c只读,<-位于chan右边
func OW(c chan <- string) {
c <- "hello world"
}
//通道c只读,无 <-
func WR(c chan string) string {
msg := <-c
c <- "hello world"
return msg
}
假设当前有多个Goroutine,可以使用select语句为通道创建一系列接收者,并执行最先收到消息的接收者.
下面的程序,第二个函数休眠阻塞,所以先接受到第一个通道的消息,执行了case 1
func ping1(c chan string) {
c <- "ping on channel1"
}
func ping2(c chan string) {
time.Sleep(time.Second * 2)
c <- "ping on channel2"
}
func main() {
channel1 := make(chan string)
channel2 := make(chan string)
go ping1(channel1)
go ping2(channel2)
select {
case msg1 := <-channel1:
fmt.Println("received", msg1)
case msg2 := <-channel2:
fmt.Println("received", msg2)
}
}
/*
OUTPUT:
received ping on channel1
*/
也可以给select指定一个超时时间,在指定时间后不再阻塞。
select {
case msg1 := <-channel1:
fmt.Println("received", msg1)
case msg2 := <-channel2:
fmt.Println("received", msg2)
case <-time.After(500 * time.Millisecond):
fmt.Println("no messages received. giving up.")
}
在select语句中,如果同时从两个通道收到消息,将随机地选择并执行一条case,且只执行被选中的case
退出通道——程序需要使用select语句实现无限制阻塞,但同时要求能够随时返回。通过在select语句中添加一个退出通道,可向退出通道发送消息来结束该语句,从而停止阻塞。
func sender(c chan string) {
t := time.NewTicker(1 * time.Second)
for {
c <- "I'm sending a message"
<-t.C
}
}
func main() {
messages := make(chan string)
stop := make(chan bool)
go sender(messages) //每秒向msg通道发送一条消息
go func() { //三秒后向退出通道发送消息
time.Sleep(time.Second * 3)
fmt.Println("Time's up!")
stop <- true
}()
for {
select {
case <-stop:
return
case msg := <-messages:
fmt.Println(msg)
}
}
}
/*
I'm sending a message
I'm sending a message
I'm sending a message
I'm sending a message
Time's up!
*/
包
下载第三方包,在配置好git的情况下: go get ...(包名)
导入包可用import
语句
创建HTTP服务器
使用Go语言编写基本HTTP服务器
import (
"net/http"
)
func print(w http.ResponseWriter, r *http.Request) { //查看或操作请求,再将响应返回给客户端
w.Write([]byte("Crush On You\n")) //生成HTTP状态响应包,包含状态,报头,响应体
}
func main() {
http.HandleFunc("/", print) //创建路由'/',这个方法接受一个模式和一个函数,前者描述了路径,后者指定如何对发送到该路径的请求做出响应
http.ListenAndServe(":8000", nil) //启动服务器,监听localhost和端口8000
}
使用命令行工具发起HTTP请求 curl http://localhost:8000
可以收到来自Web服务器的响应
StatusCode : 200
StatusDescription : OK
Content : Crush On You
RawContent : HTTP/1.1 200 OK //使用协议,状态码
Content-Length: 13 //响应长度
Content-Type: text/plain; charset=utf-8 //内容类型及编码
Date: Mon, 08 Jun 2020 05:26:05 GMT
Crush On You //响应体
Forms : {}
Headers : {[Content-Length, 13], [Content-Type, text/plain; charset=utf-8],
[Date, Mon, 08 Jun 2020 05:26:05 GMT]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 13
原本默认情况下,所有当前路由的子路由都可以定向到它,可以在处理默认路由的函数中检查路径,如果路径不对就直接返回404错误,
func helloWorld(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Write([]byte("Hello World\n"))
}
go语言支持设置响应的报头。假设服务器将发送一些JSON数据,通过设置Content-Type报头,服务器可告诉客户端,发送的是JSON数据。处理程序函数可使用ResponseWriter来添加报
func helloWorld(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8") //添加报头
w.Write([]byte(`{"hello": "world"}`))
}
func main() {
http.HandleFunc("/", helloWorld)
http.ListenAndServe(":8000", nil)
}
通过curl请求后得到结果
StatusCode : 200
StatusDescription : OK
Content : {"hello": "world"}
RawContent : HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json; charset=utf-8
Date: Mon, 08 Jun 2020 07:32:08 GMT
{"hello": "world"}
Forms : {}
Headers : {[Content-Length, 18], [Content-Type, application/json; charset=ut
f-8], [Date, Mon, 08 Jun 2020 07:32:08 GMT]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 18
可以通过switch语句控制,根据不同类型的内容响应不同的东西
func helloWorld(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
switch r.Header.Get("Accept") {
case "application/json":
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Write([]byte(`{"message": "Hello World"}`))
case "application/xml":
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?><Message>Hello World</Message>`))
default:
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write([]byte("Hello World\n"))
}
}
func main() {
http.HandleFunc("/", helloWorld)
http.ListenAndServe(":8000", nil)
}
创建HTTP客户端
发出GET请求
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
response, err := http.Get("http://localhost:8000")
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", body)
}
发出POST请求
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
func main() {
postData := strings.NewReader(`{ "some": "json" }`)
response, err := http.Post("http://localhost:8000", "application/json", postData)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", body)
}
使用自定义客户端:
- 不使用
net/http
包里的快捷方法Get,创建一个HTTP客户端 - 使用方法
NewRequest
发出GET请求 - 使用方法Do发送请求并处理响应
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
client := &http.Client{}
request, err := http.NewRequest("GET", "https://ifconfig.co", nil)
if err != nil {
log.Fatal(err)
}
response, err := client.Do(request)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", body)
}
使用自定义HTTP客户端意味着可对请求设置报头、基本身份验证和cookies,建议使用。