Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[jetty-users] send file using HTTP/3

Hello,
Im further trying to learn programming jetty and http/3 in general. My current task is to send a 10MB file using PUT request. Pardon my lack of expertise in what i will try to describe, im still learning java :-).

So far i tried 2 approaches, first which failed instantly, was:
I tried to read the whole file into a byte array and wrap it into a DataFrame following the headers frame. Client code is as follows:
byte[] fileData = Files.readAllBytes(path);

// http3 server address
HostPort hostPort = new HostPort("127.0.0.1:8181");
Session.Client session = client.connect(new InetSocketAddress(hostPort.getHost(), hostPort.getPort()), new Session.Client.Listener() {

})
.get(5, TimeUnit.SECONDS);

// latch for listener threads
CountDownLatch requestLatch = new CountDownLatch(1);

// Configure the request headers.
HttpFields requestHeaders = HttpFields.build()
.put(HttpHeader.CONTENT_TYPE, "application/octet-stream").put(HttpHeader.CONTENT_LENGTH, String.valueOf(plik.length())).put("FILE_NAME", "file.pdf");

MetaData.Request request = new MetaData.Request(HttpMethod.PUT.asString(), HttpURI.from("https://127.0.0.1:8181/files/hash=" + hash256), HttpVersion.HTTP_3, requestHeaders);
// send a new request, headers frame with signal that data will follow
CompletableFuture<Stream> streamCF = session.newRequest(new HeadersFrame(request, false), new Stream.Client.Listener()
{
@Override
public void onResponse(Stream.Client stream, HeadersFrame frame)
{
System.err.println("RESPONSE HEADER = " + frame);

if (frame.isLast())
{
requestLatch.countDown();
return;
}
stream.demand();
}

@Override
public void onDataAvailable(Stream.Client stream)
{
Stream.Data data = ""> //System.err.println("RESPONSE DATA = "" + data);
System.getLogger("http3").log(System.Logger.Level.INFO, "Odpowiedz {0}", data);
if (data != null)
{
ByteBuffer bufer = data.getByteBuffer();
String odp =  StandardCharsets.UTF_8.decode(bufer).toString();
//System.err.println(odp);
System.getLogger("http3").log(System.Logger.Level.INFO, "Odpowiedz data {0}", odp);

data.complete();
if (data.isLast())
{
requestLatch.countDown();
return;
}
}
stream.demand();
}

@Override
public void onTrailer(Stream.Client stream, HeadersFrame frame)
{
//System.err.println("RESPONSE TRAILER = " + frame);
System.getLogger("http3").log(System.Logger.Level.INFO, "Trailer {0}", frame);
requestLatch.countDown();
}
});

// Block to obtain the Stream.
Stream stream = streamCF.get();

ByteBuffer buffDane = ByteBuffer.wrap(fileData );  
CompletableFuture<Stream> dataCF1 = stream.data(new DataFrame(buffDane, true));
dataCF1.get();

// wait for listener threads to finish
requestLatch.await(5, TimeUnit.SECONDS);

------------------------------------
Server code is as follows:
Server server = new Server();

SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("/home/test/keystore3.p12");
sslContextFactory.setKeyStorePassword("pswd");
sslContextFactory.setCertAlias("jetty");

Session.Server.Listener sessionListener = new Session.Server.Listener() {
@Override
public void onAccept(Session session)
{
SocketAddress remoteAddress = session.getRemoteSocketAddress();
System.getLogger("http3").log(System.Logger.Level.INFO, "Connection from {0}", remoteAddress);
}

@Override
public org.eclipse.jetty.http3.api.Stream.Server.Listener onRequest(
org.eclipse.jetty.http3.api.Stream.Server stream, HeadersFrame frame) {

String fileName = "";

MetaData.Request request = (MetaData.Request)frame.getMetaData();
if (request.getFields().getField("FILE_NAME").getValue() != null) {
System.getLogger("http3").log(System.Logger.Level.INFO, "File name: {0}", request.getFields().getField("FILE_NAME").getValue());
fileName = request.getFields().getField("FILE_NAME").getValue();
}

final String fileNameF = fileName;

// Demand to be called back when data is available.
stream.demand();

// Return a Stream.Server.Listener to handle the request content.
return new Stream.Server.Listener()
{
@Override
public void onDataAvailable(Stream.Server stream)
{
// Read a chunk of the request content.
Stream.Data data = "">
if (data == null)
{
// No data available now, demand to be called back.
stream.demand();
}
else
{
// Get the content buffer.
ByteBuffer buffer = data.getByteBuffer();

// Consume the buffer, here - as an example - just log it.
System.getLogger("http3").log(System.Logger.Level.INFO, "Consuming buffer {0}", buffer);
try {
if (fileNameF.length() > 0) {
FileChannel fc = new FileOutputStream("/home/user/serwer/" + fileNameF, true).getChannel();
fc.write(buffer);
fc.close();
}


} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


// Tell the implementation that the buffer has been consumed.
data.complete();

if (!data.isLast())
{
// Demand to be called back.
stream.demand();
} else {
try {
respond(stream, request);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
};
}

private void respond(Stream.Server stream, MetaData.Request request) throws IOException, NoSuchAlgorithmException
{
// Prepare the response HEADERS frame.

// The response HTTP status and HTTP headers.
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_3, HttpStatus.OK_200, HttpFields.EMPTY);
System.getLogger("http3").log(System.Logger.Level.INFO, "Received request {0}", request);

if (HttpMethod.GET.is(request.getMethod()))
{
// The response content.
String strResponse = "<html><body>test</body></html>";
System.getLogger("http3").log(System.Logger.Level.INFO, "Odpowiadam {0}",  strResponse);
byte[] bytResponse = strResponse.getBytes();
ByteBuffer bufferOdpowiedz = ByteBuffer.wrap(bytOdpowiedz);      

// Send the HEADERS frame with the response status and headers,
// and a DATA frame with the response content bytes.
stream.respond(new HeadersFrame(response, false))
.thenCompose(s -> s.data(new DataFrame(bufferOdpowiedz, true)));
stream.demand();
}
else if (HttpMethod.PUT.is(request.getMethod()))
{
String fileName = request.getFields().getField("FILE_NAME").getValue();
File plik = new File("/home/user/serwer/" + fileName);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
String hash256 = calculateDigest(digest, file);

// The response content.
String strOdpowiedz = "<html><body>thanks for the file, file hash is " + hash256 + "</body></html>";
System.getLogger("http3").log(System.Logger.Level.INFO, "Odpowiadam {0}", strOdpowiedz);
byte[] bytOdpowiedz = strOdpowiedz.getBytes();
ByteBuffer bufferOdpowiedz = ByteBuffer.wrap(bytOdpowiedz);      

// Send the HEADERS frame with the response status and headers,
// and a DATA frame with the response content bytes.
stream.respond(new HeadersFrame(response, false))
.thenCompose(s -> s.data(new DataFrame(bufferOdpowiedz, true)));
stream.demand();
} else {
// Send just the HEADERS frame with the response status and headers.
stream.respond(new HeadersFrame(response, true));
}
}
};

// Create and configure the RawHTTP3ServerConnectionFactory.
RawHTTP3ServerConnectionFactory http3 = new RawHTTP3ServerConnectionFactory(sessionListener);

// Create and configure the HTTP3ServerConnector.
HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, http3);
connector.getQuicConfiguration().setMaxBidirectionalRemoteStreams(1024);

connector.setPort(8181);
server.addConnector(connector);

server.start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

------------------------------------------------------
The above example sends the file for few seconds and then crashes with java.io.UncheckedIOException: failed to read from stream 0; err=QUICHE_ERR_STREAM_RESET

with second approach I decided to split the source file into 1KB buffers and send them in separate DataFrame's, the example looks like this on client side:
// file handle
FileInputStream fis = new FileInputStream(file);

//data buffer
byte[] byteDataBuffer = new byte[1024];
int bytesCount = 0;

// send file chunks
while ((bytesCount = fis.read(byteDataBuffer)) != -1) {
boolean blnDataAvail = fis.available() > 0;
ByteBuffer buffData = ByteBuffer.wrap(byteDataBuffer);
CompletableFuture<Stream> dataCF1 =  stream.data(new DataFrame(buffData, !blnDataAvail));
dataCF1.get();
};
fis.close();

------------------------------
the abobe example sends the file, although the process takes up to 20 seconds and the resulting file checksum does not match the source file checksum.
I'd really appreciate any hint as to how to send a file properly using a PUT request and save it on the server side. Thanks in advance :)


Back to the top