# Day09 标准库net/http与context

Go语言内置的net/http包提供了HTTP客户端和服务端的实现

# HTTP 客户端

  • GET请求示例
func getDemo() {
	resp, err := http.Get("https://httpbin.org/get")
	if err != nil {
		fmt.Println(err)
		return
	}
	// 注意点:程序在使用完response后必须关闭回复的主体
	defer resp.Body.Close()

	// 借助特定工具 ioutil.ReadAll
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%s\n", b)
}

resp, err := http.Get("http://example.com/")
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}})
  • 带参数的GET请求示例
func getByParamDemo() {
	apiUrl := "https://httpbin.org/get"
	param := url.Values{}
	param.Set("name", "linda")
	param.Set("age", "18")

	u, err := url.ParseRequestURI(apiUrl)
	if err != nil {
		fmt.Println("ParseRequestURI Err:", err)
		return
	}
	u.RawQuery = param.Encode() // URL encode  age=18&name=linda
	fmt.Println("Request Info:", u.String())

	resp, err := http.Get(u.String())
	if err != nil {
		fmt.Println("Get Err:", err)
		return
	}
	// resp *Response 延时关闭
	defer resp.Body.Close()

	// 借助特定工具 ioutil.ReadAll
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%s\n", b)
}
  • POST请求示例
func postDemo() {
	url := "https://httpbin.org/post"

    // 表单数据
	//contentType := "application/x-www-form-urlencoded"
	//data := "name=linda&age=18"

    // json 数据
	contentType := "application/json"
	data := `{"key": "value"}`
	resp, err := http.Post(url, contentType, strings.NewReader(data))
	if err != nil {
		fmt.Println(err)
		return
	}
	// resp *Response 延时关闭
	defer resp.Body.Close()

	// 借助特定工具 ioutil.ReadAll
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%s\n", b)
}
  • 自定义Client
func client_demo() {
	tr := &http.Transport{
		DisableCompression: true,
	}
	client := &http.Client{
		Transport: tr,
		Timeout:   5 * time.Second,
	}

	resp, err := client.Get("https://httpbin.org/get")
	if err != nil {
		fmt.Println(err)
		return
	}
	// resp *Response 延时关闭
	defer resp.Body.Close()

	// 借助特定工具 ioutil.ReadAll
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%s\n", b)
}

# HTTP 服务端

ListenAndServe使用指定的监听地址和处理器启动一个HTTP服务端

func sayHello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Hello World!")
}

func sayIndex(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Hello Index!")
}

func server_demo() {
	http.HandleFunc("/", sayHello)
	http.HandleFunc("/index", sayIndex)

	err := http.ListenAndServe(":8090", nil)
	if err != nil {
		fmt.Printf("http server failed, err:%v\n", err)
		return
	}
}

# Context 背景介绍

  • 基本示例
var wg sync.WaitGroup

func worker() {
	for {
		fmt.Println("worker")
		time.Sleep(time.Second)
	}

	// 如何接收外部命令实现退出
	wg.Done()
}

func main() {
	wg.Add(1)
	go worker()

	// 如何优雅的实现结束子goroutine
	wg.Wait()
	fmt.Println("Done")
}
  • 全局变量方式
var wg sync.WaitGroup
var exit bool

// 全局变量方式存在的问题:
// 1. 使用全局变量在跨包调用时不容易统一;
// 2. 如果worker中再启动goroutine,就不太好控制;

func worker() {
	for {
		fmt.Println("worker")
		time.Sleep(time.Second)
		if exit {
			break
		}
	}

	wg.Done()
}

func main() {
	wg.Add(1)
	go worker()

	time.Sleep(3 * time.Second) // sleep3秒以免程序过快退出
	exit = true                 // 修改全局变量实现子goroutine的退出

	wg.Wait()
	fmt.Println("Done")
}
  • 通道方式
var wg sync.WaitGroup

// 管道方式存在的问题:
// 1. 使用全局变量在跨包调用时不容易实现规范和统一,需要维护一个共用的channel

func worker(exitChan chan struct{}) {
LOOP:
	for {
		fmt.Println("worker")
		time.Sleep(time.Second)

		select {
		case <-exitChan: // 等待接收上级通知
			break LOOP
		default:
		}
	}

	wg.Done()
}

func main() {
	var exitChan = make(chan struct{})

	wg.Add(1)
	go worker(exitChan)

	time.Sleep(3 * time.Second) // sleep3秒以免程序过快退出
	exitChan <- struct{}{}      // 修改全局变量实现子goroutine的退出
	close(exitChan)

	wg.Wait()
	fmt.Println("Done")
}
  • 官方版本
var wg sync.WaitGroup

func worker(ctx context.Context) {
LOOP:
	for {
		fmt.Println("worker")
		time.Sleep(time.Second)

		select {
		case <-ctx.Done(): // 等待接收上级通知
			break LOOP
		default:
		}
	}

	wg.Done()
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	wg.Add(1)
	go worker(ctx)

	time.Sleep(3 * time.Second) // sleep3秒以免程序过快退出
	cancel()                    // 通知子goroutine结束

	wg.Wait()
	fmt.Println("Done")
}

当子goroutine又开启另外一个goroutine时,只需要将ctx传入即可:

var wg sync.WaitGroup

func worker(ctx context.Context) {
	go worker2(ctx)
LOOP:
	for {
		fmt.Println("worker")
		time.Sleep(time.Second)
		select {
		case <-ctx.Done(): // 等待上级通知
			break LOOP
		default:
		}
	}
	wg.Done()
}

func worker2(ctx context.Context) {
LOOP:
	for {
		fmt.Println("worker2")
		time.Sleep(time.Second)
		select {
		case <-ctx.Done(): // 等待上级通知
			break LOOP
		default:
		}
	}
}
func main() {
	ctx, cancel := context.WithCancel(context.Background())
	wg.Add(1)
	go worker(ctx)
	time.Sleep(time.Second * 3)
	cancel() // 通知子goroutine结束
	wg.Wait()
	fmt.Println("over")
}
上次更新: 12/6/2022, 12:00:55 PM