All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.handler.codec.json.JsonObjectDecoder Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

The newest version!
/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package io.netty.handler.codec.json;

import static io.netty.util.internal.ObjectUtil.checkPositive;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.channel.ChannelPipeline;

import java.util.List;

/**
 * Splits a byte stream of JSON objects and arrays into individual objects/arrays and passes them up the
 * {@link ChannelPipeline}.
 * 

* The byte stream is expected to be in UTF-8 character encoding or ASCII. The current implementation * uses direct {@code byte} to {@code char} cast and then compares that {@code char} to a few low range * ASCII characters like {@code '{'}, {@code '['} or {@code '"'}. UTF-8 is not using low range [0..0x7F] * byte values for multibyte codepoint representations therefore fully supported by this implementation. *

* This class does not do any real parsing or validation. A sequence of bytes is considered a JSON object/array * if it contains a matching number of opening and closing braces/brackets. It's up to a subsequent * {@link ChannelHandler} to parse the JSON text into a more usable form i.e. a POJO. */ public class JsonObjectDecoder extends ByteToMessageDecoder { private static final int ST_CORRUPTED = -1; private static final int ST_INIT = 0; private static final int ST_DECODING_NORMAL = 1; private static final int ST_DECODING_ARRAY_STREAM = 2; private int openBraces; private int idx; private int lastReaderIndex; private int state; private boolean insideString; private final int maxObjectLength; private final boolean streamArrayElements; public JsonObjectDecoder() { // 1 MB this(1024 * 1024); } public JsonObjectDecoder(int maxObjectLength) { this(maxObjectLength, false); } public JsonObjectDecoder(boolean streamArrayElements) { this(1024 * 1024, streamArrayElements); } /** * @param maxObjectLength maximum number of bytes a JSON object/array may use (including braces and all). * Objects exceeding this length are dropped and an {@link TooLongFrameException} * is thrown. * @param streamArrayElements if set to true and the "top level" JSON object is an array, each of its entries * is passed through the pipeline individually and immediately after it was fully * received, allowing for arrays with "infinitely" many elements. * */ public JsonObjectDecoder(int maxObjectLength, boolean streamArrayElements) { this.maxObjectLength = checkPositive(maxObjectLength, "maxObjectLength"); this.streamArrayElements = streamArrayElements; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { if (state == ST_CORRUPTED) { in.skipBytes(in.readableBytes()); return; } if (this.idx > in.readerIndex() && lastReaderIndex != in.readerIndex()) { this.idx = in.readerIndex() + (idx - lastReaderIndex); } // index of next byte to process. int idx = this.idx; int wrtIdx = in.writerIndex(); if (wrtIdx > maxObjectLength) { // buffer size exceeded maxObjectLength; discarding the complete buffer. in.skipBytes(in.readableBytes()); reset(); throw new TooLongFrameException( "object length exceeds " + maxObjectLength + ": " + wrtIdx + " bytes discarded"); } for (/* use current idx */; idx < wrtIdx; idx++) { byte c = in.getByte(idx); if (state == ST_DECODING_NORMAL) { decodeByte(c, in, idx); // All opening braces/brackets have been closed. That's enough to conclude // that the JSON object/array is complete. if (openBraces == 0) { ByteBuf json = extractObject(ctx, in, in.readerIndex(), idx + 1 - in.readerIndex()); if (json != null) { out.add(json); } // The JSON object/array was extracted => discard the bytes from // the input buffer. in.readerIndex(idx + 1); // Reset the object state to get ready for the next JSON object/text // coming along the byte stream. reset(); } } else if (state == ST_DECODING_ARRAY_STREAM) { decodeByte(c, in, idx); if (!insideString && (openBraces == 1 && c == ',' || openBraces == 0 && c == ']')) { // skip leading spaces. No range check is needed and the loop will terminate // because the byte at position idx is not a whitespace. for (int i = in.readerIndex(); Character.isWhitespace(in.getByte(i)); i++) { in.skipBytes(1); } // skip trailing spaces. int idxNoSpaces = idx - 1; while (idxNoSpaces >= in.readerIndex() && Character.isWhitespace(in.getByte(idxNoSpaces))) { idxNoSpaces--; } ByteBuf json = extractObject(ctx, in, in.readerIndex(), idxNoSpaces + 1 - in.readerIndex()); if (json != null) { out.add(json); } in.readerIndex(idx + 1); if (c == ']') { reset(); } } // JSON object/array detected. Accumulate bytes until all braces/brackets are closed. } else if (c == '{' || c == '[') { initDecoding(c); if (state == ST_DECODING_ARRAY_STREAM) { // Discard the array bracket in.skipBytes(1); } // Discard leading spaces in front of a JSON object/array. } else if (Character.isWhitespace(c)) { in.skipBytes(1); } else { state = ST_CORRUPTED; throw new CorruptedFrameException( "invalid JSON received at byte position " + idx + ": " + ByteBufUtil.hexDump(in)); } } if (in.readableBytes() == 0) { this.idx = 0; } else { this.idx = idx; } this.lastReaderIndex = in.readerIndex(); } /** * Override this method if you want to filter the json objects/arrays that get passed through the pipeline. */ @SuppressWarnings("UnusedParameters") protected ByteBuf extractObject(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) { return buffer.retainedSlice(index, length); } private void decodeByte(byte c, ByteBuf in, int idx) { if ((c == '{' || c == '[') && !insideString) { openBraces++; } else if ((c == '}' || c == ']') && !insideString) { openBraces--; } else if (c == '"') { // start of a new JSON string. It's necessary to detect strings as they may // also contain braces/brackets and that could lead to incorrect results. if (!insideString) { insideString = true; } else { int backslashCount = 0; idx--; while (idx >= 0) { if (in.getByte(idx) == '\\') { backslashCount++; idx--; } else { break; } } // The double quote isn't escaped only if there are even "\"s. if (backslashCount % 2 == 0) { // Since the double quote isn't escaped then this is the end of a string. insideString = false; } } } } private void initDecoding(byte openingBrace) { openBraces = 1; if (openingBrace == '[' && streamArrayElements) { state = ST_DECODING_ARRAY_STREAM; } else { state = ST_DECODING_NORMAL; } } private void reset() { insideString = false; state = ST_INIT; openBraces = 0; } }