Serving Images with MarkLogic Server
Summary
In this Tech-Note, we are going to show you how to use XQuery to serve an image from a Mark Logic database http server.
For more information about http Servers, you should read chapter 5 of the Administrator's Guide [PDF].
Waiting for an image
We had loaded a jpeg into my Mark Logic 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 cq, as the parameter to exists.
"Hmm," we said. "
exists('/beckett.jpg') is true. Why no picture of the world's greatest writer?"
How 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","")
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 8002, and set its database to a new database, molloy.
Then set 8002's Modules database to molloy. Fire up cq and point it to molloy with the Content-Source dropdown. Type:
xdmp:load('/beckett.jpg', '/beckett.jpg'); (:notice the semi-colon- it makes this a separate transaction from the following line:)
xdmp:document-insert('/beckett.html', text<img xmlns="
<U>
<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>
</U>" src="beckett.jpg"/>);
Then go to http://localhost:8002/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.