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

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