37.Go HTTP Server

Hello World

开始使用Go编写Web服务器的典型方法是使用标准库中的net/http模块。

如下代码是最简单的HTTP服务器实现,它对任何HTTP请求都响应“Hello World”。

server.go:

package main

import (
    "log"
    "net/http"
)

func main() {
    // 所有URLs都被这个函数处理
    // http.HandleFunc使用了DefaultServeMux
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, world!"))
    })

    // Continue to process new requests until an error occurs
    log.Fatal(http.ListenAndServe(":8080", nil))
}

使用以下命令运行服务:

$ go run server.go

或者编译后再运行。

$ go build server.go
$ ./server

服务将侦听指定的端口(8080)。 可以使用任何HTTP客户端对其进行测试。 这是cURL的示例:

curl -i http://localhost:8080/
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 18:04:46 GMT
Content-Length: 13
Content-Type: text/plain; charset=utf-8

Hello, world!

按Ctrl+C结束进程.

创建一个HTTPS服务器

生成证书

为了运行HTTPS服务器,需要一个证书。通过执行以下命令,可以使用openssl生成自签名证书:

openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout key.pem -out cert.pem -subj“ /CN=example.com” -3650天
参数为:

  • req 使用证书申请工具
  • x509 创建自签名证书
  • newkey rsa:4096 通过使用4096位密钥长度的RSA算法来创建新密钥和证书
  • sha256 强制使用SHA256哈希算法,这些算法被主流浏览器认为是安全的(截至2017年)
  • nodes 禁用私钥的密码保护。如果没有此参数,则服务器每次启动时都必须询问你密码
  • keyout 命名要在其中写入密钥的文件
  • out 将文件写入证书的位置
  • subj 定义此证书对其有效的域名
  • days 该证书应有效多少天, 约3650,10年

注意:可以使用自签名证书,例如用于内部项目,调试,测试等。那里的任何浏览器都会提到此证书不安全。为了避免这种情况,证书必须由证书颁发机构签名。免费的证书颁发服务可以使用: https://letsencrypt.org

编写Go代码

使用以下代码来处理服务器的TLS配置。 cert.pem和key.pem是SSL证书和密钥,是使用上述命令生成的。

package main

import (
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, world!"))
    })

    log.Fatal(http.ListenAndServeTLS(":443","cert.pem","key.pem", nil))
}

自定义server和mux

package main

import (
    "log"
    "net/http"
)

func main() {

    // 创建mux来路由请求
    m := http.NewServeMux()

    // 处理所有请求
    m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, world!"))
    })

    // 启动服务监听8000端口
    s := &http.Server{
        Addr:    ":8000",
        Handler: m,
    }

    // 持续处理请求直到错误发生
    log.Fatal(s.ListenAndServe())
}

按Ctrl+C结束进程

使用模板响应HTTP请求

可以使用Go中的模板将响应写入http.ResponseWriter。 如果你希望创建动态页面,这是一种便捷的工具。

要了解模板,请参见文本和HTML模板章节。

继续使用简单的示例来利用html/template响应HTTP请求:

package main

import(
    "html/template"
    "net/http"
    "log"
)

func main(){
    http.HandleFunc("/",WelcomeHandler)
    http.ListenAndServe(":8080",nil)
}

type User struct{
    Name string
    nationality string //unexported field.
}

func check(err error){
    if err != nil{
        log.Fatal(err)
    }
}

func WelcomeHandler(w http.ResponseWriter, r *http.Request){
    if r.Method == "GET"{
        t,err := template.ParseFiles("welcomeform.html")
        check(err)
        t.Execute(w,nil)
    }else{
        r.ParseForm()
        myUser := User{}
        myUser.Name = r.Form.Get("entered_name")
        myUser.nationality = r.Form.Get("entered_nationality")
        t, err := template.ParseFiles("welcomeresponse.html")
        check(err)
        t.Execute(w,myUser)
    }
}

welcomeform.html:


     Help us greet you 


    
Enter Name: Enter Nationality:

welcomeresponse.html:


     Greetings, {{.Name}} 


    Greetings, {{.Name}}.
We know you are a {{.nationality}}!

注意:

  • 确保.html文件位于正确的目录中。
  • 启动服务器后可以访问http://localhost:8080/
  • 提交表单后可以看到,模板包无法按预期解析struct中未导出的国籍字段。

使用ServeMux提供内容

一个简单的静态文件服务器如下所示:

package main

import (
    "net/http"
)

func main() {
    muxer := http.NewServeMux()
    fileServerCss := http.FileServer(http.Dir("src/css"))
    fileServerJs := http.FileServer(http.Dir("src/js"))
    fileServerHtml := http.FileServer(http.Dir("content"))
    muxer.Handle("/", fileServerHtml)
    muxer.Handle("/css", fileServerCss)
    muxer.Handle("/js", fileServerJs)
    http.ListenAndServe(":8080", muxer)
}

使用处理函数

HandleFunc在server mux(router)中注册给定模式的处理程序功能。

可以传递匿名函数,如之前Hello World示例:

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello, world!")
}

也我们也可以传递HandlerFunc类型。 换句话说,我们可以传递任何尊重以下签名的函数:
func FunctionName(w http.ResponseWriter,req * http.Request)

我们重写前面的示例,将引用传递给先前定义的HandlerFunc。这是完整的示例:

package main

import (
    "fmt"
    "net/http"
)

// A HandlerFunc function
// Notice the signature of the function
func RootHandler(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, "Hello, world!")
}

func main() {
    // Here we pass the reference to the `RootHandler` handler function
    http.HandleFunc("/", RootHandler)
    panic(http.ListenAndServe(":8080", nil))
}

当然可以为不同的路径定义几个函数处理程序。

package main

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

func FooHandler(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, "Hello from foo!")
}

func BarHandler(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, "Hello from bar!")
}

func main() {
    http.HandleFunc("/foo", FooHandler)
    http.HandleFunc("/bar", BarHandler)

    log.Fatal(http.ListenAndServe(":8080", nil))
}

下面是使用cURL的测试结果:

$ curl -i localhost:8080/foo
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 18:23:08 GMT
Content-Length: 16
Content-Type: text/plain; charset=utf-8

Hello from foo!

$ curl -i localhost:8080/bar
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 18:23:10 GMT
Content-Length: 16
Content-Type: text/plain; charset=utf-8

Hello from bar!

$ curl -i localhost:8080/
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Wed, 20 Jul 2016 18:23:13 GMT
Content-Length: 19

404 page not found

获取请求参数和正文

下面是一个开发API时常见的简单示例,该API区分请求的HTTP方法,获取请求参数和正文:

package main

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

type customHandler struct{}

// ServeHTTP实现了net/http包中的http.Handler接口
func (h customHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

    // ParseForm 方法解析请求参数并且让r.Form可用
    r.ParseForm()

    // r.Form是map结构的请求参数, 它的类型是url.Values, 即map[string][]string
    queryMap := r.Form

    switch r.Method {
    case http.MethodGet:
        // 处理GET请求
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(fmt.Sprintf("Query string values: %s", queryMap)))
        return
    case http.MethodPost:
        // 处理POST请求
        body, err := ioutil.ReadAll(r.Body)
        if err != nil {
            // 解析request body时出错
            w.WriteHeader(http.StatusBadRequest)
            return
        }
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(fmt.Sprintf("Query string values: %s\nBody posted: %s", queryMap, body)))
        return
    }

    // 其他的HTTP methods(PUT,PATCH等)不被以上代码处理,所有返回405错误
    w.WriteHeader(http.StatusMethodNotAllowed)
}

func main() {
    // http.Handle类似于http.HandleFunc,使用了DefaultServeMux
    http.Handle("/", customHandler{})

    // Continue to process new requests until an error occurs
    log.Fatal(http.ListenAndServe(":8080", nil))
}

curl测试输出:

$ curl -i 'localhost:8080?city=Seattle&state=WA' -H 'Content-Type: text/plain' -X GET
HTTP/1.1 200 OK
Date: Fri, 02 Sep 2016 16:36:24 GMT
Content-Length: 51
Content-Type: text/plain; charset=utf-8

Query string values: map[city:[Seattle] state:[WA]]%

$ curl -i 'localhost:8080?city=Seattle&state=WA' -H 'Content-Type: text/plain' -X POST -d "some post data"
HTTP/1.1 200 OK
Date: Fri, 02 Sep 2016 16:36:35 GMT
Content-Length: 79
Content-Type: text/plain; charset=utf-8

Query string values: map[city:[Seattle] state:[WA]]
Body posted: some post data%

$ curl -i 'localhost:8080?city=Seattle&state=WA' -H 'Content-Type: text/plain' -X PUT
HTTP/1.1 405 Method Not Allowed
Date: Fri, 02 Sep 2016 16:36:41 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8

其他资源:

  • http.Handler接口
  • http.ResponseWriter
  • http.Request
  • 可以使用的方法和状态常量

http.ServeMux提供了一个多路复用器,该多路复用器调用HTTP请求的处理程序。
标准库多路复用器的替代方法包括:

Gorilla Mux

你可能感兴趣的:(37.Go HTTP Server)