This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch docs/ww-5256-compress-tag-documentation in repository https://gitbox.apache.org/repos/asf/struts-site.git
commit 0412d2e2715ac926576fbca0b5d0e7182a240eba Author: Lukasz Lenart <[email protected]> AuthorDate: Sat Dec 6 10:22:19 2025 +0100 docs: WW-5256 add documentation for compress tag and freemarker whitespace stripping Added comprehensive documentation for new features in Struts 7.2.0: - New compress tag documentation (compress-tag.md) - Updated tag reference with compress tag - Added FreeMarker whitespace stripping section to freemarker-support.md - Added performance tuning sections for both features - Updated constant configuration examples Related to apache/struts#1418 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]> --- .claude/settings.json | 20 ++++ source/core-developers/constant-configuration.md | 32 +++++-- source/core-developers/freemarker-support.md | 60 +++++++++--- source/core-developers/performance-tuning.md | 111 ++++++++++++++++++----- source/tag-developers/compress-tag.md | 80 ++++++++++++++++ source/tag-developers/tag-reference.md | 84 ++++++++--------- 6 files changed, 299 insertions(+), 88 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..a22e52465 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,20 @@ +{ + "permissions": { + "allow": [ + "mcp__jetbrains", + "mcp__perplexity", + "WebFetch(domain:lists.apache.org)", + "WebFetch(domain:github.com)", + "WebFetch(domain:struts.apache.org)", + "WebFetch(domain:raw.githubusercontent.com)", + "Bash(git checkout:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(gh pr create:*)", + "Bash(gh pr view:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/source/core-developers/constant-configuration.md b/source/core-developers/constant-configuration.md index f8dec3f57..ec700fa71 100644 --- a/source/core-developers/constant-configuration.md +++ b/source/core-developers/constant-configuration.md @@ -8,12 +8,12 @@ parent: # Constant Configuration -Constants provide a simple way to customize a Struts application by defining key settings that modify framework and -plugin behavior. There are two key roles for constants. First, they are used to override settings like the maximum file -upload size or whether the Struts framework should be in "devMode" or not, and so on. Second, they specify which +Constants provide a simple way to customize a Struts application by defining key settings that modify framework and +plugin behavior. There are two key roles for constants. First, they are used to override settings like the maximum file +upload size or whether the Struts framework should be in "devMode" or not, and so on. Second, they specify which [Bean](bean-configuration) implementation, among multiple implementations of a given type, should be chosen. -Constants can be declared in multiple files. By default, constants are searched for in the following order, allowing +Constants can be declared in multiple files. By default, constants are searched for in the following order, allowing for subsequent files to override previous ones: 1. [struts-default.xml](struts-default-xml) @@ -29,10 +29,10 @@ for subsequent files to override previous ones: In the various XML variants, the constant element has two required attributes: `name` and `value`. -|Attribute|Required|Description| -|---------|--------|-----------| -|name|**yes**|the name of the constant| -|value|**yes**|the value of the constant| +| Attribute | Required | Description | +|-----------|----------|---------------------------| +| name | **yes** | the name of the constant | +| value | **yes** | the value of the constant | In the [default.properties](default-properties) file, each entry is treated as a constant. @@ -40,7 +40,7 @@ In the [web.xml](web-xml) file, any FilterDispatcher initialization parameters a ### Value substitution -Since Apache Struts 2.5.6 it is possible to use value substitution when defining `constant`s in `struts.xml` file. +Since Apache Struts 2.5.6 it is possible to use value substitution when defining `constant`s in `struts.xml` file. You can also define a default value if given System property or ENV variable is missing, see example below: ```xml @@ -72,6 +72,20 @@ Note: substitution is limited to System properties and ENV variables and works o struts.devMode = true ``` +**Constant Example (Performance - Struts 7.2.0+)** + +```xml +<struts> + <!-- Enable FreeMarker whitespace stripping (default: true, auto-disabled in devMode) --> + <constant name="struts.freemarker.whitespaceStripping" value="true" /> + + <!-- Control HTML compression globally --> + <constant name="struts.compress.enabled" value="true" /> + <constant name="struts.compress.maxSize" value="10485760" /> + <constant name="struts.compress.log.maxLength" value="200" /> +</struts> +``` + **Constant Example (web.xml)** ```xml diff --git a/source/core-developers/freemarker-support.md b/source/core-developers/freemarker-support.md index f3ccf2598..151183ad0 100644 --- a/source/core-developers/freemarker-support.md +++ b/source/core-developers/freemarker-support.md @@ -7,12 +7,17 @@ parent: --- # Freemarker Support +{:.no_toc} + +* Will be replaced with the ToC, excluding a header +{:toc} Freemarker views can be rendered using a result type `freemarker`. ## Configure your action to use the freemarker result type -The `freemarker` result type is defined in `struts-default.xml`, so normally you just include it, and define your results to use `type="freemarker"`. +The `freemarker` result type is defined in `struts-default.xml`, so normally you just include it, and define your +results to use `type="freemarker"`. ```xml <include file="struts-default.xml"/> @@ -48,7 +53,7 @@ The following variables exist in the FreeMarker views: - `res` - the current `HttpServletResponse` - `stack` - the current `OgnlValueStack` - `ognl` - the `OgnlTool` instance - - This class contains useful methods to execute OGNL expressions against arbitary objects, and a method to generate a select list using + - This class contains useful methods to execute OGNL expressions against arbitrary objects, and a method to generate a select list using the `<s:select/>` pattern. (i.e. taking the name of the list property, a listKey and listValue) - `struts` - an instance of `StrutsBeanWrapper` - `action` - the current Struts action @@ -56,8 +61,8 @@ The following variables exist in the FreeMarker views: ## FreeMarker configuration with recent releases -To configure the freemarker engine that Struts uses, just add a file `freemarker.properties` to the classpath. The supported properties -are those that the Freemarker Configuration object expects - see the [Freemarker documentation](https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setSetting-java.lang.String-java.lang.String-) +To configure the freemarker engine that Struts uses, just add a file `freemarker.properties` to the classpath. +The supported properties are those that the Freemarker Configuration object expects - see the [Freemarker documentation](https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setSetting-java.lang.String-java.lang.String-) for these. ``` @@ -66,13 +71,45 @@ template_update_delay=5 locale=no_NO ``` +## FreeMarker Whitespace Stripping + +**Available since Struts 7.2.0** + +Struts supports automatic whitespace stripping during FreeMarker template compilation. When enabled, this feature +removes indentation and trailing whitespace from lines containing only FTL directives, significantly reducing the size +of generated HTML output. + +This feature is controlled by the `struts.freemarker.whitespaceStripping` constant: + +```xml +<constant name="struts.freemarker.whitespaceStripping" value="true" /> +``` + +**Default behavior:** + +- **Enabled by default** (`true`) +- **Automatically disabled** when `struts.devMode` is enabled to make debugging easier +- Works transparently with existing templates + +**Benefits:** + +- Reduces HTML output size by removing template indentation +- Improves page load times and reduces bandwidth usage +- No changes needed to existing FreeMarker templates +- Automatically disabled in development mode for easier debugging + +**See also:** + +- [Performance Tuning - FreeMarker Whitespace Stripping](performance-tuning#enable-freemarker-whitespace-stripping) +- [Compress Tag](../tag-developers/compress-tag) for runtime HTML compression + ## Using struts UI tags - or any JSP Tag Library Freemarker has builtin support for using any JSP taglib. You can use JSP taglibs in FreeMarker even if - - your servlet container has no support for JSP, or - - you didn't specify the taglib in your web.xml - note how in the example below we refer to the taglib by its webapp-absolute URL, - so no configuration in web.xml is needed. +- your servlet container has no support for JSP, or +- you didn't specify the taglib in your web.xml - note how in the example below we refer to the taglib by its + webapp-absolute URL, so no configuration in web.xml is needed. ```ftl <#assign s=JspTaglibs["/WEB-INF/struts.tld"] /> @@ -85,28 +122,25 @@ Freemarker has builtin support for using any JSP taglib. You can use JSP taglibs ``` -*NOTE*: numeric properties for tags MUST be numbers, not strings. as in the rows and cols properties above. if you use `cols="40"` you will -receive an exception. Other than that, the freemarker tag container behaves as you would expect. +*NOTE*: numeric properties for tags MUST be numbers, not strings. as in the rows and cols properties above. If you use +`cols="40"` you will receive an exception. Other than that, the freemarker tag container behaves as you would expect. ## Dynamic attributes support You can specify dynamic attributes with Struts 2 tags like this: - ```ftl <@s.textfield name="test" dynamicAttributes={"placeholder":"input","foo":"bar"}/> ``` or like this: - ```ftl <@s.textfield name="test" placeholder="input" foo="bar"/> ``` and for both case, it will be parsed into: - ```html <input type="text" name="test" value="" id="test" placeholder="input" foo="bar"/> @@ -114,14 +148,12 @@ and for both case, it will be parsed into: You can also use OGNL expressions with dynamic tags like below: - ```ftl <@s.textfield name="test" placeholder="input" foo="checked: %{bar}"/> ``` When using attributes with hyphens, use the below syntax (you can also leave the single quotes from false if you want) - ```ftl <@s.form dynamicAttributes={'data-ajax':'false'}> ... diff --git a/source/core-developers/performance-tuning.md b/source/core-developers/performance-tuning.md index 5d729b473..7f5365302 100644 --- a/source/core-developers/performance-tuning.md +++ b/source/core-developers/performance-tuning.md @@ -15,15 +15,15 @@ parent: The following are some tips and tricks to squeeze the most performance out of Struts 2. > For Struts 2 versions before 2.3: the OGNL version 3.0.3 library is a > drop-in replacement for older OGNL jars, -> and provides **much** better performance. See the following JIRA issue for more information: +> and provides **much** better performance. See the following JIRA issue for more information: > [WW-3580](https://issues.apache.org/jira/browse/WW-3580) ## Turn off logging and devMode -The [devMode](development-mode) allows reloading of configuration and validation related files, but because they happen on each -request, this setting will totally kill your performance. -When using logging, make sure to turn off logging (esp. Freemarker generates a LOT of logging), and check if a level is -enabled before printing it, or you will get the cost of the String parsing/concatenation anyways. +The [devMode](development-mode) allows reloading of configuration and validation related files, but because they happen +on each request, this setting will totally kill your performance. When using logging, make sure to turn off logging +(esp. Freemarker generates a LOT of logging), and check if a level is enabled before printing it, or you will get +the cost of the String parsing/concatenation anyways. ## Use the Java Templates @@ -32,8 +32,8 @@ which provide a drop in replacement for most of the tags, and are a lot faster t ## Do not use interceptors you do not need -If you do not require a full stack of interceptors for an Action, then try using a different one (basicStack), -or remove interceptors you do not need. Remove the `I18nInterceptor` interceptor if you don't need it, as it can cause +If you do not require a full stack of interceptors for an Action, then try using a different one (basicStack), +or remove interceptors you do not need. Remove the `I18nInterceptor` interceptor if you don't need it, as it can cause a session to be created. ## Use the correct HTTP headers (Cache-Control & Expires) @@ -42,9 +42,10 @@ When returning HTML views, make sure to add the correct headers so browsers know ## Copy the static content from the Struts 2 jar when using the Ajax theme (Dojo) or the Calendar tag -Struts 2 uses some external javascript libraries and cascading stylesheets for certain themes and tags. These by default -are located inside the Struts 2 jar, and a special filter returns them when requesting a special path (`/static`). -Although Struts 2 can handle these requests, an application/servlet container is not optimized for these kind of requests. +Struts 2 uses some external javascript libraries and cascading stylesheets for certain themes and tags. These by default +are located inside the Struts 2 jar, and a special filter returns them when requesting a special path (`/static`). +Although Struts 2 can handle these requests, an application/servlet container is not optimized for these kind of +requests. Consider moving these .js and .css files to a seperated server (Lighttpd, Apache HTTPD, ..). ## Create a freemarker.properties file in your WEB-INF/classes directory @@ -55,8 +56,9 @@ Create the freemarker.properties file and add the following setting (or whatever template_update_delay=60000 ``` -This value determines how often Freemarker checks if it needs to reloads the templates from disk. The default value -is 500 ms. Since there is no reason to check if a template needs reloading, it is best to set this to a very large value. +This value determines how often Freemarker checks if it needs to reloads the templates from disk. The default value +is 500 ms. Since there is no reason to check if a template needs reloading, it is best to set this to a very large +value. Note that this value is in seconds and freemarker will convert this value to milliseconds. You can also use `struts.freemarker.templatesCache.updateDelay` constant to achieve the same effect. @@ -65,33 +67,94 @@ See also: [Freemarker configuration properties](https://freemarker.apache.org/do ## Enable Freemarker template caching -> Note: support for this options has been removed in Struts. See the [Cache](../tag-developers/freemarker#cache) section of the FreeMarker page. +> Note: support for this options has been removed in Struts. See the [Cache](../tag-developers/freemarker#cache) section +> of the FreeMarker page. -As of Struts 2.0.10, setting the property `struts.freemarker.templatesCache` to true will enable the Struts internal +As of Struts 2.0.10, setting the property `struts.freemarker.templatesCache` to true will enable the Struts internal caching of Freemarker templates. This property is set to false by default. -In Struts versions prior to 2.0.10, you had to copy the `/template` directory from the Struts 2 jar in your `WEB_APP` +In Struts versions prior to 2.0.10, you had to copy the `/template` directory from the Struts 2 jar in your `WEB_APP` root to utilize Freemarker's built in caching mechanism in order to achieve similar results. -The built in Freemarker caching mechanism fails to properly cache templates when they are retrieved from the classpath. -Copying them to the WEB_APP root allows Freemarker to cache them correctly. Freemarker looks at the last modified time -of the template to determine if it needs to reload the templates. Resources retrieved from the classpath have no last +The built in Freemarker caching mechanism fails to properly cache templates when they are retrieved from the classpath. +Copying them to the WEB_APP root allows Freemarker to cache them correctly. Freemarker looks at the last modified time +of the template to determine if it needs to reload the templates. Resources retrieved from the classpath have no last modified time, so Freemarker will reload them on every request. +## Enable FreeMarker Whitespace Stripping + +**Available since Struts 7.2.0** + +Struts now supports automatic whitespace stripping during FreeMarker template compilation, which removes indentation and +trailing whitespace from lines containing only FTL tags. This significantly reduces the size of generated HTML output +without affecting the visual presentation. + +This feature is enabled by default and automatically disabled when `devMode` is active to make debugging easier. + +**Configuration:** + +```xml +<constant name="struts.freemarker.whitespaceStripping" value="true" /> +``` + +Or in `struts.properties`: + +```properties +struts.freemarker.whitespaceStripping=true +``` + +**Default value:** `true` (automatically disabled in devMode) + +## Use the Compress Tag for HTML Output Optimization + +**Available since Struts 7.2.0** + +The `<s:compress>` tag provides runtime HTML compression by removing unnecessary whitespace between tags. This +complements FreeMarker whitespace stripping and can further reduce output size for specific sections of your pages. + +**Usage:** + +```jsp +<s:compress> + <div> + <p>Content here will be compressed</p> + </div> +</s:compress> +``` + +**Configuration:** + +Control compression globally: + +```xml +<constant name="struts.compress.enabled" value="true" /> +<constant name="struts.compress.maxSize" value="10485760" /> +``` + +The compress tag is enabled by default and includes security protections: + +- Maximum content size limit (10MB default) prevents DoS attacks +- Body content truncation in logs (200 chars default) prevents sensitive data exposure +- ReDoS safeguards for regex operations + +For development debugging, compression is automatically disabled in devMode unless you use the `force="true"` attribute. + +**See also:** [Compress Tag](../tag-developers/compress-tag) + ## When overriding a theme, copy all necessary templates to the theme directory -There's a performance cost when a template cannot be found in the current directory. The reason for this is that -Struts 2 must check for a template in the current theme first before falling back to the parent theme. In the future, +There's a performance cost when a template cannot be found in the current directory. The reason for this is that +Struts 2 must check for a template in the current theme first before falling back to the parent theme. In the future, this penalty could be eliminated by implementing a missing template cache in Struts 2. ## Do not create sessions unless you need them -Struts 2 does not create sessions unless asked to (for example, by having the createSession interceptor in your -interceptor stack). Note that when you use SiteMesh however, a session will **always** be created. +Struts 2 does not create sessions unless asked to (for example, by having the createSession interceptor in your +interceptor stack). Note that when you use SiteMesh however, a session will **always** be created. The I18nInterceptor interceptor can create sessions, so make sure you remove it, if you don't need it. ## When using Freemarker, try to use the Freemarker equivalent rather than using the JSP tags -Freemarker has support for iterating lists, displaying properties, including other templates, macro's, and so on. -There is a small performance cost when using the S2 tags instead of the Freemarker equivalent +Freemarker has support for iterating lists, displaying properties, including other templates, macro's, and so on. +There is a small performance cost when using the S2 tags instead of the Freemarker equivalent (eg. `<s:property value="foo"/>` should be replaced by `${foo}`). diff --git a/source/tag-developers/compress-tag.md b/source/tag-developers/compress-tag.md new file mode 100644 index 000000000..34079d348 --- /dev/null +++ b/source/tag-developers/compress-tag.md @@ -0,0 +1,80 @@ +--- +layout: default +title: compress tag +parent: + title: Tag Reference + url: tag-reference +--- + +# compress + +Please make sure you have read the [Tag Syntax](tag-syntax) document and understand how tag attribute syntax works. + +## Description + +Compresses HTML output by removing unnecessary whitespace between tags while preserving content within tags. This helps +reduce the size of generated HTML output, improving page load times and bandwidth usage. + +The compress tag can be controlled globally via the `struts.compress.enabled` configuration property and is +automatically disabled in development mode for easier debugging. + +{% remote_file_content https://raw.githubusercontent.com/apache/struts/main/core/src/site/resources/tags/compress-description.html %} + +## Attributes + +{% remote_file_content https://raw.githubusercontent.com/apache/struts/main/core/src/site/resources/tags/compress-attributes.html %} + +## Security Features + +- Body content truncation in logs prevents sensitive data exposure +- Maximum size limits (10MB default) prevent DoS attacks via large inputs +- Regex operations include ReDoS safeguards with 50MB hard limit + +## Examples + +**Basic usage:** + +```jsp +<s:compress> + <div> + <p>This HTML will be compressed</p> + <span>Whitespace between tags will be removed</span> + </div> +</s:compress> +``` + +**Force compression in development mode:** + +```jsp +<s:compress force="true"> + <div> + <p>This will be compressed even in devMode</p> + </div> +</s:compress> +``` + +**Configuration:** + +Control compression globally in `struts.xml`: + +```xml +<constant name="struts.compress.enabled" value="true" /> +<constant name="struts.compress.maxSize" value="10485760" /> +``` + +Or in `struts.properties`: + +```properties +struts.compress.enabled=true +struts.compress.maxSize=10485760 +``` + +## Related Configuration + +- `struts.compress.enabled` - Controls whether compression is enabled globally (default: true) +- `struts.compress.maxSize` - Maximum size in bytes of content that can be compressed (default: 10485760 = 10MB) +- `struts.compress.log.maxLength` - Maximum length of body content in log messages (default: 200) + +## Since + +Available since Struts 7.2.0 diff --git a/source/tag-developers/tag-reference.md b/source/tag-developers/tag-reference.md index da1f1628d..32997e35c 100644 --- a/source/tag-developers/tag-reference.md +++ b/source/tag-developers/tag-reference.md @@ -6,15 +6,15 @@ parent: url: index --- -# Tag Reference +# Tag Reference {:.no_toc} * Will be replaced with the ToC, excluding a header {:toc} -Generic tags are used for controlling the execution flow when the pages render. These tags also allow for data extraction -from places other than your action or the value stack, such as _Localization_ , JavaBeans, and including additional URLs -or action executions. +Generic tags are used for controlling the execution flow when the pages render. These tags also allow for data +extraction from places other than your action or the value stack, such as _Localization_ , JavaBeans, and including +additional URLs or action executions. - Control Tags provide control flow, such as `if`, `else`, and `iterator` - Data Tags allow for data manipulation or creation, such as `bean`, `push`, and `i18n` @@ -25,45 +25,47 @@ or action executions. Struts Generic Tags control the execution flow as pages render. -|**Control Tags**| |**Data Tags**| |**Other Tags** | -| |[if](if-tag) | |[a](a-tag) | | [script](script-tag) | -| |[elseif](elseif-tag) | |[action](action-tag) | | [link](link-tag) | -| |[else](else-tag) | |[bean](bean-tag) | | | -| |[append](append-tag) | |[date](date-tag) | | | -| |[generator](generator-tag)| |[debug](debug-tag) | | | -| |[iterator](iterator-tag) | |[i18n](i18n-tag) | | | -| |[merge](merge-tag) | |[include](include-tag) | | | -| |[sort](sort-tag) | |[param](param-tag) | | | -| |[subset](subset-tag) | |[property](property-tag)| | | -| | | |[push](push-tag) | | | -| | | |[set](set-tag) | | | -| | | |[text](text-tag) | | | -| | | |[url](url-tag) | | | +| **Control Tags** | **Data Tags** | **Other Tags** | +|----------------------------|--------------------------|--------------------------| +| [if](if-tag) | [a](a-tag) | [compress](compress-tag) | +| [elseif](elseif-tag) | [action](action-tag) | [script](script-tag) | +| [else](else-tag) | [bean](bean-tag) | [link](link-tag) | +| [append](append-tag) | [date](date-tag) | | +| [generator](generator-tag) | [debug](debug-tag) | | +| [iterator](iterator-tag) | [i18n](i18n-tag) | | +| [merge](merge-tag) | [include](include-tag) | | +| [sort](sort-tag) | [param](param-tag) | | +| [subset](subset-tag) | [property](property-tag) | | +| | [push](push-tag) | | +| | [set](set-tag) | | +| | [text](text-tag) | | +| | [url](url-tag) | | ## UI Tag Reference Struts UI Tags display data in rich and reusable HTML. -|**Form Tags**| |**Non-Form UI Tags** | -| |[checkbox](checkbox-tag) | |[actionerror](actionerror-tag) | -| |[checkboxlist](checkboxlist-tag) | |[actionmessage](actionmessage-tag)| -| |[combobox](combobox-tag) | |[component](component-tag) | -| |[datetextfield](datetextfield-tag) | |[fielderror](fielderror-tag) | -| |[doubleselect](doubleselect-tag) | | | -| |[head](head-tag) | | | -| |[file](file-tag) | | | -| |[form](form-tag) | | | -| |[hidden](hidden-tag) | | | -| |[inputtransferselect](inputtransferselect-tag) | | | -| |[label](label-tag) | | | -| |[optiontransferselect](optiontransferselect-tag)| | | -| |[optgroup](optgroup-tag) | | | -| |[password](password-tag) | | | -| |[radio](radio-tag) | | | -| |[reset](reset-tag) | | | -| |[select](select-tag) | | | -| |[submit](submit-tag) | | | -| |[textarea](textarea-tag) | | | -| |[textfield](textfield-tag) | | | -| |[token](token-tag) | | | -| |[updownselect](updownselect-tag) | | | +| **Form Tags** | **Non-Form UI Tags** | +|--------------------------------------------------|------------------------------------| +| [checkbox](checkbox-tag) | [actionerror](actionerror-tag) | +| [checkboxlist](checkboxlist-tag) | [actionmessage](actionmessage-tag) | +| [combobox](combobox-tag) | [component](component-tag) | +| [datetextfield](datetextfield-tag) | [fielderror](fielderror-tag) | +| [doubleselect](doubleselect-tag) | | +| [head](head-tag) | | +| [file](file-tag) | | +| [form](form-tag) | | +| [hidden](hidden-tag) | | +| [inputtransferselect](inputtransferselect-tag) | | +| [label](label-tag) | | +| [optiontransferselect](optiontransferselect-tag) | | +| [optgroup](optgroup-tag) | | +| [password](password-tag) | | +| [radio](radio-tag) | | +| [reset](reset-tag) | | +| [select](select-tag) | | +| [submit](submit-tag) | | +| [textarea](textarea-tag) | | +| [textfield](textfield-tag) | | +| [token](token-tag) | | +| [updownselect](updownselect-tag) | |
