3

在我的一个家庭作业实践中,我必须启动一个网络服务器,当我访问网络服务器的根目录时,它会执行一个 CGI 脚本。

但是当我打开 localhost:8080 时,会出现以下错误消息:

Error code 403.

Message: CGI script is not executable ('/cgi-bin/todolist.cgi').

Error code explanation: 403 = Request forbidden -- authorization will not help.

我的服务器代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import getopt
import logging
import BaseHTTPServer
from CGIHTTPServer import CGIHTTPRequestHandler
import json
from DBBackend import TodoDao

# CRUD to REST conventions
# POST   Create
# GET    Retrieve
# PUT    Update
# DELETE Delete

"""
API REST del servidor.

GET /todos        - Recupera la lista de tareas (ToDos)
DELETE /todos/id  - Elimina la tarea con el id especificado
POST /todos       - Añade una nueva tarea con los valores especificados como parámetros
PUT /todos/id     - Actualiza los valores espcificados en los parámetros para la tarea con
                    el id dado

Tanto los parámetros (en el cuerpo de la petición) como las respuestas son en formato JSON.
"""

logging.basicConfig(level=logging.DEBUG)

class RESTHTTPRequestHandler(CGIHTTPRequestHandler):

    dao = TodoDao()
    res_string = dao.tableName
    res_path = "/" + res_string

    def _GET(self):
        if self.path == self.res_path:
            tasks = self.dao.findTasks()
            return {'code': 'ok', 'data': tasks}
        else:
            _,res,id = self.path.split("/")
            int(id)
            assert(res==self.res_string)
            data = self.dao.retrieveTask(id)
            return {'code': 'ok', 'data': data}

    def _POST(self):
        assert(self.path == self.res_path)
        if 'Content-length' in self.headers:
            data = json.loads(self.rfile.read(int(self.headers['Content-length'])))
        else:
            data = json.load(self.rfile)
        self.dao.createTask(data)
        return {'code': 'ok'}


    def _PUT(self):
        _,res,id = self.path.split("/")
        int(id)
        assert(res==self.res_string)
        if 'Content-length' in self.headers:
            data = json.loads(self.rfile.read(int(self.headers['Content-length'])))
        else:
            data = json.load(self.rfile)
        self.dao.updateTask(id, data)
        return {'code': 'ok'}


    def _DELETE(self):
        _,res,id = self.path.split("/")
        int(id)
        assert(res==self.res_string)
        self.dao.deleteTask(id)
        return {'code': 'ok'}


    def _send(self, data):
        response = json.dumps(data)
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.send_header("Content-Length", len(response))
        self.end_headers()
        self.wfile.write(response)


    # El BaseHTTPRequestHandler no está pensado para ésto :(
    def do_POST(self):
        self._reroute()
    def do_PUT(self):
        self._reroute()
    def do_GET(self):
        self._reroute()
    def do_DELETE(self):
        self._reroute()
    def _reroute(self):
        try:
            if self.path.startswith(self.res_path):
                method_name = '_' + self.command
                method = getattr(self, method_name)
                try:
                    self._send(method())
                except (ValueError, AssertionError):
                    self.send_error(400, "Invalid request")
                except:
                    logging.exception("Database access error")
                    self.send_error(500, "DDBB error")
            else:
                if self.path == "/" or self.path == "/index.html":
                    self.path = "/cgi-bin/todolist.cgi"
                method_name = 'do_' + self.command
                method = getattr(CGIHTTPRequestHandler, method_name)
                method(self)
        except AttributeError:
            self.send_error(501, "Unsupported method (%r)" % self.command)


#---- Defaults
port = "8080"
basedir = "www/"
#----

#----------------------------------------
def usage():
    print "Uso: " +  os.path.basename(sys.argv[0]) + " -h -p port"
    print "     -h         Muestra este mensaje"
    print "     -p port    Sirve en el puerto indicado (def={0})".format(port)
    print "     -d dirname Sirve el contenido del directorio indicado (def={0})".format(basedir)

#----------------------------------------

try:
    opts, args = getopt.getopt(sys.argv[1:], "hp:d:", ["help", "port=", "dir="])
except getopt.GetoptError:
    usage()
    sys.exit(2)

for o, a in opts:
    if o in ("-h", "--help"):
        usage()
        sys.exit()
    if o in ("-p", "--port"):
        port = a
    if o in ("-d", "--dir"):
        basedir = a

if (port == None):
    usage()
    sys.exit()

try:
    address = ('', int(port))
except ValueError:
    usage()
    sys.exit(2)

httpd = BaseHTTPServer.HTTPServer(address,
                                  RESTHTTPRequestHandler)
os.chdir(basedir)
httpd.serve_forever()

还有我的 todolist.cgi:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cgi
import sys
import os
import datetime
import locale
# TBD: Usar el locale del cliente
locale.setlocale(locale.LC_TIME,'')
date_format = locale.nl_langinfo(locale.D_FMT)

sys.path.append(os.path.join(os.path.dirname(__file__), "../.."))
import DBBackend

print "Content-Type: text/html"
print ""
print """
<!doctype html>

<html lang="es">

<head>
<meta charset="utf-8"/>
<title>[IPM] Lista de tareas</title>
<meta name="author" content="David Cabrero"/>
<meta name="HandheldFriendly" content="true"/>
<meta name="viewport" content="width=device-width, initial-scale=1,maximun-scale=1,user-scalable=no"/>

<meta http-equiv="X-UA-Compatible" content="IE=ecdge,chrome=1">
<!--[if lt IE 9]>
  <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  <script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script>
<![endif]-->

<link rel="stylesheet" type="text/css" href="css/default.css" />
<link rel="stylesheet" type="text/css" href="css/wide-screen.css" 
      media="only screen and (min-width : 1024px)" />
<script src="js/todolist.js"></script>
</head>

<body>

<header>
<h1>ToDo</h1>
</header>

<ul>
"""
fname = os.path.join(os.getcwd(), "../", DBBackend.DATABASEFILENAME)
li = """
  <a href="#dialog"><li data-task-id="{0}">
    <p class="desc">{1}</p>
    <time datetime="{2}">{3}</time>
    <p class="done" data-task-done="{4}">{5}</p>
  </li></a>
"""
for task in DBBackend.TodoDao(fname).findTasks():
    id = str(task['id'])
    desc = task['desc'].encode('utf-8')
    deadline = datetime.datetime.strptime(task['deadline'], "%Y-%m-%d")
    done = task['done'] == 1
    print li.format(id, desc, deadline.strftime("%Y-%m-%d"), deadline.strftime(date_format),
                    "yes" if done else "no", "Hecho" if done else "Sin hacer")

print """
</ul>


<div id="updateTask" class="dialog"><div>
  <h1>Actualizar tarea</h1>
  <form>
    <p><input type="text" name="task_desc" placeholder="task description" autofocus="autofocus" /></p>
    <p><input type="date" name="task_deadline" placeholder="deadline" /></p>
    <p><input type="checkbox" name="task_done" /></p>
    <p class="okCancel">
      <button name="ok">OK</button> 
      <button name="cancel">Cancel</button>
    </p>
  </form>
</div></div>

</body>

</html>
"""

print """
"""

所有代码都是老师给出的(我必须做一个网络应用程序),所以如果我无法让服务器正常工作,我不知道如何开始。我还在运行 Windows 7 和 Python for Windows(2.7 版),希望对您有所帮助!

4

2 回答 2

6

问题不在于您的代码,而在于文件系统权限。cgi 文件必须标记为可执行。这可以通过chmod a+x todolist.cgi, 从 cgi-bin 目录中的 shell 完成。

于 2012-11-14T21:29:39.350 回答
4

好的,要在 Windows 下进行这项工作,您需要做的是将脚本从 todolist.cgi 重命名为 todolist.py 并更改服务器代码中的行:

self.path = "/cgi-bin/todolist.cgi"

将其更改为:

self.path = "/cgi-bin/todolist.py"

这应该让它在 Windows 中运行而不会大惊小怪。这一切都与 CGIHTTPServer 的内部工作以及它如何处理可执行文件有关。我尝试了各种其他技巧(以及像猴子修补 CGIHTTPServer.executable 等彻底的黑客攻击......)以使其工作,但这似乎是最简单的。

于 2012-11-14T21:30:16.840 回答