source: trunk/autoquest-httpmonitor/src/main/java/de/ugoe/cs/autoquest/httpmonitor/proxy/HttpMonitoringProxyServlet.java @ 1420

Last change on this file since 1420 was 1420, checked in by pharms, 10 years ago
  • removed find bugs warning
File size: 12.9 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.httpmonitor.proxy;
16
17import java.io.IOException;
18import java.io.UnsupportedEncodingException;
19import java.net.URI;
20import java.net.URISyntaxException;
21import java.net.URLEncoder;
22import java.nio.ByteBuffer;
23import java.nio.charset.UnsupportedCharsetException;
24import java.util.Iterator;
25import java.util.LinkedList;
26import java.util.List;
27
28import javax.servlet.http.HttpServletRequest;
29import javax.servlet.http.HttpServletResponse;
30
31import org.eclipse.jetty.client.HttpClient;
32import org.eclipse.jetty.client.HttpRequest;
33import org.eclipse.jetty.client.api.ContentProvider;
34import org.eclipse.jetty.client.api.Request;
35import org.eclipse.jetty.client.api.Response;
36import org.eclipse.jetty.proxy.ProxyServlet;
37import org.eclipse.jetty.util.Fields;
38
39import de.ugoe.cs.autoquest.httpmonitor.exchange.Status;
40import de.ugoe.cs.util.console.Console;
41
42/**
43 * <p>
44 * the servlet deployed in the web server of the proxy that proxies all incoming messages and
45 * forwards a copy of recorded exchanges to the exchange listener manager. It is based on the
46 * proxy servlet provided by jetty. It extends the servlet and implements all hooks required to
47 * get access to the exchanged requests and responses. Each hook forwards the tracked data to the
48 * exchange listener. On exchange completion, the exchange listener ensures a logging of the
49 * recorded exchange.
50 * </p>
51 * <p>
52 * The implementation also overrides the default implementation of the jetty HTTP client
53 * and the jetty HTTP request to fix a bug in forwarding queries.
54 * </p>
55 *
56 * @author Patrick Harms
57 */
58class HttpMonitoringProxyServlet extends ProxyServlet {
59
60    /**  */
61    private static final long serialVersionUID = 1L;
62
63    /**
64     * the proxied server
65     */
66    private String proxiedServer;
67   
68    /**
69     * the port of the proxied server
70     */
71    private int proxiedPort;
72
73    /**
74     * the exchange listener to handle the different events happening when an exchange is proxied
75     */
76    private transient ExchangeListenerManager exchangeListenerManager;
77   
78    /**
79     * <p>
80     * initializes the servlet with the proxied server and the exchange listener
81     * </p>
82     *
83     * @param proxiedServer           the proxied server
84     * @param proxiedPort             the port of the proxied server
85     * @param exchangeListenerManager the exchange listener to handle the different events
86     *                                happening when an exchange is proxied
87     */
88    HttpMonitoringProxyServlet(String                  proxiedServer,
89                               int                     proxiedPort,
90                               ExchangeListenerManager exchangeListenerManager)
91    {
92        this.proxiedServer = proxiedServer;
93        this.proxiedPort = proxiedPort;
94        this.exchangeListenerManager = exchangeListenerManager;
95    }
96
97    /* (non-Javadoc)
98     * @see org.eclipse.jetty.proxy.ProxyServlet#rewriteURI(HttpServletRequest)
99     */
100    @Override
101    protected URI rewriteURI(HttpServletRequest request) {
102        try {
103            return new URI(request.getScheme(), null, proxiedServer, proxiedPort,
104                           request.getPathInfo(), request.getQueryString(), null);
105        }
106        catch (URISyntaxException e) {
107            Console.printerrln("could not rewrite URI: " + e);
108            Console.logException(e);
109            return null;
110        }
111    }
112
113    /* (non-Javadoc)
114     * @see org.eclipse.jetty.proxy.ProxyServlet#newHttpClient()
115     */
116    @Override
117    protected HttpClient newHttpClient() {
118        return new BugfixedHttpClient();
119    }
120
121    /* (non-Javadoc)
122     * @see org.eclipse.jetty.proxy.ProxyServlet#customizeProxyRequest(Request, HttpServletRequest)
123     */
124    @Override
125    protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request) {
126        super.customizeProxyRequest(proxyRequest, request);
127       
128        exchangeListenerManager.onRequest(request);
129        proxyRequest.content(new DubbingContentProvider(request, proxyRequest.getContent()));
130    }
131
132    /* (non-Javadoc)
133     * @see ProxyServlet#onResponseContent(HttpServletRequest, HttpServletResponse, Response, byte[], int, int)
134     */
135    @Override
136    protected void onResponseContent(HttpServletRequest  request,
137                                     HttpServletResponse response,
138                                     Response            proxyResponse,
139                                     byte[]              buffer,
140                                     int                 offset,
141                                     int                 length)
142        throws IOException
143    {
144        super.onResponseContent(request, response, proxyResponse, buffer, offset, length);
145       
146        exchangeListenerManager.onResponseContent(request, ByteBuffer.wrap(buffer, offset, length));
147    }
148
149    /* (non-Javadoc)
150     * @see ProxyServlet#onResponseSuccess(HttpServletRequest, HttpServletResponse, Response)
151     */
152    @Override
153    protected void onResponseSuccess(HttpServletRequest  request,
154                                     HttpServletResponse response,
155                                     Response            proxyResponse)
156    {
157        exchangeListenerManager.onResponse(request, response);
158        exchangeListenerManager.onFinish(request, Status.SUCCESS);
159       
160        super.onResponseSuccess(request, response, proxyResponse);
161    }
162
163    /* (non-Javadoc)
164     * @see ProxyServlet#onResponseFailure(HttpServletRequest, HttpServletResponse, Response, Throwable)
165     */
166    @Override
167    protected void onResponseFailure(HttpServletRequest  request,
168                                     HttpServletResponse response,
169                                     Response            proxyResponse,
170                                     Throwable           failure)
171    {
172        exchangeListenerManager.onResponse(request, response);
173        exchangeListenerManager.onFinish(request, Status.FAILURE);
174       
175        super.onResponseFailure(request, response, proxyResponse, failure);
176    }
177
178    /**
179     * This content provided is required to copy the content of a proxied request. It uses
180     * delegation to wrap the original content provided but to also copy the data and forward
181     * it to the exchange listener manager.
182     */
183    private class DubbingContentProvider implements ContentProvider {
184
185        /**
186         * the dubbed request
187         */
188        private HttpServletRequest request;
189       
190        /**
191         * the original content provider of which the data is copied
192         */
193        private ContentProvider delegate;
194
195        /**
196         * initializes this content provider with the copied request and the delegate.
197         */
198        public DubbingContentProvider(HttpServletRequest request, ContentProvider delegate) {
199            this.request = request;
200            this.delegate = delegate;
201        }
202
203        /* (non-Javadoc)
204         * @see java.lang.Iterable#iterator()
205         */
206        @Override
207        public Iterator<ByteBuffer> iterator() {
208            return new DubbingByteBufferIterator(request, delegate.iterator());
209        }
210
211        /* (non-Javadoc)
212         * @see org.eclipse.jetty.client.api.ContentProvider#getLength()
213         */
214        @Override
215        public long getLength() {
216            return delegate.getLength();
217        }
218
219    }
220
221    /**
222     * This iterator is used to implement the {@link DubbingContentProvider}. It uses delegation
223     * for wrapping the original iterator and forwards all copied data to the exchange listener
224     * manager. Furthermore, it combines several buffers into one. This seems to be required if
225     * SOAP messages are proxied.
226     */
227    public class DubbingByteBufferIterator implements Iterator<ByteBuffer> {
228
229        /**
230         * the dubbed request
231         */
232        private HttpServletRequest request;
233       
234        /**
235         * the original iterator of which the data is copied
236         */
237        private Iterator<ByteBuffer> delegate;
238       
239        /**
240         * initializes this iterator with the copied request and the delegate.
241         */
242        public DubbingByteBufferIterator(HttpServletRequest request, Iterator<ByteBuffer> delegate) {
243            this.request = request;
244            this.delegate = delegate;
245        }
246
247        /* (non-Javadoc)
248         * @see java.util.Iterator#hasNext()
249         */
250        @Override
251        public boolean hasNext() {
252            return delegate.hasNext();
253        }
254
255        /* (non-Javadoc)
256         * @see java.util.Iterator#next()
257         */
258        @Override
259        public ByteBuffer next() {
260            List<ByteBuffer> buffers = new LinkedList<ByteBuffer>();
261           
262            int size = 0;
263            while (delegate.hasNext()) {
264                ByteBuffer next = delegate.next();
265                exchangeListenerManager.onRequestContent(request, next.duplicate());
266
267                ByteBuffer copy = ByteBuffer.allocate(next.limit());
268                copy.put(next);
269                copy.position(0);
270                buffers.add(copy);
271               
272                size += next.limit();
273            }
274           
275            ByteBuffer buffer = ByteBuffer.allocate(size);
276           
277            for (ByteBuffer orig : buffers) {
278                buffer.put(orig);
279            }
280           
281            buffer.position(0);
282           
283            return buffer;
284        }
285
286        /* (non-Javadoc)
287         * @see java.util.Iterator#remove()
288         */
289        @Override
290        public void remove() {
291            while (delegate.hasNext()) {
292                delegate.remove();
293            }
294        }
295
296    }
297
298    /**
299     * <p>
300     * bugfix implementation of the jetty HTTP client to be able to use only bugfixed HTTP requests
301     * </p>
302     *
303     * @author Patrick Harms
304     */
305    private static class BugfixedHttpClient extends HttpClient {
306
307        /* (non-Javadoc)
308         * @see org.eclipse.jetty.client.HttpClient#newRequest(java.net.URI)
309         */
310        @Override
311        public Request newRequest(URI uri) {
312            return new BugfixedHttpRequest(this, uri);
313        }
314
315    }
316
317    /**
318     * <p>
319     * bugfix implementation of the jetty HTTP request. This ensures that query string
320     * representations are correctly forwarded in the case that a query parameter does not have
321     * a value.
322     * </p>
323     *
324     * @author Patrick Harms
325     */
326    private static class BugfixedHttpRequest extends HttpRequest {
327
328        /**
329         * <p>
330         * Constructor to override parent constructor
331         * </p>
332         */
333        BugfixedHttpRequest(HttpClient client, URI uri) {
334            super(client, uri);
335        }
336
337        /*
338         * (non-Javadoc)
339         *
340         * @see org.eclipse.jetty.client.HttpRequest#getQuery()
341         */
342        @Override
343        public String getQuery() {
344            return buildQuery();
345        }
346
347        /**
348         * <p>
349         * this corrects the bug implemented in the parent class on creating the query string
350         * </p>
351         *
352         * @return the correct query string
353         */
354        private String buildQuery() {
355            StringBuilder result = new StringBuilder();
356            for (Iterator<Fields.Field> iterator = super.getParams().iterator(); iterator.hasNext();)
357            {
358                Fields.Field field = iterator.next();
359                String[] values = field.values();
360                for (int i = 0; i < values.length; ++i) {
361                    if (i > 0) {
362                        result.append("&");
363                    }
364
365                    result.append(field.name());
366
367                    if ((values[i] != null) && (!"".equals(values[i].trim()))) {
368                        result.append("=");
369                        result.append(urlEncode(values[i]));
370                    }
371                }
372                if (iterator.hasNext()) {
373                    result.append("&");
374                }
375            }
376            return result.toString();
377        }
378
379        /**
380         * <p>
381         * convenience method copied from parent class, as it is private there.
382         * </p>
383         *
384         * @see HttpRequest#urlEncode(String)
385         */
386        private String urlEncode(String value) {
387            String encoding = "UTF-8";
388            try {
389                return URLEncoder.encode(value, encoding);
390            }
391            catch (UnsupportedEncodingException e) {
392                throw new UnsupportedCharsetException(encoding);
393            }
394        }
395    }
396}
Note: See TracBrowser for help on using the repository browser.