Sunday, March 3, 2013

Implementing reCaptcha in Tornado/Python asynchronously

The reCaptcha client for python lacks a proper documentation.
Indeed, there is no need to implement the reCaptcha-client plugin for python/Tornado. We can simply use the AsyncHTTPClient to directly communicate to the reCaptcha API.

Register a reCaptcha on Google. Get the:
- public key
- private key

The reference I followed is this:

Insert this script into template
  <script type="text/javascript" src="http://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>

The below one just to create the reCaptcha object
  Recaptcha.create("your_public_key",
    "element_id",
    {
      theme: "red",
      callback: Recaptcha.focus_response_field
    }
  );
Upon calling, the above code will create an image and 2 input forms, namely "recaptcha_response_field" and "recaptcha_challenge_field". The challenge field is hidden and contains a string to identify the challenge on the Google server.

- Create an ajax to send these 2 inputs to your server.

At the server side:

  
recaptcha_challenge=self.get_argument('recaptcha_challenge','')
  recaptcha_response=self.get_argument('recaptcha_response','')
  recaptcha_http=tornado.httpclient.AsyncHTTPClient()
  recaptcha_priv_key='xxxxxxyyyyyyyyzzzzz'
  remote_addr=self.request.headers['X-Real-Ip']
  recaptcha_req_data={
   'privatekey':recaptcha_priv_key,
   'remoteip':remote_addr,
   'challenge':recaptcha_challenge,
   'response':recaptcha_response
  }
  recaptcha_req_body=urllib.urlencode(recaptcha_req_data)
  
  try:
   recaptcha_http.fetch('http://www.google.com/recaptcha/api/verify', self.callback, method='POST',body=recaptcha_req_body) #https://developers.google.com/recaptcha/docs/verify
  except tornado.httpclient.HTTPError,e:
   print 'Error:',e
- Because the my Tornado is under Nginx proxy, so from the Nginx proxy I added a header (X-Real-IP) to forward the client's IP to Tornadoes.
- The above code will send to Google's API and Google will return a response with 2 lines:

true
xxyyyyzzz

- In the self.callback, you can first split the response by "\n", to obtain true or false value.
- Then you can perform your subsequent operations.

Wednesday, January 2, 2013

Using upload progress module in Nginx with Tornado

Previously I have set up a working upload conf.
I am following the official tutorial, http://wiki.nginx.org/HttpUploadProgressModule

1) Insert these into proper position in your Nginx.conf


upload_progress proxied 1m; #place under http{ ...}


track_uploads proxied 30s; #In my example, my upload button on the html template points to /upload (i.e. location /upload {...} , so I insert it in location /upload {...}


        location ^~ /progress {
            # report uploads tracked in the 'proxied' zone
            report_uploads proxied;
        } #Put this as a new location block


       #For see the effect of upload progress bar, I suggest u temporarily limit the upload rate by add upload_limit_rate 20000 in location /upload {...}



2) In your html template, just copy form the official doc


<form id="upload" enctype="multipart/form-data"
action="/upload.php" method="post" onsubmit="openProgressBar(); return true;">
  <input type="hidden" name="MAX_FILE_SIZE" value="30000000"  />
  <input name="userfile" type="file" label="fileupload" />
  <input type="submit" value="Send File" />
</form>

-- Change ...action="/upload.php"... to your destination


<div>
 <div id="progress" style="width: 400px; border: 1px solid black">
  <div id="progressbar" style="width: 1px; background-color: black; border: 1px solid white"> </div>
 </div>
 <div id="tp">(progress)</div>
</div>

-- Just copy it


 interval = null;

function openProgressBar() {
 /* generate random progress-id */
 uuid = "";
 for (i = 0; i < 32; i++) {
  uuid += Math.floor(Math.random() * 16).toString(16);
 }
 /* patch the form-action tag to include the progress-id */
 document.getElementById("upload").action="/upload.php?X-Progress-ID=" + uuid;

 /* call the progress-updater every 1000ms */
 interval = window.setInterval(
   function () {
     fetch(uuid);
   },
   1000
 );
}

function fetch(uuid) {
 req = new XMLHttpRequest();
 req.open("GET", "/progress", 1);
 req.setRequestHeader("X-Progress-ID", uuid);
 req.onreadystatechange = function () {
  if (req.readyState == 4) {
   if (req.status == 200) {
    /* poor-man JSON parser */
    var upload = eval(req.responseText);

    document.getElementById('tp').innerHTML = upload.state;

    /* change the width if the inner progress-bar */
    if (upload.state == 'done' || upload.state == 'uploading') {
     bar = document.getElementById('progressbar');
     w = 400 * upload.received / upload.size;
     bar.style.width = w + 'px';
    }
    /* we are done, stop the interval */
    if (upload.state == 'done') {
     window.clearTimeout(interval);
    }
   }
  }
 }
 req.send(null);
}

-- Copy the above too and you are done.