原文地址:Creating a RESTful API With Golang
Web应用中,可能需要多个REST API来动态渲染页面内容,完成更新或删除数据库中数据的功能。
本文将构建一个成熟的REST API,实现『GET』,『POST』,『DELETE』和『PUT』方法,完成CRUD操作。为了保持简单,这里不与数据库进行交互。
了解如何在Go中创建自己的RESTful API,处理相关方面的问题。知道如何在项目中创建可以处理POST,GET,PUT和DELETE HTTP请求的接口。
首先,我们必须创建一个非常简单的服务器来处理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函数相同。