net.luminis.quic.sample.echo.EchoClientUsing0RTT Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kwik-samples Show documentation
Show all versions of kwik-samples Show documentation
Samples for Kwik, the QUIC implementation in Java
/*
* Copyright © 2022, 2023, 2024 Peter Doornbosch
*
* This file is part of Kwik, an implementation of the QUIC protocol in Java.
*
* Kwik is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Kwik 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 Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package net.luminis.quic.sample.echo;
import net.luminis.quic.QuicClientConnection;
import net.luminis.quic.QuicConnection;
import net.luminis.quic.QuicSessionTicket;
import net.luminis.quic.QuicStream;
import net.luminis.quic.log.SysOutLogger;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.List;
/**
* A simple client that runs a very simple echo protocol on top of QUIC. This client will use 0-RTT when possible.
*
* The echo protocol is a request-response protocol, where the client sends one request on a new stream and the server
* responds by echoing the data from the request in a response on the same stream. After sending the response, the
* stream is closed.
*
* If a session ticket can be found, this client tries to setup the QUIC connection with the session ticket and
* send its request data via 0-RTT. A session ticket is saved to file (named echoclientsessionticket.bin), so just
* run the client a single time to obtain a session ticket.
*
* The class' main method requires one argument:
* - port number of the server (which is assumed to run on localhost)
*/
public class EchoClientUsing0RTT {
public static final String SESSIONTICKET_FILE = "echoclientsessionticket.bin";
private int serverPort;
public static void main(String[] args) throws IOException {
try {
EchoClientUsing0RTT client = new EchoClientUsing0RTT(Integer.parseInt(args[0]));
client.run();
}
catch (NumberFormatException e) {
System.err.println("Error: expected one argument: server-port-number");
System.exit(1);
}
}
public EchoClientUsing0RTT(int serverPort) {
this.serverPort = serverPort;
}
public void run() throws IOException {
byte[] requestData = "hello mate!".getBytes(StandardCharsets.US_ASCII);
SysOutLogger log = new SysOutLogger();
log.logPackets(true); // When 0-RTT is used, log will show a 0-RTT packet like "Packet Z|0|Z|52|1 StreamFrame[0(CIB),0,11,fin]"
// Create connection builder (do not yet create connection!)
QuicClientConnection.Builder connectionBuilder = QuicClientConnection.newBuilder()
.uri(URI.create("echo://localhost:" + serverPort))
.logger(log)
.version(QuicConnection.QuicVersion.V1)
.applicationProtocol("echo")
.noServerCertificateCheck();
// Try to load session ticket and if it can be loaded, create early data.
List earlyData = Collections.emptyList();
try {
byte[] ticketData = Files.readAllBytes(Path.of(SESSIONTICKET_FILE));
connectionBuilder.sessionTicket(ticketData); // This is why the connection should not yet have been created!
earlyData = List.of(new QuicClientConnection.StreamEarlyData(requestData, true));
}
catch (IOException e) {
System.err.println("Cannot read/load session ticket; will not be using 0-RTT!");
}
// Create connection with 0-RTT data
QuicClientConnection connection = connectionBuilder.build();
List earlyStreams = connection.connect(earlyData);
// Connect does create and return streams if earlyData parameter is empty (which is the case here when no session ticket was loaded, see above)
QuicStream quicStream = earlyStreams.stream()
.findAny()
.orElseGet(() -> {
try {
QuicStream s = connection.createStream(true);
s.getOutputStream().write(requestData);
s.getOutputStream().close();
return s;
}
catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
});
System.out.print("Response from server: ");
quicStream.getInputStream().transferTo(System.out);
// Save one session ticket for a next invocation.
List newSessionTickets = connection.getNewSessionTickets();
connection.closeAndWait();
// Note that session tickets are server specific, so if the client can connect to an arbitrary server, the
// ticket should be saved in a way that the origin server can be identified.
newSessionTickets.forEach(ticket ->
{
try {
Files.write(Path.of(SESSIONTICKET_FILE), ticket.serialize(), StandardOpenOption.CREATE);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}