001: /*
002: * Copyright (c) 2002-2008 Gargoyle Software Inc. All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * 1. Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: * 2. Redistributions in binary form must reproduce the above copyright notice,
010: * this list of conditions and the following disclaimer in the documentation
011: * and/or other materials provided with the distribution.
012: * 3. The end-user documentation included with the redistribution, if any, must
013: * include the following acknowledgment:
014: *
015: * "This product includes software developed by Gargoyle Software Inc.
016: * (http://www.GargoyleSoftware.com/)."
017: *
018: * Alternately, this acknowledgment may appear in the software itself, if
019: * and wherever such third-party acknowledgments normally appear.
020: * 4. The name "Gargoyle Software" must not be used to endorse or promote
021: * products derived from this software without prior written permission.
022: * For written permission, please contact info@GargoyleSoftware.com.
023: * 5. Products derived from this software may not be called "HtmlUnit", nor may
024: * "HtmlUnit" appear in their name, without prior written permission of
025: * Gargoyle Software Inc.
026: *
027: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
028: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
029: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
030: * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
031: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
032: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
033: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
036: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: */
038: package com.gargoylesoftware.htmlunit;
039:
040: import java.io.BufferedInputStream;
041: import java.io.ByteArrayInputStream;
042: import java.io.ByteArrayOutputStream;
043: import java.io.File;
044: import java.io.IOException;
045: import java.io.InputStream;
046: import java.lang.reflect.Field;
047: import java.lang.reflect.Method;
048: import java.net.URL;
049: import java.util.Iterator;
050: import java.util.Map;
051:
052: import org.apache.commons.httpclient.HttpClient;
053: import org.apache.commons.httpclient.HttpMethodBase;
054: import org.apache.commons.httpclient.HttpStatus;
055: import org.apache.commons.httpclient.StatusLine;
056: import org.apache.commons.httpclient.methods.GetMethod;
057: import org.apache.commons.httpclient.methods.multipart.FilePart;
058: import org.mortbay.jetty.Handler;
059: import org.mortbay.jetty.Server;
060: import org.mortbay.jetty.handler.HandlerList;
061: import org.mortbay.jetty.handler.ResourceHandler;
062: import org.mortbay.jetty.servlet.Context;
063: import org.mortbay.jetty.webapp.WebAppClassLoader;
064: import org.mortbay.jetty.webapp.WebAppContext;
065:
066: import com.gargoylesoftware.base.testing.BaseTestCase;
067: import com.gargoylesoftware.htmlunit.html.HtmlPage;
068:
069: /**
070: * Tests methods in {@link HttpWebConnection}.
071: *
072: * @version $Revision: 2168 $
073: * @author David D. Kilzer
074: * @author Marc Guillemot
075: * @author Ahmed Ashour
076: */
077: public class HttpWebConnectionTest extends BaseTestCase {
078:
079: /**
080: * The listener port for the web server.
081: */
082: public static final int PORT = 12345;
083:
084: private Server server_;
085:
086: /**
087: * Assert that the two byte arrays are equal
088: * @param expected The expected value
089: * @param actual The actual value
090: */
091: public static void assertEquals(final byte[] expected,
092: final byte[] actual) {
093: assertEquals(null, expected, actual);
094: }
095:
096: /**
097: * Assert that the two byte arrays are equal
098: * @param message The message to display on failure
099: * @param expected The expected value
100: * @param actual The actual value
101: */
102: public static void assertEquals(final String message,
103: final byte[] expected, final byte[] actual) {
104: assertEquals(message, expected, actual, expected.length);
105: }
106:
107: /**
108: * Assert that the two byte arrays are equal
109: * @param message The message to display on failure
110: * @param expected The expected value
111: * @param actual The actual value
112: * @param length How many characters at the beginning of each byte array will be compared.
113: */
114: public static void assertEquals(final String message,
115: final byte[] expected, final byte[] actual, final int length) {
116: if (expected == null && actual == null) {
117: return;
118: }
119: if (expected == null || actual == null) {
120: fail(message);
121: }
122: if (expected.length < length || actual.length < length) {
123: fail(message);
124: }
125: for (int i = 0; i < length; i++) {
126: assertEquals(message, expected[i], actual[i]);
127: }
128: }
129:
130: /**
131: * Assert that the two input streams are the same.
132: * @param expected The expected value
133: * @param actual The actual value
134: * @throws IOException If an IO problem occurs during comparison
135: */
136: public static void assertEquals(final InputStream expected,
137: final InputStream actual) throws IOException {
138: assertEquals(null, expected, actual);
139: }
140:
141: /**
142: * Assert that the two input streams are the same.
143: * @param message The message to display on failure
144: * @param expected The expected value
145: * @param actual The actual value
146: * @throws IOException If an IO problem occurs during comparison
147: */
148: public static void assertEquals(final String message,
149: final InputStream expected, final InputStream actual)
150: throws IOException {
151:
152: if (expected == null && actual == null) {
153: return;
154: }
155:
156: if (expected == null || actual == null) {
157: try {
158: fail(message);
159: } finally {
160: try {
161: if (expected != null) {
162: expected.close();
163: }
164: } finally {
165: if (actual != null) {
166: actual.close();
167: }
168: }
169: }
170: }
171:
172: InputStream expectedBuf = null;
173: InputStream actualBuf = null;
174: try {
175: expectedBuf = new BufferedInputStream(expected);
176: actualBuf = new BufferedInputStream(actual);
177:
178: final byte[] expectedArray = new byte[2048];
179: final byte[] actualArray = new byte[2048];
180:
181: int expectedLength = expectedBuf.read(expectedArray);
182: while (true) {
183:
184: final int actualLength = actualBuf.read(actualArray);
185: assertEquals(message, expectedLength, actualLength);
186:
187: if (expectedLength == -1) {
188: break;
189: }
190:
191: assertEquals(message, expectedArray, actualArray,
192: expectedLength);
193: expectedLength = expectedBuf.read(expectedArray);
194: }
195: } finally {
196: try {
197: if (expectedBuf != null) {
198: expectedBuf.close();
199: }
200: } finally {
201: if (actualBuf != null) {
202: actualBuf.close();
203: }
204: }
205: }
206: }
207:
208: /**
209: * Create an instance.
210: *
211: * @param name The name of the test.
212: */
213: public HttpWebConnectionTest(final String name) {
214: super (name);
215: }
216:
217: /**
218: * Test creation of a web response
219: * @throws Exception If the test fails
220: */
221: public void testMakeWebResponse() throws Exception {
222: final URL url = new URL("http://htmlunit.sourceforge.net/");
223: final String content = "<html><head></head><body></body></html>";
224: final int httpStatus = HttpStatus.SC_OK;
225: final long loadTime = 500L;
226:
227: final HttpMethodBase httpMethod = new GetMethod(url.toString());
228: final Field responseBodyField = HttpMethodBase.class
229: .getDeclaredField("responseBody");
230: responseBodyField.setAccessible(true);
231: responseBodyField.set(httpMethod, content.getBytes());
232:
233: final StatusLine statusLine = new StatusLine("HTTP/1.0 200 OK");
234: final Field statusLineField = HttpMethodBase.class
235: .getDeclaredField("statusLine");
236: statusLineField.setAccessible(true);
237: statusLineField.set(httpMethod, statusLine);
238:
239: final HttpWebConnection connection = new HttpWebConnection(
240: new WebClient());
241: final Method method = connection.getClass().getDeclaredMethod(
242: "makeWebResponse",
243: new Class[] { int.class, HttpMethodBase.class,
244: URL.class, long.class, String.class });
245: method.setAccessible(true);
246:
247: final WebResponse response = (WebResponse) method.invoke(
248: connection, new Object[] { new Integer(httpStatus),
249: httpMethod, url, new Long(loadTime),
250: TextUtil.DEFAULT_CHARSET });
251:
252: assertEquals(httpStatus, response.getStatusCode());
253: assertEquals(url, response.getUrl());
254: assertEquals(loadTime, response.getLoadTimeInMilliSeconds());
255: assertEquals(content, response.getContentAsString());
256: assertEquals(content.getBytes(), response.getResponseBody());
257: assertEquals(new ByteArrayInputStream(content.getBytes()),
258: response.getContentAsStream());
259: }
260:
261: /**
262: * Testing Jetty
263: * @throws Exception on failure
264: */
265: public void testJettyProofOfConcept() throws Exception {
266: server_ = startWebServer("./");
267:
268: final WebClient client = new WebClient();
269: Page page = client.getPage("http://localhost:" + PORT
270: + "/src/test/resources/event_coordinates.html");
271: final WebConnection defaultConnection = client
272: .getWebConnection();
273: assertInstanceOf("HttpWebConnection should be the default",
274: defaultConnection, HttpWebConnection.class);
275: assertInstanceOf("Response should be valid HTML", page,
276: HtmlPage.class);
277:
278: // test that // is escaped
279: final URL url = new URL("http://localhost:" + PORT
280: + "//src/test/resources/event_coordinates.html");
281: page = client.getPage(url);
282: assertEquals(url.toExternalForm(), page.getWebResponse()
283: .getUrl().toExternalForm());
284: }
285:
286: /**
287: * Starts the web server on the default {@link #PORT}.
288: * The given resourceBase is used to be the ROOT directory that serves the default context.
289: * <p><b>Don't forget to stop the returned HttpServer after the test</b>
290: *
291: * @param resouceBase the base of resources for the default context.
292: * @return the started web server.
293: * @throws Exception If the test fails.
294: */
295: public static Server startWebServer(final String resouceBase)
296: throws Exception {
297: final Server server = new Server(PORT);
298:
299: final Context context = new Context();
300: context.setContextPath("/");
301: context.setResourceBase(resouceBase);
302:
303: final ResourceHandler resourceHandler = new ResourceHandler();
304: resourceHandler.setResourceBase(resouceBase);
305:
306: final HandlerList handlers = new HandlerList();
307: handlers
308: .setHandlers(new Handler[] { resourceHandler, context });
309: server.setHandler(handlers);
310: server.setHandler(resourceHandler);
311:
312: server.start();
313: return server;
314: }
315:
316: /**
317: * Starts the web server on the default {@link #PORT}.
318: * The given resourceBase is used to be the ROOT directory that serves the default context.
319: * <p><b>Don't forget to stop the returned HttpServer after the test</b>
320: *
321: * @param resouceBase the base of resources for the default context.
322: * @param classpath additional classpath entries to add (may be null).
323: * @return the started web server.
324: * @throws Exception If the test fails.
325: */
326: public static Server startWebServer(final String resouceBase,
327: final String[] classpath) throws Exception {
328: final Server server = new Server(PORT);
329:
330: final WebAppContext context = new WebAppContext();
331: context.setContextPath("/");
332: context.setResourceBase(resouceBase);
333: final WebAppClassLoader loader = new WebAppClassLoader(context);
334: if (classpath != null) {
335: for (int i = 0; i < classpath.length; i++) {
336: loader.addClassPath(classpath[i]);
337: }
338: }
339: context.setClassLoader(loader);
340: server.setHandler(context);
341: server.start();
342: return server;
343: }
344:
345: /**
346: * Starts the web server on the default {@link #PORT}.
347: * The given resourceBase is used to be the ROOT directory that serves the default context.
348: * <p><b>Don't forget to stop the returned HttpServer after the test</b>
349: *
350: * @param resouceBase the base of resources for the default context.
351: * @param classpath additional classpath entries to add (may be null).
352: * @param servlets Map of <Class,String>, Class is the class, while String is the path spec.
353: * @return the started web server.
354: * @throws Exception If the test fails.
355: */
356: public static Server startWebServer(final String resouceBase,
357: final String[] classpath, final Map servlets)
358: throws Exception {
359: final Server server = new Server(PORT);
360:
361: final WebAppContext context = new WebAppContext();
362: context.setContextPath("/");
363: context.setResourceBase(resouceBase);
364:
365: for (final Iterator servletKeys = servlets.keySet().iterator(); servletKeys
366: .hasNext();) {
367: final Class servlet = (Class) servletKeys.next();
368: final String pathSpec = (String) servlets.get(servlet);
369: context.addServlet(servlet, pathSpec);
370:
371: }
372: final WebAppClassLoader loader = new WebAppClassLoader(context);
373: if (classpath != null) {
374: for (int i = 0; i < classpath.length; i++) {
375: loader.addClassPath(classpath[i]);
376: }
377: }
378: context.setClassLoader(loader);
379: server.setHandler(context);
380: server.start();
381: return server;
382: }
383:
384: /**
385: * Stops the web server.
386: *
387: * @param httpServer the web server.
388: * @throws Exception If the test fails.
389: */
390: public static void stopWebServer(final Server httpServer)
391: throws Exception {
392: if (httpServer != null) {
393: httpServer.stop();
394: }
395: }
396:
397: /**
398: * {@inheritDoc}
399: * Stops the web server if it has been started.
400: */
401: protected void tearDown() throws Exception {
402: super .tearDown();
403: stopWebServer(server_);
404: server_ = null;
405: }
406:
407: /**
408: * Test for feature request 1438216: HttpWebConnection should allow extension to create the HttpClient
409: * @throws Exception if the test fails
410: */
411: public void testDesignedForExtension() throws Exception {
412: server_ = startWebServer("./");
413:
414: final WebClient webClient = new WebClient();
415: final boolean[] tabCalled = { false };
416: final WebConnection myWebConnection = new HttpWebConnection(
417: webClient) {
418: protected HttpClient createHttpClient() {
419: tabCalled[0] = true;
420: return new HttpClient();
421: }
422: };
423:
424: webClient.setWebConnection(myWebConnection);
425: webClient.getPage("http://localhost:" + PORT + "/README");
426: assertTrue("createHttpClient has not been called", tabCalled[0]);
427: }
428:
429: /**
430: * Was throwing a NPE on 14.04.06
431: * @throws Exception if the test fails
432: */
433: public void testStateAccess() throws Exception {
434: final WebClient webClient = new WebClient();
435: webClient.getWebConnection().getState();
436: }
437:
438: /**
439: * Test that the right file part is built for a file that doesn't exist
440: * @throws Exception if the test fails
441: */
442: public void testBuildFilePart() throws Exception {
443: final String encoding = "ISO8859-1";
444: final KeyDataPair pair = new KeyDataPair("myFile", new File(
445: "this/doesnt_exist.txt"), "text/plain", encoding);
446: final FilePart part = new HttpWebConnection(new WebClient())
447: .buildFilePart(pair, encoding);
448: final ByteArrayOutputStream baos = new ByteArrayOutputStream();
449: part.send(baos);
450:
451: final String expected = "------------------314159265358979323846\r\n"
452: + "Content-Disposition: form-data; name=\"myFile\"; filename=\"doesnt_exist.txt\"\r\n"
453: + "Content-Type: text/plain\r\n"
454: + "Content-Transfer-Encoding: binary\r\n"
455: + "\r\n"
456: + "\r\n";
457: assertEquals(expected, baos.toString(encoding));
458: }
459: }
|