12

I have got Apache2 Installed and Python working.

I am having a problem though. I have two pages.

One a Python Page and the other an Html Page with JQuery

Can someone please tell me how I can get my ajax post to work correctly.

<html>
<head>

</head>
<body>
<script>
    $(function()
    {
        alert('Im going to start processing');

        $.ajax({
            url: "saveList.py",
            type: "post",
            data: {'param':{"hello":"world"}},
            dataType: "application/json",
            success : function(response)
            {
                alert(response);
            }
        });
    });
</script>
</body>
</html>

And the Python Code

import sys
import json

def index(req):
    result = {'success':'true','message':'The Command Completed Successfully'};

    data = sys.stdin.read();

    myjson = json.loads(data);

    return str(myjson);
TheMonkeyMan
  • 8,622
  • 8
  • 27
  • 42

3 Answers3

15

OK, let's move to your updated question.

First, you should pass Ajax data property in string representation. Then, since you mix dataType and contentType properties, change dataType value to "json":

$.ajax({
    url: "saveList.py",
    type: "post",
    data: JSON.stringify({'param':{"hello":"world"}}),
    dataType: "json",
    success: function(response) {
        alert(response);
    }
});

Finally, modify your code a bit to work with JSON request as follows:

#!/usr/bin/python

import sys, json

result = {'success':'true','message':'The Command Completed Successfully'};

myjson = json.load(sys.stdin)
# Do something with 'myjson' object

print 'Content-Type: application/json\n\n'
print json.dumps(result)    # or "json.dump(result, sys.stdout)"

As a result, in the success handler of Ajax request you will receive object with success and message properties.

VisioN
  • 143,310
  • 32
  • 282
  • 281
  • 1
    He is posting an `application/json` and not `application/x-www-form-urlencoded`. His way of reading from stdin is correct (IMO). I suppose problem is elsewhere. – UltraInstinct May 23 '12 at 11:34
  • @Thrustmaster What are you talking about? Default contentType for *standard* Ajax request is exaclty `application/x-www-form-urlencoded`. Why do you need to use something else? – VisioN May 23 '12 at 11:44
  • 1
    OP is (from his code) doing a HTTP/POST with JSON-string in the body of HTTP. The content-type will be `application/json` which would need to be set using `$.ajax({contentType:"application/json"},...)`. What you suggested up there, is to url-encode JSON string, which, IMO, is not OP is doing (since he is correctly reading from stdin on the server-side for that) – UltraInstinct May 23 '12 at 11:50
  • 1
    First, are you sure that he reads from `stdin` correctly? HTTP request is not the same as input from the console. Next, he can easily send not a string but `data : { first : 1, next : 2 }` and get both `first` and `next` with `form` on server-side. What is the problem? Then, where have you taken that OP sends `application/json` as content type? Why to do it? OP simply doesn't know how to handle post requests from Ajax with plain Python CGI. I think you overdo it. – VisioN May 23 '12 at 11:54
  • Post-data of any HTTP request is read from stdin. It is *similar* to reading from console. In this case, OP sends JSON-string (as his first line in the question says). And he is also reading it as it is meant to be. This is what I meant. Anyways, looks like OP has got it working, so lets just close this here. :) – UltraInstinct May 23 '12 at 12:01
  • I think you forgot about the [structure of HTTP request](http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5). For using `stdin` and before parsing JSON, you need first to parse the request data and get the message part from it. Simply `cgi` module does it for you, so his usage of `stdin` is not really correct and in place. And there is no difference on what content type he used for sending the request. That's what I meant :) Yep, the issue seems to be resolved. – VisioN May 23 '12 at 12:13
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/11641/discussion-between-thrustmaster-and-vision) – UltraInstinct May 23 '12 at 12:15
  • 1
    I have been searching the web to find how to make cgi recognise raw strings NOT sent from an html form but found this solution..my 3 days of late night sleep came to an end..thank you – repzero Nov 27 '15 at 02:04
8

You should read json data like this:

#!/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 can run into problems:

 myjson = json.load(sys.stdin)

or written less succinctly:

requ_body = sys.stdin.read()
my_dict = json.load(requ_body)

That does work for me when my cgi script is on an apache server, but you can't count on that working in general--as I found out when my cgi script was on another server. According to the cgi spec:

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.

The key line is:

the script MUST NOT attempt to read more than CONTENT_LENGTH bytes, even if more data is available.

Apparently, apache sends an eof signal to the cgi script immediately after sending the request body to the cgi script, which causes sys.stdin.read() to return. But according to the cgi spec, a server is not required to send an eof signal after the body of the request, and I found that my cgi script was hanging on sys.stdin.read()--when my script was on another server, which eventually caused a timeout error.

Therefore, in order to read in json data in the general case, you should 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 a bunch of environment variables for cgi scripts, which contain header information, one of which is CONTENT_LENGTH.

Here is what a 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
7stud
  • 46,922
  • 14
  • 101
  • 127
1

Adding a little bit to the great @7stud's answer I had some problems with content length when reading unicode which I fixed by reading from buffer:

content_length = int(os.environ["CONTENT_LENGTH"])
data = sys.stdin.buffer.read(content_length).decode('utf-8')
c6401
  • 91
  • 1
  • 2