Improve chapter 15 (#684)

* improve chapter 15.0

* improve chapter 15.1

* improve chapter 15.2

* improve chapter 15.3

* improve chapter 15.4

* improve chapter 15.6
This commit is contained in:
marjune
2019-07-19 11:59:53 +08:00
committed by ᴊ. ᴄʜᴇɴ
parent e84480c03f
commit a1f14b61aa
6 changed files with 109 additions and 106 deletions

View File

@@ -1,9 +1,9 @@
# 15.0 网络,模板和网页应用
go在编写web应用方面非常得力。因为目前它还没有GUIGraphic User Interface 即图形化用户界面的框架通过文本或者模板展现的html面是目前go编写应用程序的唯一方式。(**译者注实际上在翻译的时候已经有了一些不太成熟的GUI库例如go ui。**
Go 在编写 web 应用方面非常得力。因为目前它还没有GUIGraphic User Interface 即图形化用户界面)的框架,通过文本或者模板展现的 html面是目前 Go 编写界面应用程序的唯一方式。(**译者注实际上在翻译的时候已经有了一些不太成熟的GUI库例如go ui。**
## 链接
- [目录](directory.md)
- 上一节:[使用通道channel并发修改对象数据](14.17.md)
- 下一节:[一个Tcp服务器](15.1.md)
- 上一节:[使用通道并发访问对象](14.17.md)
- 下一节:[tcp服务器](15.1.md)

View File

@@ -1,8 +1,8 @@
# 15.1 tcp服务器
这部分我们将使用TCP协议和在14章讲到的协程范式编写一个简单的客户端-服务器应用一个web服务器应用需要响应众多客户端的并发请求go会为每一个客户端产生一个协程用来处理请求。我们需要使用net包中网络通信的功能。它包含了用于TCP/IP以及UDP协议、域名解析等方法。
这部分我们将使用 TCP 协议和在 14 章讲到的协程范式编写一个简单的客户端-服务器应用一个web服务器应用需要响应众多客户端的并发请求Go 会为每一个客户端产生一个协程用来处理请求。我们需要使用 net 包中网络通信的功能。它包含了处理 TCP/IP 以及 UDP 协议、域名解析等方法。
服务器代码单独的一个文件:
服务器代码是一个单独的文件:
示例 15.1 [server.go](examples/chapter_15/server.go)
@@ -44,12 +44,11 @@ func doServerStuff(conn net.Conn) {
fmt.Printf("Received data: %v", string(buf[:len]))
}
}
```
我们`main()`创建了一个`net.Listener`的变量,他是一个服务器的基本函数:用来监听和接收来自客户端的请求(来自localhostIP地址为127.0.0.1端口为50000基于TCP协议这个`Listen()`函数可以返回一个`error`类型的错误变量。用一个无限for循环的`listener.Accept()`来等待客户端的请求。客户端的请求将产生一个`net.Conn`类型的连接变量。然后一个独立的协程使用这个连接执行`doServerStuff()`开始使用一个512字节的缓冲`data`来读取客户端发送来的数据并且把它们打印到服务器的终端,`len`获取客户端发送的数据字节数;当客户端发送的所有数据都被读取完成时,协程就结束了。这段程序会为每一个客户端连接创建一个独立的协程。必须先运行服务器代码,再运行客户端代码。
`main()`创建了一个 `net.Listener` 类型的变量 `listener`,他实现了服务器的基本功能:用来监听和接收来自客户端的请求(localhostIP 地址为 127.0.0.1 端口为 50000 基于TCP协议`Listen()` 函数可以返回一个 `error` 类型的错误变量。用一个无限 for 循环的 `listener.Accept()` 来等待客户端的请求。客户端的请求将产生一个 `net.Conn` 类型的连接变量。然后一个独立的协程使用这个连接执行 `doServerStuff()`,开始使用一个 512 字节的缓冲 `data` 来读取客户端发送来的数据并且把它们打印到服务器的终端,`len` 获取客户端发送的数据字节数;当客户端发送的所有数据都被读取完成时,协程就结束了。这段程序会为每一个客户端连接创建一个独立的协程。必须先运行服务器代码,再运行客户端代码。
客户端代码写在另一个文件client.go中
客户端代码写在另一个文件 client.go 中:
示例 15.2 [client.go](examples/chapter_15/client.go)
@@ -92,21 +91,21 @@ func main() {
}
}
```
客户端通过`net.Dial`创建了一个和服务器之间的连接
客户端通过 `net.Dial` 创建了一个和服务器之间的连接
它通过无限循环中的os.Stdin接收来自键盘的输入直到输入了“Q”。注意使用`\r``\n`换行符分割字符串在windows平台下使用`\r\n`)。接下来分割后的输入通过`connection``Write`方法发送到服务器。
它通过无限循环`os.Stdin` 接收来自键盘的输入直到输入了“Q”。注意裁剪 `\r``\n` 字符(仅 Windows 平台需要)。裁剪后的输入`connection``Write` 方法发送到服务器。
当然,服务器必须先启动好,如果服务器并未开始监听,客户端是无法成功连接的。
如果在服务器没有开始监听的情况下运行客户端程序,客户端会停止并打印出以下错误信息:`对tcp 127.0.0.1:50000发起连接时产生错误由于目标计算机的积极拒绝而无法创建连接`
打开控制台并转到服务器和客户端可执行程序所在的目录Windows系统下输入server.exe或者只输入serverLinux系统下输入./server。
打开命令提示符并转到服务器和客户端可执行程序所在的目录Windows 系统下输入server.exe或者只输入serverLinux系统下输入./server。
接下来控制台出现以下信息:`Starting the server ...`
在 Windows 系统中,我们可以通过 CTRL/C 停止程序。
然后开启2个或者3个独立的控制台窗口,然后分别输入client回车启动客户端程序
然后开启 2 个或者 3 个独立的控制台窗口,分别输入 client 回车启动客户端程序
以下是服务器的输出:
@@ -121,9 +120,9 @@ Received data: MARC says: Don't forget our appointment tomorrow !
```
Error reading WSARecv tcp 127.0.0.1:50000: The specified network name is no longer available.
```
在网络编程中`net.Dial`函数是非常重要的一旦你连接到远程系统就会返回一个Conn类型接口我们可以用它发送和接收数据。`Dial`函数巧妙的抽象了网络结构及传输。所以IPv4或者IPv6TCP或者UDP都可以使用这个公用接口。
在网络编程中 `net.Dial` 函数是非常重要的,一旦你连接到远程系统,函数就会返回一个 `Conn` 类型接口,我们可以用它发送和接收数据。`Dial` 函数简洁地抽象了网络层和传输。所以不管是 IPv4 还是 IPv6TCP 或者 UDP 都可以使用这个公用接口。
边这个示例先使用TCP协议连接远程80端口然后使用UDP协议连接最后使用TCP协议连接IPv6类型的地址:
下示例先使用 TCP 协议连接远程 80 端口,然后使用 UDP 协议连接,最后使用 TCP 协议连接 IPv6 地址:
示例 15.3 [dial.go](examples/chapter_15/dial.go)
@@ -189,6 +188,7 @@ func main() {
con.Close()
}
```
**练习 15.1**
编写新版本的客户端和服务器([client1.go](exercises/chapter_15/client1.go) / [server1.go](exercises/chapter_15/server1.go)
@@ -196,7 +196,6 @@ func main() {
* 增加一个检查错误的函数 `checkError(error)`;讨论如下方案的利弊:为什么这个重构可能并没有那么理想?看看在 [示例15.14](examples/chapter_15/template_validation.go) 中它是如何被解决的
* 使客户端可以通过发送一条命令 SH 来关闭服务器
* 让服务器可以保存已经连接的客户端列表(他们的名字);当客户端发送 WHO 指令的时候,服务器将显示如下列表:
```
This is the client list: 1:active, 0=inactive
User IVO is 1
@@ -293,11 +292,11 @@ func checkError(error error, info string) {
}
}
```
(**译者注应该是由于go版本的更新会提示os.EAGAIN undefined ,修改后的代码:[simple_tcp_server_v1.go](examples/chapter_15/simple_tcp_server_v1.go)**)
**译者注应该是由于go版本的更新会提示os.EAGAIN undefined ,修改后的代码:[simple_tcp_server_v1.go](examples/chapter_15/simple_tcp_server_v1.go)**
都有哪些改进?
* 服务器地址和端口不再是硬编码,而是通过命令行传入参数并通过`flag`包来读取这些参数。这里使用了`flag.NArg()`检查是否按照期望传入了2个参数
* 服务器地址和端口不再是硬编码,而是通过命令行参数传入,并通过 `flag` 包来读取这些参数。这里使用了 `flag.NArg()` 检查是否按照期望传入了2个参数
```go
if flag.NArg() != 2{
@@ -308,11 +307,11 @@ func checkError(error error, info string) {
```go
hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1))
```
*`initServer`函数中通过`net.ResolveTCPAddr`指定了服务器地址和端口,这个函数最终返回了一个`*net.TCPListener`
* 每一个连接都会以协程的方式运行`connectionHandler`函数。这些开始于当通过`conn.RemoteAddr()`获取到客户端的地址
* 它使用`conn.Write`发送改进的go-message给客户端
* 它使用一个25字节的缓冲读取客户端发送的数据并一一打印出来。如果读取的过程中出现错误代码会进入`switch`语句`default`分支关闭连接。如果是操作系统的`EAGAIN`错误,它会重试。
* 所有的错误检查都被重构在独立的函数'checkError'中,用来分发出现的上下文错误
* `initServer` 函数中通过 `net.ResolveTCPAddr` 得到了服务器地址和端口,这个函数最终返回了一个 `*net.TCPListener`
* 每一个连接都会以协程的方式运行 `connectionHandler` 函数。函数首先通过 `conn.RemoteAddr()` 获取到客户端的地址并显示出来
* 它使用 `conn.Write` 发送 Go 推广消息给客户端
* 它使用一个 25 字节的缓冲读取客户端发送的数据并一一打印出来。如果读取的过程中出现错误,代码会进入 `switch` 语句 `default` 分支,退出无限循环并关闭连接。如果是操作系统的 `EAGAIN` 错误,它会重试。
* 所有的错误检查都被重构在独立的函数 `checkError` 中,当错误产生时,利用错误上下文来触发 panic
在命令行中输入 `simple_tcp_server localhost 50000` 来启动服务器程序,然后在独立的命令行窗口启动一些 client.go 的客户端。当有两个客户端连接的情况下服务器的典型输出如下,这里我们可以看到每个客户端都有自己的地址:
```
@@ -323,8 +322,9 @@ Connection from: 127.0.0.1:49346
Connection from: 127.0.0.1:49347
<25:Marc says: Do you remembe><25:r our first meeting serve><2:r?>
```
net.Error
这个`net`包返回错误的错误类型,下边是约定的写法,不过`net.Error`接口还定义了一些其他的错误实现,有些额外的方法。
`net` 包返回的错误类型遵循惯例为 `error`,但有些错误实现包含额外的方法,他们被定义为 `net.Error` 接口
```go
package net
@@ -333,7 +333,7 @@ type Error interface{
Temporary() bool // 是否是临时错误
}
```
通过类型断言,客户端代码可以用来测试`net.Error`,从而区分哪些临时发生的错误或者必然会出现的错误。举例来说,一个网络爬虫程序在遇到临时发生的错误时可能会休眠或者重试,如果是一个必然发生的错误,则他会放弃继续执行。
通过类型断言,客户端代码可以测试 `net.Error`,从而区分临时发生的还是必然会出现的错误。举例来说,一个网络爬虫程序在遇到临时发生的错误时可能会休眠或者重试,如果是一个必然发生的错误,则他会放弃继续执行。
```go
// in a loop - some function returns an error err
if nerr, ok := err.(net.Error); ok && nerr.Temporary(){

View File

@@ -1,28 +1,26 @@
# 15.2 一个简单的网页服务器
Http是一个比tcp更高的协议,它描述了客户端浏览器如何与网页服务器进行通信。Go有自己的`net/http`包,我们来看看它。我们从一些简单的示例开始,
首先编写一个“Hello world!”:[查看示例15.6](examples/chapter_15/hello_world_webserver.go)
http 是比 tcp 更高的协议,它描述了网页服务器如何与客户端浏览器进行通信。Go有自己的 `net/http` 包,我们来看看它。我们从一些简单的示例开始,首先编写一个“Hello world!”网页服务器:[查看示例15.6](examples/chapter_15/hello_world_webserver.go)
我们引入了`http`包并启动了网页服务器和15.1`net.Listen("tcp", "localhost:50000")`函数的tcp服务器是类似的使用`http.ListenAndServe("localhost:8080", nil)`函数,如果成功会返回空,否则会返回一个错误(可以指定localhost为其他地址8080是指定的端口号
我们引入了 `http` 包并启动了网页服务器,和 [15.1](15.1.md) 的 `net.Listen("tcp", "localhost:50000")` 函数的 tcp 服务器是类似的,使用 `http.ListenAndServe("localhost:8080", nil)` 函数,如果成功会返回空,否则会返回一个错误(地址 localhost 部分可以省略8080 是指定的端口号)
`http.URL`描述了web服务器的地址内含存放了url字符串的`Path`属性`http.Request`描述了客户端请求,内含一个`URL`属性
如果`req`请求是一个POST类型的html表单“var1”就是html表单中一个输入属性的名称然后用户输入的值就可以通过GO代码`req.FormValue("var1")`获取到(请看[章节15.4](15.4.md))。还有一种方法就是先执行`request.ParseForm()`然后再获取`request.Form["var1"]`的第一个返回参数,就像这样:
`http.URL` 用于表示网页地址,其中包含字符串属性 `Path` 保存 url 路径`http.Request` 描述了客户端请求,内含一个 `URL` 字段。
如果 `req` 是来自 html 表单的 POST 类型请求“var1” 是该表单中一个输入域的名称,那么用户输入的值就可以通过 Go 代码 `req.FormValue("var1")` 获取到(请看 [15.4节](15.4.md))。还有一种方法是先执行 `request.ParseForm()`,然后再获取 `request.Form["var1"]` 的第一个返回参数,就像这样:
```go
var1, found := request.Form["var1"]
```
第二个参数`found`就是`true`如果`var1`并未出现在表单中,`found`就是`false`
第二个参数 `found``true`如果 `var1` 并未出现在表单中,`found` 就是 `false`
表单属性实际上是一个`map[string][]string`类型。网页服务器返回了一个`http.Response`,它是通过`http.ResponseWriter`对象输出的,这个对象整合了HTTP服务器的返回结果通过对它写入内容我们就将数据发送给了HTTP客户端。
表单属性实际上是一个 `map[string][]string` 类型。网页服务器返回了一个 `http.Response`,它是通过 `http.ResponseWriter` 对象输出的,它会组装 HTTP 服务器响应;通过对它写入内容,我们就将数据发送给了 HTTP 客户端。
现在我们还需要编写网页服务器必须执行的程序,它是如何处理请求的呢。这是`http.HandleFunc`函数完成的,就是在这个例子中当根路径“/”url地址是http://localhost:8080 )被请求的时候(或者这个服务器上的其他地址),`HelloServer`函数就被执行了。这个函数是`http.HandlerFunc`类型的,它们通常用使用Prefhandler来命名,在前边加了一个Pref前缀
现在我们仍然要编写程序,以实现服务器必须做的事,即如何处理请求。这是通过 `http.HandleFunc` 函数完成的在这个例子中当根路径“/”url地址是 `http://localhost:8080`)被请求的时候(或者这个服务器上的其他任意地址),`HelloServer` 函数就被执行了。这个函数是 `http.HandlerFunc` 类型的,它们通常被命名为 Prefhandler,和某个路径前缀 Pref 匹配
`http.HandleFunc` 注册了一个处理函数(这里是 `HelloServer`)来处理对应 `/` 的请求。
`/`可以被替换为其他特定的url比如`/create``/edit`等等你可以为每一个特定的url定义一个单独的处理函数。这个函数需要两个参数第一个是`ReponseWriter`类型的`w`;第二个是请求`req`。程序向`w`写入了`Hello``r.URL.Path[1:]`组成的字符串后边的`[1:]`表示“创建一个从第一个字符到结尾的子切片”,用来丢弃路径开头的“/”,`fmt.Fprintf()`函数完成了本次写入(请看[章节12.8](12.8.md));另一种写法是`io.WriteString(w, "hello, world!\n")`
`/` 可以被替换为其他特定的 url比如 `/create``/edit` 等等;你可以为每一个特定的 url 定义一个单独的处理函数。这个函数需要两个参数:第一个是 `ReponseWriter` 类型的 `w`;第二个是请求 `req`。程序向 `w` 写入了 `Hello``r.URL.Path[1:]` 组成的字符串:末尾的 `[1:]` 表示“创建一个从索引为 1 的字符到结尾的子切片”,用来丢弃路径开头的“/”,`fmt.Fprintf()` 函数完成了本次写入(请看 [12.8](12.8.md));另一种可行的写法是 `io.WriteString(w, "hello, world!\n")`
总结:第一个参数是请求的路径,第二个参数是处理这个路径请求函数的引用。
总结:第一个参数是请求的路径,第二个参数是路径请求时,需要调用的处理函数的引用。
示例 15.6 [hello_world_webserver.go](examples/chapter_15/hello_world_webserver.go)
@@ -53,9 +51,10 @@ func main() {
Starting Process E:/Go/GoBoek/code_examples/chapter_14/hello_world_webserver.exe
...
```
然后打开你的浏览器并输入url地址`http://localhost:8080/world`,浏览器就会出现文字:`Hello, world`,网页服务器会响应你在`:8080/`后边输入的内容
使用`fmt.Println`在控制台打印状态在每个handler被请求的时候在他们内部打印日志会很有帮助
然后打开浏览器并输入 url 地址:`http://localhost:8080/world`,浏览器就会出现文字:`Hello, world`,网页服务器会响应你在 `:8080/` 后边输入的内容。
`fmt.Println` 在服务器端控制台打印状态;在每个处理函数被调用时,把请求记录下来也许更为有用。
注:
1前两行没有错误处理代码可以替换成以下写法
@@ -63,18 +62,18 @@ Starting Process E:/Go/GoBoek/code_examples/chapter_14/hello_world_webserver.exe
http.ListenAndServe(":8080", http.HandlerFunc(HelloServer))
```
2`fmt.Fprint``fmt.Fprintf`都是用来写入`http.ResponseWriter`的不错的函数(他们实现了`io.Writer`)。
2`fmt.Fprint``fmt.Fprintf` 都是可以用来写入 `http.ResponseWriter` 的函数(他们实现了 `io.Writer`)。
比如我们可以使用
```go
fmt.Fprintf(w, "<h1>%s<h1><div>%s</div>", title, body)
```
来构建一个非常简单的网页并插入`title``body`的值
来构建一个非常简单的网页并插入 `title``body` 的值
如果你需要更多复杂的替换,使用模板包(请看[章节15.7](15.7.md)
如果你需要更多复杂的替换,使用模板包(请看 [15.7](15.7.md)
3如果你需要使用安全的 https 连接,使用 `http.ListenAndServeTLS()` 代替 `http.ListenAndServe()`
4`http.HandleFunc("/", Hfunc)`中的`HFunc`是一个处理函数,如下
4除了 `http.HandleFunc("/", Hfunc)`,其中的 `HFunc` 是一个处理函数,签名为
```go
func HFunc(w http.ResponseWriter, req *http.Request) {
...
@@ -82,25 +81,29 @@ func HFunc(w http.ResponseWriter, req *http.Request) {
```
也可以使用这种方式:`http.Handle("/", http.HandlerFunc(HFunc))`
上边的`HandlerFunc`只是一个类型名称,它定义如下:
`HandlerFunc` 只是一个类型名称,它定义如下:
```go
type HandlerFunc func(ResponseWriter, *Request)
```
它是一个可以把普通的函数当做HTTP处理器的适配器。如果`f`函数声明的合适,`HandlerFunc(f)`就是一个执行了`f`函数的处理器对象。
`http.Handle`的第二个参数也可以是`T`的一个obj对象`http.Handle("/", obj)`给T提供了`ServeHTTP`方法实现了http的`Handler`接口:
它是一个可以把普通的函数当做 HTTP 处理器(`Handler`)的适配器。如果函数 `f` 声明的合适,`HandlerFunc(f)` 就是一个执行 `f` 函数的 `Handler` 对象。
`http.Handle` 的第二个参数也可以是 `T` 类型的对象 obj`http.Handle("/", obj)`
如果 T 有 `ServeHTTP` 方法那就实现了http 的 `Handler` 接口:
```go
func (obj *Typ) ServeHTTP(w http.ResponseWriter, req *http.Request) {
...
}
```
这个用法在[章节15.8](15.8.md)类型`Counter``Chan`上使用过。只要实现了`http.Handler``http`包就可以处理任何HTTP请求。
这个用法也在 [15.8节](15.8.md) `Counter``Chan` 类型上使用。只要实现了 `http.Handler``http` 包就可以处理任何 HTTP 请求。
练习 15.2[webhello2.go](exercises/chapter_15/webhello2.go)
编写一个网页服务器监听端口 9999有如下处理函数
* 当请求`http://localhost:9999/hello/Name`时,响应:`hello Name`(Name需是一个合法的姓比如Chris或者Madeleine)
* 当请求 `http://localhost:9999/hello/Name `时,响应:`hello Name`Name 需是一个合法的姓,比如 Chris 或者 Madeleine
* 当请求 `http://localhost:9999/shouthello/Name` 时,响应:`hello NAME`
@@ -108,7 +111,6 @@ func (obj *Typ) ServeHTTP(w http.ResponseWriter, req *http.Request) {
创建一个空结构 `hello` 并使它实现 `http.Handler`。运行并测试。
## 链接
- [目录](directory.md)

View File

@@ -2,7 +2,7 @@
在下边这个程序中,数组中的 url 都将被访问:会发送一个简单的 `http.Head()` 请求查看返回值;它的声明如下:`func Head(url string) (r *Response, err error)`
返回状态码会被打印出来。
返回的响应 `Response`状态码会被打印出来。
示例 15.7 [poll_url.go](examples/chapter_15/poll_url.go)
@@ -32,19 +32,17 @@ func main() {
}
}
```
输出为:
```
http://www.google.com/ : 302 Found
http://golang.org/ : 200 OK
http://blog.golang.org/ : 200 OK
```
***译者注*** 由于国内的网络环境现状,很有可能见到如下超时错误提示:
```
***译者注*** 由于国内的网络环境现状,很有可能见到如下超时错误提示:
Error: http://www.google.com/ Head http://www.google.com/: dial tcp 216.58.221.100:80: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
```
在下边的程序中我们使用`http.Get()`获取网页内容; `Get`返回值`res`中的`Body`属性包含了网页内容,然后我们用`ioutil.ReadAll`把它读出来:
在下边的程序中我们使用 `http.Get()` 获取并显示网页内容; `Get` 返回值中的 `Body` 属性包含了网页内容,然后我们用 `ioutil.ReadAll` 把它读出来:
示例 15.8 [http_fetch.go](examples/chapter_15/http_fetch.go)
@@ -74,9 +72,9 @@ func checkError(err error) {
```
当访问不存在的网站时,这里有一个`CheckError`输出错误的例子:
```
2011/09/30 11:24:15 Get: Get http://www.google.bex: dial tcp www.google.bex:80:GetHostByName: No such host is known.
```
***译者注*** 和上一个例子相似你可以把google.com更换为一个国内可以顺畅访问的网址进行测试
在下边的程序中,我们获取一个 twitter 用户的状态,通过 `xml` 包将这个状态解析成为一个结构:
@@ -113,22 +111,22 @@ func main() {
fmt.Printf("status: %s", user.Status.Text)
}
```
输出:
```
status: Robot cars invade California, on orders from Google: Google has been testing self-driving cars ... http://bit.ly/cbtpUN http://retwt.me/97p<exit code="0" msg="process exited normally"/>
```
**译者注** 和上边的示例相似你可能无法获取到xml数据另外由于go版本的更新`xml.Unmarshal` 函数的第一个参数需是[]byte类型而无法传入 `Body`
我们会在[章节15.4](15.4.md)中用到`http`包中的其他重要的函数:
我们会在 [15.4](15.4.md) 中用到 `http` 包中的其他重要的函数:
* `http.Redirect(w ResponseWriter, r *Request, url string, code int)`:这个函数会让浏览器重定向到url请求url的相对路径以及状态码。
* `http.Redirect(w ResponseWriter, r *Request, url string, code int)`:这个函数会让浏览器重定向到 `url`(可以是基于请求 url 的相对路径),同时指定状态码。
* `http.NotFound(w ResponseWriter, r *Request)`这个函数将返回网页没有找到HTTP 404错误。
* `http.Error(w ResponseWriter, error string, code int)`:这个函数返回特定的错误信息和 HTTP 代码。
*`http.Request` 对象的一个重要属性 `req``req.Method`,这是一个包含 `GET``POST` 字符串,用来描述网页是以何种方式被请求的。
go为所有的HTTP状态码定义了常量比如
```go
http.StatusContinue = 100
http.StatusOK = 200
http.StatusFound = 302
@@ -137,16 +135,16 @@ go为所有的HTTP状态码定义了常量比如
http.StatusForbidden = 403
http.StatusNotFound = 404
http.StatusInternalServerError = 500
```
你可以使用`w.header().Set("Content-Type", "../..")`设置头信息
你可以使用 `w.header().Set("Content-Type", "../..")` 设置头信息
比如在网页应用发送html字符串的时候在输出之前执行`w.Header().Set("Content-Type", "text/html")`
比如在网页应用发送 html 字符串的时候,在输出之前执行 `w.Header().Set("Content-Type", "text/html")`(通常不是必要的)
练习 15.4:扩展 http_fetch.go 使之可以从控制台读取url使用[章节12.1](12.1.md)学到的接收控制台输入的方法 [http_fetch2.go](examples/chapter_15/http_fetch2.go)
练习 15.4:扩展 http_fetch.go 使之可以从控制台读取url使用 [12.1](12.1.md) 学到的接收控制台输入的方法([http_fetch2.go](examples/chapter_15/http_fetch2.go)
练习 15.5:获取 json 格式的推特状态,就像示例 15.9twitter_status_json.go
## 链接
- [目录](directory.md)

View File

@@ -1,6 +1,6 @@
# 15.4 写一个简单的网页应用
下边的程序在端口8088上启动了一个网页服务器`SimpleServer`会处理`/test1`url使它在浏览器输出`hello world``FormServer`会处理`/test2`url如果url最初由浏览器请求那么它是一个`GET`请求,并且返回一个`form`常量,包含了简单的`input`表单,这个表单里有一个文本框和一个提交按钮。当在文本框输入一些东西并点击提交按钮的时候,会发起一个`POST`请求。`FormServer`中的代码用到了`switch`来区分两种情况。`POST`情况下,使用`request.FormValue("inp")`通过文本框的`name`属性`inp`来获取内容,并写回浏览器页面。在控制台启动程序并在浏览器中打开url`http://localhost:8088/test2`来测试这个程序:
下边的程序在端口 8088 上启动了一个网页服务器;`SimpleServer` 会处理 url `/test1` 使它在浏览器输出 `hello world``FormServer` 会处理 url `/test2`:如果 url 最初由浏览器请求,那么它是一个 `GET` 请求,返回一个 `form` 常量,包含了简单的 `input` 表单,这个表单里有一个文本框和一个提交按钮。当在文本框输入一些东西并点击提交按钮的时候,会发起一个 POST 请求。`FormServer` 中的代码用到了 `switch` 来区分两种情况。请求为 POST 类型时,`name` 属性 为 `inp` 的文本框的内容可以这样获取:`request.FormValue("inp")`。然后将其写回浏览器页面。在控制台启动程序,然后到浏览器中打开 url `http://localhost:8088/test2` 来测试这个程序:
示例 15.10 [simple_webserver.go](examples/chapter_15/simple_webserver.go)
@@ -49,11 +49,12 @@ func main() {
}
}
```
当使用字符串常量表示html文本的时候包含`<html><body></body></html>`对于让浏览器识别它收到了一个html非常重要。
更安全的做法是在处理器中使用`w.Header().Set("Content-Type", "text/html")`在写入返回之前将`header``content-type`设置为`text/html`
注:当使用字符串常量表示 html 文本的时候,包含 `<html><body>...</body></html>` 对于让浏览器将它识别为 html 文档非常重要。
`content-type`会让浏览器认为它可以使用函数`http.DetectContentType([]byte(form))`来处理收到的数据
更安全的做法是在处理函数中,在写入返回内容之前将头部的 `content-type` 设置为`text/html``w.Header().Set("Content-Type", "text/html")`
`content-type` 会让浏览器认为它可以使用函数 `http.DetectContentType([]byte(form))` 来处理收到的数据。
练习 15.6 [statistics.go](exercises/chapter_15/statistics.go)
@@ -61,6 +62,8 @@ func main() {
![](../images/15.4_fig15.1.jpg?raw=true)
## 链接
- [目录](directory.md)
- 上一节:[访问并读取页面](15.3.md)
- 下一节:[确保网页应用健壮](15.5.md)

View File

@@ -1,6 +1,6 @@
# 15.6 用模板编写网页应用
以下程序是用 100 行以内代码实现可行的 wiki 网页应用,它由一组页面组成,用于阅读、编辑和保存。它是来自 Go 网站 codelab 的 wiki 制作教程,我所知的最好的 Go 教程之一,非常值得进行完整的实验,以见证并理解程序是如何被构建起来的[https://golang.org/doc/articles/wiki/](https://golang.org/doc/articles/wiki/)。这里,我们将以自顶向下的视角,从整体上给出程序的补充说明。程序是网页服务器,它必须从命令行启动,监听某个端口,例如 8080。浏览器可以通过请求 URL 阅读 wiki 页面的内容,例如:`http://localhost:8080/view/page1`
以下程序是用 100 行以内代码实现可行的 wiki 网页应用,它由一组页面组成,用于阅读、编辑和保存。它是来自 Go 网站 codelab 的 wiki 制作教程,我所知的最好的 Go 教程之一,非常值得进行完整的实验,以见证并理解程序是如何被构建起来的[https://golang.org/doc/articles/wiki/](https://golang.org/doc/articles/wiki/)。这里,我们将以自顶向下的视角,从整体上给出程序的补充说明。程序是网页服务器,它必须从命令行启动,监听某个端口,例如 8080。浏览器可以通过请求 URL 阅读 wiki 页面的内容,例如:`http://localhost:8080/view/page1`
接着,页面的文本内容从一个文件中读取,并显示在网页中。它包含一个超链接,指向编辑页面(`http://localhost:8080/edit/page1`)。编辑页面将内容显示在一个文本域中,用户可以更改文本,点击“保存”按钮保存到对应的文件中。然后回到阅读页面显示更改后的内容。如果某个被请求阅读的页面不存在(例如:`http://localhost:8080/edit/page999`),程序可以作出识别,立即重定向到编辑页面,如此新的 wiki 页面就可以被创建并保存。