如何通过本地Python HTTP服务器以CGI方式运行带参数的Python脚本
Hey there! Let's tackle your custom HTTP server issue. You want it to run any Python script and pass CGI parameters just like your old Apache setup, right? Let's break down what's wrong with your current code and fix it step by step.
Problem with Your Current Code
Your existing server has three main gaps preventing it from working as expected:
- Hardcoded script path: You're running
my_site.pydirectly, so there's no way to point to other Python files. - Missing CGI environment variables: The
cgi.FieldStorage()in your target script relies on variables likeQUERY_STRINGandREQUEST_METHOD(which Apache automatically sets for CGI scripts). Your current code doesn't pass these, so the script can't read parameters. - Unsafe/limited execution: Using
os.popen()is risky and doesn't handle POST request bodies properly.
Fixed Solution: Custom CGI Server
Here's a revised version of your server that addresses all these issues. It supports both GET and POST requests, lets you specify any Python script via the URL path, and properly passes CGI parameters to the target script:
from http.server import CGIHTTPRequestHandler, HTTPServer import os import subprocess from urllib.parse import urlparse, parse_qs class CustomCGIHandler(CGIHTTPRequestHandler): # Allow any path to act as a CGI script (adjust to restrict to specific directories if needed) cgi_directories = ['/'] def _execute_script(self, script_path, env, input_data=None): """Helper function to execute the target Python script and return output""" try: result = subprocess.run( ['python3', script_path], input=input_data, capture_output=True, text=True, env=env ) return result.stdout, result.stderr except Exception as e: return "", f"Execution error: {str(e)}" def do_GET(self): # Parse URL to get script path and query parameters parsed_url = urlparse(self.path) script_path = parsed_url.path.lstrip('/') # Check if the script exists if not os.path.isfile(script_path): self.send_error(404, f"Script '{script_path}' not found") return # Set required CGI environment variables env = os.environ.copy() env.update({ 'REQUEST_METHOD': 'GET', 'QUERY_STRING': parsed_url.query, 'PATH_INFO': parsed_url.path, 'CONTENT_TYPE': self.headers.get('Content-Type', ''), 'REMOTE_ADDR': self.client_address[0], 'HTTP_HOST': self.headers.get('Host', '') }) # Execute the script stdout, stderr = self._execute_script(script_path, env) # Send response self.send_response(200) self.send_header('Content-Type', 'text/html; charset=utf-8') self.end_headers() self.wfile.write(stdout.encode('utf-8')) # Include error output if present if stderr: self.wfile.write(f"<pre><b>Script Errors:</b>\n{stderr}</pre>".encode('utf-8')) def do_POST(self): # Parse URL and get script path parsed_url = urlparse(self.path) script_path = parsed_url.path.lstrip('/') if not os.path.isfile(script_path): self.send_error(404, f"Script '{script_path}' not found") return # Read POST request body content_length = int(self.headers.get('Content-Length', 0)) post_data = self.rfile.read(content_length).decode('utf-8') if content_length > 0 else "" # Set POST-specific CGI environment variables env = os.environ.copy() env.update({ 'REQUEST_METHOD': 'POST', 'QUERY_STRING': parsed_url.query, 'PATH_INFO': parsed_url.path, 'CONTENT_TYPE': self.headers.get('Content-Type', 'application/x-www-form-urlencoded'), 'CONTENT_LENGTH': str(content_length), 'REMOTE_ADDR': self.client_address[0], 'HTTP_HOST': self.headers.get('Host', '') }) # Execute script with POST data as input stdout, stderr = self._execute_script(script_path, env, input_data=post_data) # Send response self.send_response(200) self.send_header('Content-Type', 'text/html; charset=utf-8') self.end_headers() self.wfile.write(stdout.encode('utf-8')) if stderr: self.wfile.write(f"<pre><b>Script Errors:</b>\n{stderr}</pre>".encode('utf-8')) if __name__ == '__main__': server = HTTPServer(('localhost', 8080), CustomCGIHandler) print('Starting server, use <Ctrl-C> to stop') server.serve_forever()
Testing the Setup
Let's create a test script (test.py) to verify everything works:
#!/usr/bin/python3 import cgi # Read CGI parameters form = cgi.FieldStorage() filename = form.getvalue('filename', 'No filename provided') # Print HTTP response header (required for CGI) print("Content-Type: text/html; charset=utf-8\n") # Output the result print(f"<h1>CGI Parameter Test</h1>") print(f"<p>Filename received: <b>{filename}</b></p>")
Test GET Request
Run the server, then visit:http://127.0.0.1:8080/test.py?filename=yomomma
You should see the output: Filename received: yomomma
Test POST Request
Use curl (or Postman) to send a POST request:
curl -X POST -d "filename=test_post_value" http://127.0.0.1:8080/test.py
The response should show: Filename received: test_post_value
Key Takeaways
- CGI Environment Variables: These are critical for
cgi.FieldStorage()to work. We're setting the same variables Apache would, likeREQUEST_METHOD,QUERY_STRING, and (for POST)CONTENT_LENGTH. - Flexible Script Execution: The server now uses the URL path to find the script (e.g.,
/test.pyrunstest.pyin the server's working directory). - Safety & Error Handling: Using
subprocess.run()is more secure thanos.popen(), and we capture script errors to help with debugging.
内容的提问来源于stack exchange,提问作者LordOdin

