Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

binary stream doesn't work on pdf?? 3

Status
Not open for further replies.

POSAPAY

IS-IT--Management
Jul 27, 2001
192
HU
Hey Everyone,
I've built an online ticketing system, where users need to log in, and can upload files.
In order to not allow anybody not logged in to use a direct link to the files, I have an asp page, that checks for an active session, and on demand, it streams the requested file in binary.

It works fine for *.doc and *.xls But I'm having issues with *.pdf and *.txt

*.txt gives me a blank page. (but in view source, it shows up) How can this just prompt for a download screen?

*.pdf gives me an error pop-up:
Internet Explorer cannot download...filedownload.asp?FileNumber=1 from server.domain.com
Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found. Please try again later.

I've tried about 50 times, and it consistantly refuses to download the pdf file.
With a direct link the file downloads fine.. so I know the file is okay.
The same exact script downloads the word doc and excel files fine, so I know the directories are not misspelled.
I've double checked the file name too.. but still gives me the error.


Code:
<%@ Language=VBScript %>
<!-- #include virtual="/inc/adovbs.asp" -->
<%
FileNumber   = Request("FileNumber")
select case FileNumber
case "1"
   FileNameSent = "file1.pdf"
case "2"
   FileNameSent = "file2.xls"
case "3"
   FileNameSent = "file3.txt"
case "4"
   FileNameSent = "file4.doc"
end select
if FileNameSent <> "" then
Set objStream = Server.CreateObject("ADODB.Stream") 
objStream.Open 
objStream.Type = 1
objStream.LoadFromFile  "c:\downloads\" & FileNameSent
response.buffer=true
FileNameResponse = "filename=" & FileNameSent & ";"
response.addheader "Content-Disposition", FileNameResponse
response.ContentType = "application/octetstream"
response.binarywrite objStream.Read
objStream.Close
set objStream = nothing
end if
%>

Any ideas?
 
Maybe try different values of ContentType like "application/pdf" or "text/plain"


Actually, if your users are plain office workers on a closed intranet rather than l33T intarweb h@XX0rz you could save yourself a lot of time by just doing this:
Code:
<%@ Language=VBScript %>
<%
FileNumber = Request("FileNumber")
select case FileNumber
case "1"
   FileNameSent = "file1.pdf"
case "2"
   FileNameSent = "file2.xls"
case "3"
   FileNameSent = "file3.txt"
case "4"
   FileNameSent = "file4.doc"
end select
if FileNameSent <> "" then
  [red]Response.Redirect FileNameSent [/red]
end if
 
[1] For pdf file, you have to set contenttype to "application/pdf". "application/octetstream" won't do.

[2] For plain text file, the main thing is objStream.type=1 would bring more trouble. Better work with .type=2 (adTypeText). Further more, charset has to be specified against all odds.

[3] msdoc and msexcel would accommodate "application/octetstream". They can do with more specific "application/msword" and "application/msexcel".

[4] Other fine detail like "inline" vs "attachment" in the contenttype may bring behavorial difference, etc...

This is a viable remodelling from your script with some other minimal fine details added.
[tt]
<%@ Language=VBScript %>
<%
response.buffer=true
response.clear

Set objStream = Server.CreateObject("ADODB.Stream")
objStream.Open

FileNumber = Request("FileNumber")

select case FileNumber
case "1"
FileNameSent = "file1.pdf"
response.contenttype="application/pdf"
response.addheader "contenttype","application/pdf"
FileNameResponse = "filename=" & FileNameSent & ";"
response.addheader "Content-Disposition", "inline; " & FileNameResponse
objStream.type=1
case "2"
FileNameSent = "file2.xls"
response.ContentType = "application/octetstream"
response.addheader "contenttype","application/octetstream"
FileNameResponse = "filename=" & FileNameSent & ";"
response.addheader "Content-Disposition", "inline; " & FileNameResponse
objStream.type=1
case "3"
FileNameSent = "file3.txt"
response.ContentType="text/plain"
response.addheader "contenttype","text/plain"
FileNameResponse = "filename=" & FileNameSent & ";"
response.addheader "Content-Disposition", "inline; " & FileNameResponse
objStream.type=2 'adTypeText
objStream.charset="utf-8" 'you must set this property for better control
case "4"
FileNameSent = "file4.doc"
response.ContentType = "application/octetstream"
response.addheader "contenttype","application/octetstream"
FileNameResponse = "filename=" & FileNameSent & ";"
response.addheader "Content-Disposition", "inline; " & FileNameResponse
objStream.type=1
end select

if FileNameSent <> "" then
objStream.LoadFromFile server.mappath(FileNameSent)
if objStream.type=1 then
response.binarywrite objStream.Read
else
response.write objStream.ReadText
end if
end if
objStream.Close
set objStream = nothing
%>
[/tt]
 
Upon re-reading your script, change my usage back to your form with absolute path.

>[self]objStream.LoadFromFile server.mappath(FileNameSent)
[tt]objStream.LoadFromFile [blue]"c:\downloads\" & FileNameSent[/blue][/tt]

 

Interesting.. I got an error message once I finished building my page, and testing it with a 6MB pdf file:

[highlight #FF99FF]
Response object error 'ASP 0251 : 80004005'

Response Buffer Limit Exceeded

Execution of the ASP page caused the Response Buffer to exceed its configured limit.
[/highlight]
 

I just found this on Microsoft's MSDN website:
MSDN said:
Note
An ASP application uses internal server buffers when writing data to the client, irrespective of the value of Response.Buffer. As the application is writing data synchronously, the data buffer can be reused when the BinaryWrite function returns, while the server is writing the data asynchronously to the client. The server, in turn, makes a copy of the data into its internal buffers. As a result of this buffering, the ASP application should not try to send unreasonable amounts of data in a single BinaryWrite, but rather break it into fragments so as to avoid running out of buffer space. Should the buffer space be exceeded, ASP error 251, "response exceeds the buffer limit," will be returned. While the default maximum buffer size is 4MB, the server administrator may increase it.


I have no idea how to change the buffer size.
Perhaps any other work around?

 
The TextStream object's Read method takes a parameter for how many to read... So keep reading in a loop until AtEndOfStream is True. Inside the loop you can use Response.Flush to avoid blowing the buffer.
 
That's an awsome idea! Thanks!

Any suggestion for the how many bytes to read per loop?

 
FYI,

I just did a few tests.

I first put 10 for Read's parameter .. took forever at 4KB/sec
at 5120 it gave a speed of 130KB/sec
at 10240 it gave 159/sec
at 20480 it gave 144KB/sec

I did these tests three times, and got consistant results.
So it seems the magical number 10240 that is 10K does the trick best. Anybody know why? Or has any other suggestions?

This is my code currently:
Code:
      do while not objStream.EOS
        response.binarywrite objStream.Read(10240)
        Response.Flush
      loop
 
Thanks for posting your benchmark results... that is very interesting.

I wonder if the optimal byte size would change depending on the load on the server. I wonder if it would be different in a server with a single drive vs. one with a RAID?
 
That is a very good way of probing performance, POSAPAY. But, it takes more rigorous conditionings to come to firm conclusion. It is an excellent start. Have a star on me.
 
My assumption would be that you found the balance between least number of loops/reads and least time for dynamic memory allocation. However I'm not convinced that it should have slowed down that much with the larger numbers if it was just memory allocation, so it sounds like something is missing. the only other thing i can thnk of is that it is not flushing the buffer asynchronouly, so your script is pausing a moment while it flushes the buffer and then continuing. That would account for the slower download on the larger size, but unfirtunatly it is a guess and I don't have my Windows box on right now to test the theory.
Additional factors include memory utilization in your system, memory allocated to IIS, etc.

Interesting numbers nonetheless, I'd be very interested if someone wanted to run some more extensive benchmarks.

-T

 
Thanks for your feedbacks regarding my tests.
Sounds like a lot of factors would need to be constant, and consistant perhaps on multiple computers in a lab environment, and only changing one thing at a time with many tests to try to narrow down what has the best results.

At any rate, any suggestions on what size of reads should be best?

Obviously 10 bytes is too small, and 10240 gave not too bad results.

Any suggestions?

I'll test your suggestions, as long as I don't get dumped on by too many! [bigears]
 
I just found on Microsoft's site:

MSDN said:
How To Raise a "File Download" Dialog Box for a Known MIME Type

When you serve a document from a Web server, you might want to immediately prompt the user to save the file directly to the user's disk, without opening it in the browser. However, for known MIME (Multipurpose Internet Mail Extensions) types such as Microsoft Word ("application/ms-word"), the default behavior is to open the document in Internet Explorer.

You can use the content-disposition header to override this default behavior. Its format is:
Code:
Content-disposition: attachment; filename=fname.ext

When Internet Explorer receives the header, it raises a File Download dialog box whose file name box is automatically populated with the file name that is specified in the header. (Note that this is by design; there is no way to use this feature to save a document to the user's computer without prompting him or her for a save location.)

There are two ways that you can use Internet Explorer to specify a content-disposition header for a file: dynamically and statically.


To apply the header dynamically, create an Active Server Pages (ASP) file that writes the document out to the browser. Use the Response.AddHeader method to add the content-disposition header. For example:
Code:
Response.AddHeader "content-disposition","attachment; filename=fname.ext"


Instructions on how to perform a binary write for nontext documents are available in the following Microsoft Knowledge Base article:
193998 ( How to read and display binary data in ASP

Content-disposition is an extension to the MIME protocol that instructs a MIME user agent on how it should display an attached file. The range of valid values for content-disposition are discussed in Request for Comment (RFC) 1806

For more information about content-disposition, see Request for Comments (RFC) 1806 at the following Internet Engineering Task Force (IETF) Web site:
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top