OData的初步认识,WCF学习之旅

2019-10-07 21:27 来源:未知

本文转自:

 一、什么是REST

       表述性状态转移(Representational State Transfer,REST),不是一种标准,而是一种软件架构风格。

       基于REST的服务与基于SOAP的服务相比,性能、效率和易用性上都更高,而SOAP协议非常的复杂和不透明。REST受到越来越多的Web服务供应商欢迎。目前大部分供应商,如淘宝、腾讯、google、Amazon等都提供REST风格的服务。

 REST的主要原则是:

 1.网络上的所有事物都可被抽象为资源;

 2.每个资源都有一个唯一的资源标识符URI;

 3.使用标准方法操作资源;

 4.所有的操作都是无状态的;

 5.通过缓存来提高性能。

        REST (Representation State Transfer) 描 述了一个架构样式的网络系统,比如Web应用程序。它首次出现在2000年 Roy Fielding 的博士论文中,他是HTTP规范的主 要编写者之一。REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。

       使用REST做为业务逻辑接口是因为,从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得 到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。

        在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个 资源都使用 URI (Universal Resource Identifier) 得到一个惟一的地址。客户端使用的是标准的 HTTP协议进行资 源访问,同时还可以使用标准的HTTP方法,比如 GET、PUT、POST 和 DELETE。

       REST的一个重要原则是系统分层,这表示组件无法了解它与之交互的中间层以外的组件。通过将系统的某些功能限制在某一层,由此可以限制整个系统的复杂性,促进了底层的独立性。

       当 REST 架构的约束条件作为一个整体应用时,将生成一个可以扩展到大量客户端的应用程序。它还降低了客户端和服务器之间的交互延迟。统一界面简化了整个系统架构,改进了子系统之间交互的可见性。REST 简化了客户端和服务器的实现。

       REST的资源表述形式可以是XML、HTML、JSON,或者其他任意的形式,这取决于服务提供商和消费服务的用户。

       但是REST不是万能的。操作无状态也会带来巨大的安全问题,如何授权和验证用户?如果要求每次请求都包含完整的身份和验证信息,又如何避免信息泄漏?复杂的功能挑战架构的易用性,这就需要在性能与功能间权衡,究竟该用REST还是SOAP。  

WebService

What – OData是什么?

二、Rest的优点

  1)缓存,使用 HTTP 向 RESTful 端点申请数据时,用到的 HTTP 动词是 GET。对于 GET 请求响应中返回的资源,可以用多种不同的方式进行缓存。Conditional GET 就是可供选择的一种实现细节,客户端可以向服务验证他的数据是否为最新版本;RESTful 端点可以通过它进一步提高速度和可伸缩性。

  2)扩展,REST 鼓励每项资源包含处理特殊请求所需的所有必要状态。满足这一约束时,RESTful 服务更易于扩展且可以没有状态。

  3)副作用,使用 GET 请求资源,RESTful 服务应该没有副作用(遗憾的是,与其他一些 REST 约束相比,这一约束更容易被打破)。

  4)幂等,统一接口另外两个常用到的主要 HTTP 动词是 PUT 和 DELETE。用户代理想要修改资源时最常使用 PUT,DELETE 可以自我描述。要点(也就是“幂等”一词所强调的)是您可以对特殊资源多次使用这两个动词,效果与首次使用一样——至少不会有任何其他影响。构建可靠的分 布式系统时(即错误、网络故障或延迟可能导致多次执行代码),这一优点可提供保障。

  5)互操作性许,多人将 SOAP 捧为建立客户端-服务器程序最具互操作性的方法。但一些语言和环境至今仍没有 SOAP 工具包。有一些虽然有工具包,但采用的是旧标准,不能保证与使用更新标准的工具包可靠沟通。对于大多数操作,REST 仅要求有 HTTP 库(当然,XML 库通常也很有帮助),它的互操作性肯定强过任何 RCP 技术(包括 SOAP)。

  6)简易性与其他优点相比,这一优点更主观一些,不同的人可能有不同的感受。对我而言,使用 REST 的简易性涉及到代表资源的 URI 和统一接口。作为一名 Web 冲浪高手,我理解在浏览器中输入不同的 URI 可以得到不同的资源(有时也被称为 URI 或 URL 黑客,但绝无恶意)。由于有多年使用 URI 的经验,所以为资源设计 URI 对我来说得心应手。使用统一接口简化了开发过程,因为我不必为每个需要建立的服务构建接口、约定或 API。接口(客户端与我的服务交互的方式)由体系结构约束设置。

 

1. WebService基本认识

WebService最早是微软提出了一种以XML为载体网络信息传输的规范,现在几乎所有的语言与平台都支持,带有状态机制,不依赖于容器,可以发送一个xml作为其请求内容.

WebService通常是基于http的远程方法调用(RMI),号称是可以返回远程对象,一般来说客户端可以象调用本地方法一样调用WebService的方法。

在各种各样的对 WebService的解释版本中, 足够官方, 却并没有太大帮助, 在我理解, WebService 是一种应用程序组件, 提供一个接口, client端 提供参数, 通过多种方式发起请求,获取到相应的返回值, 可以通过WebService进行网络通信, 同时可以实现不同平台间的数据共享, 仅此而已.

参考链接:WebService的两种方式SOAP和REST比较 (转)

不过需要注意的一点是,WebService有两种方式:

  1. SOAP协议方式, 三要素为 SOAP, WSDL, UDDI
  2. REST方式

OData - Open Data Protocol,是一个设计和使用RESTful API的标准。REST本身只是一个构建web服务的思想和理念,其没有规定一个统一的标准来限制开发人员该如何设计RESTful API。其实我们实际开发中的确也没有遵循某个统一的标准去设计WebAPI。因为大多数场景下,遵循一个统一的标准并不是必要的。但在某些场景下,有这样一个标准却能带来很大的好处。

三、WCF如何支持Rest

       WCF如何实现对于Rest支持的呢?弄清这一点是学习Rest WCF的关键。

       为了实现于对Rest的支持,在 .NET Framework 中,WCF 在 System.ServiceModel.Web 组件中新增了编程模型和一些基础架构部件。WCF Web编程模型几个重要类型就是:

   1)  WebGetAttribute 和 WebInvokeAttribute:

       我们知道,在WCF中,对于方法的调用是基于SOAP的Action的,每个客户端发送的SOAP消息都需要指定一个Action 的值。这个Action的值和WCF服务的方法对应。每个WCF服务端的操作都有一个特定的Action。通过 OperationContractAttribute.Action 属性设置。

  在Rest WCF中,基于Action的方法调用转变为了基于URI+Http动词的调用。也就是SOAP Action=URI+Http动词。

  这种映射会由WebHttpDispatchOperationSelector 类型来完成,它会把客户端请求的URI+Http动词,映射到特定的服务方法上。

  WebGetAttribute 告诉服务方法应该响应 HTTP GET 请求。

  WebInvokeAttribute 默认映射为 HTTP POST,但可将WebInvokeAttribute.Method 属性设置为支持所有其他 HTTP 动词(PUT 和 DELETE 等)。例如:

     

    

   [WebGet(UriTemplate = "/Books/Get/{BookId}", BodyStyle = WebMessageBodyStyle.Bare)]
          [OperationContract]
          List<Books> GetBook(string BookId);



        [WebInvoke(Method = "POST", UriTemplate = "/Books/Add", BodyStyle = WebMessageBodyStyle.Bare)]
         [OperationContract]
         Result AddBook(Books book);

   2)  UriTemplate 和 UriTemplateTable:

      UriTemplate 一个表示统一资源标识符 (URI) 模板的类。可以定义服务操作的路径和HTTP动词。

  UriTemplateTable一个表示一组关联 UriTemplate 对象的类。也就是UriTemplate表。

      从上面的例子代码,我们也能看出如何使用UriTemplate 定义服务操作的URI和HTTP动词。

 

      3)   WebHttpBinding 和 WebHttpBehavior:

       WebHttpBinding允许开发人员通过 HTTP 请求(这些请求使用“Plain old XML”(POX) 样式消息,而不是使用基于 SOAP 的消息)来公开 WCF Web 服务,可以很便利的实现REST。

        与其他绑定不同的是:必须使用WebHttpBehavior对服务的终结点进行配置。还要求使用WebGetAttribute或WebInvokeAttribute属性将各个服务操作映射到 URI,同时定义调用和返回结果的消息格式。

 

   WCF Web 编程模型允许开发人员通过 HTTP 请求(这些请求使用朴素的旧的“Plain old XML”(POX) 样式消息,而不是SOAP 的消息)来公开 WCF服务。为了让客户端使用 HTTP 请求与服务进行通信,必须使用附加了 WebHttpBehavior 的 WebHttpBinding 对服务的终结点进行配置。

   WebHttpBehavior 行为与 WebHttpBinding 绑定一起使用时,支持 WCF 公开和访问 Web 样式服务。WebServiceHost 会自动将此行为添加到使用 WebHttpBinding 的终结点。例如:

        

<system.serviceModel>
    <bindings>
       <webHttpBinding>
         <binding name="RestWebBinding">

         </binding>

       </webHttpBinding>

     </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="metadataBehavior">
          <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:8888/BookService/metadata" />
          <serviceDebug includeExceptionDetailInFaults="True" />

        </behavior>
          <behavior name="RestServiceBehavior">

         </behavior>

      </serviceBehaviors>
       <endpointBehaviors>
         <behavior name="RestWebBehavior">
           <!--这里必须设置-->

           <webHttp />

         </behavior>

       </endpointBehaviors>
    </behaviors>

    <services>   

      <service name="SCF.WcfService.BookRestService" behaviorConfiguration="RestServiceBehavior">
         <endpoint address="http://127.0.0.1:8888/" behaviorConfiguration="RestWebBehavior"
                   binding="webHttpBinding" bindingConfiguration="RestWebBinding" contract="SCF.Contracts.IBookRestService">
         </endpoint>
       </service>
    </services>
  </system.serviceModel>

 

   4)WebServiceHost 和 WebServiceHostFactory:

      为了支持Web编程模型,WCF框架提供一个新的宿主类型:WebServiceHost。它是一个 ServiceHost 派生类,它是对WCF Web 编程模型的补充。如果 WebServiceHost 在服务说明中找不到终结点,则它将在服务的基址中自动为 HTTP 和 HTTPS 基址创建一个默认终结点。如果用户已在基址中明确配置终结点,则它不会自动创建终结点。WebServiceHost 会自动配置终结点的绑定,以便在安全虚拟目录中使用时与关联的 Internet 信息服务 (IIS) 安全设置一起使用。

  WebServiceHostFactory在可动态创建WebServiceHost Web宿主实例以响应传入消息的托管宿主环境中提供 WebServiceHost 的实例的工厂。

     

 

1.1 SOAP概述

SOAP(Simple Object Access Protocol), 简单对象访问协议.

简单对象访问协议是交换数据的一种协议规范,是一种轻量的、简单的、基于XML(标准通用标记语言下的一个子集)的协议,它被设计成在WEB上交换结构化的和固化的信息。

SOAP的概念理解:

SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。所有的 SOAP消息都使用 XML 编码。一条 SOAP消息就是一个包含有一个必需的 SOAP 的封装包,一个可选的 SOAP 标头和一个必需的 SOAP 体块的 XML 文档。

具体表现如下:

<!-- 在SOAP的XML消息中,强制使用的 SOAP 的 Envelope 元素是 SOAP 消息的根元素。 -->
<!-- 且SOAP命名空间的使用中, 值始终应为xmlns:soap=http://www.w3.org/2001/12/soap-envelope-->
<?xml version="1.0"?>

<!-- 下面便是SOAP封装包, 将对应的XML消息封装至 soap 命名空间 -->
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
...
Message information goes here
...
</soap:Envelope>

<!--可选的SOAP头-->
<!--可选的 SOAP Header 元素可包含有关 SOAP 消息的应用程序专用信息(比如认证、支付等)。如果 Header 元素被提供,则它必须是 Envelope 元素的第一个子元素。-->

<soap:Header>
<m:Trans
xmlns:m="http://www.w3school.com.cn/transaction/"
soap:mustUnderstand="1">234</m:Trans>
</soap:Header>

<!--必需的 SOAP Body,可包含打算传送到消息最终端点的实际 SOAP 消息。-->
<soap:Body>
<m:GetPrice xmlns:m="http://www.w3school.com.cn/prices">
    <m:Item>Apples</m:Item>
</m:GetPrice>
</soap:Body>

除此之外, 还有 SOAP Fault元素,用于指示错误消息,

<!--实例-->
<!--SOAP请求-->
POST /InStock HTTP/1.1
Host: www.example.org
Content-Type: application/soap+xml; charset=utf-8
Content-Length: nnn

<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">

<soap:Body xmlns:m="http://www.example.org/stock">
    <m:GetStockPrice>
    <m:StockName>IBM</m:StockName>
    </m:GetStockPrice>
</soap:Body>

</soap:Envelope>

<!--SOAP响应-->
HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: nnn

<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">

<soap:Body xmlns:m="http://www.example.org/stock">
    <m:GetStockPriceResponse>
    <m:Price>34.5</m:Price>
    </m:GetStockPriceResponse>
</soap:Body>
</soap:Envelope>

SOAP 通讯协议使用 HTTP 来发送XML 格式的信息,SOAP 把 XML 的使用代码化为请求和响应参数编码模式, 并用HTTP 作传输。

具体地讲, 一个SOAP 方法可以简单地看作遵循SOAP编码规则的HTTP请求和响应, 一个 SOAP终端则可以看作一个基于HTTP 的URL, 它用来识别方法调用的目标。

在我目前的理解中, SOAP可以概括为以下要点:

SOAP消息用XML进行编码, 同时拥有相匹配的编码规则, 便于通信, 解析, 交互;

同时在HTTP上 发送SOAP的语义会被映射到HTTP语义, 且不仅限于HTTP协议, SOAP也可以绑定至TCP/UDP协议上;

通过HTTP发送 XML格式信息, 服务器端 转换解析相应的 信息, 处理并响应.

HTTP + XML = SOAP

OData的理想是, 无论哪个组织构建的RESTful API,只要其符合OData标准。其他组织就可以按照OData标准中定义的方式去使用这个API获取/修改资源。这个可以类比SQL标准之于RDBMS关系。无论什么关系型数据库,如果其声称支持SQL 标准,任何人就可以使用标准SQL查询语句来查询数据。

SOAP优点

  1. 可扩展的。SOAP 无需中断已有的应用程序, SOAP 客户端、 服务器和协议自身都能发展。而且SOAP 能极好地支持中间介质和层次化的体系结构。
  2. 简单的。客户端发送一个请求,调用相应的对象, 然后服务器返回结果。这些消息是XML 格式的,并且封装成符合HTTP 协议的消息。因此,它符合任何路由器、 防火墙或代理服务器的要求。
  3. 完全和厂商无关。SOAP可以相对于平台、 操作系统、 目标模型和编程语言独立实现。另外,传输和语言绑定以及数据编码的参数选择都是由具体的实现决定的。
  4. 与编程语言无关。SOAP 可以使用任何语言来完成,只要客户端发送正确SOAP 请求( 也就是说, 传递一个合适的参数给一个实际的远端服务器)。SOAP 没有对象模型,应用程序可以捆绑在任何对象模型中。

个人思考:

如果想要深入了解 SOAP, 需要了解 相应的 XML封装, 同时对 XML扩展文本标记语言有一定程度的了解. 粗浅的使用, 目前足以;

在看完了XML 和 XML schema 之后, 再对文章进行了一些补充更新, 对SOAP WSDL也有了更清晰的认知. 各种官方的语言 被转化为实际的 代码之后, 理解起来总是更简单的.

标准化的另一个好处:可以将Odata协议实现到一个通用的类库中,通过这个类库去创建和访问RESTful API可以减少开发人员的工作量。官网上有很多这样的组件。

1.2 WSDL概述

WebService的另一大核心:

WSDL(Web Services Description Language) 网络服务描述语言

如同字面意思所描述的那样,WSDL 是这样一个基于 XML 的语言,用于描述 Web Service 及其函数、参数和返回值。因为是基于 XML 的,所以 WSDL 既是机器可阅读的,又是人可阅读的.(XML的自描述性)

以下是几个元素定义,有助于更直观的理解 WSDL:

<portType> web service 执行的操作

下面截取了 WeatherWS.xml的一部分, 以便于对 WSDL 有更直观的理解

<wsdl:portType name="WeatherWSSoap">
    <wsdl:operation name="getRegionDataset">
        <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
            <br /><h3>获得中国省份、直辖市、地区;国家名称(国外)和与之对应的ID</h3><p>输入参数:无,返回数据:DataSet。</p><br />
        </wsdl:documentation>
        <wsdl:input message="tns:getRegionDatasetSoapIn"/>
        <wsdl:output message="tns:getRegionDatasetSoapOut"/>
    </wsdl:operation>
    <wsdl:operation name="getRegionProvince">
        <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
            <br /><h3>获得中国省份、直辖市、地区和与之对应的ID</h3><p>输入参数:无,返回数据:一维字符串数组。</p><br />
        </wsdl:documentation>
        <wsdl:input message="tns:getRegionProvinceSoapIn"/>
        <wsdl:output message="tns:getRegionProvinceSoapOut"/>
    </wsdl:operation>
    <wsdl:operation name="getRegionCountry">
        <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
            <br /><h3>获得国外国家名称和与之对应的ID</h3><p>输入参数:无,返回数据:一维字符串数组。</p><br />
        </wsdl:documentation>
        <wsdl:input message="tns:getRegionCountrySoapIn"/>
        <wsdl:output message="tns:getRegionCountrySoapOut"/>
    </wsdl:operation>
    <wsdl:operation name="getSupportCityDataset">
        <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
            <br /><h3>获得支持的城市/地区名称和与之对应的ID</h3><p>输入参数:theRegionCode = 省市、国家ID或名称,返回数据:DataSet。</p><br />
        </wsdl:documentation>
        <wsdl:input message="tns:getSupportCityDatasetSoapIn"/>
        <wsdl:output message="tns:getSupportCityDatasetSoapOut"/>
    </wsdl:operation>
    <wsdl:operation name="getSupportCityString">
        <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
            <br /><h3>获得支持的城市/地区名称和与之对应的ID</h3><p>输入参数:theRegionCode = 省市、国家ID或名称,返回数据:一维字符串数组。</p><br />
        </wsdl:documentation>
        <wsdl:input message="tns:getSupportCityStringSoapIn"/>
        <wsdl:output message="tns:getSupportCityStringSoapOut"/>
    </wsdl:operation>
    <wsdl:operation name="getWeather">
        <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
            <br /><h3>获得天气预报数据</h3><p>输入参数:城市/地区ID或名称,返回数据:一维字符串数组。</p><br />
        </wsdl:documentation>
        <wsdl:input message="tns:getWeatherSoapIn"/>
        <wsdl:output message="tns:getWeatherSoapOut"/>
    </wsdl:operation>
</wsdl:portType>

它可描述一个 web service、可被执行的操作,以及相关的消息。

<message> web service 使用的消息

<types> web service 使用的数据类型

<binding> web service 使用的通信协议

需要了解更多的话, 官方文档更为详细: WSDL 文档

 

1.3 UDDI概述

UDDI(Universal Description Discovery and Integration), UDDI 是一种目录服务,企业可以使用它对 Web services 进行注册和搜索。

UDDI 指的是通用描述、发现与集成服务

UDDI 是一种用于存储有关 web services 的信息的目录。

UDDI 是一种由 WSDL 描述的 web services 界面的目录。

UDDI 经由 SOAP 进行通信

UDDI 被构建入了微软的 .NET 平台

总结来说:

SOAP定义协议层交互, 映射, WSDL用来描述 WebService, UDDI 用来进行WebService 注册 搜索.

Who - 谁发布了OData?

1.4 REST

该标准由微软发起,前三个版本1.0、2.0、3.0都是微软开放标准。

1.4.1 Rest基本含义

在开始, 仍然是参考链接, 看过后,相信会对 Rest有一个比较全面的了解:

REST简介-Amber-Garden

REST(Representational State Transfer) 表现层状态转移;

REST通常基于使用HTTP,URI,和XML 以及HTML(标准通用标记语言下的一个应用)这些现有的广泛流行的协议和标准。

在阅读上述博文前,可以先了解这样几个概念, 同时也是对Rest的一种描述:

参考:REST无状态的理解

  1. 首先必须清楚的是 Rest是一种软件架构风格而并非是标准;

  2. Rest中并非是向以往的java项目设计那样, 以业务逻辑/功能为考虑出发点, 而是以 资源 为出发点考虑的, 因此很重要的一个特性为 面向资源的接口设计。

  3. 资源: 操作实体都作为资源来看待, 如在一个商城项目中, 商品类别, 具体的商品, 物流, 用户资料 等都会被看做资源,你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。 而 Client端的操作都是对资源本身的操作。

  4. 表现层: 指的是资源的表现层,它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。

  5. 状态: 应用状态(Application State)和资源状态(Resource State):

  • 应用状态:指的是与某一特定请求相关的状态信息
  • 资源状态:则反映了某一存储在服务器端资源在某一时刻的特定状态,该状态不会因为用户请求而改变,任何用户在同一时刻对该资源的请求都会获得这一状态的表现(Representation)。

当用户登录后, 一般需要保存对应的 用户信息, 用户信息本身就是一种资源, 特定的用户 对应着用户资源的一种状态, 而在服务器端取出存储在Session中的用户资源时, 实际上也就意味着 服务器保存着用户资源的 状态信息, 但在 Rest架构设计中, 实际上是期望请求方提供的,所以传递Session ID被认为是unRESTful的做法。

深感自己水平有限, 知识储备不足, 因此仅仅是将REST简介-Amber-Garden博文的要点提出来:

除了上述关于资源状态的概念理解之外,还有以下信息:

  1. Rest架构设计有以下几条约束:

  2. 使用客户/服务器模型。客户和服务器之间通过一个统一的接口来互相通讯。

  3. 层次化的系统。在一个REST系统中,客户端并不会固定地与一个服务器打交道。

  4. 无状态。在一个REST系统中,服务端并不会保存有关客户的任何状态。也就是说,客户端自身负责用户状态的维持,并在每次发送请求时都需要提供足够的信息。

  5. 可缓存。REST系统需要能够恰当地缓存请求,以尽量减少服务端和客户端之间的信息传输,以提高性能。

  6. 统一的接口。一个REST系统需要使用一个统一的接口来完成子系统之间以及服务与用户之间的交互。这使得REST系统中的各个子系统可以独自完成演化。

    其中无状态 和 统一接口 又是 较为特别的约束条件:

    1. 无状态: 理解了状态, 也就明白了什么叫无状态, 在我的理解中, 通俗的来讲就是, 对于一个请求, 请求中已经包含了足够的信息, 并不需要服务器端自身保存任何资源, 用以进行对请求的处理, 资源的返回等。

    2. 统一接口:

      1. 每个资源都拥有一个资源标识。每个资源的资源标识可以用来唯一地标明该资源。

      2. 消息的自描述性。在REST系统中所传递的消息需要能够提供自身如何被处理的足够信息。例如该消息所使用的MIME类型,是否可以被缓存等。

      3. 资源的自描述性。一个REST系统所返回的资源需要能够描述自身,并提供足够的用于操作该资源的信息,如如何对资源进行添加,删除以及修改等操作。也就是说,一个典型的REST服务不需要额外的文档对如何操作资源进行说明。

      4. HATEOAS。即客户只可以通过服务端所返回各结果中所包含的信息来得到下一步操作所需要的信息,如到底是向哪个URL发送请求等。也就是说,一个典型的REST服务不需要额外的文档标示通过哪些URL访问特定类型的资源,而是通过服务端返回的响应来标示到底能在该资源上执行什么样的操作。一个REST服务的客户端也不需要知道任何有关哪里有什么样的资源这种信息。

  7. 除了资源的状态, 定位以外, 则是对资源的操作: 基于HTTP, 则有:

    1. GET 读取某个资源。

    2. DELETE 删除某个资源

    3. POST POST动词会在目标URI之下创建一个新的子资源。

    4. PUT则是根据请求创建或修改特定位置的资源。此时向服务端发送的请求的目标URI需要包含所处理资源的ID.(对于put请求仍然不是很理解)

  8. Rest优点:

  9. 优点是基于特性而来的, 最大的特性便是 无状态。因为无状态原则的特性,让RESTful在分布式系统中得到了广泛的应用,它改善了分布式系统的可见性、可靠性以及可伸缩性,同时有效的降低了Client与Server之间的交互延迟。

  10. 其次,无状态请求有较强的容错性和可伸缩性。如果一台服务器宕机,无状态请求可以透明地交由另一台可用Server来处理,而有状态的请求则会因为存储请求状态信息的Server宕机而承担状态丢失的风险。

至于 Rest架构设计等其他, 在博文中有说明, 且对WebService 影响不大, 在此就不再说明。

 

2. WebService 与 Java

在对WebService 及 SOAP 和 Rest有了基本的了解后, 接下来就是 WebService在Java中的简单实现.

When - 什么时候成为了工业标准?

2.1 SOAP方式

第四个版本4.0于2014年3月17日在OASIS投票通过成为开放工业标准

2.1.1 使用 JDK 方式开发

参考链接: ;

重点在这样几个地方:

  1. 在 接口类和实现类 均需要 @WebService注解, 同时在接口类的方法上, 采用@WebMethod接口

  2. 在 main方法中, 用 EndPoint.publish(address, 实现类); 发布即可

  3. 访问的时候通过 address.wsdl 即可访问发布的 WebService;

  4. 打开命令行窗口,切换到src目录,执行"wsimport -keep address?wsdl生成客户端代码.

  5. 使用生成的java类创建实现类,调用对应的方法 接口.

  6. 注解有以下几种:

  7. 类级别:

    • @javax.jws.WebService(targetNamespace = "", name = "",serviceName = "")

      1. targetNamespace :生成的 WSDL 中使用的名称空间.

      2. name:Web Service 的名称,映射到 WSDL 文件中的

      3. serviceName: Web Service 的服务名,映射到 WSDL 文件

    • @javax.jws.soap.SOAPBinding(parameterStyle = javax.jws.soap.SOAPBinding.ParameterStyle.BARE)

      1. parameterStyle :确定方法参数是否表示整个消息正文,或者参数是否是包装在以操作命名的顶层元素中的元素。默认值:javax.jws.soap.SOAPBinding.ParameterStyle.WRAPPED
  8. 方法级别

    • @javax.jws.WebResult(name = "", targetNamespace = "", partName = "")

      name:指定生成的 WSDL 中的操作结果的名称, 默认名称“return”。

    • @javax.jws.WebMethod(operationName="")

      operationName: 指定方法公开的公共操作名,映射到 WSDL 文件中的

    • @javax.jws.WebParam(name="",targetNamespace="")

      name: 指定输入参数名,而不是该参数的Java;

代码如下:

@WebService
public interface IHelloWorld {

    @WebMethod
    String hello();
}

@WebService
public class HelloWorldImpl implements IHelloWorld {
    @override
    public String hello() {
        return "helloWorld";
    }
}

public class Publish {
    public static void main(String[] args) {

        String address = "http://localhost:80/Server/Test";
        Endpoint.publish(address, new HelloWorldImpl());
        System.out.println("发布成功");
    }
}
//访问地址为:
//http://localhost:80/Server/Test?wsdl

//在命令行中,切换到 Src文件夹下 , wsimport -keep http://localhost:80/Server/Test?wsdl 即可.

public class WebServiceDemo {

    public static void main(String[] args) {

        //创建一个用于产生WebServiceImpl实例的工厂,WebServiceImplService类是wsimport工具生成的
        HelloWorldImplService factory = new HelloWorldImplService();
        //通过工厂生成一个WebServiceImpl实例,WebServiceImpl是wsimport工具生成的
        HelloWorldImpl wsImpl = factory.getHelloWorldImplPort();
        //调用WebService的sayHello方法
        String resResult = wsImpl.hello();
    }
}

通过以上代码, 便实现了一个 WebService的发布, 而后访问对应的网址, 生成对应的Java, 调用并使用相应的方法及接口.

在天气, 电话号码,等等的已经开放的WebService服务在:

如: 访问2400多个城市天气预报Web服务 即可使用:

通过 wsimport -keep url 即可;

当然在执行的时候可能会报错:

无法将名称 's:schema' 解析为 'element declaration' 组件。

这个时候需要在浏览器中 打开相应的 链接, 右键另存到本地,将文件中所有出现

<s:element ref="s:schema"/><s:any/>

的地方全部使用​

 <!--这句代码的意思, 是 定义 任意 子元素出现的总次数-->
 <s:any minOccurs="0" maxOccurs="unbounded"/>

替换掉。

而后使用 wsimport -keep 文件路径即可.

 

2.1.2 使用 cxf搭建WebService项目

在这之前首先要提到 Java 开发WebService的几种框架:

参考链接: JAVA开发Web Service几种框架介绍

Axis,axis2,Xfire,CXF以及JWS, 其中 JWS 为 JavaWebService, 上面所说的 使用 JDK开发 WebService即为 JWS方式, 在 Java 1.6以后支持.

如果你需要把你的实现侧重JAVA并希望和Spring集成,CXF就是更好的选择,特别是把你的Web Service嵌入其他的程序中。

下面开始谈谈 cxf 开发WebService, 个人也是才开始了解 WebService 因此仅仅做出入门级的 使用开发案例.

官方 jar 包下载:

简单流程如下:

  1. 创建 JavaWeb项目

  2. 在创建 接口和实现类的过程 与JWS并没有区别, 不过不需要创建对应的 main方法, 毕竟是与 Spring相结合, 需要在 Web.xml 和 applicationContext.xml中配置即可.

下面是对应的代码:

@WebService
public interface WebServiceDemo {
    //如果没有 WebMethod注解, 且配置属性, 则方法名为 print;
    void print();
}

@WebService
public class WebServiceDemoImpl implements WebServiceDemo {

    @Override
    public void print() {
        // TODO Auto-generated method stub
        System.out.println("print");
    }

}

//applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://cxf.apache.org/jaxws
    http://cxf.apache.org/schemas/jaxws.xsd">

<jaxws:endpoint id="webServiceDemo" implementor="zyx.webservice.demo.WebServiceDemoImpl" address="/webServiceDemo"/>
</beans>

//web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<display-name>WebServiceDemo</display-name>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>CXFService</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>CXFService</servlet-name>
    <url-pattern>/webservice/*</url-pattern>
</servlet-mapping>
</web-app>

项目启动后访问路径为:

http://localhost/WebServiceDemo/webservice/webServiceDemo?wsdl

至此:使用 SOAP方式, 发布一个 WebService项目已经介绍完了.

Why – 为什么需要OData?

2.2 Rest方式

至于Rest的优点, 我就不再赘述, 在 1.4 节的介绍已经足够进行简单的开发了.

Java中 Restful 框架实在不少, 在这里我使用的是: Jersey

已经讲到这里了, 就将 Rest相关注解也讲一下, 有个大概的了解认识:

参考链接: REST 在 Java 中的使用

  1. 资源定位:正如之前提到的, 在统一接口 约束中的 子约束的第一条就是: 每个资源都要有 唯一标识 即 URI.

    @ApplicationPath 标识应用路径,用于由@Path提供的所有资源uri的基本uri。当发布在一个servlet容器中,它的值可以使用web.xml中的servlet-mapping进行重写。

    @path 标识要请求的资源类或类方法的uri路径。

    @PathParam 将uri中指定的路径参数绑定到资源方法参数

    @QueryParam 将http请求的Query参数绑定到资源方法参数,资源类的字段,或资源类的bean属性。

    @FormParam 与上述用法类似,不过是将http请求的Form表单中的参数绑定到资源方法参数

    @CookieParam 同上

    @HeaderParam 指的是 http header的值

    @MatrixParam(不太了解)将uri矩阵参数的值绑定到资源方法参数,资源类的字段

    @DefaultValue 配置以上参数的默认值

    //UserResource下的根路径
    @Path("/user")
        public class UserResource {
        @GET
        //传递参数 /user/user/zyx 则参数为 zyx;
        @Path("/user/{userName}")
        //访问方法的路径
        @Produces(MediaType.APPLICATION_JSON)
        ///user/user/zyx?password=123 则password参数为123
        public User getUser(@DefaultValue("zyx") @PathParam("userName") String userName, @QueryParam("password") String password) {
        ...
        }
    
    }
    

    @BeanParam 将请求中的bean绑定到资源方法参数,资源类的字段,或资源类的bean属性

    //bean的配置
    @javax.xml.bind.annotation.XmlRootElement
    public class UserBean{
        @FormParam("userName")
        private String userName;
        @FormParam("age")
        private int age;
    ...
    }
    //资源方法配置
    @Path("/user")
        public class UserRecource {
        @javax.ws.rs.POST
        @Path("/insertUserBean")
        @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
        public String insertUserBean(@BeanParam UserBean userBean){
        ...
        }
    }
    

    数据格式:

    @Consumes 定义一个资源类的方法或MessageBodyReader能够接受的媒体类型。方法级别的@Consumes会覆盖类级别的@Consumes。(指定接受的 Client端媒体类型)

    @Produces 方法级别的@Produces会覆盖类级别的@Produces(用于定义方法的响应实体的数据类型)

    @Produces("application/json");

    指定多个MIME类型 @Produces({"application/json","application/xml"});

    也可以用常量值得方式指定:

    @Produces(value = {MediaType.APPLICATION_JSON})

    媒体类型的常量值在javax.ws.rs.core.MediaType中

    同时可以为每种类型定义质量因素: 质量因素是取值范围从0到1的小数值。如果不定义质量因素,那么该类型的质量因素默认为1, 如果同一请求路径对应不同的 处理 方法, 则根据 质量因素优先选取

    参考链接: ttp://book.51cto.com/art/201701/529132.htm

    @Encoded 不需要自动解码,直接使用编码后的请求值

代码如下:

在这里我使用的是 maven: pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.com</groupId>
<artifactId>demo02</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>demo02 Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.ws.rs/jsr311-api -->
    <!-- 加了会报错 -->
    <!-- <dependency>
        <groupId>javax.ws.rs</groupId>
        <artifactId>jsr311-api</artifactId>
        <version>1.1.1</version>
    </dependency> -->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>

    <!--jersey -->
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet-core</artifactId>
        <version>2.13</version>
    </dependency>

    <!--JAXB API -->
    <dependency>
        <groupId>javax.xml.ws</groupId>
        <artifactId>jaxws-api</artifactId>
        <version>2.1</version>
    </dependency>

    <!-- Json支持 -->
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-core-asl</artifactId>
        <version>1.9.12</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-mapper-asl</artifactId>
        <version>1.9.12</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-jaxrs</artifactId>
        <version>1.9.12</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.tomcat.maven/tomcat8-maven-plugin -->
    <!-- <dependency>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat8-maven-plugin</artifactId>
        <version>3.0-r1756463</version>
    </dependency> -->

</dependencies>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <display-name>demo02</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <filter>
        <filter-name>characterEncoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>JerseyServlet</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <!-- 配置自己的资源加载类去加载资源 -->
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>cn.com.easy.resource.ApplicationAPI</param-value>
        </init-param>
        <!-- 配置默认的资源包路径,使用默认的配置类去加载资源 -->
        <!-- <init-param> -->
        <!-- <param-name>jersey.config.server.provider.packages</param-name> -->
        <!-- <param-value>com.cisdi.jersey.api</param-value> -->
        <!-- </init-param> -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>JerseyServlet</servlet-name>
        <url-pattern>/restful/*</url-pattern>
    </servlet-mapping>
</web-app>

定义自己的资源加载器

    /**
    * 这个类就是将各种资源加载进来,暴露给client, 可以自由选择需要加载的资源, 日志等;
    * @author Administrator
    *
    */
    public class ApplicationAPI extends ResourceConfig {

        {
            System.out.println("启动资源加载器");
        }

        public ApplicationAPI() {
            register(Demo.class);
            // 注册数据转换器
            register(JacksonJsonProvider.class);

            // 注册日志
            register(LoggingFilter.class);
        }
    }

定义对应的java类, 也即接口的访问程序

    @Path("/demo")
    public class Demo {
        @GET
        @Path("/demo01/{username }")
        @Produces(value = {MediaType.APPLICATION_JSON})
        public String demo01(@PathParam("username") String username) {
            return username;
        }
    }

再者就是 使用 Spring时的自定义配置, applicationContext.xml

    <!-- 自动注册 -->
    <context:annotation-config />

    <!-- 扫描对应的包 -->
    <context:component-scan base-package="cn.com.easy.restful" />

以上就是整个项目的搭建.

我觉得项目的创建并不难, 也并不是在以后应该关注的店, 而在开发中更应该注意的是, Restful的设计风格, 很容易一不小心就走偏了. 需要对 Restful 的 资源, 状态, 等概念理解到位, 这也是我最大的收获.

后感:
个人感觉, SpringMvc 的项目搭建方式与 Restful 方式挺相近, 不过关注点不同, 在 SpringMvc 的核心点 是功能与业务逻辑, 而在Rest中, 更重要的是 资源本身, 甚至于 Get Post 等方式的选取也更重要, 以便于理清楚Rest的 资源逻辑 问题.

OData是一个协议,一个标准。所以这个问题等同于为什么我们需要协议。类比TCP协议就可以理解一般。假设你开发的组件必须要和某个第三方组件通信,如果第三方组件不支持TCP而只支持其内部开发的一个私有协议,你就肯定头大了,你必须在你的组件里单独为其实现这个私有协议。如果大家都支持TCP协议,不就省事了么。这就是标准协议的作用:协议和标准用于制定一个统一通用的规则。 我们只需要按照这个协议或标准生产组件,那么这个组件就可以方便的和其他组件集成/协作。而无须根据其他组件的私有标准定制化组件。

 

前面说到Rest只是一种设计Web服务的思想,不是一种标准化的协议。正由于缺乏标准化,从而导致各家公布的Restful API 统一通用方面的欠缺。OData就是为弥补这种欠缺而被提出来的标准协议。

 

下面全是延伸阅读可略过。

Web服务有两种实现方式,一是SOAP协议方式,二是REST方式。SOAP是一套完整的实现Web服务的解决方案。这里有必要先简单了解SOAP方式的Web服务,然后对比SOAP方式,我们会发现REST方式欠缺了什么。

 

SOAP方式的Web服务中的Web服务描述语言(WSDL)和简单对象访问协议(SOAP)一起构成了SOAP方式下的Web服务的结构单元。客户端通过WSDL可以了解Web服务公开了那些可以被执行的方法以及Web服务可以发送或接收的消息格式(解决了公布访问资源方法的问题)。客户端按照SOAP将调用位于远程系统上的服务所需信息序列化为消息(解决了如何调用远程方法的问题)。注意WSDL描述的服务以及SOAP消息都是符合统一标准的,都是机器可读的.

WSDL基于XML格式,用来描述Web服务。WSDL文档可以看成是客户端和服务器之间的一个协约。使用WSDL工具,你可以自动处理这个过程,几乎不用手工编写代码就能够让应用程序整合新的服务。因此WSDL是Web服务体系结构的基础,因为它提供了一个通用语言,用来描述服务和整合这些服务的平台。

SOAP本身提供了与Web服务交换信息的方法。SOAP是序列化调用位于远程系统上的服务所需信息的标准方法,这些信息可以使用一种远程系统能够读懂的格式通过网络发送到远程系统,而不必关心远程系统运行于何种平台或者使用何种语言编写。SOAP以XML格式提供了一个简单、轻量的用于在分散或分布环境中交换结构化和类型信息的机制。实际上它通过提供一个有标准组件的包模型和在模块中编码数据的机制,定义了一个简单的表示应用程序语义的机制。

 

对照SOAP方式的Web服务,REST中没有用于描述资源(服务)列表,资源元数据的类似于WSDL的东东。所以有人在2009年提出了一个标准WADL去描述REST方式的Web服务,但至今没有被标准化。个人认为使用WSDL/WADL去描述REST方式的Web服务太别扭,这是典型的RPC思路,而REST是一种把服务抽象为资源的架构思想。用描述RPC的WSDL去描述REST方式的Web服务并不合适。我们需要其他策略去代替WSDL实现“公布访问资源方法的问题”。

TAG标签:
版权声明:本文由金沙澳门官网4166发布于文物考古,转载请注明出处:OData的初步认识,WCF学习之旅