通过示例学习 Go:第 2 部分 - 使用 Go 创建 HTTP REST API 服务器
在第一篇文章中,我们搭建了环境。现在,我们将创建我们的第一个应用程序:一个用 Go 编写的 HTTP REST API 服务器。
初始化
首先,我们可以在 GitHub 中创建我们的存储库(以便共享和开源)。
为此,我登录了GitHub 网站,点击了存储库链接,点击“新建”绿色按钮,然后创建了一个名为“learning-go-by-example”的新存储库。
现在,在您的本地计算机上,将这个新存储库 git clone 到您想要的位置:
$ git clone https://github.com/scraly/learning-go-by-examples.git
$ cd learning-go-by-examples
由于我们将重新使用这个 Git 存储库,我们将为go-rest-api
我们的第一个应用程序创建一个文件夹并进入它:
$ mkdir go-rest-api
$ cd go-rest-api
现在,我们必须初始化 Go 模块(依赖管理):
$ go mod init github.com/scraly/learning-go-by-examples/go-rest-api
go: creating new go.mod: module github.com/scraly/learning-go-by-examples/go-rest-api
这将创建一个go.mod
如下文件:
module github.com/scraly/learning-go-by-examples/go-rest-api
go 1.16
在开始我们的出色 API 之前,作为良好的做法,我们将创建一个简单的代码组织。
创建以下文件夹组织:
.
├── README.md
├── bin
├── doc
├── go.mod
├── internal
├── pkg
└── swagger
让我们创建我们的 HTTP 服务器
Go 是一种强大的语言,它的生态系统中附带了大量有用的库,例如我们感兴趣的net/http 。
我们将开始在文件夹中创建一个main.go
文件internal/
:
package main
import (
"fmt"
"html"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Println("Listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
这个简单的例子启动一个 HTTP 服务器,监听端口 8080 的传入请求,在 / 上提供服务并返回“Hello”+ 路径。
现在,是时候运行我们的应用程序来测试它了:
$ go run internal/main.go
2021/07/12 22:10:47 Listening on localhost:8080
为了测试我们的 HTTP 服务器,我们可以在 localhost:8080 上进行 curl 或在浏览器中转到此端点:
$ curl localhost:8080
Hello, "/"%
因此我们只需要在.go
文件中编写一些 Go 代码,然后运行go run myfile.go
即可测试它,太棒了!
是的,这很棒,但如果我们愿意,我们还可以使用我们的 HTTP 服务器生成可执行二进制文件:
$ go build -o bin/go-rest-api internal/main.go
Makefile -> 任务文件
为了运行我们的应用程序、打包、测试、生成 swagger 文档,执行所有命令是很酷的……但如果我们可以自动化它,你会怎么想?
过去我使用Makefile来定义一组任务,但现在我使用 Taskfile,它是 Makefile 的替代品。
我向您推荐Sébastien Kurtzemann 撰写的有关 Taskfile 的文章,其中解释了什么是 Taskfile、如何安装它以及如何创建您的第一个任务。
对于我们的应用程序,我创建了一个Taskfile.yml
包含以下内容的文件:
version: "3"
tasks:
build:
desc: Build the app
cmds:
- GOFLAGS=-mod=mod go build -o bin/go-rest-api internal/main.go
run:
desc: Run the app
cmds:
- GOFLAGS=-mod=mod go run internal/main.go
swagger.gen:
desc: Generate Go code
cmds:
- GOFLAGS=-mod=mod go generate github.com/scraly/learning-go-by-examples/go-rest-api/internal github.com/scraly/learning-go-by-examples/go-rest-api/pkg/swagger
swagger.validate:
desc: Validate swagger
cmds:
- swagger validate pkg/swagger/swagger.yml
swagger.doc:
desc: Doc for swagger
cmds:
- docker run -i yousan/swagger-yaml-to-html < pkg/swagger/swagger.yml > doc/index.html
如果您愿意,您可以在 GitHub 存储库中下载其最新版本。
在本地机器上安装task
,为此,您可以按照安装说明进行操作,或者如果您有带有homebrew的 MacOS ,则可以使用brew install
命令:
$ brew install go-task/tap/go-task
现在您可以显示可用任务的列表:
$ task --list
task: Available tasks for this project:
* build: Build the app
* run: Run the app
* swagger.doc: Doc for swagger
* swagger.gen: generate Go code
* swagger.validate: Validate swagger
太棒了,我们又学到了另一个好的做法!
现在是时候创建我们的 REST API
很酷,我们编写了一个 HTTP 服务器,但是 Aurélie 你谈到了 REST API,不是吗?
确实,我们现在将加强我们的 HTTP 服务器并使用Swagger来处理我们的 HTTP 端点的定义。
什么是 Swagger?
Swagger 允许您提供符合 OpenAPI 规范的 API 标准化文档。
通过输入 Swagger 规范文件,借助 Swagger 应用程序,您可以生成代码,最后以 HTML 格式向用户提供 API 文档。
如果您想构建公共 API,请毫不犹豫地使用 Swagger。
Swagger 安装
我们将安装go-swagger
工具,请毫不犹豫地按照安装页面进行操作。
如果您有 Mac:
$ brew tap go-swagger/go-swagger
$ brew install go-swagger
然后,您可以检查 Swagger 应用程序的版本,以验证该工具是否正确安装在您的系统中:
$ swagger version
version: v0.27.0
commit: 43c2774170504d87b104e3e4d68626aac2cd447d
让我们在新文件中创建我们的 Swagger 规范pkg/swagger/swagger.yml
:
consumes:
- application/json
info:
description: HTTP server in Go with Swagger endpoints definition.
title: go-rest-api
version: 0.1.0
produces:
- application/json
schemes:
- http
swagger: "2.0"
paths:
/healthz:
get:
operationId: checkHealth
produces:
- text/plain
responses:
'200':
description: OK message.
schema:
type: string
enum:
- OK
/hello/{user}:
get:
description: Returns a greeting to the user!
parameters:
- name: user
in: path
type: string
required: true
description: The name of the user to greet.
responses:
200:
description: Returns the greeting.
schema:
type: string
400:
description: Invalid characters in "user" were provided.
/gopher/{name}:
get:
description: Return the Gopher Image.
produces:
- image/png
parameters:
- name: name
in: path
type: string
required: true
description: The name of the Gopher to display.
responses:
200:
description: Returns the Gopher.
schema:
type: file
每次修改 Swagger 文件后,一个好的做法是检查文件的有效性:
$ task swagger.validate
task: [swagger.validate] swagger validate pkg/swagger/swagger.yml
2021/07/12 22:39:47
The swagger spec at "pkg/swagger/swagger.yml" is valid against swagger specification 2.0
太棒了,我们的 swagger 文件有效。
现在,我们将在 HTML 文档中创建 Swagger 定义。
为此,我使用了一个 Docker 镜像,它考虑了我们的 Swagger YAML 定义并返回一个漂亮的 HTML 页面:
$ task swagger.doc
task: [swagger.doc] docker run -i yousan/swagger-yaml-to-html < pkg/swagger/swagger.yml > doc/index.html
doc/index.html
如果在浏览器中打开生成的页面,您可以查看 HTML 端点定义:
当您创建和分发 API 时,Swagger 文档是人类可读的并且是完美的。
现在我们可以通过 swagger 规范生成 Go 代码。
为了做到这一点,进入包pkg/swagger/
并创建gen.go
具有以下内容的文件:
package swagger
//go:generate rm -rf server
//go:generate mkdir -p server
//go:generate swagger generate server --quiet --target server --name hello-api --spec swagger.yml --exclude-main
让我们根据 Swagger 规范生成 Go 文件:
$ task swagger.gen
task: [swagger.gen] GOFLAGS=-mod=mod go generate github.com/scraly/learning-go-by-examples/go-rest-api/internal github.com/scraly/learning-go-by-examples/go-rest-api/pkg/swagger
该命令将生成几个有用的文件(包含处理程序、结构、函数……):
pkg/swagger
├── gen.go
├── server
│ └── restapi
│ ├── configure_hello_api.go
│ ├── doc.go
│ ├── embedded_spec.go
│ ├── operations
│ │ ├── check_health.go
│ │ ├── check_health_parameters.go
│ │ ├── check_health_responses.go
│ │ ├── check_health_urlbuilder.go
│ │ ├── get_gopher_name.go
│ │ ├── get_gopher_name_parameters.go
│ │ ├── get_gopher_name_responses.go
│ │ ├── get_gopher_name_urlbuilder.go
│ │ ├── get_hello_user.go
│ │ ├── get_hello_user_parameters.go
│ │ ├── get_hello_user_responses.go
│ │ ├── get_hello_user_urlbuilder.go
│ │ └── hello_api_api.go
│ └── server.go
└── swagger.yml
这为我们的 HTTP REST API 服务器实现节省了时间。
让我们实现我们的路线!
让我们main.go
用这个新内容编辑我们的文件:
package main
import (
"fmt"
"log"
"net/http"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime/middleware"
"github.com/scraly/learning-go-by-examples/go-rest-api/pkg/swagger/server/restapi"
"github.com/scraly/learning-go-by-examples/go-rest-api/pkg/swagger/server/restapi/operations"
)
func main() {
// Initialize Swagger
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
log.Fatalln(err)
}
api := operations.NewHelloAPIAPI(swaggerSpec)
server := restapi.NewServer(api)
defer func() {
if err := server.Shutdown(); err != nil {
// error handle
log.Fatalln(err)
}
}()
server.Port = 8080
api.CheckHealthHandler = operations.CheckHealthHandlerFunc(Health)
api.GetHelloUserHandler = operations.GetHelloUserHandlerFunc(GetHelloUser)
api.GetGopherNameHandler = operations.GetGopherNameHandlerFunc(GetGopherByName)
// Start server which listening
if err := server.Serve(); err != nil {
log.Fatalln(err)
}
}
//Health route returns OK
func Health(operations.CheckHealthParams) middleware.Responder {
return operations.NewCheckHealthOK().WithPayload("OK")
}
//GetHelloUser returns Hello + your name
func GetHelloUser(user operations.GetHelloUserParams) middleware.Responder {
return operations.NewGetHelloUserOK().WithPayload("Hello " + user.User + "!")
}
//GetGopherByName returns a gopher in png
func GetGopherByName(gopher operations.GetGopherNameParams) middleware.Responder {
var URL string
if gopher.Name != "" {
URL = "https://github.com/scraly/gophers/raw/main/" + gopher.Name + ".png"
} else {
//by default we return dr who gopher
URL = "https://github.com/scraly/gophers/raw/main/dr-who.png"
}
response, err := http.Get(URL)
if err != nil {
fmt.Println("error")
}
return operations.NewGetGopherNameOK().WithPayload(response.Body)
}
我们使用了多个 Go 库,因此您可以go get <my-lib>
使用所需的依赖项执行命令,或者在go.mod
文件中复制/粘贴所需的依赖项块:
module github.com/scraly/learning-go-by-examples/go-rest-api
go 1.16
require (
github.com/go-openapi/errors v0.20.0
github.com/go-openapi/loads v0.20.2
github.com/go-openapi/runtime v0.19.29
github.com/go-openapi/spec v0.20.1
github.com/go-openapi/strfmt v0.20.0
github.com/go-openapi/swag v0.19.13
github.com/jessevdk/go-flags v1.5.0
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
)
如您所见,在我们的main.go
文件中,我们初始化了一个 REST API swagger 服务器,并定义了 3 个处理程序(及其实现:
- 检查健康处理程序
- 获取HelloUserHandler
- 获取GopherNameHandler
让我们深入研究这 3 个处理程序的实现:
健康路线 -> /healthz
根据如下代码:
//Health route returns OK
func Health(operations.CheckHealthParams) middleware.Responder {
return operations.NewCheckHealthOK().WithPayload("OK")
}
当用户调用/healthz
路由时,我们会向他们发送一个包含 OK 字符串的响应。
HelloUser 路由 -> /hello/{user}
根据如下代码:
//GetHelloUser returns Hello + user name
func GetHelloUser(user operations.GetHelloUserParams) middleware.Responder {
return operations.NewGetHelloUserOK().WithPayload("Hello " + user.User + "!")
}
当用户调用/hello/{user}
路由时,我们会向他们发送以“Hello”+ {user} 为字符串的响应。
GopherName 路由 -> /gopher/{name}
根据如下代码:
//GetGopherByName returns a gopher in png
func GetGopherByName(gopher operations.GetGopherNameParams) middleware.Responder {
var URL string
if gopher.Name != "" {
URL = "https://github.com/scraly/gophers/raw/main/" + gopher.Name + ".png"
} else {
//by default we return dr who gopher
URL = "https://github.com/scraly/gophers/raw/main/dr-who.png"
}
response, err := http.Get(URL)
if err != nil {
fmt.Println("error")
}
return operations.NewGetGopherNameOK().WithPayload(response.Body)
}
当用户调用/gopher/{name}
路由时,他们会收到一张可爱的小地鼠图片,并将图片返回给用户。如果名称为空,我们将默认返回《神秘博士》里的地鼠。
让我们构建我们的应用程序...
在 Go 中,您可以轻松地在可执行二进制文件中构建应用程序:
$ go build -o bin/go-rest-api internal/main.go
该命令将在文件夹中生成可执行二进制文件bin/
。
或者您可以执行运行任务:
$ task build
task: [build] GOFLAGS=-mod=mod go build -o bin/go-rest-api internal/main.go
现在我们可以执行二进制文件:
$ ./bin/go-rest-api
2021/07/13 20:21:34 Serving hello API at http://[::]:8080
凉爽的!
...适用于其他环境/操作系统
如果你想更深入地了解 Go,我喜欢它的地方在于,你可以为多种环境生成应用程序(可执行二进制文件),而不仅仅是你自己的环境!换句话说,你可以使用go build
命令为 macOS、Windows、Linux 等环境交叉编译 Go 应用程序。
对于 Windows:
# Windows 32 bits
$ GOOS=windows GOARCH=386 go build -o bin/go-rest-api-win-386 internal/main.go
# Windows 64 bits
$ GOOS=windows GOARCH=amd64 go build -o bin/go-rest-api-win-64 internal/main.go
对于 Linux:
# Linux 32 bits
$ GOOS=linux GOARCH=386 go build -o bin/go-rest-api-linux-386 internal/main.go
# Linux 64 bits
$ GOOS=linux GOARCH=amd64 go build -o bin/go-rest-api-linux-64 internal/main.go
对于 MacOS:
# MacOS 32 bits
$ GOOS=darwin GOARCH=386 go build -o bin/go-rest-api-darwin-386 internal/main.go
# MacOS 64 bits
$ GOOS=darwin GOARCH=amd64 go build -o bin/go-rest-api-darwin-64 internal/main.go
# MacOS 64 bits for M1 chip
$ GOOS=darwin GOARCH=arm64 go build -o bin/go-rest-api-darwin-arm64 internal/main.go
现在您可以与您的朋友分享您的精彩应用程序:-)。
让我们测试一下我们的应用程序
我们的应用程序正在运行,所以现在我们可以使用curl
命令测试我们的路线:
$ curl localhost:8080
{"code":404,"message":"path / was not found"}%
此路径未定义,我们有一个 404 错误代码,正常:-)。
$ curl localhost:8080/healthz
OK
$ curl localhost:8080/hello/aurelie
"Hello aurelie!"
$ curl -O localhost:8080/gopher/dr-who
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 992k 0 992k 0 0 955k 0 --:--:-- 0:00:01 --:--:-- 955k
$ file dr-who
dr-who: PNG image data, 1700 x 1460, 8-bit/color RGBA, non-interlaced
您还可以localhost:8080/gopher/dr-who
在浏览器中显示我们的小 Gopher :-)。
完美的! :-)
结论
正如我们在本文中看到的,可以在几秒钟内创建一个简单的 HTTP 服务器,并在几分钟内用 Go 创建 HTTP REST API 服务器。
所有代码均可在以下位置找到: https: //github.com/scraly/learning-go-by-examples/tree/main/go-rest-api
在接下来的文章中,我们将使用 Go 创建其他类型的应用程序。
希望你会喜欢它。
文章来源:https://dev.to/aurelievache/learning-go-by-examples-part-2-create-an-http-rest-api-server-in-go-1cdm