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
RevLine 
[1374]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;
[1392]18import java.io.UnsupportedEncodingException;
[1374]19import java.net.URI;
20import java.net.URISyntaxException;
[1614]21import java.net.URLDecoder;
[1392]22import java.net.URLEncoder;
[1374]23import java.nio.ByteBuffer;
[1392]24import java.nio.charset.UnsupportedCharsetException;
[1374]25import java.util.Iterator;
26
27import javax.servlet.http.HttpServletRequest;
28import javax.servlet.http.HttpServletResponse;
29
[1392]30import org.eclipse.jetty.client.HttpClient;
31import org.eclipse.jetty.client.HttpRequest;
[1374]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;
[1392]36import org.eclipse.jetty.util.Fields;
[1374]37
[1561]38import de.ugoe.cs.autoquest.plugin.http.logdata.Status;
[1374]39import de.ugoe.cs.util.console.Console;
40
41/**
42 * <p>
[1382]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.
[1374]49 * </p>
[1392]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>
[1374]54 *
55 * @author Patrick Harms
56 */
57class HttpMonitoringProxyServlet extends ProxyServlet {
58
59    /**  */
60    private static final long serialVersionUID = 1L;
61
[1382]62    /**
63     * the proxied server
64     */
[1374]65    private String proxiedServer;
66   
[1382]67    /**
68     * the port of the proxied server
69     */
[1374]70    private int proxiedPort;
71
[1382]72    /**
73     * the exchange listener to handle the different events happening when an exchange is proxied
74     */
[1384]75    private transient ExchangeListenerManager exchangeListenerManager;
[1374]76   
77    /**
78     * <p>
[1382]79     * initializes the servlet with the proxied server and the exchange listener
[1374]80     * </p>
81     *
[1382]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
[1374]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)
[1382]97     * @see org.eclipse.jetty.proxy.ProxyServlet#rewriteURI(HttpServletRequest)
[1374]98     */
99    @Override
100    protected URI rewriteURI(HttpServletRequest request) {
101        try {
[1614]102            String query = request.getQueryString();
103           
104            if (query != null) {
105                query = URLDecoder.decode(query, "UTF-8");
106            }
107           
[1374]108            return new URI(request.getScheme(), null, proxiedServer, proxiedPort,
[1614]109                           request.getPathInfo(), query, null);
[1374]110        }
111        catch (URISyntaxException e) {
112            Console.printerrln("could not rewrite URI: " + e);
113            Console.logException(e);
114            return null;
115        }
[1614]116        catch (UnsupportedEncodingException e) {
117            Console.printerrln("could not rewrite URI: " + e);
118            Console.logException(e);
119            return null;
120        }
[1374]121    }
122
123    /* (non-Javadoc)
[1392]124     * @see org.eclipse.jetty.proxy.ProxyServlet#newHttpClient()
125     */
126    @Override
127    protected HttpClient newHttpClient() {
128        return new BugfixedHttpClient();
129    }
130
131    /* (non-Javadoc)
[1382]132     * @see org.eclipse.jetty.proxy.ProxyServlet#customizeProxyRequest(Request, HttpServletRequest)
[1374]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)
[1382]143     * @see ProxyServlet#onResponseContent(HttpServletRequest, HttpServletResponse, Response, byte[], int, int)
[1374]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)
[1382]160     * @see ProxyServlet#onResponseSuccess(HttpServletRequest, HttpServletResponse, Response)
[1374]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)
[1382]174     * @see ProxyServlet#onResponseFailure(HttpServletRequest, HttpServletResponse, Response, Throwable)
[1374]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    /**
[1382]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.
[1374]192     */
193    private class DubbingContentProvider implements ContentProvider {
194
[1382]195        /**
196         * the dubbed request
197         */
[1374]198        private HttpServletRequest request;
199       
[1382]200        /**
201         * the original content provider of which the data is copied
202         */
[1374]203        private ContentProvider delegate;
204
205        /**
[1382]206         * initializes this content provider with the copied request and the delegate.
[1374]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    /**
[1392]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.
[1374]236     */
237    public class DubbingByteBufferIterator implements Iterator<ByteBuffer> {
238
[1382]239        /**
240         * the dubbed request
241         */
[1374]242        private HttpServletRequest request;
243       
[1382]244        /**
245         * the original iterator of which the data is copied
246         */
[1374]247        private Iterator<ByteBuffer> delegate;
248       
249        /**
[1382]250         * initializes this iterator with the copied request and the delegate.
[1374]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() {
[1563]270            ByteBuffer next = delegate.next();
[1392]271           
[1567]272            ByteBuffer clone1 = ByteBuffer.allocate(next.capacity());
273            ByteBuffer clone2 = ByteBuffer.allocate(next.capacity());
274           
[1563]275            next.rewind();
[1567]276            clone1.put(next);
[1563]277            next.rewind();
[1567]278            clone2.put(next);
279            next.rewind();
280           
281            clone1.flip();
282            clone2.flip();
[1563]283             
[1567]284            exchangeListenerManager.onRequestContent(request, clone1);
[1392]285           
[1567]286            return clone2;
[1374]287        }
288
289        /* (non-Javadoc)
290         * @see java.util.Iterator#remove()
291         */
292        @Override
293        public void remove() {
[1392]294            while (delegate.hasNext()) {
295                delegate.remove();
296            }
[1374]297        }
298
299    }
300
[1392]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     */
[1420]308    private static class BugfixedHttpClient extends HttpClient {
[1392]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     */
[1420]329    private static class BugfixedHttpRequest extends HttpRequest {
[1392]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            }
[1614]379           
380            if (result.length() <= 0) {
381                return null;
382            }
383            else {
384                return result.toString();
385            }
[1392]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    }
[1374]405}
Note: See TracBrowser for help on using the repository browser.