Skip to content

Commit

Permalink
Adds support for send(byte[], int, int) to ServerResponse.
Browse files Browse the repository at this point in the history
Signed-off-by: Santiago Pericas-Geertsen <[email protected]>
  • Loading branch information
spericas committed Dec 5, 2024
1 parent 75353af commit cbe7414
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@
package io.helidon.webserver.http;

import java.io.OutputStream;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.UnaryOperator;

Expand Down Expand Up @@ -112,6 +113,17 @@ default ServerResponse header(String name, String... values) {
*/
void send(byte[] bytes);

/**
* Send a byte array response.
*
* @param bytes bytes to send
* @param position starting position
* @param length number of bytes send
*/
default void send(byte[] bytes, int position, int length) {
send(Arrays.copyOfRange(bytes, position, length));
}

/**
* Send an entity, a {@link io.helidon.http.media.MediaContext} will be used to serialize the entity.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,17 +201,30 @@ protected MediaContext mediaContext() {
* if entity is empty.
*
* @param configuredEntity plain bytes
* @return encoded bytes
* @return encoded bytes or same entity array if encoding is disabled
*/
protected byte[] entityBytes(byte[] configuredEntity) {
return entityBytes(configuredEntity, 0, configuredEntity.length);
}

/**
* Entity bytes encoded using content encoding. Does not attempt encoding
* if entity is empty.
*
* @param configuredEntity plain bytes
* @param position starting position
* @param length number of bytes
* @return encoded bytes or same entity array if encoding is disabled
*/
protected byte[] entityBytes(byte[] configuredEntity, int position, int length) {
byte[] entity = configuredEntity;
if (contentEncodingContext.contentEncodingEnabled() && entity.length > 0) {
if (contentEncodingContext.contentEncodingEnabled() && length > 0) {
ContentEncoder encoder = contentEncodingContext.encoder(requestHeaders);
// we want to preserve optimization here, let's create a new byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream(entity.length);
ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
OutputStream os = encoder.apply(baos);
try {
os.write(entity);
os.write(entity, position, length);
os.close();
} catch (IOException e) {
throw new ServerConnectionException("Failed to write response", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,26 +191,32 @@ public Http1ServerResponse header(Header header) {
*/
@Override
public void send(byte[] bytes) {
send(bytes, 0, bytes.length);
}

@Override
public void send(byte[] bytes, int position, int length) {
// if no entity status, we cannot send bytes here
if (isNoEntityStatus && bytes.length > 0) {
if (isNoEntityStatus && length > 0) {
status(noEntityInternalError(status()));
return;
}

// send bytes to writer
if (outputStreamFilter == null && !headers.contains(HeaderNames.TRAILER)) {
byte[] entity = entityBytes(bytes);
BufferData bufferData = responseBuffer(entity);
byte[] entity = entityBytes(bytes, position, length);
BufferData bufferData = (bytes != entity) ? responseBuffer(entity)
: responseBuffer(entity, position, length); // no encoding, same length
bytesWritten = bufferData.available();
isSent = true;
request.reset();
dataWriter.write(bufferData);
afterSend();
} else {
// we should skip encoders if no data is written (e.g. for GZIP)
boolean skipEncoders = (bytes.length == 0);
boolean skipEncoders = (length == 0);
try (OutputStream os = outputStream(skipEncoders)) {
os.write(bytes);
os.write(bytes, position, length);
} catch (IOException e) {
throw new ServerConnectionException("Failed to write response", e);
}
Expand Down Expand Up @@ -371,6 +377,10 @@ private static void writeHeaders(io.helidon.http.Headers headers, BufferData buf
}

private BufferData responseBuffer(byte[] bytes) {
return responseBuffer(bytes, 0, bytes.length);
}

private BufferData responseBuffer(byte[] bytes, int position, int length) {
if (isSent) {
throw new IllegalStateException("Response already sent");
}
Expand All @@ -379,39 +389,36 @@ private BufferData responseBuffer(byte[] bytes) {
+ ", do not call send().");
}

int contentLength = bytes.length;
boolean forcedChunkedEncoding = false;
headers.setIfAbsent(HeaderValues.CONNECTION_KEEP_ALIVE);

if (headers.contains(HeaderValues.TRANSFER_ENCODING_CHUNKED)) {
headers.remove(HeaderNames.CONTENT_LENGTH);
// chunked enforced (and even if empty entity, will be used)
forcedChunkedEncoding = true;
} else {
if (!headers.contains(HeaderNames.CONTENT_LENGTH)) {
headers.contentLength(contentLength);
}
} else if (!headers.contains(HeaderNames.CONTENT_LENGTH)) {
headers.contentLength(length);
}

Status usedStatus = status();
sendListener.status(ctx, usedStatus);
sendListener.headers(ctx, headers);

// give some space for code and headers + entity
BufferData responseBuffer = BufferData.growing(256 + bytes.length);
BufferData responseBuffer = BufferData.growing(256 + length);

nonEntityBytes(headers, usedStatus, responseBuffer, keepAlive, validateHeaders);
if (forcedChunkedEncoding) {
byte[] hex = Integer.toHexString(contentLength).getBytes(StandardCharsets.US_ASCII);
byte[] hex = Integer.toHexString(length).getBytes(StandardCharsets.US_ASCII);
responseBuffer.write(hex);
responseBuffer.write('\r');
responseBuffer.write('\n');
responseBuffer.write(bytes);
responseBuffer.write(bytes, position, length);
responseBuffer.write('\r');
responseBuffer.write('\n');
responseBuffer.write(TERMINATING_CHUNK);
} else {
responseBuffer.write(bytes);
responseBuffer.write(bytes, position, length);
}

sendListener.data(ctx, responseBuffer);
Expand Down

0 comments on commit cbe7414

Please sign in to comment.