书名:ASP.NET Core与RESTful API 开发实战
978-7-115-51951-1 A20182001
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 杨万青
责任编辑 张 爽
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
ASP.NET Core是微软推出的新一代跨平台、高性能Web开发框架,具有模块化、内置依赖项注入、开源、易于部署等特点。作为近些年来主流的软件架构风格,REST旨在构建简单、可靠、高性能、高伸缩性的Web应用。
本书系统地介绍了如何使用ASP.NET Core开发RESTful API应用,共包含10章内容。前3章主要介绍了REST、HTTP、ASP.NET Core的基础理论。第4~10章讲述如何根据前3章的理论逐步构建规范的RESTful API应用,涉及资源的基本操作、Entity Framework Core、高级查询、日志、缓存、并发、HATEOAS、认证与安全、测试以及部署等内容。
本书适合.NET开发者或.NET初学者阅读,也适合那些有其他编程语言基础且想要学习.NET Core的开发者阅读。同时,本书也适合作为大专院校计算机专业的师生用书和培训学校的教材。
微软于2016年推出.NET Core和ASP.NET Core。作为一个全新的开发平台,.NET Core对.NET Framework框架进行了重大的改进,解决了.NET Framework的一个非常明显的缺陷,实现了跨平台性。使用.NET Core能够开发出适用于各种平台以及各种不同类型的应用,如Web应用程序、微服务、控制台程序、Windows桌面应用(自.NET Core 3.0起)等,并能够轻松地部署到各个平台上。同时,.NET Core沿用了.NET Framework框架的优点,使.NET开发人员能够顺利上手并在工作中使用。.NET Core从发布之初就加入开源组织,并受到开发者社区的支持,目前.NET Core已经达到2.2版本,其成熟性、稳定性、高性能、模块化等特点将帮助开发者开发出适用于各种场景的企业级应用。
作为.NET Core平台的重要角色,ASP.NET Core旨在开发现代主流的Web应用程序。与.NET Core一样,ASP.NET Core同样具有跨平台、开源、模块化、高性能等特点,这些特点使它完全超越了ASP.NET。ASP.NET Core还具有一些重要特点,如内置依赖项注入、轻型的高性能模块化HTTP请求管道、灵活的配置与日志系统等,使开发者能够轻松地开发出更灵活、更安全和高质量的Web应用程序。同时,ASP.NET Core内置了对云平台和容器的支持,使它能够快速地部署到不同的平台中。
自从2000年Roy Thomas Fielding在其博士论文中首次提出REST后,REST一直就是人们讨论的话题,并且不断地应用于各种技术的实现中。作为一种软件架构风格,REST旨在构建简单、可靠、高性能、高伸缩性的Web应用。然而,由于它并不像标准一样具有详尽的定义、说明与规则,并且开发者易受其他Web服务开发风格的影响,因此多数人对于它的认识与理解不够全面,甚至存在一定程度上的误解。
本书以ASP.NET Core与REST为主题,介绍了如何使用ASP.NET Core开发出规范的RESTful API应用。本书不仅详细地介绍了REST、REST约束以及HTTP协议,还深入介绍了ASP.NET Core及其重要性。充分理解ASP.NET Core与REST有助于开发者设计出规范的RESTful API。
本书系统介绍ASP.NET Core与RESTful API应用的开发,共分为10章。前3章重点介绍理论知识,后7章主要讲述实践操作。前3章的理论为后7章的实践提供了支持,如第1章介绍的HTTP消息头和状态码、第3章介绍的ASP.NET Core核心特性,会在后7章中经常提及并用到。这种从理论到实践、由浅入深的学习方式有助于读者进一步掌握所学的内容。如果你刚开始学习.NET Core开发,建议按照章节顺序阅读本书。
从第4章开始的项目实践将带领读者一步一步地开发RESTful API应用,从项目的创建到实现对资源的操作,从使用Entity Framework Core到高级查询与日志,从为项目添加认证功能到为项目应用ASP.NET Core提供的各种安全特性,从测试到部署,这一系列内容贯穿了一个实际项目的整个开发流程。读者若能从头到尾实践本书中项目的开发流程,将会受益匪浅。同时,在介绍项目开发的过程中,对于遇到的新知识,本书也进行了必要的理论性介绍,如第4章中的仓储模式、第7章中的HTTP缓存、第8章中的CORS、第10章中的Docker与Azure等。
本书主要内容如下。
第1章介绍API与REST的基本概念、REST约束、HTTP协议,以及REST中资源表述常用的JSON格式与XML格式等。
第2章介绍.NET Core、.NET Standard以及ASP.NET Core,讨论ASP.NET Core自2.0后各个版本新增加的特性与变化,展示开发环境的设置以及如何开始创建第一个Web API应用。
第3章深入剖析ASP.NET Core提供的重要特性,如启动与Kestrel服务器、中间件、依赖注入、MVC、配置、日志、错误处理。
第4章介绍实例项目(该项目将贯穿本书后面的章节)的创建,讨论如何准备测试数据、仓储模式,以及如何实现对资源的各种操作,如获取、创建、删除、更新等,最后讨论内容协商及其实现方式。
第5章介绍Entity Framework Core以及如何在项目中使用它,并使用它替换原来的内存数据源方式,以及使用异步方式替换原来的同步方式。
第6章介绍分页、过滤、搜索、排序的实现,以及如何记录日志并处理异常。
第7章介绍较为复杂的主题,包括缓存以及不同种类缓存的实现方式、并发控制的实现方式、API版本、HATEOAS,以及GraphQL及其实现。
第8章介绍如何保护API应用程序,包括为应用程序添加认证功能、使用Identity保存用户信息、使用HTTPS与HSTS。该章还会讲解数据保护API和用户机密的概念与使用,以及跨域资源访问及其实现方式。
第9章介绍如何对应用程序进行单元测试、集成测试,并为其创建OpenAPI文档。
第10章介绍如何将应用程序部署到不同的位置,如IIS、Docker以及Azure,同时介绍Docker与Azure的概念与基本操作。
完成本书的写作是一件不容易的事。本书尽可能涵盖相关的知识点,并尽力确保内容的正确性,使读者能够从中有所收获。然而由于个人水平有限,书中疏漏之处在所难免,如果你在学习过程中发现书中的错误或对本书有任何建议和意见,既可以告诉作者或本书编辑,也可以提交到异步社区中,我们将非常感激。
作者邮箱:ictcm@outlook.com
编辑邮箱:zhangshuang@ptpress.com.cn
感谢我的家人在我写作期间给予我的支持和包容,也感谢那些帮助我解决疑问并给出建议的技术专家和同事。最后,感谢本书编辑为本书提供的指导与建议。
本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。
作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。
当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。
我们的联系邮箱是contact@epubit.com.cn。
如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。
如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问http://www.epubit.com/ selfpublish/submissionwww.epubit.com/selfpublish/submission即可)。
如果您是学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。
如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。
“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。
“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。
异步社区
微信服务号
本章内容
REST,全称为REpresentational State Transfer,即表述性状态传递,它是一种应用程序的架构风格,用于构造简单、可靠、高性能的Web应用程序。REST提出了一系列约束,遵循这些约束的应用程序称为RESTful API应用。
要设计出REST风格的应用,开发者首先应该对REST及其相关概念有基本的理解。因此,本章首先介绍API以及REST的基本概念,并介绍REST所定义的架构约束,同时也会介绍常见的对REST的错误理解。
通常情况下,REST是基于HTTP协议而实现的,因此本章会重点介绍HTTP协议,包括媒体类型、HTTP方法、HTTP消息头和状态码等。之后,本章还会提出一些REST的最佳实践,指导开发者设计出优秀的RESTful应用。最后本章还会介绍RESTful API开发中的常见问题,如JSON与XML格式,以及API版本问题。
API全称Application Programming Interface,即应用程序编程接口,我们在开发应用程序时经常用到。API作为接口,用来“连接”两个不同的系统,并使其中一方为另一方提供服务,比如在操作系统上运行的应用程序能够访问操作系统所提供的API,并通过这些API来调用操作系统的各种功能。因此,API是一个系统向外暴露或公开的一套接口,通过这些接口,外部应用程序能够访问该系统。
在Web应用程序中,Web API具有同样的特性,它作为一个Web应用程序,向外提供了一些接口,这些接口的功能通常是对数据进行操作(如获取或修改数据等),它们能够被外部应用程序,比如桌面应用程序、手机应用甚至其他Web应用程序(如ASP.NET Core MVC视图应用、单页Web应用)等访问并调用。
Web API能够实现不同应用程序之间的访问,它与平台或编程语言无关,可以使用不同的技术来构建Web API,如Java、.NET等;调用方也不受Web API使用的平台或技术限制,比如一个使用Java语言开发的Android手机应用可以调用一个使用C#语言开发的ASP.NET Core Web API应用程序。同时,API作为接口,仅向外部应用程序提供了抽象,而不是在其内部实现。这正如高级编程语言中的接口(Interface)一样,调用方无须关心接口如何实现,仅需要调用它所提供的方法即可。此外,Web API由于其自身的特点,不能直接被用户使用,相反地,它通常由开发人员来使用。通过调用Web API,开发人员能够设计出丰富多样的应用。因此,设计良好、有丰富文档的Web API更易于被开发者接受并使用。
REST(REpresentational State Transfer)的意思是表述性状态传递,它是Roy Thomas Fielding在2000年发表的博士论文中提出来的一种软件架构风格。作为一种Web服务的设计与开发方式,REST可以降低开发的复杂性,提高系统的可伸缩性。
REST是一种基于资源的架构风格,在REST中,资源(Resource)是最基本的概念。任何能够命名的对象都是一个资源,如document、user、order等,通常情况下,它表示Web服务中要操作的一个实体。一个资源具有一个统一的资源标识符(Uniform Resource Identifier,URI),如users/1234,通过资源标识符能够标识并访问该资源。
除了单个的资源外,资源集合表示多个相同类型的资源,如users。在系统设计时,不同的实体之间往往存在某种关联关系,如一个用户有多个订单。同样,在REST中,这种关联关系也能够由资源之间的层次关系体现出来,如users/1234/orders/1。
由于REST以资源为中心,因此REST接口的端点(Endpoint)均以资源或资源集合结尾,它不像其他形式的Web服务一样以动词结尾,如api/GetUserInfo或api/UpdateUserInfo。在REST中,对资源的动作或操作是通过HTTP方法来完成的,如下:
GET http://api.domain.com/users/1234
PUT http://api.domain.com/users/1234
上例中用到了两个HTTP方法,分别为GET与PUT,它们的作用分别是获取和更新指定资源。当请求方发起请求,修改了资源的状态后,更新后的资源表述应返回给请求方,这也是表述性状态传递的意义。
从上面的例子可以看出,REST与HTTP有一定的关系,资源在服务的提供方与请求方之间进行传递,需要借助于协议来约定,比如协议所规定的消息格式等,而HTTP协议则是非常成熟且被广泛使用的网络协议。事实上,HTTP协议完全满足REST中所定义的约束,因此REST能够充分地使用HTTP协议以及其中的功能(如HTTP方法、HTTP消息等),并设计出松耦合的Web服务。1.1.3节将介绍REST约束,在1.2节中将会介绍HTTP协议。
REST定义了6个架构约束(Constraint),遵循这些约束的Web服务是真正的RESTful服务,即REST风格的服务。如果一个系统违反了其中的约束,则不能称其为RESTful,这些约束包括如下。
(1)客户端-服务器(Client-Server)
客户端-服务器约束体现了关注点分离(Separation of Concerns)原则,使客户端与服务端各自能够独立实现并独立开发,只要它们之间的接口不改变即可;客户端与服务端可以使用不同的技术或编程语言。
(2)统一接口(Uniform Interface)
统一接口是设计任何RESTful服务的基础,也是区别REST架构风格与其他Web服务风格的最主要约束。系统中的多个组件(包括服务端、客户端,以及可能存在的代理服务器等)都依赖于统一接口。统一接口约束本身又由4个子约束组成,分别如下。
前面提到过,任何能够命名的对象都是一个资源,资源能够通过统一资源标识符来区别。对于Web系统,统一资源标识符通常是一个URL,即统一资源定位符(Uniform Resource Locator)。每个URL代表一个资源或资源集合,当访问一个URL时,能够获取该资源或对它执行相应的操作。
当请求一个资源时,服务器返回该资源的一个表述。该表述表示资源当前的状态,它由表述正文和表述元数据组成,格式通常为JSON、XML和HTML等,比如以下代码是同一资源的两种不同的表述形式。
{
"User": {
"id": "123",
"name": "Tom"
}
}
或
<User>
<id>1234</id>
<name>Tom</name>
</User>
客户端在请求资源时,能够指定期望的表述格式,服务器在返回响应时,在响应中包含了指定表述格式的资源;访问同一个资源的不同格式无须修改资源的标识符,客户端也可以通过资源的表述(而非资源本身)对资源进行操作。
客户端与服务器之间传递的每一条消息都应包含足够的信息,这些信息不仅包含了资源的表述,也包含了资源表述的相关信息(如资源表述的格式与内容长度等),甚至包含了与该资源相关的其他操作信息。
服务器返回的资源表述中不仅要包含资源的表述,也应包含与之相关的链接,这些链接能够对资源执行其他操作,比如当获取资源时,返回的链接中包含更新该资源、删除该资源等链接。关于HATEOAS,第7章会有更详细的介绍。
(3)分层系统(Layered System)
分层系统约束能够使网络中介(如代理或网关等)透明地部署到客户端与服务器之间,只要它们遵循并且使用前面提到的统一接口约束即可;而客户端和服务端则都不知道网络中介的存在。中间服务器主要用于增强安全、负载均衡和响应缓存等目的。
(4)缓存(Cache)
缓存是Web架构中最重构的特性之一。客户端或网络中介均能够缓存服务器返回的响应,因此当服务器返回响应时,应指明该响应的缓存特性。对响应进行缓存将有助于减少数据获取延迟以及对服务器的请求,从而提高系统的性能。
(5)无状态(Stateless)
无状态约束将指明服务器不会记录或存储客户端的状态信息,反之,这些状态信息应由客户端来保存并维护,因此客户端对服务器的请求不能依赖于已发生过的其他请求,当客户端请求服务器时,必须在请求消息中包含所有与之相关的信息(如认证信息等)。
(6)按需编码(Code-On-Demand)
按需编码约束允许服务器临时向客户端返回可执行的程序代码(如脚本等),返回这些代码主要用于为客户端提供扩展性或自定义的功能。由于客户端必须理解并能够执行服务器返回的代码,因此这一约束增加了客户端与服务器之间的耦合,同时,这一约束是可选的。
理解REST及其约束,将有助于我们设计RESTful服务或RESTful API。然而在现实中,人们仍然对REST有错误的认识,认为只要有某种特性的API就是RESTful API。这些对REST错误的认识可能包含但不限于以下几种情况。
尽管REST风格的API同样具有上述特点并能够完成上述功能,然而这并不是说具有上述特点的API就是RESTful API。只有遵守了REST约束的API,才能够称为RESTful API。另外,Richardson成熟度模型是衡量API成熟度的一种方式,该模型进一步描述了各种Web API的特征,根据该模型,只有最成熟的API才是RESTful API。在7.4节中,将会更详细地介绍Richardson成熟度模型。
除了REST外,另一种常见的API风格是RPC风格,即远程过程调用(Remote Procedure Call)。下面是一个典型的RPC风格的API。
GET api.domain.com/getUserInfo
GET api.domain.com/UpdateUserInfo
REST风格与RPC风格的区别如下。
在几乎所有的情况中,REST是基于HTTP协议而实现的,因此深入了解HTTP协议是非常重要且必要的。对于Web开发人员而言,深入了解HTTP协议将有助于开发者开发出更好、更高质量的Web应用程序。此外,当应用程序出现问题时,也能够很容易地找出问题并解决问题。
超文本传输(Hyper Text Transfer Protocol,HTTP)协议,是互联网上应用最为广泛的一种网络协议,也是基于TCP/IP协议的应用层协议。其中最为常见的浏览网页的过程,就是通过HTTP协议来传递浏览器与服务器之间的请求与响应的,其流程图如图1-1所示。
图1-1 HTTP协议流程图
从图1-1中可以看出,HTTP协议采用了请求/响应模型。当客户端(通常是浏览器)发起一个HTTP请求时,它首先会建立起到HTTP服务器指定端口(HTTP协议默认使用80端口)的TCP连接,而HTTP服务器则负责在该端口监听来自客户端的请求。当TCP连接成功建立后,浏览器就会向HTTP服务器发送请求命令,如GET /index.html HTTP/1.1。一旦收到请求,服务器会根据请求向客户端返回响应,其响应内容通常包括一个状态行(如HTTP/1.1 200 OK)和若干个消息头,以及消息正文。消息正文则是资源、请求的文件、错误或者其他信息等。
HTTP协议采用的是明文传输数据,这种方式并不安全,因此网景公司(Netscape)在1994年设计了HTTPS协议,即超文本传输安全协议(Hypertext Transfer Protocol Secure),也被称为HTTP over TLS,HTTP over SSL,在第8章中将有关于HTTPS协议更详细的介绍。
统一资源定位符(Uniform Resource Locator),即通常所说的URL,代表网络上一个特定的资源。URL作为URI的子集,一个URL就是一个URI,用于标识并定位资源。
对于HTTP而言,当用户在浏览器中输入了一个URL后,意味着他想要获取或查看一些资源。在互联网上,有无穷尽的、各种格式的资源,包括图片、HTML页面、XML、视频、音频、可执行文件和Word文档等,通过URL才能在无数的资源中准确地定位或找到要查看的资源。每一个URL都代表一个不同的资源,因此,要访问HTTP资源,就需要使用URL。例如,当用户想要查看某个公司的网站首页时,就需要在浏览器中输入http://www.….com网址,如果要查看该公司的Logo,则需要输入http://www.….com/images/logo.png网址来获取代表此公司Logo的图片。浏览器会根据用户输入的URL向相应的服务器发送HTTP请求,而服务器会最终将对应的资源返回给客户端,并由浏览器处理后呈现。
对于一个URL,如http://www.….com/images/logo.png,它由以下几个部分组成。
除了上述3个主要部分外,URL还常常包括以下几个可选部分。
由此可见,一个完整的URL形式如下所示。
<protocol>://<host>[:port]/[path][?query][#fragment]
当HTTP服务器对请求返回响应时,它不仅返回资源本身,也会在响应中指明资源的内容类型(Content Type),也称媒体类型。要指定内容类型,HTTP依赖于MIME标准。MIME(Multipurpose Internet Mail Extensions),即多用途互联网邮件扩展类型,是一种表示文档的性质和格式的标准,因此,媒体类型也被称为MIME类型。MIME标准最初用于电子邮件,它用来告诉客户端具体是什么类型的内容。后来,HTTP协议也使用这一标准,并用作同样的目的。浏览器通过MIME类型来决定如何处理文档,因此服务器在返回响应时为资源设置正确的MIME类型非常重要。例如,对于音频、视频文件,只有设置了正确的MIME类型,才能被HTML语言中的<video>或<audio>所识别和播放。
当客户端请求HTML页面时,HTTP服务器会返回HTML内容,并标识其内容类型为text/html,前面text为主类型,后一部分html则是子类型。而当请求一个图片资源时,根据图片文件本身的格式,HTTP服务器将返回资源的媒体类型标记为image/jpeg或image/gif。因此MIME的组成结构非常简单,其语法为type/subtype,它由类型与子类型两个字符串构成,中间用“/”分隔,并且不允许空格存在。MIME类型对大小写不敏感,但是传统写法都是小写的,常见的MIME类型如表1-1所示。
表1-1 常见的MIME类型
类 型 |
描 述 |
典型示例 |
---|---|---|
text |
普通文本 |
text/plain、text/html、text/css、text/javascript |
image |
图片 |
image/gif、image/png、image/jpeg、image/bmp、image/webp |
audio |
音频 |
audio/midi、audio/mpeg、audio/webm、audio/ogg、audio/wav |
video |
视频 |
video/webm、video/ogg |
application |
二进制数据等 |
application/octet-stream、application/vnd.mspowerpoint、application/xml、application/pdf、application/json |
其中比较常用的MIME类型及其意义如下。
前面已提到,HTTP是一个采用请求/响应模式的协议。客户端想要获取资源,就应向服务器发出请求,如果服务器能够正确处理来自客户端的请求,并且拥有客户端所请求的资源,它就能正确地响应,同时将资源返回给客户端。反之,如果客户端发出的请求有问题或者服务器上没有所要请求的资源,那么就无法返回客户端所期望的结果。
这个请求与响应过程如同“对话”一样,服务器与客户端都必须理解对方的“语言”,这正是HTTP消息所要解决的问题。当客户端向服务器发送请求时,应使用HTTP协议规定格式的消息;而服务器也会向客户端返回规定格式的响应,这样客户端才能够理解。HTTP消息正是服务器和客户端之间交换数据的方式,它有两种类型:请求消息和响应消息。
HTTP请求消息和响应消息具有相似的结构,它们都包括以下4部分的内容。
以下是典型的 HTTP 请求和响应的格式。
客户端请求如下:
GET / HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134
Accept-Language: zh-CN
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Accept-Encoding: gzip, deflate
Host: microsoft.com
Connection: Keep-Alive
服务端响应如下:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
其中,HTTP请求是由客户端发出的消息,用于请求服务器执行某个操作,它的起始行包括以下3项。
而HTTP响应的起始行被称作状态行,包含以下3项。
请求的起始行与响应的状态行,在上例中分别为GET / HTTP/1.1和HTTP/1.1 200 OK。
请求的最后一部分是它的正文。注意,并不是所有的请求都有正文,比如获取资源(GET)、获取资源元数据(HEAD),以及删除资源(DELETE)等请求,通常它们不需要正文,而那些要将数据从客户端发送到服务器的HTTP方法,它们若要创建资源或更新资源,就需要提供正文,比如POST和PUT等。
响应的最后一部分也是正文。与请求消息一样,不是所有的响应都有正文,状态码如 201或204等的响应,不包含正文。响应消息的正文通常是所请求资源的表述。
HTTP定义了一组请求方法,以表明要对指定资源执行的操作。每一个请求消息都必须包括一个HTTP方法,该方法将告诉服务器当前请求要执行哪一种操作。常见的HTTP方法有GET、POST、PUT、DELETE、PATCH、HEAD和OPTIONS等。
GET方法的作用是获取指定的资源,它并不会修改资源,因此GET方法是安全的。所谓安全方法,是指不会修改资源的方法。此外,GET方法也是幂等的。所谓幂等,是指多次对同一个URL调用同一个HTTP方法,其效果总是一样的。
POST方法的作用是创建资源。POST方法不是安全方法,因为它会修改服务器上的资源,并且也不是幂等方法,多次请求同一个POST操作会产生多个不同的资源。
PUT方法的作用是更新资源,因为PUT会修改资源,所以它也不是安全的方法。与POST方法不同的是,PUT方法是幂等的,多次更新同一个资源,其返回结果都是一样的。PUT方法除了更新资源外,当资源不存在时,它还可以创建资源。
需要注意的是,尽管POST与PUT方法都可以创建资源,但它们所请求的URI是有区别的,POST请求的URI是资源集合,而PUT则是请求单个不存在的资源,例如:
POST http://api.appdomain.com/users
PUT http://api.appdomain.com/users/1234
DELETE方法的作用是删除资源,它不是安全的,但它是幂等的,这意味着对同一资源请求多次DELETE方法,效果都是一样的。当第一次对资源调用 DELETE方法时,返回表示操作成功的200 OK状态码,后续再调用DELETE方法,由于资源已经不存在,则应返回404 Not Found状态码。
PATCH方法的作用是对资源进行部分更新,它与PUT方法的区别是:PUT会更新指定资源的全部内容,而PATCH可以根据需要仅更新资源的部分字段或属性。
HEAD方法与GET方法相同,但它并不返回消息正文,在响应消息中仅包含响应状态码与消息头,该方法常用来检测资源是否存在以及获取资源的元数据。
OPTIONS方法用于获取资源支持的操作,服务器在返回的响应中会包含Allow消息头,它的值为HTTP方法列表,例如:
Allow: GET, POST
综上所述,常见的HTTP方法总结如表1-2所示。
表1-2 HTTP方法总结
方法名称 |
作 用 |
安 全 |
幂 等 |
---|---|---|---|
GET |
获取资源 |
是 |
是 |
POST |
创建资源 |
否 |
否 |
PUT |
更新指定的资源 |
否 |
是 |
DELETE |
删除指定的资源 |
否 |
是 |
PATCH |
对资源进行部分更新 |
否 |
否 |
HEAD |
与GET方法作用完全一样,但在响应中没有消息正文 |
是 |
是 |
OPTION |
获取指定资源所支持的操作 |
是 |
是 |
客户端和服务器之间的请求消息与响应消息中均包含消息头,用来传递附加信息。一个消息头由消息头名称和它的值组成,中间用冒号“:”隔开,比如Content-Type: text/plain。
每个消息头都有特定的意义,比如上例的消息头用来指明请求或响应消息中正文的内容类型。HTTP请求与响应中均可包含多个消息头。
常见的请求消息头如表1-3所示。
表1-3 常见的请求消息头
消息头 |
说 明 |
示 例 |
---|---|---|
Accept |
可接受的响应内容类型(Content-Types) |
Accept: text/plain |
Accept-Charset |
可接受的字符集 |
Accept-Charset: utf-8 |
Accept-Encoding |
可接受的响应内容编码方式 |
Accept-Encoding: gzip, deflate |
Accept-Language |
可接受的响应内容语言列表 |
Accept-Language: en-US |
Authorization |
用于表示HTTP协议中需要认证资源的认证信息 |
Authorization: Basic OSdjJGRpbjpvcGVuIANlc2SdDE== |
Cache-Control |
用来指定当前请求中是否使用缓存 |
Cache-Control: no-cache |
Connection |
客户端(浏览器)想要优先使用的连接类型 |
Connection: keep-alive |
Cookie |
向服务器提供Cookie |
Cookie: name=value; name2=value2 |
Content-Length |
请求正文的长度 |
Content-Length: 348 |
Content-Type |
请求正文的MIME类型(用于POST和PUT请求中) |
Content-Type: application/json |
Date |
发送该消息的日期和时间 |
Date: Dec, 26 Dec 2015 17:30:00 GMT |
Host |
服务器的主机名以及使用的端口号 |
Host: www.itbilu.com:80 |
If-Match |
仅当客户端提供的值与服务器上对应的值相匹配时,才进行对应的操作 |
If-Match: "9jd00cdj34pss9ejqiw39d82f20d0ikd" |
If-Modified-Since |
允许当请求资源未被修改时,返回304 Not Modified状态码 |
If-Modified-Since: Dec, 26 Dec 2015 17:30:00 GMT |
If-None-Match |
当服务器的任何资源和客户端提供的值不匹配时,服务器端才会返回所请求的资源 |
If-None-Match: "9jd00cdj34pss9ejqiw39d82f20d0ikd" |
If-Unmodified-Since |
仅当资源自某个特定时间以来未被修改时,才发送响应 |
If-Unmodified-Since: Dec, 26 Dec 2015 17:30:00 GMT |
Origin |
用于发起一个跨域资源共享的请求 |
Origin: http://www.domain.com |
Proxy-Authorization |
用于向代理进行认证的认证信息 |
Proxy-Authorization: Basic IOoDZRgDOi0vcGVuIHNlNidJi2== |
User-Agent |
浏览器的身份标识字符串 |
User-Agent: Mozilla/…… |
常见的响应消息头如表1-4所示。
表1-4 常见的响应消息头
消息头 |
说 明 |
示 例 |
---|---|---|
Allow |
用于指明资源支持的有效操作 |
Allow: GET, HEAD |
Cache-Control |
指明该响应使用的缓存机制 |
Cache-Control: max-age=3600 |
Connection |
针对该连接所预期的选项 |
Connection: close |
Content-Encoding |
响应正文所使用的编码类型 |
Content-Encoding: gzip |
Content-Language |
响应正文所使用的语言 |
Content-Language: zh-cn |
Content-Length |
响应正文的长度 |
Content-Length: 348 |
Content-Type |
响应正文的MIME类型 |
Content-Type: text/html; charset=utf-8 |
Date |
消息被发送时的日期和时间 |
Date: Tue, 15 Nov 1994 08:12:31 GMT |
ETag |
表示资源的当前状态的一个标识符 |
ETag: "737060cd8c284d8af7ad3082f209582d" |
Expires |
指定一个时间,超过该时间则认为此响应已经过期 |
Expires: Thu, 01 Dec 1994 16:00:00 GMT |
Last-Modified |
所请求资源的最后修改日期 |
Last-Modified: Dec, 26 Dec 2015 17:30:00 GMT |
Location |
指向另一个URI,用于在进行重定向、成功创建新资源时使用 |
Location: https://localhost:5001/api/authors/1234 |
Proxy-Authenticate |
要求在访问代理时提供身份认证信息 |
Proxy-Authenticate: Basic |
Server |
服务器的名称 |
Server: nginx/1.6.3 |
Set-Cookie |
设置HTTP Cookie |
Set-Cookie: UserID=itbilu; Max-Age=3600; Version=1 |
WWW-Authenticate |
表示请求应使用的认证方式 |
WWW-Authenticate: Basic |
除了标准的HTTP消息头外,一些Web应用程序还会添加自定义消息头,用于返回一些描述或备注类的信息。自定义消息头的名称一般以“X-”开头,以此来指明它并不是一个标准的HTTP消息头,例如X-AspNet-Version用于指明当前服务器运行的ASP.NET的版本。
HTTP响应状态代码由3个数字组成,用于指明HTTP请求的结果。在状态码后会有一个状态文本,它以文字形式简单描述状态的信息,如200 OK、404 Not Found和500 Internal Server Error等。根据其表述意义,状态码可分为以下5类。
状态码以其首位数字表示它所属的类别,而后两位则表示在该类别中具体的信息,表1-5为常见的HTTP状态码。
表1-5 常见的HTTP状态码
状态码 |
状态码名称 |
描 述 |
---|---|---|
200 |
OK |
请求操作成功执行,并且响应正文中包含预期的资源 |
201 |
Created |
资源创建成功,响应正文为空 |
202 |
Accepted |
已接受请求,并成功开始异步执行,但还未处理完成 |
204 |
No Content |
请求的操作成功执行,响应正文为空 |
301 |
Moved Permanently |
请求的资源已被永久移动,响应消息头中应包括资源的新URI,浏览器会自动重定向到新URI |
303 |
See Other |
对当前请求的响应可以在另一个URI上被找到,该URI在当前响应的Location消息头中 |
304 |
Not Modified |
所请求的资源未修改,客户端可以从缓存中得到该资源;服务器返回此状态码时,消息正文不应包含任何内容(与204 No Content一样) |
307 |
Temporary Redirect |
服务端不处理客户端的请求,客户端应向另一个URI请求,该URI在当前响应的Location消息头中 |
400 |
Bad Request |
客户端请求存在错误,如语法错误或请求参数有误,服务器无法理解 |
401 |
Unauthorized |
当前请求要访问受保护资源,但却未向服务器提供正确的认证信息,或并未提供任何认证信息 |
403 |
Forbidden |
当请求受保护资源时,尽管客户端提供了正确的认证信息,但由于权限不够,服务器禁止访问该资源 |
404 |
Not Found |
请求的资源不存在 |
405 |
Method Not Allowed |
请求的资源不支持客户端指定的HTTP请求方法,该响应的消息头中必须包含Allow项,用以表示当前资源能够接受的请求方法列表 |
406 |
Not Acceptable |
服务器不支持请求中指定的资源表述格式(由Accept消息头指定) |
409 |
Conflict |
由于和被请求资源的当前状态之间存在冲突,请求无法完成,冲突通常发生于对PUT方法请求的处理 |
412 |
Precondition Failed |
客户端请求头中指定了一个或多个先决条件,服务器验证这些先决条件失败 |
415 |
Unsupported Media Type |
请求中使用了服务器不支持的资源表述格式(由Content-Type消息头指定) |
500 |
Internal Server Error |
服务器内部错误,无法完成请求 |
503 |
Service Unavailable |
由于临时的服务器维护或者过载,服务器当前无法处理请求 |
REST作为一种架构风格,它不是标准,因此并没有一套确定的、公认的规则。尽管REST包含了6个用于指导设计出RESTful系统的约束,但在具体实现时,在很多细节上仍然会有多种多样的方式。不同的实现方式使系统具有不同的表现或不同的使用方式,因此在实现时,应遵循一些基本原则,也即最佳实践。
首先,在实现RESTful系统时,应正确地使用HTTP方法、HTTP消息头和HTTP状态码。
在上一节我们已经看到,HTTP协议对HTTP方法、消息头、响应码等都有详尽且明确的定义,当设计RESTful API时,应遵循其定义。比如,对于HTTP方法,应使用GET方法获取资源,使用POST方法创建资源。如果没有遵循这些方法的定义,则可能会出现使用POST方法获取、删除资源等情况,这是因为当客户端发送一个POST请求时,服务器上相应的方法不是创建资源,反而是获取或删除资源。
同样,客户端与服务端在进行请求与响应时,应正确地使用HTTP消息头。比如,当客户端要想指定资源的预期表述格式时,应使用Accept消息头,而非其他方式,这也正是该消息头的意义所在。
当服务器向客户端返回响应时,也应正确地使用HTTP状态码。比如,当操作成功却不需要返回响应正文时,应使用返回204 No Content(删除或更新资源)或201 Created(创建资源)。又如,当服务器不支持客户端指定的资源表述格式时,应返回406 Not Acceptable状态码。
由此可以看出,理解HTTP协议并正确地使用它对于设计出规范的RESTful API非常重要。除了这些原则以外,在设计资源的URI时也应注意下列原则。
PUT /users/1234/set-admin
DELETE /users/1234/set-admin
GET /users?role=admin
GET /users?searchQuery=abc
GET /users?pageSize=25&pageNumber=2
在RESTful API中,JSON和XML是最常用到的两种资源表述格式,它们都可以用来传递数据,且都具有简洁、自描述等特点。
JavaScript对象表示法(JavaScript Object Notation,JSON)是一种轻量级的数据交换格式。它采用完全独立于编程语言的文本格式来存储、表示数据,简洁和清晰的层次结构使它成为理想的数据交换格式,不仅易于人阅读和编写,也易于机器解析和生成。JSON的MIME类型为aplication/json。
JSON的语法非常简单,它的数据使用名称/值对来表示,名称/值对包括字段名称和它的值,中间用冒号隔开。其中字段名称应使用双引号表示,字段的值如果是字符串,也应使用双引号表示,如"firstName" : "John"。
JSON数据项的值的类型可以是下列类型。
JSON对象在大括号{}中书写,对象可以包含多个名称/值对。
{
"id": "1234",
"name": "Tom"
}
多个相同的数据项构成了JSON数组,该数组使用方括号[]表示。
{
"users": [
{
"id": "1234",
"name": "Tom"
},
{
"id": "1235",
"name": "Smith"
}
]
}
XML是可扩展标记语言(eXtensible Markup Language),它与HTML语言很相似,包含标签、属性等元素,主要用于传输和存储数据。与HTML不同的是,XML具有非常严格的层次结构,且一个标签必须同时具有起始标签与结束标签,XML中允许创建自定义标签。
XML文档必须包含根元素,该元素是文档中其他元素的父元素。文档中的所有元素形成了一棵文档树,这棵树从根部开始,并扩展到树的最底端。
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
在XML中,每个标签除了必须有起始标签与结束标签外,标签与标签间还必须要正确嵌套,比如以下示例。
<root>
<child>
<subchild>.....</subchild>
</child>
</root>
另外,标签名区分大小写,大小写不同的标签会被认为是不同的标签。标签允许包含一个或多个属性,每个属性的值必须使用引号。
<user id="1234">
<name>Tom</name>
</user>
JSON与XML均有各自的特点。相比XML,JSON要更简洁,且更容易解析。JSON是面向数据的一种格式,因此可以很容易地转换为高级编程语言中的对象,JSON现在已经被广泛地使用在各种数据处理与交互场合下。然而JSON并不支持注释,且扩展性也不如XML。XML是面向文档的一种格式,它支持注释,允许创建自定义标签和属性。这些标签不仅清楚良好地描述了数据,而且还增加了文档的可扩展性,但同时这也增加了XML文档的大小。另外,在表示复杂的层次结构类型的数据时,XML要比JSON更适合。
当API发生了变化,比如资源表述内容有新增项(字段或属性)或系统添加了新资源类型时,应使用不同的版本来区别对API的更改,为RESTful API添加版本有以下4种方式。
第7章将会详细介绍如何为API添加版本功能。
整体来看,本章所有内容都属于理论。通过本章的介绍,我们认识了REST、REST约束和HTTP协议等重要概念,理解并掌握这些基本知识对于开发REST风格的Web应用程序是非常重要的。同时,本章也讨论了人们对REST可能存在的一些错误认识、REST的最佳实践以及RESTful应用中常用到的JSON与XML格式等,这些将帮助我们进一步认识REST并设计出更规范、更灵活的RESTful API应用。
第2章将会介绍.NET Core和ASP.NET Core,ASP.NET Core是微软推出的新一代Web应用开发框架,后续章节将介绍如何基于该平台来创建RESTful API应用。