Introduction
HTTP protocol was designed to send one file per one request.
Sometimes you will need to send more files - usually when a client selects several files for download and the files have to be delivered to the client.
I created ASP sample which will do such task - the sample is located at https://www.motobit.com/help.
The sample uses command line compression (zip/arj) to pack more files to one file and then sends the zip/arj archive to client. Client must have some software to uncompress the data package (unzip, pkunzip, winzip ...).
Zip/Arj is great for download of files. But there are some tasks which require send more files in one request, for example HTML page with many small images as a preview.
This task is usually completted as one request to HTML page and many (ten or more) requests to external images.
Client-server communication for tenth of http request takes additional overhead on server, additional communication time, consumes line bandwith.
The situation is even worse if you need authentication for each image preview - you must do tenth of authentication requests against user database, separated for each http request.
This article shows another way - you can send HTML document and images (or applets, javascripts, iframes, frames and other external tags with SRC=...)
as one multipart/related document, in one response to client request, with one authentication against user database. (see more about multipart/related at Google)
Prepare multipart/related document
There are several steps to create and send multipart document from server. This sample is an ASP/VBS code, but you can simply create similar code for another environment.
Content - type
I tried to use multipart/related directly in http header, but unforunatelly, it does not work in IE. So I must use anothe content-type - message/rfc822. The outgoing document contains complette multipart/related header and data then.
Response.ContentType = "message/rfc822"
|
Boundary of multipart document
Boundary is a unique string which separates file fields in multipart document. Encoded document files must not contain the string.
Const Boundary = "----=_NextPart_000_0000_01C31FDD.14FE27E0"
|
Mime header at the top of document
Mime header contains at least MIME-Version and Content-Type headers. The header has two CrLf at the end.
Function MimeHeader(Boundary)
Dim HTML
HTML = "MIME-Version: 1.0" & vbCrLf
HTML = HTML & "Content-Type: multipart/related;" & vbCrLf
HTML = HTML & vbTab & "type=""text/html"";" & vbCrLf
HTML = HTML & vbTab & "boundary=""" & Boundary & """" & vbCrLf
HTML = HTML & vbCrLf & "This is a multi-part message In MIME format." & vbCrLf
MimeHeader = HTML
End Function
|
File fields
One file field consists from Boundary preceeded by two hypens ("--"). Next are multipart headers (Content-Type, Content-Location at least, you can add Content-Disposition, Content-ID, ...). Content-Location is a parameter for file location - SRC parameter of file, referred by first document.
I'm using base-64 encoding for binary files, but the encoding is not required.
Sub WriteFilePart(FileName)
Dim CT
CT = GetContentType(FileName)
'Write boundary with file multipart header.
Response.Write vbCrLf & "--" & Boundary & vbCrLf
Response.Write "Content-Type: " & GetContentType(FileName) & "" & vbCrLf
Response.Write "Content-Location: " & GetFileName(FileName) & "" & vbCrLf
Response.Write "Content-Disposition: attachment; filename=""" & _
GetFileName(FileName) & """" & vbCrLf
Response.Write "Content-ID: " & GetFileName(FileName) & "" & vbCrLf
'Write contents of the file
If Left(CT, 4) = "text" Then
Response.Write vbCrLf
Response.BinaryWrite ReadBinaryFile(FileName)
Else
'Use Base64 For binary files.
Response.Write "Content-Transfer-Encoding: base64" & vbCrLf
Response.Write vbCrLf
Response.BinaryWrite GetFileAsBase64(FileName)
End If
Response.Write vbCrLf
End Sub
|
First file in the multipart document should be main (start) HTML file, next fields are other data - images, scripts, etc.
HTML document
The first file field contains HTML document. The document has standard formatting, including text, references, etc.
There is a change in URL interpretting - local URLs (without http://) poinst to multipart document, to multipart fields, with Content-Location header as address.
Next is a sample of the HTML - op.gif means multipart field with Content-Location: op.gif, A Href="cl.gif" means reference to cl.gif file field.
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE>Tips</TITLE>
<BASEFONT face="Arial, Verdana, Helvetica">
</HEAD>
<Body TOPMARGIN=0 BGPROPERTIES=FIXED BGCOLOR=WHITE>
<IMG src="op.gif" border=0>
<br>
<A Href="cl.gif"><IMG src="cl.gif" border=0></A>
</BODY>
</BASEFONT>
</HTML>
Full source code of support functions.
There is an ASP file include _related.asp bellow,
containing support functions to send multipart data documents.
The file is using ADODB.Stream to send binary files, Scripting.FileSystemObject
to read file properties, WScript.Shell to get content-type of a file and optionally ScriptUtils.ByteArray to do Base64 conversion.
<%
'_related.asp include - lets you send HTML code
' along with images, scripts And other referenced files
' In one HTML response
' c 2003 Antonin Foller, Motobit Software, http://www.motobit.com
'Sends primary file containing links To other files
' As one response To http request.
' Primary file - main HTML file
' OtherFiles - array of referenced files going with HTML document
Sub SendFileWithImages(PrimaryFile, OtherFiles)
Dim HTML
'Unique multipart boundary string.
Const Boundary = "----=_NextPart_000_0000_01C31FDD.14FE27E0"
Response.ContentType = "message/rfc822"
'mime header For files.
Response.Write MimeHeader(PrimaryFile, Boundary)
'Write primary file
WriteFilePart Boundary, PrimaryFile
'Write other files.
Dim FileName
For Each FileName In OtherFiles
WriteFilePart Boundary, FileName
Next
'Closing boundary
Response.Write vbCrLf & "--" & Boundary & "--"
End Sub
'Write MIME header And HTML part For primary HTML
Sub WriteMimeHeaderAndHTMLPart(Boundary, StartHTML)
Dim HTML
HTML = "MIME-Version: 1.0" & vbCrLf
HTML = HTML & "Content-Type: multipart/related;" & vbCrLf
HTML = HTML & vbTab & "type=""text/html"";" & vbCrLf
HTML = HTML & vbTab & "boundary=""" & Boundary & """" & vbCrLf
HTML = HTML & vbCrLf & "This is a multi-part message In MIME format." & vbCrLf
Response.Write HTML
'Write boundary with file multipart header.
Response.Write vbCrLf & "--" & Boundary & vbCrLf
Response.Write "Content-Type: text/html" & vbCrLf
Response.Write "Content-Location: " & BaseLocation & "start.html" & vbCrLf
'Write contents of the file. You can use BASE64 encoding For binary files.
Response.Write vbCrLf
Response.Write StartHTML
Response.Write vbCrLf
End Sub
'write main MIME header of the document.
Function MimeHeader(PrimaryFile, Boundary)
Dim HTML
HTML = "MIME-Version: 1.0" & vbCrLf
HTML = HTML & "Content-Type: multipart/related;" & vbCrLf
' HTML = HTML & vbTab & "type=""text/html"";" & vbCrLf
HTML = HTML & vbTab & "type=""" & GetContentType(PrimaryFile) & """;" & vbCrLf
' HTML = HTML & vbTab & "start=""" & GetFileName(PrimaryFile) & """" & vbCrLf
HTML = HTML & vbTab & "boundary=""" & Boundary & """" & vbCrLf
HTML = HTML & vbCrLf & "This is a multi-part message In MIME format." & vbCrLf
MimeHeader = HTML
End Function
'Write one mp header with contents.
Sub WriteFilePart(Boundary, FileName)
Dim CT
CT = GetContentType(FileName)
'Write boundary with file multipart header.
Response.Write vbCrLf & "--" & Boundary & vbCrLf
Response.Write "Content-Type: " & GetContentType(FileName) & "" & vbCrLf
Response.Write "Content-Location: " & BaseLocation & _
GetFileName(FileName) & "" & vbCrLf
' Response.Write "Content-Disposition: attachment; filename=""" & _
GetFileName(FileName) & """" & vbCrLf
' Response.Write "Content-ID: " & GetFileName(FileName) & "" & vbCrLf
'Write contents of the file. You can use BASE64 encoding For binary files.
If Left(CT, 4) = "text" Then
Response.Write vbCrLf
Response.BinaryWrite ReadBinaryFile(FileName)
Else
'Use Base64 For binary files.
Response.Write vbCrLf
' Response.Write "Content-Transfer-Encoding: base64" & vbCrLf
Response.BinaryWrite ReadBinaryFile(FileName)
' Response.BinaryWrite GetFileAsBase64(FileName)
End If
Response.Write vbCrLf
End Sub
'Support functions - read files, separate names, etc.
Dim BA
Function GetFileAsBase64(FileName)
If isempty(BA) Then Set BA = CreateObject("ScriptUtils.ByteArray")
BA.ReadFrom FileName
GetFileAsBase64 = BA.Base64
End Function
Function GetFileExtension(FileName)
Dim Pos
Pos = instrrev(FileName, ".")
If Pos>0 Then GetFileExtension = Mid(FileName, Pos+1)
End Function
Function GetContentType(FileName)
GetContentType = GetContentTypeByExt(GetFileExtension(FileName))
End Function
Dim Shell
'This Function reads content type from windows registry
Function GetContentTypeByExt(Extension)
Dim CT
If isempty(Shell) Then Set Shell = CreateObject("WScript.Shell")
on error resume Next
CT = Shell.regRead("HKCR\." & Extension & "\Content Type")
If Len(CT) = 0 Then CT = "application/x-msdownload"
GetContentTypeByExt = CT
End Function
Function SplitFileName(FileName)
SplitFileName = InStrRev(FileName, "\")
End Function
Function GetFileName(fullPath)
GetFileName = Mid(fullPath, SplitFileName(fullPath) + 1)
End Function
Function ReadBinaryFile(FileName)
Const adTypeBinary = 1
'Create Stream object
Dim BinaryStream
Set BinaryStream = CreateObject("ADODB.Stream")
'Specify stream type - we want To get binary data.
BinaryStream.Type = adTypeBinary
'Open the stream
BinaryStream.Open
'Load the file data from disk To stream object
BinaryStream.LoadFromFile FileName
'Open the stream And get binary data from the object
ReadBinaryFile = BinaryStream.Read
End Function
Function GetFileSize(FileName)
On Error Resume Next
Dim FS: Set FS = CreateObject("Scripting.FileSystemObject")
GetFileSize = FS.GetFile(FileName).Size
If err<>0 Then GetFileSize = -1
End Function
%>
|
Samples.
First sample sends an HTML formatted file (img/primary.htm)
along with two images. The files are located in img/ relative folder.
<%option explicit%><!--#INCLUDE FILE="_related.asp"-->
<%
'URL of folder containing base HTML file And included files
Const BaseLocation = ""
SendFileWithImages Server.MapPath("img/primary.htm"), _
array(Server.MapPath("img/op.gif"), Server.MapPath("img/cl.gif"))
%>
|
Next sample is a complette list of images in one folder. The images are send as a preview in one response stream to a client.
<%option explicit%><!--#INCLUDE FILE="_related.asp"-->
<%
'URL of folder containing base HTML file And included files
Const BaseLocation = ""
SendFolderImagePreview "D:\apl\Cool2000\Quick Start\images"
'Creates one HTML page with all images In the folder.
' Then sends the HTML along with images In one response.
Sub SendFolderImagePreview(Folder)
'Create HTML data with image references.
Dim HTML
HTML = "<!DOCTYPE HTML Public ""-//W3C//DTD HTML 4.0 Transitional//EN"">"
HTML = HTML & "<HTML><HEAD><TITLE>Folder " & _
Folder & " contents.</TITLE></HEAD>"
HTML = HTML & "<Body>" & vbCrLf
Const imageExts = ".gif,.jpg,.png,.jpeg,.bmp,.ico"
Dim FS: Set FS = CreateObject("Scripting.FileSystemObject")
Dim File, ImageList
'Create folder image list
For Each File In FS.GetFolder(Folder).Files
'Check file To extension
If InStr(1, imageExts, GetFileExtension(File) & ",", 1)>0 Then
'Reference To image
HTML = HTML & "<Img Src=" & File.Name & _
" Width=100 height=100>" & vbCrLf
'Array of files To send along with HTML
ImageList = ImageList & "*" & File.Path
End If
Next
ImageList = Mid(ImageList, 2)
HTML = HTML & "</Body>"
HTML = HTML & "</HTML>" & vbCrLf
'Unique multipart boundary string.
Const Boundary = "----=_NextPart_000_0000_01C31FDD.14FE27E0"
Response.ContentType = "message/rfc822"
'mime header For files.
WriteMimeHeaderAndHTMLPart Boundary, HTML
'Write image files.
Dim FileName
For Each FileName In split(ImageList, "*")
WriteFilePart Boundary, FileName
Next
'Closing boundary
Response.Write vbCrLf & "--" & Boundary & "--"
End Sub
%>
|
Copyright and use this code
The source code on this page and other samples at
https://www.motobit.com/tips/
are a free code, you can use it as you want: copy it, modify it, use it in your products, ...
If you use this code, please:
1. Leave the author note in the source.
or
2. Link this sample from you page.
<A
Href="https://www.motobit.com/tips/detpg_multiple-files-one-request/"
Title="This article shows a way to
download multiple files in one
http request. It let's you
send an HTML page along
with image preview, prepare more
files for download and send
the files as one data
stream. One request, one authentication
per multiple files."
>Download multiple files in one http request</A>