1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.compression;
17
18 import com.aayushatharva.brotli4j.encoder.BrotliEncoderChannel;
19 import com.aayushatharva.brotli4j.encoder.Encoder;
20 import io.netty.buffer.ByteBuf;
21 import io.netty.buffer.Unpooled;
22 import io.netty.channel.Channel;
23 import io.netty.channel.ChannelFuture;
24 import io.netty.channel.ChannelHandler;
25 import io.netty.channel.ChannelHandlerContext;
26 import io.netty.channel.ChannelPromise;
27 import io.netty.handler.codec.MessageToByteEncoder;
28 import io.netty.util.AttributeKey;
29 import io.netty.util.ReferenceCountUtil;
30 import io.netty.util.internal.ObjectUtil;
31
32 import java.io.IOException;
33 import java.nio.ByteBuffer;
34 import java.nio.channels.ClosedChannelException;
35 import java.nio.channels.WritableByteChannel;
36
37
38
39
40
41
42 @ChannelHandler.Sharable
43 public final class BrotliEncoder extends MessageToByteEncoder<ByteBuf> {
44
45 private static final AttributeKey<Writer> ATTR = AttributeKey.valueOf("BrotliEncoderWriter");
46
47 private final Encoder.Parameters parameters;
48 private final boolean isSharable;
49 private Writer writer;
50
51
52
53
54
55 public BrotliEncoder() {
56 this(BrotliOptions.DEFAULT);
57 }
58
59
60
61
62
63
64
65 public BrotliEncoder(BrotliOptions brotliOptions) {
66 this(brotliOptions.parameters());
67 }
68
69
70
71
72
73
74
75 public BrotliEncoder(Encoder.Parameters parameters) {
76 this(parameters, true);
77 }
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 public BrotliEncoder(Encoder.Parameters parameters, boolean isSharable) {
96 super(ByteBuf.class);
97 this.parameters = ObjectUtil.checkNotNull(parameters, "Parameters");
98 this.isSharable = isSharable;
99 }
100
101 @Override
102 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
103 Writer writer = new Writer(parameters, ctx);
104 if (isSharable) {
105 ctx.channel().attr(ATTR).set(writer);
106 } else {
107 this.writer = writer;
108 }
109 super.handlerAdded(ctx);
110 }
111
112 @Override
113 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
114 finish(ctx);
115 super.handlerRemoved(ctx);
116 }
117
118 @Override
119 protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
120
121 }
122
123 @Override
124 protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception {
125 if (!msg.isReadable()) {
126 return Unpooled.EMPTY_BUFFER;
127 }
128
129 Writer writer;
130 if (isSharable) {
131 writer = ctx.channel().attr(ATTR).get();
132 } else {
133 writer = this.writer;
134 }
135
136
137 if (writer == null) {
138 return Unpooled.EMPTY_BUFFER;
139 } else {
140 writer.encode(msg, preferDirect);
141 return writer.writableBuffer;
142 }
143 }
144
145 @Override
146 public boolean isSharable() {
147 return isSharable;
148 }
149
150
151
152
153
154
155
156 public void finish(ChannelHandlerContext ctx) throws IOException {
157 finishEncode(ctx);
158 }
159
160 private ChannelFuture finishEncode(ChannelHandlerContext ctx) throws IOException {
161 Writer writer;
162
163 if (isSharable) {
164 writer = ctx.channel().attr(ATTR).getAndSet(null);
165 } else {
166 writer = this.writer;
167 }
168
169 if (writer != null) {
170 writer.close();
171 this.writer = null;
172 return writer.closeFuture;
173 }
174 return ctx.newSucceededFuture();
175 }
176
177 @Override
178 public void close(final ChannelHandlerContext ctx, final ChannelPromise promise) throws Exception {
179 ChannelFuture f = finishEncode(ctx);
180 EncoderUtil.closeAfterFinishEncode(ctx, f, promise);
181 }
182
183
184
185
186
187 private static final class Writer implements WritableByteChannel {
188
189 private ByteBuf writableBuffer;
190 private final BrotliEncoderChannel brotliEncoderChannel;
191 private final ChannelHandlerContext ctx;
192 private final ChannelPromise closeFuture;
193 private boolean closeInitiated;
194 private boolean isClosed;
195
196 private Writer(Encoder.Parameters parameters, ChannelHandlerContext ctx) throws IOException {
197 brotliEncoderChannel = new BrotliEncoderChannel(this, parameters);
198 this.ctx = ctx;
199 this.closeFuture = ctx.newPromise();
200 }
201
202 private void encode(ByteBuf msg, boolean preferDirect) throws Exception {
203 try {
204 allocate(preferDirect);
205
206
207
208
209
210
211
212
213 ByteBuffer nioBuffer = CompressionUtil.safeReadableNioBuffer(msg);
214 int position = nioBuffer.position();
215 brotliEncoderChannel.write(nioBuffer);
216 msg.skipBytes(nioBuffer.position() - position);
217 brotliEncoderChannel.flush();
218 } catch (Exception e) {
219 ReferenceCountUtil.release(msg);
220 throw e;
221 }
222 }
223
224 private void allocate(boolean preferDirect) {
225 if (preferDirect) {
226 writableBuffer = ctx.alloc().ioBuffer();
227 } else {
228 writableBuffer = ctx.alloc().buffer();
229 }
230 }
231
232 @Override
233 public int write(ByteBuffer src) throws IOException {
234 if (!isOpen()) {
235 throw new ClosedChannelException();
236 }
237
238 return writableBuffer.writeBytes(src).readableBytes();
239 }
240
241 @Override
242 public boolean isOpen() {
243 return !isClosed;
244 }
245
246 @Override
247 public void close() {
248 if (closeInitiated) {
249 return;
250 }
251 closeInitiated = true;
252 ctx.executor().execute(new Runnable() {
253 @Override
254 public void run() {
255 try {
256 finish(closeFuture);
257 } catch (IOException ex) {
258 closeFuture.setFailure(new IllegalStateException("Failed to finish encoding", ex));
259 }
260 }
261 });
262 }
263
264 public void finish(final ChannelPromise promise) throws IOException {
265 if (!isClosed) {
266
267 allocate(true);
268
269 try {
270 brotliEncoderChannel.close();
271 isClosed = true;
272 } catch (Exception ex) {
273 promise.setFailure(ex);
274
275
276
277 ReferenceCountUtil.release(writableBuffer);
278 return;
279 }
280
281 ctx.writeAndFlush(writableBuffer, promise);
282 }
283 }
284 }
285 }