4

I am using CGIHTTPServer.py for creating simple CGI server. I want my CGI script to take care of response code if some operation goes wrong . How can I do that?

Code snippet from my CGI script.

if authmxn.authenticate():
     stats = Stats()
     print "Content-Type: application/json"
     print 'Status: 200 OK'
     print

     print json.dumps(stats.getStats())
 else:
     print 'Content-Type: application/json'
     print 'Status: 403 Forbidden'
     print
     print json.dumps({'msg': 'request is not authenticated'})

Some of the snippet from request handler,

def run_cgi(self):
'''
    rest of code
'''
    if not os.path.exists(scriptfile):
        self.send_error(404, "No such CGI script (%s)" % `scriptname`)
        return
    if not os.path.isfile(scriptfile):
        self.send_error(403, "CGI script is not a plain file (%s)" %
                        `scriptname`)
        return
    ispy = self.is_python(scriptname)
    if not ispy:
        if not (self.have_fork or self.have_popen2):
            self.send_error(403, "CGI script is not a Python script (%s)" %
                            `scriptname`)
            return
        if not self.is_executable(scriptfile):
            self.send_error(403, "CGI script is not executable (%s)" %
                            `scriptname`)
            return

    if not self.have_fork:
        # Since we're setting the env in the parent, provide empty
        # values to override previously set values
        for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
                  'HTTP_USER_AGENT', 'HTTP_COOKIE'):
            env.setdefault(k, "")

    self.send_response(200, "Script output follows") # overrides the headers

    decoded_query = query.replace('+', ' ')
3
  • 1
    What is the problem, actually? What you have seems like it should work. Are you talking about exception handling? (In which case you could catch the exception and return http response code 500 internal server error. Commented Sep 12, 2014 at 3:47
  • @mhawke you can check code above in the comment above you will see that its overriding headers. did you understand the problem? Commented Sep 12, 2014 at 3:51
  • OK, when I made a simple test CGI, I saw either an error message from the browser or the error message provided by the script only. It seems it needs further investigation what actually triggers the effect. Commented Nov 14 at 9:44

2 Answers 2

4

It is possible to implement support for a Status: code message header that overrides the HTTP status line (first line of HTTP response, e.g. HTTP/1.0 200 OK). This requires:

  1. sub-classing CGIHTTPRequestHandler in order to trick it into writing the CGI script's output into a StringIO object instead of directly to a socket.
  2. Then, once the CGI script is complete, update the HTTP status line with the value provided in the Status: header.

It's a hack, but it's not too bad and no standard library code needs to be patched.

import BaseHTTPServer
import SimpleHTTPServer

from CGIHTTPServer import CGIHTTPRequestHandler
from cStringIO import StringIO

class BufferedCGIHTTPRequestHandler(CGIHTTPRequestHandler):
    def setup(self):
        """
        Arrange for CGI response to be buffered in a StringIO rather than
        sent directly to the client.
        """
        CGIHTTPRequestHandler.setup(self)
        self.original_wfile = self.wfile
        self.wfile = StringIO()
        # prevent use of os.dup(self.wfile...) forces use of subprocess instead
        self.have_fork = False

    def run_cgi(self):
        """
        Post-process CGI script response before sending to client.
        Override HTTP status line with value of "Status:" header, if set.
        """
        CGIHTTPRequestHandler.run_cgi(self)
        self.wfile.seek(0)
        headers = []
        for line in self.wfile:
            headers.append(line)    # includes new line character
            if line.strip() == '':  # blank line signals end of headers
                body = self.wfile.read()
                break
            elif line.startswith('Status:'):
                # Use status header to override premature HTTP status line.
                # Header format is: "Status: code message"
                status = line.split(':')[1].strip()
                headers[0] = '%s %s' % (self.protocol_version, status)

        self.original_wfile.write(''.join(headers))
        self.original_wfile.write(body)


def test(HandlerClass = BufferedCGIHTTPRequestHandler,
         ServerClass = BaseHTTPServer.HTTPServer):
    SimpleHTTPServer.test(HandlerClass, ServerClass)

if __name__ == '__main__':
    test()

Needless to say, this is probably not the best way to go and you should look at a non-CGIHTTPServer solution, e.g. a micro-framework such as bottle, a proper web-server (from memory, CGIHTTPServer is not recommended for production use), fastcgi, or WSGI - just to name a few options.

Sign up to request clarification or add additional context in comments.

1 Comment

twisted comes with a built-in web server which can be run with twistd -n web --path . and which of course can be extended in python: twistedmatrix.com/documents/15.0.0/web/howto/…
3

With the standard library HTTP server you cannot do this. From the library documentation:

Note CGI scripts run by the CGIHTTPRequestHandler class cannot execute redirects (HTTP code 302), because code 200 (script output follows) is sent prior to execution of the CGI script. This pre-empts the status code.

This means that the server does not support the Status: <status-code> <reason> header from the script. You correctly identified the portion in the code that shows this support does not exist: The server sends status code 200 before even running the script. There is no way you can change this from within the script.

There are several tickets related to this in the Python bugtracker, some with patches, see e.g., issue13893. So one option for you would be to patch the standard library to add this feature.

However, I would strongly suggest you switch to another technology instead of CGI (or run a real web server).

2 Comments

Great... Can I make it possible using WSCGI?
Just a note: I am using Perl and I got redirects working (meaning status codes also, obviously).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.