This document explains how different resources (.js
, .css
, etc.) are handled by DXP to be served to the browser.
When it comes to serving anything from DXP there are two important files from which to start the investigation. Both are inside portal-web/docroot/WEB-INF:
web.xml
: this is the standardweb.xml
file of DXP webapp. It defines the portlets and filters that should be loaded by the application server (by default, Tomcat).liferay-web.xml
: this is a clone ofweb.xml
that is loaded by DXP oncecom.liferay.portal.kernel.servlet.filters.invoker.InvokerFilter
is loaded.
The default DXP startup sequence goes like this:
Tomcat
is started.Tomcat
bootstraps theROOT
application.- The
ROOT
application initializescom.liferay.portal.kernel.servlet.filters.invoker.InvokerFilter
and maps it to every URL requested. - The
InvokerFilter
processesliferay-web.xml
and behaves like a standard application server.
This means that we only have to care about liferay-web.xml
unless we need to find anything so low-level that we need to look at web.xml
. It also means, that any request to DXP passes through InvokerFilter
so it is a good place to put a breakpoint if we want to investigate any request routing.
Keep in mind that, even though you could theoretically know how a request will be routed by looking at
liferay-web.xml
, there are thousands of ways to alter the flow. For example: some filters can be disabled by configuration (portal.properties
orSystem Settings
) and even the code of a filter may invoke another filter not mapped underliferay-web.xml
under the hood.
In general, the current state of request servicing is a bit entangled so the safest way to learn something is to use the debugger and use a default
portal.properties
file that doesn't disable filters (unless we really want to investigate what happens when a certain filter is disabled).
Let's now analyze each resource type request and see how it is serviced.
This section analyzes how each resource type is piped through the different filters and servlets until it is finally served. You can see the next section to find an in-depth description of what each filter does.
URLs ending in .js
are routed through these filters:
- Header Filter
- ETag Filter
- Cache Filter - Resource
- GZip Filter
- Language Filter
- Aggregate Filter
URLs ending in .css
are routed through these filters:
- Header Filter
- ETag Filter
- Cache Filter - Resource
- GZip Filter
- Aggregate Filter
- Dynamic CSS Filter
- Header Filter
- ETag Filter
- GZip Filter - Theme PNG
- Header Filter
- ETag Filter
This is a configurable filter that allows adding arbitrary headers to a HTTP response.
Out of the box it comes preconfigured in DXP to add:
Cache-Control: max-age=315360000, public
Expires: 315360000
Note that, even though the filter looks generic, for any header, its implementation assumes that it is aimed at cache handling (probably because of its OOB configuration) and takes decisions based on the t
parameter of the request and the If-Modified-Since
header so that if the value of t
is older than the one in the If-Modified-Since
the portal automatically sends a 304 Not modified
HTTP status code, bypassing any further processing.
The filter also has some logic to avoid adding those two headers under certain circumstances when the session is new or the web.server.proxy.legacy.mode
portal property has been set to true
.
This filter computes ETags for served resources unless they are bigger than the configured portal property etag.response.size.max
or the request has the etag
parameter set to false
. The computed ETags are then added to the HTTP response as a header.
The algorithm to compute the ETag is a simple checksum computation that can be found in the ETagUtil
class.
Note that this filter just adds the
ETag
header to the response, but doesn't handle the rest of the caching control protocol that is necessary for HTTP.
Also note that, for dynamic resources, it may make more sense to avoid using this filter and compute
ETag
s with a better/safer/faster algorithm.
This filter is implemented in a single class but can be configured for friendly URLs, layouts and resources. We will describe the resources behavior only.
Caches are maintained by companyId
. Then, there's a portal property called cache.filter.include.user.agent
that when set to true
maintains caches per user agent, instead of a global one for all browsers. By default it is true
.
Basically this filter caches responses that are subject to caching (similar to what a proxy cache would do). Specifically it scans the Cache-Control
header for no-cache
but runs other additional tests to make sure that a request can be cached safely.
When a cache hit occurs, the server returns the same response it sent last time (including headers, not just the content).
This filter compresses the response with the gzip
algorithm. Its activation can be altered:
- Request parameter
compress
: when set tofalse
filter is inhibited. - The browser's
Accept-Encoding
header: unlessgzip
is accepted, the filter is disabled.
This filter first determines the request's locale from a variety of sources: the languageId
parameter, the ThemeDisplay
, doAsUserLanguageId
parameter, stored session value, user, cookies, ... See the class' source code for more detail.
Once the locale is determined, it replaces each occurrence of Liferay.Language.get(...)
by the translated value according to the deployed Language.properties
files.
This filter is the reason why placing any
Liferay.Language.get(...)
call in your JavaScript source works transparently without calling the server again or executing any code.
And of course, it is also the reason why dynamic language keys cannot be used in JavaScript.
This filter is used to bundle multiple JavaScript files into a minified single resource (called bundle) that can be fetched from the server, as well as to minify single JavaScript or CSS files.
In addition, it also gathers all JavaScript code contained in <aui:script>
tags (and not marked inline) into a single block of code that is written at the end of the page.
⚠ This is a deprecated mechanism, though it still works if someone configures it. ⚠
This filter was used to serve JavaScript bundles (sets of .js
files that are bundled into a single resource) which were identified in the portal.properties
file with the javascript.bundle.ids
property. Out of the box, DXP came configured with two bundles: javascript.barebone.files
and javascript.everything.files
.
Each bundle had a location in the server's URL space defined by the portal property javascript.bundle.dir
. So, for example, both javascript.bundle.dir[javascript.barebone.files]
and javascript.bundle.dir[javascript.everything.files]
were located in the root of the server (/
). This is the place from which the files in the bundle were retrieved when requested.
Finally, any .js
resource you requested (no matter what the URL was) which had valid minifierType
and minifierBundleId
/bundleId
request parameters returned a cached copy of the JavaScript bundle aggregate. Any value was valid for minifierType
while only defined bundles could be passed as minifierBundleId
/bundleId
.
However, as it is now, DXP doesn't define any file in portal.properties
for any of the two predefined bundles because they were migrated to a DynamicInclude
service registration.
Note that the caches of this filter are invalidated by means of the PortalWebResourcesUtil.getLastModified("js")
method, which looks up all registered PortalWebResources
services with the "js"
resourceType
to find the latest modified one and the time when it was updated.
When a single .js
file is requested and the minifierType
is set to js
, the filter will minify the JavaScript file contents delegating to the MinifierUtil.minifyJavascript()
method, which invokes the configured minifier (the OSGi service implementing the JavaScriptMinifier
interface with highest service ranking).
Currently there are two implementations of JavaScriptMinifier
which come out of the box with DXP: YahooJavaScriptMinifier
and GoogleJavaScriptMinifier
(the default).
In this case (when a single .css
file is requested) the filter does three things:
- Aggregating all imported CSS files (using the
@import url(...);
syntax) into one single resource. - Replacing CSS tokens.
- Minifying the resulting CSS content.
The first step simply concatenates the CSS content of the imported files and the host one and updates relative URLs to avoid breaking the resulting CSS.
The second invokes DynamicCSSUtil.replaceToken()
to substitute the tokens @base_url@
, @portal_ctx@
and @theme_image_path@
by their respective values.
Finally, the last step invokes MinifierUtil.minifyCss()
to minify the generated CSS which, in turn, uses CSSCompressor
(an inlined fork of the old YUI CSS compressor) to minify the served CSS.
This is a very limited version of the AggregateFilter
for CSS files. All it does is call DynamicCSSUtil.replaceToken()
, as in the other filter, to substitute the tokens @base_url@
, @portal_ctx@
and @theme_image_path@
by their respective values.
Other than that, it also caches the result, but doesn't aggregate @import url()
referenced files or manipulate the CSS in any other way.
Note that this filter is affected by the
t
parameter which can be used to override thelast modified
value ift
is set to a higher value (if it is set to a lower value it will be ignored).
Also note that this filter is only enabled for
.css
files belonging to context paths NOT registered asPortalWebResources
.