Serving Images with MarkLogic Server

Frank Rubino, Paul Rooney, Andy Agrawal and Danny Sokolsky
Last updated July 1, 2009

Summary

In this note we are going to show you how to use XQuery to serve an image from a MarkLogic database http server.

For more information about http Servers, you should read the "HTTP Servers" in the Administrator's Guide.


Waiting for an image

We had loaded a jpeg into my MarkLogic database and wanted to display it using XQuery. "So,"we thought, "why not just compute a uri to the jpeg and then stick it in an image tag?" We wrote:

 <html>
    <body>
       <img src="{$img_uri}"/>
    </body>
 </html>

Our database was served by an http app server and set to find modules in the file system, so we saved our code into our app server's root and prepared to run it. (Remember this for later. This turns out to be key. We had set up the app server root, by the way, in the admin page for our http server.)

Excited to see our image (a portrait of the author Samuel Beckett), we invoked our XQuery program and.... all we got was a cracked picture icon. Our uri, we confirmed, looked like '/beckett.jpg'. "This doesn't make sense," we thought. "These things are usually busted path problems." We typed '/beckett.jpg' into Query Console (or cq in 4.2 or earlier), as the parameter to exists.

"Hmm," we said. " exists('/beckett.jpg') is true. Why no picture of the world's greatest writer?"

One Way to Make it Work

As it turned out, while MarkLogic Server knew about our author's mug, our browser would not, and could not, resolve a uri that looked to it like http://localhost:8001/beckett.jpg.

In a minute, we'll explain why, but perhaps you can beat us to the punch if you learn that the solution to seeing Samuel Beckett was to have the browser point instead to an XQuery module that could access the data.

Our HTML would have to change. It was supposed to look more like:

<img src="get-db-file.xqy?uri={$img_uri}"/>

So what's this get-db-file.xqy?

It's a very simple XQuery main module that grabs the "uri" request field and returns the resource from the database with the appropriate mime-type. Here are its entire contents:

let $uri := xdmp:get-request-field("uri","")

return
  if (ends-with($uri,".jpg"))
  then (
    xdmp:set-response-content-type("image/jpeg"),
    fn:doc($uri)
  )

That's it, in its most terse form. If you want to get gifs sometimes too, you could add:

else if (ends-with($uri,".gif"))
then (
  xdmp:set-response-content-type("image/gif"),
  fn:doc($uri)
)

And so on.

Why It Works

Why is this necessary? The HTTP server is supposed be able to access the content in the database via an XQuery program, and we thought we created that HTML with our XQuery program!

Let's look at what happens. {$uri}gets turned into its value, '/beckett.jpg' by the XQuery evaluator on the server.

The browser sees the evaluated expression and tries to resolve it in terms of the HTTP app server's root.

"Okey dokey," it says to the server, "just grab '/beckett.jpg' from the root directory on your file system!"

But wait a minute. Sam's not in the file system, is he? He's in the database; We're trying to get him from the database.

But because our XQuery is running in the file system (remember, we said to take note of this above), the app server root is also in the file system. So the only way to get our picture is to put another XQuery module in the database that can access it. Specifically, get-db-file.xqy.

You don't even need to do this much however if you store your code in the database. Here's a scenario where <img src="{$img_uri}"/>is in an XQuery module in the database, and invoking it produces the author of 'Waiting For Godot'. For the purposes of this illustration, several conditions apply:

  • $img_uri resolves to '/beckett.jpg'.
  • / is the app server root.
  • The Modules database is the same as the Content database.

To give this idea a whirl, create up an http server, let's say at port 8008, and set its database to a new database, molloy.

Then set 8008's Modules database to molloy. Fire up Query Console (or cq) and point it to molloy with the Content-Source dropdown. Type:


xdmp:document-load('/beckett.jpg')
;
(:notice the ; above--makes a separate transaction :)
xdmp:document-insert('/beckett.html', text { '<img xmlns=
  "http://www.w3.org/1999/xhtml" src="beckett.jpg"/>' });

Then go to http://localhost:8008/beckett.html.

Why are we finally done waiting for our author? In this case, since the code is in the database, the database is the app server root. So the uri in the code, beckett.jpg', is resolved relative to the database, not the file system.

URL Rewriting

Starting with version 4.1, MarkLogic Server supports URL rewriting--the ability to use a clean, intuitive and easy to remember URL instead of an internal URL like "get-db-file.xqy?uri={$img_uri}".

To accomplish this, first set up a URL rewriter for your App Server. Go to the App Server configuration page in the Admin Interface and scroll down to the text box that says "URL rewriter". Let’s set its value to "url-rewriter.xqy". What happens now is that every URL that is requested from this App Server will first go to url-rewriter.xqy and get rewritten under the covers. This lets you use a clean and user-friendly URL. What this means is that our HTML can be changed back to what we started with:

<img src="{$img_uri}"/>

Now all you have to do is to create your URL rewriter script and store it on the file system under the file url-rewriter.xqy:

define function local:is-image($uri as xs:string)
as xs:boolean 
{
fn:ends-with($uri, ".jpg") or fn:ends-with($uri, ".gif")
};

let $uri := xdmp:get-request-url()
return
if (local:is-image($uri)) 
then fn:concat("get-db-file.xqy?uri=", $uri)
else $uri

All this does is to check the URL and check if it has an image extension, in which case it uses the get-db-file script we created earlier. Otherwise, it proceeds as before. This keeps the HTML simple and intuitive and hides all the ugly URL manipulation from the user. The function to check for the image type (local:is-image) could be moved into a library module that could be shared by both the URL rewriter and the get-db-file script for maximum code re-use. That’s it, you’re done!

Comments