一个简单的web服务器

15.2 一个简单的网页服务器

Http是一个比tcp更高级的协议,它描述了客户端浏览器如何与网页服务器进行通信。Go有自己的net/http包,我们来看看它。我们从一些简单的示例开始, 首先编写一个“Hello world!”:查看示例15.6

我们引入了http包并启动了网页服务器,和15.1的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)。还有一种方法就是先执行request.ParseForm()然后再获取request.Form["var1"]的第一个返回参数,就像这样:

  var1, found := request.Form["var1"]

第二个参数found就是true,如果var1并未出现在表单中,found就是false

表单属性实际上是一个map[string][]string类型。网页服务器返回了一个http.Response,它是通过http.ResponseWriter对象输出的,这个对象整合了HTTP服务器的返回结果;通过对它写入内容,我们就将数据发送给了HTTP客户端。

现在我们还需要编写网页服务器必须执行的程序,它是如何处理请求的呢。这是在http.HandleFunc函数中完成的,就是在这个例子中当根路径“/”(url地址是http://localhost:8080 )被请求的时候(或者这个服务器上的其他地址),HelloServer函数就被执行了。这个函数是http.HandlerFunc类型的,它们通常用使用Prehandler来命名,在前边加了一个Pref前缀。

http.HandleFunc注册了一个处理函数(这里是HelloServer)来处理对应/的请求。

/可以被替换为其他特定的url比如/create/edit等等;你可以为每一个特定的url定义一个单独的处理函数。这个函数需要两个参数:第一个是ReponseWriter类型的w;第二个是请求req。程序向w写入了Hellor.URL.Path[1:]组成的字符串后边的[1:]表示“创建一个从第一个字符到结尾的子切片”,用来丢弃掉路径开头的“/”,fmt.Fprintf()函数完成了本次写入(请看章节12.8);另外一种写法是io.WriteString(w, "hello, world!\n")

总结:第一个参数是请求的路径,第二个参数是处理这个路径请求的函数的引用。

示例 15.6 hello_world_webserver.go

package main

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

func HelloServer(w http.ResponseWriter, req *http.Request) {
    fmt.Println("Inside HelloServer handler")
    fmt.Fprintf(w, "Hello,"+req.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", HelloServer)
    err := http.ListenAndServe("localhost:8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}

使用命令行启动程序,会打开一个命令窗口显示如下文字:

Starting Process E:/Go/GoBoek/code_examples/chapter_14/hello_world_webserver.exe
...

然后打开你的浏览器并输入url地址:http://localhost:8080/world,浏览器就会出现文字:Hello, world,网页服务器会响应你在:8080/后边输入的内容

使用fmt.Println在控制台打印状态,在每个handler被请求的时候,在他们内部打印日志会很有帮助

注: 1)前两行(没有错误处理代码)可以替换成以下写法:

http.ListenAndServe(":8080", http.HandlerFunc(HelloServer))

2)fmt.Fprintfmt.Fprintf都是用来写入http.ResponseWriter的不错的函数(他们实现了io.Writer)。 比如我们可以使用

fmt.Fprintf(w, "<h1>%s<h1><div>%s</div>", title, body)

来构建一个非常简单的网页并插入titlebody的值

如果你需要更多复杂的替换,使用模板包(请看章节15.7) 3)如果你需要使用安全的https连接,使用http.ListenAndServeTLS()代替http.ListenAndServe() 4)http.HandleFunc("/", Hfunc)中的HFunc是一个处理函数,如下:

func HFunc(w http.ResponseWriter, req *http.Request) {
    ...
}

也可以使用这种方式:http.Handle("/", http.HandlerFunc(HFunc))

上边的HandlerFunc只是一个类型名称,它定义如下:

type HandlerFunc func(ResponseWriter, *Request)

它是一个可以把普通的函数当做HTTP处理器的适配器。如果f函数声明的合适,HandlerFunc(f)就是一个执行了f函数的处理器对象。

http.Handle的第二个参数也可以是T的一个obj对象:http.Handle("/", obj)给T提供了ServeHTTP方法,实现了http的Handler接口:

func (obj *Typ) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ...
}

这个用法在章节15.8类型CounterChan上使用过。只要实现了http.Handlerhttp包就可以处理任何HTTP请求。

练习 15.2:webhello2.go

编写一个网页服务器监听端口9999,有如下处理函数:

  • 当请求http://localhost:9999/hello/Name时,响应:hello Name(Name需是一个合法的姓,比如Chris或者Madeleine)

  • 当请求http://localhost:9999/shouthello/Name时,响应:hello NAME

练习 15.3:hello_server.go

创建一个空结构hello并使它实现http.Handler。运行并测试。

链接