forked from cocoajin/http-api-design-ZH_CN
-
Notifications
You must be signed in to change notification settings - Fork 225
/
http-api-设计指南.html
705 lines (545 loc) · 26.9 KB
/
http-api-设计指南.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
<!DOCTYPE html><html>
<head>
<meta charset="utf-8">
<title></title>
<style>
body {
font-family: Helvetica, arial, sans-serif;
font-size: 14px;
line-height: 1.6;
padding-top: 10px;
padding-bottom: 10px;
background-color: white;
padding: 30px; }
body > *:first-child {
margin-top: 0 !important; }
body > *:last-child {
margin-bottom: 0 !important; }
a {
color: #4183C4; }
a.absent {
color: #cc0000; }
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0; }
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative; }
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==) no-repeat 10px center;
text-decoration: none; }
h1 tt, h1 code {
font-size: inherit; }
h2 tt, h2 code {
font-size: inherit; }
h3 tt, h3 code {
font-size: inherit; }
h4 tt, h4 code {
font-size: inherit; }
h5 tt, h5 code {
font-size: inherit; }
h6 tt, h6 code {
font-size: inherit; }
h1 {
font-size: 28px;
color: black; }
h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
color: black; }
h3 {
font-size: 18px; }
h4 {
font-size: 16px; }
h5 {
font-size: 14px; }
h6 {
color: #777777;
font-size: 14px; }
p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0; }
hr {
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0;
}
body > h2:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child + h2 {
margin-top: 0;
padding-top: 0; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
margin-top: 0;
padding-top: 0; }
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0; }
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0; }
li p.first {
display: inline-block; }
li {
margin: 0; }
ul, ol {
padding-left: 30px; }
ul :first-child, ol :first-child {
margin-top: 0; }
dl {
padding: 0; }
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px; }
dl dt:first-child {
padding: 0; }
dl dt > :first-child {
margin-top: 0; }
dl dt > :last-child {
margin-bottom: 0; }
dl dd {
margin: 0 0 15px;
padding: 0 15px; }
dl dd > :first-child {
margin-top: 0; }
dl dd > :last-child {
margin-bottom: 0; }
blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }
table {
padding: 0;border-collapse: collapse; }
table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
table tr:nth-child(2n) {
background-color: #f8f8f8; }
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
table tr td {
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
table tr th :first-child, table tr td :first-child {
margin-top: 0; }
table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
img {
max-width: 100%; }
span.frame {
display: block;
overflow: hidden; }
span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto; }
span.frame span img {
display: block;
float: left; }
span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px 0 0; }
span.align-center {
display: block;
overflow: hidden;
clear: both; }
span.align-center > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center; }
span.align-center span img {
margin: 0 auto;
text-align: center; }
span.align-right {
display: block;
overflow: hidden;
clear: both; }
span.align-right > span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right; }
span.align-right span img {
margin: 0;
text-align: right; }
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left; }
span.float-left span {
margin: 13px 0 0; }
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right; }
span.float-right > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right; }
code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px; }
pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent; }
.highlight pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre code, pre tt {
background-color: transparent;
border: none; }
sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
* {
-webkit-print-color-adjust: exact;
}
@media screen and (min-width: 914px) {
body {
width: 854px;
margin:0 auto;
}
}
@media print {
table, pre {
page-break-inside: avoid;
}
pre {
word-wrap: break-word;
}
}
</style>
<style> @media print{ {.hljs{overflow: visible; word-wrap: break-word;} }</style></head><body><div class="markdown-body">
<h1>HTTP API 设计指南</h1>
<blockquote>
<p>翻译自 <code>HTTP API Design Guide</code> <a href="https://github.com/interagent/http-api-design">https://github.com/interagent/http-api-design</a></p>
</blockquote>
<ul>
<li>更新时间:<code>2015-10-08</code> 更新至 <a href="https://github.com/interagent/http-api-design/commit/50bda693252a4f52653d638fd5503b39b88b8470">#50bda6</a></li>
<li>欢迎大家问题和共同维护这个文档</li>
<li>HTML和PDF通过<code>MWeb</code>生成</li>
<li>翻译人员见<code>CONTRIBUTORS.md</code></li>
</ul>
<h2>前言</h2>
<p>这篇指南介绍描述了 HTTP+JSON API 的一种设计模式,最初摘录整理自 Heroku 平台的 API 设计指引 <a href="https://devcenter.heroku.com/articles/platform-api-reference">Heroku 平台 API 指引</a>。</p>
<p>这篇指南除了详细介绍现有的 API 外,Heroku 将来新加入的内部 API 也会符合这种设计模式,我们希望非 Heroku 员工的API设计者也能感兴趣。</p>
<p>我们的目标是保持一致性,专注业务逻辑同时避免过度设计。我们一直试图找出一种良好的、一致的、显而易见的 API 设计方法,而并不是所谓的<q>最终/理想模式</q>。</p>
<p>我们假设你熟悉基本的 HTTP+JSON API 设计方法,所以本篇指南并不包含所有的 API 设计基础。</p>
<p>我们欢迎你为这篇指南做<a href="https://github.com/interagent/http-api-design/blob/master/CONTRIBUTING.md">贡献</a>。</p>
<h2>目录</h2>
<ul>
<li>基础
<ul>
<li>强制使用安全连接(Secure Connections)</li>
<li>强制头信息 Accept 中提供版本号</li>
<li>支持Etag缓存</li>
<li>为内省而提供 Request-Id</li>
<li>通过请求中的范围(Range)拆分大的响应</li>
</ul></li>
<li>请求(Requests)
<ul>
<li>在请求的body体使用JSON格式数据</li>
<li>使用统一的资源路径格式</li>
<li>路径和属性要小写</li>
<li>支持方便的无id间接引用</li>
<li>最小化路径嵌套</li>
</ul></li>
<li>响应(Responses)
<ul>
<li>返回合适的状态码</li>
<li>提供全部可用的资源</li>
<li>提供资源的(UU)ID</li>
<li>提供标准的时间戳</li>
<li>使用UTC(世界标准时间)时间,用ISO8601进行格式化</li>
<li>嵌套外键关系</li>
<li>生成结构化的错误</li>
<li>显示频率限制状态</li>
<li>保证响应JSON最小化</li>
</ul></li>
<li>工件(Artifacts)
<ul>
<li>提供机器可读的JSON模式</li>
<li>提供人类可读的文档</li>
<li>提供可执行的例子</li>
<li>描述稳定性</li>
</ul></li>
<li>译者注</li>
</ul>
<h3>基础</h3>
<h4>隔离关注点</h4>
<p>设计时通过将请求和响应之间的不同部分隔离来让事情变得简单。保持简单的规则让我们能更关注在一些更大的更困难的问题上。</p>
<p>请求和响应将解决一个特定的资源或集合。使用路径(path)来表明身份,body来传输内容(content)还有头信息(header)来传递元数据(metadata)。查询参数同样可以用来传递头信息的内容,但头信息是首选,因为他们更灵活、更能传达不同的信息。</p>
<h4>强制使用安全连接(Secure Connections)</h4>
<p>所有的访问API行为,都需要用 TLS 通过安全连接来访问。没有必要搞清或解释什么情况需要 TLS 什么情况不需要 TLS,直接强制任何访问都要通过 TLS。</p>
<p>理想状态下,通过拒绝所有非 TLS 请求,不响应 http 或80端口的请求以避免任何不安全的数据交换。如果现实情况中无法这样做,可以返回<code>403 Forbidden</code>响应。</p>
<p>把非 TLS 的请求重定向(Redirect)至 TLS 连接是不明智的,这种含混/不好的客户端行为不会带来明显好处。依赖于重定向的客户端访问不仅会导致双倍的服务器负载,还会使 TLS 加密失去意义,因为在首次非 TLS 调用时,敏感信息就已经暴露出去了。</p>
<h4>强制头信息 Accept 中提供版本号</h4>
<p>制定版本并在版本之间平缓过渡对于设计和维护一套API是个巨大的挑战。所以,最好在设计之初就使用一些方法来预防可能会遇到的问题。</p>
<p>为了避免API的变动导致用户使用中产生意外结果或调用失败,最好强制要求所有访问都需要指定版本号。请避免提供默认版本号,一旦提供,日后想要修改它会相当困难。</p>
<p>最适合放置版本号的位置是头信息(HTTP Headers),在 <code>Accept</code> 段中使用自定义类型(content type)与其他元数据(metadata)一起提交。例如:</p>
<pre><code>Accept: application/vnd.heroku+json; version=3
</code></pre>
<h4>支持Etag缓存</h4>
<p>在所有返回的响应中包含<code>ETag</code>头信息,用来标识资源的版本。这让用户对资源进行缓存处理成为可能,在后续的访问请求中把<code>If-None-Match</code>头信息设置为之前得到的<code>ETag</code>值,就可以侦测到已缓存的资源是否需要更新。</p>
<h4>为内省而提供 Request-Id</h4>
<p>为每一个请求响应包含一个<code>Request-Id</code>字段,并使用UUID作为该值。通过在客户端、服务器或任何支持服务上记录该值,它能主我们提供一种机制来跟踪、诊断和调试请求。</p>
<h4>通过请求中的范围(Range)拆分大的响应</h4>
<p>一个大的响应应该通过多个请求使用<code>Range</code>头信息来拆分,并指定如何取得。详细的请求和响应的头信息(header),状态码(status code),范围(limit),排序(ordering)和迭代(iteration)等,参考<a href="https://devcenter.heroku.com/articles/platform-api-reference#ranges">Heroku Platform API discussion of Ranges</a>.</p>
<h3>请求(Requests)</h3>
<h4>在请求的body体使用JSON格式数据</h4>
<p>在 <code>PUT</code>/<code>PATCH</code>/<code>POST</code> 请求的正文(request bodies)中使用JSON格式数据,而不是使用 form 表单形式的数据。这与我们使用JSON格式返回请求相对应,例如:</p>
<pre><code>$ curl -X POST https://service.com/apps \
-H "Content-Type: application/json" \
-d '{"name": "demoapp"}'
{
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "demoapp",
"owner": {
"email": "username@example.com",
"id": "01234567-89ab-cdef-0123-456789abcdef"
},
...
}
</code></pre>
<h4>使用统一的资源路径格式</h4>
<h5>资源名(Resource names)</h5>
<p>使用复数形式为资源命名,除非这个资源在系统中是单例的 (例如,在大多数系统中,给定的用户帐户只有一个)。 这种方式保持了特定资源的统一性。</p>
<h5>行为(Actions)</h5>
<p>好的末尾不需要为资源指定特殊的行为,但在特殊情况下,为某些资源指定行为却是必要的。为了描述清楚,在行为前加上一个标准的<code>actions</code>:</p>
<pre><code>/resources/:resource/actions/:action
</code></pre>
<p>例如:</p>
<pre><code>/runs/{run_id}/actions/stop
</code></pre>
<h4>路径和属性要小写</h4>
<p>为了和域名命名规则保持一致,使用小写字母并用<code>-</code>分割路径名字,例如:</p>
<pre><code>service-api.com/users
service-api.com/app-setups
</code></pre>
<p>属性也使用小写字母,但是属性名要用下划线<code>_</code>分割,以便在Javascript中省略引号。 例如:</p>
<pre><code class="language-json">service_class: "first"
</code></pre>
<h4>支持方便的无id间接引用</h4>
<p>在某些情况下,让用户提供ID去定位资源是不方便的。例如,一个用户想取得他在Heroku平台app信息,但是这个app的唯一标识是UUID。这种情况下,你应该支持接口通过名字和ID都能访问,例如:</p>
<pre><code>$ curl https://service.com/apps/{app_id_or_name}
$ curl https://service.com/apps/97addcf0-c182
$ curl https://service.com/apps/www-prod
</code></pre>
<p>不要只接受使用名字而放弃了使用id。</p>
<h4>最小化路径嵌套</h4>
<p>在一些有父路径/子路径嵌套关系的资源数据模块中,路径可能有非常深的嵌套关系,例如:</p>
<pre><code>/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}
</code></pre>
<p>推荐在根(root)路径下指定资源来限制路径的嵌套深度。使用嵌套指定范围的资源。在上述例子中,dyno属于app,app属于org可以表示为:</p>
<pre><code>/orgs/{org_id}
/orgs/{org_id}/apps
/apps/{app_id}
/apps/{app_id}/dynos
/dynos/{dyno_id}
</code></pre>
<h3>响应(Responses)</h3>
<h4>返回合适的状态码</h4>
<p>为每一次的响应返回合适的HTTP状态码。 好的响应应该使用如下的状态码:</p>
<ul>
<li><code>200</code>: <code>GET</code>请求成功,及<code>DELETE</code>或<code>PATCH</code>同步请求完成,或者<code>PUT</code>同步更新一个已存在的资源</li>
<li><code>201</code>: <code>POST</code> 同步请求完成,或者<code>PUT</code>同步创建一个新的资源</li>
<li><code>202</code>: <code>POST</code>,<code>PUT</code>,<code>DELETE</code>,或<code>PATCH</code>请求接收,将被异步处理</li>
<li><code>206</code>: <code>GET</code> 请求成功,但是只返回一部分,参考:<a href="#%E6%8C%89%E8%8C%83%E5%9B%B4%E5%88%86%E9%A1%B5">上文中范围分页</a></li>
</ul>
<p>使用身份认证(authentication)和授权(authorization)错误码时需要注意:</p>
<ul>
<li><code>401 Unauthorized</code>: 用户未认证,请求失败</li>
<li><code>403 Forbidden</code>: 用户无权限访问该资源,请求失败</li>
</ul>
<p>当用户请求错误时,提供合适的状态码可以提供额外的信息:</p>
<ul>
<li><code>422 Unprocessable Entity</code>: 请求被服务器正确解析,但是包含无效字段</li>
<li><code>429 Too Many Requests</code>: 因为访问频繁,你已经被限制访问,稍后重试</li>
<li><code>500 Internal Server Error</code>: 服务器错误,确认状态并报告问题</li>
</ul>
<p>对于用户错误和服务器错误情况状态码,参考: <a href="https://tools.ietf.org/html/rfc7231#section-6">HTTP response code spec</a></p>
<h4>提供全部可用的资源</h4>
<p>提供全部可显现的资源 (例如: 这个对象的所有属性) ,当响应码为200或是201时返回所有可用资源,包含 <code>PUT</code>/<code>PATCH</code> 和 <code>DELETE</code><br/>
请求,例如:</p>
<pre><code class="language-json">$ curl -X DELETE \
https://service.com/apps/1f9b/domains/0fd4
HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
...
{
"created_at": "2012-01-01T12:00:00Z",
"hostname": "subdomain.example.com",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"updated_at": "2012-01-01T12:00:00Z"
}
</code></pre>
<p>当请求状态码为202时,不返回所有可用资源,例如:</p>
<pre><code>$ curl -X DELETE \
https://service.com/apps/1f9b/dynos/05bd
HTTP/1.1 202 Accepted
Content-Type: application/json;charset=utf-8
...
{}
</code></pre>
<h4>提供资源的(UU)ID</h4>
<p>在默认情况给每一个资源一个<code>id</code>属性。除非有更好的理由,否则请使用UUID。不要使用那种在服务器上或是资源中不是全局唯一的标识,尤其是自动增长的id。</p>
<p>生成小写的UUID格式 <code>8-4-4-4-12</code>,例如:</p>
<pre><code class="language-json">"id": "01234567-89ab-cdef-0123-456789abcdef"
</code></pre>
<h4>提供标准的时间戳</h4>
<p>为资源提供默认的创建时间 <code>created_at</code> 和更新时间 <code>updated_at</code>,例如:</p>
<pre><code class="language-json">{
...
"created_at": "2012-01-01T12:00:00Z",
"updated_at": "2012-01-01T13:00:00Z",
...
}
</code></pre>
<p>有些资源不需要使用时间戳那么就忽略这两个字段。</p>
<h4>使用UTC(世界标准时间)时间,用ISO8601进行格式化</h4>
<p>在接收和返回时都只使用UTC格式。ISO8601格式的数据,例如:</p>
<pre><code class="language-json">"finished_at": "2012-01-01T12:00:00Z"
</code></pre>
<h4>嵌套外键关系</h4>
<p>使用嵌套对象序列化外键关联,例如:</p>
<pre><code class="language-json">{
"name": "service-production",
"owner": {
"id": "5d8201b0..."
},
// ...
}
</code></pre>
<p>而不是像这样:</p>
<pre><code class="language-json">{
"name": "service-production",
"owner_id": "5d8201b0...",
...
}
</code></pre>
<p>这种方式尽可能的把相关联的资源信息内联在一起,而不用改变资源的结构,或者引入更多的字段,例如:</p>
<pre><code class="language-json">{
"name": "service-production",
"owner": {
"id": "5d8201b0...",
"name": "Alice",
"email": "alice@heroku.com"
},
...
}
</code></pre>
<h4>生成结构化的错误</h4>
<p>响应错误的时,生成统一的、结构化的错误信息。包含一个机器可读的错误 <code>id</code>,一个人类能识别的错误信息(<code>message</code>),根据情况可以添加一个<code>url</code>来告诉客户端关于这个错误的更多信息以及如何去解决它,例如:</p>
<pre><code>HTTP/1.1 429 Too Many Requests
</code></pre>
<pre><code class="language-json">{
"id": "rate_limit",
"message": "Account reached its API rate limit.",
"url": "https://docs.service.com/rate-limits"
}
</code></pre>
<p>文档化客户端可能遇到的错误信息格式,以及这些可能的错误信息<code>id</code>。</p>
<h4>显示频率限制状态</h4>
<p>客户端的访问速度限制可以维护服务器的良好状态,保证为其他客户端请求提供高性的服务。你可以使用<a href="http://en.wikipedia.org/wiki/Token_bucket">token bucket algorithm</a>技术量化请求限制。</p>
<p>为每一个带有<code>RateLimit-Remaining</code>响应头的请求,返回预留的请求tokens。</p>
<h4>保证响应JSON最小化</h4>
<p>请求中多余的空格会增加响应大小,而且现在很多的HTTP客户端都会自己输出可读格式(<q>prettify</q>)的JSON。所以最好保证响应JSON最小化,例如:</p>
<pre><code class="language-json">{"beta":false,"email":"alice@heroku.com","id":"01234567-89ab-cdef-0123-456789abcdef","last_login":"2012-01-01T12:00:00Z","created_at":"2012-01-01T12:00:00Z","updated_at":"2012-01-01T12:00:00Z"}
</code></pre>
<p>而不是这样:</p>
<pre><code class="language-json">{
"beta": false,
"email": "alice@heroku.com",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"last_login": "2012-01-01T12:00:00Z",
"created_at": "2012-01-01T12:00:00Z",
"updated_at": "2012-01-01T12:00:00Z"
}
</code></pre>
<p>你可以提供可选的方式为客户端提供更详细可读的响应,使用查询参数(例如:<code>?pretty=true</code>)或者通过<code>Accept</code>头信息参数(例如:<code>Accept: application/vnd.heroku+json; version=3; indent=4;</code>)。</p>
<h3>工件(Artifacts)</h3>
<h4>提供机器可读的JSON模式</h4>
<p>提供一个机器可读的模式来恰当的表现你的API。使用<br/>
<a href="https://github.com/interagent/prmd">prmd</a>管理你的模式,并且确保用<code>prmd verify</code>验证是有效的。</p>
<h4>提供人类可读的文档</h4>
<p>提供人类可读的文档让客户端开发人员可以理解你的API。</p>
<p>如果你用prmd创建了一个概要并且按上述要求描述,你可以为所有节点很容易的使用<code>prmd doc</code>生成Markdown文档。</p>
<p>除了节点信息,提供一个API概述信息:</p>
<ul>
<li>验证授权,包含如何取得和如何使用token。</li>
<li>API稳定及版本管理,包含如何选择所需要的版本。</li>
<li>一般情况下的请求和响应的头信息。</li>
<li>错误的序列化格式。</li>
<li>不同编程语言客户端使用API的例子。</li>
</ul>
<h4>提供可执行的例子</h4>
<p>提供可执行的示例让用户可以直接在终端里面看到API的调用情况,最大程度的让这些示例可以简单的使用,以减少用户尝试使用API的工作量。例如:</p>
<pre><code>$ export TOKEN=... # acquire from dashboard
$ curl -is https://$TOKEN@service.com/users
</code></pre>
<p>如果你使用<a href="https://github.com/interagent/prmd">prmd</a>生成Markdown文档,每个节点都会自动获取一些示例。</p>
<h4>描述稳定性</h4>
<p>描述您的API的稳定性或是它在各种各样节点环境中的完备性和稳定性,例如:加上 原型版(prototype)/开发版(development)/产品版(production)等标记。</p>
<p>更多关于可能的稳定性和改变管理的方式,查看 <a href="https://devcenter.heroku.com/articles/api-compatibility-policy">Heroku API compatibility policy</a></p>
<p>一旦你的API宣布产品正式版本及稳定版本时,不要在当前API版本中做一些不兼容的改变。如果你需要,请创建一个新的版本的API。</p>
</div></body>
</html>