Issue #7277 - Allow `Request.getLocalName()` and `.getLocalPort()` to be overridden (#7357)

* Issue #7277 - Allow `Request.getLocalName()` and `.getLocalPort()` to be overridden (#7316)

* Introduce `HttpConfiguration.setServerAuthority(HostPort)`
  to influence `ServletRequest.getServerName()` and `ServletRequest.getServerPort()`
* Introduce `HttpConfiguration.setLocalAddress(SocketAddress)`
  to influence `ServletRequest.getLocalName()`, `ServletRequest.getLocalPort()`, and `ServletRequest.getLocalAddr()`
* Correcting Request URI logic on abs-uri without authority
* Adding test cases

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
Joakim Erdfelt 2022-01-06 08:01:08 -06:00 committed by GitHub
parent 3042f2b2bf
commit 1984d2de11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 996 additions and 60 deletions

View File

@ -156,6 +156,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
private long _shutdownIdleTimeout = 1000L;
private String _defaultProtocol;
private ConnectionFactory _defaultConnectionFactory;
/* The name used to link up virtual host configuration to named connectors */
private String _name;
private int _acceptorPriorityDelta = -2;
private boolean _accepting = true;
@ -348,6 +349,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
}
_lease = ThreadPoolBudget.leaseFrom(getExecutor(), this, _acceptors.length);
super.doStart();
for (int i = 0; i < _acceptors.length; i++)

View File

@ -20,6 +20,7 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
@ -97,7 +98,7 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor
public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport)
{
_connector = connector;
_configuration = configuration;
_configuration = Objects.requireNonNull(configuration);
_endPoint = endPoint;
_transport = transport;
@ -325,8 +326,81 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor
return _endPoint;
}
/**
* <p>Return the local name of the connected channel.</p>
*
* <p>
* This is the host name after the connector is bound and the connection is accepted.
* </p>
* <p>
* Value can be overridden by {@link HttpConfiguration#setLocalAddress(SocketAddress)}.
* </p>
* <p>
* Note: some connectors are not based on IP networking, and default behavior here will
* result in a null return. Use {@link HttpConfiguration#setLocalAddress(SocketAddress)}
* to set the value to an acceptable host name.
* </p>
*
* @return the local name, or null
*/
public String getLocalName()
{
HttpConfiguration httpConfiguration = getHttpConfiguration();
if (httpConfiguration != null)
{
SocketAddress localAddress = httpConfiguration.getLocalAddress();
if (localAddress instanceof InetSocketAddress)
return ((InetSocketAddress)localAddress).getHostName();
}
InetSocketAddress local = getLocalAddress();
if (local != null)
return local.getHostString();
return null;
}
/**
* <p>Return the Local Port of the connected channel.</p>
*
* <p>
* This is the port the connector is bound to and is accepting connections on.
* </p>
* <p>
* Value can be overridden by {@link HttpConfiguration#setLocalAddress(SocketAddress)}.
* </p>
* <p>
* Note: some connectors are not based on IP networking, and default behavior here will
* result in a value of 0 returned. Use {@link HttpConfiguration#setLocalAddress(SocketAddress)}
* to set the value to an acceptable port.
* </p>
*
* @return the local port, or 0 if unspecified
*/
public int getLocalPort()
{
HttpConfiguration httpConfiguration = getHttpConfiguration();
if (httpConfiguration != null)
{
SocketAddress localAddress = httpConfiguration.getLocalAddress();
if (localAddress instanceof InetSocketAddress)
return ((InetSocketAddress)localAddress).getPort();
}
InetSocketAddress local = getLocalAddress();
return local == null ? 0 : local.getPort();
}
public InetSocketAddress getLocalAddress()
{
HttpConfiguration httpConfiguration = getHttpConfiguration();
if (httpConfiguration != null)
{
SocketAddress localAddress = httpConfiguration.getLocalAddress();
if (localAddress instanceof InetSocketAddress)
return ((InetSocketAddress)localAddress);
}
SocketAddress local = _endPoint.getLocalSocketAddress();
if (local instanceof InetSocketAddress)
return (InetSocketAddress)local;
@ -341,6 +415,18 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor
return null;
}
/**
* @return return the HttpConfiguration server authority override
*/
public HostPort getServerAuthority()
{
HttpConfiguration httpConfiguration = getHttpConfiguration();
if (httpConfiguration != null)
return httpConfiguration.getServerAuthority();
return null;
}
/**
* If the associated response has the Expect header set to 100 Continue,
* then accessing the input stream indicates that the handler/servlet
@ -551,7 +637,7 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor
// If send error is called we need to break.
if (checkAndPrepareUpgrade())
break;
// Set a close callback on the HttpOutput to make it an async callback
_response.completeOutput(Callback.from(NON_BLOCKING, () -> _state.completed(null), _state::completed));

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.server;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
@ -23,6 +24,7 @@ import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -75,6 +77,8 @@ public class HttpConfiguration implements Dumpable
private CookieCompliance _responseCookieCompliance = CookieCompliance.RFC6265;
private boolean _notifyRemoteAsyncErrors = true;
private boolean _relativeRedirectAllowed;
private HostPort _serverAuthority;
private SocketAddress _localAddress;
/**
* <p>An interface that allows a request object to be customized
@ -145,6 +149,8 @@ public class HttpConfiguration implements Dumpable
_notifyRemoteAsyncErrors = config._notifyRemoteAsyncErrors;
_relativeRedirectAllowed = config._relativeRedirectAllowed;
_uriCompliance = config._uriCompliance;
_serverAuthority = config._serverAuthority;
_localAddress = config._localAddress;
}
/**
@ -651,6 +657,69 @@ public class HttpConfiguration implements Dumpable
return _relativeRedirectAllowed;
}
/**
* Get the SocketAddress override to be reported as the local address of all connections
*
* @return Returns the connection local address override or null.
*/
@ManagedAttribute("Local SocketAddress override")
public SocketAddress getLocalAddress()
{
return _localAddress;
}
/**
* <p>
* Specify the connection local address used within application API layer
* when identifying the local host name/port of a connected endpoint.
* </p>
* <p>
* This allows an override of higher level APIs, such as
* {@code ServletRequest.getLocalName()}, {@code ServletRequest.getLocalAddr()},
* and {@code ServletRequest.getLocalPort()}.
* </p>
*
* @param localAddress the address to use for host/addr/port, or null to reset to default behavior
*/
public void setLocalAddress(SocketAddress localAddress)
{
_localAddress = localAddress;
}
/**
* Get the Server authority override to be used if no authority is provided by a request.
*
* @return Returns the connection server authority (name/port) or null
*/
@ManagedAttribute("The server authority if none provided by requests")
public HostPort getServerAuthority()
{
return _serverAuthority;
}
/**
* <p>
* Specify the connection server authority (name/port) used within application API layer
* when identifying the server host name/port of a connected endpoint.
* </p>
*
* <p>
* This allows an override of higher level APIs, such as
* {@code ServletRequest.getServerName()}, and {@code ServletRequest.getServerPort()}.
* </p>
*
* @param authority the authority host (and optional port), or null to reset to default behavior
*/
public void setServerAuthority(HostPort authority)
{
if (authority == null)
_serverAuthority = null;
else if (!authority.hasHost())
throw new IllegalStateException("Server Authority must have host declared");
else
_serverAuthority = authority;
}
@Override
public String dump()
{

View File

@ -22,7 +22,6 @@ import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
@ -420,6 +419,7 @@ public class Request implements HttpServletRequest
* A response is being committed for a session,
* potentially write the session out before the
* client receives the response.
*
* @param session the session
*/
private void commitSession(Session session)
@ -978,63 +978,47 @@ public class Request implements HttpServletRequest
@Override
public String getLocalAddr()
{
if (_channel == null)
if (_channel != null)
{
try
{
String name = InetAddress.getLocalHost().getHostAddress();
if (StringUtil.ALL_INTERFACES.equals(name))
return null;
return formatAddrOrHost(name);
}
catch (UnknownHostException e)
{
LOG.trace("IGNORED", e);
return null;
}
InetSocketAddress local = _channel.getLocalAddress();
if (local == null)
return "";
InetAddress address = local.getAddress();
String result = address == null
? local.getHostString()
: address.getHostAddress();
return formatAddrOrHost(result);
}
InetSocketAddress local = _channel.getLocalAddress();
if (local == null)
return "";
InetAddress address = local.getAddress();
String result = address == null
? local.getHostString()
: address.getHostAddress();
return formatAddrOrHost(result);
return "";
}
/*
* @see javax.servlet.ServletRequest#getLocalName()
*/
@Override
public String getLocalName()
{
if (_channel != null)
{
InetSocketAddress local = _channel.getLocalAddress();
if (local != null)
return formatAddrOrHost(local.getHostString());
String localName = _channel.getLocalName();
return formatAddrOrHost(localName);
}
try
{
String name = InetAddress.getLocalHost().getHostName();
if (StringUtil.ALL_INTERFACES.equals(name))
return null;
return formatAddrOrHost(name);
}
catch (UnknownHostException e)
{
LOG.trace("IGNORED", e);
}
return null;
return ""; // not allowed to be null
}
@Override
public int getLocalPort()
{
if (_channel == null)
return 0;
InetSocketAddress local = _channel.getLocalAddress();
return local == null ? 0 : local.getPort();
if (_channel != null)
{
int localPort = _channel.getLocalPort();
if (localPort > 0)
return localPort;
}
return 0;
}
@Override
@ -1321,32 +1305,38 @@ public class Request implements HttpServletRequest
@Override
public String getServerName()
{
return _uri == null ? findServerName() : formatAddrOrHost(_uri.getHost());
if ((_uri != null) && StringUtil.isNotBlank(_uri.getAuthority()))
return formatAddrOrHost(_uri.getHost());
else
return findServerName();
}
private String findServerName()
{
if (_channel != null)
{
HostPort serverAuthority = _channel.getServerAuthority();
if (serverAuthority != null)
return formatAddrOrHost(serverAuthority.getHost());
}
// Return host from connection
String name = getLocalName();
if (name != null)
return formatAddrOrHost(name);
// Return the local host
try
{
return formatAddrOrHost(InetAddress.getLocalHost().getHostAddress());
}
catch (UnknownHostException e)
{
LOG.trace("IGNORED", e);
}
return null;
return ""; // not allowed to be null
}
@Override
public int getServerPort()
{
int port = _uri == null ? -1 : _uri.getPort();
int port = -1;
if ((_uri != null) && StringUtil.isNotBlank(_uri.getAuthority()))
port = _uri.getPort();
else
port = findServerPort();
// If no port specified, return the default port for the scheme
if (port <= 0)
@ -1358,11 +1348,15 @@ public class Request implements HttpServletRequest
private int findServerPort()
{
// Return host from connection
if (_channel != null)
return getLocalPort();
{
HostPort serverAuthority = _channel.getServerAuthority();
if (serverAuthority != null)
return serverAuthority.getPort();
}
return -1;
// Return host from connection
return getLocalPort();
}
@Override
@ -1532,7 +1526,7 @@ public class Request implements HttpServletRequest
_session = _sessionHandler.newHttpSession(this);
if (_session == null)
throw new IllegalStateException("Create session failed");
HttpCookie cookie = _sessionHandler.getSessionCookie(_session, getContextPath(), isSecure());
if (cookie != null)
_channel.getResponse().replaceCookie(cookie);
@ -2563,7 +2557,7 @@ public class Request implements HttpServletRequest
// which we recover from the IncludeAttributes wrapper.
return findServletPathMapping();
}
private String formatAddrOrHost(String name)
{
return _channel == null ? HostPort.normalizeHost(name) : _channel.formatAddrOrHost(name);

View File

@ -237,6 +237,9 @@ public class DetectorConnectionTest
assertThat(response, Matchers.containsString("HTTP/1.1 200"));
assertThat(response, Matchers.containsString("pathInfo=/path"));
assertThat(response, Matchers.containsString("servername=server"));
assertThat(response, Matchers.containsString("serverport=80"));
assertThat(response, Matchers.containsString("localname=5.6.7.8"));
assertThat(response, Matchers.containsString("local=5.6.7.8:222"));
assertThat(response, Matchers.containsString("remote=1.2.3.4:111"));
}

View File

@ -105,6 +105,8 @@ public class DumpHandler extends AbstractHandler
writer.write("<pre>\ncontentType=" + request.getContentType() + "\n</pre>\n");
writer.write("<pre>\nencoding=" + request.getCharacterEncoding() + "\n</pre>\n");
writer.write("<pre>\nservername=" + request.getServerName() + "\n</pre>\n");
writer.write("<pre>\nserverport=" + request.getServerPort() + "\n</pre>\n");
writer.write("<pre>\nlocalname=" + request.getLocalName() + "\n</pre>\n");
writer.write("<pre>\nlocal=" + request.getLocalAddr() + ":" + request.getLocalPort() + "\n</pre>\n");
writer.write("<pre>\nremote=" + request.getRemoteAddr() + ":" + request.getRemotePort() + "\n</pre>\n");
writer.write("<h3>Header:</h3><pre>");

View File

@ -0,0 +1,766 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class HttpConfigurationAuthorityOverrideTest
{
@Test
public void testLocalAuthorityHttp10NoHostDump() throws Exception
{
InetSocketAddress localAddress = InetSocketAddress.createUnresolved("foo.local.name", 80);
try (CloseableServer server = startServer(null, localAddress))
{
String rawRequest = "GET /dump HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[foo.local.name]"),
containsString("ServerPort=[80]"),
containsString("LocalAddr=[foo.local.name]"),
containsString("LocalName=[foo.local.name]"),
containsString("LocalPort=[80]"),
containsString("RequestURL=[http://foo.local.name/dump]")
));
}
}
@Test
public void testLocalAuthorityHttp10NoHostRedirect() throws Exception
{
InetSocketAddress localAddress = InetSocketAddress.createUnresolved("foo.local.name", 80);
try (CloseableServer server = startServer(null, localAddress))
{
String rawRequest = "GET /redirect HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://foo.local.name/dump"));
}
}
@Test
public void testLocalAuthorityHttp10NotFound() throws Exception
{
InetSocketAddress localAddress = InetSocketAddress.createUnresolved("foo.local.name", 777);
try (CloseableServer server = startServer(null, localAddress))
{
String rawRequest = "GET /bogus HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
// because of the custom error handler, we actually expect a redirect
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://foo.local.name:777/error"));
}
}
@Test
public void testLocalAuthorityHttp11EmptyHostDump() throws Exception
{
InetSocketAddress localAddress = InetSocketAddress.createUnresolved("foo.local.name", 80);
try (CloseableServer server = startServer(null, localAddress))
{
String rawRequest = "GET /dump HTTP/1.1\r\n" +
"Host: \r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[foo.local.name]"),
containsString("ServerPort=[80]"),
containsString("LocalAddr=[foo.local.name]"),
containsString("LocalName=[foo.local.name]"),
containsString("LocalPort=[80]"),
containsString("RequestURL=[http://foo.local.name/dump]")
));
}
}
@Test
public void testLocalAuthorityHttp11EmptyHostRedirect() throws Exception
{
InetSocketAddress localAddress = InetSocketAddress.createUnresolved("foo.local.name", 80);
try (CloseableServer server = startServer(null, localAddress))
{
String rawRequest = "GET /redirect HTTP/1.1\r\n" +
"Host: \r\n" +
"Connect: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://foo.local.name/dump"));
}
}
@Test
public void testLocalAuthorityHttp11EmptyHostAbsUriDump() throws Exception
{
InetSocketAddress localAddress = InetSocketAddress.createUnresolved("bar.local.name", 9999);
try (CloseableServer server = startServer(null, localAddress))
{
String rawRequest = "GET mobile:///dump HTTP/1.1\r\n" +
"Host: \r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[bar.local.name]"),
containsString("ServerPort=[9999]"),
containsString("LocalAddr=[bar.local.name]"),
containsString("LocalName=[bar.local.name]"),
containsString("LocalPort=[9999]"),
containsString("RequestURL=[mobile://bar.local.name:9999/dump]")
));
}
}
@Test
public void testLocalAuthorityHttp11ValidHostDump() throws Exception
{
InetSocketAddress localAddress = InetSocketAddress.createUnresolved("zed.local.name", 9999);
try (CloseableServer server = startServer(null, localAddress))
{
String rawRequest = "GET /dump HTTP/1.1\r\n" +
"Host: jetty.eclipse.org:8888\r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[jetty.eclipse.org]"),
containsString("ServerPort=[8888]"),
containsString("LocalAddr=[zed.local.name]"),
containsString("LocalName=[zed.local.name]"),
containsString("LocalPort=[9999]"),
containsString("RequestURL=[http://jetty.eclipse.org:8888/dump]")
));
}
}
@Test
public void testLocalAuthorityHttp11ValidHostRedirect() throws Exception
{
InetSocketAddress localAddress = InetSocketAddress.createUnresolved("zed.local.name", 9999);
try (CloseableServer server = startServer(null, localAddress))
{
String rawRequest = "GET /redirect HTTP/1.1\r\n" +
"Host: jetty.eclipse.org:8888\r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://jetty.eclipse.org:8888/dump"));
}
}
@Test
public void testServerAuthorityNoPortHttp11EmptyHostDump() throws Exception
{
HostPort serverUriAuthority = new HostPort("foo.server.authority");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /dump HTTP/1.1\r\n" +
"Host: \r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[foo.server.authority]"),
containsString("ServerPort=[80]"),
// expect default locals
containsString("LocalAddr=[" + server.getConnectorLocalAddr() + "]"),
containsString("LocalName=[" + server.getConnectorLocalName() + "]"),
containsString("LocalPort=[" + server.getConnectorLocalPort() + "]"),
containsString("RequestURL=[http://foo.server.authority/dump]")
));
}
}
@Test
public void testServerAuthorityNoPortHttp11EmptyHostRedirect() throws Exception
{
HostPort serverUriAuthority = new HostPort("foo.server.authority");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /redirect HTTP/1.1\r\n" +
"Host: \r\n" +
"Connect: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://foo.server.authority/dump"));
}
}
@Test
public void testServerAuthorityWithPortHttp11EmptyHostDump() throws Exception
{
HostPort serverUriAuthority = new HostPort("foo.server.authority:7777");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /dump HTTP/1.1\r\n" +
"Host: \r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[foo.server.authority]"),
containsString("ServerPort=[7777]"),
// expect default locals
containsString("LocalAddr=[" + server.getConnectorLocalAddr() + "]"),
containsString("LocalName=[" + server.getConnectorLocalName() + "]"),
containsString("LocalPort=[" + server.getConnectorLocalPort() + "]"),
containsString("RequestURL=[http://foo.server.authority:7777/dump]")
));
}
}
@Test
public void testServerAuthorityWithPortHttp11EmptyHostRedirect() throws Exception
{
HostPort serverUriAuthority = new HostPort("foo.server.authority:7777");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /redirect HTTP/1.1\r\n" +
"Host: \r\n" +
"Connect: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://foo.server.authority:7777/dump"));
}
}
@Test
public void testServerUriAuthorityNoPortHttp10NoHostDump() throws Exception
{
HostPort serverUriAuthority = new HostPort("foo.server.authority");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /dump HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[foo.server.authority]"),
containsString("ServerPort=[80]"),
// expect default locals
containsString("LocalAddr=[" + server.getConnectorLocalAddr() + "]"),
containsString("LocalName=[" + server.getConnectorLocalName() + "]"),
containsString("LocalPort=[" + server.getConnectorLocalPort() + "]"),
containsString("RequestURL=[http://foo.server.authority/dump]")
));
}
}
@Test
public void testServerUriAuthorityNoPortHttp10NoHostRedirect() throws Exception
{
HostPort serverUriAuthority = new HostPort("foo.server.authority");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /redirect HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://foo.server.authority/dump"));
}
}
@Test
public void testServerUriAuthorityNoPortHttp10NotFound() throws Exception
{
HostPort severUriAuthority = new HostPort("foo.server.authority");
try (CloseableServer server = startServer(severUriAuthority, null))
{
String rawRequest = "GET /bogus HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
// because of the custom error handler, we actually expect a redirect
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://foo.server.authority/error"));
}
}
@Test
public void testServerUriAuthorityNoPortHttp10PathError() throws Exception
{
HostPort severUriAuthority = new HostPort("foo.server.authority");
try (CloseableServer server = startServer(severUriAuthority, null))
{
String rawRequest = "GET /%00 HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.BAD_REQUEST_400));
}
}
@Test
public void testServerUriAuthorityNoPortHttp11ValidHostDump() throws Exception
{
HostPort serverUriAuthority = new HostPort("zed.server.authority");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /dump HTTP/1.1\r\n" +
"Host: jetty.eclipse.org:8888\r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[jetty.eclipse.org]"),
containsString("ServerPort=[8888]"),
// expect default locals
containsString("LocalAddr=[" + server.getConnectorLocalAddr() + "]"),
containsString("LocalName=[" + server.getConnectorLocalName() + "]"),
containsString("LocalPort=[" + server.getConnectorLocalPort() + "]"),
containsString("RequestURL=[http://jetty.eclipse.org:8888/dump]")
));
}
}
@Test
public void testServerUriAuthorityNoPortHttp11ValidHostRedirect() throws Exception
{
HostPort serverUriAuthority = new HostPort("zed.local.name");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /redirect HTTP/1.1\r\n" +
"Host: jetty.eclipse.org:8888\r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://jetty.eclipse.org:8888/dump"));
}
}
@Test
public void testServerUriAuthorityWithPortHttp10NoHostDump() throws Exception
{
HostPort serverUriAuthority = new HostPort("bar.server.authority:9999");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /dump HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[bar.server.authority]"),
containsString("ServerPort=[9999]"),
// expect default locals
containsString("LocalAddr=[" + server.getConnectorLocalAddr() + "]"),
containsString("LocalName=[" + server.getConnectorLocalName() + "]"),
containsString("LocalPort=[" + server.getConnectorLocalPort() + "]"),
containsString("RequestURL=[http://bar.server.authority:9999/dump]")
));
}
}
@Test
public void testServerUriAuthorityWithPortHttp10NoHostRedirect() throws Exception
{
HostPort severUriAuthority = new HostPort("foo.server.authority:9999");
try (CloseableServer server = startServer(severUriAuthority, null))
{
String rawRequest = "GET /redirect HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://foo.server.authority:9999/dump"));
}
}
@Test
public void testServerUriAuthorityWithPortHttp10NotFound() throws Exception
{
HostPort severUriAuthority = new HostPort("foo.server.authority:7777");
try (CloseableServer server = startServer(severUriAuthority, null))
{
String rawRequest = "GET /bogus HTTP/1.0\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
// because of the custom error handler, we actually expect a redirect
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://foo.server.authority:7777/error"));
}
}
@Test
public void testServerUriAuthorityWithPortHttp11ValidHostDump() throws Exception
{
HostPort serverUriAuthority = new HostPort("zed.server.authority:7777");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /dump HTTP/1.1\r\n" +
"Host: jetty.eclipse.org:8888\r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[jetty.eclipse.org]"),
containsString("ServerPort=[8888]"),
// expect default locals
containsString("LocalAddr=[" + server.getConnectorLocalAddr() + "]"),
containsString("LocalName=[" + server.getConnectorLocalName() + "]"),
containsString("LocalPort=[" + server.getConnectorLocalPort() + "]"),
containsString("RequestURL=[http://jetty.eclipse.org:8888/dump]")
));
}
}
@Test
public void testServerUriAuthorityWithPortHttp11EmptyHostAbsUriDump() throws Exception
{
HostPort serverUriAuthority = new HostPort("zed.server.authority:7777");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET mobile:///dump HTTP/1.1\r\n" +
"Host: \r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[zed.server.authority]"),
containsString("ServerPort=[7777]"),
// expect default locals
containsString("LocalAddr=[" + server.getConnectorLocalAddr() + "]"),
containsString("LocalName=[" + server.getConnectorLocalName() + "]"),
containsString("LocalPort=[" + server.getConnectorLocalPort() + "]"),
containsString("RequestURL=[mobile://zed.server.authority:7777/dump]")
));
}
}
@Test
public void testServerUriAuthorityWithPortHttp11ValidHostRedirect() throws Exception
{
HostPort serverUriAuthority = new HostPort("zed.local.name:7777");
try (CloseableServer server = startServer(serverUriAuthority, null))
{
String rawRequest = "GET /redirect HTTP/1.1\r\n" +
"Host: jetty.eclipse.org:8888\r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://jetty.eclipse.org:8888/dump"));
}
}
@Test
public void testUnsetAuthoritiesHttp11EmptyHostDump() throws Exception
{
try (CloseableServer server = startServer(null, null))
{
String rawRequest = "GET /dump HTTP/1.1\r\n" +
"Host: \r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat("response.status", response.getStatus(), is(200));
String responseContent = response.getContent();
assertThat("response content", responseContent, allOf(
containsString("ServerName=[" + server.getConnectorLocalName() + "]"),
containsString("ServerPort=[" + server.getConnectorLocalPort() + "]"),
// expect default locals
containsString("LocalAddr=[" + server.getConnectorLocalAddr() + "]"),
containsString("LocalName=[" + server.getConnectorLocalName() + "]"),
containsString("LocalPort=[" + server.getConnectorLocalPort() + "]"),
containsString("RequestURL=[http://" + server.getConnectorLocalName() + ":" + server.getConnectorLocalPort() + "/dump]")
));
}
}
@Test
public void testUnsetAuthoritiesHttp11EmptyHostRedirect() throws Exception
{
try (CloseableServer server = startServer(null, null))
{
String rawRequest = "GET /redirect HTTP/1.1\r\n" +
"Host: \r\n" +
"Connection: close\r\n" +
"\r\n";
HttpTester.Response response = issueRequest(server, rawRequest);
assertThat(response.getStatus(), is(HttpStatus.MOVED_TEMPORARILY_302));
String location = response.get(HttpHeader.LOCATION);
assertThat(location, is("http://" + server.getConnectorLocalName() + ":" + server.getConnectorLocalPort() + "/dump"));
}
}
private HttpTester.Response issueRequest(CloseableServer server, String rawRequest) throws Exception
{
try (Socket socket = new Socket("localhost", server.getConnectorLocalPort());
OutputStream output = socket.getOutputStream();
InputStream input = socket.getInputStream())
{
output.write(rawRequest.getBytes(StandardCharsets.UTF_8));
output.flush();
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(input));
assertNotNull(response, "response");
return response;
}
}
private CloseableServer startServer(HostPort serverUriAuthority, InetSocketAddress localAddress) throws Exception
{
Server server = new Server();
HttpConfiguration httpConfiguration = new HttpConfiguration();
if (serverUriAuthority != null)
httpConfiguration.setServerAuthority(serverUriAuthority);
if (localAddress != null)
httpConfiguration.setLocalAddress(localAddress);
ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
connector.setPort(0);
server.addConnector(connector);
HandlerList handlers = new HandlerList();
handlers.addHandler(new RedirectHandler());
handlers.addHandler(new DumpHandler());
handlers.addHandler(new ErrorMsgHandler());
server.setHandler(handlers);
server.setErrorHandler(new RedirectErrorHandler());
server.start();
return new CloseableServer(server, connector);
}
private static class DumpHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (target.startsWith("/dump"))
{
baseRequest.setHandled(true);
response.setCharacterEncoding("utf-8");
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.printf("ServerName=[%s]%n", request.getServerName());
out.printf("ServerPort=[%d]%n", request.getServerPort());
out.printf("LocalName=[%s]%n", request.getLocalName());
out.printf("LocalAddr=[%s]%n", request.getLocalAddr());
out.printf("LocalPort=[%s]%n", request.getLocalPort());
out.printf("RequestURL=[%s]%n", request.getRequestURL());
}
}
}
private static class RedirectHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (target.startsWith("/redirect"))
{
baseRequest.setHandled(true);
response.sendRedirect("/dump");
}
}
}
private static class ErrorMsgHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (target.startsWith("/error"))
{
baseRequest.setHandled(true);
response.setCharacterEncoding("utf-8");
response.setContentType("text/plain");
response.getWriter().println("Generic Error Page.");
}
}
}
public static class RedirectErrorHandler extends ErrorHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.sendRedirect("/error");
}
}
private static class CloseableServer implements AutoCloseable
{
private final Server server;
private final ServerConnector connector;
public CloseableServer(Server server, ServerConnector connector)
{
this.server = Objects.requireNonNull(server, "Server");
this.connector = Objects.requireNonNull(connector, "Connector");
}
@Override
public void close() throws Exception
{
LifeCycle.stop(this.server);
}
public String getConnectorLocalAddr()
{
return "127.0.0.1";
}
public String getConnectorLocalName()
{
return HostPort.normalizeHost(getConnectorLocalAddr());
}
public int getConnectorLocalPort()
{
return this.connector.getLocalPort();
}
}
}

View File

@ -13,6 +13,8 @@
package org.eclipse.jetty.util;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
/**
* <p>Parse an authority string (in the form {@code host:port}) into
* {@code host} and {@code port}, handling IPv4 and IPv6 host formats
@ -99,6 +101,7 @@ public class HostPort
*
* @return the host
*/
@ManagedAttribute("host")
public String getHost()
{
return _host;
@ -109,6 +112,7 @@ public class HostPort
*
* @return the port
*/
@ManagedAttribute("port")
public int getPort()
{
return _port;
@ -125,6 +129,16 @@ public class HostPort
return _port > 0 ? _port : defaultPort;
}
public boolean hasHost()
{
return StringUtil.isNotBlank(_host);
}
public boolean hasPort()
{
return _port > 0;
}
@Override
public String toString()
{