-
-
Notifications
You must be signed in to change notification settings - Fork 199
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2921 from agentgt/jstachio_pre_encode
Add Rocker style pre-encoding for JStachio
- Loading branch information
Showing
8 changed files
with
310 additions
and
81 deletions.
There are no files selected for viewing
117 changes: 117 additions & 0 deletions
117
modules/jooby-jstachio/src/main/java/io/jooby/jstachio/ByteBufferedOutputStream.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
* Jooby https://jooby.io | ||
* Apache License Version 2.0 https://jooby.io/LICENSE.txt | ||
* Copyright 2014 Edgar Espina | ||
*/ | ||
package io.jooby.jstachio; | ||
|
||
import java.io.OutputStream; | ||
import java.nio.ByteBuffer; | ||
import java.util.Arrays; | ||
|
||
/** | ||
* This is basically the same as Rockers byte buffer but as an OutputStream because JStachio wants | ||
* that interface. Currently it is internal. | ||
* | ||
* @author agentgt | ||
*/ | ||
class ByteBufferedOutputStream extends OutputStream { | ||
|
||
/** Default buffer size: <code>4k</code>. */ | ||
public static final int BUFFER_SIZE = 4096; | ||
|
||
/** | ||
* The maximum size of array to allocate. Some VMs reserve some header words in an array. Attempts | ||
* to allocate larger arrays may result in OutOfMemoryError: Requested array size exceeds VM limit | ||
*/ | ||
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; | ||
|
||
/** The buffer where data is stored. */ | ||
protected byte[] buf; | ||
|
||
/** The number of valid bytes in the buffer. */ | ||
protected int count; | ||
|
||
ByteBufferedOutputStream(int bufferSize) { | ||
this.buf = new byte[bufferSize]; | ||
} | ||
|
||
void reset() { | ||
count = 0; | ||
} | ||
|
||
@Override | ||
public void close() { | ||
this.reset(); | ||
} | ||
|
||
@Override | ||
public void write(byte[] bytes) { | ||
int len = bytes.length; | ||
ensureCapacity(count + len); | ||
System.arraycopy(bytes, 0, buf, count, len); | ||
count += len; | ||
} | ||
|
||
public int size() { | ||
return count; | ||
} | ||
|
||
/** | ||
* Copy internal byte array into a new array. | ||
* | ||
* @return Byte array. | ||
*/ | ||
public byte[] toByteArray() { | ||
byte[] array = new byte[count]; | ||
System.arraycopy(buf, 0, array, 0, count); | ||
return array; | ||
} | ||
|
||
/** | ||
* Get a view of the byte buffer. | ||
* | ||
* @return Byte buffer. | ||
*/ | ||
public ByteBuffer toBuffer() { | ||
return ByteBuffer.wrap(buf, 0, count); | ||
} | ||
|
||
private void ensureCapacity(int minCapacity) { | ||
// overflow-conscious code | ||
if (minCapacity - buf.length > 0) { | ||
grow(minCapacity); | ||
} | ||
} | ||
|
||
/** | ||
* Increases the capacity to ensure that it can hold at least the number of elements specified by | ||
* the minimum capacity argument. | ||
* | ||
* @param minCapacity the desired minimum capacity | ||
*/ | ||
private void grow(int minCapacity) { | ||
// overflow-conscious code | ||
int oldCapacity = buf.length; | ||
int newCapacity = oldCapacity << 1; | ||
if (newCapacity - minCapacity < 0) { | ||
newCapacity = minCapacity; | ||
} | ||
if (newCapacity - MAX_ARRAY_SIZE > 0) { | ||
newCapacity = hugeCapacity(minCapacity); | ||
} | ||
buf = Arrays.copyOf(buf, newCapacity); | ||
} | ||
|
||
private static int hugeCapacity(int minCapacity) { | ||
if (minCapacity < 0) { | ||
throw new OutOfMemoryError(); | ||
} | ||
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; | ||
} | ||
|
||
@Override | ||
public void write(int b) { | ||
throw new UnsupportedOperationException("expecting only write(byte[])"); | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
modules/jooby-jstachio/src/main/java/io/jooby/jstachio/JStachioBuffer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* Jooby https://jooby.io | ||
* Apache License Version 2.0 https://jooby.io/LICENSE.txt | ||
* Copyright 2014 Edgar Espina | ||
*/ | ||
package io.jooby.jstachio; | ||
|
||
/** | ||
* To provide Rocker like buffer support | ||
* | ||
* @author agentgt | ||
*/ | ||
interface JStachioBuffer { | ||
|
||
public ByteBufferedOutputStream acquire(); | ||
|
||
public void release(ByteBufferedOutputStream buffer); | ||
|
||
static JStachioBuffer of(int bufferSize, boolean reuseBuffer) { | ||
if (reuseBuffer) { | ||
return new ReuseBuffer(bufferSize); | ||
} else { | ||
return new NoReuseBuffer(bufferSize); | ||
} | ||
} | ||
} | ||
|
||
record NoReuseBuffer(int bufferSize) implements JStachioBuffer { | ||
@Override | ||
public ByteBufferedOutputStream acquire() { | ||
return new ByteBufferedOutputStream(bufferSize); | ||
} | ||
|
||
@Override | ||
public void release(ByteBufferedOutputStream buffer) {} | ||
} | ||
|
||
class ReuseBuffer implements JStachioBuffer { | ||
private final ThreadLocal<ByteBufferedOutputStream> localBuffer; | ||
|
||
public ReuseBuffer(int bufferSize) { | ||
super(); | ||
this.localBuffer = ThreadLocal.withInitial(() -> new ByteBufferedOutputStream(bufferSize)); | ||
} | ||
|
||
@Override | ||
public ByteBufferedOutputStream acquire() { | ||
return localBuffer.get(); | ||
} | ||
|
||
@Override | ||
public void release(ByteBufferedOutputStream buffer) { | ||
buffer.reset(); | ||
} | ||
} |
43 changes: 32 additions & 11 deletions
43
modules/jooby-jstachio/src/main/java/io/jooby/jstachio/JStachioHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,56 @@ | ||
/* | ||
* Jooby https://jooby.io | ||
* Apache License Version 2.0 https://jooby.io/LICENSE.txt | ||
* Copyright 2023 Edgar Espina | ||
* Copyright 2014 Edgar Espina | ||
*/ | ||
package io.jooby.jstachio; | ||
|
||
import java.io.IOException; | ||
|
||
import io.jooby.Context; | ||
import io.jooby.MediaType; | ||
import io.jooby.Route; | ||
import io.jooby.Route.Handler; | ||
import io.jstach.jstachio.JStachio; | ||
import io.jstach.jstachio.Template; | ||
|
||
class JStachioHandler extends JStachioRenderer<Context> implements Route.Filter { | ||
|
||
class JStachioHandler implements Route.Filter { | ||
|
||
private final JStachioMessageEncoder encoder; | ||
|
||
public JStachioHandler(JStachioMessageEncoder encoder) { | ||
super(); | ||
this.encoder = encoder; | ||
public JStachioHandler(JStachio jstachio, JStachioBuffer buffer) { | ||
super(jstachio, buffer); | ||
} | ||
|
||
@Override | ||
public Handler apply(Handler next) { | ||
return ctx -> { | ||
try { | ||
Object model = next.apply(ctx); | ||
ctx.setResponseType(MediaType.html); | ||
return ctx.send(encoder.render(model)); | ||
Object model = next.apply(ctx); | ||
return render(ctx, model); | ||
} catch (Throwable x) { | ||
ctx.sendError(x); | ||
return x; | ||
} | ||
}; | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Override | ||
Context render( | ||
Context ctx, | ||
@SuppressWarnings("rawtypes") Template template, | ||
Object model, | ||
ByteBufferedOutputStream stream) | ||
throws IOException { | ||
ctx.setResponseType(MediaType.html); | ||
template.write(model, stream); | ||
/* | ||
* Rocker used a byte buffer here BUT it just wraps the internal buffer in the stream | ||
* instead of copying. | ||
* | ||
* Which is good for performance but bad if the ctx.send call is not blocking aka | ||
* hand the buffer off to another thread. | ||
*/ | ||
ctx.send(stream.toBuffer()); | ||
return ctx; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.