说Tornado之前分享几个前端不错的网站:

-- Bootstraphttp://www.bootcss.com/-- Font Awesomehttp://fontawesome.io/-- bxsliderhttp://bxslider.com/-- jQuery EasyUIhttp://www.jeasyui.com/download/index.php-- jQuery UIhttp://jqueryui.com/-- parsleyjs        http://parsleyjs.org/-- jQuery Validate  http://jqueryvalidation.org/

View Code

web框架的本质

总所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
import socketdef request_handler(client):a = client.recv(1024)client.send(bytes("HTTP/1.1 200 OK\r\n\r\n",encoding='utf-8'))client.send(bytes("hello",encoding="utf-8"))def main():sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)sock.bind(("127.0.0.1",8000))sock.listen(5)while True:con,add = sock.accept()request_handler(con)con.close()if __name__ == '__main__':main()

上述通过socket来实现了其本质,而对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

WSGI(Web Server Gateway Interface)是一种规范,它定义了使用python编写的web app与web server之间接口格式,实现web app与web server间的解耦。

python标准库提供的独立WSGI服务器称为wsgiref。

2.x环境运行

from wsgiref.simple_server import make_serverdef RunServer(environ, start_response):start_response('200 OK', [('Content-Type', 'text/html')])return '<h1>Hello, web!</h1>'if __name__ == '__main__':httpd = make_server('', 8888, RunServer)httpd.serve_forever()

自定义Web框架  

#!/usr/bin/env python
# _*_ coding:utf-8 _*_from wsgiref.simple_server import make_serverdef index():return "hi"URLS = {"/index":index,
}def RunServer(environ, start_response): #environ 封装了请求头信息start_response('200 OK', [('Content-Type', 'text/html')])url = environ["PATH_INFO"]if url in URLS.keys():ret=URLS[url]()else:ret = "404"return retif __name__ == '__main__':httpd = make_server('', 8000, RunServer)httpd.serve_forever()

在上一步骤中,对于所有的index返回给用户浏览器一个简单的字符串,在现实的Web请求中一般会返回一个复杂的符合HTML规则的字符串,所以我们一般将要返回给用户的HTML写在指定文件中,然后再返回。如:  

<!DOCTYPE html>
<html>
<headlang="en"><metacharset="UTF-8"><title></title>
</head>
<body><form><inputtype="text" /><inputtype="text" /><inputtype="submit" /></form>
</body>
</html>

返回表单

#!/usr/bin/env python
# _*_ coding:utf-8 _*_from wsgiref.simple_server import make_serverdef login():file_list = open("temp/s2.html",'r')data = file_list.read()return datadef index():file_list = open("temp/s1.html",'r')data = file_list.read()return dataURLS = {"/index":index,"/login":login,
}def RunServer(environ, start_response):start_response('200 OK', [('Content-Type', 'text/html')])url = environ["PATH_INFO"]if url in URLS.keys():ret=URLS[url]()else:ret = "404"return retif __name__ == '__main__':httpd = make_server('', 8000, RunServer)httpd.serve_forever()

对于上述代码,虽然可以返回给用户HTML的内容以现实复杂的页面,但是还是存在问题:如何给用户返回动态内容?  

  • 自定义一套特殊的语法,进行替换
  • 使用开源工具jinja2,遵循其指定语法

<!DOCTYPE html>
<html>
<headlang="en"><metacharset="UTF-8"><title></title>
</head>
<body><h1>{{name}}</h1><ul>{% for item in user_list %}<li>{{item}}</li>{% endfor %}</ul></body>
</html>

index

#!/usr/bin/env python
# _*_ coding:utf-8 _*_from wsgiref.simple_server import make_server
from jinja2 import Templatedef index():file_list = open("temp/s3.html",'r')file_text = file_list.read()temp = Template(file_text)data = temp.render(name="john Doe",user_list = ['tangseng','wukong'])return data.encode("utf-8")def login():file_list = open("temp/s2.html",'r')data = file_list.read()return dataURLS = {"/index":index,"/login":login,
}def RunServer(environ, start_response):start_response('200 OK', [('Content-Type', 'text/html')])url = environ["PATH_INFO"]if url in URLS.keys():ret=URLS[url]()else:ret = "404"return retif __name__ == '__main__':httpd = make_server('', 8000, RunServer)httpd.serve_forever()

遵循jinja2的语法规则,其内部会对指定的语法进行相应的替换,从而达到动态的返回内容

 Tornado 前戏

执行字符串表示的函数,并为该函数提供全局变量

本篇的内容从题目中就可以看出来,就是为之后剖析tornado模板做准备,也是由于该知识点使用的巧妙,所有就单独用一篇来介绍了。废话不多说,直接上代码:

#!usr/bin/env python
#coding:utf-8namespace = {'name':'wukong','data':[18,73,84]}code =  '''def hellocute():return  "name %s ,age %d" %(name,data[0],) '''func = compile(code, '<string>', "exec")exec func in namespaceresult = namespace['hellocute']()

此段代码的执行结果是:name wukong,age 18  

上述代码解析:

  • 第6行,code是一个字符串,该字符串的内容是一个函数体。
  • 第8行,将code字符串编译成函数 hello
  • 第10行,将函数 hello 添加到namespace字典中(key为hello),同时也将python的所有内置函数添加到namespace字段中(key为__builtins__),如此一来,namespace中的内容好比是一个个的全局变量,即
  • 第12行,执行Hello函数并将返回值复制给result
  • 第14行,输入result

name = wupeiqi
data = [18,73,84]def hellocute():return  "name %s ,age %d" %(name,data[0],)

View Code

这段代码用的很是巧妙有木有,亮瞎狗眼有木有,居然把字符串变成了函数并且还为该函数提供了全局变量。对于该功能,它就是python的web框架中模板语言部分至关重要的部分,因为在模板处理过程中,首先会读取html文件,然后分割html文件,再然后讲分割的文件组成一个字符串表示的函数,再再然后就是利用上述方法执行字符串表示的函数。

快速上手Tornado

Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本。这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具 和优化。

Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。(关于如何扩容 服务器,以处理数以千计的客户端的连接的问题,请参阅 C10K problem。)

 一、安装如下:

pip install tornado
源码安装https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz

第一次运行Tornado代码:  

#!/usr/bin/env python
# -*- coding:utf-8 -*-import tornado.ioloop
import tornado.webclass MainHandler(tornado.web.RequestHandler):def get(self):self.write("Hello, world")application = tornado.web.Application([(r"/index", MainHandler),
])if __name__ == "__main__":application.listen(8888)tornado.ioloop.IOLoop.instance().start()

第一步:执行脚本,监听 8888 端口

第二步:浏览器客户端访问 /index  -->  http://127.0.0.1:8888/index

第三步:服务器接受请求,并交由对应的类处理该请求

第四步:类接受到请求之后,根据请求方式(post / get / delete ...)的不同调用并执行相应的方法

第五步:方法返回值的字符串内容发送浏览器

#!/usr/bin/env python
# -*- coding:utf-8 -*-import tornado.ioloop
import tornado.web
from tornado import httpclient
from tornado.web import asynchronous
from tornado import genimport uimodules as md
import uimethods as mtclass MainHandler(tornado.web.RequestHandler):@asynchronous@gen.coroutinedef get(self):print 'start get 'http = httpclient.AsyncHTTPClient()http.fetch("http://127.0.0.1:8008/post/", self.callback)self.write('end')def callback(self, response):print response.bodysettings = {'template_path': 'template','static_path': 'static','static_url_prefix': '/static/','ui_methods': mt,'ui_modules': md,
}application = tornado.web.Application([(r"/index", MainHandler),
], **settings)if __name__ == "__main__":application.listen(8009)tornado.ioloop.IOLoop.instance().start()

异步非阻塞

二、路由系统

路由系统其实就是 url 和 类 的对应关系,这里不同于其他框架,其他很多框架均是 url 对应 函数,Tornado中每个url对应的是一个类。  

#!/usr/bin/env python
# _*_ coding:utf-8 _*_import tornado.ioloop
import tornado.webclass LoginHandler(tornado.web.RequestHandler):def get(self, *args, **kwargs):self.render("s2.html")class IndexHandler(tornado.web.RequestHandler):def get(self, *args, **kwargs):self.render("index.html")
#路由系统
settings = {"template_path":"templates",}application = tornado.web.Application([(r"/index",IndexHandler),(r"/login",LoginHandler),
])if __name__ == '__main__':httpd = application.listen(8888)tornado.ioloop.IOLoop.instance().start()

Tornado中原生支持二级域名的路由,如:

三、模板

Tornao中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。

Tornado 的模板支持“控制语句”和“表达语句”,控制语句是使用 {% 和 %} 包起来的 例如 {% if len(items) > 2 %}。表达语句是使用 {{ 和 }} 包起来的,例如 {{ items[0] }}

控制语句和对应的 Python 语句的格式基本完全相同。我们支持 ifforwhile 和 try,这些语句逻辑结束的位置需要用 {% end %} 做标记。还通过 extends 和 block 语句实现了模板继承。这些在 template 模块 的代码文档中有着详细的描述。

1.基本使用

#!/usr/bin/env python#-*- coding:utf-8 -*-importtornado.ioloopimporttornado.webclassMainHandler(tornado.web.RequestHandler):defget(self):self.render("index.html", list_info = [11,22,33])application=tornado.web.Application([(r"/index", MainHandler),
])if __name__ == "__main__":application.listen(8888)tornado.ioloop.IOLoop.instance().start()

app

<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><title>Title</title><link href="{{static_url("css/common.css")}}" rel="stylesheet" />
</head>
<body><div><ul>{% for item in list_info %}<li>{{item}}</li>{% end %}</ul></div><script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script></body>
</html>

index

在模板中默认提供了一些函数、字段、类以供模板使用:

escape: tornado.escape.xhtml_escape 的別名
xhtml_escape: tornado.escape.xhtml_escape 的別名
url_escape: tornado.escape.url_escape 的別名
json_encode: tornado.escape.json_encode 的別名
squeeze: tornado.escape.squeeze 的別名
linkify: tornado.escape.linkify 的別名
datetime: Python 的 datetime 模组
handler: 当前的 RequestHandler 对象
request: handler.request 的別名
current_user: handler.current_user 的別名
locale: handler.locale 的別名
_: handler.locale.translate 的別名
static_url:forhandler.static_url 的別名
xsrf_form_html: handler.xsrf_form_html 的別名 

其他方法

2.母板

<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><title>老男孩</title><link href="{{static_url("css/common.css")}}" rel="stylesheet" />{% block CSS %}{% end %}</head>
<body><div class="pg-header"></div>{% block RenderBody %}{% end %}<script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>{% block JavaScript %}{% end %}</body>
</html>

layout.html

{% extends 'layout.html'%}
{% block CSS %}<link href="{{static_url("css/index.css")}}" rel="stylesheet" />{% end %}{% block RenderBody %}<h1>Index</h1><ul>{%  for item in li %}<li>{{item}}</li>{% end %}</ul>{% end %}{% block JavaScript %}{% end %}

index.html

3.导入

<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><title>老男孩</title><link href="{{static_url("css/common.css")}}" rel="stylesheet" />
</head>
<body><div class="pg-header">{% include 'header.html' %}</div><script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script></body>
</html>

index.html

4.自定义UIMethod以UIModule

Tornado默认提供的这些功能其实本质上就是 UIMethod 和 UIModule,我们也可以自定义从而实现类似于Django的simple_tag的功能:

1、定义

# uimethods.pydef tab(self):return 'UIMethod'

uimethods.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from tornado.web import UIModule
from tornado import escapeclass custom(UIModule):def render(self, *args, **kwargs):return escape.xhtml_escape('<h1>wukong</h1>')#return escape.xhtml_escape('<h1>wukong</h1>')

uimmodule

注册

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#!/usr/bin/env python
# -*- coding:utf-8 -*-import tornado.ioloop
import tornado.web
from tornado.escape import linkify
import uimodules as md
import uimethods as mtclass MainHandler(tornado.web.RequestHandler):def get(self):self.render('index.html')settings = {'template_path': 'template','static_path': 'static','static_url_prefix': '/static/','ui_methods': mt,'ui_modules': md,
}application = tornado.web.Application([(r"/index", MainHandler),
], **settings)if __name__ == "__main__":application.listen(8009)tornado.ioloop.IOLoop.instance().start()

main.py

使用

<!DOCTYPE html>
<html>
<headlang="en"><metacharset="UTF-8"><title></title><linkhref="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body><h1>hello</h1>{% module custom(123) %}{{ tab() }}</body>

index

四、静态文件

对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且Tornaodo还支持静态文件缓存。

#!/usr/bin/env python
# -*- coding:utf-8 -*-import tornado.ioloop
import tornado.webclass MainHandler(tornado.web.RequestHandler):def get(self):self.render('home/index.html')settings = {'template_path': 'template','static_path': 'static',                  //静态资源的的路径比如 css,js'static_url_prefix': '/static/',       // 静态资源的的 URL 前缀
}application = tornado.web.Application([(r"/index", MainHandler),
], **settings)if __name__ == "__main__":application.listen(80)tornado.ioloop.IOLoop.instance().start()

View Code

备注:静态文件缓存的实现

五、cookie

Tornado中可以对cookie进行操作,并且还可以对cookie进行签名以放置伪造。

a、基本操作

class MainHandler(tornado.web.RequestHandler):def get(self):if not self.get_cookie("mycookie"):self.set_cookie("mycookie", "myvalue")self.write("Your cookie was not set yet!")else:self.write("Your cookie was set!")

View Code

b、加密cookie签名

Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对 cookie 作签名以防止伪造。Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词参数传入应用的设置中:

class MainHandler(tornado.web.RequestHandler):def get(self):if not self.get_secure_cookie("mycookie"):self.set_secure_cookie("mycookie", "myvalue")self.write("Your cookie was not set yet!")else:self.write("Your cookie was set!")application = tornado.web.Application([(r"/", MainHandler),
], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")//随意增加

Code

def _create_signature_v1(secret, *parts):hash= hmac.new(utf8(secret), digestmod=hashlib.sha1)for part inparts:hash.update(utf8(part))returnutf8(hash.hexdigest())def_create_signature_v2(secret, s):hash= hmac.new(utf8(secret), digestmod=hashlib.sha256)hash.update(utf8(s))return utf8(hash.hexdigest())

内部算法

def create_signed_value(secret, name, value, version=None, clock=None,key_version=None):if version isNone:version=DEFAULT_SIGNED_VALUE_VERSIONif clock isNone:clock=time.timetimestamp=utf8(str(int(clock())))value=base64.b64encode(utf8(value))if version == 1:signature=_create_signature_v1(secret, name, value, timestamp)value= b"|".join([value, timestamp, signature])returnvalueelif version == 2:#The v2 format consists of a version number and a series of#length-prefixed fields "%d:%s", the last of which is a#signature, all separated by pipes.  All numbers are in#decimal format with no leading zeros.  The signature is an#HMAC-SHA256 of the whole string up to that point, including#the final pipe.#        #The fields are:#- format version (i.e. 2; no length prefix)#- key version (integer, default is 0)#- timestamp (integer seconds since epoch)#- name (not encoded; assumed to be ~alphanumeric)#- value (base64-encoded)#- signature (hex-encoded; no length prefix)defformat_field(s):return utf8("%d:" % len(s)) +utf8(s)to_sign= b"|".join([b"2",format_field(str(key_versionor0)),format_field(timestamp),format_field(name),format_field(value),b''])ifisinstance(secret, dict):assert key_version is not None, 'Key version must be set when sign key dict is used'assert version >= 2, 'Version must be at least 2 for key version support'secret=secret[key_version]signature=_create_signature_v2(secret, to_sign)return to_sign +signatureelse:raise ValueError("Unsupported version %d" % version)

算法加密

def_decode_signed_value_v1(secret, name, value, max_age_days, clock):parts= utf8(value).split(b"|")if len(parts) != 3:returnNonesignature= _create_signature_v1(secret, name, parts[0], parts[1])if not _time_independent_equals(parts[2], signature):gen_log.warning("Invalid cookie signature %r", value)returnNonetimestamp= int(parts[1])if timestamp < clock() - max_age_days * 86400:gen_log.warning("Expired cookie %r", value)returnNoneif timestamp > clock() + 31 * 86400:#_cookie_signature does not hash a delimiter between the#parts of the cookie, so an attacker could transfer trailing#digits from the payload to the timestamp without altering the#signature.  For backwards compatibility, sanity-check timestamp#here instead of modifying _cookie_signature.gen_log.warning("Cookie timestamp in future; possible tampering %r",value)returnNoneif parts[1].startswith(b"0"):gen_log.warning("Tampered cookie %r", value)returnNonetry:returnbase64.b64decode(parts[0])exceptException:returnNonedef_decode_fields_v2(value):def_consume_field(s):length, _, rest= s.partition(b':')n=int(length)field_value=rest[:n]#In python 3, indexing bytes returns small integers; we must#use a slice to get a byte string as in python 2.if rest[n:n + 1] != b'|':raise ValueError("malformed v2 signed value field")rest= rest[n + 1:]returnfield_value, restrest= value[2:]  #remove version numberkey_version, rest =_consume_field(rest)timestamp, rest=_consume_field(rest)name_field, rest=_consume_field(rest)value_field, passed_sig=_consume_field(rest)returnint(key_version), timestamp, name_field, value_field, passed_sigdef_decode_signed_value_v2(secret, name, value, max_age_days, clock):try:key_version, timestamp, name_field, value_field, passed_sig=_decode_fields_v2(value)exceptValueError:returnNonesigned_string= value[:-len(passed_sig)]ifisinstance(secret, dict):try:secret=secret[key_version]exceptKeyError:returnNoneexpected_sig=_create_signature_v2(secret, signed_string)if not_time_independent_equals(passed_sig, expected_sig):returnNoneif name_field !=utf8(name):returnNonetimestamp=int(timestamp)if timestamp < clock() - max_age_days * 86400:#The signature has expired.returnNonetry:returnbase64.b64decode(value_field)exceptException:returnNonedefget_signature_key_version(value):value=utf8(value)version=_get_version(value)if version < 2:returnNonetry:key_version, _, _, _, _=_decode_fields_v2(value)exceptValueError:returnNonereturn key_version

算法解密

签名Cookie的本质是:

写cookie过程:将值进行base64加密
对除值以外的内容进行签名,哈希算法(无法逆向解析)
拼接 签名 + 加密值
读cookie过程:读取 签名 + 加密值
对签名进行验证
base64解密,获取值内容

注:许多API验证机制和安全cookie的实现机制相同。

#!/usr/bin/env python#-*- coding:utf-8 -*-importtornado.ioloopimporttornado.webclassMainHandler(tornado.web.RequestHandler):defget(self):login_user= self.get_secure_cookie("login_user", None)iflogin_user:self.write(login_user)else:self.redirect('/login')classLoginHandler(tornado.web.RequestHandler):defget(self):self.current_user()self.render('login.html', **{'status': ''})def post(self, *args, **kwargs):username= self.get_argument('name')password= self.get_argument('pwd')if username == 'wukong' and password == '123':self.set_secure_cookie('login_user', '悟空')self.redirect('/')else:self.render('login.html', **{'status': '用户名或密码错误'})settings={'template_path': 'template','static_path': 'static','static_url_prefix': '/static/','cookie_secret': 'aiuasdhflashjdfoiuashdfiuh'}application=tornado.web.Application([(r"/index", MainHandler),(r"/login", LoginHandler),
],**settings)if __name__ == "__main__":application.listen(8888)tornado.ioloop.IOLoop.instance().start()

基于cookie进行用户验证

#!/usr/bin/env python#-*- coding:utf-8 -*-importtornado.ioloopimporttornado.webclassBaseHandler(tornado.web.RequestHandler):defget_current_user(self):return self.get_secure_cookie("login_user")classMainHandler(BaseHandler):@tornado.web.authenticateddefget(self):login_user=self.current_userself.write(login_user)classLoginHandler(tornado.web.RequestHandler):defget(self):self.current_user()self.render('login.html', **{'status': ''})def post(self, *args, **kwargs):username= self.get_argument('name')password= self.get_argument('pwd')if username == 'wukong' and password == '123':self.set_secure_cookie('login_user', '悟空')self.redirect('/')else:self.render('login.html', **{'status': '用户名或密码错误'})settings={'template_path': 'template','static_path': 'static','static_url_prefix': '/static/','cookie_secret': 'aiuasdhflashjdfoiuashdfiuh','login_url': '/login'}application=tornado.web.Application([(r"/index", MainHandler),(r"/login", LoginHandler),
],**settings)if __name__ == "__main__":application.listen(8888)tornado.ioloop.IOLoop.instance().start()

Demo-Toando内部提供基于cookie进行用户验证

3、JavaScript操作Cookie

由于Cookie保存在浏览器端,所以在浏览器端也可以使用JavaScript来操作Cookie。

/*
设置cookie,指定秒数过期*/
function setCookie(name,value,expires){var temp = [];var current_date = new Date();current_date.setSeconds(current_date.getSeconds() + 5);document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString();
}  

对于参数:

  • domain   指定域名下的cookie
  • path       域名下指定url中的cookie
  • secure    https使用

注:jQuery中也有指定的插件 jQuery Cookie 专门用于操作cookie,猛击这里

六、CSRF

Tornado中的夸张请求伪造和Django中的相似,跨站伪造请求(Cross-site request forgery)

限制与POST请求

View Code
普通表单使用
ajax使用

注:Ajax使用时,本质上就是去获取本地的cookie,携带cookie再来发送请求

七、上传文件

1、Form表单上传

<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><title>上传文件</title>
</head>
<body><form id="my_form" name="form" action="/index" method="POST"  enctype="multipart/form-data" ><input name="fff" id="my_file"  type="file" /><input type="submit" value="提交"  /></form>
</body>
</html>

html

#!/usr/bin/env python#-*- coding:utf-8 -*-importtornado.ioloopimporttornado.webclassMainHandler(tornado.web.RequestHandler):defget(self):self.render('index.html')def post(self, *args, **kwargs):file_metas= self.request.files["fff"]#print(file_metas)for meta infile_metas:file_name= meta['filename']with open(file_name,'wb') as up:up.write(meta['body'])settings={'template_path': 'template',
}application=tornado.web.Application([(r"/index", MainHandler),
],**settings)if __name__ == "__main__":application.listen(8000)tornado.ioloop.IOLoop.instance().start()

python

2、AJAX上传

<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title></title>
</head>
<body><input type="file" id="img" /><input type="button" οnclick="UploadFile();" /><script>function UploadFile(){var fileObj= document.getElementById("img").files[0];var form=new FormData();form.append("k1", "v1");form.append("fff", fileObj);var xhr=new XMLHttpRequest();xhr.open("post", '/index', true);xhr.send(form);}</script>
</body>
</html>

HTML - XMLHttpRequest

<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title></title>
</head>
<body><input type="file" id="img" /><input type="button" οnclick="UploadFile();" /><script>function UploadFile(){var fileObj= $("#img")[0].files[0];var form=new FormData();form.append("k1", "v1");form.append("fff", fileObj);$.ajax({type:'POST',url:'/index',data: form,processData: false,// tell jQuery notto process the datacontentType: false,// tell jQuery notto set contentTypesuccess: function(arg){console.log(arg);}})}</script>
</body>
</html>

HTML - jQuery

<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title></title>
</head>
<body><form id="my_form" name="form" action="/index" method="POST"  enctype="multipart/form-data" ><div id="main"><input name="fff" id="my_file"  type="file" /><input type="button" name="action" value="Upload" οnclick="redirect()"/><iframe id='my_iframe' name='my_iframe' src=""  class="hide"></iframe></div></form><script>function redirect(){document.getElementById('my_iframe').onload =Testt;document.getElementById('my_form').target = 'my_iframe';document.getElementById('my_form').submit();}function Testt(ths){var t= $("#my_iframe").contents().find("body").text();console.log(t);}</script>
</body>
</html>

HTML - iframe

#!/usr/bin/env python#-*- coding:utf-8 -*-importtornado.ioloopimporttornado.webclassMainHandler(tornado.web.RequestHandler):defget(self):self.render('index.html')def post(self, *args, **kwargs):file_metas= self.request.files["fff"]#print(file_metas)for meta infile_metas:file_name= meta['filename']with open(file_name,'wb') as up:up.write(meta['body'])settings={'template_path': 'template',
}application=tornado.web.Application([(r"/index", MainHandler),
],**settings)if __name__ == "__main__":application.listen(8000)tornado.ioloop.IOLoop.instance().start()

Python

<script type="text/javascript">$(document).ready(function () {$("#formsubmit").click(function () {var iframe= $('<iframe name="postiframe" id="postiframe" style="display: none"></iframe>');$("body").append(iframe);var form= $('#theuploadform');form.attr("action", "/upload.aspx");form.attr("method", "post");form.attr("encoding", "multipart/form-data");form.attr("enctype", "multipart/form-data");form.attr("target", "postiframe");form.attr("file", $('#userfile').val());form.submit();$("#postiframe").load(function () {iframeContents=this.contentWindow.document.body.innerHTML;$("#textarea").html(iframeContents);});returnfalse;});});</script><form id="theuploadform"><input id="userfile" name="userfile" size="50" type="file" /><input id="formsubmit" type="submit" value="Send File" />
</form><div id="textarea">
</div>

扩展:基于iframe实现Ajax上传示例

八、验证码

验证码原理在于后台自动创建一张带有随机内容的图片,然后将内容通过img标签输出到页面。

安装图像处理模块:

pip3 install pillow

示例截图:

  

验证码Demo源码下载:猛击这里

源码中有两个文件要注意Monaco.ttf和check_code.py 要引入到工程内;demo:

自定义Web组件

一、Session

1、面向对象基础

面向对象中通过索引的方式访问对象,需要内部实现 __getitem__ 、__delitem__、__setitem__方法

#!/usr/bin/env python#-*- coding:utf-8 -*-classFoo(object):def __getitem__(self, key):print  '__getitem__',keydef __setitem__(self, key, value):print '__setitem__',key,valuedef __delitem__(self, key):print '__delitem__',keyobj=Foo()
result= obj['k1']#obj['k2'] = 'wupeiqi'#del obj['k1']

View Code

2、Tornado扩展

Tornado框架中,默认执行Handler的get/post等方法之前默认会执行 initialize方法,所以可以通过自定义的方式使得所有请求在处理前执行操作...

classBaseHandler(tornado.web.RequestHandler):definitialize(self):self.xxoo= "wupeiqi"classMainHandler(BaseHandler):defget(self):print(self.xxoo)self.write('index')classIndexHandler(BaseHandler):defget(self):print(self.xxoo)self.write('index')

View Code

3、session

session其实就是定义在服务器端用于保存用户会话的容器,其必须依赖cookie才能实现。

importhashlibimporttimecontainer={}classSession:def __init__(self, handler):self.handler=handlerself.random_str=Nonedef __genarate_random_str(self):'''用于生成加密字符串:return:'''obj=hashlib.md5()obj.update(bytes(str(time.time()), encoding='utf-8'))random_str=obj.hexdigest()returnrandom_strdef __setitem__(self, key, value):#在container中加入随机字符串#定义专属于自己的数据#在客户端中写入随机字符串#判断,请求的用户是否已有随机字符串if notself.random_str:random_str= self.handler.get_cookie('__kakaka__')if notrandom_str:random_str= self.__genarate_random_str()container[random_str]={}else:#客户端有随机字符串if random_str in container.keys(): #判断字符串是否在container中passelse:random_str= self.__genarate_random_str() #生成字符串container[random_str] = {} #生成专属的字典self.random_str = random_str #self.random_str = asdfasdfasdfasdf
container[self.random_str][key]=valueself.handler.set_cookie("__kakaka__", self.random_str)def __getitem__(self,key):#获取客户端的随机字符串#从container中获取专属于我的数据#专属信息【key】random_str =  self.handler.get_cookie("__kakaka__")if notrandom_str:returnNone#客户端有随机字符串user_info_dict =container.get(random_str,None)if notuser_info_dict:returnNonevalue=user_info_dict.get(key, None)return value

View Code

4、分布式Session

#!/usr/bin/env python#coding:utf-8importsysimportmathfrom bisect importbisectif sys.version_info >= (2, 5):importhashlibmd5_constructor=hashlib.md5else:importmd5md5_constructor=md5.newclassHashRing(object):"""一致性哈希"""def __init__(self,nodes):'''初始化nodes : 初始化的节点,其中包含节点已经节点对应的权重默认每一个节点有32个虚拟节点对于权重,通过多创建虚拟节点来实现如:nodes = [{'host':'127.0.0.1:8000','weight':1},{'host':'127.0.0.1:8001','weight':2},{'host':'127.0.0.1:8002','weight':1},]'''self.ring=dict()self._sorted_keys=[]self.total_weight=0self.__generate_circle(nodes)def __generate_circle(self,nodes):for node_info innodes:self.total_weight+= node_info.get('weight',1)for node_info innodes:weight= node_info.get('weight',1)node= node_info.get('host',None)virtual_node_count= math.floor((32*len(nodes)*weight) /self.total_weight)for i inxrange(0,int(virtual_node_count)):key= self.gen_key_thirty_two( '%s-%s' %(node, i) )if self._sorted_keys.__contains__(key):raise Exception('该节点已经存在.')self.ring[key]=nodeself._sorted_keys.append(key)defadd_node(self,node):'''新建节点node : 要添加的节点,格式为:{'host':'127.0.0.1:8002','weight':1},其中第一个元素表示节点,第二个元素表示该节点的权重。'''node= node.get('host',None)if notnode:raise Exception('节点的地址不能为空.')weight= node.get('weight',1)self.total_weight+=weightnodes_count= len(self._sorted_keys) + 1virtual_node_count= math.floor((32 * nodes_count * weight) /self.total_weight)for i inxrange(0,int(virtual_node_count)):key= self.gen_key_thirty_two( '%s-%s' %(node, i) )if self._sorted_keys.__contains__(key):raise Exception('该节点已经存在.')self.ring[key]=nodeself._sorted_keys.append(key)defremove_node(self,node):'''移除节点node : 要移除的节点 '127.0.0.1:8000''''for key,value inself.ring.items():if value ==node:delself.ring[key]self._sorted_keys.remove(key)defget_node(self,string_key):'''获取 string_key 所在的节点'''pos=self.get_node_pos(string_key)if pos isNone:returnNonereturn self.ring[ self._sorted_keys[pos]].split(':')defget_node_pos(self,string_key):'''获取 string_key 所在的节点的索引'''if notself.ring:returnNonekey=self.gen_key_thirty_two(string_key)nodes=self._sorted_keyspos=bisect(nodes, key)returnposdefgen_key_thirty_two(self, key):m=md5_constructor()m.update(key)return long(m.hexdigest(), 16)defgen_key_sixteen(self,key):b_key= self.__hash_digest(key)return self.__hash_val(b_key, lambdax: x)def __hash_val(self, b_key, entry_fn):return (( b_key[entry_fn(3)] << 24)|(b_key[entry_fn(2)] << 16)|(b_key[entry_fn(1)] << 8)|b_key[entry_fn(0)] )def __hash_digest(self, key):m=md5_constructor()m.update(key)returnmap(ord, m.digest())"""nodes = [{'host':'127.0.0.1:8000','weight':1},{'host':'127.0.0.1:8001','weight':2},{'host':'127.0.0.1:8002','weight':1},
]ring = HashRing(nodes)
result = ring.get_node('98708798709870987098709879087')
print result"""

一致性哈西

from hashlib importsha1importos, timecreate_session_id= lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest()classSession(object):session_id= "__sessionId__"def __init__(self, request):session_value=request.get_cookie(Session.session_id)if notsession_value:self._id=create_session_id()else:self._id=session_valuerequest.set_cookie(Session.session_id, self._id)def __getitem__(self, key):#根据 self._id ,在一致性哈西中找到其对应的服务器IP#找到相对应的redis服务器,如: r = redis.StrictRedis(host='localhost', port=6379, db=0)#使用python redis api 链接#获取数据,即:#return self._redis.hget(self._id, name)def __setitem__(self, key, value):#根据 self._id ,在一致性哈西中找到其对应的服务器IP#使用python redis api 链接#设置session#self._redis.hset(self._id, name, value)def __delitem__(self, key):#根据 self._id 找到相对应的redis服务器#使用python redis api 链接#删除,即:return self._redis.hdel(self._id, name)

session

二、表单验证

在Web程序中往往包含大量的表单验证的工作,如:判断输入是否为空,是否符合规则。

<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title></title><link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body><h1>hello</h1><form action="/index" method="post"><p>hostname: <input type="text" name="host" /> </p><p>ip: <input type="text" name="ip" /> </p><p>port: <input type="text" name="port" /> </p><p>phone: <input type="text" name="phone" /> </p><input type="submit" /></form>
</body>
</html>

HTML

#!/usr/bin/env python#-*- coding:utf-8 -*-importtornado.ioloopimporttornado.webfrom hashlib importsha1importos, timeimportreclassMainForm(object):def __init__(self):self.host= "(.*)"self.ip= "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"self.port= '(\d+)'self.phone= '^1[3|4|5|8][0-9]\d{8}$'defcheck_valid(self, request):form_dict= self.__dict__for key, regular inform_dict.items():post_value=request.get_argument(key)#让提交的数据 和 定义的正则表达式进行匹配ret =re.match(regular, post_value)printkey,ret,post_valueclassMainHandler(tornado.web.RequestHandler):defget(self):self.render('index.html')def post(self, *args, **kwargs):obj=MainForm()result=obj.check_valid(self)self.write('ok')settings={'template_path': 'template','static_path': 'static','static_url_prefix': '/static/','cookie_secret': 'aiuasdhflashjdfoiuashdfiuh','login_url': '/login'}application=tornado.web.Application([(r"/index", MainHandler),
],**settings)if __name__ == "__main__":application.listen(8888)tornado.ioloop.IOLoop.instance().start()

Python

由于验证规则可以代码重用,所以可以如此定义:

#!/usr/bin/env python#-*- coding:utf-8 -*-importtornado.ioloopimporttornado.webimportreclassField(object):def __init__(self, error_msg_dict, required):self.id_valid=Falseself.value=Noneself.error=Noneself.name=Noneself.error_msg=error_msg_dictself.required=requireddefmatch(self, name, value):self.name=nameif notself.required:self.id_valid=Trueself.value=valueelse:if notvalue:if self.error_msg.get('required', None):self.error= self.error_msg['required']else:self.error= "%s is required" %nameelse:ret=re.match(self.REGULAR, value)ifret:self.id_valid=Trueself.value=ret.group()else:if self.error_msg.get('valid', None):self.error= self.error_msg['valid']else:self.error= "%s is invalid" %nameclassIPField(Field):REGULAR= "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"def __init__(self, error_msg_dict=None, required=True):error_msg= {}  #{'required': 'IP不能为空', 'valid': 'IP格式错误'}iferror_msg_dict:error_msg.update(error_msg_dict)super(IPField, self).__init__(error_msg_dict=error_msg, required=required)classIntegerField(Field):REGULAR= "^\d+$"def __init__(self, error_msg_dict=None, required=True):error_msg= {'required': '数字不能为空', 'valid': '数字格式错误'}iferror_msg_dict:error_msg.update(error_msg_dict)super(IntegerField, self).__init__(error_msg_dict=error_msg, required=required)classCheckBoxField(Field):def __init__(self, error_msg_dict=None, required=True):error_msg= {}  #{'required': 'IP不能为空', 'valid': 'IP格式错误'}iferror_msg_dict:error_msg.update(error_msg_dict)super(CheckBoxField, self).__init__(error_msg_dict=error_msg, required=required)defmatch(self, name, value):self.name=nameif notself.required:self.id_valid=Trueself.value=valueelse:if notvalue:if self.error_msg.get('required', None):self.error= self.error_msg['required']else:self.error= "%s is required" %nameelse:ifisinstance(name, list):self.id_valid=Trueself.value=valueelse:if self.error_msg.get('valid', None):self.error= self.error_msg['valid']else:self.error= "%s is invalid" %nameclassFileField(Field):REGULAR= "^(\w+\.pdf)|(\w+\.mp3)|(\w+\.py)$"def __init__(self, error_msg_dict=None, required=True):error_msg= {}  #{'required': '数字不能为空', 'valid': '数字格式错误'}iferror_msg_dict:error_msg.update(error_msg_dict)super(FileField, self).__init__(error_msg_dict=error_msg, required=required)defmatch(self, name, value):self.name=nameself.value=[]if notself.required:self.id_valid=Trueself.value=valueelse:if notvalue:if self.error_msg.get('required', None):self.error= self.error_msg['required']else:self.error= "%s is required" %nameelse:m=re.compile(self.REGULAR)ifisinstance(value, list):for file_name invalue:r=m.match(file_name)ifr:self.value.append(r.group())self.id_valid=Trueelse:self.id_valid=Falseif self.error_msg.get('valid', None):self.error= self.error_msg['valid']else:self.error= "%s is invalid" %namebreakelse:if self.error_msg.get('valid', None):self.error= self.error_msg['valid']else:self.error= "%s is invalid" %namedef save(self, request, upload_path=""):file_metas=request.files[self.name]for meta infile_metas:file_name= meta['filename']with open(file_name,'wb') as up:up.write(meta['body'])classForm(object):def __init__(self):self.value_dict={}self.error_dict={}self.valid_status=Truedef validate(self, request, depth=10, pre_key=""):self.initialize()self.__valid(self, request, depth, pre_key)definitialize(self):passdef __valid(self, form_obj, request, depth, pre_key):"""验证用户表单请求的数据:param form_obj: Form对象(Form派生类的对象):param request: Http请求上下文(用于从请求中获取用户提交的值):param depth: 对Form内容的深度的支持:param pre_key: Html中name属性值的前缀(多层Form时,内部递归时设置,无需理会):return: 是否验证通过,True:验证成功;False:验证失败"""depth-= 1if depth <0:returnNoneform_field_dict= form_obj.__dict__for key, field_obj inform_field_dict.items():printkey,field_objif isinstance(field_obj, Form) orisinstance(field_obj, Field):ifisinstance(field_obj, Form):#获取以key开头的所有的值,以参数的形式传至self.__valid(field_obj, request, depth, key)continueifpre_key:key= "%s.%s" %(pre_key, key)ifisinstance(field_obj, CheckBoxField):post_value=request.get_arguments(key, None)elifisinstance(field_obj, FileField):post_value=[]file_list=request.request.files.get(key, None)for file_item infile_list:post_value.append(file_item['filename'])else:post_value=request.get_argument(key, None)printpost_value#让提交的数据 和 定义的正则表达式进行匹配
field_obj.match(key, post_value)iffield_obj.id_valid:self.value_dict[key]=field_obj.valueelse:self.error_dict[key]=field_obj.errorself.valid_status=FalseclassListForm(object):def __init__(self, form_type):self.form_type=form_typeself.valid_status=Trueself.value_dict={}self.error_dict={}defvalidate(self, request):name_list= request.request.arguments.keys() +request.request.files.keys()index=0flag=FalsewhileTrue:pre_key= "[%d]" %indexfor name inname_list:ifname.startswith(pre_key):flag=Truebreakifflag:form_obj=self.form_type()form_obj.validate(request, depth=10, pre_key="[%d]" %index)ifform_obj.valid_status:self.value_dict[index]=form_obj.value_dictelse:self.error_dict[index]=form_obj.error_dictself.valid_status=Falseelse:breakindex+= 1flag=FalseclassMainForm(Form):def __init__(self):#self.ip = IPField(required=True)#self.port = IntegerField(required=True)#self.new_ip = IPField(required=True)#self.second = SecondForm()self.fff = FileField(required=True)super(MainForm, self).__init__()#
#class SecondForm(Form):#
#def __init__(self):#self.ip = IPField(required=True)#self.new_ip = IPField(required=True)#
#super(SecondForm, self).__init__()classMainHandler(tornado.web.RequestHandler):defget(self):self.render('index.html')def post(self, *args, **kwargs):#for i in  dir(self.request):#print i#print self.request.arguments#print self.request.files#print self.request.query#name_list = self.request.arguments.keys() + self.request.files.keys()#print name_list#list_form = ListForm(MainForm)#list_form.validate(self)#        #print list_form.valid_status#print list_form.value_dict#print list_form.error_dict#obj = MainForm()#obj.validate(self)#        #print "验证结果:", obj.valid_status#print "符合验证结果:", obj.value_dict#print "错误信息:"#for key, item in obj.error_dict.items():#print key,item#print self.get_arguments('favor'),type(self.get_arguments('favor'))#print self.get_argument('favor'),type(self.get_argument('favor'))#print type(self.get_argument('fff')),self.get_argument('fff')#print self.request.files#obj = MainForm()#obj.validate(self)#print obj.valid_status#print obj.value_dict#print obj.error_dict#print self.request,type(self.request)#obj.fff.save(self.request)#from tornado.httputil import HTTPServerRequest#name_list = self.request.arguments.keys() + self.request.files.keys()#print name_list#print self.request.files,type(self.request.files)#print len(self.request.files.get('fff'))#obj = MainForm()#obj.validate(self)#print obj.valid_status#print obj.value_dict#print obj.error_dict#obj.fff.save(self.request)self.write('ok')settings={'template_path': 'template','static_path': 'static','static_url_prefix': '/static/','cookie_secret': 'aiuasdhflashjdfoiuashdfiuh','login_url': '/login'}application=tornado.web.Application([(r"/index", MainHandler),
],**settings)if __name__ == "__main__":application.listen(8888)tornado.ioloop.IOLoop.instance().start()

View Code

转载于:https://www.cnblogs.com/yyyg/p/5690427.html

python web框架之Tornado相关推荐

  1. Python Web 框架:Tornado

    版权声明:如需转载本文章,请保留出处! https://blog.csdn.net/xc_zhou/article/details/80637714 1.Tornado Tornado:python编 ...

  2. python twisted和flask_浅谈Python Web 框架:Django, Twisted, Tornado, Flask, Cyclone 和 Pyramid...

    Django 是一个高级的 Python Web 框架,支持快速开发,简洁.实用的设计.如果你正在建一个和电子商务网站相似的应用,那你应该选择用 Django 框架.它能使你快速完成工作,也不必担心太 ...

  3. Python Web框架Tornado的异步处理代码演示样例

    1. What is Tornado Tornado是一个轻量级但高性能的Python web框架,与还有一个流行的Python web框架Django相比.tornado不提供操作数据库的ORM接口 ...

  4. python web框架介绍对比

    Django Python框架虽然说是百花齐放,但仍然有那么一家是最大的,它就是Django.要说Django是Python框架里最好的,有人同意也有人 坚决反对,但说Django的文档最完善.市场占 ...

  5. python写web难受-(2017)你最不建议使用的Python Web框架?

    原标题:(2017)你最不建议使用的Python Web框架? 前言 这是一篇发在知乎专栏的文章,内容是之前我提的「(2017)你最不建议使用的Python Web框架?」下的回答.不过有些话没有说完 ...

  6. python的web框架哪个最好学_2018年要学习的10大Python Web框架

    通过为开发人员提供应用程序开发结构,框架使开发人员的生活更轻松.他们自动执行通用解决方案,缩短开发时间,并允许开发人员更多地关注应用程序逻辑而不是常规元素. 在本文中,我们分享了我们自己的前十大Pyt ...

  7. 第9章 Python Web 框架考察点

    Python WSGI与web框架常考点 WSGI 常见Web框架 ◆什么是WSGI? ◆常用的 Python Web框架Dang/Fask/Tornado对比 ◆web框架的组成(淡化框架,加强基础 ...

  8. 属于web框架的python库_(2017)我不建议使用的Python Web框架

    昨天我提了一个问题 (2017)你最不建议使用的Python Web框架? 下面是在原问题下的回答,看过的同学可以点赞关闭了. --- 分割线 --- 在知乎可以看到的很多Web框架推荐,有很多内容已 ...

  9. 浅谈五大Python Web框架

    http://www.csdn.net/article/2011-02-17/292058 导读:作者飞龙写了一篇<浅谈Python Web框架>,文中他介绍了几个Python Web框架 ...

最新文章

  1. java处理中文字符_Java中文字符处理的四大迷题
  2. tf.keras.layers.MaxPool2D 最大池化层 示例
  3. HTML标签大全(三)
  4. angular5项目端口冲突之解决办法
  5. Spring 事务方法与非事务方法相互调用 @Transactional 注解失效不回滚?
  6. Fuschia 是什么样的一个操作系统
  7. 【bug】VUE:Cannot read property '_withTask' of undefined
  8. [转]VC6 STLport-5.1.4 编译 安装
  9. network 关于PV,网站访问量和服务器带宽的选择
  10. Python+uiautomation 查找微信僵尸粉
  11. IT 工作者如何写交接文档
  12. 微信小程序开发(1)—— 微信开发者工具
  13. 1976年图灵奖--米凯尔·拉宾和达纳·斯科特简介
  14. [Inception V1]赫布学习理论(Hebbian theory)
  15. c++ | 尝试攥写头文件遇到的坑
  16. UVa10158 War(并查集)
  17. 【 爬虫解决了什么问题】
  18. 测试用例设计 通用原则
  19. 进阶课程⑩ | Apollo地图采集方案
  20. 斗地主吗?能学区块链那种! | 原力计划

热门文章

  1. python中list函数中variables变量_如何在Python中使用变量,浅谈,Pytorch,的,Variable,方法...
  2. cursor用法java,Cursor的基本使用方法
  3. 读取外部配置文件_SpringBoot外部配置、优先级及配置详解
  4. rocketmq queue_RocketMQ 实战(三) - 消息的有序性
  5. echart 数据点可以加链接吗_地理可视化就这么简单、酷炫,蚂蚁金服AntV 空间数据可视化引擎 L72.0发布...
  6. JS数据结构与算法——选择排序(把小的数字依次往前放)
  7. 五、pink老师的学习笔记——CSS精灵技术(sprite)
  8. LeetCode MySQL 1075. 项目员工 I
  9. 程序员面试金典 - 面试题 10.03. 搜索旋转数组(二分查找)
  10. LeetCode 543. 二叉树的直径(DFS)