I am looking for sample examples implementing all the features of HTTP/2(Client-Server) like STREAMS,FRAMES PUSH_PROMISE,HPACK with detailed configuration using JETTY EMBEDDED(Jetty 10/Jetty 11) ? It should be able to run at localhost and also if ssl certificates are needed for low level https2 detailed way to implement that. The examples in the documentations are not every clear and are explained in parts. I tried a lot with them. Hopefully a successfully answer to this will help a lot of amateurs who wants to implement low level h2 with embedded jetty. Can anyone help me with the reference ?
Asked
Active
Viewed 824 times
0
-
1Why do you want to implement the low level http/2? streams, frames, and hpack are what Jetty does internally, and is not a public API. push_promise is a server only feature and is made available via the Servlet 4.0+ spec via the PushBuilder. – Joakim Erdfelt Apr 12 '21 at 12:12
-
2The documentation is pretty comprehensive about the HTTP/2 low-level APIs, see https://www.eclipse.org/jetty/documentation/jetty-11/programming-guide/index.html#pg-client-http2. If you feel there is something missing, feel free to open an issue at https://github.com/eclipse/jetty.project/issues, as StackOverflow is not the right place to clarify or extend the documentation of a project. – sbordet Apr 12 '21 at 13:46
-
Well I was able to implement low level APIs but I want to make it accessible only through https and also the server should be accessible from browser...Is that a possibility with low level? and How HttpConfiguration will affect at low-level api implementation ? – Zipperz Apr 14 '21 at 13:10
1 Answers
0
Code for reference for others.
H2Server.java
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.UnaryOperator;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
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.GoAwayFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import static java.lang.System.Logger.Level.INFO;
import java.io.OutputStream;
@SuppressWarnings("unused") public class H2Server {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
// Create a Server instance.
QueuedThreadPool serverExecutor = new QueuedThreadPool();
serverExecutor.setName("server");
// server = new Server(serverExecutor);
Server server = new Server(serverExecutor);
// ServerSessionListener sessionListener = new ServerSessionListener.Adapter();
ServerSessionListener sessionListener = new ServerSessionListener.Adapter() {
@Override
public Map<Integer, Integer> onPreface(Session session) {
System.out.println("onPreface Called");
// Customize the settings, for example:
Map<Integer, Integer> settings = new HashMap<>();
// Tell the client that HTTP/2 push is disabled.
settings.put(SettingsFrame.ENABLE_PUSH, 0);
settings.put(SettingsFrame.ENABLE_CONNECT_PROTOCOL, 8);
return settings;
}
@Override
public void onAccept(Session session) {
System.out.println("onAccept Called");
InetSocketAddress remoteAddress = session.getRemoteAddress();
System.getLogger("http2").log(INFO, "Connection from {0}", remoteAddress);
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) {
System.out.println("onNewStream Called");
// This is the "new stream" event, so it's guaranteed to be a request.
MetaData.Request request = (MetaData.Request) frame.getMetaData();
if (frame.isEndStream()) {
respond(stream, request);
return null;
} else {
// Return a Stream.Listener to handle the request events,
// for example request content events or a request reset.
return new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
// Get the content buffer.
ByteBuffer buffer = frame.getData();
// Consume the buffer, here - as an example - just log it.
// System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);
System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);
System.out.println("Consuming buffer {0} " +StandardCharsets.UTF_8.decode(buffer).toString());
// Tell the implementation that the buffer has been consumed.
callback.succeeded();
// By returning from the method, implicitly tell the implementation
// to deliver to this method more DATA frames when they are available.
if (frame.isEndStream()) {
System.out.println("EndStream");
respond(stream, request);
}
}
};
}
}
private void respond(Stream stream, MetaData.Request request) {
// Prepare the response HEADERS frame.
System.out.println("respond Called");
// The response HTTP status and HTTP headers.
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200,
HttpFields.EMPTY);
if (HttpMethod.GET.is(request.getMethod())) {
// The response content.
ByteBuffer resourceBytes = getResourceBytes(request);
System.out.println("Request==GET resourceBytes== "+ StandardCharsets.UTF_8.decode(resourceBytes).toString());
// Send the HEADERS frame with the response status and headers,
// and a DATA frame with the response content bytes.
stream.headers(new HeadersFrame(stream.getId(), response, null, false))
.thenCompose(s -> s.data(new DataFrame(s.getId(), resourceBytes, true)));
} else {
// Send just the HEADERS frame with the response status and headers.
System.out.println("Request==POST response== "+ response);
String content1 = "{\"greet\": \"Welcome!!!\"}";
ByteBuffer buffer1 = StandardCharsets.UTF_8.encode(content1);
//stream.headers(new HeadersFrame(stream.getId(), response, null, true));
stream.headers(new HeadersFrame(stream.getId(), response, null, false))
.thenCompose(s -> s.data(new DataFrame(s.getId(), buffer1, true)));
}
}
private ByteBuffer getResourceBytes(MetaData.Request request)
{
return ByteBuffer.allocate(1024);
}
};
// HTTP Configuration
HttpConfiguration httpConfig = new HttpConfiguration();
// httpConfig.setSecureScheme("https");
httpConfig.setSecureScheme("https");
// httpConfig.setSecurePort(8443);
httpConfig.setSecurePort(8443);
httpConfig.setSendXPoweredBy(true);
httpConfig.setSendServerVersion(true);
// httpConfig.setRequestHeaderSize(16 * 1024);
// HTTPS Configuration
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
// Create a ServerConnector with RawHTTP2ServerConnectionFactory.
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(httpConfig, sessionListener);
// Configure RawHTTP2ServerConnectionFactory, for example:
// Configure the max number of concurrent requests.
http2.setMaxConcurrentStreams(128);
// Enable support for CONNECT.
http2.setConnectProtocolEnabled(true);
// Create the ServerConnector.
ServerConnector connector = new ServerConnector(server, http2);
// connector.setPort(8080);
connector.setPort(8443);
connector.setHost("localhost");
connector.setAcceptQueueSize(128);
// Add the Connector to the Server
server.addConnector(connector);
// Start the Server so it starts accepting connections from clients.
server.start();
// new H2Server().testNoPrefaceBytes();
}
}
H2Client.java
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpURI;
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.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
public class H2Client {
public static void main(String[] args) throws Exception {
HTTP2Client http2Client = new HTTP2Client();
http2Client.start();
ClientConnector connector = http2Client.getClientConnector();
// Address of the server's encrypted port.
SocketAddress serverAddress = new InetSocketAddress("localhost", 8443);
// SocketAddress serverAddress = new InetSocketAddress("http://www.google.com/", 8080);
// Address of the server's encrypted port.
// SocketAddress serverAddress = new InetSocketAddress("localhost", 8443);
// CompletableFuture<Session> sessionCF = http2Client.connect(connector.getSslContextFactory(), serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
System.out.println("onPreface Called");
Map<Integer, Integer> configuration = new HashMap<>();
// Disable push from the server.
configuration.put(SettingsFrame.ENABLE_PUSH, 0);
// Override HTTP2Client.initialStreamRecvWindow for this session.
configuration.put(SettingsFrame.INITIAL_WINDOW_SIZE, 1024 * 1024);
return configuration;
}
});
Session session = sessionCF.get();
/*
// Configure the request headers.
HttpFields requestHeaders = HttpFields.build()
.put(HttpHeader.USER_AGENT, "Jetty HTTP2Client {version}");
// The request metadata with method, URI and headers.
MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:61432"), HttpVersion.HTTP_2, requestHeaders);
// MetaData.Request request = new MetaData.Request("GET", HttpURI.from("https://www.google.com/"), HttpVersion.HTTP_2, requestHeaders);
// The HTTP/2 HEADERS frame, with endStream=true
// to signal that this request has no content.
HeadersFrame headersFrame = new HeadersFrame(request, null, true);
// Open a Stream by sending the HEADERS frame.
session.newStream(headersFrame,new Promise.Adapter<>(), new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
System.out.println("onHeaders Called");
MetaData metaData = frame.getMetaData();
// Is this HEADERS frame the response or the trailers?
if (metaData.isResponse())
{
MetaData.Response response = (MetaData.Response)metaData;
System.out.println( "Received response {0}== "+ response);
}
else
{
System.out.println("Received trailers {0}== " + metaData.getFields());
}
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
System.out.println("onData Called");
// Get the content buffer.
ByteBuffer buffer = frame.getData();
// Consume the buffer, here - as an example - just log it.
System.out.println("Consuming buffer {0}" +buffer);
// Tell the implementation that the buffer has been consumed.
callback.succeeded();
// By returning from the method, implicitly tell the implementation
// to deliver to this method more DATA frames when they are available.
}
});
*/
// Configure the request headers.
HttpFields requestHeaders = HttpFields.build()
.put(HttpHeader.CONTENT_TYPE, "application/json");
// The request metadata with method, URI and headers.
MetaData.Request request = new MetaData.Request("POST", HttpURI.from("http://localhost:8443/"), HttpVersion.HTTP_2, requestHeaders);
// MetaData.Request request = new MetaData.Request("POST", HttpURI.from("0.0.0.0:63780"), HttpVersion.HTTP_2, requestHeaders);
// The HTTP/2 HEADERS frame, with endStream=false to
// signal that there will be more frames in this stream.
HeadersFrame headersFrame = new HeadersFrame(request, null, false);
// Open a Stream by sending the HEADERS frame.
// CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter());
// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
{
public void onHeaders(Stream stream, HeadersFrame frame)
{
System.out.println("onHeaders Called");
MetaData metaData = frame.getMetaData();
// Is this HEADERS frame the response or the trailers?
if (metaData.isResponse())
{
MetaData.Response response = (MetaData.Response)metaData;
System.out.println( "Received response {0}== "+ response);
}
else
{
System.out.println("Received trailers {0}== " + metaData.getFields());
}
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
System.out.println("onData Called");
// Get the content buffer.
ByteBuffer buffer = frame.getData();
// Consume the buffer, here - as an example - just log it.
System.out.println("Consuming buffer {0}" +StandardCharsets.UTF_8.decode(buffer).toString());
// Tell the implementation that the buffer has been consumed.
callback.succeeded();
// By returning from the method, implicitly tell the implementation
// to deliver to this method more DATA frames when they are available.
}
});
// Block to obtain the Stream.
// Alternatively you can use the CompletableFuture APIs to avoid blocking.
Stream stream = streamCF.get();
// The request content, in two chunks.
String content1 = "{\"greet\": \"hello world\"}";
ByteBuffer buffer1 = StandardCharsets.UTF_8.encode(content1);
String content2 = "{\"user\": \"jetty\"}";
ByteBuffer buffer2 = StandardCharsets.UTF_8.encode(content2);
// Send the first DATA frame on the stream, with endStream=false
// to signal that there are more frames in this stream.
CompletableFuture<Stream> dataCF1 = stream.data(new DataFrame(stream.getId(), buffer1, false));
// Only when the first chunk has been sent we can send the second,
// with endStream=true to signal that there are no more frames.
dataCF1.thenCompose(s -> s.data(new DataFrame(s.getId(), buffer2, true)));
// end::newStreamWithData[]
System.out.println("EOF");
/*
*/
}
}

Zipperz
- 11
- 1
-
I still could not figure out how to set protocol to set secured(HTTPS) only at the server end.And also can the server be accessed from a browser or it needs a low-level client only? – Zipperz Apr 15 '21 at 09:36