COMP 321 Week 13. Overview Filters Scaling and Remote Models MVC and Struts

Preview:

Citation preview

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 Requestspublic 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 Filterspublic 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 Filterspublic 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 Filterspublic 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 Filterspublic 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@Overridepublic 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@Overridepublic 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 Filterspublic 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 ManageCustomer stub from JNDI

3. Use Business Delegate and stub to get Customer bean (another stub), and return to Controller

4. Add Customer stub to request

5. Forward to View JSP

6. 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

Controllerprotected 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}

Controllerprotected 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 Strutspublic 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 Strutspublic 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

Recommended