19.4 用户界面:web 服务端
(本节代码见 goto_v1/main.go。)
我们尚未编写启动程序的必要函数。它们(总是)类似 C,C++ 或 Java 中的 main()
函数,我们的 web 服务器由它启动,例如用如下命令在本地 8080 端口启动 web 服务器:
http.ListenAndServe(":8080", nil)
(web 服务器的功能来自于 http
包,15 章 做了深入介绍)。web 服务器会在一个无限循环中监听到来的请求,但我们必须定义针对这些请求,服务器该如何响应。可以用被称为 HTTP 处理器的 HandleFunc
函数来办到,例如代码:
http.HandleFunc("/add", Add)
如此,每个以 /add
结尾的请求都会调用 Add
函数(尚未完成)。
程序有两个 HTTP 处理器:
Redirect
,用于对短 URL 重定向Add
,用于处理新提交的 URL
示意图:
最简单的 main()
函数类似这样:
func main() {
http.HandleFunc("/", Redirect)
http.HandleFunc("/add", Add)
http.ListenAndServe(":8080", nil)
}
对 /add
的请求由 Add
处理器处理,所有其他请求会被 Redirect
处理器处理。处理函数从到来的请求(一个类型为 *http.Request
的变量)中获取信息,然后产生响应并写入 http.ResponseWriter
类型变量 w
。
Add
函数必须做的事有:
- 读取长 URL,即:用
r.FormValue("url")
从 HTML 表单提交的 HTTP 请求中读取 URL - 使用 store 上的
Put
方法存储长 URL - 将对应的短 URL 发送给用户
每个需求都转化为一行代码:
func Add(w http.ResponseWriter, r *http.Request) {
url := r.FormValue("url")
key := store.Put(url)
fmt.Fprintf(w, "http://localhost:8080/%s", key)
}
这里 fmt
包的 Fprintf
函数用来替换字符串中的关键字 %s
,然后将结果作为响应发送回客户端。注意 Fprintf
把数据写到了 ResponseWriter
中,其实 Fprintf
可以将数据写到任何实现了 io.Writer
的数据结构,即该结构实现了 Write
方法。Go 中 io.Writer
称为接口,可见 Fprintf
利用接口变得十分通用,可以对很多不同的类型写入数据。Go 中接口的使用十分普遍,它使代码更通用(见 11 章)。
还需要一个表单,仍然可以用 Fprintf
来输出,这次将常量写入 w
。让我们来修改 Add
,当未指定 URL 时显示 HTML 表单:
func Add(w http.ResponseWriter, r *http.Request) {
url := r.FormValue("url")
if url == "" {
fmt.Fprint(w, AddForm)
return
}
key := store.Put(url)
fmt.Fprintf(w, "http://localhost:8080/%s", key)
}
const AddForm = `
<form method="POST" action="/add">
URL: <input type="text" name="url">
<input type="submit" value="Add">
</form>
`
在那种情况下,发送字符串常量 AddForm
到客户端,它是 html 表单,包含一个 url
输入域和一个提交按钮,点击后发送 POST 请求到 /add
。这样 Add
处理函数被再次调用,此时 url
的值来自文本域。(`
用来创建原始字符串,否则按惯例
“”` 将成为字符串边界。)
Redirect
函数在 HTTP 请求路径中找到键(短 URL 的键是请求路径去除首字符,在 Go 中可以写为 [1:]
。例如请求 “/abc”,键就是 “abc”),用 Get
函数从 store
检索到对应的长 URL,对用户发送 HTTP 重定向。如果没找到 URL,发送 404 “Not Found” 错误取而代之:
func Redirect(w http.ResponseWriter, r *http.Request) {
key := r.URL.Path[1:]
url := store.Get(key)
if url == "" {
http.NotFound(w, r)
return
}
http.Redirect(w, r, url, http.StatusFound)
}
(http.NotFound
和 http.Redirect
是发送通用 HTTP 响应的工具函数。)
我们已经完整地遍历了 goto_v1 的代码。
编译和运行
可执行程序已包含在示例代码下,如果你想立即测试可以跳过本节。其中包含 3 个 go 源文件和一个 Makefile 文件,通过它应用可以被编译和链接,只须如下操作:
- Linux 和 OSX 平台: 在终端窗口源码目录下启动
make
命令,或在 LiteIDE 中构建项目。 - Windows 平台: 启动 MINGW 环境,步骤为:开始菜单,所有程序,MinGW,MinGW Shell(见 2.5.5 节),在命令行窗口输入
make
并回车,源代码被编译并链接为原生 exe 可执行程序。
生成内容为可执行程序,Linux/OS X 下为 goto
,Windows 下为 goto.exe
。
要启动并运行 web 服务器,那么:
- Linux 和 OSX 平台: 输入命令
./goto
。 - Windows 平台: 从 Go IDE 启动程序(如果 Windows 防火墙阻止程序启动,设置允许该程序)
测试该程序
打开浏览器并请求 url:http://localhost:8080/add
这会激活 Add
处理函数。请求还未包含 url 变量,所以响应会输出 html 表单询问输入:
添加一个长 URL 以获取等价的缩短版本,例如 http://golang.org/pkg/bufio/#Writer
,然后单击按钮。应用会为你产生一个短 URL 并打印出来,例如 http://
localhost:8080/2
。
复制该 URL 并在浏览器地址栏粘贴以发出请求,现在轮到 Redirect
处理函数上场了,对应长 URL 的页面被显示了出来。