This sample includes some commentary on how the surrounding infrastructure should look, but it's a little out of date.
create or replace procedure get_image(p_id IN images.id%TYPE) IS l_mime images.image_type%TYPE; l_length NUMBER; l_lob BLOB; -- This procedure needs -- Grant to apex_public_user -- Public synonym, so not reliant on #OWNER# -- Function wwv_flow_epg_include_mod_local needs to include this procedure name -- Test rendering using call such as -- http://domain.com.au/pls/apex/GET_IMAGE?p_id=2 -- If not found, check case, synonym -- If no permission, check execute priv, presence in wwv_flow_epg_include_mod_local BEGIN IF p_id IS NOT NULL THEN SELECT image_type, the_blob, DBMS_LOB.GETLENGTH(the_blob) INTO l_mime, l_lob, l_length FROM images WHERE id = p_id; OWA_UTIL.MIME_HEADER(COALESCE(l_mime, 'application/octet'), FALSE); HTP.P ('Content-length: '||l_length); OWA_UTIL.HTTP_HEADER_CLOSE; WPG_DOCLOAD.DOWNLOAD_FILE(l_lob); END IF; END get_image;There are alternative methods available, so you can make ORDS more inherently secure, as descibed by Kris Rice. In fact, this was the reason I needed to do something. Regression testing with a newer version of ORDS uncovered a difference with requestValidationFunction parameter.
For instance, you could use an application process, thanks to Joel Kallman for yet another solution ;p.
We've previously delivered other images using web services, thanking Kris again for the inspiration. The web service is a basic query on the images table, and it also means referencing the image within the HTML is easy
<img src="/ords/rest/image/fetch/3141592" />
Often this would translate to an expression like this within your report
<img src="/ords/rest/image/fetch/#IMAGE_ID#" />
Or perhaps
<img src="#YOUR_IMAGE_PATH##IMAGE_ID#" />
I recently upgraded SQL Developer to a relatively shiny 17.3, so I could use the fancy REST navigator in the connections window.
I've also been meaning to note down the process, and typical values, since I still only understand web services at a relatively basic level.
(secret: the real reason we write blogs is so we can google ourselves later.)
(secret: the real reason we write blogs is so we can google ourselves later.)
I new based on existing web services that the following values were required, so I just had to match these to the steps in the create wizard.
Module: image
URI Prefix: /image/
URI Pattern: fetch/{id}
The module being a group of templates, which are perhaps difference sources of images -> image table, user table, product table. Then a handler is created to get or post data.
The first step is a good start. I've always had trouble working out what value goes where in the URL equation, so it's great to see the translation in the first step.
Step 1 - Define Module |
Step 2 finalises the URL by defining how the identifier for the database record will be provided.
Step 2 - Define Template |
And we're at the summary already, noting we can create web services without initially publishing them for use (from step 1).
Step 3 - Summary |
We can flip over to the SQL tab to show all that SQL Developer is doing behind the scenes - calling supplied APIs.
Step 3b - Review SQL |
Now the web service should be visible in the SQL Developer Connections navigator.
SQL Developer UI |
Righ click on the template to create a handler, in this case to GET the image.
Step 4 - Add Handler |
All the source types return some form of text, except Media Resource, which delivers a binary representation of our blob.
Handler Type |
And the handler is just a SQL statement fetching the blob from the table, filtering with a bind variable mapped to the
{id}
named in the URI pattern.Handler SQL |
The query in the image:
select image_type as mimetype ,the_blob from images where id = :idAll pretty simple so far, but we had another similar procedure that delivered binary content using
bfilename()
. ie - files in the file system, resolved via an Oracle Directory.I figured I could still write a query that delivered this using a table function:
select mime_type, document from table(get_job_doc(:code));
Where get_job_doc returns an object type. The function doesn't need to be pipelined, because it only returns the one row. I describe the relevant pseudo-code in this forum post.
It works, but I had trouble adding
In the end, one procedure was replaced with a web service, another with an application process. Both more secure options that don't require custom PL/SQL to be available via the URL. A whitelist is safer when there's no need for one.
In the era of cloud, web services are king.It works, but I had trouble adding
content-disposition
in the header, so .msg
files don't download well. I bet there is some tidy ORDS feature/solution I'm yet to discover.In the end, one procedure was replaced with a web service, another with an application process. Both more secure options that don't require custom PL/SQL to be available via the URL. A whitelist is safer when there's no need for one.
Now we just need to wait for ORDS & APEX to talk the same data set when it comes to manipulating these services.
You can add a content-disposition paramater as an OUT HTTP HEADER and set it via :bind value in your Anon block.
ReplyDeleteThat's a great suggestion, and I think I understand, except in SQL Developer 17.3 I've tried
ReplyDeleteName: contentdisposition
Bind parameter: contentdisp
Access Method: IN -- this is the only option, perhaps because not anon block?
Source type: HTTP Header
Data type: String
And I have a SQL query, not an anonymous block?