// Copyright 2012 Georg-August-Universität Göttingen, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.ugoe.cs.autoquest.httpmonitor.proxy; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import de.ugoe.cs.autoquest.httpmonitor.HttpMonitorExchangeHandler; import de.ugoe.cs.autoquest.httpmonitor.exchange.Address; import de.ugoe.cs.autoquest.httpmonitor.exchange.Content; import de.ugoe.cs.autoquest.httpmonitor.exchange.Cookie; import de.ugoe.cs.autoquest.httpmonitor.exchange.Cookies; import de.ugoe.cs.autoquest.httpmonitor.exchange.Header; import de.ugoe.cs.autoquest.httpmonitor.exchange.Headers; import de.ugoe.cs.autoquest.httpmonitor.exchange.HttpExchange; import de.ugoe.cs.autoquest.httpmonitor.exchange.HttpRequest; import de.ugoe.cs.autoquest.httpmonitor.exchange.HttpResponse; import de.ugoe.cs.autoquest.httpmonitor.exchange.Method; import de.ugoe.cs.autoquest.httpmonitor.exchange.ObjectFactory; import de.ugoe.cs.autoquest.httpmonitor.exchange.Protocol; import de.ugoe.cs.autoquest.httpmonitor.exchange.Status; import de.ugoe.cs.util.console.Console; /** *

* recording an exchange can not be done in one step. This is due to the fact, that the proxy * servlet notifies different processing states for requests and response. An exchange listener * records all these event. On the occurrence of the final event, it compiles an * {@link HttpExchange} and forwards it to the exchange handler. *

* * @author Patrick Harms */ class ExchangeListener { /** *

* the exchange handler to forward compiles exchanges to *

*/ private HttpMonitorExchangeHandler exchangeHandler; /** *

* the request of compiled exchange *

*/ private HttpServletRequest request; /** *

* the content of the request of compiled exchange *

*/ private List requestData = new LinkedList(); /** *

* the response of compiled exchange *

*/ private HttpServletResponse response; /** *

* the content of the response of compiled exchange *

*/ private List responseData = new LinkedList(); /** *

* the last time an event for the exchange was received (used for supporting timeouts) *

*/ private long lastUpdate = System.currentTimeMillis(); /** *

* initialized the exchange listener with the exchange handler to forward compiled exchanges to *

* * @param exchangeHandler the exchange handler to forward compiled exchanges to */ ExchangeListener(HttpMonitorExchangeHandler exchangeHandler) { this.exchangeHandler = exchangeHandler; } /** *

* called, when the request was received by the proxy *

* * @param request the request of the exchange */ public void onRequest(HttpServletRequest request) throws IllegalStateException { Console.traceln(Level.FINEST, this + ": onRequest " + request); if (request == null) { throw new IllegalArgumentException("request must not be null"); } lastUpdate = System.currentTimeMillis(); this.request = request; } /** *

* called, when some content of the request was processed by the proxy *

* * @param data the processed content of the request of the exchange */ public void onRequestContent(ByteBuffer data) { Console.traceln(Level.FINEST, this + ": onRequestContent " + data); if (data == null) { throw new IllegalArgumentException("data must not be null"); } lastUpdate = System.currentTimeMillis(); requestData.add(data); } /** *

* called, when the response is to be returned by the proxy *

* * @param response the response of the exchange */ public void onResponse(HttpServletResponse response) { Console.traceln(Level.FINEST, this + ": onResponse " + response); if (response == null) { throw new IllegalArgumentException("response must not be null"); } lastUpdate = System.currentTimeMillis(); this.response = response; } /** *

* called, when some content of the response was processed by the proxy *

* * @param data the processed content of the response of the exchange */ public void onResponseContent(ByteBuffer data) { Console.traceln(Level.FINEST, this + ": onResponseContent " + data); if (data == null) { throw new IllegalArgumentException("data must not be null"); } lastUpdate = System.currentTimeMillis(); responseData.add(data); } /** *

* called, when proxy finished proxying a request *

* * @param status the status of the proxying after finalization */ public void onFinish(Status status) { Console.traceln(Level.FINEST, this + ": onFinish " + status); if (status == null) { throw new IllegalArgumentException("status must not be null"); } lastUpdate = System.currentTimeMillis(); sendToExchangeHandler(status); } /** * @return the request of the exchange */ HttpServletRequest getRequest() { return request; } /** * @return the last time this listener received an event */ long getLastUpdate() { return lastUpdate; } /** *

* convenience method to compile an {@link HttpExchange} and send it to the exchange handler * after finalization of the exchange. *

* * @param status the status of the proxying after finalization */ private void sendToExchangeHandler(Status status) { ObjectFactory eventObjectFactory = new ObjectFactory(); HttpExchange exchange = eventObjectFactory.createHttpExchange(); exchange.setStatus(status); // the remote address Address address = eventObjectFactory.createAddress(); address.setIp(request.getRemoteAddr()); address.setHost(request.getRemoteHost()); address.setPort(BigInteger.valueOf(request.getRemotePort())); exchange.setSender(address); // the local address address = eventObjectFactory.createAddress(); address.setIp(request.getLocalAddr()); address.setHost(request.getLocalName()); address.setPort(BigInteger.valueOf(request.getLocalPort())); exchange.setReceiver(address); exchange.setRequest(map(request, eventObjectFactory)); exchange.setResponse(map(response, eventObjectFactory)); exchangeHandler.handleHttpExchange(exchange); } /** *

* convenience method to map an {@link HttpServletRequest} to an {@link HttpRequest} *

*/ private HttpRequest map(HttpServletRequest request, ObjectFactory eventObjectFactory) { HttpRequest eventRequest = eventObjectFactory.createHttpRequest(); eventRequest.setMethod(Method.fromValue(request.getMethod())); eventRequest.setProtocol(Protocol.fromValue(request.getProtocol())); eventRequest.setUrl(request.getRequestURL().toString()); eventRequest.setQuery(request.getQueryString()); Headers headers = eventObjectFactory.createHeaders(); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); Enumeration headerValues = request.getHeaders(headerName); while (headerValues.hasMoreElements()) { Header header = eventObjectFactory.createHeader(); header.setKey(headerName); header.setValue(headerValues.nextElement()); headers.getHeader().add(header); } } eventRequest.setHeaders(headers); if (request.getCookies() != null) { Cookies cookies = eventObjectFactory.createCookies(); for (javax.servlet.http.Cookie requestCookie : request.getCookies()) { Cookie cookie = eventObjectFactory.createCookie(); cookie.setComment(requestCookie.getComment()); cookie.setDomain(requestCookie.getDomain()); cookie.setIsHttpOnly(requestCookie.isHttpOnly()); cookie.setIsSecure(requestCookie.getSecure()); cookie.setMaxAge(BigInteger.valueOf(requestCookie.getMaxAge())); cookie.setName(requestCookie.getName()); cookie.setPath(requestCookie.getPath()); cookie.setValue(requestCookie.getValue()); cookie.setVersion(BigInteger.valueOf(requestCookie.getVersion())); cookies.getCookie().add(cookie); } eventRequest.setCookies(cookies); } eventRequest.setAuthType(request.getAuthType()); eventRequest.setRemoteUser(request.getRemoteUser()); eventRequest.setRequestedSessionId(request.getRequestedSessionId()); if (requestData.size() > 0) { Content content = eventObjectFactory.createContent(); content.setEncoding(request.getCharacterEncoding()); content.setType(request.getContentType()); content.setLength(request.getContentLength()); content.setData(createString(requestData)); eventRequest.setContent(content); } return eventRequest; } /** *

* convenience method to map an {@link HttpServletResponse} to an {@link HttpResponse} *

*/ private HttpResponse map(HttpServletResponse response, ObjectFactory eventObjectFactory) { HttpResponse eventResponse = eventObjectFactory.createHttpResponse(); eventResponse.setStatus(BigInteger.valueOf(response.getStatus())); Headers headers = eventObjectFactory.createHeaders(); Collection headerNames = response.getHeaderNames(); for (String headerName : headerNames) { Collection headerValues = response.getHeaders(headerName); for (String headerValue : headerValues) { Header header = eventObjectFactory.createHeader(); header.setKey(headerName); header.setValue(headerValue); headers.getHeader().add(header); } } eventResponse.setHeaders(headers); if (responseData.size() > 0) { Content content = eventObjectFactory.createContent(); content.setEncoding(response.getCharacterEncoding()); content.setType(response.getContentType()); String data = createString(responseData); content.setLength(data.length()); content.setData(data); eventResponse.setContent(content); } return eventResponse; } /** *

* convenience method to create a string out of recorded request or response content *

*/ private String createString(List bufferList) { StringBuffer str = new StringBuffer(); for (ByteBuffer buffer : bufferList) { while (buffer.hasRemaining()) { str.append((char) buffer.get()); } } return str.toString(); } }