Transcript Slide 1

COMP 321
Week 13
Overview
Filters
Scaling and Remote Models
MVC and Struts
Problem
We have a working web application with
many Servlets. Now we decide we need
to keep track of how many times each
users accesses each Servlet
How can we do this without modifying
each Servlet?
Filters
Can intercept requests before they are
passed to the servlet, and intercept
responses before they are returned to the
client
Can be chained together
Filters
Request filters can:
 Perform security checks
 Reformat request headers or bodies
 Audit or log requests
Response filters can:
 Compress the response stream
 Append to or alter the response stream
 Create an entirely different response
Difference between a request and response filter is only
the programmers intention – there is no actual difference
in implementation!
Logging Requests
public class BeerRequestFilter implements Filter {
private FilterConfig fc;
public void init(FilterConfig config) throws ServletException {
this.fc = config;
}
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) req;
String name = httpReq.getRemoteUser();
if (name != null) {
fc.getServletContext().log("User " + name + " is updating");
}
chain.doFilter(req, resp);
}
}
Declaring and Ordering Filters
<!-- In DD -->
<filter>
<filter-name>BeerRequest</filter-name>
<filter-class>com.example.web.BeerRequestFilter</filter-class>
<init-param>
<param-name>LogFileName</param-name>
<param-value>UserLog.txt</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>BeerRequest</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>BeerRequest</filter-name>
<servlet-name>AdviceServlet</servlet-name>
</filter-mapping>
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /Recipes/HopsReport.do
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /Recipes/HopsReport.do
Filters: 1, 5
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /Recipes/HopsList.do
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /Recipes/HopsList.do
Filters: 1, 5, 2
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /Recipes/Modify/ModRecipes.do
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /Recipes/Modify/ModRecipes.do
Filters: 1, 5, 4
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /HopsList.do
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /HopsList.do
Filters: 5
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /Recipes/Add/AddRecipes.do
Sharpen Your Pencil
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/Recipes/*</url-pattern>
</filter-mapping>
<!-- Mapping ... -->
<filter-name>Filter2</filter-name>
<servlet-name>/Recipes/HopsList.do</servlet-name>
<filter-name>Filter3</filter-name>
<url-pattern>/Recipes/Add/*</url-pattern>
<filter-name>Filter4</filter-name>
<servlet-name>/Recipes/Modify/ModRecipes.do</servlet-name>
<filter-name>Filter5</filter-name>
<url-pattern>/*</url-pattern>
Request: /Recipes/Add/AddRecipes.do
Filters: 1, 3, 5
Response Filters
What if we want to compress the
response? How can we do this?
Will this work?
public void doFilter(…) {
// request handling
chain.doFilter(request, response);
// do compression here
}
Response Filters
By the time the filter gets the response
back, the servlet has already written to the
output stream in the response, and the
data has been sent back to the browser
We need to intercept this data somehow
Response Filters
public class CompressionResponseWrapper extends HttpServletResponseWrapper {
public ServletOutputStream getOutputStream() throws IOException {
return new GZIPOutputStream(getResponse().getOutputStream());
}
}
public class MyCompressionFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
CompressionResponseWrapper wrappedResp =
new CompressionResponseWrapper(response);
chain.doFilter(request, wrappedResp);
//Some compression logic here?
}
Response Filters
public class CompressionResponseWrapper extends HttpServletResponseWrapper {
public ServletOutputStream getOutputStream() throws IOException {
return new GZIPOutputStream(getResponse().getOutputStream());
}
}
public class MyCompressionFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
CompressionResponseWrapper wrappedResp =
new CompressionResponseWrapper(response);
chain.doFilter(request, wrappedResp);
//Some compression logic here?
}
Problems:
getOutputStream() returns a new stream each time it's called
GZIPOutputStream is not a ServletOutputStream
GZIPOutputStream.finish() must be called
Response Filters
public class MyCompressionFilter implements Filter {
private FilterConfig cfg;
private ServletContext ctx;
@Override
public void init(FilterConfig cfg) throws ServletException {
this.cfg = cfg;
ctx = cfg.getServletContext();
ctx.log(cfg.getFilterName() + " initialized.");
}
@Override
public void destroy() {
cfg = null;
ctx = null;
}
Response Filters
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)resp;
String validEncodings = request.getHeader("Accept-Encoding");
if(validEncodings.indexOf("gzip") > -1) {
CompressionResponseWrapper wrappedResp =
new CompressionResponseWrapper(response);
wrappedResp.setHeader("Context-Encoding", "gzip");
chain.doFilter(request, wrappedResp);
wrappedResp.finishGZIP();
ctx.log(cfg.getFilterName() + ": finished the request.");
} else {
ctx.log(cfg.getFilterName() + ": no encoding performed.");
chain.doFilter(request, response);
}
}
}
Response Filters
public class CompressionResponseWrapper extends HttpServletResponseWrapper {
private GZIPServletOutputStream gzos = null;
private PrintWriter pw = null;
private Object streamUsed = null;
public CompressionResponseWrapper(HttpServletResponse response)
{
super(response);
}
public void finishGZIP() throws IOException
{
gzos.finish();
}
Response Filters
@Override
public ServletOutputStream getOutputStream() throws IOException {
if(streamUsed != null && streamUsed != gzos)
throw new IllegalStateException();
if(gzos == null) {
gzos = new GZIPServletOutputStream(getResponse().getOutputStream());
streamUsed = gzos;
}
return gzos;
}
Response Filters
@Override
public PrintWriter getWriter() throws IOException {
if(streamUsed != null && streamUsed != pw)
throw new IllegalStateException();
if(pw == null) {
gzos = new GZIPServletOutputStream(getResponse().getOutputStream());
OutputStreamWriter osw =
new OutputStreamWriter(gzos, getResponse().getCharacterEncoding());
pw = new PrintWriter(osw);
streamUsed = pw;
}
return pw;
}
Response Filters
public class GZIPServletOutputStream extends ServletOutputStream {
GZIPOutputStream os;
public GZIPServletOutputStream(ServletOutputStream sos) throws IOException {
this.os = new GZIPOutputStream(sos);
}
public void finish() throws IOException
{
os.finish();
}
public void write(int param) throws IOException {
os.write(param);
}
}
Horizontal Scaling
Enterprise web applications can get
hundreds of thousands of hits per day
To handle this volume, work must be
distributed across many machines
Hardware is normally configured in tiers,
and increased load can be handled by
adding machines to a tier
Horizontal Scaling
Two Classes of Requirements
Functional:
 Application operates correctly
Non-Functional:





Performance
Modularity
Flexibility
Maintainability
Extensibility
How do we make sure we can handle these?
To Meet Non-functional Requirements
Code to interfaces
Separation of Concerns
Cohesion
Hiding Complexity
Loose Coupling
Increase Declarative Control
Improving the Beer App
Current Implementation:
1. Web request received, Controller calls
ManageCustomer service, and gets a
Customer bean back
2. Controller adds Customer bean to request
object
3. Controller forwards to the View JSP
4. JSP uses EL to get properties and generate
page
Local Model
Question
How can we put the components on
different servers and have them still talk to
each other?
Solution
JNDI – supplies centralized network
service for finding things
RMI – allows method calls to objects on
different machines
JNDI
Java Naming and Directory Interface
Maintains a registry of objects
Allows object lookup via locator string
Allows objects to be relocated
transparently - clients don’t need to know
RMI
Remote method invocation
Allows methods on an object to be called
from a client on a different machine
Moving parameters and return values
across the network requires only that they
be Serializable
RMI (cont’d) – Server Side
1. Create a remote interface
2. Create implementation
3. Generate stub and skeleton
4. Register objects
RMI (cont’d) – Client Side
1. Look up object
2. Call methods normally
Design Issues
We would like to use the same controller
whether the model is local or remote
– How do we handle RMI lookups?
– How do we handle remote exceptions?
Solution
We need a go-between to handle these
things - the Business Delegate
Business Delegate
Looks like a model object - implements
same interface
Connects to the real model object via RMI
Delegates all calls to the real model object
(possibly across the network)
Service Locator
Helps avoid duplicating code in Business
Delegates
Responsible for locating objects via JNDI,
and returning stubs to Business Delegates
Local Model (Take #2)
Remote Model
1. Register services with JNDI
2. Use Business Delegate and Service Locator to get
3.
4.
5.
6.
ManageCustomer stub from JNDI
Use Business Delegate and stub to get Customer bean
(another stub), and return to Controller
Add Customer stub to request
Forward to View JSP
View JSP uses EL to get properties from Customer
bean, unaware that it isn’t the actual bean
Remote Model
Remote Model - Downsides
Fine-grained calls to get properties cause
a large performance hit
JSP shouldn’t have to handle remote
exceptions
How can we solve these problems?
Solution – Transfer Objects!
Remember Transfer Object?
 Serializable beans that can be returned
across remote interfaces
 Prevent simple get/set calls from having to
traverse network boundaries
 See Week 5 slides
Return to MVC
Where we left off…
 Each view was a JSP
 Data was held in model classes
 Each URL had its own controller, and there
was a lot of duplicated code between them
Controller
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Dealing with request...
String c = request.getParameter("startDate");
// Do data conversion on date parameter
// Validate that date is in range
// If any errors happen, forward to hard-coded retry JSP
// Dealing with model...
// Invoke hard-coded model components
// add model results to request object
// Dealing with view...
// dispatch to hard-coded view JSP
}
Controller
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Dealing with request...
String c = request.getParameter("startDate");
// Do data conversion on date parameter
// Validate that date is in range
// If any errors happen, forward to hard-coded retry JSP
// Dealing with model...
// Invoke hard-coded model components
// add model results to request object
// Dealing with view...
// dispatch to hard-coded view JSP
}
The controller is tightly
coupled to the model and
views, and we also have
issues with duplicate code.
How can we split this up?
Controller
1. Handle request – give this task to a
separate validation component
2. Invoke model – declaratively list models
that should be used
3. Dispatch to the view – declaratively
define views to be used based on
controller results
Introducing Struts
1. Controller receives request, looks up
Form Bean, sets attributes, and calls
validate()
2. Controller looks up Action Object, calls
execute()
3. Using result of action, Controller forwards
request to correct view
Struts (cont’d)
Moving to Struts
public class BeerSelectForm extends ActionForm {
private String color;
public void setColor(String color) { this.color = color; }
public String getColor() { return color; }
private static final String VALID_COLORS = "amber,dark,light,brown";
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
if(VALID_COLORS.indexOf(color) == -1) {
errors.add("color", new ActionMessage("error.colorField.notValid");
}
return errors;
}
}
Moving to Struts
public class BeerSelectAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
BeerSelectForm myForm = (BeerSelectForm)form;
//Process the business logic
BeerExpert be = new BeerExpert();
List result = be.getBrands(myForm.getColor());
//Forward to the results view
request.setAttribute("styles", result);
return mapping.findForward("show_results");
}
}
Moving to Struts
<struts-config>
<form-beans>
<form-bean name="selectBeerForm" type="com.example.web.BeerSelectForm"/>
</form-beans>
<action-mappings>
<action path="/SelectBeer"
type="com.example.web.BeerSelectAction"
name="selectBeerForm" scope="request"
validate="true" input="/form.jsp">
<forward name="show_results" path="/result.jsp" />
</action>
</action-mappings>
...
</struts-config>
Progress Check
Due this week
– Lab 12-1 Wrapper Design Problem
Due next week
– Lab 10-1 JSP User Interfaces
– Lab 13-1 Web Frameworks