You should read json data as follows:
#!/usr/bin/env python3 import os import sys import json content_len = int(os.environ["CONTENT_LENGTH"]) req_body = sys.stdin.read(content_len) my_dict = json.loads(req_body)
With the following code, you may run into problems:
myjson = json.load(sys.stdin)
or written less succinctly:
requ_body = sys.stdin.read() my_dict = json.load(requ_body)
This works for me when my cgi script is on the apache server, but you cannot count on what works at all - as I found out when my cgi script was on another server. According to cgi specification:
RFC 3875 CGI Version 1.1 October 2004 4.2. Request Message-Body Request data is accessed by the script in a system-defined method; unless defined otherwise, this will be by reading the 'standard input' file descriptor or file handle. Request-Data = [ request-body ] [ extension-data ] request-body = <CONTENT_LENGTH>OCTET extension-data = *OCTET A request-body is supplied with the request if the CONTENT_LENGTH is not NULL. The server MUST make at least that many bytes available for the script to read. The server MAY signal an end-of-file condition after CONTENT_LENGTH bytes have been read or it MAY supply extension data. Therefore, the script MUST NOT attempt to read more than CONTENT_LENGTH bytes, even if more data is available. However, it is not obliged to read any of the data.
Key line:
The script SHOULD not try to read more than the CONTENT_LENGTH bytes, even if more data is available.
Apparently, apache sends the eof signal to the cgi script immediately after sending the request body to the cgi script, which returns sys.stdin.read() . But according to the cgi specification, the server is not required to send an eof signal after the request body, and I found that my cgi script was hanging on sys.stdin.read() - when my script was on another server, which ultimately caused a time error out.
Therefore, to read in json data in general, you must do this:
content_len = int(os.environ["CONTENT_LENGTH"]) req_body = sys.stdin.read(content_len) my_dict = json.loads(req_body)
The server sets up a bunch of environment variables for cgi scripts that contain header information, one of which is CONTENT_LENGTH.
This is what the failed curl request looked like when I used myjson = json.load(sys.stdin) :
-v verbose output -H specify one header --data implicitly specifies a POST request Note that curl automatically calculates a Content-Length header for you.
~$ curl -v \ > -H 'Content-Type: application/json' \ > --data '{"a": 1, "b": 2}' \ > http://localhost:65451/cgi-bin/1.py * Trying ::1... * TCP_NODELAY set * Connection failed * connect to ::1 port 65451 failed: Connection refused * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 65451 (#0) > POST /cgi-bin/1.py HTTP/1.1 > Host: localhost:65451 > User-Agent: curl/7.58.0 > Accept: */* > Content-Type: application/json > Content-Length: 16 > * upload completely sent off: 16 out of 16 bytes === hung here for about 5 seconds ==== < HTTP/1.1 504 Gateway Time-out < Date: Thu, 08 Mar 2018 17:53:30 GMT < Content-Type: text/html < Server: inets/6.4.5 * no chunk, no close, no size. Assume close to signal end < * Closing connection 0