如何用 Go 快速编写出 HTTP REST API 服务?
在本教程中,明白如何用Go语言写出一个HTTP REST API服务。
作者 | Aurelie Vache
译者 | 槐序,责编 | 郭芮
出品 | CSDN(ID:CSDNnews)
以下为译文:
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
git clone https://github.com/scraly/http-go-server.git
cd http-go-server
$ go mod init github.com/scraly/http-go-server
go: creating new go.mod: module github.com.scraly/http-go-server
.
├── README.md
├── bin
├── doc
├── go.mod
├── internal
├── pkg
│ └── swagger
└── scripts
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:https://raw.githubusercontent.com/scraly/http-go-server/master/Makefile
## Build all binaries
build:
$(GO) build -o bin/http-go-server internal/main.go
$ swagger version
现在要做的第一件事是在我们的代码中创建swagger标准文档:
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 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
$ make swagger.validate
2019/10/27 21:15:12
The swagger spec at "pkg/swagger/swagger.yml" is valid against swagger specification 2.0
$ make swagger.doc
如果你在浏览器中打开已经生成的doc/index.html页面,可以查看HTML endpoints定义:
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
$ 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服务器的实现是节省了时间的。
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"
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}
//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
$ 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
$ 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
---
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文件特性中,你可以添加例外项,黑名单和白名单特性。
$ 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
.
├── 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
热 文 推 荐
☞实例分析+ 实践步骤,手把手教你编写以太坊、EOS智能合约!