File Download
In general setting up a data file for being sent over http is as easy as determining the file's URI and letting Apache's do all that is needed. If this approach fits your design all you have to do is to keep the downloadable files somewhere within Apache's DocumentRoot (or in any of the directories Apache has right to access).
When a client sends a request for a file, Apache takes care of determining the filetype, sends appropriate
headers to the client and then the file content. The client is responsible for deciding how to handle the
data accordingly to the content-type
headers and its internal design.
For example when browsers give up
trying to display a certain "content-type" they display a download dialog box asking for directions from the user.
Rivet can help if you have more sofisticated needs. For instance you may be developing an application that uses webpages to collect input data. This information might be passed on to scripts or programs for processing. In this case a real file representing the data doesn't exist and the content is generated on demand by the server. In other circumstances you may need to dynamically inhibit the download of specific files and hide them away, your scripts may expunge from the pages every link to these files (your pages are dynamic, aren't they?) and move them out of way, but it looks like a cumbersome solution.
Putting Tcl and Rivet in charge of the whole download mechanism helps in building cleaner and safer approaches to the download problem.
In this example a procedure checks for the existence of a parameter passed in by the browser. The parameter is the name (without extension) of a pdf file. Pdf files are stored in a directory whose path is in the pdf_repository variable.
This code is reported as an example of how to control the protocol using the headers command.
# Code example for the transmission of a pdf file. if {[var exists pdfname]} { set pdfname [var get pdfname] # let's build the full path to the pdf file. The 'pdf_repository' # directory must be readable by the apache children set pdf_full_path [file join $pdf_repository ${pdfname}.pdf] if {[file exists $pdf_full_path]} { # Before the file is sent we inform the client about the file type and # file name. The client can be proposed a filename different from the # original one. In this case, this is the point where a new file name # must be generated. headers type "application/pdf" headers add Content-Disposition "attachment; filename=${pdfname}.pdf" headers add Content-Description "PDF Document" # The pdf is read and stored in a Tcl variable. The file handle is # configured for a binary read: we are just shipping raw data to a # client. The following 4 lines of code can be replaced by any code # that is able to retrieve the data to be sent from any data source # (e.g. database, external program, other Tcl code) set paper [open $pdf_full_path r] fconfigure $paper -translation binary set pdf [read $paper] close $paper # Now we got the data: let's tell the client how many bytes we are # about to send (useful for the download progress bar of a dialog box) headers add Content-Length [string length $pdf] # Let's send the actual file content puts $pdf } else { parse pdf_not_found_error.rvt } } else { parse parameter_not_defined_error.rvt }
Before the pdf is sent the procedure sets the Content-Type, Content-Disposition, Content-Description and Content-Length headers to inform the client about the file type, name and size. Notice that in order to set the Content-Type header Rivet uses a specialiezed form of the headers command. Headers must be sent before data gets sent down the output channel. Messing with this prescription causes an error to be raised (in fact the protocol itself is being violated)