一、wsgi实现
1.1 application
路径:odoo.service.wsgi_server.application
实现代码:
def application(environ, start_response):
# FIXME: is checking for the presence of HTTP_X_FORWARDED_HOST really useful?
# we're ignoring the user configuration, and that means we won't
# support the standardised Forwarded header once werkzeug supports
# it
if config['proxy_mode'] and 'HTTP_X_FORWARDED_HOST' in environ:
return ProxyFix(application_unproxied)(environ, start_response)
else:
return application_unproxied(environ, start_response)
1.2 server
负责启动wsgi,加载application()。odoo由三个类定义server,分别是ThreadedServer、
PreforkServer、GeventServer,通过调用全局方法start来实现对application的启动,该方法路径
odoo.service.server
def start(preload=None, stop=False):
""" Start the odoo http server and cron processor.
"""
global server
load_server_wide_modules()
odoo.service.wsgi_server._patch_xmlrpc_marshaller()
#
if odoo.evented:
# GeventServer启动app
server = GeventServer(odoo.service.wsgi_server.application)
elif config['workers']:
if config['test_enable'] or config['test_file']:
_logger.warning("Unit testing in workers mode could fail; use --workers 0.")
# PreforkServer启动app
server = PreforkServer(odoo.service.wsgi_server.application)
# Workaround for Python issue24291, fixed in 3.6 (see Python issue26721)
if sys.version_info[:2] == (3,5):
# turn on buffering also for wfile, to avoid partial writes (Default buffer = 8k)
werkzeug.serving.WSGIRequestHandler.wbufsize = -1
else:
# ThreadedServer启动app
server = ThreadedServer(odoo.service.wsgi_server.application)
watcher = None
if 'reload' in config['dev_mode'] and not odoo.evented:
if inotify:
watcher = FSWatcherInotify()
watcher.start()
elif watchdog:
watcher = FSWatcherWatchdog()
watcher.start()
else:
if os.name == 'posix' and platform.system() != 'Darwin':
module = 'inotify'
else:
module = 'watchdog'
_logger.warning("'%s' module not installed. Code autoreload feature is disabled", module)
if 'werkzeug' in config['dev_mode']:
server.app = DebuggedApplication(server.app, evalex=True)
rc = server.run(preload, stop)
if watcher:
watcher.stop()
# like the legend of the phoenix, all ends with beginnings
if getattr(odoo, 'phoenix', False):
_reexec()
return rc if rc else 0
二、三种服务启动方式
2.1 gevent模式
当启动参数配置为gevent时,odoo采用gevent模式启动,参考1.2的start方法
伪代码:
if len(sys.argv) > 1 and sys.argv[1] == 'gevent':
sys.argv.remove('gevent')
import gevent.monkey
import psycopg2
from gevent.socket import wait_read, wait_write
gevent.monkey.patch_all()
pass
# 设置event参数标志位
evented = True
gevent服务的功能:监听一个longpolling_port端口,实现longpolling长连接
2.2 multiprocessing模式
当配置设置了worker参数且不为0时,odoo采用该模式启动。 worker数采用"Rule of thumb"为(#cpu*2+1)
开启的服务有HTTP、Cron、Longpolling,以进程的方式启动服务
def worker_spawn(self, klass, workers_registry):
self.generation += 1
worker = klass(self)
pid = os.fork()
if pid != 0:
worker.pid = pid
self.workers[pid] = worker
workers_registry[pid] = worker
return worker
else:
worker.run()
sys.exit(0)
def long_polling_spawn(self):
nargs = stripped_sys_argv()
cmd = [sys.executable, sys.argv[0], 'gevent'] + nargs[1:]
popen = subprocess.Popen(cmd)
self.long_polling_pid = popen.pid
def process_spawn(self):
if config['http_enable']:
while len(self.workers_http) < self.population:
# 根据worker参数(population)创建子进程
self.worker_spawn(WorkerHTTP, self.workers_http)
# 创建long_polling
if not self.long_polling_pid:
self.long_polling_spawn()
while len(self.workers_cron) < config['max_cron_threads']:
# 创建cron进程
self.worker_spawn(WorkerCron, self.workers_cron)
LiveChat worker自动开启,并且监听"longpolling port",该端口与"normal http port"不同,
在配置文件中可设置。客户端只能连接"normal http port",并不能连接到"longpolling port"。
解决方案:使用代理服务器,如Nginx、Apache等,转发请求,凡是url中带有/longpolling/则
转发至"longpolling port",其它则转发至"normal http port"
2.3 multithreading模式
当以上两种模式都没有设置时,默认的饿启动模式。开启HTTP、Cron服务。均以线程方式启动服务
def http_thread(self):
def app(e, s):
return self.app(e, s)
self.httpd = ThreadedWSGIServerReloadable(self.interface, self.port, app)
self.httpd.serve_forever()
def http_spawn(self):
t = threading.Thread(target=self.http_thread, name="odoo.service.httpd")
t.setDaemon(True)
t.start()
def cron_thread(self, number):
from odoo.addons.base.models.ir_cron import ir_cron
while True:
time.sleep(SLEEP_INTERVAL + number) # Steve Reich timing style
registries = odoo.modules.registry.Registry.registries
_logger.debug('cron%d polling for jobs', number)
for db_name, registry in registries.items():
if registry.ready:
thread = threading.currentThread()
thread.start_time = time.time()
try:
ir_cron._acquire_job(db_name)
except Exception:
_logger.warning('cron%d encountered an Exception:', number, exc_info=True)
thread.start_time = None
def cron_spawn(self):
""" Start the above runner function in a daemon thread.
The thread is a typical daemon thread: it will never quit and must be
terminated when the main process exits - with no consequence (the processing
threads it spawns are not marked daemon).
"""
# Force call to strptime just before starting the cron thread
# to prevent time.strptime AttributeError within the thread.
# See: http://bugs.python.org/issue7980
datetime.datetime.strptime('2012-01-01', '%Y-%m-%d')
# 根据配置的max_cron_threads参数,创建cron线程
for i in range(odoo.tools.config['max_cron_threads']):
def target():
self.cron_thread(i)
t = threading.Thread(target=target, name="odoo.service.cron.cron%d" % i)
t.setDaemon(True)
t.type = 'cron'
t.start()
_logger.debug("cron%d started!" % i)
三、服务启动过程
第一步:运行启动文件odoo-bin
第二步:加载系统默认参数
导入Python包时运行其__init__文件,最终执行到文件odoo/tools/config.py,其中初始化类configmanager
class configmanager(object):
def __init__(self, fname=None):
"""Constructor.
:param fname: a shortcut allowing to instantiate :class:`configmanager`
from Python code without resorting to environment
variable
"""
# Options not exposed on the command line. Command line options will be added
# from optparse's parser.
self.options = {
'admin_passwd': 'admin',
'csv_internal_sep': ',',
'publisher_warranty_url': 'http://services.openerp.com/publisher-warranty/',
'reportgz': False,
'root_path': None,
}
# Not exposed in the configuration file.
self.blacklist_for_save = set([
'publisher_warranty_url', 'load_language', 'root_path',
'init', 'save', 'config', 'update', 'stop_after_init', 'dev_mode', 'shell_interface'
])
# dictionary mapping option destination (keys in self.options) to MyOptions.
self.casts = {}
self.misc = {}
self.config_file = fname
self._LOGLEVELS = dict([
(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')
])
pass
第三步: 调用odoo/cli/command.py下main()方法
加载"--addons-path"参数
第四步:调用odoo/cli/server.py mian方法
确认数据库,是否是多进程启动,csv字段大小设置,调用方法odoo.service.server.start
第五步:执行odoo.service.server.start
根据参数配置决定服务的启动方式
四、HTTP请求
以odoo.service.server.ThreadedServer为例
第一步:
请求通过server到达http_thread,即http线程,该线程调用application
def application(environ, start_response):
if config['proxy_mode'] and 'HTTP_X_FORWARDED_HOST' in environ:
return werkzeug.contrib.fixers.ProxyFix(application_unproxied)(environ, start_response)
else:
return application_unproxied(environ, start_response)
def http_thread(self):
def app(e, s):
# 调用application
return self.app(e, s)
self.httpd = ThreadedWSGIServerReloadable(self.interface, self.port, app)
self.httpd.serve_forever()
def http_spawn(self):
t = threading.Thread(target=self.http_thread, name="odoo.service.httpd")
t.setDaemon(True)
t.start()
第二步:
application经过处理函数application_unproxied,到达
"Root WSGI application for the OpenERP Web Client ",odoo.http.Root实现__call__方法,
使application_unproxied返回的Root实例可调用,最终实现对odoo.http.Root.dispatch调用,
dispatch方法分发处理,调用odoo.api.ir.http._dispatch(),该方法请求参数处理、session处理
# Root.dispatch,截取部分代码
def dispatch(self, environ, start_response):
"""
Performs the actual WSGI dispatching for the application.
"""
try:
。。。。。。。。。。。。
#
explicit_session = self.setup_session(httprequest)
self.setup_db(httprequest)
self.setup_lang(httprequest)
request = self.get_request(httprequest)
def _dispatch_nodb():
try:
func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match()
except werkzeug.exceptions.HTTPException as e:
return request._handle_exception(e)
request.set_handler(func, arguments, "none")
result = request.dispatch()
return result
with request:
db = request.session.db
if db:
try:
odoo.registry(db).check_signaling()
with odoo.tools.mute_logger('odoo.sql_db'):
ir_http = request.registry['ir.http']
except (AttributeError, psycopg2.OperationalError, psycopg2.ProgrammingError):
# psycopg2 error or attribute error while constructing
# the registry. That means either
# - the database probably does not exists anymore
# - the database is corrupted
# - the database version doesnt match the server version
# Log the user out and fall back to nodb
request.session.logout()
# If requesting /web this will loop
if request.httprequest.path == '/web':
result = werkzeug.utils.redirect('/web/database/selector')
else:
result = _dispatch_nodb()
else:
result = ir_http._dispatch()
else:
result = _dispatch_nodb()
response = self.get_response(httprequest, result, explicit_session)
return response(environ, start_response)
版权声明:本文为weixin_47681417原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。