实现一个 RESTFUL API 服务器

4,430 阅读5分钟
原文链接: chars.tech
31 Jul 2018

实现一个 RESTful API 服务器

RESTful 是目前最为流行的一种互联网软件结构。因为它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

什么是 REST

REST(REpresentational State Transfer),首次出现在 2000 年 Roy Thomas Fielding 的博士论文中,它指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful 的。

  • 资源(Resources),REST 是“表现层状态转化”,其实它省略了主语。“表现层”其实指的是“资源”的“表现层”。那么什么是资源呢?我们平时网上访问到图片、文字、文档、多媒体等就是资源,一般通过 URI 来定位。也就是说,一个 URI 就表示一个资源。
  • 表现层(Representation),资源是作为一个具体的实体信息,它可以有多种的展现方式。而把实体展现出来就是表现层。例如一个 txt 文本信息,它可以输出成 html、json 等。
  • 状态转化(State Transfer),访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,就涉及到数据和状态的变化。而 HTTP 协议是无状态的,那么这些状态肯定保存在服务器端,所以如果客户端想要通知服务器端改变数据和状态的变化,就要通过某种方式来通知它。客户端能通知服务器端的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。

综上所述,我们总结一下什么是 RESTful 架构:

1、每一个 URI 代表一种资源

2、客户端和服务端之间,传递这种资源的某种表现层

3、客户端通过四个 HTTP 动词,对服务端资源进行操作,实现“表现层状态转化”

将它们概述为图片形式,则 REST 架构图为:

REST架构图

REST 的扩展性:

REST 的扩展性

什么是 RPC

RPC(Remote Procedure Call Protocol)远程过程调用协议,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。它假定某些传输协议的存在,如 TCP 或 UDP,以便为通信程序之间携带信息数据。通过它可以使函数调用模式网络化。在 OSI 网络通信模型中,RPC 跨越了传输层和应用层。RPC 使得开发包括网络分布式多程序在内的应用程序更加容易。

工作原理

RPC工作流程图

运行时,一次客户端对服务器的 RPC 调用,其内部操作大致有如下步骤:

1、调用客户端句柄;执行传送参数

2、调用本地系统内核发送网络消息

3、消息传送到服务端

4、服务器句柄得到消息并取得参数

5、执行远程过程

6、执行的过程将结果返回服务器句柄

7、服务器句柄返回结果,调用远程系统内核

8、消息传回本地主机

9、客户端句柄由内核接收消息

10、客户端接收句柄返回的数据

REST vs RPC

在做 API 服务器开发时,很多人都会遇到这个问题 —— 选择 REST 还是 RPC。RPC 相比 REST 的优点主要有 3 点:

1、RPC+Protobuf 采用的是 TCP 做传输协议,REST 直接使用 HTTP 做应用层协议,这种区别导致 REST 在调用性能上会比 RPC+Protobuf 低

2、RPC 不像 REST 那样,每一个操作都要抽象成对资源的增删改查,在实际开发中,有很多操作很难抽象成资源,比如登录操作。所以在实际开发中并不能严格按照 REST 规范来写 API,RPC 就不存在这个问题

3、RPC 屏蔽网络细节、易用,和本地调用类似

但是 REST 相较 RPC 也有很多优势:

1、轻量级,简单易用,维护性和扩展性都比较好

2、REST 相对更规范,更标准,更通用,无论哪种语言都支持 HTTP 协议,可以对接外部很多系统,只要满足 HTTP 调用即可,更适合对外,RPC 会有语言限制,不同语言的 RPC 调用起来很麻烦

3、JSON 格式可读性更强,开发调试都很方便

4、在开发过程中,如果严格按照 REST 规范来写 API,API 看起来更清晰,更容易被大家理解

其实业界普遍采用的做法是,内部系统之间调用用 RPC,对外用 REST,因为内部系统之间可能调用很频繁,需要 RPC 的高性能支撑。对外用 REST 更易理解,更通用些。

一个基本的 Web Server

一个 RESTful 服务本质上首先是一个 Web service。下面是一个最简单的 Web server,对于任何请求都简单的直接返回请求链接:

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.Fatal(http.ListenAndServe(":8080", nil))
}

编译运行之后,使用 curl 测试,结果如下:

$ curl -v -XGET -H "Content-Type: application/json" http://127.0.0.1:8080/user

Hello, "/user"

路由功能

很显然,我们的线上项目不可能使用这么简单的 API 服务器。当用户增加,请求也会不断上涨,该如何处理好这些请求?作者使用了一个开源路由框架 mux。这是一个小巧高效,且使用较广的第三方框架。接下来的篇幅里,作者会使用 mux 搭建一个 API 服务器框架。

安装 mux

$go get github.com/gorilla/mux

Router

//Router.go
import (
	"net/http"

	"github.com/gorilla/mux"
)

type Route struct {
	Name        string
	Method      string
	Pattern     string
	HandlerFunc http.HandlerFunc
}

type Routes []Route

func NewRouter() *mux.Router {
	router := mux.NewRouter().StrictSlash(true)
	for _, route := range routes {
		var handler http.Handler

		handler = route.HandlerFunc
		handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			handler.ServeHTTP(w, r)
		})

		router.Methods(route.Method).Path(route.Pattern).Name(route.Name).Handler(handler)
	}
	return router
}

var routes = Routes{
	Route{
		"DeleteItem",
		"DELETE",
		"/v1/delete",
		v1_deleteItem,
	},
	...
}

Handler

//Handler.go
func v1_deleteItem(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(jsonErr{Code: http.StatusOK, Text: "操作成功"}); err != nil {
		log.print("%s\n%s", err.Error(), debug.Stack())
	}
}

整体 mux 功能实现:


                    
//main.go
func main() {
	router := NewRouter()

	log.print("service running(PID:%d)...", os.Getpid())
	log.Fatal(http.ListenAndServe(":8080", router))
}

 

API 基本框架已经实现,接下来就是将相应功能实现模块与相应接口对接即可。

写在最后

对于想要学习作为一个客户端开发者如何独立完成一个具有 API 服务器功能的线上 APP,可以参考专栏《如何独立开发一个完整应用》,专栏中使用线上 APP 靓手艺 作为案例,详细分享了笔者如何实现 APP 全部功能。