## 1.5. 获取URL
对于很多现代应用来说,访问互联网上的信息和访问本地文件系统一样重要。Go语言在net这个强大package的帮助下提供了一系列的package来做这件事情,使用这些包可以更简单地用网络收发信息,还可以建立更底层的网络连接,编写服务器程序。在这些情景下,Go语言原生的并发特性(在第八章中会介绍)显得尤其好用。
为了最简单地展示基于HTTP获取信息的方式,下面给出一个示例程序fetch,这个程序将获取对应的url,并将其源文本打印出来;这个例子的灵感来源于curl工具(译注:unix下的一个用来发http请求的工具,具体可以man curl)。当然,curl提供的功能更为复杂丰富,这里只编写最简单的样例。这个样例之后还会多次被用到。
<u><i>gopl.io/ch1/fetch</i></u>
```go
// Fetch prints the content found at a URL.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
func main() {
for _, url := range os.Args[1:] {
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
fmt.Printf("%s", b)
}
}
```
这个程序从两个package中导入了函数,net/http和io/ioutil包,http.Get函数是创建HTTP请求的函数,如果获取过程没有出错,那么会在resp这个结构体中得到访问的请求结果。resp的Body字段包括一个可读的服务器响应流。ioutil.ReadAll函数从response中读取到全部内容;将其结果保存在变量b中。resp.Body.Close关闭resp的Body流,防止资源泄露,Printf函数会将结果b写出到标准输出流中。
```
$ go build gopl.io/ch1/fetch
$ ./fetch http://gopl.io
<html>
<head>
<title>The Go Programming Language</title>title>
...
```
HTTP请求如果失败了的话,会得到下面这样的结果:
```
$ ./fetch http://bad.gopl.io
fetch: Get http://bad.gopl.io: dial tcp: lookup bad.gopl.io: no such host
```
译注:在大天朝的网络环境下很容易重现这种错误,下面是Windows下运行得到的错误信息:
```
$ go run main.go http://gopl.io
fetch: Get http://gopl.io: dial tcp: lookup gopl.io: getaddrinfow: No such host is known.
```
无论哪种失败原因,我们的程序都用了os.Exit函数来终止进程,并且返回一个status错误码,其值为1。
**练习 1.7:** 函数调用io.Copy(dst, src)会从src中读取内容,并将读到的结果写入到dst中,使用这个函数替代掉例子中的ioutil.ReadAll来拷贝响应结构体到os.Stdout,避免申请一个缓冲区(例子中的b)来存储。记得处理io.Copy返回结果中的错误。
**练习 1.8:** 修改fetch这个范例,如果输入的url参数没有 `http://` 前缀的话,为这个url加上该前缀。你可能会用到strings.HasPrefix这个函数。
**练习 1.9:** 修改fetch打印出HTTP协议的状态码,可以从resp.Status变量得到该状态码。
- 前言
- Go语言起源
- Go语言项目
- 本书的组织
- 更多的信息
- 致谢
- 入门
- Hello, World
- 命令行参数
- 查找重复的行
- GIF动画
- 获取URL
- 并发获取多个URL
- Web服务
- 本章要点
- 程序结构
- 命名
- 声明
- 变量
- 赋值
- 类型
- 包和文件
- 作用域
- 基础数据类型
- 整型
- 浮点数
- 复数
- 布尔型
- 字符串
- 常量
- 复合数据类型
- 数组
- Slice
- Map
- 结构体
- JSON
- 文本和HTML模板
- 函数
- 函数声明
- 递归
- 多返回值
- 错误
- 函数值
- 匿名函数
- 可变参数
- Deferred函数
- Panic异常
- Recover捕获异常
- 方法
- 方法声明
- 基于指针对象的方法
- 通过嵌入结构体来扩展类型
- 方法值和方法表达式
- 示例: Bit数组
- 封装
- 接口
- 接口是合约
- 接口类型
- 实现接口的条件
- flag.Value接口
- 接口值
- sort.Interface接口
- http.Handler接口
- error接口
- 示例: 表达式求值
- 类型断言
- 基于类型断言识别错误类型
- 通过类型断言查询接口
- 类型分支
- 示例: 基于标记的XML解码
- 补充几点
- Goroutines和Channels
- Goroutines
- 示例: 并发的Clock服务
- 示例: 并发的Echo服务
- Channels
- 并发的循环
- 示例: 并发的Web爬虫
- 基于select的多路复用
- 并发的退出
- 示例: 聊天服务
- 基于共享变量的并发
- 竞争条件
- sync.Mutex互斥锁
- sync.RWMutex读写锁
- 内存同步
- 竞争条件检测
- 示例: 并发的非阻塞缓存
- Goroutines和线程
- 包和工具
- 包简介
- 导入路径
- 包声明
- 导入声明
- 包的匿名导入
- 包和命名
- 工具
- 测试
- go test
- 测试函数
- 测试覆盖率
- 基准测试
- 剖析
- 示例函数
- 反射
- 为何需要反射?
- reflect.Type和reflect.Value
- Display递归打印
- 示例: 编码S表达式
- 通过reflect.Value修改值
- 示例: 解码S表达式
- 显示一个类型的方法集
- 几点忠告
- 底层编程
- unsafe.Sizeof, Alignof 和 Offsetof
- unsafe.Pointer
- 示例: 深度相等判断
- 通过cgo调用C代码
- 几点忠告
- 附录
- 附录A:原文勘误
- 附录B:作者译者
- 附录C:译文授权
- 附录D:其它语言