580 likes | 745 Views
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?.
E N D
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 publicclassBeerRequestFilter implementsFilter { privateFilterConfig fc; publicvoidinit(FilterConfig config) throwsServletException { this.fc = config; } publicvoiddoFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throwsIOException, 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 publicclassCompressionResponseWrapper extendsHttpServletResponseWrapper { publicServletOutputStream getOutputStream() throwsIOException { returnnewGZIPOutputStream(getResponse().getOutputStream()); } } publicclassMyCompressionFilter implementsFilter { publicvoiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain) throwsIOException, ServletException { CompressionResponseWrapper wrappedResp = new CompressionResponseWrapper(response); chain.doFilter(request, wrappedResp); //Some compression logic here? }
Response Filters publicclassCompressionResponseWrapper extendsHttpServletResponseWrapper { publicServletOutputStream getOutputStream() throwsIOException { returnnewGZIPOutputStream(getResponse().getOutputStream()); } } publicclassMyCompressionFilter implementsFilter { publicvoiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain) throwsIOException, 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 publicclassMyCompressionFilterimplementsFilter { privateFilterConfig cfg; privateServletContext ctx; @Override publicvoidinit(FilterConfig cfg) throwsServletException { this.cfg = cfg; ctx = cfg.getServletContext(); ctx.log(cfg.getFilterName() + " initialized."); } @Override publicvoiddestroy() { cfg = null; ctx = null; }
Response Filters publicvoiddoFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throwsIOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)resp; String validEncodings = request.getHeader("Accept-Encoding"); if(validEncodings.indexOf("gzip") > -1) { CompressionResponseWrapper wrappedResp = newCompressionResponseWrapper(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 publicclassCompressionResponseWrapper extendsHttpServletResponseWrapper { privateGZIPServletOutputStream gzos = null; privatePrintWriter pw = null; privateObject streamUsed = null; publicCompressionResponseWrapper(HttpServletResponse response) { super(response); } publicvoidfinishGZIP() throwsIOException { gzos.finish(); }
Response Filters @Override publicServletOutputStream getOutputStream() throwsIOException { if(streamUsed != null&& streamUsed != gzos) thrownewIllegalStateException(); if(gzos == null) { gzos = newGZIPServletOutputStream(getResponse().getOutputStream()); streamUsed = gzos; } returngzos; }
Response Filters @Override publicPrintWriter getWriter() throwsIOException { if(streamUsed != null&& streamUsed != pw) thrownewIllegalStateException(); if(pw == null) { gzos = newGZIPServletOutputStream(getResponse().getOutputStream()); OutputStreamWriter osw = newOutputStreamWriter(gzos, getResponse().getCharacterEncoding()); pw = newPrintWriter(osw); streamUsed = pw; } returnpw; }
Response Filters publicclass GZIPServletOutputStream extends ServletOutputStream { GZIPOutputStream os; public GZIPServletOutputStream(ServletOutputStream sos) throws IOException { this.os = new GZIPOutputStream(sos); } publicvoid finish() throws IOException { os.finish(); } publicvoid 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
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: • Web request received, Controller calls ManageCustomer service, and gets a Customer bean back • Controller adds Customer bean to request object • Controller forwards to the View JSP • JSP uses EL to get properties and generate page
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 • Create a remote interface • Create implementation • Generate stub and skeleton • Register objects
RMI (cont’d) – Client Side • Look up object • 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
Remote Model • Register services with JNDI • Use Business Delegate and Service Locator to get 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 - 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 protectedvoiddoPost(HttpServletRequest request, HttpServletResponse response) throwsServletException, 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 }