简单的HTTP服务器
现在项目的框架结构已经就绪,让我们将注意力集中在启动和运行HTTP服务器上。
首先,我们将服务器配置为只有一个路由:/v1/healthcheck。这条路由将返回有关API服务的一些基本信息,包括其当前版本号和操作环境(开发、测试、生产等)。
URL模式 | Handler | 操作 |
---|---|---|
/v1/healthcheck | healthcheckHandler | 显示应用信息 |
如果你跟着本文操作,下面打开cmd/api/main.go文件用以下代码替换’hello world‘应用:
File:main.go
package main
import (
"flag"
"fmt"
"log"
"net/http"
"os"
"time"
)
// 声明一个包含应用程序版本号的字符串. 在本书的后面,我们将在构建时自动生成它
//但是现在我们只将版本号存储为一个硬编码的全局常量.
const version = "1.0.0"
// 定义一个配置结构体来保存应用程序的所有配置设置.
//目前,唯一的配置是服务器监听的端口和应用程序的环境名称 (开发, 预发, 生成等等)
//将从命令行参数中读取这些配制信息
type config struct {
port int
env string
}
// 定义一个application结构体来保存HTTP处理程序的依赖项
//目前,这只包含配置实例的一个副本和日志对象,但随着项目深入会增加更多内容
type application struct {
config config
logger *log.Logger
}
func main() {
// 声明一个配置结构体实例
var cfg config
// 从命令行参数中将port和env读取到配制结构体实例当中。
//默认端口使用4000以及环境信息使用开发环境development
flag.IntVar(&cfg.port, "port", 4000, "API server port")
flag.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)")
flag.Parse()
// 初始化日志对象,将消息写入到标准输出。 以当前日期和时间为前缀
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime)
// 声明应用程序结构的实例, 包含配制对象实例和日志对象。
app := &application{
config: cfg,
logger: logger}
// 申明一个新的servemux并添加/v1/healthcheck路由,将请求路由到即将实现的handler处理程序中去
mux := http.NewServeMux()
mux.HandleFunc("/v1/healthcheck", app.healthcheckHandler)
// 使用一些合理的超时设置声明HTTP服务器
//使用配制对象中的端口好以及创建的servemux作为handler
srv := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.port),
Handler: mux,
IdleTimeout: time.Minute,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
}
// 启动HTTP服务
logger.Printf("starting %s server on %s", cfg.env, srv.Addr)
err := srv.ListenAndServe()
logger.Fatal(err)
}
创建healthcheck处理程序
接下来我们需要创建用于响应HTTP请求的healthcheckHandler方法。现在,我们将保持这个处理程序中的逻辑非常简单,只让它返回一个包含三段信息的纯文本响应:
- 一个固定的“status: available”字符串
- API版本从硬编码version常量获取
- 操作环境名从命令行参数读取存在env中
继续创建cmd/api/healthcheck.go文件:
$ touch cmd/api/healthcheck.go
添加如下代码到文件中:
File:cmd/api/healthcheck.go
package main
import (
"fmt"
"net/http"
)
//申明一个handler返回应用程序状态,操作环境和版本
func (app *application) healthcheck(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "status: available")
fmt.Fprintf(w, "environment %s\n", app.config.env)
fmt.Fprintf(w, "version: %s\n", version)
}
这里需要指出的重要一点是,healthcheckHandler是作为application结构体上的一个方法实现的.
这是一种有效且惯用的方法,使我们的处理程序可以使用依赖项,而不需要借助全局变量或闭包——当我们在main()中初始化healthcheckHandler时,任何依赖项都可以简单地作为一个字段包含在应用程序结构中。
我们可以看到上面的代码中已经使用了这个模式,其中通过调用app.config.env从应用程序结构中检索操作环境名称。
演示
代码我们来试试接口,请确保你的所有更改都已保存,然后再次使用go run命令来执行cmd/api包中的代码。您应该会看到一条日志消息,确认HTTP服务器正在运行,类似如下:
$ go run ./cmd/api
2021/11/15 19:42:50 starting development server on :4000
当服务器运行时,继续尝试在浏览器中访问localhost:4000/v1/healthcheck。你应该从healthcheckHandler得到如下响应:
或者,您可以使用curl工具从您的终端发出请求:
$ curl -i localhost:4000/v1/healthcheck
HTTP/1.1 200 OK
Date: Mon, 05 Apr 2021 17:46:14 GMT Content-Length: 58
Content-Type: text/plain; charset=utf-8
status: available environment: development version: 1.0.0
注意:上面命令中的-i标志表示curl显示HTTP响应头和响应体。
如果你想验证在命令行参数是否正常工作,通过指定port和env值然后在启动服务器。你应该可以看到日志信息,如下:
$ go run ./cmd/api -port=3030 -env=production
2021/04/05 19:48:34 starting production server on :3030
附加说明
API版本
支持真实业务和用户的api经常需要随着时间的推移而更改其功能和API——有时是以一种向后不兼容的方式。因此,为了避免客户端出现问题和困惑,最好实现某种形式的API版本控制。
通常有两种方法来实现:
1、通过给所有url加上API版本的前缀,比如/v1/healthcheck或/v2/healthcheck。
2、通过在请求和响应中使用自定义的Accept和Content-Type头来传递API版本,比如Accept: application/vnd.greenlight-v1。
从HTTP语义的角度来看,使用请求头来传递API版本是一种“更纯粹”的方法。但从用户体验的角度来看,使用URL前缀可能更好。开发者一眼就能看出API的版本,也意味着可以使用常规的浏览器来访问API(如果放在请求头就更不方便)。
在整个应用中,我们将通过在所有URL路径前加上/v1/来对API进行版本化,就像我们在本章中对/v1/healthcheck接口所做的那样。