没人理解REST和HTTP

2012-02-28 10:27

本文翻译自 Nobody Understands REST or HTTP, 有删减


Update: Part II of this post is here.

更新: 博文的第二部分在 这里.

: Please note that REST is over. Hypermedia API is the new nomenclature.

: 请关注 REST结束了, Hypermedia API是新的术语

The more that I’ve learned about web development, the more that I’ve
come to appreciate the thoroughness and thoughtfulness of the authors of
the HTTP RFC and Roy Fielding’s dissertation. It seems like the answers
to most problems come down to “There’s a section of the spec for that.”
Now, obviously, they’re not infallible, and I’m not saying that there’s
zero room for improvement. But it really disappoints me when people
don’t understand the way that a given issue is supposed to be solved,
and so they make up a partial solution that solves their given case but
doesn’t jive well with the way that everything else works. There are
valid criticisms of the specs, but they have to come from an informed
place about what the spec says in the first place.

对web开发了解的越多, 我就越来越钦佩HTTP RFC的作者和Roy Fielding论文的周全和细致。就似所有问题的答案可以归结到“那里有一节定义了”。当然, 他们并不是没有错误, 而且我也没说没有改进的空间了。 但是当看到人们没有理解问题应该那样去解决的原因, 或者是他们想出了一个不完整的方案来解决他们陪到的问题, 却没有遵守其他运转良好部分的处理方式。对协议规范确有有价值的批评,但是应该把协议规范的所说的放在第一位。

Let’s talk about a few cases where either REST or HTTP (which is clearly
RESTful in its design) solves a common web development problem.

让我们来看看REST或HTTP(HTTP本身就是RESTful的)解决一些web开发常见问题的例子

I need to design my API

我需要设计我的API

This one is a bit more general, but the others build off of it, so bear
with me.

这是个常见的问题, 所以请耐心容我道来

The core idea of REST is right there in the name: “Representational
State Transfer” It’s about transferring representations of the state…
of resources. Okay, so one part isn’t in the name. But still, let’s
break this down.

REST的核心思想就在它的名字里面: 表现性状态转移(Representational
State Transfer)。 即转移(资源的)状态的各种表现。好吧,还有一部分没有在名字里, 让我们一一来讲吧。

Resources

资源

From Fielding’s dissertation:

引用自a href=”http://www.ics.uci.edu/%7Efielding/pubs/dissertation/rest_arch_style.htm#sec_5_2_1_1″ rel=”">Fielding’s dissertation:

The key abstraction of information in REST is a resource. Any information
that can be named can be a resource: a document or image, a temporal service
(e.g. “today’s weather in Los Angeles”), a collection of other resources, a
non-virtual object (e.g. a person), and so on. In other words, any concept that
might be the target of an author’s hypertext reference must fit within the
definition of a resource. A resource is a conceptual mapping to a set of
entities, not the entity that corresponds to the mapping at any particular
point in time.

REST里对信息最关键的抽象就是资源。任何可以命名的信息都可以是资源: 一份文档或图片, 一个临时服务(例如洛杉矶今日天气), 一些资源的集合, 一个非虚拟的物体(例如一个人), 等等。换句话说, 一个作者的超链接引用指向的任何东西也必须遵循资源的定义。一个资源是一种概念上的到实体集合的的映射, 在任何时候都不是针对一个实体。

When we interact with a RESTful system, we’re interacting with a set of
resources. Clients request resources from the server in a variety of
ways. But the key thing here is that resources are nouns. So a RESTful
API consists of a set of URIs that map entities in your system to
endpoints, and then you use HTTP itself for the verbs. If your URLs have
action words in them, you’re doing it wrong. Let’s look at an example of
this, from the early days of Rails. When Rails first started messing
around with REST, the URLs looked like this:

当我们和RESTful系统交互的时候, 我们是在和资源交互。客户端以各种各样的方式向服务端请求资源。最关键的就是资源是名词(nouns). 所以一个RESTful的API,包含了一组把实体映射到端点的URI, 然后使用HTTP本身来做到动词。如果你的URL里面包含任何动作, 那么你就做错了。 让我来看看早期Rails的一个例子。当Rails开始掺和REST的时候, URL是这样的:

/posts/show/1

If you use Rails today, you’ll note that the corresponding URL is
this:

如果你现在使用Rails, 你会发现相应的URL是这样的:

/posts/1

Why? Well, it’s because the ‘show’ is unnecessary; you’re performing a
GET request, and that demonstrates that you want to show that resource.
It doesn’t need to be in the URL.

为什么呢? 因为‘显示(show)‘是不必要的; 发起一个GET请求的时候, 就意味着你想显示那个资源。 没必要在URL里添加它们。

A digression about actions

扯一扯动作

Sometimes, you need to perform some sort of action, though. Verbs are
useful. So how’s this fit in? Let’s consider the example of transferring
money from one Account to another. You might decided to build a URI like
this:

有时你需要执行一些动作。动词很有用。但是该怎么做? 例如把资金从一个帐号转到另一个帐号, 你也许会用这样的URI:

POST /accounts/1/transfer/500.00/to/2

to transfer $500 from Account 1 to Account 2. But this is wrong! What
you really need to do is consider the nouns. You’re not transferring
money, you’re creating a Transaction resource:

从帐号1转移$500到帐号2。 但是这不对!你需要的只是考虑名词。 你不是在转移资金, 而是在创建一种交易资源:

POST /transactions HTTP/1.1
Host: <snip, and all other headers>

from=1&to=2&amount=500.00

Got it? So then, it returns the URI for your new Transaction:

明白了吗?然后, 它返回了你的交易:

HTTP/1.1 201 OK
Date: Sun, 3 Jul 2011 23:59:59 GMT
Content-Type: application/json
Content-Length: 12345
Location: http://foo.com/transactions/1

{"transaction":{"id":1,"uri":"/transactions/1","type":"transfer"}}

Whoah, HATEOS! Also, it
may or may not be a good idea to return this JSON as the body; the
important thing is that we have the Location header which tells us where
our new resource is. If we give a client the ID, they might try to
construct their own URL, and the URI is a little redundant, since we
have one in the Location. Regardless, I’m leaving that JSON there,
because that’s the way I typed it first. I’d love to hear your thoughts
on this
if you feel strongly one way or
the other.

哇噢,HATEOS!
返回JSON可能是也可能不是一个好主意; 重要的是Location头告诉我们交易资源在哪里。 如果我们提供一个客户ID, 它们或许会尝试建立它们自己的URL,但是有了Location头之后URI有一些多余。 先不管了, 就把JSON留在那里吧,我最开始就是那样写的。 我很想听听你们对此的意见, 如果你强烈的觉得某一种或者别的方式会更好一些。

EDIT: I’ve since decided that yes, including the URI is a bad idea. The
Location header makes much more sense. More on this in Part ii, yet to
come.

更改: 最终我决定了, 包含URI是不好的。 Location头更有意义。在第二章里面会讨论更多, 你就快看到了.

Anyway, so now we can GET our Transaction:

无论如何, 我们现在能得到我们的交易了:

GET /transactions/1 HTTP/1.1
Accept: application/json

and the response:

还有资源:

HTTP/1.1 blah blah blah

{"id":1,"type":"transfer","status":"in-progress"}

So we know it’s working. We can continue to poll the URI and see when
our transaction is finished, or if it failed, or whatever. Easy! But
it’s about manipulating those nouns.

我们可以继续查询这个URI来得知交易是否完成, 或者是失败了, 或者别的。 放松!我们该说说如何操作这些名词了。

Representations

表现

You’ll notice a pair of headers in the above HTTP requests and
responses: Accept and Content-Type. These describe the different
‘representation’ of any given resource. From Fielding:

你会注意到在上面提到的HTTP请求和返回中有一些HTTP头。它们描述了资源的不同‘表现’。引用自 Fielding:

REST components perform actions on a resource by using a representation to
capture the current or intended state of that resource and transferring that
representation between components. A representation is a sequence of bytes,
plus representation metadata to describe those bytes. Other commonly used but
less precise names for a representation include: document, file, and HTTP
message entity, instance, or variant.

REST部件通过表现来获得资源的当前或有意为之的状态, 并且在不同部件之间转移表现. 表现是一系列的字节和描述字节的元数据. 表现的其他常用但不是很精确的名字还有: 文档, 文件, HTTP消息实体, 实例, 变量.

A representation consists of data, metadata describing the data, and, on
occasion, metadata to describe the metadata (usually for the purpose of
verifying message integrity).

一个表现有数据, 描述数据的元数据, 有时候还有描述元数据的元数据(通常用来验证消息的完整性)组成.

So /accounts/1 represents a resource. But it doesn’t include the form
that the resource takes. That’s what these two headers are for.

/accounts/1展示了一个资源, 但是没有包含这个资源的格式。下面这两个HTTP头就是干这个的

This is also why adding .html to the end of your URLs is kinda silly.
If I request /accounts/1.html with an Accept header of
application/json, then I’ll get JSON. The Content-Type header is the
server telling us what kind of representation it’s sending back as well.
The important thing, though, is that a given resource can have many
different representations. Ideally, there should be one unambiguous
source of information in a system, and you can get different
representations using Accept.

这也是为什么加.html到URL末尾有点愚蠢的原因。
如果我请求 /accounts/1.htmlAccept头是application/json, 然后我得到了JSON。 但是服务端返回给我们的Content-Type头也告诉了我们返回的东西是什么样的表现形式。一个资源可能有不同的表现形式。 理想情况下是使用Accept来做到一个信息来源多个表现形式。

State and Transfer

状态和转移

This is more about the way HTTP is designed, so I’ll just keep this
short: Requests are designed to be stateless, and the server holds all
of the state for its resources. This is important for caching and a few
other things, but it’s sort of out of the scope of this post.

这更关乎于HTTP是如何设计的, 简短来说: 请求被设计为无状态的(stateless), 服务端有它拥有的资源的所有状态。 这对于缓存等很重要, 不过对于本文来说有点离题

Okay. With all of that out of the way, let’s talk about some more
specific problems that REST/HTTP solve.

好的。 不说偏离主题的东西, 我们来说一说REST/HTTP解决的更特殊点的问题。

I want my API to be versioned

我想我的API有不同版本

The first thing that people do when they want a versioned API is to
shove a /v1/ in the URL. THIS IS BAD!!!!!1. Accept solves this
problem. What you’re really asking for is “I’d like the version two
representation of this resource.” So use accept!

人们首先想到的解决API不同版本的方法就是在URL里添加一个 /v1/这非常糟糕!!!. Accept就解决这个问题了呀。你所需要的就是请求“我需要这个资源的版本二的表现形式“。 那就用accept!

Here’s an example:

例子:

GET /accounts/1 HTTP/1.1
Accept: application/vnd.steveklabnik-v2+json

You’ll notice a few things: we have a + in our MIME type, and before it
is a bunch of junk that wasn’t there before. It breaks down into three
things: vnd, my name, and v2. You can guess what v2 means, but what
about vnd. It’s a Vendor MIME Type.
After all, we don’t really want just any old JSON, we want my specific
form of JSON. This lets us still have our one URL to represent our
resource, yet version everything appropriately.

你会注意到几点: 在MIME类型里面有个+号, 在+号之前是一些从来没有过的东西。 它可以分解为两部分:
vnd, 我的名字, 还有v2。 你可以猜到v2的意思, 但是vnd是什么呢。 它是一个 Vendor MIME Type.

I got a comment from Avdi Grimm about this, too:

来自 Avdi Grimm 关于此的评论:

Here’s an article you might find interesting: http://www.informit.com/articles/article.aspx?p=1566460

你也许会对这篇文章(http://www.informit.com/articles/article.aspx?p=1566460)感兴趣

The author points out that MIMETypes can have parameters, which means you can actually have a mimetype that looks like this:

文章作者指出MIME类型是可以有参数的, 那就是说可以有这样的MIME:

vnd.example-com.foo+json; version=1.0

Sadly, Rails does not (yet) understand this format.

可惜的是, Rails目前(暂时)不支持这种格式。

I’d like my content to be displayed in multiple languages

多语言支持

This is related, but a little different. What about pages in different
languages? Again, we have a question of representation, not one of
content. /en/whatever is not appropriate here. Turns out, there’s a
header for that: Accept-Language
.
Respect the headers, and everything works out.

这是个类似的问题, 不过有一点儿不同。不同语言的页面如何呢? 又一次, 我们碰到了表现的问题, 而不是关于内容。 /en/或者别的什么都不适合。 貌似有一个HTTP头能有用: Accept-Language

Oh, and I should say this, too: this doesn’t solve the problem of “I’d
like to read this article in Spanish, even though I usually browse in
English.” Giving your users the option to view your content in different
ways is a good thing. Personally, I’d consider this to fall out in two
ways:

噢, 我也应该提一下: 这并没有解决”我想阅读这篇文章的西班牙语版, 虽然我通常浏览英文的”. 给用户多种选择来查看你的内容是非常好的. 个人觉得有两种方式:

  • It’s temporary. Stick this option in the session, and if they have the
    option set, it trumps the header. You’re still respecting their usual
    preferences, but allowing them to override it.
  • 临时的. 把这个选择存放在session里, 如果session里有这个选择, 那么覆盖了HTTP头中的. 你尊重了用户通常的设置, 同时也能让他们选择覆盖默认值.
  • It’s more permanent. Make it some aspect of their account, and it
    trumps a specific header. Same deal.
  • 永久的. 在用户的账户里面添加相应的选项, 同时优先级比HTTP头高.

I’d like my content to have a mobile view

移动样式

Sounds like I’m beating a dead horse, but again: it’s a representation
question. In this case, you’d like to vary the response by the
User-Agent: give one that’s mobile-friendly. There’s a whole list of
mobile best practices that the w3c
recommends, but the short of it is this: the User-Agent should let you
know that you’re dealing with a mobile device. For example, here’s the
first iPhone UA:

听起来好像是老生常谈, 但是有一次的: 这是一个表现的问题. 在这种情况下, 可以根据User-Agent头来输出不同的返回: 给他们对移动终端友好的. 这里有一整个列表的W3C推荐的移动终端最佳实践, 简要来说:User-Agent让你知道你在处理一个移动设备的请求. 例如, 这是第一个iPhone的UA:

Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3

Then, once detecting you have a mobile User-Agent, you’d give back a
mobile version of the site. Hosting it on a subdomain is a minor sin,
but really, like I said above, this is really a question of
representation, and so having two URLs that point to the same resource
is kinda awkward.

然后, 一但检测出你有一个移动User-Agent, 你就给得到一个移动版的站点. 把移动版放在子域名下是可以接受的. 但是真的, 如我所说, 这是一个表现的问题, 有两套URL指向同一个资源有点怪异.

Whatever you do, for the love of users, please don’t detect these
headers, then redirect your users to m.whatever.com, at the root. One of my
local news websites does this, and it means that every time I try to follow a
link from Twitter in my mobile browser, I don’t see their article, I see
their homepage. It’s infuriating.

不管你怎样做, 都要为用户考虑, 请不要检测HTTP头然后重定向用户到m.whatever.com, . 我这儿当地的一个新闻站点就这样做了, 每次我想在我的移动终端的Twitter中打开其链接时, 我看不到对应的文章, 我看到了主页. 真令人生气.

I’d like to hide some of my content

我想隐藏某些内容

Every once in a while, you see a story like this: Local paper boasts
ultimate passive-agressive paywall policy
.
Now, I find paywalls distasteful, but this is not the way to do it.
There are technological means to limit content on the web: making users
be logged-in to read things, for example.

你常常会碰到这样一种情况: Local paper boasts
ultimate passive-agressive paywall policy
.
我反感付费墙, 这不是解决问题之道. 有很多技术手段可以限制web上的内容: 比如让用户登录之后查看文章.

When this was discussed on Hacker News, here’s
what I had to say:

当在Hacker News讨论这个问题的时候, 是我说的:

nkurz:

I presume if I had an unattended roadside vegetable stand with a cash-box, that I’d be able to prosecute someone who took vegetables without paying, certainly if they also made off with the cash-box. Why is this different on the web? And if a written prohibition has no legal standing, why do so many companies pay lawyers to write click-through “terms of service” agreements?

假定我有一个无人看守的蔬菜, 旁边是一个付款箱, 我想禁止别人不付款就拿走蔬菜, 当然他们付款后可以拿走蔬菜. 为什么这在web上是如此不同? 如果书面禁令没有法律地位, 为什么那么多公司都聘请律师来写”同意后继续”的”服务协议”?

me:

我:

Why is this different on the web?

为什么这在web上行不通?

Let’s go through what happens when I visit a web site. I type a URL in my bar, and hit enter. My web browser makes a request via http to a server, and the server inspects the request, determines if I should see the content or not, and returns either a 200 if I am allowed, and a 403 if I’m not. So, by viewing their pages, I’m literally asking permission, and being allowed.

让我们看看当我们访问一个网站的时候发生了什么. 我在地址栏输入URL, 敲击回车. 浏览器发送一个HTTP请求到服务器, 服务器接收到这个请求, 决定我是否有权查看, 返回一个200如果我有权查看, 或者返回一个404如果我无权查看. 所以, 当访问网页的时候, 我就是在询问是否有权查看.

It sounds to me like a misconfiguration of their server; it’s not doing what they want it to.

这对于我来说更像是他们没有配置好服务器; 而不是他们没有做到想要做的那样.

ajax和永久链接

This is an example of where the spec is obviously deficient, and so
something had to be done.

这是规范没有提及的地方之一, 我们必须做些什么.

As the web grew, AJAXy ‘web applications’ started to become more and
more the norm. And so applications wanted to provide deep-linking
capabilities to users, but there’s a problem: they couldn’t manipulate
the URL with Javascript without causing a redirect. They could
manipulate the anchor, though. You know, that part after the #. So,
Google came up with a convention: Ajax Fragments.
This fixed the problem in the short term, but then the spec got fixed in
the long term: pushState.
This lets you still provide a nice deep URL to your users, but not have
that awkward #!.

在web增长的同时, AJAX式的’web应用’开始越来越多. 应用想提供给用户深度链接的能力, 但是这儿有一些问题: 不能用JavaScript操作URL而不引起重定向. 但是可以操作锚点, 你知道的, #之后的那部分. 于是Google提出了一个约定: Ajax Fragments.
短期来看这解决了问题, 但是之后这个规范彻底解决了问题: pushState. 这让你可以在没有那奇怪的#!的同时向用户提供深度的URL链接.

In this case, there was a legitimate technical issue with the spec, and
so it’s valid to invent something. But then the standard improved, and
so people should stop using #! as HTML5 gains browser support.

这个例子中, 确有一个技术问题, 所以发明一些东西是挺好的. 随着标准改进, 越来越多的浏览器支持HTML5, 应该停止使用#!.

In conclusion

结束语

Seriously, most of the problems that you’re solving are social, not
technical. The web is decades old at this point, most people have
considered these kinds of problems in the past. That doesn’t mean that
they always have the right answer, but they usually do have an answer,
and it’d behoove you to know what it is before you invent something on
your own.

严格来说, 你碰到的大部分问题都是social的, 而不是技术的. 这点来说, web有一些古老了, 大多数人还在用过去的思维来对待这些问题. 这并不能让他们总是得到正确的解法, 但好歹是一个解法. 在你发明什么东西之前, 你应该了解一下它过去的是什么样子.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>