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.