Python Web Server Tornado Usage Summary

Source: Internet
Author: User
Tags ord
The first thing to say is its security, which really makes me feel its good intentions. This can be divided into two main points:

One, the prevention of cross-site forgery request (Cross-site request forgery, referred to as CSRF or XSRF)

The CSRF means simply that an attacker forges a real user to send a request.

For example, suppose a bank website has such a URL:
Http://bank.example.com/withdraw?amount=1000000&for=Eve
When the user of this bank website accesses the URL, it will give Eve the user 1 million yuan. The user will certainly not be able to click this URL easily, but an attacker could embed a fake image on another site and set the image address to that URL:

Then when the user visits the malicious site, the browser will initiate a GET request to the URL, so without the user's knowledge, 1 million was transferred away.

It is easy to protect against these attacks by not allowing a GET request to perform a change operation (such as a transfer). However, other types of requests are still unsafe, if the attacker constructs a form:

The code is as follows:

The user of the unknown truth clicked the "Forward" button, and the money was transferred away ...

To eliminate this situation, you need to add a field that an attacker cannot forge when a non-GET request is processed, and verify that the field has been modified when processing the request.
Tornado's approach is simple, adding a randomly generated _xsrf field to the request, and adding this field to the cookie, which compares the values of these 2 fields when the request is received.
Since non-site web pages are unable to obtain or modify cookies, this ensures that _XSRF cannot be forged by third party websites (HTTP sniffing exceptions).
Of course, the user is free to obtain and modify the cookie, but this does not belong to the scope of CSRF: The user to forge their own things to do, of course, his own to bear.

To use this feature, you need to add the Xsrf_cookies=true parameter when generating the Tornado.web.Application object, which will give the User a cookie field named _XSRF.
You also need to add xsrf_form_html () to a form that is not a GET request, and if you don't use a Tornado template, you can generate it with self.xsrf_form_html () inside the Tornado.web.RequestHandler.

For AJAX requests, there is basically no need to worry about cross-site, so Tornado 1.1.1 Previous versions do not validate requests with x-requested-with:xmlhttprequest.
Later, Google's engineers pointed out that malicious browser plug-ins can forge cross-domain AJAX requests, so it should also be validated. I am not in the right, because the browser plugin can be very large permissions, fake cookies or directly submit the form is OK.
But the solution is still to say, in fact, just get the _xsrf field from the cookie, and then add this parameter to the AJAX request, or put it in the X-xsrftoken or X-csrftoken request. If you have trouble, you can use JQuery's $.ajaxsetup () to deal with it:

The code is as follows:


$.ajaxsetup ({
Beforesend:function (JQXHR, settings) {
Type = Settings.type
if (type! = ' GET ' && type! = ' HEAD ' && type! = ' OPTIONS ') {
var pattern =/(. +; *)? _xsrf *= * ([^; "] +)/;
var xsrf = pattern.exec (Document.cookie);
if (XSRF) {
Jqxhr.setrequestheader (' X-xsrftoken ', xsrf[2]);
}
}
}});

Also talk about cross-site scripting (Cross-site scripting, referred to as XSS). In contrast to CSRF, XSS is exploiting the vulnerability of the compromised Web site to inject script code that an attacker would like to execute to allow users to browse the site.
However, as long as the user is not allowed to enter HTML (for example, < and > escaped), the attributes of the HTML elements are verified (for example, attribute quotes to escape, src and event processing properties can not fill in the JavaScript code, etc.), and check the CSS (including the Style property You can avoid the expression in the.

Second, to prevent the forgery of cookies.

The aforementioned CSRF and XSS are the attackers who, without the user's knowledge, run their own names, while the forged cookie is the attacker's own initiative to forge other users to do the work.
For example, suppose the login verification of a Web site is to check the user name in a cookie, and as long as it is compliant, it is assumed that the user is logged in. Then an attacker could impersonate an administrator by setting a value such as username=admin in a cookie.

To prevent a cookie from being forged, you first need to mention the two parameters when setting the cookie: Secure and HttpOnly. These two parameters are not in the argument list of Tornado.web.RequestHandler.set_cookie (), but are passed as keyword arguments and are defined in cookie.morsel._reserved.
The former means that the cookie can only be passed through a secure connection (that is, HTTPS), which makes it impossible for the sniffer to intercept the cookie, which requires it to be accessed only under the HTTP protocol (that is, the field in Document.cookie cannot be obtained through JavaScript). and is not sent to the server via the HTTP protocol after Setup, which makes it impossible for an attacker to simply forge cookies through JavaScript scripts.

However, for a malicious attacker, these two parameters do not prevent the cookie from being forged. To do this, you need to make a signature on the cookie, and once it is modified, the server can determine it.
The Set_secure_cookie () method is provided in Tornado to sign the cookie. The signature needs to provide a string of keys (the Cookie_secret parameter when generating the Tornado.web.Application object), which can be generated by the following code:
Base64.b64encode (Uuid.uuid4 (). Bytes + uuid.uuid4 (). bytes)
This parameter can be randomly generated, but if there are multiple Tornado processes at the same time to serve, or sometimes restart, it is better to share a constant, and be careful not to divulge it.

This signature uses the HMAC algorithm, and the hash algorithm uses the SHA1. Simply put the cookie name, value and timestamp hash as the signature, and then the "value | timestamp | signature" as the new value. So that the server side as long as the secret key encryption again, compare the signature whether there is a change can be judged authenticity.
It is worth mentioning that reading the source also found such a function:
Def _time_independent_equals (A, B):
If Len (a)! = Len (b):
Return False
result = 0
If Type (a[0]) is int: # Python3 byte strings
For x, y in Zip (A, B):
Result |= x ^ y
else: # Python2
For x, y in Zip (A, B):
Result |= Ord (x) ^ ord (y)
return result = = 0
Read for a long time also did not find and ordinary string comparison has any advantage, until looked at the answer on the StackOverflow to know: In order to avoid the attackers to test the time to determine the correct number of digits, this function to compare the times more constant, also to eliminate this situation. (saying that the answer to my various admiration ah, security experts are not so superficial ... )

Thirdly, inherit the Tornado.web.RequestHandler.

On the execution process, Tornado.web.Application looks for a matching RequestHandler class based on the URL and initializes it. Its __init__ () method calls the Initialize () method, so you can overwrite the latter, and you do not need to call the parent class's Initialize ().
Then, according to different HTTP methods to find the handler get/post () and other methods, and run prepare () before execution. None of these methods are actively invoking the parent class, so call it yourself when necessary.
Finally, the handler's finish () method is called, which is best not to overwrite. It calls the On_finish () method, which can be overridden to handle some aftermath (such as closing a database connection), but no longer sending data to the browser (because the HTTP response has been sent and the connection may have been closed).

By the way, how to handle the error page.
In a nutshell, any RequestHandler error is captured by its write_error () method when executing the _execute () method (which executes the prepare (), the Get (), and the finish (). Therefore, this method can be overridden:

The code is as follows:

Class RequestHandler (Tornado.web.RequestHandler):
def write_error (self, Status_code, **kwargs):
if Status_code = = 404:
Self.render (' 404.html ')
Elif Status_code = = 500:
Self.render (' 500.html ')
Else
Super (RequestHandler, self). Write_error (Status_code, **kwargs)


For historical reasons, you can also overwrite the get_error_html () method, but it is not recommended.
In addition, you may not get to the _execute () method to make a mistake.
For example, the Initialize () method throws an unhandled exception that is caught by IOStream, then closes the connection directly and cannot output any error page to the user.
For example, if you do not find a handler that can handle the request, you will use Tornado.web.ErrorHandler to handle the 404 error. In this case, you can replace this class to implement a custom error page:

The code is as follows:

Class Pagenotfoundhandler (RequestHandler):
def get (self):
Raise Tornado.web.HTTPError (404)

Tornado.web.ErrorHandler = Pagenotfoundhandler


Another method is to add a handler that captures any URL at the end of the handlers parameter of the application:

The code is as follows:

application = Tornado.web.Application ([
# ...
('. * ', Pagenotfoundhandler)
])


Four, then talk about processing login.

Tornado provides @tornado. web.authenticated this adorner, which is added before the handler get () method.
It relies on three codes:
You need to define the Get_current_user () method for handler, for example:

The code is as follows:

def get_current_user (self):
Return Self.get_secure_cookie (' user_id ', 0)


When the return value is false, it jumps to the login page.
Set the Login_url parameter when creating application:

The code is as follows:

application = Tornado.web.Application (
[
# ...
],
Login_url = '/login '
)


Defines the Get_login_url () method for handler.
You can override the Get_login_url () method if you cannot use the default Login_url parameter (for example, normal users and administrators require a different logon address):

The code is as follows:

Class Adminhandler (RequestHandler):
def get_login_url (self):
Return '/admin/login '


Incidentally, when you jump to the login page, a next parameter is attached, pointing to the URL you visited before logging in. To achieve a better user experience, you need to jump to that URL after logging in:

The code is as follows:

Class Loginhandler (RequestHandler):
def get (self):
If Self.get_current_user ():
Self.redirect ('/')
Return
Self.render (' login.html ')

Def post (self):
If Self.get_current_user ():
Raise Tornado.web.HTTPError (403)
# Check username and password
If success:
Self.redirect (Self.get_argument (' Next ', '/'))


In addition, I have used AJAX technology in many places, and the front end is too lazy to handle 403 errors, so I can only change the authenticated ():

The code is as follows:

Def authenticated (method):
"" "Decorate methods with the require, that the user is logged in." "
@functools. Wraps (method)
def wrapper (self, *args, **kwargs):
If not self.current_user:
If Self.request.headers.get (' x-requested-with ') = = ' XMLHttpRequest ': # JQuery and other libraries will be attached to this header
Self.set_header (' Content-type ', ' Application/json; Charset=utf-8 ')
Self.write (Json.dumps ({' Success ': False, ' msg ': U ' your session has expired, please login again!) '}))
Return
If Self.request.method in ("GET", "HEAD"):
url = Self.get_login_url ()
If "?" Not in URL:
If Urlparse.urlsplit (URL). Scheme:
# If login URL is absolute, make next absolute too
Next_url = Self.request.full_url ()
Else
Next_url = Self.request.uri
URL + = "?" + Urllib.urlencode (Dict (Next=next_url))
Self.redirect (URL)
Return
Raise Tornado.web.HTTPError (403)
Return method (self, *args, **kwargs)
Return wrapper

Five, then say get the IP address of the user.

Simply put, you can get it with self.request.remote_ip in the handler method.
However, if you use a reverse proxy, get the proxy IP, this time you need to add xheaders when creating Httpserver:

The code is as follows:

if __name__ = = ' __main__ ':
From Tornado.httpserver import Httpserver
From Tornado.netutil import bind_sockets

sockets = Bind_sockets (80)
Server = Httpserver (application, Xheaders=true)
Server.add_sockets (Sockets)
Tornado.ioloop.IOLoop.instance (). Start ()


In addition, I only need to deal with IPV4, but the local test will get:: 1 This IPv6 address, so also need to set up:

The code is as follows:

If settings. Ipv4_only:
Import socket
sockets = Bind_sockets (family=socket.af_inet)
Else
sockets = Bind_sockets (80)

Finally, how to improve the performance under the production environment.

Tornado can create multiple child processes before Httpserver calls Add_sockets (), taking advantage of multiple CPUs to handle concurrent requests.

In short, the code looks like this:

The code is as follows:

if __name__ = = ' __main__ ':
If settings. Ipv4_only:
Import socket
sockets = Bind_sockets (family=socket.af_inet)
Else
sockets = Bind_sockets (80)
If not settings. Debug_mode:
Import tornado.process
Tornado.process.fork_processes (0) # 0 indicates the number of child processes created by number of CPUs
Server = Httpserver (application, Xheaders=true)
Server.add_sockets (Sockets)
Tornado.ioloop.IOLoop.instance (). Start ()


Note In this way, the Autoreload feature cannot be enabled (the debug parameter cannot be true when application is created).
  • Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.