Advanced Topics
Variables | Mapping URLs onto JSPs |
Security | Handling Errors |
Evaluation Formatting | Displaying Mathematics and Graphics |
Multiple Kernel Pools | Including Static Files |
This section discusses a variety of more complicated and advanced details of a MSP site.
Variables
This section discusses the use of variables in MSP pages. It covers the way that input variables are processed as well as issues, such as scoping, that concern the use of local variables in code that appears in MSP pages.
There are three kinds of variables in MSP: input variables, page variables and session variables. Variables that start with $$ are input variables; they are given values from the HTTP request and are cleared when the kernel is cleaned when the page is finished. Variables that do not start with $$ only get values if they are assigned values. These assignments last until the page is cleared if they are page variables, and for the lifetime of an HTTP session if they are session variables.
Input Variables
Input variables are named to start with $$ and are given values if their names are sent with the HTTP request. They have a special name because it is important to know which are the input variables. In the following example, the input variable $$setting will get the value entered into the input element (because the input element uses the name setting). You can test if a variable has a value with the MSP function MSPValueQ:
<input type="text" name="setting" />
<msp:evaluate>
If[ MSPValueQ[ $$setting],
....
]
</msp:evaluate>
One important decision governs whether the value of input variables should or should not be interpreted. If the actual string value of the variable is suitable for your uses, you should use it. Alternatively, the string value may represent some input to a Wolfram Language command and it will be necessary to interpret it. For interpretation, it must be something that the Wolfram Engine can interpret and the result must pass validation by the security system. If you find that you are starting to modify the security system, you should consider working with the uninterpreted values.
Interpretation of Input Variables
If you want the Wolfram Engine to compute with an input variable, it must be interpreted. MSP provides various functions to help with interpretation. It is important that you use these functions because they make use of the security features. If you try to bypass them, you could compromise the security of your system.
MSPBlock and MSPToExpression are provided to obtain expressions from input variables. This is, of course, completely essential for any type of interactivity. There are two stages to this process: the first involves interpretation and the second involves validation. Interpretation determines the input to Wolfram Language, and validation ensures that the Wolfram Language commands to be executed do not endanger the security of your site.
Interpretation is based on the Wolfram Language function ToExpression that calls a parser to try to determine input to Wolfram Language. Valid input for MSPBlock and MSPToExpressioncan be regular Wolfram Language input or MathML. The following examples demonstrate input processing with MSPToExpression, showing both Wolfram Language and MathML input.
First, load the MSP application and then lock down the security system. Security is described in its own section:
Now you can use MSPToExpression to interpret some Wolfram Language input:
MSPBlock provides additional functionality to work with the interpreted value of an input variable. This is described further in the Wolfram Language Function Reference section.
MSP carries out validation to ensure the security of the system. Validation involves checking any input that was sent to the server to see if it is safe to be used in a Wolfram Language computation. You can find more information on the topic in the Security section.
Interpreted versus Non-interpreted Values
As described above, whenever you work with an input variable, you need to decide how to work with its value. You can work with the non-interpreted value and make choices based upon its setting. (This will be a string.) Alternatively, you can interpret the value so that it can be used for computation in Wolfram Language. This section gives an example of working with both interpreted and non-interpreted values. If you installed the Wolfram Web Engine as described previously, you should be able to connect to this JSP via http://localhost:8080/webengine/Examples/SimplifyIntegrate.jsp.
Here is a listing of the form element from the JSP SimplifyIntegrate.jsp:
<form action="SimplifyIntegrate.jsp" method="post">
Input:
<br>
<msp:evaluate>
integrand = Null;
If[ MSPValueQ[ $$expr],
integrand = MSPToExpression[ $$expr]] ;
</msp:evaluate>
<input type="text" name="expr" size="24"
value="<msp:evaluate> MSPValue[ $$expr, "Sin[x]^2"]</msp:evaluate>" />
<br/>
<br/>
<msp:evaluate>
If[ integrand =!= Null,
res = Integrate[ integrand,x] ;
If[ $$simplify === "on", res = Simplify[ res]] ;
MSPFormat[res,StandardForm]]
</msp:evaluate>
<br/>
<input type="submit" name="btnSubmit" value="Evaluate">
<br/>
Simplify result:
<input type="checkbox" name="simplify"
<msp:evaluate> If[ $$simplify === "on", "checked=\"checked\""]</msp:evaluate>>
<br>
</form>
In this example, there are two input variables, simplify and expr, that may be submitted with a request. The first of these is not interpreted and is used to select the action to be taken. Only expr is actually interpreted as Wolfram Language input ($$expr is used inside MSPToExpression). This is necessary because it represents a general Wolfram Language expression and will be used as input to Wolfram Language's Integrate function. The setting of the checkbox is carried by the variable $$simplify, and this is tested to see if it has the value "on". There is thus no need to interpret it. In general, it is better not to interpret if it can be avoided.
MSPBlock versus MSPToExpression
The Wolfram Web Engine provides two MSP functions for interpreting input variables, MSPBlock and MSPToExpression. This section contrasts and compares them.
MSPBlock is probably the simpler of the two. It offers a compact and simple way to interpret and use the value of an input variable, as shown here:
Remember that the $$expr in the body, used here in an Expand computation, refers to the interpreted value of the input variable $$expr. If the value of $$expr cannot be interpreted or fails a security test, an exception will be thrown. If $$expr has no value, then the MSPBlock will not be evaluated and a null result will be returned.
An alternative way to interpret input is to use MSPToExpression. This can use page variables to hold the result of interpretation. It is not quite as neat as the use of MSPBlock, but is somewhat more expressive. Here is an example:
<msp:evaluate>
poly = Null;
exponent = Null;
If[ MSPValueQ[ $$expr, $$num],
poly = MSPToExpression[ $$expr] ;
exponent = MSPToExpression[ $$num]] ;
</msp:evaluate>
<msp:evaluate>
If[ poly =!= Null && exponent =!= Null,
Expand[ poly^exponent]]
</msp:evaluate>
This example shows how MSP extracts the interpreted value of $$expr and stores it with the page variable poly. This is especially useful if you use the interpreted value in a number of different places.
Page Variables
Standard Wolfram Language programming constructs such as Block, Module and Function all work in their typical ways with regard to localization and scoping issues. You can find more information on their operations in standard Wolfram Language references.
You can use variables in Wolfram Language code inside of msp:evaluate; they can store intermediate values and be used for computation. Since these variables will be cleared when the kernel is cleared, it is not important to put these variables into a normal Wolfram Language program structure such as Module or Block. Consequently, these are called page variables.
In the following example, the page variable tmp holds the value Null. It then gets the result of calling MSPToExpression on the input variable $$expr. If MSPToExpression is unable to complete its task, for example, because of a security error, it will throw an exception and tmp will still have the value Null. Later, if tmp is not set to Null, you can be certain that the input was processed without problems and you can use it for calculations:
<msp:evaluate>
tmp = Null;
tmp = MSPToExpression[ $$expr] ;
</msp:evaluate>
<p>
<msp:evaluate>
If[ tmp =!= Null,
....
]
</msp:evaluate>
</p>
When the page is finished, tmp will be cleared.
Session Variables
If you want to save any values from one request to the next, you can use MSPSessionVariable to make a session variable. These will be stored in the server and can be used in pages that are part of different requests. They use HTTP sessions, and so the session variables for one user are not visible to those of another (just as the shopping cart at an e-commerce site is for one user and not visible to another).
In the following code fragment, there are two variables. savedInput is a session variable, declared with MSPSessionVariable, while xInput is a page variable. In the second evaluation, if xInput has a value, this is added to savedInput:
<msp:evaluate>
MSPSessionVariable[ savedInput, {}];
xInput = Null;
xInput = MSPToExpression[ $$expr] ;
</msp:evaluate>
<p>
<msp:evaluate>
If[ xInput =!= Null, savedInput = Append[ savedInput, xInput]];
</msp:evaluate>
</p>
You can work with session variables in the same way that you work with page variables: assigning them the results of calculations and then later retrieving them. The difference is that session variables last after the page is finished.
An example of MSPSessionVariable is shown in Session.jsp.
Security
MSP security can be divided into two parts. First, there is security concerned with general web server security. Secondly, there is security concerned with the use of Wolfram Language programs inside a web server.
Server Security
MSP is based on standard web server technology: Java Servlet and JavaServer Pages (JSPs). Typically, it runs in a server called a servlet container such as Apache Tomcat. This greatly facilities security issues, because these technologies already have many well-documented and well-understood security features.
The Apache Tomcat wiki site states, "There have been no public cases of damage done to a company, organization, or individual due to a Tomcat security issue." Many other servlet containers have similar security records.
To decide how much security to add to your server, start by checking the security policy of your organization. You can then decide whether you want to add features such as restricting server access to users within your organization, locating the server in some special network, setting up authentication and using HTTPS for communication.
These security features, and many others, are all well supported for many types of servers. Remember that some of these solutions, such as restricting access, might not be available to all Wolfram Web Engine licenses.
Wolfram Language Program Security
Wolfram Language is a general programming language with many features and tools for interacting with the computer on which it runs. For example, it can add, delete and modify files as well as launch and run programs. Since MSP executes Wolfram Language programs on the server, this means there are security implications. It is necessary to prevent unintended execution of Wolfram Language code.
Wolfram Language is a very dynamic language and, consequently, it is very straightforward for one Wolfram Language program to construct another. In fact, it is quite easy to pass a textual form of a Wolfram Language program as a string, turn it into a program and execute it. This dynamic nature is an important security issue to consider.
MSP provides a security process to guard against such problems. The main focus is to prevent input from the external world from being accepted by an MSP program and executed. The security process analyzes input from the server and accepts or rejects it based on a set of criteria. The MSP developer writing scripts in Wolfram Language uses functions from the security system. This ensures the security of the server.
MSPBlock
MSPBlock is one of the key security functions for MSP. It is useful for taking input to the server and converting it to be used in a computation. A typical script, taken from the example Expand.jsp, is shown here:
Remember that variables starting with $$, such as $$expr, are input variables. These have been transmitted as part of the web request and are potential sources of attack. In fact, giving them a special naming convention, which draws attention to them, is one security feature. Using MSPBlock avoids security problems because it applies the security test to its variables, in this case $$expr and $$num. If either fails the test, a security exception is thrown and the body is never evaluated; in fact, a page error results. (The section on handling errors shows how you can customize the exact behavior of page errors.)
MSPToExpression
MSPToExpression is the other key security function for web MSP. It is used for taking input to the server and turning it into a Wolfram Language expression that can be used for computing. A typical script, taken from the example Integrate.jsp, is shown here:
<msp:evaluate>
integrand = Null;
If[ MSPValueQ[ $$expr],
integrand = MSPToExpression[ $$expr]] ;
</msp:evaluate>
If the variable $$expr failed the security test, then MSPToExpression will throw a security exception and the page will be terminated. You can modify the treatment of page errors as discussed in the section on handling errors.
Avoid ToExpression
One of the key functions to avoid is ToExpression, the command that turns a string into a Wolfram Language program. In fact, well-written Wolfram Language programs rarely need to actually use this. One case might be when an input has been passed with the web request. But this is exactly what MSPToExpression is for, and ToExpression should not be used.
MSP still provides a check to prevent users from calling ToExpression on input to the server. For example, in the following, the security test is still applied to the input:
This provides an extra level of security, though it would be better to use MSPToExpression.
You can disable this check. This is described in the section on ToExpression Validation.
Security Validation
This section describes how the security validation process works and how it can be customized.
The Validation Process
The validation process works in a straightforward manner, and you can customize it to give more or less security. You can investigate its operation in the following steps.
First, load the MSP Wolfram Language application and then lock down the security model, which cannot be modified after SetSecurity is called. When the server initializes the Wolfram Engine, it calls SetSecurity:
Now you can test expressions for validity. The first example shows a harmless mathematical expression that is found to be secure:
Here is a less-than-friendly expression, the sort of thing that could be sent as an attack:
Validation works by collecting all the symbols into a list and steadily reducing the list. If any symbols remain after reduction, the expression is not secure. The reduction process works with lists of symbol and context names that either can be allowed or disallowed according to the following steps:
1. If AllowedContexts is set, remove symbols with these contexts; otherwise, remove symbols with contexts not in DisallowedContexts.
2. If DisallowedSymbols is set, remove symbols not in DisallowedSymbols; otherwise, remove symbols that are in AllowedSymbols.
3. If no symbols remain, the expression is secure; otherwise, it is not secure.
These tests allow you to be restrictive or flexible. If you use the allowed lists, you are restrictive and have more security, whereas if you use the disallowed lists, you are less restrictive and have less security. It is up to each individual site to decide the appropriate balance.
When the server is started, a default security model is installed. This default security model looks like this.
This is the value of AllowedContexts:
This is the value of AllowedSymbols:
DisallowedContexts has the value Null:
DisallowedSymbols has the value Null:
This model will allow any symbol in the Global` context, in addition to a number of other specific symbols. This is a fairly restrictive model that provides a higher level of security.
Configuring a Security Model
To make your own security definitions, you should put them into a file in the /WEB-INF directory. The name of the file is set in the WWEConfiguration.xml file with the configuration parameter SecurityConfigurationFile, which refers to the name relative to the base web application. For example, if the configuration information is in a file called ComputerSiteSecurity.m, inside of WEB-INF, the following should be added:
<SecurityConfigurationFile>
/WEB-INF/ComputeSiteSecurity.m
</SecurityConfigurationFile>
A sample security configuration file is shown here. This only allows symbols in the Global` context in addition to Plus, Times and Power. This is a particularly restrictive security system that might be appropriate in some circumstances:
{
"AllowedContexts" -> {"Global`"}
,
"AllowedSymbols" ->
HoldComplete[ Plus, Times, Power, HoldComplete]
}
As described in the section on multiple kernel pools, it is possible to use different configuration details for different request URLs. Each pool has its own configuration file and its own security system.
When each Wolfram kernel is launched, the security data is sent to the log system at the DEBUG level.
ToExpression Validation
The MSP security system adds a security test to ToExpression when it is used on input from the server. This is described in the section on avoiding ToExpression.
You can disable this security test by setting the Wolfram Language variable MSP`Utility`CheckToExpression to False. In addition, you can disable the test in the WWEConfiguration.xml file with the configuration parameter CheckToExpression:
<CheckToExpression>
false
</CheckToExpression>
It is probably an exceptional site that disables this security feature.
Of course, if the string input to ToExpression comes from an input sent with the request but is modified in some way, the call to ToExpression will not carry out any validation. Therefore, it is highly recommended that you never use ToExpression but instead use MSPToExpression.
Security and Kernel Pools
The security system is configured as part of a kernel pool. This means you can have different styles of security configuration for different types of access. More information can be found in the section on kernel pools.
Access Restrictions
You may wish to restrict access to certain parts of your system such as the kernel monitor, which is provided for monitoring and debugging your system. In this case, refer to the sections on logging and the kernel monitor. The installation section on Apache and Tomcat describes how this can be done when MSP is used from the Apache web server.
Evaluation Formatting
The output of an msp:evaluate tag is inserted into the page that is returned as part of the web request. This section will describe the different types of formatting output. This topic is related to the placement of Wolfram Language commands into MSP pages and more information is found in Appendix: evaluate.
Automatic Formatting
Any result that is computed by an msp:evaluate tag that is not a string will be formatted into a string that will use the necessary HTML escapes. An example is shown here:
This type of formatting is equivalent to MSPFormat with a format type of OutputForm.
MSPFormat
Different styles of formatting output can be generated with MSPFormat. The following example uses MSPFormat with a format type of TraditionalForm:
Output can be generated that is formatted into HTML, MathML or an image. The latter gives a convenient way to show typeset mathematics.
String Formatting
If the result of evaluate is a string, it is left unmodified and added to the output page. This is often useful for constructing HTML, as shown in the following example:
If you have a string and you want it to be formatted with HTML escapes, then you can wrap it in MSPFormat.
Graphics and Image Formatting
There are several convenient functions for formatting graphics objects so that a picture appears in the output. The following example uses MSPShow to display a plot:
Suppressing Output
You may want to use the msp:evaluate tag to evaluate something without leaving output in the resulting page. This can be done by adding a semicolon (;) after the computation, as shown here:
Adding a semicolon causes the Wolfram Language symbol Null to be returned, and this is formatted to leave no trace in the output.
Output is suppressed whatever the computation, whether it uses one of the formatting functions, a graphics function or a function that returns Print or Message output. In the following example, no output will be seen from the message output function because it is followed by a semicolon:
Multiple Calculations
To calculate more than one result in an msp:evaluate tag, the different steps must be separated with a semicolon. The result of the last computation will be formatted and appear in the output page. In the following example, the numerical result of the x+y computation will appear:
If you wish to suppress the result of the last computation, you can use a semicolon as described in the section on Suppressing Output.
Multiple Kernel Pools
For some applications, it is useful to use several pools of Wolfram kernels to serve different requests. You can configure the kernels in each pool differently, perhaps with different timeout parameters or different initialization files. Another possibility would be to use a pool with one Wolfram kernel as a demonstration server and another pool with four Wolfram kernels to serve customer requests. This would ensure that the customers received priority. Another benefit of multiple pools is the guarantee that one set of computations is completely isolated from another. However this will require additional administration and extra Wolfram Web Engine licenses.
The different pools are all configured in the WWEConfiguration.xml file. By default, one pool is created, called the general pool. If you wish to configure for additional pools, add a KernelPool section to the WWEConfiguration.xml file, as shown here:
<KernelPool>
<KernelPoolName>Longer</KernelPoolName>
<KernelExecutable>C:\Program Files\Wolfram Research\Mathematica\13.2</KernelExecutable>
<KernelTimeLimit>60000</KernelTimeLimit>
<URLPattern>/Longer/*</URLPattern>
</KernelPool>
This creates a pool called Longer, with a time limit of 60 seconds. Any URL that starts with Longer will use this pool. The documentation on WWEConfiguration.xml describes the configuration parameters that can be placed in these files.
If you wish to work with multiple pools, you need to have Wolfram Web Engine licenses for all the kernels you wish to run. The kernel monitor contains information on all the pools, so this is a good place to confirm that a pool has been properly initialized. The servlet log files also contain information about each pool, as the section on logging describes.
Multiple Web Applications
Yet another way to divide requests is to install multiple web applications. This might be beneficial if your server provides special configuration tools for web applications. For this, you would repeat the installation process for the Wolfram Web Engine web application, giving each new installation a different name. All web applications that are run in the same instance of the Java virtual machine will share the same kernel monitor.
Mapping URLs onto JSPs
The way that the Wolfram Web Engine maps URLs onto JSPs is very straightforward. The URL names a JSP that lives directly in the Wolfram Web Engine web application or a subdirectory. Some examples are shown in this section.
In the first table, the MSP Script.jsp is located in the top of the Wolfram Web Engine web application directory—for example, /usr/local/tomcat/webapps/WolframWebEngine. It can be accessed by the URL http://host/webengine/Script.jsp.
locating in the root directory | |
script name | Script.jsp |
script location | /usr/local/tomcat/webapps/WolframWebEngine |
URL | http://host/WolframWebEngine/Script.jsp |
In this second table, Script1.jsp is located in a subdirectory of the Wolfram Web Engine web application directory—for example, /usr/local/tomcat/webapps/WolframWebEngine/Test. The URL http://host/webengine/Test/Script1.jsp will find this JSP.
locating in a subdirectory | |
script name | Script1.jsp |
script location | /usr/local/tomcat/webapps/WolframWebEngine/Test |
URL | http://host/WolframWebEngine/Test/Script1.jsp |
Remember that you should not place JSPs inside the WEB-INF directory. If you do, they will not be accessible.
Handling Errors
There are a number of ways that MSP computations can lead to an error. An input might fail the security test, the computation might take too long so that the Wolfram kernel is restarted, or there might be some type of page logic error. You can learn about certain types of errors and, often, solve them using the logging system. However, it is not possible to avoid all errors; for example, you cannot predict all inputs to the server, so some of them might fail the security test. In these cases, you might want to customize the way the error is handled.
When an error is generated, MSP deals with it in one of two ways. For serious errors that require the Wolfram kernel to be restarted, the request results in an HTTP error of status 403, indicating that the server could not complete the request. Alternatively, other errors result in a Wolfram Language exception being thrown, which leads to a normal page being returned but with some special text being inserted for the error.
If you want to customize the handling of these errors, you can do this with Wolfram Language code to catch the Wolfram Language exceptions and by adding an error page to handle the HTTP error.
Catching Wolfram Language Error Exceptions
MSP throws errors in Wolfram Language as MSPException expressions, and you can add code to catch these. The exceptions that can be caught are listed here.
MSPException["ParseError"] | if the value cannot be interpreted by the Wolfram Engine |
MSPException["SecurityError"] | if the value does not pass the security test |
MSPException["ValueError"] | if the value is not a string; this indicates a programmatic error by the page author |
MSPException["VariableError"] | if the variable is not a Wolfram Language symbol; this indicates a programmatic error by the page author |
MSPException["NoValueError"] | if a variable has no value |
MSPException["VersionError"] | if a version mismatch problem is found |
Some sample code that uses Catch to catch a security exception is shown here:
A sample implementation for errorFunction is shown here. As you can see, its signature gets two arguments; the actual exception expression is the second argument:
Adding an HTTP Error Page
You can catch the HTTP errors by adding an error page. This can be done by adding code to the web.xml file that is found in the WEB-INF folder. The following should redirect to a page /Resources/Tools/Error.jsp; in fact, this is a sample page found in the Wolfram Web Engine layout:
<error-page>
<error-code>403</error-code>
<location>/Resources/Tools/Error.jsp</location>
</error-page>
Inside the error page, you need to reset the error. You can also get an integral listing of the error and an error string. Sample code is shown in the following:
<%
response.setStatus(200);
Object type = (Object)request.getAttribute(
com.wolfram.msp.MSPStatics.MSPErrorType);
String text = (String)request.getAttribute(
com.wolfram.msp.MSPStatics.MSPErrorText);
%>
Displaying Mathematics and Graphics
In order to display typeset mathematics and graphics, the server can generate images. These provide a simple way to view the results of computations. However, they suffer from the serious defect that they cannot be used by the client. They cannot be resized, drawn with different fonts or viewed in some alternative way. It is also hard for a computer program to extract any meaning from an image. Alternatives to images exist. In the case of mathematics, you can use MathML, and for graphics you can use SVG. These alternatives are not always the appropriate solution and, for this reason, functions for generating images are provided.
When a Wolfram kernel generates an image, it is stored in a file on the server and adds a reference in the HTML file that is returned. For example, the following img element may be generated:
<img src="/webengine/MSP?MSPStoreID=MSPStore1042942578_0&MSPStoreType=image/gif" alt="Created by WolframWebEngine"/>
The SRC attribute references the MSP servlet through a URL, which includes a parameter that gives the name of the file. The MSP servlet returns the contents of the file and periodically deletes old image files. The actual location in which image files are saved is a workspace directory provided by the servlet container.
More information about generating images can be found in the function pages for MSPFormat and MSPShow.
An alternative way to generate images is to use the Wolfram Language command Export, available for use with the function MSPExportImage. This provides more features, such as transparent backgrounds, but takes longer to generate. MSPExportImage always makes use of the Wolfram Engine front end.
MSP Functions Returning Images
There are various MSP functions that return images: MSPShow, MSPShowAnimation and MSPExportImage; in addition, MSPFormat may return an image. It should be noted that these all work by returning a string that contains the necessary img tag to reference the image file that is stored on the server. An example is shown here:
Therefore, if the MSP function is followed by a semicolon, as shown int he following, the output is suppressed. The use of a semicolon to suppress output is discussed in the section on evaluation formatting:
Another use of these functions is to embed their results into some other formatting function, such as those in the HTML package. This example will return an HTML table with two images:
<msp:evaluate>
Needs["MSP`HTML`"]
</msp:evaluate>
<msp:evaluate>
HTMLTableForm[ {MSPShow[ g1], MSPShow[ g2]}]
</msp:evaluate>
Including Static Files
MSP involves returning dynamically generated material. However, the webpages that MSP generates may include static images, which might have been generated by a designer. Wolfram Web Engine comes with a number of images, such as banners and buttons, that you may use. This section discusses how MSP pages can use static files. It will focus on image files, but the principles apply in general to other files.
Images are placed in HTML pages with an img tag. It is convenient for these tags to use a relative URL to refer to the server from which the HTML page originated. Webpages that use relative URLs are easy to move from one server to another. There are two types of relative URLs: those that start with a "/" character and those that do not. The following URL starts with a "/" character:
If MSP returns an HTML page containing this URL, the browser will try to load the image from the Images directory within the Wolfram Web Engine web application. The servlet container is capable of returning this image, which should appear correctly. If these image requests do not work and you are using a servlet container as a back end to another web server, you should make certain that it forwards requests for images to the servlet engine.
An alternative is a relative URL that does not start with a "/" character. For example, an HTML page, which is generated by a URL such as http://server/webengine/Demo/Test.jsp, may contain an img tag such as the following:
In this case, the browser will try to retrieve the image with the URL http://server/webengine/Demo/folder/bullet.gif, which can be processed by the servlet container to return the appropriate image file. This is convenient because you can place images and JSPs together in the same directory.