Skip to content

Commit 901bbcb

Browse files
committed
Context API: Add ctx.send(byte[]...) and ctx.send(ByteBuffer[])
1 parent 9d76c32 commit 901bbcb

8 files changed

Lines changed: 127 additions & 0 deletions

File tree

jooby/src/main/java/io/jooby/Context.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,10 @@ public interface Context extends Registry {
985985
*/
986986
@Nonnull Context send(@Nonnull ByteBuffer data);
987987

988+
@Nonnull Context send(@Nonnull byte[]... data);
989+
990+
@Nonnull Context send(@Nonnull ByteBuffer[] data);
991+
988992
/**
989993
* Send response data.
990994
*

jooby/src/main/java/io/jooby/DefaultContext.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.OutputStream;
2020
import java.io.PrintWriter;
2121
import java.lang.reflect.Type;
22+
import java.nio.ByteBuffer;
2223
import java.nio.channels.FileChannel;
2324
import java.nio.charset.Charset;
2425
import java.nio.charset.StandardCharsets;
@@ -401,6 +402,14 @@ default Context setResponseHeader(@Nonnull String name, @Nonnull Object value) {
401402
return send(redirect);
402403
}
403404

405+
@Override default @Nonnull Context send(@Nonnull byte[]... data) {
406+
ByteBuffer[] buffer = new ByteBuffer[data.length];
407+
for (int i = 0; i < data.length; i++) {
408+
buffer[i] = ByteBuffer.wrap(data[i]);
409+
}
410+
return send(buffer);
411+
}
412+
404413
@Override default @Nonnull Context send(@Nonnull String data) {
405414
return send(data, StandardCharsets.UTF_8);
406415
}

jooby/src/main/java/io/jooby/ForwardingContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,16 @@ public Context sendRedirect(@Nonnull StatusCode redirect, @Nonnull String locati
471471
return this;
472472
}
473473

474+
@Nonnull @Override public Context send(@Nonnull byte[]... data) {
475+
ctx.send(data);
476+
return this;
477+
}
478+
479+
@Nonnull @Override public Context send(@Nonnull ByteBuffer[] data) {
480+
ctx.send(data);
481+
return this;
482+
}
483+
474484
@Override @Nonnull public Context send(@Nonnull ReadableByteChannel channel) {
475485
ctx.send(channel);
476486
return this;

modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.jooby.Value;
2727
import io.jooby.ValueNode;
2828
import io.jooby.WebSocket;
29+
import org.eclipse.jetty.http.HttpContent;
2930
import org.eclipse.jetty.http.HttpFields;
3031
import org.eclipse.jetty.http.HttpHeader;
3132
import org.eclipse.jetty.http.HttpHeaderValue;
@@ -34,6 +35,7 @@
3435
import org.eclipse.jetty.server.HttpOutput;
3536
import org.eclipse.jetty.server.Request;
3637
import org.eclipse.jetty.server.Response;
38+
import org.eclipse.jetty.util.BufferUtil;
3739
import org.eclipse.jetty.util.Callback;
3840
import org.eclipse.jetty.util.MultiMap;
3941
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
@@ -43,8 +45,11 @@
4345

4446
import javax.annotation.Nonnull;
4547
import javax.annotation.Nullable;
48+
import javax.servlet.AsyncContext;
4649
import javax.servlet.MultipartConfigElement;
4750
import javax.servlet.ServletException;
51+
import javax.servlet.ServletOutputStream;
52+
import javax.servlet.WriteListener;
4853
import javax.servlet.http.Part;
4954
import java.io.FileInputStream;
5055
import java.io.IOException;
@@ -65,6 +70,7 @@
6570
import java.util.List;
6671
import java.util.Map;
6772
import java.util.concurrent.Executor;
73+
import java.util.concurrent.atomic.AtomicBoolean;
6874

6975
import static org.eclipse.jetty.http.HttpHeader.CONTENT_TYPE;
7076
import static org.eclipse.jetty.http.HttpHeader.SET_COOKIE;
@@ -380,6 +386,17 @@ public Context setResponseType(@Nonnull MediaType contentType, @Nullable Charset
380386
return this;
381387
}
382388

389+
@Nonnull @Override public Context send(@Nonnull ByteBuffer[] data) {
390+
if (response.getContentLength() <= 0) {
391+
setResponseLength(BufferUtil.remaining(data));
392+
}
393+
ifStartAsync();
394+
HttpOutput out = response.getHttpOutput();
395+
out.setWriteListener(writeListener(request.getAsyncContext(), out, data));
396+
responseStarted = true;
397+
return this;
398+
}
399+
383400
@Nonnull @Override public Context send(@Nonnull byte[] data) {
384401
return send(ByteBuffer.wrap(data));
385402
}
@@ -554,4 +571,25 @@ private static void formParam(Request request, Formdata form) {
554571
}
555572
}
556573

574+
private static WriteListener writeListener(AsyncContext async, HttpOutput out,
575+
ByteBuffer[] data) {
576+
return new WriteListener() {
577+
int i = 0;
578+
579+
@Override public void onWritePossible() throws IOException {
580+
while (out.isReady()) {
581+
if (i < data.length) {
582+
out.write(data[i++]);
583+
} else {
584+
async.complete();
585+
return;
586+
}
587+
}
588+
}
589+
590+
@Override public void onError(Throwable x) {
591+
async.complete();
592+
}
593+
};
594+
}
557595
}

modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyContext.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,14 @@ public NettyContext(ChannelHandlerContext ctx, HttpRequest req, Router router, S
401401
return send(wrappedBuffer(data));
402402
}
403403

404+
@Nonnull @Override public Context send(@Nonnull byte[]... data) {
405+
return send(Unpooled.wrappedBuffer(data));
406+
}
407+
408+
@Nonnull @Override public Context send(@Nonnull ByteBuffer[] data) {
409+
return send(Unpooled.wrappedBuffer(data));
410+
}
411+
404412
@Override public final Context send(ByteBuffer data) {
405413
return send(wrappedBuffer(data));
406414
}

modules/jooby-test/src/main/java/io/jooby/MockContext.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Map;
2929
import java.util.concurrent.Executor;
3030
import java.util.stream.Collectors;
31+
import java.util.stream.IntStream;
3132

3233
/**
3334
* Unit test friendly context implementation. Allows to set context properties.
@@ -478,13 +479,27 @@ public MockContext setResponseType(@Nonnull MediaType contentType, @Nullable Cha
478479
return this;
479480
}
480481

482+
@Nonnull @Override public MockContext send(@Nonnull byte[]... data) {
483+
responseStarted = true;
484+
this.response.setResult(data)
485+
.setContentLength(IntStream.range(0, data.length).map(i -> data[i].length).sum());
486+
return this;
487+
}
488+
481489
@Nonnull @Override public MockContext send(@Nonnull ByteBuffer data) {
482490
responseStarted = true;
483491
this.response.setResult(data)
484492
.setContentLength(data.remaining());
485493
return this;
486494
}
487495

496+
@Nonnull @Override public Context send(@Nonnull ByteBuffer[] data) {
497+
responseStarted = true;
498+
this.response.setResult(data)
499+
.setContentLength(IntStream.range(0, data.length).map(i -> data[i].remaining()).sum());
500+
return this;
501+
}
502+
488503
@Nonnull @Override public MockContext send(InputStream input) {
489504
responseStarted = true;
490505
this.response.setResult(input);

modules/jooby-utow/src/main/java/io/jooby/internal/utow/UtowContext.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
import io.jooby.ValueNode;
2626
import io.jooby.WebSocket;
2727
import io.undertow.Handlers;
28+
import io.undertow.connector.PooledByteBuffer;
2829
import io.undertow.io.IoCallback;
2930
import io.undertow.io.Sender;
3031
import io.undertow.server.HttpServerExchange;
32+
import io.undertow.server.ServerConnection;
3133
import io.undertow.server.handlers.form.FormData;
3234
import io.undertow.util.HeaderMap;
3335
import io.undertow.util.HeaderValues;
@@ -347,6 +349,20 @@ public Context setResponseType(@Nonnull MediaType contentType, @Nullable Charset
347349
return send(ByteBuffer.wrap(data.getBytes(charset)));
348350
}
349351

352+
@Nonnull @Override public Context send(@Nonnull ByteBuffer[] data) {
353+
HeaderMap headers = exchange.getResponseHeaders();
354+
if (!headers.contains(CONTENT_LENGTH)) {
355+
long len = 0;
356+
for (ByteBuffer b : data) {
357+
len += b.remaining();
358+
}
359+
exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Long.toString(len));
360+
}
361+
362+
exchange.getResponseSender().send(data,this);
363+
return this;
364+
}
365+
350366
@Nonnull @Override public Context send(@Nonnull ByteBuffer data) {
351367
exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Long.toString(data.remaining()));
352368
exchange.getResponseSender().send(data, this);

tests/src/test/java/io/jooby/FeaturedTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.time.ZoneId;
4040
import java.time.format.DateTimeFormatter;
4141
import java.time.temporal.ChronoUnit;
42+
import java.util.ArrayList;
4243
import java.util.Arrays;
4344
import java.util.HashMap;
4445
import java.util.HashSet;
@@ -2885,6 +2886,32 @@ public void beanConverter() {
28852886
});
28862887
}
28872888

2889+
@Test
2890+
public void byteArrayResponse() {
2891+
new JoobyRunner(app -> {
2892+
app.get("/bytearray", ctx -> {
2893+
return ctx.send(partition(_19kb.getBytes(StandardCharsets.UTF_8), 1536));
2894+
});
2895+
}).ready(client -> {
2896+
client.get("/bytearray", rsp -> {
2897+
assertEquals(_19kb, rsp.body().string());
2898+
});
2899+
});
2900+
}
2901+
2902+
private byte[][] partition(byte[] bytes, int size) {
2903+
List<byte[]> result = new ArrayList<>();
2904+
int offset = 0;
2905+
while (offset < bytes.length) {
2906+
int len = Math.min(size,bytes.length - offset);
2907+
byte[] b = new byte[len];
2908+
System.arraycopy(bytes, offset, b, 0, len);
2909+
result.add(b);
2910+
offset += size;
2911+
}
2912+
return result.toArray(new byte[0][0]);
2913+
}
2914+
28882915
private static String readText(Path file) {
28892916
try {
28902917
return new String(Files.readAllBytes(file), StandardCharsets.UTF_8);

0 commit comments

Comments
 (0)