From 4912c23732a7f38987d08db413a051b6e1f02293 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 17 Dec 2009 03:31:26 +0000 Subject: [PATCH] 297783 Handle HEAD reponses in HttpClient git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@1157 7e9141cc-0065-0410-87d8-b60c137991c4 --- VERSION.txt | 1 + .../eclipse/jetty/client/HttpConnection.java | 5 +- .../jetty/client/ContentExchangeTest.java | 287 ++++++++++++++++++ .../org/eclipse/jetty/http/HttpParser.java | 31 +- 4 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/ContentExchangeTest.java diff --git a/VERSION.txt b/VERSION.txt index 53b64db9446..490740f98d5 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -3,6 +3,7 @@ jetty-7.0.2-SNAPSHOT + 290765 Reset input for HttpExchange retry. + 296765 JMX Connector Server and ShutdownThread + 297421 Hide server/system classes from WebAppClassLoader.getResources + + 297783 Handle HEAD reponses in HttpClient + JETTY-1156 SSL blocking close with JVM Bug busy key fix + JETTY-1157 Don't hold array passed in write(byte[]) + COMETD-46 reset ContentExchange response content on resend diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index d495d7f0a0c..c5845d54a40 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -23,6 +23,7 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpHeaderValues; import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpSchemes; import org.eclipse.jetty.http.HttpVersions; @@ -404,7 +405,9 @@ public class HttpConnection implements Connection auth.setCredentials(_exchange); } - _generator.setRequest(_exchange.getMethod(), uri); + String method=_exchange.getMethod(); + _generator.setRequest(method, uri); + _parser.setHeadResponse(HttpMethods.HEAD.equalsIgnoreCase(method)); HttpFields requestHeaders = _exchange.getRequestFields(); if (_exchange.getVersion() >= HttpVersions.HTTP_1_1_ORDINAL) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ContentExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ContentExchangeTest.java new file mode 100644 index 00000000000..80fd1a18215 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ContentExchangeTest.java @@ -0,0 +1,287 @@ +// ======================================================================== +// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.client; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLDecoder; + +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.client.security.Realm; +import org.eclipse.jetty.client.security.SimpleRealmResolver; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.IO; + +public class ContentExchangeTest + extends TestCase +{ + private static String _content0 = + "Hello World"; + + private static String _content = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "+ + "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "+ + "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. "+ + "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam "+ + "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate "+ + "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. "+ + "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum "+ + "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa "+ + "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam "+ + "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. "+ + "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "+ + "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque."; + + private File _docRoot; + private Server _server; + private HttpClient _client; + private Realm _realm; + private String _protocol; + private String _requestUrl; + + public void setUp() + throws Exception + { + _docRoot = new File("target/test-output/docroot/"); + _docRoot.mkdirs(); + _docRoot.deleteOnExit(); + + File content = new File(_docRoot,"content.txt"); + FileOutputStream out = new FileOutputStream(content); + out.write(_content0.getBytes("utf-8")); + out.close(); + + _server = new Server(); + configureServer(_server); + _server.start(); + + int port = _server.getConnectors()[0].getLocalPort(); + _requestUrl = _protocol+"://localhost:"+port+ "/content.txt"; + } + + public void tearDown() + throws Exception + { + if (_server != null) + { + _server.stop(); + _server = null; + } + } + + public void testPut() throws Exception + { + startClient(_realm); + + ContentExchange putExchange = new ContentExchange(); + putExchange.setURL(_requestUrl); + putExchange.setMethod(HttpMethods.PUT); + putExchange.setRequestContent(new ByteArrayBuffer(_content.getBytes())); + + _client.send(putExchange); + int state = putExchange.waitForDone(); + + int responseStatus = putExchange.getResponseStatus(); + + stopClient(); + + boolean statusOk = (responseStatus == 200 || responseStatus == 201); + assertTrue(statusOk); + + String content = IO.toString(new FileInputStream(new File(_docRoot,"content.txt"))); + assertEquals(_content,content); + } + + public void testGet() throws Exception + { + startClient(_realm); + + ContentExchange getExchange = new ContentExchange(); + getExchange.setURL(_requestUrl); + getExchange.setMethod(HttpMethods.GET); + + _client.send(getExchange); + int state = getExchange.waitForDone(); + + String content = ""; + int responseStatus = getExchange.getResponseStatus(); + if (responseStatus == HttpStatus.OK_200) + { + content = getExchange.getResponseContent(); + } + + stopClient(); + + assertEquals(HttpStatus.OK_200,responseStatus); + assertEquals(_content0,content); + } + + public void testHead() throws Exception + { + startClient(_realm); + + ContentExchange getExchange = new ContentExchange(); + getExchange.setURL(_requestUrl); + getExchange.setMethod(HttpMethods.HEAD); + + _client.send(getExchange); + int state = getExchange.waitForDone(); + + int responseStatus = getExchange.getResponseStatus(); + + stopClient(); + + assertEquals(HttpStatus.OK_200,responseStatus); + } + + protected void configureServer(Server server) + throws Exception + { + setProtocol("http"); + + SelectChannelConnector connector = new SelectChannelConnector(); + server.addConnector(connector); + + Handler handler = new PutHandler(getBasePath()); + + ServletContextHandler root = new ServletContextHandler(); + root.setContextPath("/"); + root.setResourceBase(_docRoot.getAbsolutePath()); + ServletHolder servletHolder = new ServletHolder( new DefaultServlet() ); + servletHolder.setInitParameter( "gzip", "true" ); + root.addServlet( servletHolder, "/*" ); + + HandlerCollection handlers = new HandlerCollection(); + handlers.setHandlers(new Handler[]{handler, root}); + server.setHandler( handlers ); + + } + + protected void startClient(Realm realm) + throws Exception + { + _client = new HttpClient(); + _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + if (realm != null) + _client.setRealmResolver(new SimpleRealmResolver(realm)); + _client.start(); + } + + protected void stopClient() + throws Exception + { + if (_client != null) + { + _client.stop(); + _client = null; + } + } + + protected String getBasePath() + { + return _docRoot.getAbsolutePath(); + } + + protected void setProtocol(String protocol) + { + _protocol = protocol; + } + + protected void setRealm(Realm realm) + { + _realm = realm; + } + + public static void copyStream(InputStream in, OutputStream out) + { + try + { + byte[] buffer=new byte[1024]; + int len; + while ((len=in.read(buffer))>=0) + { + out.write(buffer,0,len); + } + } + catch (EofException e) + { + System.err.println(e); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + protected static class PutHandler extends AbstractHandler { + private final String resourcePath; + + public PutHandler(String repositoryPath) { + this.resourcePath = repositoryPath; + } + + public void handle(String target, Request baseRequest, + HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + if (baseRequest.isHandled() || !baseRequest.getMethod().equals("PUT")) { + return; + } + + baseRequest.setHandled(true); + + File file = new File(resourcePath, URLDecoder.decode(request.getPathInfo())); + file.getParentFile().mkdirs(); + file.deleteOnExit(); + + FileOutputStream out = new FileOutputStream(file); + ServletInputStream in = request.getInputStream(); + try + { + copyStream( in, out ); + } + finally + { + in.close(); + out.close(); + } + + response.setStatus(HttpServletResponse.SC_CREATED); + } + } +} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index 7c8fbf3229f..3d660700f5f 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -30,6 +30,14 @@ import org.eclipse.jetty.util.log.Log; /** * */ + +/* ------------------------------------------------------------ */ +/** + */ + +/* ------------------------------------------------------------ */ +/** + */ public class HttpParser implements Parser { // States @@ -76,6 +84,7 @@ public class HttpParser implements Parser protected long _contentPosition; protected int _chunkLength; protected int _chunkPosition; + private boolean _headResponse; /* ------------------------------------------------------------------------------- */ /** @@ -116,12 +125,22 @@ public class HttpParser implements Parser { return _contentLength; } - + + /* ------------------------------------------------------------ */ public long getContentRead() { return _contentPosition; } + /* ------------------------------------------------------------ */ + /** Set if a HEAD response is expected + * @param head + */ + public void setHeadResponse(boolean head) + { + _headResponse=head; + } + /* ------------------------------------------------------------------------------- */ public int getState() { @@ -702,6 +721,15 @@ public class HttpParser implements Parser // ========================== + // Handle HEAD response + if (_responseStatus>0 && _headResponse) + { + _state=STATE_END; + _handler.messageComplete(_contentLength); + } + + // ========================== + // Handle _content length=_buffer.length(); Buffer chunk; @@ -924,6 +952,7 @@ public class HttpParser implements Parser } } + /* ------------------------------------------------------------------------------- */ public void reset(boolean returnBuffers) {