如何从CGI脚本发送响应头和状态
我正在使用 CGIHTTPServer.py 来创建一个简单的CGI服务器。我希望我的CGI脚本能够处理响应代码,以便在某些操作出错时能够正确反馈。请问我该怎么做呢?
以下是我CGI脚本中的一段代码。
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'})
这是请求处理程序中的一部分代码,
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('+', ' ')
2 个回答
3
使用标准库的HTTP服务器,你是无法做到这一点的。根据库的文档:
注意,由CGIHTTPRequestHandler类运行的CGI脚本无法执行重定向(HTTP代码302),因为在执行CGI脚本之前,服务器会先发送200代码(脚本输出跟随)。这就导致状态码被提前发送了。
这意味着服务器不支持来自脚本的Status: <status-code> <reason>
头信息。你已经正确识别了代码中显示这一点的部分:服务器在运行脚本之前就发送了200状态码。你无法在脚本内部更改这一点。
在Python的错误追踪系统中,有几个相关的记录,有些还提供了修复方案,比如issue13893。所以你可以选择修补标准库来添加这个功能。
不过,我强烈建议你换用其他技术,而不是CGI(或者直接使用一个真正的网络服务器)。
4
可以实现一个支持 Status: code message
头的功能,这个功能可以覆盖HTTP状态行(HTTP响应的第一行,比如 HTTP/1.0 200 OK
)。要做到这一点,需要:
- 创建一个
CGIHTTPRequestHandler
的子类,这样可以让它把CGI脚本的输出写入一个StringIO
对象,而不是直接写入套接字。 - 然后,在CGI脚本完成后,用
Status:
头中提供的值来更新HTTP状态行。
这算是一种小技巧,虽然有点 hack,但其实还不错,而且不需要修改标准库的代码。
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()
不用说,这可能不是最好的方法,你应该考虑使用其他的解决方案,比如非CGIHTTPServer的方案,比如 bottle
这样的微框架,或者一个合适的网络服务器(记得,CGIHTTPServer不推荐用于生产环境),fastcgi,或者WSGI - 这些都是不错的选择。