I have described here how to generate PDF content using the PDF generation library PDFlib (see www.pdflib.com) from Caché ObjectScript. This document describes how this content can be served from within a Caché CSP application.
Serving PDF content that is generated as a file is straightforward if the file resides somewhere in the CSP application's directory tree. A simple link is all that is required, as in:
An obvious advantage if this approach is its simplicity. The user can elect to either open the document directly in their browser (if the browser supports this) or right-click on the link and download the document to their computer. There are some disadvantages as well:
If if any of the above items is of concern, the best alternative is probably to use a Caché stream server to supply the content.
Caché's CSP server is perfectly capable of serving any type of content, including PDF. Arbitrary content of arbitrary length is often referred to as streams. Caché includes a generic stream server, %CSP.StreamServer, that can be looked at for some ideas about the basic techniques involved. Note that I do not reccommend using this class directly, as (when it is enabled in Configuration Manager) it will serve any file in the CSP directory tree, including e.g. the source of CSP files, if these are present.
The best way to serve the PDF content is to create a class deriving from
%CSP.Page,
and implementing either the OnPreHTTP
and OnPage
methods, or overriding the Page
method. (I elected to do the
latter.) Linking to a stream server class (assume it is called
PDF.Server
) is done as follows:
When the browser requests an URL of this form, the request is passed to
the Caché CSP engine, which calls the specified class's Page
method, which in turn (when not overridden), calls OnPreHTTP
,
OnPage
and OnPostHTTP
, in that order.
To serve PDF content, a minimum of two things need to be done:
ContentType
must be set to application/pdf.For example, the following class serves the document with the filename
specified in the file
URL parameter:
Class PDF.Server Extends %CSP.Page [ ProcedureBlock ] { ClassMethod Page(SkipHeader As %Boolean = 1) As %Status [ ServerOnly ] { Set DoPage=(%request.Method'="HEAD") Set PDFStream=##class(%File).%New(%request.Get("file")) If '$IsObject(PDFStream) Quit %objlasterror Set sc=PDFStream.Open("RSU") If $$$ISERR(sc) Quit sc If 'SkipHeader { Set %response.ContentType="application/pdf" ;An Expires header is required to get IE to open the document. ;Set to 30 seconds from now: Set %response.Expires=30 Do %response.SetHeader("Content-Length",PDFStream.Size) Set sc=%response.WriteHTTPHeader(.DoPage) If $$$ISERR(sc) Quit sc } If 'DoPage Quit $$$OK Do PDFStream.OutputToDevice() Quit $$$OK } }
Note that this will serve any file reachable for the Caché process, and it doesn't have to be PDF! Error conditions aren't handled very elegantly either, if e.g. the requested file does not exist, a basic CSP error page will be displayed that gives no clue as to what the problem was.
It is therefore important to do some error checking, and return a
meaningful status to the user if a problem was detected (i.e., the
requested content doesn't exist or the current user isn't entitled to
see it). The way to do this is set a HTTP response indicative of the
problem, and return an HTML document with a description. The following
code fragment (that should be placed at the top of the Page
method described above) demonstrates how to do this:
Set DoPage=(%request.Method'="HEAD") ;Check if user is authorized to view this document: If '..CheckPermission() { If 'SkipHeader { Set %response.Status="401 Unauthorized" Set sc=%response.WriteHTTPHeader(.DoPage) If $$$ISERR(sc) Quit sc } If DoPage Do ..Unauthorized() Quit $$$OK }
In this fragment, CheckPermission
is a classmethod that
analizes the %request
properties and determines if the document
request is valid. It returns a boolean indicating this. The Unauthorized
classmethod should return an HTML document describing the problem; a minimal
example is:
ClassMethod Unauthorized() [ Private ] { &html< <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Unauthorized</title> </head> <body> <h1>Unauthorized</h1> <p>You are not authorized to view the requested document.</p> </body> </html> > Quit }
Of course, the document could also be served from a file.
To suggest to the browser to open a Save As dialogue for the PDF
content, and to suggest a filename, the HTTP Content-Disposition
header can be used as follows:
Do %response.SetHeader("Content-Disposition","attachment; filename="_PDFFileName)
So far, the example code demonstrates how to send PDF content present in
a file. It isn't necessary to have a file, though: any (binary) stream will
do. For example, the PDF.Lib class's interface to PDFlib's
GetBuffer
method returns the in-memory generated PDF as a
%GlobalBinaryStream
, which can be used exactly as described above.
As described here, I have not been able to get
the COM class's GetBuffer
method working,
but those that do may find they can use it similarly. As far as I have been
able to determine, this method may either return a string or a stream, so
this should probably be checked for, and the code adjusted accordingly.
I have created a minimal sample class that incorporates the above
techniques, that can be used as starting point for an application-specific
(and more secure!) version. It can be found here;
licensing is the same as for the other PDFlib-related code, and
described here. Note that this class, if used as-is,
will serve any file on the server's filesystem that the Caché process has
access to, so if this class is to be used, be sure to implement the
CheckPermission
method!