Introducing WOPI

This blog post describes how to create a custom WOPI host. A WOPI host is document storage location that can connect to Office Web Apps Server to open Office documents in the browser. When the WOPI host is configured to use Office Web Apps Server, users are able to view and edit documents stored on the WOPI host by using Office Web Apps. Tyler Butler, Program Manager, Office Shared Experiences team, authored today’s post.

One of the great things about the Office Web Apps is that they let people work with their documents anywhere, on any computer. It turns out that there are lots of documents in lots of different places. For example, consider Outlook.com, or more generally, web-based email. You probably get documents sent to you via email every so often, and you probably want to open those documents to read them. One option is to download the file and open it in an application on your local computer. But if you're really just wanting to quickly check out the contents of the document, wouldn't it be easier if you could just click the document and have it open immediately in the browser? Wouldn't it be cool if that worked regardless of what software you had installed on your local computer? It could work when you're traveling and using an internet cafe computer, when you're using a friend's computer, or when you're at a conference and using a shared computer there.

With the most recent release of the Office Web Apps, developers can enable such scenarios in their apps directly by implementing the Web Application Open Platform Interface (WOPI). WOPI provides web-based services a way to view and edit documents in the Office Web Apps, with all the high-fidelity and richness that you expect from an Office document. While email attachments are a great example, there are plenty of other places where documents are used on the web -- bank statements, file-synchronization services like OneDrive and Dropbox, etc. -- and there are compelling ways developers can leverage WOPI to treat those documents as something more than just a file that users download.

So if you’re a developer, this post is for you. Simply put, WOPI is the glue that connects the web apps to your documents, and it's an interface that you can implement yourself -- to dazzling effect. In this post series I'll talk about what WOPI is, walk through the basics of implementing it, and show you an example implementation.

In addition to this post, WOPI is documented in the [MS-WOPI] protocol document. That document is the authoritative documentation on the interface.

Fundamentals

To better understand what WOPI is conceptually, consider this: Have you ever opened a document on your computer by double-clicking the file and watch it open in Word? When you did that, you signaled the operating system that you wanted to open the file, so the OS figured out what application was associated with that file type, then passed the file over to the application to handle. That's a very basic description of the process, but conceptually that's what happens.

WOPI provides similar semantics for web applications, but the web is a little different than your local hard drive, so the process of opening the file is also a little different. In my simplified example above, the document was stored on your hard drive, and the operating system handled passing the file off to the application to open. In the scenarios for which WOPI was designed, though, the document is stored somewhere on the web. We call those document storage locations WOPI hosts or WOPI servers. This term will become a little clearer in just a moment, but think of a WOPI host as a service like OneDrive. It's a place where you can put your documents or other files and make them accessible on the web.

If a WOPI host is a place where your files are stored, then what about the actual applications that open those files? We unsurprisingly call those WOPI applications or WOPI clients; the Word/Excel/PowerPoint/OneNote web apps are all examples of WOPI applications. These applications support various different actions, such as view or edit, and these actions can apply to different file types.

Going back to my original example of the document on your hard drive, the WOPI host is kind of like the hard drive -- but a little smarter. It stores the documents. The WOPI application is the app capable of performing an action -- doing something, in other words -- on those files, and WOPI is the interface that those two entities use to talk to one another. If you're a service or application that stores documents, then you can implement WOPI, making yourself a WOPI host, and WOPI applications such as the Office Web Apps can then consume the documents you're storing. Pretty compelling, huh? The rest of this post will go into the details of the interface.

WOPI itself is more or less a callback interface that operates over REST. As a WOPI host, you expose a number of endpoints, in the form of URLs, and then you load a WOPI application, passing it the URL to your endpoint. The WOPI application then calls your endpoint -- again, this is REST, so the application issues an HTTP request to the URL you passed it earlier -- and you respond appropriately based on the information that the WOPI application sent you via its request. WOPI defines a number of different endpoints that you can expose. Many of these are optional and necessary only if you wish to enable things like editing. At a minimum you'll need to implement endpoints for CheckFileInfo and GetFile, which will enable the simplest WOPI action, view. We'll talk about each of those endpoints in a moment.

The overall flow of a WOPI conversation looks like this:

Figure 1. WOPI conversation flow

Intro_WOPI

Host Requirements

As you can see in the diagram, as a WOPI host you need to provide some information about the files you have as well as the file itself. Since WOPI is a REST-based callback interface you provide that information via specific URLs. In addition, you need to provide ways to identify files, users and permissions. Essentially, you're providing a small REST API around your files, which a WOPI application will then use to work with those files. Luckily, this is simpler than it might seem at first. The basic things you need to provide as a WOPI host are as follows:

1. File IDs

The files you're storing must have a unique identifier that you can use to find the file when the WOPI application asks you for it. This ID is passed in URLs, so it should be a URL-safe string.

2. Access tokens

You need to provide access tokens that represent users and their permissions within your system. The WOPI application doesn't know what permissions a user has; as the storer of the file, you, the WOPI host, are the authority on that. Thus, you need to provide a token that the WOPI application will then pass back to you. When you get the token, you need to validate it and respond appropriately if the token is invalid. While not strictly required, the WOPI spec recommends that the token be scoped to a single user and document and expire after a certain period of time. The specific format and recommendations about how WOPI hosts should handle access tokens is covered in [MS-WOPI] section 2.2.2.

3. URLs for file access

WOPI requires that a WOPI host expose at least two specific URLs: one to retrieve information about and manipulate files, and a second to retrieve and potentially update the contents of a file. (There are also some additional optional endpoints for manipulating folders and their contents, but those are required only for certain actions.) The URLs have some requirements around their structure which is covered in detail in [MS-WOPI] section 3.3.5.

File IDs are pretty straightforward; you probably already have them. Access tokens might be a little trickier but they're similarly straightforward to implement if you need to. Providing URLs for file access is probably the requirement most likely to be a minor hurdle, especially if you're not currently using a REST or REST-like architecture to expose your files. Fortunately, WOPI's needs are relatively small, and implementing REST endpoints boils down to exposing some URLs, so it's not that difficult in the end.

Let's talk about the two core URL endpoints that you'll need to expose in order to implement view, the simplest WOPI action.

CheckFileInfo

See also [MS-WOPI] section 3.3.5.1.1

Simply put, CheckFileInfo is how the WOPI application gets information about the file and the permissions a user has on the file. It should have a URL that looks like this:

https://server/<...>/wopi*/files/<id>?access_token=<token>

This is the primary URL a WOPI application will call to retrieve information about a file. You'll always receive a file ID in the URL, so each of the operations that the application may be requesting you to perform are file-specific. CheckFileInfo is required for all WOPI actions.

The WOPI application expects a JSON object in the response body. This JSON response can contain a number of optional values, all covered in [MS-WOPI] section 3.3.5.1.1.2, but it needs to include at least BaseFileName, OwnerId, Size, SHA256, and Version.

For example, here's a valid response body to a CheckFileInfo request:

 { "BaseFileName": "Sample Document.docx", "OwnerId": "tylerbutler", "Size": 300519, "SHA256": "+17lwXXN0TMwtVJVs4Ll+gDHEIO06l+hXK6zWTUiYms=", "Version": "GIYDCMRNGEYC2MJREAZDCORQGA5DKNZOGIZTQMBQGAVTAMB2GAYA====" } 

As you can see, we've gotten a CheckFileInfo request for the 'Sample Document.docx' file, and we've responded with some information about that file. As I mentioned, there are a variety of other values that the CheckFileInfo response can contain, all covered in [MS-WOPI] section 3.3.5.1.1.2.

GetFile

See also [MS-WOPI] section 3.3.5.3.1

While CheckFileInfo provides information about a file, GetFile returns the file itself. It should have a URL that looks like this:

 https://server/<...>/wopi*/files/<id>/contents?access_token=<token>

You'll notice it looks very similar to the CheckFileInfo URL, but the /contents portion indicates that the content of the file is being requested, not information about it. When you receive a GetFile request, your response should include the binary contents of the file requested in your HTTP response body (after you've validated the access token and permissions, of course).

Once you've implemented CheckFileInfo and GetFile endpoints, a WOPI application can talk to you to retrieve information about the files you have and get the file contents if needed. These two REST endpoints are all that is required to implement the WOPI view action, but there are additional endpoints you may wish to implement in order to support additional actions.

At this point, you've made it possible for a WOPI application to request files from you; next, we need to actually instantiate the WOPI application.

Loading a WOPI Application

In order to actually view one of the files that we've exposed, we need to tell the WOPI application, "Please load the file with this ID using this access token." We do that by loading an action-specific URL on the WOPI application server. For example, a URL to view a Word document might look like this:

 https://wopi-app-server.contoso.com/wv/wordviewerframe.aspx?WOPISrc=
http%3A%2F%2Fmy-wopi-host%2Flocal%2Fwopi
%2Ffiles%2F1-Sample%2520Document.docx&access_token=
dc172034-c6f9-4a43-bc3f-d80dd93c1de1

NOTE: Throughout this blog post we wrap long URLs on to more than one line in order to fit them into the viewing area.

We'll talk about how we actually determine what the URL for a given action is in just a moment, but for now let's just examine the query string parameters. You can see that we pass in two query string parameters, WOPISrc and access_token. We've talked about access_token already; it's the token that represents the user requesting the file. WOPISrc, on the other hand, is new. The parameter is the "WOPI source" of the file being loaded. In other words, this parameter is how we tell the WOPI application how to find the file that we want to view. The parameter is URL encoded, so let's decode it so we can see what it looks like:

 https://my-wopi-host/local/wopi/files/1-Sample%20Document.docx

Hmmm, that URL looks an awful lot like the CheckFileInfo URL. In fact, it is the CheckFileInfo URL for the file (without the access token, of course). Using that URL, the WOPI application can determine the URLs for GetFile or other calls that it may need to make. Precise specifications for the WOPISrc and access_token parameters can be found in the [MS-WOPI] document section 3.1.5.1.1.2.3.3.

So, to recap, in order to load a WOPI application, we load a URL corresponding to the WOPI action we want to perform, pass the WOPISrc and access_token parameters, and the application calls us back, passing us the file ID and access tokens as needed. We validate the access tokens and respond to the requests made by the WOPI application, and it loads our document. Cool!

There's still one missing piece though. How do we know what the URLs are for a given WOPI action?

WOPI Discovery

As I mentioned, since WOPI is a REST-based interface, the communication between the host and the app is all done via HTTP requests to specific URLs. However, how do you know what the URLs to use are? Simple -- you ask the application server itself! Every WOPI application server exposes a single URL, called the discovery URL, that provides an XML document describing the actions, document types, and URLs it understands. As a WOPI host, you retrieve this XML document and use it to determine the URLs you need to talk to in order to perform actions in addition to what actions are supported by the application server and what file types those actions apply to. We refer to this process as WOPI discovery, and it's meant to be a one-time thing. It's easiest to think of discovery as a configuration step; when you are configuring your WOPI host to talk to a new WOPI application, you'll run discovery. As a WOPI host, you'll retrieve the discovery XML once, process it, then you shouldn't need it again.

The discovery XML looks like this (the URL in urlsrc is shown on 2 lines in order to fit on the page):

 <?xml version="1.0" encoding="utf-8"?> <wopi-discovery> <net-zone name="external-https"> <app name="Word" favIconUrl="https://wopi-app-server.contoso.com/wv/
 resources/1033/FavIcon_Word.ico" checkLicense="true"> <action name="view" ext="doc" default="true" urlsrc="https://wopi-app-server.contoso.com/
 wv/wordviewerframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> <action name="view" ext="docm" default="true" urlsrc="https://wopi-app-server.contoso.com/
 wv/wordviewerframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> <action name="view" ext="docx" default="true" urlsrc="https://wopi-app-server.contoso.com/
 wv/wordviewerframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> <action name="view" ext="dot" default="true" urlsrc="https://wopi-app-server.contoso.com/
 wv/wordviewerframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> <action name="view" ext="dotm" default="true" urlsrc="https://wopi-app-server.contoso.com/
 wv/wordviewerframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> <action name="view" ext="dotx" default="true" urlsrc="https://wopi-app-server.contoso.com/
 wv/wordviewerframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> <action name="view" ext="odt" default="true" urlsrc="https://wopi-app-server.contoso.com/
 wv/wordviewerframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> <action name="edit" ext="docm" requires="locks,cobalt,update" urlsrc="https://wopi-app-server.contoso.com/
 we/wordeditorframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> <action name="edit" ext="docx" requires="locks,cobalt,update" urlsrc="https://wopi-app-server.contoso.com/
 we/wordeditorframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> <action name="edit" ext="odt" requires="locks,cobalt,update" urlsrc="https://wopi-app-server.contoso.com/
 we/wordeditorframe.aspx? <ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>"/> </app> </net-zone> </wopi-discovery> 

As you can see, this example discovery XML defines an app (Word) that can perform two actions (view and edit) on several different document types, defined by their file extensions. For each of the actions, the urlsrc attribute defines the URL that should be used to invoke the action on a given file. This URL has a number of optional query string parameters that can be included, denoted by angle brackets (< and >). Precise instructions for parsing the urlsrc attribute and replacing the placeholder values, as well as the meaning of each of the placeholder values, is available in [MS-WOPI] section 3.1.5.1.1.2.3.3.

In addition to the optional parameters, you must also append the WOPISrc and access_token query string parameters that we discussed earlier to the URL in order to correctly invoke the WOPI action. Looking at the sample discovery XML above, you'll notice that the URL for the view action on the docx file extension is this:

 https://wopi-app-server.contoso.com/wv/wordviewerframe.aspx
?<ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>

In our earlier example, we used this URL to load the viewer:

 https://wopi-app-server.contoso.com/wv/wordviewerframe.aspx
?WOPISrc=http%3A%2F%2Fmy-wopi-host%2Flocal
%2Fwopi%2Ffiles%2F1-Sample%2520Document.docx&access_token=
dc172034-c6f9-4a43-bc3f-d80dd93c1de1

You can see that this URL is the one from the discovery XML with the optional parameters from the urlsrc removed and the required WOPISrc and access_token parameters appended.

Once you have performed WOPI discovery, you can invoke any supported action on a file by visiting the URL specified in the urlsrc parameter, assuming you have correctly transformed the URL according to the protocol specifications. When you visit the URL, the WOPI application will in turn call you back via the URL you passed to it in the WOPISrc parameter, at which point you'd respond with information about the file, the file content, etc.

Wrapping the Application URL

As a WOPI host, you would likely prefer to be able to provide your users with a URL that they could visit and see a document opened up in a WOPI application. You could just hand out the URL to the WOPI application itself, but there are a couple of problems with that. First, the URL isn't very friendly-looking; it's a bit unwieldy. Second, it's not a URL for your WOPI host, so it's potentially confusing for users. Most importantly, though, it contains an access token, which is a user-specific and time-sensitive thing. Thus, that URL isn't appropriate to share with multiple users.

There is still a way for you to provide friendly URLs, though. The way you do this is by creating a page within your WOPI host that contains an iframe that points to the WOPI application URL. For example, you could have a page with a URL like this:

 https://my-wopi-host/documents/1-Sample%20Document.docx

When loaded, you'd determine the user requesting the page, issue them an access token if needed, then respond with a simple page similar to this:

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="https://www.w3.org/1999/xhtml"> <head> <title>My WOPI Host</title> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> <!-- Enable IE Standards mode --> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <link rel="shortcut icon" href="/static/icons/favicon.ico" /> <style type="text/css"> #waciframe { width: 100%; height: 100%; border: none; position: absolute; top: 0; left: 0; } </style> </head> <body style="overflow: hidden;"> <iframe id="waciframe" src="https://wopi-app-server.contoso.com/
wv/wordviewerframe.aspx?WOPISrc=http%3A%2F%2Fmy-wopi-host%2Flocal
%2Fwopi%2Ffiles%2F1-Sample%2520Document.docx
&access_token=dc172034-c6f9-4a43-bc3f-d80dd93c1de1"></iframe> </body> </html> 

As you can see, this page is very simple. Its body only contains an iframe that points to the WOPI application URL. Because you can dynamically generate this page content, you can fill in the user's access token as needed, and users will see a URL that points to your WOPI host, not the WOPI application.

Putting It All Together

We've covered an awful lot of ground in this post, and hopefully it's become a little clearer how you can enable some pretty cool scenarios with documents on the web using WOPI and the Office Web Apps. To briefly summarize what we've covered, in order to implement WOPI you need the following things:

1. Provide unique file IDs for your files and access tokens for your users

2. Implement at least the CheckFileInfo and GetFile WOPI REST endpoints

3. Parse discovery XML to determine action URLs

4. Wrap action URLs in a page/URL within your service for a more seamless user experience

I highly recommend reading the [MS-WOPI] documentation if you're interested in learning more or are implementing the interface. That document covers the entirety of the interface in great detail, and is a great reference.

In my next post, I'll walk through a sample WOPI host implementation and we'll see more of WOPI in action.