Read commands

CommandReader #

In order to read and parse client command, defined an interface CommandReader, and an implementation ClientCommandReader, in the future there may be other kind of CommandReader, such as PeerCommandReader which reads requests from another service in the same cluster.

ByteWord #

This class represents a word in a command, a word is separated by whitespace or quoted by " or '.

To avoid allocating large memory again and again, the inside of ByteWord is an array of bytes or a list of byte arrays.

SingleBytesWord represet a word with one byte array.

    private static class SingleBytesWord extends ByteWord {
        private final byte[] word;

        public SingleBytesWord(byte[] word) {
            if (word == null || word.length == 0) {
                throw new IllegalArgumentException("given byte array is null or empty");
            }
            this.word = word;
        }

        @Override
        public int size() {
            return word.length;
        }

        @Override
        public byte[] get() {
            return word;
        }
    }

MultiBytesWord represents a word with multiple byte arrays.

    private static class MultiBytesWord extends ByteWord {
        private final List<byte[]> word;
        private int length;

        /**
         * Only initialized when needed.
         */
        private byte[] flattenedWord;

        public MultiBytesWord(List<byte[]> word) {
            if (word == null || word.size() == 0) {
                throw new IllegalArgumentException("given list is null or empty");
            }
            this.word = word;
            for (byte[] w : word) {
                length += w.length;
            }
        }

        @Override
        public int size() {
            return length;
        }

        @Override
        public byte[] get() {
            if (flattenedWord == null) {
                flattenedWord = new byte[length];
                int index = 0;
                for (byte[] w : word) {
                    System.arraycopy(w, 0, flattenedWord, index, w.length);
                    index += w.length;
                }
            }
            return flattenedWord;
        }
    }

ClientCommandReader #

Read commands from client, it will only slipt input byte array by whitespace( ), and support single/double quotes(', "). All are stored as byte[] , other than String

read(InputStream) method returns a list of ByteWord, but will not validate it.

  1. read all data from InputStream, the max command size is 512M for now.
    public List<ByteWord> read(InputStream stream) throws IOException {
        byte[] buf = new byte[bufSize];
        int count = 0;
        List<byte[]> bufList = new ArrayList<>();
        int totalLen = 0;
        while (count >= 0) {
            count = stream.read(buf);
            totalLen += count;
            if (totalLen > MAX_COMMAND_LENGTH) {
                throw new InvalidCommandException("command is too long");
            }
            if (count == buf.length) {
                bufList.add(buf);
                buf = new byte[bufSize];
            } else if (count > 0) {
                byte[] array = new byte[count];
                System.arraycopy(buf, 0, array, 0, count);
                bufList.add(array);
            }
        }
        return splitCommand(bufList);
    }
  1. then split the command by whitespace ' ':
    private List<ByteWord> splitCommand(List<byte[]> bufList) {
        List<ByteWord> words = new ArrayList<>();
        int curBufListIndex = 0;
        int curBufIndex = 0;
        while (curBufListIndex < bufList.size()) {
            Word word = readWithQuote(bufList, curBufListIndex, curBufIndex);
            curBufListIndex = word.bufListIndex;
            curBufIndex = word.bufIndex;
            if (word.hasWord()) {
                words.add(ByteWord.create(word.bytes));
            }
        }
        return words;
    }