目录:Tornado其他篇

01: tornado基础篇

02: tornado进阶篇

03: 自定义异步非阻塞tornado框架

04: 打开tornado源码剖析处理过程

目录:

1.1 tornado处理的两个阶段:启动程序阶段 & 处理请求阶段返回顶部

  1、第一阶段:启动程序阶段(也叫待处理请求阶段)

      1. 第一步,获取配置文件然后生成url映射(即:一个url对应一个XXRequestHandler,从而让XXRequestHandler来处理指定url发送的请求);

      2. 第二步,创建服务器socket对象并添加到epoll中;

      3. 第三步,创建无线循环去监听epoll,当请求到达时交由HttpServer类的_handle_events方法处理请求

  2、第二阶段:接收并处理请求阶段

      1. 第一步,接收客户端socket发送的请求(socket.accept);

      2. 第二步,从请求中获取请求头信息,再然后根据请求头中的请求url去匹配某个XXRequestHandler;

      3. 第三步,匹配成功的XXRequestHandler处理请求;

      4. 第四步,将处理后的请求发送给客户端;

      5. 第五步,关闭客户端socket。

  3、tornado使用最基本代码

import tornado.ioloop
import tornado.web #1、 处理访问/index/的get请求: http://127.0.0.1:8888/index/
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("I am index!!") #2、 配置settings
settings = {
'template_path': 'template', # 配置html文件模板位置
'static_path': 'static', # 配置静态文件路径(图片等)
'static_url_prefix': '/static/', # 前端引入静态文件路径
} #3、路由系统
application = tornado.web.Application([
(r"/index/", MainHandler),
],**settings) #4、启动这个tornado这个程序
if __name__ == "__main__":
application.listen(8888)
print('http://127.0.0.1:8888/index/')
tornado.ioloop.IOLoop.instance().start()

tornado使用最基本代码

1.2 application = tornado.web.Application([(xxx,xxx)]) : 启动程序阶段(第一步)返回顶部

   1、此句代码处理过程

      1. 执行Application类的构造函数,并传入一个列表类型的参数,这个列表里保存的是url规则和对应的处理类

      2. 即:当客户端的请求url可以配置这个规则时,那么该请求就交由对应的Handler去执行

      3. 注意:Handler泛指继承自RequestHandler的所有类,Handlers泛指继承自RequestHandler的所有类的集合

  2、处理此句代码对应源码

      1. Application.__init__:加载配置信息和生成url映射,并且把所有的信息封装在一个application对象中。

      2. Application.add_handlers: 遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中

      3. URLSpec : 接收传入的url映射,生成URLSpec类

class Application(object):
def __init__(self, handlers=None, default_host="", transforms=None, wsgi=False, **settings):
# 设置响应的编码和返回方式,对应的http响应头:Content-Encoding和Transfer-Encoding
# Content-Encoding:gzip 表示对数据进行压缩,然后再返回给用户,从而减少流量的传输。
# Transfer-Encoding:chunck 表示数据的传送方式通过一块一块的传输。
if transforms is None:
self.transforms = []
if settings.get("gzip"):
self.transforms.append(GZipContentEncoding)
self.transforms.append(ChunkedTransferEncoding)
else:
self.transforms = transforms
# 将参数赋值为类的变量
self.handlers = []
self.named_handlers = {}
self.default_host = default_host
self.settings = settings
# ui_modules和ui_methods用于在模版语言中扩展自定义输出
# 这里将tornado内置的ui_modules和ui_methods添加到类的成员变量self.ui_modules和self.ui_methods中
self.ui_modules = {'linkify': _linkify,
'xsrf_form_html': _xsrf_form_html,
'Template': TemplateModule,
}
self.ui_methods = {}
self._wsgi = wsgi
# 获取获取用户自定义的ui_modules和ui_methods,并将他们添加到之前创建的成员变量self.ui_modules和self.ui_methods中
self._load_ui_modules(settings.get("ui_modules", {}))
self._load_ui_methods(settings.get("ui_methods", {})) # 设置静态文件路径,设置方式则是通过正则表达式匹配url,让StaticFileHandler来处理匹配的url
if self.settings.get("static_path"):
# 从settings中读取key为static_path的值,用于设置静态文件路径
path = self.settings["static_path"]
# 获取参数中传入的handlers,如果空则设置为空列表
handlers = list(handlers or [])
# 静态文件前缀,默认是/static/
static_url_prefix = settings.get("static_url_prefix", "/static/")
# 在参数中传入的handlers前再添加三个映射:
# 【/static/.*】 --> StaticFileHandler
# 【/(favicon\.ico)】 --> StaticFileHandler
# 【/(robots\.txt)】 --> StaticFileHandler
handlers = [
(re.escape(static_url_prefix) + r"(.*)", StaticFileHandler, dict(path=path)),
(r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
(r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
] + handlers
# 执行本类的Application的add_handlers方法
# 此时,handlers是一个列表,其中的每个元素都是一个对应关系,即:url正则表达式和处理匹配该正则的url的Handler
if handlers: self.add_handlers(".*$", handlers) # Automatically reload modified modules
# 如果settings中设置了 debug 模式,那么就使用自动加载重启
if self.settings.get("debug") and not wsgi:
import autoreload
autoreload.start()

Application.__init__

class Application(object):
def add_handlers(self, host_pattern, host_handlers):
# 如果主机模型最后没有结尾符,那么就为他添加一个结尾符。
if not host_pattern.endswith("$"):
host_pattern += "$"
handlers = []
# 对主机名先做一层路由映射,例如:http://www.wupeiqi.com 和 http://safe.wupeiqi.com
# 即:safe对应一组url映射,www对应一组url映射,那么当请求到来时,先根据它做第一层匹配,之后再继续进入内部匹配。 # 对于第一层url映射来说,由于.*会匹配所有的url,所将 .* 的永远放在handlers列表的最后,不然 .* 就会截和了...
# re.complie是编译正则表达式,以后请求来的时候只需要执行编译结果的match方法就可以去匹配了
if self.handlers and self.handlers[-1][0].pattern == '.*$':
self.handlers.insert(-1, (re.compile(host_pattern), handlers))
else:
self.handlers.append((re.compile(host_pattern), handlers)) # 遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中(构造函数中会对url进行编译)
# 并将所有的URLSpec对象添加到handlers列表中,而handlers列表和主机名模型组成一个元祖,添加到self.Handlers列表中。
for spec in host_handlers:
if type(spec) is type(()):
assert len(spec) in (2, 3)
pattern = spec[0]
handler = spec[1]
if len(spec) == 3:
kwargs = spec[2]
else:
kwargs = {}
spec = URLSpec(pattern, handler, kwargs)
handlers.append(spec) if spec.name:
# 未使用该功能,默认spec.name = None
if spec.name in self.named_handlers:
logging.warning("Multiple handlers named %s; replacing previous value", spec.name)
self.named_handlers[spec.name] = spec

Application.add_handlers

class URLSpec(object):
def __init__(self, pattern, handler_class, kwargs={}, name=None):
if not pattern.endswith('$'):
pattern += '$'
self.regex = re.compile(pattern)
self.handler_class = handler_class
self.kwargs = kwargs
self.name = name
self._path, self._group_count = self._find_groups()

URLSpec

  3、加载的配置信息包括:

      1. 编码和返回方式信息
      2. 静态文件路径
      3. ui_modules(模版语言中使用,暂时忽略)
      4. ui_methods(模版语言中使用,暂时忽略)
      5. 是否debug模式运行

      注: 以上的所有配置信息,都可以在settings中配置,然后在创建Application对象时候,传入参数即可。

            如:application = tornado.web.Application([(r"/index", MainHandler),],**settings)

  4、生成url映射:

      将url和对应的Handler添加到对应的主机前缀中,如:safe.index.com、www.auto.com

   5、 封装数据:

      将配置信息和url映射关系封装到Application对象中,信息分别保存在Application对象的以下字段中:
      1. self.transforms,     保存着编码和返回方式信息
      2. self.settings,          保存着配置信息
      3. self.ui_modules,    保存着ui_modules信息
      4. self.ui_methods,    保存这ui_methods信息
      5. self.handlers,         保存着所有的主机名对应的Handlers,每个handlers则是url正则对应的Handler

1.3 application.listen(xxx) : 启动程序阶段(第二步)返回顶部

   1、此句代码处理过程

      1. 第一步操作将配置和url映射等信息封装到了application对象中, 而这第二步执行application对象的listen方法,

      2. 该方法内部又把之前包含各种信息的application对象封装到了一个HttpServer对象中,然后继续调用HttpServer对象的liseten方法。

  2、处理此句代码对应源码(简要代码)

  3、处理此句代码对应源码(详细代码)

class HTTPServer(object):
def __init__(self, request_callback, no_keep_alive=False, io_loop=None,xheaders=False, ssl_options=None):
#Application对象
self.request_callback = request_callback
#是否长连接
self.no_keep_alive = no_keep_alive
#IO循环
self.io_loop = io_loop
self.xheaders = xheaders
#Http和Http
self.ssl_options = ssl_options
self._socket = None
self._started = False def listen(self, port, address=""):
self.bind(port, address)
self.start(1) def bind(self, port, address=None, family=socket.AF_UNSPEC):
assert not self._socket
#创建服务端socket对象,IPV4和TCP连接
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
flags = fcntl.fcntl(self._socket.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags)
#配置socket对象
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._socket.setblocking(0)
#绑定IP和端口
self._socket.bind((address, port))
#最大阻塞数量
self._socket.listen(128) def start(self, num_processes=1):
assert not self._started
self._started = True
if num_processes is None or num_processes <= 0:
num_processes = _cpu_count()
if num_processes > 1 and ioloop.IOLoop.initialized():
logging.error("Cannot run in multiple processes: IOLoop instance "
"has already been initialized. You cannot call "
"IOLoop.instance() before calling start()")
num_processes = 1
#如果进程数大于1
if num_processes > 1:
logging.info("Pre-forking %d server processes", num_processes)
for i in range(num_processes):
if os.fork() == 0:
import random
from binascii import hexlify
try:
# If available, use the same method as
# random.py
seed = long(hexlify(os.urandom(16)), 16)
except NotImplementedError:
# Include the pid to avoid initializing two
# processes to the same value
seed(int(time.time() * 1000) ^ os.getpid())
random.seed(seed)
self.io_loop = ioloop.IOLoop.instance()
self.io_loop.add_handler(
self._socket.fileno(), self._handle_events,
ioloop.IOLoop.READ)
return
os.waitpid(-1, 0)
#进程数等于1,默认
else:
if not self.io_loop:
#设置成员变量self.io_loop为IOLoop的实例,注:IOLoop使用methodclass完成了一个单例模式
self.io_loop = ioloop.IOLoop.instance()
#执行IOLoop的add_handler方法,将socket句柄、self._handle_events方法和IOLoop.READ当参数传入
self.io_loop.add_handler(self._socket.fileno(),
self._handle_events,
ioloop.IOLoop.READ)
def _handle_events(self, fd, events):
while True:
try:
#====important=====#
connection, address = self._socket.accept()
except socket.error, e:
if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
return
raise
if self.ssl_options is not None:
assert ssl, "Python 2.6+ and OpenSSL required for SSL"
try:
#====important=====#
connection = ssl.wrap_socket(connection,server_side=True,do_handshake_on_connect=False,**self.ssl_options)
except ssl.SSLError, err:
if err.args[0] == ssl.SSL_ERROR_EOF:
return connection.close()
else:
raise
except socket.error, err:
if err.args[0] == errno.ECONNABORTED:
return connection.close()
else:
raise
try:
if self.ssl_options is not None:
stream = iostream.SSLIOStream(connection, io_loop=self.io_loop)
else:
stream = iostream.IOStream(connection, io_loop=self.io_loop)
#====important=====#
HTTPConnection(stream, address, self.request_callback,self.no_keep_alive, self.xheaders)
except:
logging.error("Error in connection callback", exc_info=True)

HTTPServer

class IOLoop(object):
# Constants from the epoll module
_EPOLLIN = 0x001
_EPOLLPRI = 0x002
_EPOLLOUT = 0x004
_EPOLLERR = 0x008
_EPOLLHUP = 0x010
_EPOLLRDHUP = 0x2000
_EPOLLONESHOT = (1 << 30)
_EPOLLET = (1 << 31) # Our events map exactly to the epoll events
NONE = 0
READ = _EPOLLIN
WRITE = _EPOLLOUT
ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP def __init__(self, impl=None):
self._impl = impl or _poll()
if hasattr(self._impl, 'fileno'):
self._set_close_exec(self._impl.fileno())
self._handlers = {}
self._events = {}
self._callbacks = []
self._timeouts = []
self._running = False
self._stopped = False
self._blocking_signal_threshold = None # Create a pipe that we send bogus data to when we want to wake
# the I/O loop when it is idle
if os.name != 'nt':
r, w = os.pipe()
self._set_nonblocking(r)
self._set_nonblocking(w)
self._set_close_exec(r)
self._set_close_exec(w)
self._waker_reader = os.fdopen(r, "rb", 0)
self._waker_writer = os.fdopen(w, "wb", 0)
else:
self._waker_reader = self._waker_writer = win32_support.Pipe()
r = self._waker_writer.reader_fd
self.add_handler(r, self._read_waker, self.READ) @classmethod
def instance(cls):
if not hasattr(cls, "_instance"):
cls._instance = cls()
return cls._instance def add_handler(self, fd, handler, events):
"""Registers the given handler to receive the given events for fd."""
self._handlers[fd] = stack_context.wrap(handler)
self._impl.register(fd, events | self.ERROR)

IOLoop

def wrap(fn):
'''Returns a callable object that will resore the current StackContext
when executed. Use this whenever saving a callback to be executed later in a
different execution context (either in a different thread or
asynchronously in the same thread).
'''
if fn is None:
return None
# functools.wraps doesn't appear to work on functools.partial objects
#@functools.wraps(fn)
def wrapped(callback, contexts, *args, **kwargs):
# If we're moving down the stack, _state.contexts is a prefix
# of contexts. For each element of contexts not in that prefix,
# create a new StackContext object.
# If we're moving up the stack (or to an entirely different stack),
# _state.contexts will have elements not in contexts. Use
# NullContext to clear the state and then recreate from contexts.
if (len(_state.contexts) > len(contexts) or
any(a[1] is not b[1]
for a, b in itertools.izip(_state.contexts, contexts))):
# contexts have been removed or changed, so start over
new_contexts = ([NullContext()] +
[cls(arg) for (cls,arg) in contexts])
else:
new_contexts = [cls(arg)
for (cls, arg) in contexts[len(_state.contexts):]]
if len(new_contexts) > 1:
with contextlib.nested(*new_contexts):
callback(*args, **kwargs)
elif new_contexts:
with new_contexts[0]:
callback(*args, **kwargs)
else:
callback(*args, **kwargs)
if getattr(fn, 'stack_context_wrapped', False):
return fn
contexts = _state.contexts
result = functools.partial(wrapped, fn, contexts)
result.stack_context_wrapped = True
return result

stack_context.wrap

    1. HTTPServer实质做了以下四件事

        1. 调用自己的构造方法,把包含了各种配置信息的application对象封装到了HttpServer对象的request_callback字段

        2. 调用socketsocket() , 创建服务端socket对象

        3. 单例模式创建IOLoop对象,然后将socket对象句柄作为key,被封装了的函数_handle_events作为value,添加到IOLoop对象的_handlers字段中

        4. 向epoll中注册监听服务端socket对象的读可用事件

    2. IOLoop作用

        1. 在HttpServer中生成IOLoop对象,并调用 IOLoop的add_handler方法

        2. add_handler 方法内部,将_handle_events函数封装到stack_context.wrap中,然后保存到IOLoop对象的self._handlers[fd]字段中

        3. 然后调用epoll对象,向epoll中注册监听socket事件

    3. stack_context.wrap作用

        1. stack_context.wrap其实就是对函数进行一下封装,即:函数在不同情况下上下文信息可能不同。

  4、使用epoll创建服务端socket(补充)

import socket, select 

EOL1 = b'/n/n'
EOL2 = b'/n/r/n'
response = b'HTTP/1.0 200 OK/r/nDate: Mon, 1 Jan 1996 01:01:01 GMT/r/n'
response += b'Content-Type: text/plain/r/nContent-Length: 13/r/n/r/n'
response += b'Hello, world!' serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
serversocket.setblocking(0) epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN) try:
connections = {}; requests = {}; responses = {}
while True:
events = epoll.poll(1)
for fileno, event in events:
if fileno == serversocket.fileno():
connection, address = serversocket.accept()
connection.setblocking(0)
epoll.register(connection.fileno(), select.EPOLLIN)
connections[connection.fileno()] = connection
requests[connection.fileno()] = b''
responses[connection.fileno()] = response
elif event & select.EPOLLIN:
requests[fileno] += connections[fileno].recv(1024)
if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
epoll.modify(fileno, select.EPOLLOUT)
print('-'*40 + '/n' + requests[fileno].decode()[:-2])
elif event & select.EPOLLOUT:
byteswritten = connections[fileno].send(responses[fileno])
responses[fileno] = responses[fileno][byteswritten:]
if len(responses[fileno]) == 0:
epoll.modify(fileno, 0)
connections[fileno].shutdown(socket.SHUT_RDWR)
elif event & select.EPOLLHUP:
epoll.unregister(fileno)
connections[fileno].close()
del connections[fileno]
finally:
epoll.unregister(serversocket.fileno())
epoll.close()
serversocket.close()

使用epoll创建服务端socket

    1.  上述,其实就是利用epoll对象的poll(timeout)方法去轮询已经注册在epoll中的socket句柄,

    2.  当有读可用的信息时候,则返回包含当前句柄和Event Code的序列,然后在通过句柄对客户端的请求进行处理

1.4 tornado.ioloop.IOLoop.instance().start() : 启动程序阶段(第三步)返回顶部

   1、此句代码处理过程

      1. 执行IOLoop实例的start()方法后程序就进入“死循环”,也就是会一直不停的轮询的去检查是否有请求到来

      2. 如果有请求到达,则执行封装了HttpServer类的_handle_events方法和相关上下文的stack_context.wrap(handler)
           (其实就是执行HttpServer类的_handle_events方法)

  2、处理此句代码对应源码(简要代码)

class IOLoop(object):
def add_handler(self, fd, handler, events):
# HttpServer的Start方法中会调用该方法
self._handlers[fd] = stack_context.wrap(handler)
self._impl.register(fd, events | self.ERROR) def start(self):
while True:
poll_timeout = 0.2
try:
# epoll中轮询
event_pairs = self._impl.poll(poll_timeout)
except Exception, e:
# 省略其他
# 如果有读可用信息,则把该socket对象句柄和Event Code序列添加到self._events中
self._events.update(event_pairs)
# 遍历self._events,处理每个请求
while self._events:
fd, events = self._events.popitem()
try:
# 以socket为句柄为key,取出self._handlers中的stack_context.wrap(handler),并执行
# stack_context.wrap(handler)包装了HTTPServer类的_handle_events函数的一个函数
# 是在上一步中执行add_handler方法时候,添加到self._handlers中的数据。
self._handlers[fd](fd, events)
except:
# 省略其他

IOLoop.start

class HTTPServer(object):
def _handle_events(self, fd, events):
while True:
try:
connection, address = self._socket.accept()
except socket.error, e:
if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
return
raise
if self.ssl_options is not None:
assert ssl, "Python 2.6+ and OpenSSL required for SSL"
try:
connection = ssl.wrap_socket(connection,
server_side=True,
do_handshake_on_connect=False,
**self.ssl_options)
except ssl.SSLError, err:
if err.args[0] == ssl.SSL_ERROR_EOF:
return connection.close()
else:
raise
except socket.error, err:
if err.args[0] == errno.ECONNABORTED:
return connection.close()
else:
raise
try:
if self.ssl_options is not None:
stream = iostream.SSLIOStream(connection, io_loop=self.io_loop)
else:
stream = iostream.IOStream(connection, io_loop=self.io_loop)
HTTPConnection(stream, address, self.request_callback,
self.no_keep_alive, self.xheaders)
except:
logging.error("Error in connection callback", exc_info=True)

HTTPServer._handle_events

04: 打开tornado源码剖析处理过程的更多相关文章

  1. 老李推荐:第8章5节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-运行测试脚本

    老李推荐:第8章5节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-运行测试脚本   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化 ...

  2. 老李推荐:第8章1节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-运行环境初始化

    老李推荐:第8章1节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-运行环境初始化   首先大家应该清楚的一点是,MonkeyRunner的运行是牵涉到主机端和目 ...

  3. 老李推荐:第8章7节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-小结

    老李推荐:第8章7节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-小结   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性 ...

  4. 04 drf源码剖析之版本

    04 drf源码剖析之版本 目录 04 drf源码剖析之版本 1. 版本简述 2. 版本使用 3.源码剖析 4. 总结 1. 版本简述 API版本控制使您可以更改不同客户端之间的行为.REST框架提供 ...

  5. 第五篇:白话tornado源码之褪去模板的外衣

    上一篇<白话tornado源码之请求来了>介绍了客户端请求在tornado框架中的生命周期,其本质就是利用epoll和socket来获取并处理请求.在上一篇的内容中,我们只是给客户端返回了 ...

  6. Python框架之Tornado (源码之褪去模板外衣)

    上一篇介绍了客户端请求在tornado框架中的生命周期,其本质就是利用epoll和socket来获取并处理请求.在上一篇的内容中,我们只是给客户端返回了简单的字符串,如:“Hello World”,而 ...

  7. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  8. 第三篇:白话tornado源码之请求来了

    上一篇<白话tornado源码之待请求阶段>中介绍了tornado框架在客户端请求之前所做的准备(下图1.2部分),本质上就是创建了一个socket服务端,并进行了IP和端口的绑定,但是未 ...

  9. SpringMVC源码剖析(二)- DispatcherServlet的前世今生

    上一篇文章<SpringMVC源码剖析(一)- 从抽象和接口说起>中,我介绍了一次典型的SpringMVC请求处理过程中,相继粉墨登场的各种核心类和接口.我刻意忽略了源码中的处理细节,只列 ...

随机推荐

  1. Numpy基础学习与总结

    Numpy类型学习 1.数组的表示 import numpy as np In [2]: #numpy核心是高维数组,库中的ndarray支持多维数组,同时提供了数值运算,可对向量矩阵进行运算 In ...

  2. 初识Spring Webflux

    Important to know is that there are two ways to use Spring Webflux. One using annotations, which is ...

  3. CGI servlet Applet Scriptlet Scriptlet JSP data layer(数据层),business layer(业务层), presentation layer(表现层)

    https://en.wikipedia.org/wiki/Common_Gateway_Interface In computing, Common Gateway Interface (CGI) ...

  4. bootstrap弹出模态框

    (1)引入jquery, bootstrap相关的 jquery下载地址: https://jquery.com/download/ bootstrap下载地址: https://v3.bootcss ...

  5. 测试Celery 在Windows中搭建和使用的版本

    官网:http://docs.celeryproject.org/en/latest/faq.html#does-celery-support-windows 描述如下:表示Celery 4.0版本以 ...

  6. 如何正确的把 Java 数组 Array 转为列表 List

    最近想把 java 数组转成 List,网上普遍的答案都是 Arrays.asList: String[] a = new String[] {"hello", "wor ...

  7. 图文解析Spark2.0核心技术(转载)

    导语 Spark2.0于2016-07-27正式发布,伴随着更简单.更快速.更智慧的新特性,spark 已经逐步替代 hadoop 在大数据中的地位,成为大数据处理的主流标准.本文主要以代码和绘图的方 ...

  8. git pull报错,error: cannot lock ref导致拉流失败

    使用git命令删除相应refs文件,git update-ref -d refs/remotes/XXX,或者手动删除文件 简单粗暴强行git pull,执行git pull -p 原文:https: ...

  9. (转)es进行聚合操作时提示Fielddata is disabled on text fields by default

    根据es官网的文档执行 GET /megacorp/employee/_search { "aggs": { "all_interests": { " ...

  10. Ultra-QuickSort(poj 2299归并排序)

    http://acm.sdut.edu.cn:8080/vjudge/contest/view.action?cid=232#problem/A B - Ultra-QuickSort Time Li ...