烂翻译系列之Golang新手入门——教程:使用Go和Gin开发一个RESTful API

目录

Prerequisites  前提条件

Design API endpoints  设计API终节点

Create a folder for your code  为你的代码创建文件夹

Create the data  创建数据

Write a handler to return all items  编写一个处理器来返回所有项

Write a handler to add a new item  编写一个处理器来增加新项

Write a handler to return a specific item  编写一个处理器来返回特定项

Conclusion  结论

Completed code  完整代码

This tutorial introduces the basics of writing a RESTful web service API with Go and the Gin Web Framework (Gin).

本教程介绍使用Go和Gin Web Framework(Gin)编写RESTful web service API的基本知识。

You’ll get the most out of this tutorial if you have a basic familiarity with Go and its tooling. If this is your first exposure to Go, please see Tutorial: Get started with Go for a quick introduction.

如果你对Go和它的工具已经有基本了解,将在本章中充分发挥。如果这是你首次面对Go,请看教程:从Go开始,作为快速介绍。

Gin simplifies many coding tasks associated with building web applications, including web services. In this tutorial, you’ll use Gin to route requests, retrieve request details, and marshal JSON for responses.

Gin简化许多与构建web应用相关编码任务,包括web services。在本教程中,你将使用Gin来路由请求,检索请求细节,并将响应解析为JSON。

In this tutorial, you will build a RESTful API server with two endpoints. Your example project will be a repository of data about vintage jazz records.

在本教程中,你将构建一个有两个终节点的RESTful API server。你的示例项目将是关于古典爵士乐唱片的数据存储库。

The tutorial includes the following sections:

本教程包括以下部分:

  1. Design API endpoints.  设计API终节点
  2. Create a folder for your code.  为你的代码创建文件夹
  3. Create the data.  创建数据
  4. Write a handler to return all items.  编写一个处理器来返回所有项
  5. Write a handler to add a new item.  编写一个处理器来增加新项
  6. Write a handler to return a specific item.  编写一个处理器来返回特定项

Note: For other tutorials, see Tutorials.

注意:其它教程,请看教程。

To try this as an interactive tutorial you complete in Google Cloud Shell, click the button below.

要在谷歌云Shell上以交互方式试用此教程,点击后面的按钮。

Open in Cloud Shell

Prerequisites  前提条件

  • An installation of Go 1.16 or later. For installation instructions, see Installing Go.  安装1.16以上版本的Go。对于安装说明,请看安装Go。
  • A tool to edit your code. Any text editor you have will work fine.  编辑代码的工具。任何称手的文本编辑器。
  • A command terminal. Go works well using any terminal on Linux and Mac, and on PowerShell or cmd in Windows.  一个命令终端。使用Linux或Mac上的任何终端和Windows上的PowerShell,Go工作的都很好。
  • The curl tool. On Linux and Mac, this should already be installed. On Windows, it’s included on Windows 10 Insider build 17063 and later. For earlier Windows versions, you might need to install it. For more, see Tar and Curl Come to Windows.  curl工具。在Linux和Mac上,它应该已经安装。对于Windows,它被包括在Windows 10 insider build 17063或以上版本。对于Windows的早期版本,你可能需要安装。更多信息,请看Windows上的Tar和Curl。

Design API endpoints  设计API终节点

You’ll build an API that provides access to a store selling vintage recordings on vinyl. So you’ll need to provide endpoints through which a client can get and add albums for users.

你将构建一个访问销售古典乐唱片的商店的API。这样,你将需要提供终节点,客户端可以通过终节点为用户获取或增加专辑。

When developing an API, you typically begin by designing the endpoints. Your API’s users will have more success if the endpoints are easy to understand.

当开发API时,你通常以设计终节点开始。如果终节点易于理解,你的API的用户将取得更大的成功。

Here are the endpoints you’ll create in this tutorial.

这里是本教程中你将创建的终节点。

/albums

  • GET – Get a list of all albums, returned as JSON.  获取专辑列表,以JSON返回。
  • POST – Add a new album from request data sent as JSON.  根据JSON格式的请求数据增加新专辑。

/albums/:id

  • GET – Get an album by its ID, returning the album data as JSON.  根据专辑的ID来获取专辑,以JSON格式返回专辑数据。

Next, you’ll create a folder for your code.

将下来,你将为即将编写的代码创建文件夹。

Create a folder for your code  为即将编写的代码创建文件夹

To begin, create a project for the code you’ll write.  开始,为即将编写的代码创建一个项目。

  1. Open a command prompt and change to your home directory.  打开一个命令提示符窗口并定位到你的主目录。

    On Linux or Mac:  在Linux或Mac上:

    $ cd

    On Windows:  在Windows上:

    C:\> cd %HOMEPATH%
  2. Using the command prompt, create a directory for your code called web-service-gin.  使用命令提示符,为你的代码创建一个名为web-service-gin的目录。

    $ mkdir web-service-gin
    $ cd web-service-gin
  3. Create a module in which you can manage dependencies.  创建模块来管理依赖。

    Run the go mod init command, giving it the path of the module your code will be in.  运行go mod init命令,参数为模块路径。

    $ go mod init example/web-service-gin
    go: creating new go.mod: module example/web-service-gin

    This command creates a go.mod file in which dependencies you add will be listed for tracking. For more about naming a module with a module path, see Managing dependencies.  此命令创建一个名为go.mod的文件,依赖将在此文件中列出并跟踪。关于使用模块路径命名模块的更多信息,请看管理依赖。

Next, you’ll design data structures for handling data.

接下来,你将为处理数据设计数据结构。

Create the data  创建数据

To keep things simple for the tutorial, you’ll store data in memory. A more typical API would interact with a database.

为使本教程简单化,你将在内存中存储数据。通常,API与数据库交互。

Note that storing data in memory means that the set of albums will be lost each time you stop the server, then recreated when you start it.

注意,在内存中存储数据,专辑集将在每次停止服务后丢失,然后在启动服务时重建。

Write the code  编码

  1. Using your text editor, create a file called main.go in the web-service directory. You’ll write your Go code in this file.  使用文本编辑器,在web-service目录下创建名为main.go的文件。

  2. Into main.go, at the top of the file, paste the following package declaration.  进入main.go文件,在文件顶部,粘贴以下包声明。

    package main

    A standalone program (as opposed to a library) is always in package main.  独立程序(区别于类库)总是在main包中

  3. Beneath the package declaration, paste the following declaration of an album struct. You’ll use this to store album data in memory.  在包声明下方,粘贴以下album结构声明,你将使用它在内存中存放album数据。

    Struct tags such as json:"artist" specify what a field’s name should be when the struct’s contents are serialized into JSON. Without them, the JSON would use the struct’s capitalized field names – a style not as common in JSON.  形如json:"artist"的结构标签指定结构体内容被序列化为JSON时对应的字段名称。没有他们,JSON将使用结构体的兼容字段名称——JSON中不常见的风格。

    // album represents data about a record album.
    type album struct {
        ID     string  `json:"id"`
        Title  string  `json:"title"`
        Artist string  `json:"artist"`
        Price  float64 `json:"price"`
    }
  4. Beneath the struct declaration you just added, paste the following slice of album structs containing data you’ll use to start.  在你刚刚增加的结构体声明下方,粘贴下面的album类型的切片,这个切片包含你将开始使用的数据。

    // albums slice to seed record album data.
    var albums = []album{
        {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
        {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
        {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
    }

Next, you’ll write code to implement your first endpoint.

接下来,你将编码实现你的首个终节点。

Write a handler to return all items  编写返回所有项的处理器

When the client makes a request at GET /albums, you want to return all the albums as JSON.

当客户端发出GET /albums请求,你想要以JSON返回所有专辑。

To do this, you’ll write the following:

要这样做,你将编写以下代码:

  • Logic to prepare a response  准备响应的逻辑
  • Code to map the request path to your logic  映射请求路径到你的逻辑的代码

Note that this is the reverse of how they’ll be executed at runtime, but you’re adding dependencies first, then the code that depends on them.

注意,这与它们执行时的顺序是相反的,但是你首先增加依赖,然后增加依赖它们的代码。

Write the code  编码

  1. Beneath the struct code you added in the preceding section, paste the following code to get the album list.  在上一节增加的结构体代码下方,粘贴以下代码以获取专辑列表。

    This getAlbums function creates JSON from the slice of album structs, writing the JSON into the response.  此getAlbums函数将album结构切片解析为JSON,再将JSON写入响应。

    // getAlbums responds with the list of all albums as JSON.
    func getAlbums(c *gin.Context) {
        c.IndentedJSON(http.StatusOK, albums)
    }

    In this code, you:  在此代码中,你:

    • Write a getAlbums function that takes a gin.Context parameter. Note that you could have given this function any name – neither Gin nor Go require a particular function name format.  编写了getAlbums函数,它接收一个指向gin.Context的参数。注意你可以给这个函数随意取名——Gin和Go都对函数格式没有特定要求。

      gin.Context is the most important part of Gin. It carries request details, validates and serializes JSON, and more. (Despite the similar name, this is different from Go’s built-in context package.)  gin.Context是Gin中非常重要的部分。它携带有请求细节,验证并序列化JSON,还有更多。(尽管名字相似,但与Go内置的context包不同)

    • Call Context.IndentedJSON to serialize the struct into JSON and add it to the response.  调用Context.IndentedJSON将结构序列化为JSON并将JSON添加到响应。

      The function’s first argument is the HTTP status code you want to send to the client. Here, you’re passing the StatusOK constant from the net/http package to indicate 200 OK.  函数的首个参数是你想要发送给客户端的HTTP状态码。这里,你传入net/http包中的StatusOK常量表示200 OK。

      Note that you can replace Context.IndentedJSON with a call to Context.JSON to send more compact JSON. In practice, the indented form is much easier to work with when debugging and the size difference is usually small.  注意,你可以使用Context.JSON代替Context.IndentedJSON来发送更加紧凑的JSON。事实上,缩进格式对于调试工作是非常容易的,并且大小差异通常较小。

  2. Near the top of main.go, just beneath the albums slice declaration, paste the code below to assign the handler function to an endpoint path.  在接近main.go顶部,albums切片的下方,粘贴后面的代码为处理器函数关联一个终节点路径。

    This sets up an association in which getAlbums handles requests to the /albums endpoint path.  这将到/albums终节点路径的请求关联到了getAlbums。

    func main() {
        router := gin.Default()
        router.GET("/albums", getAlbums)
    
        router.Run("localhost:8080")
    }

    In this code, you:  在此代码中,你:

    • Initialize a Gin router using Default.  使用Default初始化Gin路由。

    • Use the GET function to associate the GET HTTP method and /albums path with a handler function.  使用GET函数将GET HTTP方法、/albums路径与处理器函数联系起来。

      Note that you’re passing the name of the getAlbums function. This is different from passing the result of the function, which you would do by passing getAlbums() (note the parenthesis).  注意,你传入getAlbums函数的名字。这不同于传入函数的调用结果(如传入getAlbums())。

    • Use the Run function to attach the router to an http.Server and start the server.  使用Run函数将路由附加到http.Server并启动服务。

  3. Near the top of main.go, just beneath the package declaration, import the packages you’ll need to support the code you’ve just written.  在接近main.go顶部,紧挨着包声明下方,导入为你代码提供支持的包。

    The first lines of code should look like this:  前几行代码应该类似这样:

    package main
    
    import (
        "net/http"
    
        "github.com/gin-gonic/gin"
    )
  4. Save main.go.  保存main.go

Run the code  运行代码

  1. Begin tracking the Gin module as a dependency.  开始跟踪作为依赖的Gin模块

    At the command line, use go get to add the github.com/gin-gonic/gin module as a dependency for your module. Use a dot argument to mean “get dependencies for code in the current directory.”  在命令行窗口,使用go get命令来为你的模块添加github.com/gin-gonic/gin模块依赖。使用点参数意味着"为当前目录代码获取依赖。"

    $ go get .
    go get: added github.com/gin-gonic/gin v1.7.2

    Go resolved and downloaded this dependency to satisfy the import declaration you added in the previous step.  Go解析并下载此依赖以满足导入声明(你前一步添加的导入声明)。

  2. From the command line in the directory containing main.go, run the code. Use a dot argument to mean “run code in the current directory.”  在命令行窗口,定位到包含main.go的目录,运行代码。使用点参数意味着“在当前目录运行代码。”

    $ go run .

    Once the code is running, you have a running HTTP server to which you can send requests.  一旦代码运行起来,就拥有一个运行中的HTTP服务,你可以向它发送请求。

  3. From a new command line window, use curl to make a request to your running web service.  打开一个新的命令行窗口,使用curl向正在运行的web service发送一个请求。

    $ curl http://localhost:8080/albums

    The command should display the data you seeded the service with.  此命令应该显示你在服务中留存的数据。

    [
            {
                    "id": "1",
                    "title": "Blue Train",
                    "artist": "John Coltrane",
                    "price": 56.99
            },
            {
                    "id": "2",
                    "title": "Jeru",
                    "artist": "Gerry Mulligan",
                    "price": 17.99
            },
            {
                    "id": "3",
                    "title": "Sarah Vaughan and Clifford Brown",
                    "artist": "Sarah Vaughan",
                    "price": 39.99
            }
    ]

You’ve started an API! In the next section, you’ll create another endpoint with code to handle a POST request to add an item.

你已经启动了一个API!在接下来的部分中,你将使用创建另一个终节点,这个终节点处理POST请求并增加一个新专辑。

Write a handler to add a new item  编写处理器添加新项

When the client makes a POST request at /albums, you want to add the album described in the request body to the existing albums data.

当客户端发起POST /albums请求,你想要将请求主体中描述的专辑添加到已有的专辑数据中。

To do this, you’ll write the following:

要做到这样,你将编写以下代码:

  • Logic to add the new album to the existing list.  添加新专辑到已有列表的逻辑
  • A bit of code to route the POST request to your logic.  将POST请求路由到你的逻辑的一点代码

Write the code  编码

  1. Add code to add albums data to the list of albums.  增加代码将唱片数据添加到唱片列表。

    Somewhere after the import statements, paste the following code. (The end of the file is a good place for this code, but Go doesn’t enforce the order in which you declare functions.)  在import语句之后的某个地方,粘贴下方代码。(文件末尾是个好地方,但是Go不强制函数声明顺序。)

    // postAlbums adds an album from JSON received in the request body.
    func postAlbums(c *gin.Context) {
        var newAlbum album
    
        // Call BindJSON to bind the received JSON to
        // newAlbum.
        if err := c.BindJSON(&newAlbum); err != nil {
            return
        }
    
        // Add the new album to the slice.
        albums = append(albums, newAlbum)
        c.IndentedJSON(http.StatusCreated, newAlbum)
    }

    In this code, you:  在此代码中,你:

    • Use Context.BindJSON to bind the request body to newAlbum.  使用Context.BindJSON将请求体绑定到newAlbum。
    • Append the album struct initialized from the JSON to the albums slice.  将newAlbum增加到albums切片。
    • Add a 201 status code to the response, along with JSON representing the album you added.  向响应添加201状态码和newAlbum(以JSON表示)。
  2. Change your main function so that it includes the router.POST function, as in the following.  修改main函数,让它包括router.POST函数,

    func main() {
        router := gin.Default()
        router.GET("/albums", getAlbums)
        router.POST("/albums", postAlbums)
    
        router.Run("localhost:8080")
    }

    In this code, you:  在此代码中,你:

    • Associate the POST method at the /albums path with the postAlbums function.  将POST /albums路径与postAlbums函数关联。

      With Gin, you can associate a handler with an HTTP method-and-path combination. In this way, you can separately route requests sent to a single path based on the method the client is using.  通过Gin,你可以将处理器与HTTP 方法和路径关联。用这个办法,可以将请求分发到一个基于客户端使用的HTTP方法的单一的路径。

Run the code  运行代码

  1. If the server is still running from the last section, stop it.  如果从服务仍在运行(在上节时启动),请停止运行。

  2. From the command line in the directory containing main.go, run the code.  在命令行窗口,定位到main.go所在的目录,运行下方代码。

    $ go run .
  3. From a different command line window, use curl to make a request to your running web service.  打开另一个命令行窗口,使用curl来请求正在运行的web service。

    $ curl http://localhost:8080/albums \
        --include \
        --header "Content-Type: application/json" \
        --request "POST" \
        --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'

    The command should display headers and JSON for the added album.  此命令应该显示响应头和JSON格式的新增的专辑。

    HTTP/1.1 201 Created
    Content-Type: application/json; charset=utf-8
    Date: Wed, 02 Jun 2021 00:34:12 GMT
    Content-Length: 116
    
    {
        "id": "4",
        "title": "The Modern Sound of Betty Carter",
        "artist": "Betty Carter",
        "price": 49.99
    }
  4. As in the previous section, use curl to retrieve the full list of albums, which you can use to confirm that the new album was added.  上一节中,使用curl检索完整的专辑列表,你可以使用它来确认新专辑被添加到专辑列表中。

    $ curl http://localhost:8080/albums \
        --header "Content-Type: application/json" \
        --request "GET"

    The command should display the album list.  此命令应该显示专辑列表。

    [
            {
                    "id": "1",
                    "title": "Blue Train",
                    "artist": "John Coltrane",
                    "price": 56.99
            },
            {
                    "id": "2",
                    "title": "Jeru",
                    "artist": "Gerry Mulligan",
                    "price": 17.99
            },
            {
                    "id": "3",
                    "title": "Sarah Vaughan and Clifford Brown",
                    "artist": "Sarah Vaughan",
                    "price": 39.99
            },
            {
                    "id": "4",
                    "title": "The Modern Sound of Betty Carter",
                    "artist": "Betty Carter",
                    "price": 49.99
            }
    ]

In the next section, you’ll add code to handle a GET for a specific item.

在接下来的部分中,你将增加处理获取某个特定专辑的代码。

Write a handler to return a specific item  编写一个处理器返回特定专辑

When the client makes a request to GET /albums/[id], you want to return the album whose ID matches the id path parameter.

当客户端请求GET /albums/[id]时,你想要返回ID匹配路径参数id的专辑。

To do this, you will:  要这样做,你将:

  • Add logic to retrieve the requested album.  增加检索请求的专辑的逻辑
  • Map the path to the logic.  映射路径到逻辑。

Write the code  编码

  1. Beneath the postAlbums function you added in the preceding section, paste the following code to retrieve a specific album.  在postAlbums函数下方,粘贴下方代码以检索某个特定专辑。

    This getAlbumByID function will extract the ID in the request path, then locate an album that matches.  此getAlbumByID函数提取请求路径中的ID,然后定位到匹配的专辑。

    // getAlbumByID locates the album whose ID value matches the id
    // parameter sent by the client, then returns that album as a response.
    func getAlbumByID(c *gin.Context) {
        id := c.Param("id")
    
        // Loop over the list of albums, looking for
        // an album whose ID value matches the parameter.
        for _, a := range albums {
            if a.ID == id {
                c.IndentedJSON(http.StatusOK, a)
                return
            }
        }
        c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
    }

    In this code, you:  在此代码中,你:

    • Use Context.Param to retrieve the id path parameter from the URL. When you map this handler to a path, you’ll include a placeholder for the parameter in the path.  使用Context.Praram从URL中获取路径参数id。当映射处理器到某个路径时,你将在路径中包含参数的占位符。

    • Loop over the album structs in the slice, looking for one whose ID field value matches the id parameter value. If it’s found, you serialize that album struct to JSON and return it as a response with a 200 OK HTTP code.  循环切片中的专辑结构,查找专辑ID与参数值id相匹配的专辑。如果找到了,将专辑结构序列化为JSON与200 OK HTTP码作为响应返回。

      As mentioned above, a real-world service would likely use a database query to perform this lookup.  上面提到,在真实世界,服务可能会使用数据库查询来执行查找。

    • Return an HTTP 404 error with http.StatusNotFound if the album isn’t found.  如果未找到专辑,使用http.StatusNotFound返回HTTP 404错误。

  2. Finally, change your main so that it includes a new call to router.GET, where the path is now /albums/:id, as shown in the following example.  最后,修改main.go,使它包括新的对router.GET的调用,此时它的路径是/albums/:id,像如下示例:

    func main() {
        router := gin.Default()
        router.GET("/albums", getAlbums)
        router.GET("/albums/:id", getAlbumByID)
        router.POST("/albums", postAlbums)
    
        router.Run("localhost:8080")
    }

    In this code, you:  在此代码中,你:

    • Associate the /albums/:id path with the getAlbumByID function. In Gin, the colon preceding an item in the path signifies that the item is a path parameter.  关联/albums/:id路径和getAlbumByID函数。在Gin中,路径中的某项前面的冒号表明该项是个路径参数。

Run the code  运行代码

  1. If the server is still running from the last section, stop it.  如果服务仍在运行(上节开始启动),请停止运行。

  2. From the command line in the directory containing main.go, run the code to start the server.  在命令行窗口,定位到包含maig.go的目录,运行启动服务的命令。

    $ go run .
  3. From a different command line window, use curl to make a request to your running web service.  

    $ curl http://localhost:8080/albums/2

    The command should display JSON for the album whose ID you used. If the album wasn’t found, you’ll get JSON with an error message.  此命令应该以JSON显示相应的专辑。如果专辑未找到,你将得到一个JSON表示的错误消息。

    {
            "id": "2",
            "title": "Jeru",
            "artist": "Gerry Mulligan",
            "price": 17.99
    }

Conclusion  结论

Congratulations! You’ve just used Go and Gin to write a simple RESTful web service.

恭喜!你已经使用Go和Gin编写了一个简单的RESTful web service。

Suggested next topics:  推荐下一个主题:

  • If you’re new to Go, you’ll find useful best practices described in Effective Go and How to write Go code.  如果你是Go新手,你将在Go本质和如何编写Go代码找到有用的最佳实践。
  • The Go Tour is a great step-by-step introduction to Go fundamentals.  Go指南是个很好的循序渐进的Go基础介绍。
  • For more about Gin, see the Gin Web Framework package documentation or the Gin Web Framework docs.  关于Gin的更多信息,请看Gin Web 框架包文档或Gin Web 框架文档。

Completed code  完整代码

This section contains the code for the application you build with this tutorial.  本节包含本教程构建的应用的代码。

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

// album represents data about a record album.
type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}

// albums slice to seed record album data.
var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
    router.GET("/albums/:id", getAlbumByID)
    router.POST("/albums", postAlbums)

    router.Run("localhost:8080")
}

// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, albums)
}

// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
    var newAlbum album

    // Call BindJSON to bind the received JSON to
    // newAlbum.
    if err := c.BindJSON(&newAlbum); err != nil {
        return
    }

    // Add the new album to the slice.
    albums = append(albums, newAlbum)
    c.IndentedJSON(http.StatusCreated, newAlbum)
}

// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
    id := c.Param("id")

    // Loop through the list of albums, looking for
    // an album whose ID value matches the parameter.
    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
posted @ 2022-01-11 14:17  菜鸟吊思  阅读(787)  评论(0)    收藏  举报