Exchange Web Service(EWS) 协议同步邮件

Exchange Web Service(EWS) 协议

EWS是微软实现的一种客户端和服务器之间的交换信息的协议。Exchange Server提供了包括从电子邮件、会议安排、团体日程管理、任务管理、文档管理、实时会议和工作流等丰富的协作应用。

EWS基于标准的web service, 使用HTTP+XML+SOAP来传输消息的。一条 SOAP 消息就是一个普通的 XML 文档,包含下列元素:

  • 必需的 Envelope 元素,可把此 XML 文档标识为一条 SOAP 消息
  • 可选的 Header 元素,包含头部信息
  • 必需的 Body 元素,包含所有的调用和响应信息
  • 可选的 Fault 元素所发生错误的信息,提供有关在处理此消息

EWS的SOAP消息,Header中指明使用的EWS版本信息:

<s:Header>
    <t:RequestServerVersion s:mustUnderstand="1" Version="Exchange2010_SP1"/>
s:Header>

Body中是真正的命令信息,指明要调用的接口和使用的参数:

<s:Body>
    <m:GetFolder>
        <m:FolderShape>
            <t:BaseShape>IdOnlyt:BaseShape>
            <t:AdditionalProperties>
                <t:FieldURI FieldURI="folder:DistinguishedFolderId"/>
            t:AdditionalProperties>
        m:FolderShape>
        <m:FolderIds>
            <t:DistinguishedFolderId Id="deleteditems"/>
        m:FolderIds>
    m:GetFolder>
s:Body>

当请求成功是,响应的内容也是放在Body当中:

<s:Body>
 <m:GetFolderResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
        <m:ResponseMessages>
            <m:GetFolderResponseMessage ResponseClass="Success">
                <m:ResponseCode>NoErrorm:ResponseCode>
                <m:Folders>
                    <t:Folder>
                        <t:FolderId Id="AQMkADAwATMwMAItZDAyNi05ODYxLTAwAi0wMAoALgAAA428HhUZWjFJpUSRJeFqnBwBAHQq8eP3dM1JkMChdMfyLSgAAAIBCgAAAA==" ChangeKey="AQAAABYAAAB0KvHj93TNSZDAoXTH8i0oAAB17L8t" />
                    t:Folder>
                m:Folders>
            m:GetFolderResponseMessage>
        m:ResponseMessages>
    m:GetFolderResponse>
s:Body>

EWS有多个实现版本,每个版本支持的请求都描述在以下三个文件中:

  • services.wsdl : 定义了客户端和服务器之间支持的消息协议
  • Messages.xds: 定义了SOAP消息请求和响应的信息类型
  • Types.xds:定义了SOAP消息使用的数据类型

要访问EWS服务器, 可以用这三个文件自动生成soap访问接口,但是生成的接口可读性比较差,使用起来很不方便。所以在我的项目中,我直接拼装SOAP消息来构建我的请求和解析服务器的响应。

在邮箱客户端中, 主要涉及到的操作如下表所示:

folder操作 item操作
FindFolder operation CopyItem operation
GetFolder operation CreateItem operation
CreateFolder operation FindItem operation
UpdateFolder operation GetItem operation
DeleteFolder operation MoveItem operation
SyncFolderHierar operation -
- SyncFolderItems operation
- SendItem operation
- UpdateItem operation
- GetItem operation

可以看到,EWS支持对邮件folder和邮件对象的增删改查,还支持对folder和item的增量同步。这几个操作是邮箱功能的核心操作,从EWS一开始的版本就开始支持,并不存在版本兼容性的问题。

EWS对每一个folder和item对象都维护了一个changeKey,来表示对象的版本。因此,可以通过changeKey是否变化来判断folder或者item是否发生了改变。EWS为了可以方便的让客户端知道服务器有哪些对象发生了改变,对folder提供了SyncFolderHierar接口用于增量同步folder,对item提供了SyncFolderItems用于增量同步item。这两个增量同步的接口使用了SyncState用于标识同步状态, 如果服务器上的数据发生了改变, 则在响应只返回变更的对象,并将最新的SyncState下发给客户端。下面是这两个命令的具体请求和返回数据示例:

SyncFolderHierar Request


<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
  <soap:Body>
    <SyncFolderHierarchy  xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
      <FolderShape>
        <t:BaseShape>AllPropertiest:BaseShape>
      FolderShape>
      <SyncState>H4sIA=SyncState>
    SyncFolderHierarchy>
  soap:Body>
soap:Envelope>

SyncFolderHierar Response


<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Header>
    <t:ServerVersionInfo MajorVersion="8" MinorVersion="0" 
                         MajorBuildNumber="628" MinorBuildNumber="0" 
                         xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" />
  soap:Header>
  <soap:Body>
    <SyncFolderHierarchyResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" 
                                 xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" 
                                 xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
      <m:ResponseMessages>
        <m:SyncFolderHierarchyResponseMessage ResponseClass="Success">
          <m:ResponseCode>NoErrorm:ResponseCode>
          <m:SyncState>H4sIAAA==m:SyncState>
          <m:IncludesLastFolderInRange>truem:IncludesLastFolderInRange>
          <m:Changes>
            <t:Create>
              <t:Folder>
                <t:FolderId Id="AQApAHR=" ChangeKey="AQAAABY" />
                <t:ParentFolderId Id="AQApA=" ChangeKey="AQAAAA==" />
                <t:FolderClass>IPF.Notet:FolderClass>
                <t:DisplayName>NewFoldert:DisplayName>
                <t:TotalCount>0t:TotalCount>
                <t:ChildFolderCount>0t:ChildFolderCount>
                <t:UnreadCount>0t:UnreadCount>
              t:Folder>
            t:Create>
          m:Changes>
        m:SyncFolderHierarchyResponseMessage>
      m:ResponseMessages>
    SyncFolderHierarchyResponse>
  soap:Body>
soap:Envelope>

其中IncludesLastFolderInRange节点是告知是否已经返回了所有的变更信息,如果没有,则需要再用新的SyncState再请求多一次,直到IncludesLastFolderInRange节点为true。

SyncFolderItems Request


<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
    <s:Header>
        <t:RequestServerVersion s:mustUnderstand="1" Version="Exchange2010_SP1" />
    s:Header>
    <s:Body>
        <m:SyncFolderItems>
            <m:ItemShape>
                <t:BaseShape>IdOnlyt:BaseShape>
                <t:IncludeMimeContent>falset:IncludeMimeContent>
                <t:AdditionalProperties>
                    <t:FieldURI FieldURI="item:DateTimeSent" />
                t:AdditionalProperties>
            m:ItemShape>
            <m:SyncFolderId>
                <t:FolderId Id="AQMkADAwATMwMAItZDAyNi05ODYxLTAwAi0wMAoALgAAA428HhUZWjFJpUSRJeFqnBwBAHQq8eP3dM1JkMChdMfyLSgAAAIBCQAAAA==" />
            m:SyncFolderId>
            <m:SyncState>H4sIAAAAAAAEAGNgYGcAAotqE0tHE2NTA0ddZ3NHC10TR2djXSdnJ2ddNyMnC2cnczdLU1OD2vBgveDKvOTgksSSVOfEvMSiSgYr0nW65eekpBZ5pjBYkq43LLWoODM/j8GaaK3+QMuKS4JSk1Mzy1JTQjJzU0nwrU9icYlnXnFJYl5yqncqKb71zS9K9SxJzS32zwtOLSpLLSLByXDfhgNxUW5iUTYklrgYGISA0tDwAxkOUskgCJQyAGI9kJrePXKiklGGnktdJqo+zJojw8hQovXx8feSs54TDiwsOf5JVwOkipETiBn4GJhBHG6GoCfHOrpTrngwCAFFeYEYaN1aRgYGX8cAT19HP5AiBjdTtzCQ8gAgVmVAgH9AnIvELwXiTgZMkA00zwyJb2BgYAzEBropBkZmupYWZoa6YK4BEsBwOxMDAwsDg/MDZnZn86B9BwJYpFr185nl2yrt43452fdfahanntrERWEWDm:SyncState>
            <m:MaxChangesReturned>20m:MaxChangesReturned>
        m:SyncFolderItems>
    s:Body>
s:Envelope>

SyncFolderItems Response


<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
        <h:ServerVersionInfo xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" MajorVersion="15" MinorVersion="1" MajorBuildNumber="947" MinorBuildNumber="19" Version="V2017_01_07" />
    s:Header>
    <s:Body>
        <m:SyncFolderItemsResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
            <m:ResponseMessages>
                <m:SyncFolderItemsResponseMessage ResponseClass="Success">
                    <m:ResponseCode>NoErrorm:ResponseCode>
                    <m:SyncState>H4sIAAAAAAAEAGNgYGcAAotqE0tHE2NTA0ddZ3NHC10TR2djXSdnJ2ddNyMnC2cnczdLU1OD2vBgveDKvOTgksSSVOfEvMSiSgYr0nW65eekpBZ5pjBYkq43LLWoODM/j8GaaK3+QMuKS4JSk1Mzy1JTQjJzU0nwrU9icYlnXnFJYl5yqncqKb71zS9K9SxJzS32zwtOLSpLLSLByXDfhgNxUW5iUTYklrgYGISA0tDwAxkOUskgCJQyAGI9kJrePXKiklGGnktdJqo+zJojw8hQovXx8feSs54TDiwsOf5JVwOkipETiBn4GJhBHG6GoCfHOrpTrngwCAFFeYEYaN1aRgYGX8cAT19HP5AiBjdTtzCQ8gAgVmVAgH9AnIvELwXiTgZMkA00zwyJb2BgYAzEBropBkZmupYWZoa6YK4BEsBwOxMDAwsDg/MDZnZn86B9BwJYpFr185nlwxyDzC0CWBQuJ2YxK6W7BvHwBzCretwLausPYLZeEhJ0/EQAs8s9saBbtwNY3DZHvGH2U4oIEpUMYA58eDzI1S2AOWTZsiA3zwDm8HtNQU6uAcwRLPeCGpsCmKPcHgd9+h4QgM0hzNx7vwU9egK0YZOnkxdTAJP93qAVf1cCrXZ5X+50lwlo1VG/X8x+5hlOrauAVv2ocZJiAlq1b7fTOi2gVd+2OjEB6QhZU6elQPGoBHenGasYF2G3LAgUaSVT3HcGMNwLYd+tVs/hO1n/X9VcpR972cDBychQzMDQAADqPT+hzwMAAA==m:SyncState>
                    <m:IncludesLastItemInRange>truem:IncludesLastItemInRange>
                    <m:Changes>
                        <t:Create>
                            <t:MeetingRequest>
                                <t:ItemId Id="AQMkADAwATMwMAItZDAyNi05ODYxLTAwAi0wMAoARgAAA428HhUZWjFJpUSRJeFqnBwHAHQq8eP3dM1JkMChdMfyLSgAAAIBCQAAAHQq8eP3dM1JkMChdMfyLSgAAAAHQze+AAAA" ChangeKey="CwAAABYAAAB0KvHj93TNSZDAoXTH8i0oAAALvfbi" />
                                <t:DateTimeSent>2016-09-26T04:36:53Zt:DateTimeSent>
                            t:MeetingRequest>
                        t:Create>
                        <t:Update>
                            <t:MeetingRequest>
                                <t:ItemId Id="AQMkADAwATMwMAItZDAyNi05ODYxLTAwAi0wMAoARgAAA428HhUZWjFJpUSRJeFqnBwHAHQq8eP3dM1JkMChdMfyLSgAAAIBCQAAAHQq8eP3dM1JkMChdMfyLSgAAAAHQze/AAAA" ChangeKey="CwAAABYAAAB0KvHj93TNSZDAoXTH8i0oAAALvfbj" />
                                <t:DateTimeSent>2016-09-26T04:38:57Zt:DateTimeSent>
                            t:MeetingRequest>
                        t:Update>
                        <t:Delete>
                            <t:MeetingRequest>
                                <t:ItemId Id="AQMkADAwATMwMAItZDAyNi05ODYxLTAwAi0wMAoARgAAA428HhUZWjFJpUSRJeFqnBwHAHQq8eP3dM1JkMChdMfyLSgAAAIBCQAAAHQq8eP3dM1JkMChdMfyLSgAAABaRuP3AAAA"" ChangeKey="CQAAABYAAAB0KvHj93TNSZDAoXTH8i0oAABaYEei" />
                            t:MeetingRequest>
                        t:Delete>
                    m:Changes>
                m:SyncFolderItemsResponseMessage>
            m:ResponseMessages>
        m:SyncFolderItemsResponse>
    s:Body>
s:Envelope>

其中IncludesLastItemInRange节点是告知是否已经返回了所有的变更信息,如果没有,则需要再用新的SyncState再请求多一次,直到IncludesLastFolderInRange节点为true。

使用这两个同步的接口,就可以方便的获取到服务器变更的内容。
理论上,同步folder中的邮件列表,使用SyncFolderItems这个命令就足够了,在将SyncState更新到最新状态时,客户端已经同步到了folder中所有的邮件的id,之后便可以拉取所有的邮件的摘要了。但是在移动客户端中,下载所有的邮件并不是非常理智的行为,非常耗费流量和存储空间。因此在移动客户端中,初始化folder时对邮件的同步数目需要进行限制。为了解决这个问题,在移动客户端中,对邮件里列表的同步分为一下三个操作:

  • 初始化阶段:获取最新的SyncState,对这个过程中返回的所有邮件id截取最新的n个id进行邮件下载,存储。
  • 下拉更新阶段:使用当前存储的SyncState拉取到所有变化的邮件id,全部进行下载存储。
  • 加载更多阶段:用当前存储的最旧邮件的发送时间time1,使用findItem命令搜索不晚于time1时间发送的最近n封邮件邮件id,进行下载存储。

结合SyncFolderItems和findItem两个请求,就可以不需要在初始化邮件列表时中同步所有邮件,但是又可以支持加载旧邮件的需求。


你可能感兴趣的:(邮箱协议)