【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
}

然后访问就可以在屏幕看到字符串Crush On You

使用命令行工具发起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,建议使用。

posted @ 2020-06-06 18:24  Greenty  阅读(164)  评论(0编辑  收藏  举报