The generation inside the rendering has some drawbacks: - `commit_assetsbundle` is needed for reports rendering because the template rendering may generate some assets that will be accessed by another transaction before the transaction is committed. But this solution is not ideal since the transaction is committed in the middle of the request - when the first rendered page is a 404, the assets are not committed and the page is broken. - when starting, deleting an attachment can create a concurrent update error and the request is retried. This will occur once per attachment and for all worker trying to access the same resource. The whole transaction is rollbacked, even the previously created assets bundle. - The cold page load is a slower since there is more work to do. - Implementing a readonly request is difficult because it could be transformed to read write and re-executed if the assets bundle does not exist. Generating assets when needed solves those issues. The concurrency when deleting an assets could still occur but only once per bundle, and in a smaller transaction. This could be solved with a lock now that we have more control on the transaction. The commit_assetsbundle can be removed and 404 page should have a correct layout. The cold page load could be a little faster because the assets bundle can be generated in parallel requests instead of sequentially when rendering the page. Part-of: odoo/odoo#131353
70 lines
3.3 KiB
Python
70 lines
3.3 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import json
|
|
from werkzeug.exceptions import ServiceUnavailable
|
|
|
|
from odoo.http import Controller, request, route, SessionExpiredException
|
|
from odoo.addons.base.models.assetsbundle import AssetsBundle
|
|
from ..models.bus import channel_with_db
|
|
from ..websocket import WebsocketConnectionHandler
|
|
|
|
|
|
class WebsocketController(Controller):
|
|
@route('/websocket', type="http", auth="public", cors='*', websocket=True)
|
|
def websocket(self):
|
|
"""
|
|
Handle the websocket handshake, upgrade the connection if
|
|
successfull.
|
|
"""
|
|
is_headful_browser = request.httprequest.user_agent and 'Headless' not in request.httprequest.user_agent.string
|
|
if request.registry.in_test_mode() and is_headful_browser:
|
|
# Prevent browsers from interfering with the unittests
|
|
raise ServiceUnavailable()
|
|
return WebsocketConnectionHandler.open_connection(request)
|
|
|
|
@route('/websocket/health', type='http', auth='none', save_session=False)
|
|
def health(self):
|
|
data = json.dumps({
|
|
'status': 'pass',
|
|
})
|
|
headers = [('Content-Type', 'application/json'),
|
|
('Cache-Control', 'no-store')]
|
|
return request.make_response(data, headers)
|
|
|
|
@route('/websocket/peek_notifications', type='json', auth='public', cors='*')
|
|
def peek_notifications(self, channels, last, is_first_poll=False):
|
|
if not all(isinstance(c, str) for c in channels):
|
|
raise ValueError("bus.Bus only string channels are allowed.")
|
|
if is_first_poll:
|
|
# Used to detect when the current session is expired.
|
|
request.session['is_websocket_session'] = True
|
|
elif 'is_websocket_session' not in request.session:
|
|
raise SessionExpiredException()
|
|
channels = list(set(
|
|
channel_with_db(request.db, c)
|
|
for c in request.env['ir.websocket']._build_bus_channel_list(channels)
|
|
))
|
|
last_known_notification_id = request.env['bus.bus'].sudo().search([], limit=1, order='id desc').id or 0
|
|
if last > last_known_notification_id:
|
|
last = 0
|
|
notifications = request.env['bus.bus']._poll(channels, last)
|
|
return {'channels': channels, 'notifications': notifications}
|
|
|
|
@route('/websocket/update_bus_presence', type='json', auth='public', cors='*')
|
|
def update_bus_presence(self, inactivity_period, im_status_ids_by_model):
|
|
if 'is_websocket_session' not in request.session:
|
|
raise SessionExpiredException()
|
|
request.env['ir.websocket']._update_bus_presence(int(inactivity_period), im_status_ids_by_model)
|
|
return {}
|
|
|
|
@route('/bus/websocket_worker_bundle', type='http', auth='public', cors='*')
|
|
def get_websocket_worker_bundle(self, v=None): # pylint: disable=unused-argument
|
|
"""
|
|
:param str v: Version of the worker, frontend only argument used to
|
|
prevent new worker versions to be loaded from the browser cache.
|
|
"""
|
|
bundle_name = 'bus.websocket_worker_assets'
|
|
bundle = request.env["ir.qweb"]._get_asset_bundle(bundle_name, debug_assets="assets" in request.session.debug)
|
|
stream = request.env['ir.binary']._get_stream_from(bundle.js())
|
|
return stream.get_response()
|