前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Go语言入门经典》19~21章读书笔记

《Go语言入门经典》19~21章读书笔记

作者头像
跑马溜溜的球
发布2020-12-07 14:47:54
1.3K0
发布2020-12-07 14:47:54
举报
文章被收录于专栏:日积月累1024

第19章创建HTTP客户端

19.2 发出GET请求

Go语言在net/http包中提供了一个快捷方法,可用于发出简单的GET请求。使用这个方法意味着不需要考虑如何配置HTTP客户端以及如何设置请求报头。如果只是要从远程网站获取一些数据,那么默认配置完全够用。

代码语言:javascript
复制
package main

import (
    "net/http"
        "fmt"
        "io/ioutil"
        "log"
)

func main(){
    response, err := http.Get("https://ifconfig.io/")
    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)
}

19.3 发出POST请求

标准库中的net/http包也提供了用于发出简单POST请求的快捷方法——Post,它支持设置内容类型以及发送数据。

代码语言:javascript
复制
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

func main() {
	postData := strings.NewReader(`{"some":"json"}`)
	response, err := http.Post("https://httpbin.org/post", "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)
}

19.4 进一步控制HTTP请求

要进一步控制HTTP请求,应使用自定义的HTTP客户端。您可使用net/http包提供的默认HTTP客户端,但这将自动使用默认设置,除非您手工修改这些设置。下例使用的是设置为默认的自定义HTTP客户端。

代码语言:javascript
复制
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)
	defer response.Body.Close()

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s", body)
}

对为使用自定义HTTP客户端所做的修改解读如下。

  • 不使用net/http包的快捷方法Get,而创建一个HTTP客户端。
  • 使用方法NewRequest向https://ifconfig.co发出GET请求。
  • 使用方法Do发送请求并处理响应。

使用自定义HTTP客户端意味着可对请求设置报头、基本身份验证和cookies。鉴于使用快捷方法和自定义HTTP客户端时,发出请求所需代码的差别很小,建议除非要完成的任务非常简单,否则都使用自定义HTTP客户端。

19.5 调试HTTP请求

创建HTTP客户端时,了解收发请求和响应的报头和数据对整个流程很有用。为此,可使用标准库中的fmt包来输出各项数据,但net/http/httputil也提供了能够让您轻松调试HTTP客户端和服务器的方法。这个包中的方法DumpRequestOut和DumpResponse能够让您查看请求和响应。

可在调试时添加这些方法,并在调试完毕后删除它们,但还有一种选择,那就是使用环境变量来开关调试。标准库中的os包支持读取环境变量,这能够让您轻松地开关调试。

获取环境变量

代码语言:javascript
复制
os.Getevn(变量名)

输出请求

代码语言:javascript
复制
debugRequest, err := httputil.DumpRequestOut(request, true)
fmt.Printf("%s", debugRequest)

得到类似如下的数据

代码语言:javascript
复制
GET / HTTP/1.1
Host: ifconfig.co
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

输出响应

代码语言:javascript
复制
debugResponse, err := httputil.DumpResponse(response, true)
fmt.Printf("%s", debugResponse)

响应中包含response header

19.6 处理超时

HTTP事务会为接收响应等待一定的时间。客户端向服务器发送请求后,完全无法知道响应会在多长时间内返回。在底层,有大量影响响应速度的变数。

  • DNS查找速度。
  • 打开到服务器IP地址的TCP套接字的速度。
  • 建立TCP连接的速度。
  • TLS握手的速度(如果连接是TLS的)。
  • 向服务器发送数据的速度。
  • 重定向的速度。
  • Web服务器返回响应的速度。
  • 将数据传输到客户端的速度。
代码语言:javascript
复制
import(
    "net/http"
	"time"
)

client := &http.Client{
	Timeout: 50 * time.Microsecond
}

上述配置要求客户端在50ms内完成请求。

通过创建一个传输(transport)并将其传递给客户端,可更细致地控制超时:控制HTTP连接的各个阶段。在大多数情况下,使用Timeout就足以控制整个HTTP事务,但在Go语言中,还可通过创建传输来控制HTTP事务的各个部分。

代码语言:javascript
复制
import (
	"net"
	"net/http"
	"time"
)
    tr := &http.Transport{
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		TLSHandshakeTimeout:   10 * time.Second,
		IdleConnTimeout:       90 * time.Second,
		ResponseHeaderTimeout: 10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}
	
	client := &http.Client{
		Transport: tr,
	}

19.8 问与答

问:能够同时发出多个HTTP请求吗?

答:可以。通过使用goroutine,客户端可同时发出多个HTTP请求。

问:能够根据返回HTTP状态码调整程序采取的措施吗?

答:可以。可通过Response.StatusCode来访问响应的状态码,因此可编写基于服务器响应的逻辑。

第20章处理JSON

20.4 解码JSON

JSON解码也是一种常见的网络编程任务。收到的数据可能来自数据库、API调用或配置文件。原始JSON就是文本格式的数据,在Go语言中可表示为字符串。函数Unmarshal接受一个字节切片以及一个指定要将数据解码为何种格式的接口。根据数据是如何收到的,它可能是字节切片,也可能不是。如果不是字节切片,就必须先进行转换,再将其传递给函数Unmarshal。

代码语言:javascript
复制
	jsonStringData := `{"name":"George", "age":40, "hobbies":["Cycling", "Cheese"]}`
	//转为字节切片
	jsonByteData := []byte(jsonStringData)

与将数据编码为JSON格式一样,必须定义一个接口,以指定要将数据解码为何种格式。与将数据编码为JSON格式一样,可使用结构体标签来告诉解码器如何将键映射到字段。

代码语言:javascript
复制
type Person struct {
	Name    string   `json:"name"`
	Age     int      `json:"Age"`
	Hobbies []string `json:"hobbies"`
}

下例演示了如何将JSON字符串数据转换为字节切片,再使用json.Unmarshal进行解码。

代码语言:javascript
复制
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Person struct {
	Name    string   `json:"name"`
	Age     int      `json:"Age"`
	Hobbies []string `json:"hobbies"`
}

func main() {
	jsonStringData := `{"name":"George", "age":40, "hobbies":["Cycling", "Cheese"]}`
	jsonByteData := []byte(jsonStringData)

	p := Person{}
	err := json.Unmarshal(jsonByteData, &p)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%+v\n", p)
}

结果

代码语言:javascript
复制
{Name:George Age:40 Hobbies:[Cycling Cheese]}

20.5 映射数据类型

JSON数据类型不会自动映射到Go语言中的数据类型,因此encoding/json包执行显式的数据类型转换。下表显示了JSON数据类型和Go数据类型之间的对应关系。

JSON

GO

Boolean

bool

Number

float64

String

string

Array

[]interface{}

Object

map[string]interface{}

Null

nil

创建用于编码和解码JSON的结构体时,必须对上述数据类型的对应关系做到心中有数,因为如果数据类型不匹配,encoding/ json包将引发错误。

下列一个将JSON字符串解码为结构体的示例,您认为结果将如何呢?

代码语言:javascript
复制
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Switch struct {
	On bool `json:"on"`
}

func main() {
	jsonStringData := `{"on":"true"}`
	jsonByteData := []byte(jsonStringData)

	s := Switch{}
	err := json.Unmarshal(jsonByteData, &s)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%+v\n", s)
}

如果您运行这个示例,将出现错误,因为在JSON中,值true实际上是一个字符串,因为它被放在引号内。Go解码器试图将这个值转换为Go布尔值,但由于这是一个字符串,这种转换是不可能的,因此进而引发致命错误。

代码语言:javascript
复制
json: cannot unmarshal string into Go struct field Switch.on of type bool

20.6 处理通过HTTP收到的JSON

在Go语言中,通过HTTP请求获取JSON时,收到的数据为流而不是字符串或字节切片。

由于获取的数据为流,因此可使用encoding/json包中的函数NewDecoder。这个函数接受一个io.Reader(这正是http.Get返回的类型),并返回一个Decoder。通过对返回的Decoder调用方法Decode,可将数据解码为结构体。与以前一样,Decode也接受一个结构体,因此必须创建一个结构体实例,并将其作为参数传递给Decode。下面是一个完整的示例,将获取的数据解码为一个Go结构体。与以前一样,必要时可使用结构体标签将JSON响应中的字段映射到结构体字段。

代码语言:javascript
复制
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type User struct {
	Name string `json:"name"`
	Blog string `json:"blog"`
}

func main() {
	var u User
	res, err := http.Get("https://api.github.com/users/shapeshed")
	if err != nil {
		log.Fatal(err)
	}

	defer res.Body.Close()

	err = json.NewDecoder(res.Body).Decode(&u)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%+v\n", u)
}

20.9 作业

必须将JSON对象中的所有字段都解码到结构体中吗?

不是这样的,可定义只包含您感兴趣的字段的结构体。您可使用结构体标签来将JSON字段映射到Go结构体字段。

如果一个结构体字段可能为空,那么该使用哪个结构体标签?在这种情况下,如果该字段确实为空,结果将如何呢?

如果一个字段可能为空,应给它添加结构体标签omitempty。这样解码时,如果该字段确实为空,将忽略它。

第21章处理文件

21.2 使用ioutil包读写文件

21.2.1 读取文件

读取文件是最常见的操作之一。ioutil包提供了函数Readfile,您可使用它来完成这项任务,这个函数将一个文件名作为参数,并以字节切片的方式返回文件的内容。这意味着如果要将文件内容作为字符串使用,则必须将返回的字节切片转换为字符串。

代码语言:javascript
复制
package main

import (
	"fmt"
	"io/ioutil"
	"log"
)

func main() {
	fileBytes, err := ioutil.ReadFile("demo.txt")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(fileBytes)

	fileString := string(fileBytes)
	fmt.Println(fileString)
}

对程序清单21.1解读如下。

  • 使用ioutil包中的函数Readfile读取文件。
  • 这个函数返回一个字节切片。
  • 将返回的字节切片转换为字符串。
  • 将字符串打印到终端,以显示文件的内容。

21.2.2 创建文件

ioutil包还提供了用于创建文件的便利函数WriteFile。这个函数设计用于将数据写入文件,但也可使用它来创建文件。函数WriteFile接受一个文件名、要写入文件的数据以及应用于文件的权限。

符号表示法是数字表示法的视觉表示。符号表示法总共包含10个字符。最左边的字符指出了文件是普通文件、目录还是其他东西,如果这个字符为-,就表示文件为普通文件;接下来的3个字符指定了文件所有者的权限;再接下来的3个字符表示所有者所在用户组的权限;而最后3个字符表示其他人的权限。

在UNIX型系统中,文件的默认权限为0644,即所有者能够读取和写入,而其他人只能读取。

代码语言:javascript
复制
package main

import (
	"io/ioutil"
	"log"
)

func main() {
	b := make([]byte, 0)
	err := ioutil.WriteFile("demo.txt", b, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

解读如下。

  • 函数WriteFile接受一个字节切片,因此创建一个空字节切片,并将其赋给变量b。
  • 调用函数WriteFile,并向它传递文件名、空字节切片以及要给文件设置的权限。
  • 如果没有错误,将创建指定的文件。

这里给函数WriteFile传递了空字节切片,这是一种使用ioutil包中便利函数的技巧。函数WriteFile在指定的文件不存在时创建它,因此也可使用这个函数来创建空文件。

21.3 写入文件

正如您预期的,函数WriteFile也可用来写入文件。要写入文件,只需传入一些值,而不是传入空字节切片。要将字符串写入文件,必须先将其转换为字节切片。

代码语言:javascript
复制
s := "Hello World"
err := ioutil.WriteFile("demo.txt", []byte(s), 0644)

21.4 列出目录的内容

要处理文件系统中的文件,必须知道目录结构。为此,ioutil包提供了便利函数ReadDir,它接受以字符串方式指定的目录名,并返回一个列表,其中包含按文件名排序的文件。文件名的类型为FileInfo,包含如下信息。

  • Name:文件的名称。
  • Size:文件的长度,单位为字节。
  • Mode:用二进制位表示的权限。
  • ModTime:文件最后一个被修改的时间。
  • IsDir:文件是否是目录。
  • Sys:底层数据源。

下面的代码列出了目录中文件的权限,文件名及大小。

代码语言:javascript
复制
package main

import (
	"fmt"
	"io/ioutil"
	"log"
)

func main() {
	files, err := ioutil.ReadDir(".")
	if err != nil {
		log.Fatal(err)
	}

	for _, file := range files {
		fmt.Println(file.Mode(), file.Name(), file.Size())
	}
}

21.5 复制文件

ioutil包可用于执行一些常见的文件处理操作,但要执行更复杂的操作,应使用os包。os包运行在稍低的层级,因此使用它时,必须手工关闭打开的文件。

要复制文件,只需结合使用os包中的几个函数。以编程方式复制文件的步骤如下。 1.打开要复制的文件。 2.读取其内容。 3.创建并打开要将这些内容复制到其中的文件。 4.将内容写入这个文件。 5.关闭所有已打开的文件。

代码语言:javascript
复制
package main

import (
	"io"
	"log"
	"os"
)

func main() {
	from, err := os.Open("demo.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer from.Close()

	to, err := os.OpenFile("demo.copy.txt", os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		log.Fatal(err)
	}

	_, err = io.Copy(to, from)
	if err != nil {
		log.Fatal(err)
	}

}

解读如下。

  • 使用os包中的函数Open来读取磁盘文件。
  • 使用defer语句在程序完成其他所有操作后关闭文件。
  • 使用函数OpenFile打开文件。第一个参数是要打开(如果不存在,就创建)的文件的名称;第二个参数是用于文件的标志,在这里指定的是读写文件,并在文件不存在时创建它;最后一个参数设置文件的权限。
  • 再次使用defer语句在执行完其他操作后关闭文件。
  • 使用io包中的函数Copy复制源文件的内容,并将其写入目标文件。

21.6 删除文件

os包提供了函数Remove,它能够将文件或文件夹删除。需要指出的是,使用这个函数时,不会发出警告,您也无法将删除的文件恢复,因此务必要谨慎。

代码语言:javascript
复制
os.Remove("filename")
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/07/06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第19章创建HTTP客户端
    • 19.2 发出GET请求
      • 19.3 发出POST请求
        • 19.4 进一步控制HTTP请求
          • 19.5 调试HTTP请求
            • 19.6 处理超时
              • 19.8 问与答
              • 第20章处理JSON
                • 20.4 解码JSON
                  • 20.5 映射数据类型
                    • 20.6 处理通过HTTP收到的JSON
                      • 20.9 作业
                      • 第21章处理文件
                        • 21.2 使用ioutil包读写文件
                          • 21.2.1 读取文件
                          • 21.2.2 创建文件
                        • 21.3 写入文件
                          • 21.4 列出目录的内容
                            • 21.5 复制文件
                              • 21.6 删除文件
                              相关产品与服务
                              多因子身份认证
                              多因子身份认证(Multi-factor Authentication Service,MFAS)的目的是建立一个多层次的防御体系,通过结合两种或三种认证因子(基于记忆的/基于持有物的/基于生物特征的认证因子)验证访问者的身份,使系统或资源更加安全。攻击者即使破解单一因子(如口令、人脸),应用的安全依然可以得到保障。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档