协慌网

登录 贡献 社区

RESTful 编程究竟是什么?

RESTful 编程究竟是什么?

答案

REST是 Web 的基础架构原则。关于 Web 的惊人之处在于,客户端(浏览器)和服务器可以以复杂的方式进行交互,而客户端无需事先了解服务器及其承载的资源。关键的限制是服务器和客户端必须同意所使用的媒体 ,在网络的情况下是HTML

遵循REST原则的 API 不要求客户端了解有关 API 结构的任何信息。相反,服务器需要提供客户端与服务交互所需的任何信息。 HTML 表单就是一个例子:服务器指定资源的位置和必填字段。 浏览器事先不知道提交信息的位置,并且事先不知道要提交哪些信息。两种形式的信息完全由服务器提供。 (这个原则被称为HATEOAS :超媒体作为应用程序状态的引擎 。)

那么,这如何应用于HTTP ,以及如何在实践中实现? HTTP 围绕动词和资源。主流使用的两个动词是GETPOST ,我想每个人都会认识到。但是,HTTP 标准定义了其他几个,如PUTDELETE 。然后根据服务器提供的指令将这些动词应用于资源。

例如,假设我们有一个由 Web 服务管理的用户数据库。我们的服务使用基于 JSON 的自定义超媒体,我们为其分配了 mimetype application/json+userdb (可能还有application/xml+userdbapplication/whatever+userdb - 可能支持许多媒体类型)。客户端和服务器都已编程为理解这种格式,但他们对彼此一无所知。正如Roy Fielding指出:

REST API 应该花费几乎所有的描述性工作来定义用于表示资源和驱动应用程序状态的媒体类型,或者为现有标准媒体类型定义扩展关系名称和 / 或启用超文本的标记。

一种基础资源请求/可能返回是这样的:

请求

GET /
Accept: application/json+userdb

响应

200 OK
Content-Type: application/json+userdb

{
    "version": "1.0",
    "links": [
        {
            "href": "/user",
            "rel": "list",
            "method": "GET"
        },
        {
            "href": "/user",
            "rel": "create",
            "method": "POST"
        }
    ]
}

我们从媒体的描述中了解到,我们可以从名为 “链接” 的部分找到有关相关资源的信息。这称为超媒体控件 。在这种情况下,我们可以从这样的部分告诉我们可以通过为/user发出另一个请求来找到用户列表:

请求

GET /user
Accept: application/json+userdb

响应

200 OK
Content-Type: application/json+userdb

{
    "users": [
        {
            "id": 1,
            "name": "Emil",
            "country: "Sweden",
            "links": [
                {
                    "href": "/user/1",
                    "rel": "self",
                    "method": "GET"
                },
                {
                    "href": "/user/1",
                    "rel": "edit",
                    "method": "PUT"
                },
                {
                    "href": "/user/1",
                    "rel": "delete",
                    "method": "DELETE"
                }
            ]
        },
        {
            "id": 2,
            "name": "Adam",
            "country: "Scotland",
            "links": [
                {
                    "href": "/user/2",
                    "rel": "self",
                    "method": "GET"
                },
                {
                    "href": "/user/2",
                    "rel": "edit",
                    "method": "PUT"
                },
                {
                    "href": "/user/2",
                    "rel": "delete",
                    "method": "DELETE"
                }
            ]
        }
    ],
    "links": [
        {
            "href": "/user",
            "rel": "create",
            "method": "POST"
        }
    ]
}

我们可以从这个回复中说出很多。例如,我们现在知道我们可以通过POST/user创建一个新/user

请求

POST /user
Accept: application/json+userdb
Content-Type: application/json+userdb

{
    "name": "Karl",
    "country": "Austria"
}

响应

201 Created
Content-Type: application/json+userdb

{
    "user": {
        "id": 3,
        "name": "Karl",
        "country": "Austria",
        "links": [
            {
                "href": "/user/3",
                "rel": "self",
                "method": "GET"
            },
            {
                "href": "/user/3",
                "rel": "edit",
                "method": "PUT"
            },
            {
                "href": "/user/3",
                "rel": "delete",
                "method": "DELETE"
            }
        ]
    },
    "links": {
       "href": "/user",
       "rel": "list",
       "method": "GET"
    }
}

我们也知道我们可以更改现有数据:

请求

PUT /user/1
Accept: application/json+userdb
Content-Type: application/json+userdb

{
    "name": "Emil",
    "country": "Bhutan"
}

响应

200 OK
Content-Type: application/json+userdb

{
    "user": {
        "id": 1,
        "name": "Emil",
        "country": "Bhutan",
        "links": [
            {
                "href": "/user/1",
                "rel": "self",
                "method": "GET"
            },
            {
                "href": "/user/1",
                "rel": "edit",
                "method": "PUT"
            },
            {
                "href": "/user/1",
                "rel": "delete",
                "method": "DELETE"
            }
        ]
    },
    "links": {
       "href": "/user",
       "rel": "list",
       "method": "GET"
    }
}

请注意,我们使用不同的 HTTP 谓词( GETPUTPOSTDELETE等)来操纵这些资源,而我们在客户端部分所假设的唯一知识就是我们的媒体定义。

进一步阅读:

(这个答案一直是批评错误的主题。在大多数情况下,这是一个公平的批评。我最初描述的更符合几年前我通常实施 REST 的时候首先写下这个,而不是它的真实含义。我修改了答案以更好地代表真正的意义。)

称为REST(Representational State Transfer)架构风格提倡 Web 应用程序应该像最初设想的那样使用 HTTP。查找应该使用GET请求。 PUTPOSTDELETE请求应分别用于变异,创建和删除

REST 支持者倾向于支持 URL,例如

http://myserver.com/catalog/item/1729

但 REST 架构不需要这些 “漂亮的 URL”。带参数的 GET 请求

http://myserver.com/catalog?item=1729

就像 RESTful 一样。

请记住,绝不应该使用 GET 请求来更新信息。例如,GET 请求将项目添加到购物车

http://myserver.com/addToCart?cart=314159&item=1729

不合适。 GET 请求应该是幂等的 。也就是说,发出两次请求应该与发出一次请求没有什么不同。这就是使请求可缓存的原因。 “添加到购物车” 请求不是幂等的 - 发布它两次将该项目的两个副本添加到购物车。在这种情况下,POST 请求显然是合适的。因此,即使是RESTful Web 应用程序也需要它的 POST 请求份额。

这取自 David M. Geary 的优秀书籍Core JavaServer 面子书。

RESTful 编程是关于:

  • 由持久性标识符标识的资源:URI 是当今无处不在的标识符选择
  • 使用一组通用动词操纵资源:HTTP 方法是常见的案例 - 古老的CreateRetrieveUpdateDelete变为POSTGETPUTDELETE 。但 REST 不仅限于 HTTP,它现在只是最常用的传输方式。
  • 为资源检索的实际表示取决于请求而不是标识符:使用 Accept 标头来控制是否需要 XML,HTTP,甚至是表示资源的 Java 对象
  • 维护对象中的状态并表示表示中的状态
  • 表示资源表示中资源之间的关系:对象之间的链接直接嵌入表示中
  • 资源表示描述了如何使用表示以及在什么情况下应该以一致的方式丢弃 / 重新获取表示:HTTP Cache-Control 头的使用

就 REST 的后果和整体有效性而言,最后一个可能是最重要的。总体而言,大多数 RESTful 讨论似乎都集中在 HTTP 及其在浏览器中的使用,而不是。我理解 R.Fielding 在描述导致 HTTP 的架构和决策时创造了这个术语。他的论文更多地是关于资源的体系结构和缓存能力,而不是 HTTP。

如果您真的对 RESTful 架构是什么以及它的工作原理感兴趣,请阅读他的论文几次并阅读整篇文章而不仅仅是第 5 章!接下来看看DNS 的工作原理 。了解 DNS 的层次结构以及推介的工作原理。然后阅读并考虑 DNS 缓存的工作原理。最后,阅读 HTTP 规范(特别是RFC2616RFC3040 )并考虑缓存如何以及为什么以它的方式工作。最终,它只会点击。对我来说,最后的启示是当我看到 DNS 和 HTTP 之间的相似之处时。在此之后,了解 SOA 和消息传递接口可扩展的原因开始单击。

我认为理解 RESTful 和Shared Nothing架构的体系结构重要性和性能影响的最重要技巧是避免对技术和实现细节感到困惑。专注于谁拥有资源,谁负责创建 / 维护它们等等。然后考虑表示,协议和技术。

REST是 Web 的基础架构原理。 Web 的惊人之处在于,客户端(浏览器)和服务器可以以复杂的方式进行交互,而无需客户端事先了解服务器及其托管的资源。关键约束是服务器和客户端都必须在所使用的媒体上达成共识,在网络上为HTML

遵循REST原理的 API 不需要客户端了解有关 API 结构的任何信息。相反,服务器需要提供客户端与服务交互所需的任何信息。 HTML 表单就是这样的一个示例:服务器指定资源的位置和必填字段。 浏览器不预先知道在哪里提交信息,也不知道要提交什么信息。两种形式的信息完全由服务器提供。 (此原理称为HATEOAS :作为应用程序状态引擎的超媒体 。)

那么,这如何适用于HTTP以及如何在实践中实现? HTTP 围绕动词和资源。主流用法中的两个动词是GETPOST ,我想每个人都会意识到。但是,HTTP 标准定义了其他几种,例如PUTDELETE 。然后根据服务器提供的指示将这些动词应用于资源。

例如,假设我们有一个由 Web 服务管理的用户数据库。我们的服务使用基于 JSON 的自定义超媒体,为此我们为其分配了 mimetype application/json+userdb (可能还有application/xml+userdbapplication/whatever+userdb可能支持许多媒体类型)。客户端和服务器都经过编程可以理解这种格式,但是彼此之间一无所知。正如Roy Fielding指出的那样:

REST API 应该花费其几乎所有的描述性工作来定义用于表示资源和驱动应用程序状态的媒体类型,或者为现有标准媒体类型定义扩展关系名称和 / 或启用超文本的标记。

对基本资源的请求/可能返回以下内容:

请求

GET /
Accept: application/json+userdb

响应

200 OK
Content-Type: application/json+userdb

{
    "version": "1.0",
    "links": [
        {
            "href": "/user",
            "rel": "list",
            "method": "GET"
        },
        {
            "href": "/user",
            "rel": "create",
            "method": "POST"
        }
    ]
}

从对媒体的描述中我们知道,我们可以从称为 “链接” 的部分中找到有关相关资源的信息。这称为超媒体控件 。在这种情况下,我们可以从这样的部分告诉我们可以通过再次请求/user来找到用户列表:

请求

GET /user
Accept: application/json+userdb

响应

200 OK
Content-Type: application/json+userdb

{
    "users": [
        {
            "id": 1,
            "name": "Emil",
            "country: "Sweden",
            "links": [
                {
                    "href": "/user/1",
                    "rel": "self",
                    "method": "GET"
                },
                {
                    "href": "/user/1",
                    "rel": "edit",
                    "method": "PUT"
                },
                {
                    "href": "/user/1",
                    "rel": "delete",
                    "method": "DELETE"
                }
            ]
        },
        {
            "id": 2,
            "name": "Adam",
            "country: "Scotland",
            "links": [
                {
                    "href": "/user/2",
                    "rel": "self",
                    "method": "GET"
                },
                {
                    "href": "/user/2",
                    "rel": "edit",
                    "method": "PUT"
                },
                {
                    "href": "/user/2",
                    "rel": "delete",
                    "method": "DELETE"
                }
            ]
        }
    ],
    "links": [
        {
            "href": "/user",
            "rel": "create",
            "method": "POST"
        }
    ]
}

从这个回应中我们可以看出很多。例如,我们现在知道可以通过POST/user创建一个新/user

请求

POST /user
Accept: application/json+userdb
Content-Type: application/json+userdb

{
    "name": "Karl",
    "country": "Austria"
}

响应

201 Created
Content-Type: application/json+userdb

{
    "user": {
        "id": 3,
        "name": "Karl",
        "country": "Austria",
        "links": [
            {
                "href": "/user/3",
                "rel": "self",
                "method": "GET"
            },
            {
                "href": "/user/3",
                "rel": "edit",
                "method": "PUT"
            },
            {
                "href": "/user/3",
                "rel": "delete",
                "method": "DELETE"
            }
        ]
    },
    "links": {
       "href": "/user",
       "rel": "list",
       "method": "GET"
    }
}

我们也知道我们可以更改现有数据:

请求

PUT /user/1
Accept: application/json+userdb
Content-Type: application/json+userdb

{
    "name": "Emil",
    "country": "Bhutan"
}

响应

200 OK
Content-Type: application/json+userdb

{
    "user": {
        "id": 1,
        "name": "Emil",
        "country": "Bhutan",
        "links": [
            {
                "href": "/user/1",
                "rel": "self",
                "method": "GET"
            },
            {
                "href": "/user/1",
                "rel": "edit",
                "method": "PUT"
            },
            {
                "href": "/user/1",
                "rel": "delete",
                "method": "DELETE"
            }
        ]
    },
    "links": {
       "href": "/user",
       "rel": "list",
       "method": "GET"
    }
}

请注意,我们正在使用不同的 HTTP 动词( GETPUTPOSTDELETE等)来操纵这些资源,并且我们假定客户端唯一的知识就是媒体定义。

进一步阅读:

(此答案因缺少要点而受到了很多批评。大多数情况下,这是一个合理的批评。我最初描述的内容与几年前我通常使用 REST 的方式更加一致首先是写这个,而不是真正的意思。我已经修改了答案,以更好地代表真实的意思。)

一种称为REST(代表性状态传输)架构风格主张 Web 应用程序应使用最初设想的 HTTP。查找应使用GET请求。 PUTPOSTDELETE请求应分别用于突变,创建和删除

REST 支持者倾向于使用 URL,例如

http://myserver.com/catalog/item/1729

但是 REST 体系结构不需要这些 “漂亮的 URL”。带有参数的 GET 请求

http://myserver.com/catalog?item=1729

都是 RESTful。

请记住,决不要将 GET 请求用于更新信息。例如,用于将商品添加到购物车的 GET 请求

http://myserver.com/addToCart?cart=314159&item=1729

不合适。 GET 请求应该是幂等的 。也就是说,两次发出请求应该与一次发出请求没有什么不同。这就是使请求可缓存的原因。 “添加到购物车” 请求不是幂等的 - 发出两次请求会将该商品的两个副本添加到购物车。在这种情况下,POST 请求显然是合适的。因此,即使是RESTful Web 应用程序也需要其 POST 请求共享。

这摘自 David M. Geary 的出色著作《 Core JavaServer Faces 》。

RESTful 编程是关于:

  • 资源由持久标识符标识:URI 是当今标识符的普遍选择
  • 使用一组常见的动词来操纵资源:HTTP 方法是常见的情况 - 古老的CreateRetrieveUpdateDelete变为POSTGETPUTDELETE 。但是 REST 不限于 HTTP,它只是目前最常用的传输方式。
  • 为资源检索的实际表示形式取决于请求而不是标识符:使用 Accept 标头控制您是否要使用 XML,HTTP 甚至是表示该资源的 Java 对象
  • 保持对象中的状态并在表示中表示状态
  • 在资源表示中表示资源之间的关系:对象之间的链接直接嵌入在表示中
  • 资源表示形式描述了如何使用表示形式以及在什么情况下应以一致的方式丢弃 / 重新获取该表示形式:HTTP Cache-Control 标头的使用

就 REST 的后果和整体有效性而言,最后一个可能是最重要的。总体而言,大多数 RESTful 讨论似乎都集中在 HTTP 及其在浏览器中的用法,而不是 HTTP。我了解 R. Fielding 在描述导致 HTTP 的体系结构和决策时创造了该术语。他的论文更多地是关于资源的体系结构和缓存能力,而不是 HTTP。

如果您真的对 RESTful 架构是什么以及它为什么起作用感兴趣,请阅读他的论文几次,并完整地阅读整个内容,而不仅仅是第 5 章!接下来研究DNS 为什么起作用 。阅读有关 DNS 的层次结构以及引用如何工作的信息。然后阅读并考虑 DNS 缓存如何工作。最后,阅读 HTTP 规范(特别是RFC2616RFC3040 ),并考虑缓存如何以及为什么以这种方式工作。最终,它只会单击。对我来说,最后的启示是当我看到 DNS 和 HTTP 之间的相似之处时。此后,开始理解为什么可以扩展 SOA 和消息传递接口。

我认为理解 RESTful 和无共享架构的体系结构重要性和性能含义的最重要技巧是避免陷入技术和实现细节上。专注于谁拥有资源,谁负责创建 / 维护资源等,然后考虑表示形式,协议和技术。