//
// ========================================================================
// Copyright (c) 1995-2017 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.http2.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Promise;
import org.junit.Assert;
import org.junit.Test;
public class TrailersTest extends AbstractTest
{
@Test
public void testTrailersSentByClient() throws Exception
{
CountDownLatch latch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
MetaData.Request request = (MetaData.Request)frame.getMetaData();
Assert.assertFalse(frame.isEndStream());
Assert.assertTrue(request.getFields().containsKey("X-Request"));
return new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
MetaData trailer = frame.getMetaData();
Assert.assertTrue(frame.isEndStream());
Assert.assertTrue(trailer.getFields().containsKey("X-Trailer"));
latch.countDown();
}
};
}
});
Session session = newClient(new Session.Listener.Adapter());
HttpFields requestFields = new HttpFields();
requestFields.put("X-Request", "true");
MetaData.Request request = newRequest("GET", requestFields);
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter());
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
// Send the trailers.
HttpFields trailerFields = new HttpFields();
trailerFields.put("X-Trailer", "true");
MetaData trailers = new MetaData(HttpVersion.HTTP_2, trailerFields);
HeadersFrame trailerFrame = new HeadersFrame(stream.getId(), trailers, null, true);
stream.headers(trailerFrame, Callback.NOOP);
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testServletRequestTrailers() throws Exception
{
CountDownLatch trailerLatch = new CountDownLatch(1);
start(new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Request jettyRequest = (Request)request;
// No trailers yet.
Assert.assertNull(jettyRequest.getTrailerHttpFields());
trailerLatch.countDown();
// Read the content.
ServletInputStream input = jettyRequest.getInputStream();
while (true)
{
int read = input.read();
if (read < 0)
break;
}
// Now we have the trailers.
HttpFields trailers = jettyRequest.getTrailerHttpFields();
Assert.assertNotNull(trailers);
Assert.assertNotNull(trailers.get("X-Trailer"));
}
});
Session session = newClient(new Session.Listener.Adapter());
HttpFields requestFields = new HttpFields();
requestFields.put("X-Request", "true");
MetaData.Request request = newRequest("GET", requestFields);
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
MetaData.Response response = (MetaData.Response)frame.getMetaData();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
if (frame.isEndStream())
latch.countDown();
}
});
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
// Send some data.
Callback.Completable callback = new Callback.Completable();
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), false), callback);
Assert.assertTrue(trailerLatch.await(5, TimeUnit.SECONDS));
// Send the trailers.
callback.thenRun(() ->
{
HttpFields trailerFields = new HttpFields();
trailerFields.put("X-Trailer", "true");
MetaData trailers = new MetaData(HttpVersion.HTTP_2, trailerFields);
HeadersFrame trailerFrame = new HeadersFrame(stream.getId(), trailers, null, true);
stream.headers(trailerFrame, Callback.NOOP);
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testTrailersSentByServer() throws Exception
{
start(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
HttpFields responseFields = new HttpFields();
responseFields.put("X-Response", "true");
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, responseFields);
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false);
stream.headers(responseFrame, new Callback()
{
@Override
public void succeeded()
{
HttpFields trailerFields = new HttpFields();
trailerFields.put("X-Trailer", "true");
MetaData trailer = new MetaData(HttpVersion.HTTP_2, trailerFields);
HeadersFrame trailerFrame = new HeadersFrame(stream.getId(), trailer, null, true);
stream.headers(trailerFrame, NOOP);
}
});
return null;
}
});
Session session = newClient(new Session.Listener.Adapter());
MetaData.Request request = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(request, null, true);
CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
{
private boolean responded;
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
if (!responded)
{
MetaData.Response response = (MetaData.Response)frame.getMetaData();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertTrue(response.getFields().containsKey("X-Response"));
Assert.assertFalse(frame.isEndStream());
responded = true;
}
else
{
MetaData trailer = frame.getMetaData();
Assert.assertTrue(trailer.getFields().containsKey("X-Trailer"));
Assert.assertTrue(frame.isEndStream());
latch.countDown();
}
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}