openerp-server是启动Odoo服务器的第一步,其代码如下。
#!/usr/bin/env pythonimport openerpif __name__ == "__main__": openerp.cli.main()# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
调用了openerp.cli.main()
openerp.cli.main
...import serverimport deployimport scaffoldimport startdef main(): args = sys.argv[1:] # sys.argv:启动文件和配置文件的路径['D:/rhwl/odoo/odoo.py', '-c', 'D:\\rhwl\\local.conf'] # The only shared option is '--addons-path=' needed to discover additional # commands from modules if len(args) > 1 and args[0].startswith('--addons-path=') and not args[1].startswith("-"): # parse only the addons-path, do not setup the logger... tools.config._parse_config([args[0]])#设置路径 args = args[1:] # Default legacy command command = "server" # Subcommand discovery if len(args) and not args[0].startswith("-"): logging.disable(logging.CRITICAL) for m in module.get_modules(): m = 'openerp.addons.' + m __import__(m)#导入模块 #try: #except Exception, e: # raise # print e logging.disable(logging.NOTSET) command = args[0] args = args[1:] if command in commands: o = commands[command]() o.run(args)#运行server
openerp.cli.main() 通过tools.config._parse_config([args[0]])设计模块路径,通过最后一行o.run(args)
运行server
openerp.cli.server
def main(args): check_root_user() openerp.tools.config.parse_config(args) check_postgres_user() report_configuration() config = openerp.tools.config if config["test_file"]: config["test_enable"] = True if config["translate_out"]: export_translation() sys.exit(0) if config["translate_in"]: import_translation() sys.exit(0) # This needs to be done now to ensure the use of the multiprocessing # signaling mecanism for registries loaded with -d if config['workers']: openerp.multi_process = True preload = [] if config['db_name']: preload = config['db_name'].split(',') stop = config["stop_after_init"] setup_pid_file() rc = openerp.service.server.start(preload=preload, stop=stop) sys.exit(rc)class Server(Command): """Start the odoo server (default command)""" def run(self, args): main(args)
通过openerp.service.server.start(preload=preload, stop=stop)
启动Odoo服务器
openerp.service.server.start
def start(preload=None, stop=False): """ Start the openerp http server and cron processor. """ global server load_server_wide_modules() if openerp.evented: server = GeventServer(openerp.service.wsgi_server.application) elif config['workers']: server = PreforkServer(openerp.service.wsgi_server.application) else: server = ThreadedServer(openerp.service.wsgi_server.application) if config['auto_reload']: autoreload = AutoReload(server) autoreload.run() rc = server.run(preload, stop)#在这里运行服务器 # like the legend of the phoenix, all ends with beginnings if getattr(openerp, 'phoenix', False): modules = [] if config['auto_reload']: modules = autoreload.modules.keys() _reexec(modules) return rc if rc else 0
openerp.service.server.start启动opernerp服务器和cron进程
从代码中可以看到,odoo支持三种服务类型:
-
- GeventServer
- PreforkServer
- ThreadedServer 其中,ThreadedServer为默认的服务器类型
Odoo服务器通过ThreadedServer.run()开始运行
ThreadedServer.run
def run(self, preload=None, stop=False): """ Start the http server and the cron thread then wait for a signal. The first SIGINT or SIGTERM signal will initiate a graceful shutdown while a second one if any will force an immediate exit. """ self.start(stop=stop) rc = preload_registries(preload) if stop: self.stop() return rc # Wait for a first signal to be handled. (time.sleep will be interrupted # by the signal handler.) The try/except is for the win32 case. try: while self.quit_signals_received == 0: time.sleep(60) except KeyboardInterrupt: pass self.stop()
ThreadedServer.start()
start()方法如下。
def start(self, stop=False): _logger.debug("Setting signal handlers") if os.name == 'posix': signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler) signal.signal(signal.SIGCHLD, self.signal_handler) signal.signal(signal.SIGHUP, self.signal_handler) signal.signal(signal.SIGQUIT, dumpstacks) elif os.name == 'nt': import win32api win32api.SetConsoleCtrlHandler(lambda sig: self.signal_handler(sig, None), 1) test_mode = config['test_enable'] or config['test_file'] if test_mode or (config['xmlrpc'] and not stop): # some tests need the http deamon to be available... self.http_spawn() if not stop: # only relevant if we are not in "--stop-after-init" mode self.cron_spawn()
在默认参数下,将执行self.http_spawn()
ThreadedServer.http_spawn
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="openerp.service.httpd") t.setDaemon(True) t.start() _logger.info('HTTP service (werkzeug) running on %s:%s', self.interface, self.port)
http_spawn启动命令为openerp.service.httpd的线程,并在控制台输入'HTTP service (werkzeug) running...
的提示信息。
ThreadedWSGIServerReloadable
在http_thread线程中,将调用ThreadedWSGIServerReloadable
class ThreadedWSGIServerReloadable(LoggingBaseWSGIServerMixIn, werkzeug.serving.ThreadedWSGIServer): """ werkzeug Threaded WSGI Server patched to allow reusing a listen socket given by the environement, this is used by autoreload to keep the listen socket open when a reload happens. """ def __init__(self, host, port, app): super(ThreadedWSGIServerReloadable, self).__init__(host, port, app, handler=RequestHandler) def server_bind(self): envfd = os.environ.get('LISTEN_FDS') if envfd and os.environ.get('LISTEN_PID') == str(os.getpid()): self.reload_socket = True self.socket = socket.fromfd(int(envfd), socket.AF_INET, socket.SOCK_STREAM) # should we os.close(int(envfd)) ? it seem python duplicate the fd. else: self.reload_socket = False super(ThreadedWSGIServerReloadable, self).server_bind() def server_activate(self): if not self.reload_socket: super(ThreadedWSGIServerReloadable, self).server_activate()
ThreadedWSGIServerReloadable由werkzeug.serving.ThreadedWSGIServer继承而来,作为参数的app为openerp.service.server.start传递的openerp.service.wsgi_server.application()函数
openerp.service.wsgi_server.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)
默认参数的情况下,将调用application_unproxied()
openerp.service.wsgi_server.application_unproxied
def application_unproxied(environ, start_response): """ WSGI entry point.""" # cleanup db/uid trackers - they're set at HTTP dispatch in # web.session.OpenERPSession.send() and at RPC dispatch in # openerp.service.web_services.objects_proxy.dispatch(). # /!\ The cleanup cannot be done at the end of this `application` # method because werkzeug still produces relevant logging afterwards if hasattr(threading.current_thread(), 'uid'): del threading.current_thread().uid if hasattr(threading.current_thread(), 'dbname'): del threading.current_thread().dbname with openerp.api.Environment.manage(): # Try all handlers until one returns some result (i.e. not None). wsgi_handlers = [wsgi_xmlrpc] wsgi_handlers += module_handlers for handler in wsgi_handlers: result = handler(environ, start_response) if result is None: continue return result # We never returned from the loop. response = 'No handler found.\n' start_response('404 Not Found', [('Content-Type', 'text/plain'), ('Content-Length', str(len(response)))]) return [response]
application_unproxied函数查找合适的回调函数对HTTP访问进行处理。回调函数来源于两个部分:
- wsgi_xmlrpc - module_handlers 其中,module_handlers是WSGI回调函数列表。系统在执行import openerp
的过程中已将Root类添加到了WSGI回调表中。代码在openerp.http: # register main wsgi handlerroot = Root()openerp.service.wsgi_server.register_wsgi_handler(root)
openerp.http.Root
Root类是 OpenERP Web客户端的WSGI应用,处理HTTP请求的接口为__call__()
def __call__(self, environ, start_response): """ Handle a WSGI request """ if not self._loaded: self._loaded = True self.load_addons() return self.dispatch(environ, start_response)
可见,dispatch()是处理HTTP请求的核心方法
def dispatch(self, environ, start_response): """ Performs the actual WSGI dispatching for the application. """ try: httprequest = werkzeug.wrappers.Request(environ) httprequest.app = self 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, 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: openerp.modules.registry.RegistryManager.check_registry_signaling(db) try: with openerp.tools.mute_logger('openerp.sql_db'): ir_http = request.registry['ir.http'] except (AttributeError, psycopg2.OperationalError): # psycopg2 error or attribute error while constructing # the registry. That means the database probably does # not exists anymore or the code doesnt match the db. # Log the user out and fall back to nodb request.session.logout() result = _dispatch_nodb() else: result = ir_http._dispatch() openerp.modules.registry.RegistryManager.signal_caches_change(db) else: result = _dispatch_nodb() response = self.get_response(httprequest, result, explicit_session) return response(environ, start_response) except werkzeug.exceptions.HTTPException, e: return e(environ, start_response)
dispatch将通过setup_session()方法恢复或者创建session,session保存在openerp.tools.config.session_dir所指明的目录下。
在dispatch()中,通过httprequest = werkzeug.wrappers.Request(environ)
得到WSGI的Request对象,并对werkzeug.wrappers.Request类通过HttpRequest类进行包装。 通过bind_to_environ
函数,得到当前Http请求对应的回调函数。并将回调函数通过request.set_handler(func, arguments, "none")
传递给request。紧接着通过ir_http = request.registry['ir.http']
得到一个ir_http对象 Odoo 模块加载
request.registry
request.registry定义如下:
class WebRequest(object): ... @property def registry(self): """ The registry to the database linked to this request. Can be ``None`` if the current request uses the ``none`` authentication. .. deprecated:: 8.0 use :attr:`.env` """ return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None
RegistryManager.get()从指定的名称中返回一个registry对象,其定义如下。
@classmethod def get(cls, db_name, force_demo=False, status=None, update_module=False): """ Return a registry for a given database name.""" with cls.lock(): try: return cls.registries[db_name] except KeyError: return cls.new(db_name, force_demo, status, update_module) finally: # set db tracker - cleaned up at the WSGI # dispatching phase in openerp.service.wsgi_server.application threading.current_thread().dbname = db_name
新建Registry的代码如下,此时应当为加载一个新的数据库。
@classmethod def new(cls, db_name, force_demo=False, status=None, update_module=False): """ Create and return a new registry for a given database name. The (possibly) previous registry for that database name is discarded. """ import openerp.modules with cls.lock(): with openerp.api.Environment.manage(): registry = Registry(db_name) # Initializing a registry will call general code which will in # turn call registries.get (this object) to obtain the registry # being initialized. Make it available in the registries # dictionary then remove it if an exception is raised. cls.delete(db_name) cls.registries[db_name] = registry try: with registry.cursor() as cr: seq_registry, seq_cache = Registry.setup_multi_process_signaling(cr) registry.base_registry_signaling_sequence = seq_registry registry.base_cache_signaling_sequence = seq_cache # This should be a method on Registry openerp.modules.load_modules(registry._db, force_demo, status, update_module) except Exception: del cls.registries[db_name] raise # load_modules() above can replace the registry by calling # indirectly new() again (when modules have to be uninstalled). # Yeah, crazy. registry = cls.registries[db_name] cr = registry.cursor() try: registry.do_parent_store(cr) cr.commit() finally: cr.close() registry.ready = True if update_module: # only in case of update, otherwise we'll have an infinite reload loop! cls.signal_registry_change(db_name) return registry
openerp.modules.load_modules
加载模块
#openerp.modules.load_modules(registry._db, force_demo, status, update_module)def load_modules(db, force_demo=False, status=None, update_module=False): ... # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) graph = openerp.modules.graph.Graph() graph.add_module(cr, 'base', force) ... loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report) # STEP 2: Mark other modules to be loaded/updated ... # STEP 3: Load marked modules (skipping base which was done in STEP 1) # IMPORTANT: this is done in two parts, first loading all installed or # partially installed modules (i.e. installed/to upgrade), to # offer a consistent system to the second part: installing # newly selected modules. # We include the modules 'to remove' in the first step, because # they are part of the "currently installed" modules. They will # be dropped in STEP 6 later, before restarting the loading # process. # IMPORTANT 2: We have to loop here until all relevant modules have been # processed, because in some rare cases the dependencies have # changed, and modules that depend on an uninstalled module # will not be processed on the first pass. # It's especially useful for migrations. ... if update_module: processed_modules += load_marked_modules(cr, graph, ['to install'], force, status, report, loaded_modules, update_module) ... # STEP 4: Finish and cleanup installations ... # STEP 5: Cleanup menus # Remove menu items that are not referenced by any of other # (child) menu item, ir_values, or ir_model_data. # TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children ... # STEP 6: Uninstall modules to remove # Remove records referenced from ir_model_data for modules to be # removed (and removed the references from ir_model_data). ... # STEP 7: verify custom views on every model ... # STEP 8: call _register_hook on every model ... # STEP 9: Run the post-install tests ...
opernerp.modules.load_module_graph()
for index, package in enumerate(graph): module_name = package.name module_id = package.id ... load_openerp_module(package.name) new_install = package.installed_version is None if new_install: py_module = sys.modules['openerp.addons.%s' % (module_name,)] pre_init = package.info.get('pre_init_hook') if pre_init: getattr(py_module, pre_init)(cr) models = registry.load(cr, package)#加载模块 loaded_modules.append(package.name) if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): registry.setup_models(cr, partial=True) init_module_models(cr, package.name, models) # Can't put this line out of the loop: ir.module.module will be # registered by init_module_models() above. modobj = registry['ir.module.module'] ...
openerp.modules.load_openerp_module()
def load_openerp_module(module_name): global loaded if module_name in loaded: return initialize_sys_path() try: mod_path = get_module_path(module_name) __import__('openerp.addons.' + module_name) info = load_information_from_description_file(module_name) if info['post_load']: getattr(sys.modules['openerp.addons.' + module_name], info['post_load'])() except Exception, e: msg = "Couldn't load module %s" % (module_name) _logger.critical(msg) _logger.critical(e) raise else: loaded.append(module_name)
openerp.modules.init_module_models()
def init_module_models(cr, module_name, obj_list): _logger.info('module %s: creating or updating database tables', module_name) todo = [] for obj in obj_list: result = obj._auto_init(cr, { 'module': module_name}) ... cr.commit()
通过obj._auto_end(cr, {'module': module_name})
初始化数据库表
def _auto_init(self, cr, context=None): ... self._field_create(cr, context=context)#添加ir_model、ir_model_fields和ir_model_data数据项 create = not self._table_exist(cr) ... self._create_table(cr) ...
openerp.modules.registry.load()
def load(self, cr, module): ... from .. import models models_to_load = [] # need to preserve loading order lazy_property.reset_all(self) for cls in models.MetaModel.module_to_models.get(module.name, []): model = cls._build_model(self, cr)#实例化模块 if model._name not in models_to_load: models_to_load.append(model._name) return [self.models[m] for m in models_to_load]
openrp.Models.build_model()
openerp.modules.registry将自身作为pool传递给_build_model
@classmethod def _build_model(cls, pool, cr): ... # instantiate the model, and initialize it model = object.__new__(cls) model.__init__(pool, cr) return model