HTTP 编程(三):HTTP/HTTPS 请求处理

本篇教程我们将介绍 HTTP 服务端技术,包括如何处理 HTTP 请求和 HTTPS 请求。

处理 HTTP 请求

服务端实现

使用 net/http 包提供的 http.ListenAndServe() 方法,可以开启一个 HTTP 服务器,并且在指定的 IP 地址和端口上监听客户端请求,该方法的原型如下:

func ListenAndServe(addr string, handler Handler) error

该方法有两个参数:第一个参数 addr 表示监听的 IP 地址及端口号;第二个参数表示服务端对应的处理程序,通常为空,意味着将调用 http.DefaultServeMux 进行处理,而服务端编写的业务逻辑处理程序 http.Handle()http.HandleFunc() 默认会被注入到 http.DefaultServeMux 中。

下面我们按照上述思路实现一个最基本的 HTTP 服务器 server.go

package main

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

func main() {
    http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
        params := request.URL.Query();
        fmt.Fprintf(writer, "你好, %s", params.Get("name"))
    })
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatalf("启动 HTTP 服务器失败: %v", err)
    }
}

在这段代码中,我们通过 http.HandleFunc 方法定义了一个 /hello 路由及对应处理程序,在这个处理程序中我们会返回一个欢迎字符串,其中还引用了客户端传递过来的请求参数。然后通过 http.ListenAndServe 方法启动 HTTP 服务器,监听本地 IP 的 8080 端口,当客户端请求 http://127.0.0.1:8080/hello URL 时 HTTP 服务器会将其转发给默认的 http.DefaultServeMux 处理,这里最终会调用 http.HandleFunc 方法定义的处理器处理请求返回响应。

客户端请求

我们将前面教程实现的 HTTP 客户端示例代码 client.go 中的请求地址调整如下:

req, err := http.NewRequest("GET", "http://127.0.0.1:8080/hello?name=学院君", nil)

接下来,启动 HTTP 服务器:

go run server.go

然后新开一个 Terminal 窗口执行客户端调用代码:

HTTP请求处理

打印出了预期结果,说明 HTTP 服务器可以正常提供服务。

处理 HTTPS 请求

服务端逻辑

net/http 包还提供 http.ListenAndServeTLS() 方法,用于处理 HTTPS 连接请求:

func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error

ListenAndServeTLS()ListenAndServe() 的行为一致,区别在于前者只处理 HTTPS 请求。要正确处理 HTTPS 请求,服务器上必须存在 SSL 证书和与之匹配的私钥相关文件,比如 certFile 对应 SSL 证书文件存放路径,keyFile 对应证书私钥文件路径。如果证书是由证书颁发机构签署的,certFile 参数指定的路径必须是存放在服务器上的经由 CA 认证过的 SSL 证书。

通过 OpenSSL 生成自签名证书

这里我们在本地演示 HTTPS 请求的处理,可以通过 OpenSSL 工具生成自签名证书:

# Generate CA private key 
openssl genrsa -out ca.key 2048 
# Generate CSR 
openssl req -new -key ca.key -out ca.csr
# Generate Self Signed certificate(CA 根证书)
openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

如果是通过 IP 地址访问 HTTPS 服务,红框内的部分请填写 IP 地址,比如 127.0.0.1,如果通过域名访问,可以将其设置为域名:

生成 CA 认证的 SSL 证书

这些文件都保存到 HTTPS 服务器代码所在目录:

HTTPS服务端目录结构

服务端代码

开启 HTTPS 监听服务也很简单,除了调用方法调整为 http.ListenAndServeTLS 并传入上面生成的 SSL 证书和私钥文件外,其它都和 HTTP 服务器一样,我们在 https 目录下新建 server.go 编写对应代码:

package main

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

func main() {
    http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
        params := request.URL.Query();
        fmt.Fprintf(writer, "你好, %s", params.Get("name"))
    })
    err := http.ListenAndServeTLS(":8443", "./ca.crt", "./ca.key",nil)
    if err != nil {
        log.Fatalf("启动 HTTPS 服务器失败: %v", err)
    }
}

然后启动这个 HTTPS 服务器:

go run server.php

客户端请求

接下来,我们在 https 目录下创建客户端调用代码 client.go

package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    req, err := http.NewRequest("GET", "https://127.0.0.1:8443/hello?name=学院君", nil)
    if err != nil {
        fmt.Printf("请求初始化失败:%v", err)
        return
    }

    // 设置跳过不安全的 HTTPS
    tls11Transport := &http.Transport{
        MaxIdleConnsPerHost: 10,
        TLSClientConfig: &tls.Config{
            MaxVersion: tls.VersionTLS11,
            InsecureSkipVerify: true,
        },
    }

    client := &http.Client{
        Transport: tls11Transport,
    }

    resp, err := client.Do(req)
    if err != nil {
        fmt.Printf("客户端发起请求失败:%v", err)
        return
    }

    defer resp.Body.Close()
    io.Copy(os.Stdout, resp.Body)
}

这里,我们将请求 URL 调整为 HTTPS 协议,并且自定义了客户端实例的 Transport(更多底层细节可以参考上篇教程介绍)来跳过不安全的 HTTPS 连接验证。运行客户端代码,打印结果如下:

HTTPS请求处理

说明 HTTPS 请求成功。

当然以上 HTTP 服务和 HTTPS 服务通过浏览器访问也是可以的:

通过浏览器访问 HTTPS 服务

上一篇: HTTP 编程(二):http.Client 底层实现剖析

下一篇: RPC 编程(一):客户端与服务端 RPC 调用的简单实现