The basic principle of using subscriptions and notifications with Exchange Web Services (EWS) is that the Subscribe requests create subscriptions and GetEvents or GetStreamingEvents requests must find their way back to the same Exchange server where the subscriptions reside to get events from them. This post describes how an EWS client ensures this happens in Exchange 2010 vs. Exchange 2013.
This post assumes you have some working knowledge of managing affinity for Exchange Web Services applications with Exchange 2010 and Wave 14 of Exchange Online. For a refresher, I'd suggest watching my talk on Channel9 found here.
This post also assumes you have a general understanding of Subscriptions and Notifications. You can review my presentation on Channel9 here (skip to 13:19) or read this article on MSDN for an overview.
In Exchange 2010 (and W14 of Exchange Online) when an EWS client subscribed for notifications from a given mailbox folder it was the Client Access Server (CAS) that kept track of the active subscriptions. Office 365 and any substantial deployment of Exchange 2010 involved an array of CAS servers front-ended by a load balancer. This meant the EWS app only knew the URL of the load balancer and knew nothing of the servers behind it.
The EWS app used cookies to ensure subscription-related requests went back to the same CAS through the load balancer. The cookie, provided on the first request to a CAS, was sent with subsequent Subscribe and GetStreamingEvents (streaming notifications) or GetEvents (pull notifications) requests and the load balancer was configured to create affinity between the cookie and a given CAS.
The pseudo-code for an EWS app subscribing and getting events from multiple mailboxes looked like this:
- Perform Autodiscover for each mailbox requesting the ExternalEwsUrl (mailbox1, mailbox2, mailbox3, etc.)
- Group the mailboxes by the ExternalEwsUrl
For each group:
- Send a Subscribe request for each mailbox in group to get a SubscriptionId (using ApplicationImpersonation)
- The first Subscribe response will contain a cookie called "exchangecookie", resend this cookie with all subsequent requests in this group.
- Group the SubscriptionIds (breaking down further into groups of 200 ids if needed)
- Send a GetStreamingEvents or GetEvents request for each group of SubscriptionIds (not using ApplicationImpersonation)
- Pass events returned by GetStreamingEvents or GetEvents to a separate thread for processing
With Office 365 W15 and Exchange 2013, changes to the server roles require some additional steps for the EWS app. The key change is that the CAS no longer manages the subscriptions, they are now managed by the Mailbox (MBX) server role. Now the load balancer and the CAS sit between the EWS app and the table of active subscriptions on the MBX server. The CAS does no processing of the EWS requests and just forwards them to the MBX for processing. The load balancer address in Office 365 is also simplified (https://outlook.office365.com/ews/exchange.asmx) and not specific to any site other group boundary in the datacenter. The EWS app now needs to do some work to achieve the same basic objectives: (1) group the mailboxes by site and (2) make sure requests within a group find their way to the server behind the load balancer which knows about the subscription.
To do this given the changes in W15, the EWS app must implement the following logic:
1. Call Autodiscover for each mailbox requesting the ExternalEwsUrl and a new setting called "GroupingInformation" (mailbox1, mailbox2, mailbox3, etc.)
2. Group the mailboxes by a concatenation of ExternalEwsUrl + GroupingInformation
3. For each group:
a. Send a Subscribe request for each mailbox in group to get a SubscriptionId (using ApplicationImpersonation)
i. Add a header to this request called "X-AnchorMailbox" with a value set the SMTP address of the first mailbox in the group
ii. Add a header to this request called "X-PreferServerAffinity" with a value set to "true"
iii. The first Subscribe response will contain a cookie called "X-BackEndOverrideCookie", resend this cookie with all subsequent requests in this group
b. Group the SubscriptionIds (breaking down further into groups of 200 ids if needed)
c. Send a GetStreamingEvents or GetEvents request for each group of SubscriptionIds (not using ApplicationImpersonation)
i. Add the "X-PreferServerAffinity" header set to "true" and the " X-BackEndOverrideCookie" to these requests as well
4. Pass events returned by GetStreamingEvents or GetEvents to a separate thread for processing
Initially, the X-AnchorMailbox header is what directs the CAS to send the request to a specific MBX server. The response to that initial request has a cookie called "X-BackEndOverrideCookie", this cookie works similar to the "exchangecookie" in W14 in that it can be used on subsequent requests to ensure they find their way back to the same MBX server. However, it also requires at the X-PreferServerAffinity HTTP header. This forces the client to be very intentional about creating affinity to a particular MBX server. That is because (unlike in W14) the only time affinity really should be managed by the client is subscription related requests like Subscribe, GetStreamingEvents, GetEvents, and Unsubscribe. Other requests like SyncFolderItems, FindItem, GetItem, etc. should not set X-PreferServerAffinity or return the X-BackEndOverrideCookie.***
|***Updated 7/17/2013: For the most part the above is still true but if you are using Exchange Impersonation this post explains why/how you might also want to manage affinity.|