Sorry for the possible duplication of the question. I'm using python 3.7 http.server (python -m http.server --cgi 8080
) to test the webserver locally, it works fine under Windows (and under Ubuntu on the server where nginx
is running). But when I use it under Ubuntu, my cgi code hangs at the line where the code reads the input: sys.stdin.readline()
(or for sys.stdin.read()
). When I remove this line (put the string with some input) it works.
The code of cgi-file looks like:
#!/usr/bin/env python3
# coding: utf-8
import os
import sys
import json
# trace cgi errors
import traceback
import codecs
from collections import namedtuple
from time import process_time
from datetime import datetime
import cgi
sys.stderr = sys.stdout
try:
json_inputdata = sys.stdin.readline()
# Parse JSON into an object with attributes corresponding to dict keys.
InputData = json.loads(json_inputdata, object_hook=lambda d: namedtuple('InputData', d.keys())(*d.values()))
...
except:
print("\n\n<PRE>")
traceback.print_exc()
Maybe someone knows what the problem is? Thanks in advance.
Finally, I found a solution to my problem, that is in these two lines:
sys.stderr = sys.stdout
json_inputdata = sys.stdin.readline()
The problem is that this code only reads one line from stdin, which is not guaranteed to work reliably, since the body of a POST request (e.g., a JSON payload) is not necessarily a single line. If the client sends more than one line, or there is no line break character at all, you will get incomplete or empty data.
The correct way is to read exactly that many bytes, regardless of line breaks. So the working version looks like this:
content_length_str = os.environ.get('CONTENT_LENGTH', '')
if content_length_str.isdigit():
content_length = int(content_length_str)
else:
content_length = 0
json_inputdata = sys.stdin.buffer.read(content_length)
CONTENT_LENGTH
is the standard CGI environment variable that reports the number of bytes in the request body.
Small detail: sys.stdin.buffer.read()
returns bytes, not str
, so additional decoding may be required (for example: .decode("utf-8")
).