在本教程中,明白如何用Go语言写出一个HTTP REST API服务。
作者 | Aurelie Vache
译者 | 槐序,责编 | 郭芮
出品 | CSDN(ID:CSDNnews)
以下为译文:
学习一门新语言并不容易,但是如果有具体的例子和手把手指导教程,就很容易上手了。因此,我决定编写一系列分步指导教程。
让我们使用Go语言的强大功能来编写一个HTTP REST API 服务。
首先要做的就是安装GVM(Go版本管理器),当然还有安装GO。
要安装GO,你可以按照官方网站上的安装步骤进行操作,也可以使用GVM来安装。对于Go而言,GVM是一个非常实用的版本管理工具,它允许你通过指定所需版本来更新Go的版本。
安装
Bash:
bash < <(curl -s-S-L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
zsh:
zsh < <(curl -s-S-L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
用法
$ gvm
Usage: gvm [command]
Description:
GVM is the Go Version Manager
Commands:
version — print the gvm version number
get — gets the latest code (for debugging)
use — select a go version to use (--default to set permanently)
diff — view changes to Go root
help — display this usage text
implode — completely remove gvm
install — install go versions
uninstall — uninstall go versions
cross — install go cross compilers
linkthis — link this directory into GOPATH
list — list installed go versions
listall — list available versions
alias — manage go version aliases
pkgset — manage go packages sets
pkgenv — edit the environment for a package set
让我们比较感兴趣的GVM 命令是gvm install命令,它的用法如下:
$ gvm install [version] [options]
Go的安装:
$ gvm install go1.13.3 -B
$ gvm use go1.13.3 --default
在你的.zshrc或者.bashrc文件中,设置$GOROOT 和 $GOPATH环境变量,下面是一个例子:(原文下面的2 3 4代码行命令错误,少了空格)
[[ -s"$HOME/.gvm/scripts/gvm" ]] && source"$HOME/.gvm/scripts/gvm"
export GOPATH=$HOME/go
export GOBIN=$GOPATH/bin
export PATH=${PATH}:$GOBIN
以上就是利用版本管理器来安装GO。现在进入本文章的核心,来创建我们第一个CLI(命令行程序)。
现在我们在GitHub中创建一个仓库(为了共享和开源)。
首先,登录GitHub网站,单击仓库链接,创建一个名叫“http-go-server”的仓库:
然后,在你本地机器上,把这个新建的仓库克隆(git clone)到你想放的地方。
“放任何我想放的地方?你确定?”
我们会用GO模块作为依赖项,所以不要忘记在GOPATH目录外git clone。但事实上,好消息是,从GO 1.13版本开始,我们不需要再担心这个问题了。Go模块现在可以在GOPATH目录下和目录外使用。
这也是我在本文选择使用GO 1.13版本的原因。
现在,为了方便找回及同步本地代码到git仓库,我们要git clone这个仓库:
$git clone https://github.com/scraly/http-go-server.git
$cd http-go-server
然后初始化go模块(依赖管理):
$ go mod init github.com/scraly/http-go-server
go: creating new go.mod: module github.com.scraly/http-go-server
我们将创建一个简单的HTTP服务,但是为了代码组织方面具有良好实践。因此我们不会将所有的代码放到main.go文件,我们会创建一个代码目录然后管理我们的代码。
创建下面的文件目录结构:
.
├── README.md
├── bin
├── doc
├── go.mod
├── internal
├── pkg
│ └── swagger
└── scripts
现在开始编写HTTP服务代码。
Go是一个很强大的语言。其中一个强大的功能就是有许多可用的内置包,例如net/HTTP。在internal/路径下面创建一个main.go文件,如下所示:
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的传入请求,并在返回到根目录/。
现在构建我们的应用来测试它,然后启动应用程序二进制文件:
$ go run internal/main.go
2019/10/27 20:53:39 Listening on localhost:8080 ...
为了测试你的HTTP服务,你可以使用curl命令来测试下 localhost:8080,或者直接用浏览器输入这个URL来测试:
$ curl localhost:8080
Hello, "/"%
很好,我们创建了一个小型的HTTP服务,它可以正常运行。
现在我们可以在二进制可执行文件中构建它:
$ go build -o bin/http-go-server internal/main.go
很好,我们在几分钟内完成了,但是我们将在下文深入了解:-)。
我不想手动执行每个命令,所以,为了持续改进我们的应用,好的方法就是创建一个Makefile 文件,这样我们就可以直接通过命令行来构建应用,生成文件,生成swagger文档,检查许可证以及执行单元测试和静态测试。
我们可以在Makefile里面定义好一系列将由make工具执行的任务。
因此,在这个项目中,我创建了一个Makefile文件,大家可以直接下载,这样就能节约大家的时间。
Makefile:https://raw.githubusercontent.com/scraly/http-go-server/master/Makefile
出于好奇,在Makefile文件中,我们创建几个指令(targets,可以是目标文件或者执行文件或者标签)用来执行一个或者多个命令。
总结下来就是,一个指令需要或者不需要依赖文件(prerequisites)都将执行方法(recipe):target: prerequisites。
<TAB> recipe
在我创建的Makefile文件中,一个build指令构建和打包我们的应用为二进制文件,存放到目录bin/http-go-server下:
## Build all binaries
build:
$(GO) build -o bin/http-go-server internal/main.go
现在我们将优化我们的HTTP服务,并使用Swagger,它可以处理我们的HTTP endpoints定义。
什么是Swagger?
Swagger允许你提供符合OpenAPI规范的标准化APIs文档。
因为有了Swagger应用,使用Swagger标准文件输入,你可以在最后生成代码,并且可以为用户提供HTML格式的API文档。
如果你想构建一个公共API,请不要犹豫使用Swagger。
Swagger安装:请参考go-swagger安装页面(https://github.com/go-swagger/go-swagger/blob/master/docs/install.md)。
然后,为了检查该工具在你的系统中正确安装,你可以检查Swagger的版本。
$ swagger version
现在要做的第一件事是在我们的代码中创建swagger标准文档:
pkg/swagger/swagger.yml:
consumes:
- application/json
info:
description: HTTP server in Go with Swagger endpoints definition
title: http-go-server
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.
每次修改swagger文件后,一个好的做法是检查文件的有效性。
为此,我们可以使用swagger validate命令:
$ swagger validate pkg/swagger/swagger.yml
2019/10/27 21:14:47
The swagger spec at "pkg/swagger/swagger.yml" is valid against swagger specification 2.0
或者可以使用一个Makefile指令:
$ make swagger.validate
2019/10/27 21:15:12
The swagger spec at "pkg/swagger/swagger.yml" is valid against swagger specification 2.0
太棒了,我们的Swagger文件是有效的。
现在我们将在HTML文档中创建Swagger定义。为此,我们可以使用Docker镜像,该镜像考虑到了Swagger YAML定义并且返回一个漂亮的HTML页面。
$ make swagger.doc
如果你在浏览器中打开已经生成的doc/index.html页面,可以查看HTML endpoints定义:
很好,是具有可读性的。
归功于Swagger规范,现在我们可以生成代码。
为此,我们进入到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
由于有Makefile,我们可以执行生成swagger go代码的文件:
$ make generate
==> generating go code
GOFLAGS=-mod=vendor go generate github.com.scraly/http-go-server/internal github.com.scraly/http-go-server/pkg/swagger
如下所示,使用一个swagger endpoint定义输入,生成了很多代码,这对于HTTP服务器的实现是节省了时间的。
下面使用Swagger编辑main.go文件:
package main
import (
"log"
"github.com/go-openapi/loads"
"github.com/scraly/http-go-server/pkg/swagger/server/restapi"
"github.com/scraly/http-go-server/pkg/swagger/server/restapi/operations"
)
func main{
// Initialize Swagger
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
log.Fatalln(err)
}
api := operations.NewHelloAPI(swaggerSpec)
server := restapi.NewServer(api)
defer server.Shutdown
server.Port = 8080
// Start listening using having the handlers and port
// already set up.
if err := server.Serve; err != nil {
log.Fatalln(err)
}
}
现在启动服务:
$ go run internal/main.go
2019/10/28 14:27:26 Serving hello at http://[::]:8080
现在我们可以做几个测试:
$ curl localhost:8080
{"code":404,"message":"path / was not found"}%
$ curl localhost:8080/hello
{"code":404,"message":"path /hello was not found"}%
$ curl localhost:8080/hello/aurelie
"operation GetHelloUser has not yet been implemented"
完美,我们的HTTP服务正在应答,甚至告知我们GetHelloUser接口尚未实现,接下来实现它吧!
编辑main.go文件如下所示:
package main
import (
"log"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime/middleware"
"github.com/scraly/http-go-server/pkg/swagger/server/restapi"
"github.com/scraly/http-go-server/pkg/swagger/server/restapi/operations"
)
func main{
// Initialize Swagger
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
log.Fatalln(err)
}
api := operations.NewHelloAPI(swaggerSpec)
server := restapi.NewServer(api)
defer func{
if err := server.Shutdown; err != nil {
// error handle
log.Fatalln(err)
}
}
server.Port = 8080
// Implement the CheckHealth handler
api.CheckHealthHandler = operations.CheckHealthHandlerFunc(
func(user operations.CheckHealthParams) middleware.Responder {
return operations.NewCheckHealthOK.WithPayload("OK")
})
// Implement the GetHelloUser handler
api.GetHelloUserHandler = operations.GetHelloUserHandlerFunc(
func(user operations.GetHelloUserParams) middleware.Responder {
return operations.NewGetHelloUserOK.WithPayload("Hello " + user.User + "!")
})
// Start server which listening
if err := server.Serve; err != nil {
log.Fatalln(err)
}
}
再来一次,我们重启服务:
$ go run internal/main.go
2019/10/28 21:45:38 Serving hello at http://[::]:8080
$ curl localhost:8080/hello/aurelie
"Hello aurelie!"
$ curl localhost:8080/healthz
OK%
很好,我们有一个遵守OpenAPI规范的HTTP服务和两个路由:
GET/healthz
GET/hello/{name}
我们可以到此为止,因为我们的HTTP服务正常工作,但是我们将继续深入。
我们将在main.go文件(在文件末尾)中,为路由实现增加新函数。
//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 + "!")
}
现在我们只需在主函数中调用这些新函数:
func main{
// Initialize Swagger
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
log.Fatalln(err)
}
api := operations.NewHelloAPI(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)
// Start server which listening
if err := server.Serve; err != nil {
log.Fatalln(err)
}
}
和之前一样,我们测试静态测试是否通过,以及我们应用是否构建:
$ make lint.full
==> linters (slow)
INFO [config_reader] Config search paths: [./ /Users/uidn3817/git/github.com/scraly/http-go-server/internal /Users/uidn3817/git/github.com/scraly/http-go-server /Users/uidn3817/git/github.com/scraly /Users/uidn3817/git/github.com /Users/uidn3817/git /Users/uidn3817 /Users /]
INFO [config_reader] Used config file .golangci.yml
INFO [lintersdb] Active 13 linters: [deadcode errcheck goimports golint govet ineffassign maligned misspell nakedret structcheck typecheck unconvert varcheck]
INFO [loader] Go packages loading at mode load types and syntax took 1.474090863s
INFO [loader] SSA repr building timing: packages building 15.964643ms, total 220.705097ms
INFO [runner] worker.4 took 652.824µs with stages: deadcode: 244.82µs, unconvert: 110.42µs, errcheck: 102.565µs, varcheck: 81.099µs, structcheck: 38.623µs, maligned: 34.263µs, nakedret: 22.825µs, typecheck: 5.339µs
INFO [runner] worker.6 took 1.883µs
INFO [runner] worker.8 took 2.125µs
INFO [runner] worker.5 took 1.040528ms with stages: ineffassign: 1.035173ms
INFO [runner] worker.7 took 3.211184ms with stages: goimports: 3.2029ms
INFO [runner] worker.3 took 102.06494ms with stages: misspell: 102.056568ms
INFO [runner] worker.1 took 120.104406ms with stages: golint: 120.096599ms
INFO [runner] worker.2 took 204.48169ms with stages: govet: 204.471908ms
INFO [runner] Workers idle times: #1: 84.514968ms, #3: 86.547645ms, #4: 203.167851ms, #5: 202.957443ms, #6: 203.09743ms, #7: 201.160969ms, #8: 202.973757ms
INFO [runner] processing took 18.697µs with stages: max_same_issues: 14.808µs, skip_dirs: 737ns, cgo: 498ns, nolint: 420ns, filename_unadjuster: 398ns, max_from_linter: 281ns, autogenerated_exclude: 172ns, path_prettifier: 170ns, identifier_marker: 167ns, diff: 164ns, skip_files: 161ns, replacement_builder: 158ns, exclude: 156ns, source_code: 90ns, max_per_file_from_linter: 81ns, path_shortener: 79ns, uniq_by_line: 79ns, exclude-rules: 78ns
INFO File cache stats: 0 entries of total size 0B
INFO Memory: 24 samples, avg is 248.1MB, max is 671.8MB
INFO Execution took 2.277787079s
另外一个好的做法是使用linters 分析来做静态代码测试。为此,我们可以使用golang-ci工具(Go里的快速linter,比gometaliner好)。
还是因为有了Makefile,你只需要获取此处我列出的工具,例如golang-cli:
$ make get.tools
一个好的做法是新建一个.golangci.yml文件来定义我们想要的linter配置。下面是golang-cli配置示例:
run:
modules-download-mode: vendor
deadline: 10m
issues-exit-code: 1
tests: true
skip-files:
- ".*\.pb\.go$"
- ".*\.gen\.go$"
- "mock_.*\.go"
linters:
enable:
- govet # check standard vet rules
- golint # check standard linting rules
- staticcheck# comprehensive checks
- errcheck # find unchecked errors
- ineffassign# find ineffective assignments
- varcheck # find unused global variables and constants
- structcheck# check for unused struct parameters
- deadcode # find code that is not used
- nakedret # check for naked returns
- goimports # fix import order
- misspell # check spelling
- unconvert # remove unnecessary conversions
- maligned # check for better memory usage
disable:
- goconst # check for things that could be replaced by constants
- gocyclo # needs tweaking
- depguard # unused
- gosec # needs tweaking
- dupl # slow
- interfacer # not that useful
- gosimple # part of staticcheck
- unused # part of staticcheck
- megacheck # part of staticcheck
- lll
fast: false
output:
format: colored-line-number
print-issued-lines: true
print-linter-name: true
linters-settings:
errcheck:
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
# default is false: such cases aren't reported by default.
check-type-assertions: false
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
# default is false: such cases aren't reported by default.
check-blank: false
govet:
# report about shadowed variables
#TODO# check-shadowing: true
# Obtain type information from installed (to $GOPATH/pkg) package files:
# golangci-lint will execute `go install -i` and `go test -i` for analyzed packages
# before analyzing them.
# Enable this option only if all conditions are met:
# 1. you use only "fast" linters (--fast e.g.): no program loading occurs
# 2. you use go >= 1.10
# 3. you do repeated runs (false for CI) or cache $GOPATH/pkg or `go env GOCACHE` dir in CI.
use-installed-packages: false
golint:
min-confidence: 0.8
gofmt:
simplify: true
gocyclo:
min-complexity: 10
maligned:
suggest-new: true
dupl:
threshold: 150
goconst:
min-len: 3
min-occurrences: 3
misspell:
locale: US
lll:
line-length: 140
tab-width: 1
unused:
# treat code as a program (not a library) and report unused exported identifiers; default is false.
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
unparam:
# call graph construction algorithm (cha, rta). In general, use cha for libraries,
# and rta for programs with main packages. Default is cha.
algo: cha
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
nakedret:
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
max-func-lines: 30
prealloc:
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
# True by default.
simple: true
range-loops: true# Report preallocation suggestions on range loops, true by default
for-loops: false# Report preallocation suggestions on for loops, false by default
issues:
max-per-linter: 0
max-same: 0
new: false
exclude-use-default: false
接下来我们可以检查代码是否包含lint错误:
$ make lint.full
==> linters (slow)
INFO [config_reader] Config search paths: [./ /Users/uidn3817/git/github.com/scraly/http-go-server/internal /Users/uidn3817/git/github.com/scraly/http-go-server /Users/uidn3817/git/github.com/scraly /Users/uidn3817/git/github.com /Users/uidn3817/git /Users/uidn3817 /Users /]
INFO [config_reader] Used config file .golangci.yml
INFO [lintersdb] Active 13 linters: [deadcode errcheck goimports golint govet ineffassign maligned misspell nakedret structcheck typecheck unconvert varcheck]
INFO [loader] Go packages loading at mode load types and syntax took 1.403040989s
INFO [loader] SSA repr building timing: packages building 17.446103ms, total 215.11635ms
INFO [runner] worker.1 took 319.338µs with stages: unconvert: 126.709µs, structcheck: 105.706µs, varcheck: 80.624µs
INFO [runner] worker.8 took 279.76µs with stages: errcheck: 102.203µs, nakedret: 88.6µs, deadcode: 54.547µs, maligned: 22.796µs, typecheck: 2.416µs
INFO [runner] worker.2 took 908ns
INFO [runner] worker.7 took 1.424891ms with stages: ineffassign: 1.419068ms
INFO [runner] worker.4 took 2.395856ms with stages: goimports: 2.39105ms
INFO [runner] worker.6 took 75.843872ms with stages: golint: 75.832987ms
INFO [runner] worker.5 took 77.126536ms with stages: misspell: 77.092913ms
INFO [runner] worker.3 took 124.506172ms with stages: govet: 124.498137ms
INFO [runner] Workers idle times: #1: 124.053298ms, #2: 123.973576ms, #4: 122.078696ms, #5: 47.339761ms, #6: 48.693713ms, #7: 122.946009ms, #8: 124.035904ms
INFO [runner] processing took 19.597µs with stages: max_same_issues: 16.123µs, cgo: 541ns, skip_dirs: 493ns, nolint: 398ns, max_from_linter: 280ns, path_prettifier: 179ns, filename_unadjuster: 172ns, replacement_builder: 170ns, autogenerated_exclude: 170ns, exclude: 164ns, diff: 162ns, skip_files: 161ns, identifier_marker: 150ns, source_code: 97ns, path_shortener: 97ns, max_per_file_from_linter: 82ns, exclude-rules: 80ns, uniq_by_line: 78ns
INFO File cache stats: 0 entries of total size 0B
INFO Memory: 23 samples, avg is 255.8MB, max is 672.0MB
INFO Execution took 2.119246564s
很好,一起都好。
如果你想编辑.golangci.yml文件,请查看golang-ci支持的linters。
另外一个好的实践是检查许可证。
你需要去检查许可证(你项目依赖项使用的许可证),例如,当你希望你的应用程序或者公司的代码开源时,为了避免使用被禁的许可证,就要去检查。
在Go中存在一个名为wwhrd的工具。
首先,我们创建一个名为.wwhrd.yml的文件,来定义选项:
---
blacklist:
- AGPL-3.0
- GPL-2.0
- GPL-3.0
- CDDL-1.0
whitelist:
- Apache-2.0
- MIT
- NewBSD
- FreeBSD
- LGPL-2.1
- LGPL-3.0
- ISC
- MPL-2.0
- EPL-1.0
- Unlicense
# exceptions:
# - github.com/davecgh/go-spew/spew/... # ISC License misrecognized
# - github.com/dchest/uniuri # https://creativecommons.org/publicdomain/zero/1.0/
在这个wwhrd文件特性中,你可以添加例外项,黑名单和白名单特性。
一个检查证书的指令已经写在了Makefile文件中的,因此你只需要执行它即可:
$ make license
==> license check
wwhrd check
INFO[0000] Found Approved license license=Apache-2.0 package=go.mongodb.org/mongo-driver/bson/primitive
INFO[0000] Found Approved license license=Apache-2.0 package=github.com/go-openapi/swag
INFO[0000] Found Approved license license=NewBSD package=github.com/PuerkitoBio/purell
INFO[0000] Found Approved license license=Apache-2.0 package=github.com/go-openapi/jsonreference
INFO[0000] Found Approved license license=Apache-2.0 package=go.mongodb.org/mongo-driver/bson/bsoncodec
INFO[0000] Found Approved license license=Apache-2.0 package=github.com/go-openapi/loads
…
很好,没有许可证问题了。
现在,我们可以在一个可执行的二进制文件里构建应用,并测试二进制文件:
$ make build
go build -o bin/http-go-server internal/main.go
$ ./bin/http-go-server
2019/10/28 21:47:38 Serving hello at http://[::]:8080
非常棒:-)
正如你在这篇文章第一部分看到的一样,使用net/http包和Gorilla/mux作为路由,可以在几分钟或几秒钟内创建一个HTTP服务,但是我想向你展示的是,在代码组织的最佳实践方面如何一步一步深入。为了符合OpenAPI标准使用了Swagger,还使用一些其他有用的工具。
最后,我们小型的HTTP服务成长了一些,如下代码目录结构所示:
.
├── Makefile
├── README.md
├── bin
│ └── http-go-server
├── doc
│ └── index.html
├── go.mod
├── go.sum
├── internal
│ └── main.go
├── pkg
│ └── swagger
│ ├── gen.go
│ ├── server
│ │ └── restapi
│ │ ├── configure_hello.go
│ │ ├── doc.go
│ │ ├── embedded_spec.go
│ │ ├── operations
│ │ │ ├── check_health.go
│ │ │ ├── check_health_parameters.go
│ │ │ ├── check_health_responses.go
│ │ │ ├── check_health_urlbuilder.go
│ │ │ ├── get_hello_user.go
│ │ │ ├── get_hello_user_parameters.go
│ │ │ ├── get_hello_user_responses.go
│ │ │ ├── get_hello_user_urlbuilder.go
│ │ │ └── hello_api.go
│ │ └── server.go
│ └── swagger.yml
├── scripts
└── vendor
├──…
└── modules.txt
└── .gitignore
└── .golangci.yml
└── .wwhrd.yml
所有的代码都可以在GitHub仓库找到:https://github.com/scraly/http-go-server,希望这类文章对你有所帮助。
原文:https://dzone.com/articles/how-to-write-a-http-rest-api-server-in-go-in-minut
本文为 CSDN 翻译,转载请注明来源出处。
【End】