Golang实现RESTful API

原文地址:Creating a RESTful API With Golang

Web应用中,可能需要多个REST API来动态渲染页面内容,完成更新或删除数据库中数据的功能。
本文将构建一个成熟的REST API,实现『GET』,『POST』,『DELETE』和『PUT』方法,完成CRUD操作。为了保持简单,这里不与数据库进行交互。

运行环境

  • Go 1.11+

目标

了解如何在Go中创建自己的RESTful API,处理相关方面的问题。知道如何在项目中创建可以处理POST,GET,PUT和DELETE HTTP请求的接口。

从基础的API开始

首先,我们必须创建一个非常简单的服务器来处理HTTP请求。为此,我们创建一个名为main.go的新文件。 在这个main.go文件中,我们将要定义3个不同的函数。一个homePage函数(将处理对我们根URL的所有请求),handleRequests函数(将与已定义函数匹配的URL路径匹配)和main函数(将启动我们的API)。

package main

import (
    "fmt"
    "log"
    "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "Welcome to the HomePage!")
    fmt.Println("Endpoint Hit: homePage")
}

func handleRequests() {
    http.HandleFunc("/", homePage)
    log.Fatal(http.ListenAndServe(":10000", nil))
}

func main() {
    handleRequests()
}

如果我们现在运行,则应该可以看到程序在端口10000上启动(该端口未被其他进程使用)。 打开http://localhost:10000/,应该看到“Welcome to the HomePage!” ,现在已经成功创建了一个基础的API。

定义结构体

我们将创建一个REST API,使我们可以『CREATE』,『READ』,『UPDATE』和『DELETE』网站上的文章。

在开始之前,我们必须定义我们的Article结构。Go的结构概体念非常适合这种情况。创建一个具有标题,描述(desc)和内容的Article结构体,如下所示:

type Article struct {
    Title string `json:"Title"`
    Desc string `json:"desc"`
    Content string `json:"content"`
}

// let's declare a global Articles array
// that we can then populate in our main function
// to simulate a database
var Articles []Article

我们的Struct包含3个属性,它们代表了我们网站上的所有文章。为了使其正常工作,我们还必须导入“encoding/json”包。

现在,更新我们的main函数,在Articles变量中填充一些虚拟数据,以便稍后我们可以检索和修改。

func main() {
    Articles = []Article{
        Article{Title: "Hello", Desc: "Article Description", Content: "Article Content"},
        Article{Title: "Hello 2", Desc: "Article Description", Content: "Article Content"},
    }
    handleRequests()
}

现在让我们继续创建/articles接口,该接口将返回我们刚刚在此处定义的所有文章。

召回所有文章

在这一部分中,我们将创建一个新的REST接口,当该接口遇到HTTP GET请求时,它将返回我们站点的所有文章。
首先,我们将创建一个名为returnAllArticles的新函数,该函数将返回以JSON格式编码的新填充的Articles变量:

func returnAllArticles(w http.ResponseWriter, r *http.Request){
    fmt.Println("Endpoint Hit: returnAllArticles")
    json.NewEncoder(w).Encode(Articles)
}

调用json.NewEncoder(w).Encode(article)的作用是将articles数组编码为JSON字符串,然后将其作为响应的一部分返回。
在此之前,我们还需要向handleRequests函数添加一条新路由,该路由会将对http:// localhost:10000/articles的所有调用映射到我们新定义的函数。

func handleRequests() {
    http.HandleFunc("/", homePage)
    // add our articles route and map it to our 
    // returnAllArticles function like so
    http.HandleFunc("/articles", returnAllArticles)
    log.Fatal(http.ListenAndServe(":10000", nil))
}

go run main.go来运行代码,然后在浏览器中打开http://localhost:10000/articles,应该会看到:

[
  {
    Title: "Hello",
    desc: "Article Description",
    content: "Article Content"
  },
  {
    Title: "Hello 2",
    desc: "Article Description",
    content: "Article Content"
  }
];

在下一部分中,我们将更新REST API,使用"gorilla/mux"而不是传统的"net/http"路由器。能够更轻松地执行任务,例如解析可能存在于传入HTTP请求中的任何路径或查询参数。

构建路由

现在,标准库已足够提供启动和运行自己的简单REST API所需的一切,但掌握了基本概念后,是时候使用第三方路由包了。

创建自己的路由

更新现有的main.go文件,替换"net/http"为"gorilla/mux",修改"handleRequests"函数来创建新路由。

package main

import (
    "fmt"
    "log"
    "net/http"
    "encoding/json"
    "github.com/gorilla/mux"
)// Existing code from above
func handleRequests() {
    // creates a new instance of a mux router
    myRouter := mux.NewRouter().StrictSlash(true)
    // replace http.HandleFunc with myRouter.HandleFunc
    myRouter.HandleFunc("/", homePage)
    myRouter.HandleFunc("/all", returnAllArticles)
    // finally, instead of passing in nil, we want
    // to pass in our newly created router as the second
    // argument
    log.Fatal(http.ListenAndServe(":10000", myRouter))
}

func main() {
    fmt.Println("Rest API v2.0 - Mux Routers")
    Articles = []Article{
        Article{Title: "Hello", Desc: "Article Description", Content: "Article Content"},
        Article{Title: "Hello 2", Desc: "Article Description", Content: "Article Content"},
    }
    handleRequests()
}

路由中变量

如何查看某篇具体的文章呢?

我们可以将变量添加到路径中,然后根据这些变量选择要返回的文章。创建一个新路由和映射函数:

myRouter.HandleFunc("/article/{id}", returnSingleArticle)

请注意,我们已经在路径中添加了{id}。这将代表我们的id变量,当我们只希望返回具有确切关键字的文章时,可以使用它。目前,我们的Article结构没有ID属性,现在添加一下:

type Article struct {
    Id      string `json:"Id"`
    Title   string `json:"Title"`
    Desc    string `json:"desc"`
    Content string `json:"content"`
}

然后,我们可以更新main函数,在Articles数组中填充Id值:

func main() {
    Articles = []Article{
        Article{Id: "1", Title: "Hello", Desc: "Article Description", Content: "Article Content"},
        Article{Id: "2", Title: "Hello 2", Desc: "Article Description", Content: "Article Content"},
    }
    handleRequests()
}

在returnSingleArticle函数中,我们可以从URL中获取此{id}值,并且可以返回与该条件匹配的文章。由于我们尚未将数据存储在任何地方,因此我们将只返回传递给浏览器的ID。

如果现在运行此程序,然后打开http://localhost:10000/article/1,会看到在浏览器中已打印出Key:1。

让我们使用此键值返回与该键匹配的特定文章。

func returnSingleArticle(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    key := vars["id"]

    // Loop over all of our Articles
    // if the article.Id equals the key we pass in
    // return the article encoded as JSON
    for _, article := range Articles {
        if article.Id == key {
            json.NewEncoder(w).Encode(article)
        }
    }
}

运行程序,打开http://localhost:10000/article/1,会看见JSON格式的响应:

{
Id: "1",
Title: "Hello",
desc: "Article Description",
content: "Article Content"
}

创建、更新文章

在这一部分中,我们将构建CRUD REST API的Create,Update和DELETE部分。

创建文章

我们将需要创建一个新函数来完成创建新文章的工作。

func createNewArticle(w http.ResponseWriter, r *http.Request) {
    // get the body of our POST request
    // return the string response containing the request body    
    reqBody, _ := ioutil.ReadAll(r.Body)
    fmt.Fprintf(w, "%+v", string(reqBody))
}

定义此功能后,可以将路由添加到handleRequests函数中定义的路由列表中。不过,这一次,我们将在路由的末尾添加.Methods(“POST”),以指定仅在传入请求是HTTP POST请求时才要调用此函数:

func handleRequests() {
    myRouter := mux.NewRouter().StrictSlash(true)
    myRouter.HandleFunc("/", homePage)
    myRouter.HandleFunc("/articles", returnAllArticles)
    // NOTE: Ordering is important here! This has to be defined before
    // the other `/article` endpoint. 
    myRouter.HandleFunc("/article", createNewArticle).Methods("POST")
    myRouter.HandleFunc("/article/{id}", returnSingleArticle)
    log.Fatal(http.ListenAndServe(":10000", myRouter))
}

再次运行它,然后尝试提交包含以下POST正文的HTTP POST请求:

{
    "Id": "3", 
    "Title": "Newly Created Post", 
    "desc": "The description for my new post", 
    "content": "my articles content" 
}

确认新接口正常工作后,更新createNewArticle函数,以便将请求正文中的JSON解组到新的Article结构中,然后可以将其附加到Articles数组中:

func createNewArticle(w http.ResponseWriter, r *http.Request) {
    // get the body of our POST request
    // unmarshal this into a new Article struct
    // append this to our Articles array.    
    reqBody, _ := ioutil.ReadAll(r.Body)
    var article Article 
    json.Unmarshal(reqBody, &article)
    // update our global Articles array to include
    // our new Article
    Articles = append(Articles, article)

    json.NewEncoder(w).Encode(article)
}

如果立即运行此命令,并将相同的POST请求发送到相应路由,将看到它返回了与以前相同的JSON格式,但还会将新的Article追加到Articles数组中。
通过http://localhost:10000/articles验证:

[
    {
        "Id": "1",
        "Title": "Hello",
        "desc": "Article Description",
        "content": "Article Content"
    },
    {
        "Id": "2",
        "Title": "Hello 2",
        "desc": "Article Description",
        "content": "Article Content"
    },
    {
        "Id": "3",
        "Title": "Newly Created Post",
        "desc": "The description for my new post",
        "content": "my articles content"
    }
]

删除文章

有时我们可能需要删除REST API公开的数据。为此,我们需要在API中公开一个DELETE接口,该接口将接收一个标识符并删除与该标识符关联的所有内容。

在此部分中,我们将创建另一个接口,该接口接收HTTP DELETE请求,并在它们与给定的ID路径参数匹配时删除文章。

在main.go文件中添加一个新函数deleteArticle:

func deleteArticle(w http.ResponseWriter, r *http.Request) {
    // once again, we will need to parse the path parameters
    vars := mux.Vars(r)
    // we will need to extract the `id` of the article we
    // wish to delete
    id := vars["id"]

    // we then need to loop through all our articles
    for index, article := range Articles {
        // if our id path parameter matches one of our
        // articles
        if article.Id == id {
            // updates our Articles array to remove the 
            // article
            Articles = append(Articles[:index], Articles[index+1:]...)
        }
    }

}

同样,将路由添加到handleRequests函数中定义的路由列表中。

func handleRequests() {
    myRouter := mux.NewRouter().StrictSlash(true)
    myRouter.HandleFunc("/", homePage)
    myRouter.HandleFunc("/articles", returnAllArticles)
    myRouter.HandleFunc("/article", createNewArticle).Methods("POST")
    // add our new DELETE endpoint here
    myRouter.HandleFunc("/article/{id}", deleteArticle).Methods("DELETE")
    myRouter.HandleFunc("/article/{id}", returnSingleArticle)
    log.Fatal(http.ListenAndServe(":10000", myRouter))
}

尝试用新的HTTP DELETE请求发送到http:// localhost:10000/article/2。这将删除Articles数组中的第二篇文章,当您随后使用HTTP GET请求访问http://localhost:10000/articles时,您现在应该看到它仅包含一个Article。

更新文章

最后需要实现的接口是Update。该接口基于HTTP PUT请求,并且需要采用ID路径参数,就像我们对HTTP DELETE接口以及JSON请求文章所做的一样。
传入HTTP PUT请求正文中的JSON将包含我们要更新的文章的较新版本。
在handleRequests函数中创建一个updateArticle函数和相应的路由。实现updateArticle函数方法与createNewArticle函数相同。

你可能感兴趣的:(golang,golang)