From e3e5b8ad72dab169c4353988d3ffb9f2c7a44c89 Mon Sep 17 00:00:00 2001 From: Chris Hegarty Date: Wed, 5 May 2010 13:18:31 +0100 Subject: [PATCH] 6886723: light weight http server doesn't return correct status code for HEAD requests Reviewed-by: michaelm --- .../sun/net/httpserver/ExchangeImpl.java | 54 +++++--- .../com/sun/net/httpserver/bugs/HeadTest.java | 117 ++++++++++++++++++ 2 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 jdk/test/com/sun/net/httpserver/bugs/HeadTest.java diff --git a/jdk/src/share/classes/sun/net/httpserver/ExchangeImpl.java b/jdk/src/share/classes/sun/net/httpserver/ExchangeImpl.java index ccb9151b69c..69ed824f67f 100644 --- a/jdk/src/share/classes/sun/net/httpserver/ExchangeImpl.java +++ b/jdk/src/share/classes/sun/net/httpserver/ExchangeImpl.java @@ -26,16 +26,12 @@ package sun.net.httpserver; import java.io.*; -import java.nio.*; -import java.nio.channels.*; import java.net.*; import javax.net.ssl.*; import java.util.*; import java.util.logging.Logger; import java.text.*; -import sun.net.www.MessageHeader; import com.sun.net.httpserver.*; -import com.sun.net.httpserver.spi.*; class ExchangeImpl { @@ -65,6 +61,8 @@ class ExchangeImpl { df.setTimeZone (tz); } + private static final String HEAD = "HEAD"; + /* streams which take care of the HTTP protocol framing * and are passed up to higher layers */ @@ -116,6 +114,10 @@ class ExchangeImpl { return connection.getHttpContext(); } + private boolean isHeadRequest() { + return HEAD.equals(getRequestMethod()); + } + public void close () { if (closed) { return; @@ -220,24 +222,36 @@ class ExchangeImpl { } contentLen = -1; } - if (contentLen == 0) { - if (http10) { - o.setWrappedStream (new UndefLengthOutputStream (this, ros)); - close = true; + + if (isHeadRequest()) { + /* HEAD requests should not set a content length by passing it + * through this API, but should instead manually set the required + * headers.*/ + if (contentLen >= 0) { + final Logger logger = server.getLogger(); + String msg = + "sendResponseHeaders: being invoked with a content length for a HEAD request"; + logger.warning (msg); + } + noContentToSend = true; + contentLen = 0; + } else { /* not a HEAD request */ + if (contentLen == 0) { + if (http10) { + o.setWrappedStream (new UndefLengthOutputStream (this, ros)); + close = true; + } else { + rspHdrs.set ("Transfer-encoding", "chunked"); + o.setWrappedStream (new ChunkedOutputStream (this, ros)); + } } else { - rspHdrs.set ("Transfer-encoding", "chunked"); - o.setWrappedStream (new ChunkedOutputStream (this, ros)); + if (contentLen == -1) { + noContentToSend = true; + contentLen = 0; + } + rspHdrs.set("Content-length", Long.toString(contentLen)); + o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen)); } - } else { - if (contentLen == -1) { - noContentToSend = true; - contentLen = 0; - } - /* content len might already be set, eg to implement HEAD resp */ - if (rspHdrs.getFirst ("Content-length") == null) { - rspHdrs.set ("Content-length", Long.toString(contentLen)); - } - o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen)); } write (rspHdrs, tmpout); this.rspContentLen = contentLen; diff --git a/jdk/test/com/sun/net/httpserver/bugs/HeadTest.java b/jdk/test/com/sun/net/httpserver/bugs/HeadTest.java new file mode 100644 index 00000000000..8b7055338a1 --- /dev/null +++ b/jdk/test/com/sun/net/httpserver/bugs/HeadTest.java @@ -0,0 +1,117 @@ +/* + * Copyright 2010 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/** + * @test + * @bug 6886723 + * @summary light weight http server doesn't return correct status code for HEAD requests + */ + +import java.net.InetSocketAddress; +import java.net.HttpURLConnection; +import java.net.URL; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +public class HeadTest { + + public static void main(String[] args) throws Exception { + server(); + } + + static void server() throws Exception { + InetSocketAddress inetAddress = new InetSocketAddress(0); + HttpServer server = HttpServer.create(inetAddress, 5); + try { + server.setExecutor(Executors.newFixedThreadPool(5)); + HttpContext chunkedContext = server.createContext("/chunked"); + chunkedContext.setHandler(new HttpHandler() { + @Override + public void handle(HttpExchange msg) { + try { + try { + if (msg.getRequestMethod().equals("HEAD")) { + msg.getRequestBody().close(); + msg.getResponseHeaders().add("Transfer-encoding", "chunked"); + msg.sendResponseHeaders(200, -1); + } + } catch(IOException ioe) { + ioe.printStackTrace(); + } + } finally { + msg.close(); + } + } + }); + HttpContext clContext = server.createContext("/content"); + clContext.setHandler(new HttpHandler() { + @Override + public void handle(HttpExchange msg) { + try { + try { + if (msg.getRequestMethod().equals("HEAD")) { + msg.getRequestBody().close(); + msg.getResponseHeaders().add("Content-length", "1024"); + msg.sendResponseHeaders(200, -1); + } + } catch(IOException ioe) { + ioe.printStackTrace(); + } + } finally { + msg.close(); + } + } + }); + server.start(); + String urlStr = "http://localhost:" + server.getAddress().getPort() + "/"; + System.out.println("Server is at " + urlStr); + + // Run the chunked client + for(int i=0; i < 10; i++) { + runClient(urlStr + "chunked/"); + } + // Run the content length client + for(int i=0; i < 10; i++) { + runClient(urlStr + "content/"); + } + } finally { + // Stop the server + ((ExecutorService)server.getExecutor()).shutdown(); + server.stop(0); + } + } + + static void runClient(String urlStr) throws Exception { + HttpURLConnection conn = (HttpURLConnection) new URL(urlStr).openConnection(); + conn.setRequestMethod("HEAD"); + int status = conn.getResponseCode(); + if (status != 200) { + throw new RuntimeException("HEAD request doesn't return 200, but returns " + status); + } + } +}