首页
学习
活动
专区
圈层
工具
发布
16 篇文章
1
Go 语言 Web 编程系列(一)—— 快速入门:创建第一个 Web 应用
2
Go 语言 Web 编程系列(二)—— HTTP 请求处理的底层运行机制
3
Go 语言 Web 编程系列(三)—— 基于官方标准库自定义路由处理器
4
Go 语言 Web 编程系列(四)—— 基于 gorilla/mux 包实现路由定义:基本使用篇
5
Go 语言 Web 编程系列(五)—— 基于 gorilla/mux 包实现路由匹配:进阶使用篇
6
Go 语言 Web 编程系列(六)—— 基于 gorilla/mux 包实现路由匹配:路由中间件
7
Go 语言 Web 编程系列(七)—— 基于 gorilla/mux 包实现路由匹配:处理静态资源响应
8
Go 语言 Web 编程系列(八)—— 基于 gorilla/mux 包实现路由匹配:服务 SPA 应用
9
Go 语言 Web 编程系列(九)—— 基于 gorilla/mux 包实现路由匹配:通过 CORS 处理跨域请求
10
Go 语言 Web 编程系列(十)—— 基于 gorilla/mux 包实现路由匹配:健康检查与接口测试
11
Go 语言 Web 编程系列(十一)—— 仿照 Laravel 框架对 Go 路由代码进行拆分
12
Go 语言 Web 编程系列(十二)—— 通过 Request 读取 HTTP 请求报文
13
Go 语言 Web 编程系列(十三)—— 获取用户请求数据(上)
14
Go 语言 Web 编程系列(十四)—— 获取用户请求数据(下)
15
Go 语言 Web 编程系列(十五)—— 通过 ResponseWriter 接口创建 HTTP 响应
16
Go 语言 Web 编程系列(十六)—— 设置、读取和删除 Cookie

Go 语言 Web 编程系列(十五)—— 通过 ResponseWriter 接口创建 HTTP 响应

1、HTTP 响应报文结构

前面几篇教程我们了解了如何在 Go 语言中解析用户请求信息,包括表单字段和文件上传,接下来,我们来看看处理完请求后,如何将响应发送给客户端。HTTP 响应的报文结构如下所示:

HTTP 响应报文结构

和 HTTP 请求报文结构类似,响应报文也可以分为三部分:状态行、响应头(首部字段)和响应主体。

首先是状态行,在状态行中包含了 HTTP 协议版本和响应状态码,200 OK 表示响应成功,更多状态码信息(常见的有 404、403、500、301 等)可以网上查看下 HTTP 协议或者阅读 HTTP 响应状态码这篇文章了解。

然后是响应头,其中包含了 HTTP 响应的首部字段,比如内容类型/编码、缓存控制、Cookie 信息等。

最后是响应实体,对于 API 接口来说,通常就是返回的 XML/JSON 格式数据,对于 HTML 视图响应,就是一个标准的 HTML 文档,如上图所示。响应头和响应报文之间通过两个换行符分隔。

2、ResponseWriter 接口

在 Go 语言中,客户端请求信息都封装到了 Request 对象,但是发送给客户端的响应并不是 Response 对象,而是 ResponseWriter

代码语言:javascript
复制
func Home(w http.ResponseWriter, r *http.Request)  {
    io.WriteString(w, "Welcome to my blog site")
}

ResponseWriter 是处理器用来创建 HTTP 响应的接口,其源码结构如下所示:

代码语言:javascript
复制
type ResponseWriter interface {
   // 用于设置/获取所有响应头信息
    Header() Header
   // 用于写入数据到响应实体
    Write([]byte) (int, error)
   // 用于设置响应状态码
    WriteHeader(statusCode int)
}

实际上,在底层支撑 ResponseWriter 的结构体就是 http.response,详见 net/http 包下 server.go 中的 readRequest 方法(调用处理器处理 HTTP 请求时调用了该方法返回响应对象),并且其返回值是 response 指针,这也是为什么在处理器方法声明的时候 Request 是指针类型,而 ResponseWriter 不是,实际上在底层,响应对象也是指针类型(因为在应用代码中需要设置响应头和响应实体,所以响应对象理应是指针类型):

代码语言:javascript
复制
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
    ...

    w = &response{
        conn:          c,
        cancelCtx:     cancelCtx,
        req:           req,
        reqBody:       req.Body,
        handlerHeader: make(Header),
        contentLength: -1,
        closeNotifyCh: make(chan bool, 1),
        wants10KeepAlive: req.wantsHttp10KeepAlive(),
        wantsClose:       req.wantsClose(),
    }
    if isH2Upgrade {
        w.closeAfterReply = true
    }
    w.cw.res = w
    w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)
    return w, nil
}

response 结构体定义和 ResponseWriter 一样都位于 server.go,感兴趣的同学可以去看下源码,不过由于 response 对外不可见,所以只能通过 ResponseWriter 接口访问它。两者之间的关系是 ResponseWriter 是一个接口,而 http.response 实现了它。当我们引用 ResponseWriter 时,实际上引用的是 http.response 对象实例。

3、设置响应状态码

如上面的 ResponseWriter 接口定义源码所示,它包含三个方法:

  • WriteHeader
  • Header
  • Write

WriteHeader 这个方法名有点误导,其实它并不是用来设置响应头的,该方法支持传入一个整型数据用来表示响应状态码,如果不调用该方法的话,默认响应状态码是 200 OK

WriteHeader 的主要作用是在 API 接口中返回错误码,我们在 goblog/handlers/common.go 中新增一个处理器方法 Error,并通过 w.WriteHeader 返回一个 401 未认证状态码(注意在运行时 w 代表的是对应的 response 对象实例,而不是接口):

代码语言:javascript
复制
func Error(w http.ResponseWriter, r *http.Request)  {
    w.WriteHeader(401)
    fmt.Fprintln(w, "认证后才能访问该接口")
}

注:这里通过 fmt.Fprintln 将文本字符串写入响应对象。

然后在 routes/web.go 中添加一个路由与之映射:

代码语言:javascript
复制
WebRoute{
    "ApiError",
    "GET",
    "/error",
    handlers.Error,
},

重启 HTTP 服务器,通过 curl 访问 http://localhost:8080/error,返回的完整响应报文如下:

可以看到响应状态码是 401 Unauthorized,表示该接口需要认证后才能访问。这里,我们在运行 curl 时带上 -i 选项,以便可以看到完整的响应报文,第一行是响应状态行,然后是响应头信息,响应头每一行是一个键值对映射,通过冒号分隔,左侧是字段名,右侧是字段值,最后是响应实体,也就是我们在代码中写入的响应数据,响应实体和响应头之间通过一个空行分隔(两个换行符)。

5、设置响应头

Header 方法用于设置响应头信息,我们可以通过 w.Header().Set 方法设置响应头(w.Header() 方法返回的是 Header 响应头对象,它和请求头共用一个结构体,因此请求头上支持的方法这里都支持,比如可以通过 w.Header().Add 方法新增响应头),这里我们设置一个 301 重定向响应,只需要通过 w.WriteHeader 方法将响应状态码设置为 301,再通过 w.Header().Set 方法将负责重定向的响应头 Location 设置为一个可访问域名即可。

goblog/handlers/common.go 中新建一个处理器方法 Redirect,在其中编写重定向实现代码如下:

代码语言:javascript
复制
func Redirect(w http.ResponseWriter, r *http.Request)  {
    // 设置一个 301 重定向
    w.Header().Set("Location", "https://xueyuanjun.com")
    w.WriteHeader(301)
}

对于重定向请求,无需设置响应实体,另外需要注意的是 w.Header().Set 必须在 w.WriteHeader 之前调用,因为一旦调用 w.WriteHeader 之后,就不能对响应头进行设置了。

接下来,在 routes/web.go 中注册对应的重定向路由:

代码语言:javascript
复制
WebRoute{
    "Redirect",
    "GET",
    "/redirect",
    handlers.Redirect,
},

重启 HTTP 服务器,通过 curl 访问该路由可以清楚看到响应被重定向,并且响应实体为空:

如果是在浏览器中访问的话,页面就会跳转到 https://xueyuanjun.com

6、写入数据到响应实体

Write 方法用于写入数据到 HTTP 响应实体,如果调用 Write 方法时还不知道 Content-Type,会通过数据的前 512 个字节进行判断。

返回文本字符串

goblog/handlers/common.go 中定义的 Home 处理器方法为例,我们可以通过 w.Write 写入一段欢迎文本到响应实体:

代码语言:javascript
复制
func Home(w http.ResponseWriter, r *http.Request)  {
    w.Write([]byte("欢迎访问学院君个人网站?"));
}

由于 Write 方法接受的参数类型是 []byte 切片,所以需要将字符串转换为字节切片类型。启动 HTTP 服务器,通过 curl 访问首页,就可以看到返回的文本信息了:

返回 HTML 文档

如果要返回 HTML 文档,可以这么写入响应数据:

代码语言:javascript
复制
func Home(w http.ResponseWriter, r *http.Request)  {
    //w.Write([]byte("欢迎访问学院君个人网站?"));
    html := `<html> 
        <head>
            <title>学院君个人网站</title>
        </head> 
        <body>
            <h1>欢迎访问学院君个人网站?</h1>
        </body> 
    </html>`
    w.Write([]byte(html))
}

当然,后面介绍视图模板后,可以通过视图模板渲染 HTML 文档,这里我们先通过一个简单的包含 HTML 文档信息的字符串替代,重启 HTTP 服务器,通过浏览器访问,就可以看到对应的 HTML 视图了:

此外,由于响应数据的内容类型变成了 HTML,在响应头中,也可以看到 Content-Type 也自动调整成了 text/html,不再是纯文本格式。这里的 Content-Type 就是根据传入的数据自行判断出来的。

返回 JSON 格式数据

当然,我们也可以返回 JSON 格式数据:

代码语言:javascript
复制
type Greeting struct {
    Message string `json:"message"`
}

func Home(w http.ResponseWriter, r *http.Request)  {
    // 返回文本字符串
    //w.Write([]byte("欢迎访问学院君个人网站?"));
    // 返回 HTML 文档
    /*html := `<html>
        <head>
            <title>学院君个人网站</title>
        </head> 
        <body>
            <h1>欢迎访问学院君个人网站?</h1>
        </body> 
    </html>`
    w.Write([]byte(html))*/
    // 返回 JSON 格式数据
    greeting := Greeting{
        "欢迎访问学院君个人网站?",
    }
    message, _ := json.Marshal(greeting)
    w.Write(message)
}

重启 HTTP 服务器,在浏览器中访问 http://localhost:8080

虽然返回的确实是合法的 JSON 格式数据,但是内容类型依然是 text/plain,而不是 application/json,要返回这个格式的响应头,需要设置响应头才能实现:

代码语言:javascript
复制
// 返回 JSON 格式数据
greeting := Greeting{
    "欢迎访问学院君个人网站?",
}
message, _ := json.Marshal(greeting)
w.Header().Set("Content-Type", "application/json")
w.Write(message)

重启 HTTP 服务器,并再次通过 curl 访问首页,就可以看到内容类型变成 application/json 了:

下一篇
举报
领券