Sunday, November 24, 2013

CGI Python: serving binary file from web server to client

CGI Python: serving binary file from web server to client

1. Problem.

I want to send binary data (image or packed zip file) to the client from our CGI Python script. If you will work under *nix system it will not be complicated.



The correct code is:

import os

#file to send
FILE_NAME = "d:/mydocs/DesktopWallpapers/ws_Neon_Violin_1600x1200.jpg"

#retrieving base name
baseName = os.path.basename(FILE_NAME)

#writing headers
print("Content-Type:image/jpeg; name=\"{0}\"".format(baseName))
print("Content-Disposition: attachment; filename=\"{0}\"".format(baseName));
print()


file = open(FILE_NAME,'rb')
print(file.read())


But, unfortunately we must to do our task from IIS hosted on Windows Server. Also, we use Python 3. So our task will be little complicated, but we will solve it! (I promise)

2. Solution.


2.1 Fixing problems with HTTP 503 Error (The service is unavailable).


Sometime, after some experiments with your Python CGI scripts you will get "Service Unavailable. 
HTTP Error 503. The service is unavailable." error. And even restart of ISS will not change the situation.

You can easily fix this problem with restarting Application Pool for your site. You can do this with IIS Manager.

2.2 Complete solution for main task


2.2.1 Changing Handler Mappings for Python scripts.



You must change Handler Mapping for .py files from python.exe "%s" "%s" to python.exe -u "%s" "%s"

Note: the -u following the interpreter path; this is very important. It puts the python interpreter in "unbuffered" mode. Trying to run python cgi scripts in the (default) buffered mode will either result in a complete lack of return value from your cgi script (manifesting as a blank html page) or a "premature end of script headers" error.

2.2.2 Switching stdout to binary mode


As we know, any Windows OS opens stdout in text mode. So we must switch it to binary. So correct code which will solve main problem will be:

import os
import sys

#file to send
FILE_NAME = "d:/mydocs/DesktopWallpapers/ws_Neon_Violin_1600x1200.jpg"

#retrieving base name
baseName = os.path.basename(FILE_NAME)

#writing headers
print("Content-Type:image/jpeg; name=\"{0}\"".format(baseName))
print("Content-Disposition: attachment; filename=\"{0}\"".format(baseName));
print()


bstdout = open(sys.stdout.fileno(), 'wb', closefd=False)
file = open(FILE_NAME,'rb')
bstdout.write(file.read())
bstdout.flush()


In this code we opens bstdout in binary mode and then works only with bstdout.

Hope it was useful.




No comments:

Post a Comment