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

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