Fixes #477 - HTTP/2 client transport must not send absolute-form :path pseudo-header.

Reducing the target to a path if it is in absolute-form.
This commit is contained in:
Simone Bordet 2016-04-01 19:16:52 +02:00
parent aac9f70243
commit 4f68d44a50
3 changed files with 153 additions and 51 deletions

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.http2.client.http;
import java.net.URI;
import org.eclipse.jetty.client.HttpContent;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpSender;
@ -47,8 +49,9 @@ public class HttpSenderOverHTTP2 extends HttpSender
@Override
protected void sendHeaders(HttpExchange exchange, final HttpContent content, final Callback callback)
{
final Request request = exchange.getRequest();
HttpURI uri = new HttpURI(request.getScheme(), request.getHost(), request.getPort(), request.getPath(), null, request.getQuery(), null);
Request request = exchange.getRequest();
String path = relativize(request.getPath());
HttpURI uri = new HttpURI(request.getScheme(), request.getHost(), request.getPort(), path, null, request.getQuery(), null);
MetaData.Request metaData = new MetaData.Request(request.getMethod(), uri, HttpVersion.HTTP_2, request.getHeaders());
HeadersFrame headersFrame = new HeadersFrame(metaData, null, !content.hasContent());
HttpChannelOverHTTP2 channel = getHttpChannel();
@ -84,6 +87,24 @@ public class HttpSenderOverHTTP2 extends HttpSender
channel.getSession().newStream(headersFrame, promise, channel.getStreamListener());
}
private String relativize(String path)
{
try
{
String result = path;
URI uri = URI.create(result);
if (uri.isAbsolute())
result = uri.getPath();
return result.isEmpty() ? "/" : result;
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not relativize " + path);
return path;
}
}
@Override
protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
{

View File

@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
@ -260,6 +261,58 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
Assert.assertEquals(lastStream.get(), stream.getId());
}
@Test
public void testAbsoluteFormTarget() throws Exception
{
String path = "/path";
String query = "a=b";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(path, request.getRequestURI());
Assert.assertEquals(query, request.getQueryString());
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.path("http://localhost:" + connector.getLocalPort() + path + "?" + query)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testRequestViaForwardHttpProxy() throws Exception
{
String path = "/path";
String query = "a=b";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(path, request.getRequestURI());
Assert.assertEquals(query, request.getQueryString());
}
});
int proxyPort = connector.getLocalPort();
client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
int serverPort = proxyPort + 1; // Any port will do, just not the same as the proxy.
ContentResponse response = client.newRequest("localhost", serverPort)
.path(path + "?" + query)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Ignore
@Test
public void testExternalServer() throws Exception

View File

@ -21,11 +21,13 @@ package org.eclipse.jetty.http2.server;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http2.IStream;
@ -70,67 +72,93 @@ public class HttpChannelOverHTTP2 extends HttpChannel
public Runnable onRequest(HeadersFrame frame)
{
MetaData.Request request = (MetaData.Request)frame.getMetaData();
HttpFields fields = request.getFields();
// HTTP/2 sends the Host header as the :authority
// pseudo-header, so we need to synthesize a Host header.
if (!fields.contains(HttpHeader.HOST))
try
{
String authority = request.getURI().getAuthority();
if (authority != null)
MetaData.Request request = (MetaData.Request)frame.getMetaData();
HttpFields fields = request.getFields();
// HTTP/2 sends the Host header as the :authority
// pseudo-header, so we need to synthesize a Host header.
if (!fields.contains(HttpHeader.HOST))
{
// Lower-case to be consistent with other HTTP/2 headers.
fields.put("host", authority);
String authority = request.getURI().getAuthority();
if (authority != null)
{
// Lower-case to be consistent with other HTTP/2 headers.
fields.put("host", authority);
}
}
_expect100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
HttpFields response = getResponse().getHttpFields();
if (getHttpConfiguration().getSendServerVersion())
response.add(SERVER_VERSION);
if (getHttpConfiguration().getSendXPoweredBy())
response.add(POWERED_BY);
onRequest(request);
boolean endStream = frame.isEndStream();
if (endStream)
onRequestComplete();
_delayedUntilContent = getHttpConfiguration().isDelayDispatchUntilContent() &&
!endStream && !_expect100Continue;
if (LOG.isDebugEnabled())
{
Stream stream = getStream();
LOG.debug("HTTP2 Request #{}/{}, delayed={}:{}{} {} {}{}{}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
_delayedUntilContent, System.lineSeparator(),
request.getMethod(), request.getURI(), request.getVersion(),
System.lineSeparator(), fields);
}
return _delayedUntilContent ? null : this;
}
_expect100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
HttpFields response = getResponse().getHttpFields();
if (getHttpConfiguration().getSendServerVersion())
response.add(SERVER_VERSION);
if (getHttpConfiguration().getSendXPoweredBy())
response.add(POWERED_BY);
onRequest(request);
boolean endStream = frame.isEndStream();
if (endStream)
onRequestComplete();
_delayedUntilContent = getHttpConfiguration().isDelayDispatchUntilContent() &&
!endStream && !_expect100Continue;
if (LOG.isDebugEnabled())
catch (BadMessageException x)
{
Stream stream = getStream();
LOG.debug("HTTP2 Request #{}/{}, delayed={}:{}{} {} {}{}{}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
_delayedUntilContent, System.lineSeparator(),
request.getMethod(), request.getURI(), request.getVersion(),
System.lineSeparator(), fields);
onBadMessage(x.getCode(), x.getReason());
return null;
}
catch (Throwable x)
{
onBadMessage(HttpStatus.INTERNAL_SERVER_ERROR_500, null);
return null;
}
return _delayedUntilContent ? null : this;
}
public Runnable onPushRequest(MetaData.Request request)
{
onRequest(request);
getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE);
onRequestComplete();
if (LOG.isDebugEnabled())
try
{
Stream stream = getStream();
LOG.debug("HTTP2 PUSH Request #{}/{}:{}{} {} {}{}{}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(),
request.getMethod(), request.getURI(), request.getVersion(),
System.lineSeparator(), request.getFields());
}
onRequest(request);
getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE);
onRequestComplete();
return this;
if (LOG.isDebugEnabled())
{
Stream stream = getStream();
LOG.debug("HTTP2 PUSH Request #{}/{}:{}{} {} {}{}{}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(),
request.getMethod(), request.getURI(), request.getVersion(),
System.lineSeparator(), request.getFields());
}
return this;
}
catch (BadMessageException x)
{
onBadMessage(x.getCode(), x.getReason());
return null;
}
catch (Throwable x)
{
onBadMessage(HttpStatus.INTERNAL_SERVER_ERROR_500, null);
return null;
}
}
@Override