Advanced Web Applications
Streaming Images from Web Applications
By Corbin Dunn
You may have had an instance when you wanted to stream a custom image out to a web page based on what the user selected. This document describes several different ways to store and send images to a web browser.
Sending a jpeg image from the hard drive |
This tutorial was created with Delphi 5, but it should work fine with previous versions. You will need the Enterprise (or Client/Server) version of Delphi to create Web Applications.
Create a new Web Application (File->New->Web Server Application, ISAPI DLL) and add a new Action. Set the PathInfo to '/motorcycle.jpg' and in the OnAction event add the following code: procedure TWebModMain.WebModMainwbactnGetJpegAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); // Helper function to get the current directory the DLL is in function GetDllDir: string; begin SetLength(Result, MAX_PATH+1); // Add 1 for the null character GetModuleFileName(hInstance, PChar(Result), MAX_PATH+1); SetLength(Result, Length(PChar(Result))); Result := ExtractFilePath(Result); end; var FileStream: TFileStream; begin // This demonstrates returning a Jpeg file from a file on the hard drive. try FileStream := TFileStream.Create(GetDllDir + 'motorcycle.jpg', fmOpenRead); // Note that the file 'motorycle.jpg' must be in the same // directory as this DLL. FileStream.Position := 0; // Go to the start of the stream Response.ContentStream := FileStream; Response.ContentType := 'image/jpeg'; Response.SendResponse; // Notice that the stream is not freed (it shouldn't be!) except // Catch all exceptions and show a custom message on E: Exception do Response.Content := '<html><body>An error occurred ' + E.ClassName + ': ' + E.Message + '</body></html>'; end; end; The first thing you will see is the helper function GetDllDir that simply get's the current directory the DLL resides in. You will have to put the file 'motorcycle.jpg' in the same directory as your ISAPI dll for this to work correctly. Something to notice is how the whole function is wrapped in a try..except so that I can show a custom error message if something goes wrong. The TFileStream.Create can potentially raise an exception if the file passed to it doesn't exist or is locked. Next, the Response.ContentStream is set to the FileStream we just created. Once you do this note that you should not free the stream. It is taken care of for you in the TWebResponse destructor. Then the Response.ContentType is set based on what we are sending (a Jpeg in this case), and the Response is sent with Response.SendResponse. One thing which you may want to do is send other file types. A problem you may encounter is what to set the Response.ContentType to. This can easily be solved in code by looking up the ContentType in the registry: // MimeType is a string Reg := TRegistry.Create; try Reg.RootKey := HKEY_CLASSES_ROOT; if Reg.OpenKey(ExtractFileExt(FileName), False) then begin if Reg.ValueExists('Content Type') then MimeType := Reg.ReadString('Content Type'); Reg.CloseKey; end; finally Reg.Free; end;Of course, if the ContentType wasn't found, you will probably want to default to something such as 'text/plain' (plain text). Another thing which you may be interested in doing is setting the "suggested file name" when a user goes to download a file by sending back a response other than an image. This can easily be done by adding a custom Content-Disposition header field: Response.CustomHeaders.Add('Content-Disposition=; ' + 'filename="Suggested Name.ext"'); If you would like to know more about what you can do with the Content-Disposition header, take a look at the RFC: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html |
Sending an image from a resource in the DLL |
At one point I wanted to create an ISAPI dll that was completely self contained with its own images. All I had to output was a simple checked or unchecked image, so I decided a simple way to solve this problem was to put the GIF images in the resource portion of the DLL.
The first thing I did was to create a text file named "web_gifs.rc". Inside it, I had the lines: CHECKED GIF "checked.gif" UNCHECKED GIF "unchecked.gif"The two gif files were in the same directory as the "web_gifs.rc" file, and I compiled it into a resource file (.res) with a command line of: brcc32 web_gifs.rcFor ease of use, I included a simple batch file that does this for you. I then added two new Actions to my project: one with a PathInfo of '/unchecked.gif' and another with a PathInfo of '/checked.gif'. I then created on OnAction event, and hooked them both to the same event with the following code in the event: // Link the Gif's into the DLL resource section {$R web_gifs.RES} procedure TWebModMain.WebModMainwbactnGetGifAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var ImageType: string; begin try // Two Web Actions are hooked up to this single OnAction. // Based on which PathInfo is in the Action, we send the // correct Gif image. if (Sender as TWebActionItem).PathInfo = '/unchecked.gif' then ImageType := 'UNCHECKED' // Resource name in .RES file else ImageType := 'CHECKED'; Response.ContentStream := TResourceStream.Create(hInstance, ImageType, 'GIF'); Response.ContentType := 'image/gif'; Response.SendResponse; except on E: Exception do Response.Content := '<html><body>An error occurred ' + E.ClassName + ': ' + E.Message + '</body></html>'; end; end; Based on the previous example, this one should be very easy to understand. |
Dynamically creating a custom image |
The next challenge was to dynamically create a custom image at run-time and return it to the user. Conceptually, the way this will be done is to create a TBitmap, draw on that Bitmap's Canvas, and then save that Bitmap to a TJpegImage. The JpegImage can then be streamed out to the web browser as before.
I created another WebAction and set the PathInfo to '/custom.jpg'. The OnAction event is as follows: procedure TWebModMain.WebModMainwbactnGetOnFlyAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var Bitmap: TBitmap; // The unit Graphics must be in your uses clause JpegImage: TJpegImage; // The unit JPEG must be in your uses clause MemoryStream: TMemoryStream; I: Integer; begin // This demonstrates creating a custom image on the fly, // converting it from a Bitmap to a Jpeg, and sending the // Jpeg to the browser. try Bitmap := TBitmap.Create; try // Draw some custom stuff on the bitmap Bitmap.Width := 300; Bitmap.Height := 300; for I := 0 to Bitmap.Height - 1 do begin // Add a little color Bitmap.Canvas.Pen.Color := Trunc(I/Bitmap.Height*255); Bitmap.Canvas.MoveTo(0, I); Bitmap.Canvas.LineTo(Bitmap.Width -1, I); end; // Set the background mode to be transparent when writing text. SetBkMode(Bitmap.Canvas.Handle, TRANSPARENT); // Write a custom message based on some of the Request properties Bitmap.Canvas.Font.Color := clWhite; Bitmap.Canvas.TextOut(5, 5, 'Hello Host ' + Request.RemoteHost + ' (' + Request.RemoteAddr + ')'); Bitmap.Canvas.TextOut(5, 30, 'You are using ' + Request.UserAgent); Bitmap.Canvas.TextOut(5, 55, 'The date/time is ' + DateTimeToStr(Now)); JpegImage := TJPEGImage.Create; try // Assign the Bitmap to the Jpeg JpegImage.Assign(Bitmap); MemoryStream := TMemoryStream.Create; JpegImage.SaveToStream(MemoryStream); MemoryStream.Position := 0; Response.ContentStream := MemoryStream; Response.ContentType := 'image/jpeg'; Response.SendResponse; finally JpegImage.Free; end; finally Bitmap.Free; end; except on E: Exception do Response.Content := '<html><body>An error occurred ' + E.ClassName + ': ' + E.Message + '</body></html>'; end; end; A new thing to notice in this example is the saving the Jpeg to a TMemoryStream. The Jpeg and Bitmap can safely be freed, but the MemoryStream is automatically freed for you in the TWebResponse's destructor. |
Putting it all together |
When you want to display any of the images, all you have to do is type in the path to the DLL and add the right PathInfo. More commonly, you will probably embed it into an <IMG> tag, such as: <img src="/scripts/ImageStreamer.dll/custom.jpg"> The last thing to do in this example is to put it all together. I created one more action and set the Default property to True. In the OnAction event I added the following code: procedure TWebModMain.WebModMainwbactnDefaultAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin // Default action, display several different images // that were streamed out by the ISAPI dll Response.Content := '<html><body>' + #10#13 + '<h2>Below are some images sent out from this Web Application</h2>' + '<b>Here is a Gif that was streamed out from a resource inside ' + 'the Dll: checked <img src="' + Request.URL + '/checked.gif"> and unchecked: '+ '<img src="' + Request.URL + '/unchecked.gif"><br><br>' + 'Here is a Jpeg that was loaded from a file:<br>' + '<img src="' + Request.URL + '/motorcycle.jpg"><br><br>' + 'Here is a custom image created on the fly:<br>' + '<img src="' + Request.URL + '/custom.jpg">' + '</b></body></html>'; end; |
Last Modified: 17-DEC-99