Add Json Protocol support
This commit is contained in:
Dino Kovač 2017-11-18 18:45:12 +01:00 коммит произвёл Ben Bader
Родитель 68175513c6
Коммит 4c5e96b38d
6 изменённых файлов: 1227 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,44 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.integration.conformance;
import com.microsoft.thrifty.protocol.JsonProtocol;
import com.microsoft.thrifty.protocol.Protocol;
import com.microsoft.thrifty.testing.ServerProtocol;
import com.microsoft.thrifty.testing.ServerTransport;
import com.microsoft.thrifty.transport.Transport;
public class JsonProtocolConformance extends ConformanceBase {
@Override
protected ServerTransport getServerTransport() {
return ServerTransport.BLOCKING;
}
@Override
protected ServerProtocol getServerProtocol() {
return ServerProtocol.JSON;
}
@Override
protected Protocol createProtocol(Transport transport) {
return new JsonProtocol(transport);
}
}

Просмотреть файл

@ -0,0 +1,51 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.integration.conformance;
import com.microsoft.thrifty.protocol.JsonProtocol;
import com.microsoft.thrifty.protocol.Protocol;
import com.microsoft.thrifty.testing.ServerProtocol;
import com.microsoft.thrifty.testing.ServerTransport;
import com.microsoft.thrifty.transport.FramedTransport;
import com.microsoft.thrifty.transport.Transport;
public class NonblockingJsonProtocolConformance extends ConformanceBase {
@Override
protected ServerTransport getServerTransport() {
return ServerTransport.NON_BLOCKING;
}
@Override
protected ServerProtocol getServerProtocol() {
return ServerProtocol.JSON;
}
@Override
protected Transport decorateTransport(Transport transport) {
// non-blocking servers require framing
return new FramedTransport(transport);
}
@Override
protected Protocol createProtocol(Transport transport) {
return new JsonProtocol(transport);
}
}

Просмотреть файл

@ -0,0 +1,934 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
/*
* This file is derived from the file TCompactProtocol.java, in the Apache
* Thrift implementation. The original license header is reproduced
* below:
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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
*
* http://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 com.microsoft.thrifty.protocol;
import com.microsoft.thrifty.TType;
import com.microsoft.thrifty.transport.Transport;
import okio.Buffer;
import okio.ByteString;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Stack;
/**
* Json protocol implementation for thrift.
* <p>
* This is a full-featured protocol supporting write and read.
*/
public class JsonProtocol extends Protocol {
private static final byte[] COMMA = new byte[]{','};
private static final byte[] COLON = new byte[]{':'};
private static final byte[] LBRACE = new byte[]{'{'};
private static final byte[] RBRACE = new byte[]{'}'};
private static final byte[] LBRACKET = new byte[]{'['};
private static final byte[] RBRACKET = new byte[]{']'};
private static final byte[] QUOTE = new byte[]{'"'};
private static final byte[] BACKSLASH = new byte[]{'\\'};
private static final byte[] ESCSEQ = new byte[]{'\\', 'u', '0', '0'};
private static final long VERSION = 1;
private static final byte[] JSON_CHAR_TABLE = {
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
1, 1, '"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
};
private static final String ESCAPE_CHARS = "\"\\/bfnrt";
private static final byte[] ESCAPE_CHAR_VALS = {
'"', '\\', '/', '\b', '\f', '\n', '\r', '\t',
};
// Stack of nested contexts that we may be in
private Stack<JsonBaseContext> contextStack = new Stack<>();
// Current context that we are in
private JsonBaseContext context = new JsonBaseContext();
// Reader that manages a 1-byte buffer
private LookaheadReader reader = new LookaheadReader();
// Write out the TField names as a string instead of the default integer value
private boolean fieldNamesAsString = false;
// Push a new Json context onto the stack.
private void pushContext(JsonBaseContext c) {
contextStack.push(context);
context = c;
}
// Pop the last Json context off the stack
private void popContext() {
context = contextStack.pop();
}
// Reset the context stack to its initial state
private void resetContext() {
while (!contextStack.isEmpty()) {
popContext();
}
}
public JsonProtocol(Transport transport) {
super(transport);
}
public JsonProtocol(Transport transport, boolean fieldNamesAsString) {
super(transport);
this.fieldNamesAsString = fieldNamesAsString;
}
@Override
public void reset() {
contextStack.clear();
context = new JsonBaseContext();
reader = new LookaheadReader();
}
// Temporary buffer used by several methods
private byte[] tmpbuf = new byte[4];
// Read a byte that must match b[0]; otherwise an exception is thrown.
// Marked protected to avoid synthetic accessor in JsonListContext.read
// and JsonPairContext.read
protected void readJsonSyntaxChar(byte[] b) throws IOException {
byte ch = reader.read();
if (ch != b[0]) {
throw new ProtocolException("Unexpected character:" + (char) ch);
}
}
// Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its
// corresponding hex value
private static byte hexVal(byte ch) throws IOException {
if ((ch >= '0') && (ch <= '9')) {
return (byte) ((char) ch - '0');
} else if ((ch >= 'a') && (ch <= 'f')) {
return (byte) ((char) ch - 'a' + 10);
} else {
throw new ProtocolException("Expected hex character");
}
}
// Convert a byte containing a hex value to its corresponding hex character
private static byte hexChar(byte val) {
val = (byte) (val & 0x0F);
if (val < 10) {
return (byte) ((char) val + '0');
} else {
return (byte) ((char) (val - 10) + 'a');
}
}
// Write the bytes in array buf as a Json characters, escaping as needed
private void writeJsonString(byte[] b) throws IOException {
context.write();
transport.write(QUOTE);
int len = b.length;
for (int i = 0; i < len; i++) {
if ((b[i] & 0x00FF) >= 0x30) {
if (b[i] == BACKSLASH[0]) {
transport.write(BACKSLASH);
transport.write(BACKSLASH);
} else {
transport.write(b, i, 1);
}
} else {
tmpbuf[0] = JSON_CHAR_TABLE[b[i]];
if (tmpbuf[0] == 1) {
transport.write(b, i, 1);
} else if (tmpbuf[0] > 1) {
transport.write(BACKSLASH);
transport.write(tmpbuf, 0, 1);
} else {
transport.write(ESCSEQ);
tmpbuf[0] = hexChar((byte) (b[i] >> 4));
tmpbuf[1] = hexChar(b[i]);
transport.write(tmpbuf, 0, 2);
}
}
}
transport.write(QUOTE);
}
// Write out number as a Json value. If the context dictates so, it will be
// wrapped in quotes to output as a Json string.
private void writeJsonInteger(long num) throws IOException {
context.write();
String str = Long.toString(num);
boolean escapeNum = context.escapeNum();
if (escapeNum) {
transport.write(QUOTE);
}
try {
byte[] buf = str.getBytes("UTF-8");
transport.write(buf);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
if (escapeNum) {
transport.write(QUOTE);
}
}
// Write out a double as a Json value. If it is NaN or infinity or if the
// context dictates escaping, write out as Json string.
private void writeJsonDouble(double num) throws IOException {
context.write();
String str = Double.toString(num);
boolean special = false;
switch (str.charAt(0)) {
case 'N': // NaN
case 'I': // Infinity
special = true;
break;
case '-':
if (str.charAt(1) == 'I') { // -Infinity
special = true;
}
break;
default:
break;
}
boolean escapeNum = special || context.escapeNum();
if (escapeNum) {
transport.write(QUOTE);
}
try {
byte[] b = str.getBytes("UTF-8");
transport.write(b, 0, b.length);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
if (escapeNum) {
transport.write(QUOTE);
}
}
private void writeJsonObjectStart() throws IOException {
context.write();
transport.write(LBRACE);
pushContext(new JsonPairContext());
}
private void writeJsonObjectEnd() throws IOException {
popContext();
transport.write(RBRACE);
}
private void writeJsonArrayStart() throws IOException {
context.write();
transport.write(LBRACKET);
pushContext(new JsonListContext());
}
private void writeJsonArrayEnd() throws IOException {
popContext();
transport.write(RBRACKET);
}
@Override
public void writeMessageBegin(String name, byte typeId, int seqId) throws IOException {
resetContext(); // THRIFT-3743
writeJsonArrayStart();
writeJsonInteger(VERSION);
try {
byte[] b = name.getBytes("UTF-8");
writeJsonString(b);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
writeJsonInteger(typeId);
writeJsonInteger(seqId);
}
@Override
public void writeMessageEnd() throws IOException {
writeJsonArrayEnd();
}
@Override
public void writeStructBegin(String structName) throws IOException {
writeJsonObjectStart();
}
@Override
public void writeStructEnd() throws IOException {
writeJsonObjectEnd();
}
@Override
public void writeFieldBegin(String fieldName, int fieldId, byte typeId) throws IOException {
if (fieldNamesAsString) {
writeString(fieldName);
} else {
writeJsonInteger(fieldId);
}
writeJsonObjectStart();
writeJsonString(JsonTypes.ttypeToJson(typeId));
}
@Override
public void writeFieldEnd() throws IOException {
writeJsonObjectEnd();
}
@Override
public void writeFieldStop() {
}
@Override
public void writeMapBegin(byte keyTypeId, byte valueTypeId, int mapSize) throws IOException {
writeJsonArrayStart();
writeJsonString(JsonTypes.ttypeToJson(keyTypeId));
writeJsonString(JsonTypes.ttypeToJson(valueTypeId));
writeJsonInteger(mapSize);
writeJsonObjectStart();
}
@Override
public void writeMapEnd() throws IOException {
writeJsonObjectEnd();
writeJsonArrayEnd();
}
@Override
public void writeListBegin(byte elementTypeId, int listSize) throws IOException {
writeJsonArrayStart();
writeJsonString(JsonTypes.ttypeToJson(elementTypeId));
writeJsonInteger(listSize);
}
@Override
public void writeListEnd() throws IOException {
writeJsonArrayEnd();
}
@Override
public void writeSetBegin(byte elementTypeId, int setSize) throws IOException {
writeJsonArrayStart();
writeJsonString(JsonTypes.ttypeToJson(elementTypeId));
writeJsonInteger(setSize);
}
@Override
public void writeSetEnd() throws IOException {
writeJsonArrayEnd();
}
@Override
public void writeBool(boolean b) throws IOException {
writeJsonInteger(b ? (long) 1 : (long) 0);
}
@Override
public void writeByte(byte b) throws IOException {
writeJsonInteger((long) b);
}
@Override
public void writeI16(short i16) throws IOException {
writeJsonInteger((long) i16);
}
@Override
public void writeI32(int i32) throws IOException {
writeJsonInteger((long) i32);
}
@Override
public void writeI64(long i64) throws IOException {
writeJsonInteger(i64);
}
@Override
public void writeDouble(double dub) throws IOException {
writeJsonDouble(dub);
}
@Override
public void writeString(String str) throws IOException {
try {
byte[] b = str.getBytes("UTF-8");
writeJsonString(b);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
@Override
public void writeBinary(ByteString buf) throws IOException {
writeString(buf.base64());
}
/**
* Reading methods.
*/
// Read in a Json string, unescaping as appropriate.. Skip reading from the
// context if skipContext is true.
private ByteString readJsonString(boolean skipContext)
throws IOException {
Buffer buffer = new Buffer();
ArrayList<Character> codeunits = new ArrayList<>();
if (!skipContext) {
context.read();
}
readJsonSyntaxChar(QUOTE);
while (true) {
byte ch = reader.read();
if (ch == QUOTE[0]) {
break;
}
if (ch == ESCSEQ[0]) {
ch = reader.read();
if (ch == ESCSEQ[1]) {
transport.read(tmpbuf, 0, 4);
short cu = (short) (
((short) hexVal(tmpbuf[0]) << 12)
+ ((short) hexVal(tmpbuf[1]) << 8)
+ ((short) hexVal(tmpbuf[2]) << 4)
+ (short) hexVal(tmpbuf[3]));
try {
if (Character.isHighSurrogate((char) cu)) {
if (codeunits.size() > 0) {
throw new ProtocolException("Expected low surrogate char");
}
codeunits.add((char) cu);
} else if (Character.isLowSurrogate((char) cu)) {
if (codeunits.size() == 0) {
throw new ProtocolException("Expected high surrogate char");
}
codeunits.add((char) cu);
buffer.write((new String(new int[]{codeunits.get(0), codeunits.get(1)}, 0, 2))
.getBytes("UTF-8"));
codeunits.clear();
} else {
buffer.write((new String(new int[]{cu}, 0, 1)).getBytes("UTF-8"));
}
continue;
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
} catch (IOException ex) {
throw new ProtocolException("Invalid unicode sequence");
}
} else {
int off = ESCAPE_CHARS.indexOf(ch);
if (off == -1) {
throw new ProtocolException("Expected control char");
}
ch = ESCAPE_CHAR_VALS[off];
}
}
buffer.write(new byte[]{ch});
}
return buffer.readByteString();
}
// Return true if the given byte could be a valid part of a Json number.
private boolean isJsonNumeric(byte b) {
switch (b) {
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'E':
case 'e':
return true;
}
return false;
}
// Read in a sequence of characters that are all valid in Json numbers. Does
// not do a complete regex check to validate that this is actually a number.
private String readJsonNumericChars() throws IOException {
StringBuilder strbld = new StringBuilder();
while (true) {
byte ch = reader.peek();
if (!isJsonNumeric(ch)) {
break;
}
strbld.append((char) reader.read());
}
return strbld.toString();
}
// Read in a Json number. If the context dictates, read in enclosing quotes.
private long readJsonInteger() throws IOException {
context.read();
if (context.escapeNum()) {
readJsonSyntaxChar(QUOTE);
}
String str = readJsonNumericChars();
if (context.escapeNum()) {
readJsonSyntaxChar(QUOTE);
}
try {
return Long.valueOf(str);
} catch (NumberFormatException ex) {
throw new ProtocolException("Bad data encountered in numeric data");
}
}
// Read in a Json double value. Throw if the value is not wrapped in quotes
// when expected or if wrapped in quotes when not expected.
private double readJsonDouble() throws IOException {
context.read();
if (reader.peek() == QUOTE[0]) {
ByteString str = readJsonString(true);
double dub = Double.valueOf(str.utf8());
if (!context.escapeNum() && !Double.isNaN(dub)
&& !Double.isInfinite(dub)) {
// Throw exception -- we should not be in a string in this case
throw new ProtocolException("Numeric data unexpectedly quoted");
}
return dub;
} else {
if (context.escapeNum()) {
// This will throw - we should have had a quote if escapeNum == true
readJsonSyntaxChar(QUOTE);
}
try {
return Double.valueOf(readJsonNumericChars());
} catch (NumberFormatException ex) {
throw new ProtocolException("Bad data encountered in numeric data");
}
}
}
// Read in a Json string containing base-64 encoded data and decode it.
private ByteString readJsonBase64() throws IOException {
ByteString str = readJsonString(false);
return ByteString.decodeBase64(str.utf8());
}
private void readJsonObjectStart() throws IOException {
context.read();
readJsonSyntaxChar(LBRACE);
pushContext(new JsonPairContext());
}
private void readJsonObjectEnd() throws IOException {
readJsonSyntaxChar(RBRACE);
popContext();
}
private void readJsonArrayStart() throws IOException {
context.read();
readJsonSyntaxChar(LBRACKET);
pushContext(new JsonListContext());
}
private void readJsonArrayEnd() throws IOException {
readJsonSyntaxChar(RBRACKET);
popContext();
}
@Override
public MessageMetadata readMessageBegin() throws IOException {
resetContext(); // THRIFT-3743
readJsonArrayStart();
if (readJsonInteger() != VERSION) {
throw new ProtocolException("Message contained bad version.");
}
String name;
try {
name = readJsonString(false).utf8();
} catch (UnsupportedEncodingException ex) {
throw new AssertionError(ex);
}
byte type = (byte) readJsonInteger();
int seqid = (int) readJsonInteger();
return new MessageMetadata(name, type, seqid);
}
@Override
public void readMessageEnd() throws IOException {
readJsonArrayEnd();
}
@Override
public StructMetadata readStructBegin() throws IOException {
readJsonObjectStart();
return new StructMetadata("");
}
@Override
public void readStructEnd() throws IOException {
readJsonObjectEnd();
}
@Override
public FieldMetadata readFieldBegin() throws IOException {
byte ch = reader.peek();
byte type;
short id = 0;
if (ch == RBRACE[0]) {
type = TType.STOP;
} else {
id = (short) readJsonInteger();
readJsonObjectStart();
type = JsonTypes.jsonToTtype(readJsonString(false).toByteArray());
}
return new FieldMetadata("", type, id);
}
@Override
public void readFieldEnd() throws IOException {
readJsonObjectEnd();
}
@Override
public MapMetadata readMapBegin() throws IOException {
readJsonArrayStart();
byte keyType = JsonTypes.jsonToTtype(readJsonString(false).toByteArray());
byte valueType = JsonTypes.jsonToTtype(readJsonString(false).toByteArray());
int size = (int) readJsonInteger();
readJsonObjectStart();
return new MapMetadata(keyType, valueType, size);
}
@Override
public void readMapEnd() throws IOException {
readJsonObjectEnd();
readJsonArrayEnd();
}
@Override
public ListMetadata readListBegin() throws IOException {
readJsonArrayStart();
byte elemType = JsonTypes.jsonToTtype(readJsonString(false).toByteArray());
int size = (int) readJsonInteger();
return new ListMetadata(elemType, size);
}
@Override
public void readListEnd() throws IOException {
readJsonArrayEnd();
}
@Override
public SetMetadata readSetBegin() throws IOException {
readJsonArrayStart();
byte elemType = JsonTypes.jsonToTtype(readJsonString(false).toByteArray());
int size = (int) readJsonInteger();
return new SetMetadata(elemType, size);
}
@Override
public void readSetEnd() throws IOException {
readJsonArrayEnd();
}
@Override
public boolean readBool() throws IOException {
return (readJsonInteger() == 0 ? false : true);
}
@Override
public byte readByte() throws IOException {
return (byte) readJsonInteger();
}
@Override
public short readI16() throws IOException {
return (short) readJsonInteger();
}
@Override
public int readI32() throws IOException {
return (int) readJsonInteger();
}
@Override
public long readI64() throws IOException {
return readJsonInteger();
}
@Override
public double readDouble() throws IOException {
return readJsonDouble();
}
@Override
public String readString() throws IOException {
return readJsonString(false).utf8();
}
@Override
public ByteString readBinary() throws IOException {
return readJsonBase64();
}
// Holds up to one byte from the transport
protected class LookaheadReader {
private boolean hasData;
private byte[] data = new byte[1];
// Return and consume the next byte to be read, either taking it from the
// data buffer if present or getting it from the transport otherwise.
protected byte read() throws IOException {
if (hasData) {
hasData = false;
} else {
transport.read(data, 0, 1);
}
return data[0];
}
// Return the next byte to be read without consuming, filling the data
// buffer if it has not been filled already.
protected byte peek() throws IOException {
if (!hasData) {
transport.read(data, 0, 1);
}
hasData = true;
return data[0];
}
}
private static final class JsonTypes {
static final byte[] BOOLEAN = new byte[]{'t', 'f'};
static final byte[] BYTE = new byte[]{'i', '8'};
static final byte[] I16 = new byte[]{'i', '1', '6'};
static final byte[] I32 = new byte[]{'i', '3', '2'};
static final byte[] I64 = new byte[]{'i', '6', '4'};
static final byte[] DOUBLE = new byte[]{'d', 'b', 'l'};
static final byte[] STRUCT = new byte[]{'r', 'e', 'c'};
static final byte[] STRING = new byte[]{'s', 't', 'r'};
static final byte[] MAP = new byte[]{'m', 'a', 'p'};
static final byte[] LIST = new byte[]{'l', 's', 't'};
static final byte[] SET = new byte[]{'s', 'e', 't'};
static byte[] ttypeToJson(byte typeId) {
switch (typeId) {
case TType.STOP:
throw new IllegalArgumentException("Unexpected STOP type");
case TType.VOID:
throw new IllegalArgumentException("Unexpected VOID type");
case TType.BOOL:
return BOOLEAN;
case TType.BYTE:
return BYTE;
case TType.DOUBLE:
return DOUBLE;
case TType.I16:
return I16;
case TType.I32:
return I32;
case TType.I64:
return I64;
case TType.STRING:
return STRING;
case TType.STRUCT:
return STRUCT;
case TType.MAP:
return MAP;
case TType.SET:
return SET;
case TType.LIST:
return LIST;
case TType.ENUM:
return I32;
default:
throw new IllegalArgumentException(
"Unknown TType ID: " + typeId);
}
}
static byte jsonToTtype(byte[] jsonId) {
byte result = TType.STOP;
if (jsonId.length > 1) {
switch (jsonId[0]) {
case 'd':
result = TType.DOUBLE;
break;
case 'i':
switch (jsonId[1]) {
case '8':
result = TType.BYTE;
break;
case '1':
result = TType.I16;
break;
case '3':
result = TType.I32;
break;
case '6':
result = TType.I64;
break;
}
break;
case 'l':
result = TType.LIST;
break;
case 'm':
result = TType.MAP;
break;
case 'r':
result = TType.STRUCT;
break;
case 's':
if (jsonId[1] == 't') {
result = TType.STRING;
} else {
result = TType.SET;
}
break;
case 't':
result = TType.BOOL;
break;
}
}
if (result == TType.STOP) {
throw new IllegalArgumentException(
"Unknown json type ID: " + Arrays.toString(jsonId));
}
return result;
}
private JsonTypes() {
throw new AssertionError("no instances");
}
}
// Base class for tracking Json contexts that may require inserting/reading
// additional Json syntax characters
// This base context does nothing.
protected static class JsonBaseContext {
protected void write() throws IOException {
}
protected void read() throws IOException {
}
protected boolean escapeNum() {
return false;
}
}
// Context for Json lists. Will insert/read commas before each item except
// for the first one
protected class JsonListContext extends JsonBaseContext {
private boolean first = true;
@Override
protected void write() throws IOException {
if (first) {
first = false;
} else {
transport.write(COMMA);
}
}
@Override
protected void read() throws IOException {
if (first) {
first = false;
} else {
readJsonSyntaxChar(COMMA);
}
}
}
// Context for Json records. Will insert/read colons before the value portion
// of each record pair, and commas before each key except the first. In
// addition, will indicate that numbers in the key position need to be
// escaped in quotes (since Json keys must be strings).
protected class JsonPairContext extends JsonBaseContext {
private boolean first = true;
private boolean colon = true;
@Override
protected void write() throws IOException {
if (first) {
first = false;
colon = true;
} else {
transport.write(colon ? COLON : COMMA);
colon = !colon;
}
}
@Override
protected void read() throws IOException {
if (first) {
first = false;
colon = true;
} else {
readJsonSyntaxChar(colon ? COLON : COMMA);
colon = !colon;
}
}
@Override
protected boolean escapeNum() {
return colon;
}
}
}

Просмотреть файл

@ -0,0 +1,195 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.protocol;
import com.microsoft.thrifty.TType;
import com.microsoft.thrifty.transport.BufferTransport;
import okio.Buffer;
import okio.ByteString;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
public class JsonProtocolTest {
private Buffer buffer;
private JsonProtocol protocol;
@Before
public void setup() {
buffer = new Buffer();
BufferTransport transport = new BufferTransport(buffer);
protocol = new JsonProtocol(transport);
}
@Test
public void emptyJsonString() throws Exception {
protocol.writeString("");
assertThat(buffer.readUtf8()).isEqualTo("\"\"");
}
@Test
public void escapesNamedControlChars() throws Exception {
protocol.writeString("\b\f\r\n\t");
assertThat(buffer.readUtf8()).isEqualTo("\"\\b\\f\\r\\n\\t\"");
}
@Test
public void escapesQuotes() throws Exception {
protocol.writeString("\"");
assertThat(buffer.readUtf8()).isEqualTo("\"\\\"\""); // or, in other words, "\""
}
@Test
public void normalStringIsQuoted() throws Exception {
protocol.writeString("y u no quote me?");
assertThat(buffer.readUtf8()).isEqualTo("\"y u no quote me?\"");
}
@Test
public void emptyList() throws Exception {
protocol.writeListBegin(TType.STRING, 0);
protocol.writeListEnd();
assertThat(buffer.readUtf8()).isEqualTo("[\"str\",0]");
}
@Test
public void listWithOneElement() throws Exception {
protocol.writeListBegin(TType.STRING, 1);
protocol.writeString("foo");
protocol.writeListEnd();
assertThat(buffer.readUtf8()).isEqualTo("[\"str\",1,\"foo\"]");
}
@Test
public void listWithTwoElements() throws Exception {
protocol.writeListBegin(TType.STRING, 2);
protocol.writeString("foo");
protocol.writeString("bar");
protocol.writeListEnd();
assertThat(buffer.readUtf8()).isEqualTo("[\"str\",2,\"foo\",\"bar\"]");
}
@Test
public void emptyMap() throws Exception {
protocol.writeMapBegin(TType.STRING, TType.I32, 0);
protocol.writeMapEnd();
assertThat(buffer.readUtf8()).isEqualTo("[\"str\",\"i32\",0,{}]");
}
@Test
public void mapWithSingleElement() throws Exception {
protocol.writeMapBegin(TType.STRING, TType.I32, 1);
protocol.writeString("key1");
protocol.writeI32(1);
protocol.writeMapEnd();
assertThat(buffer.readUtf8()).isEqualTo("[\"str\",\"i32\",1,{\"key1\":1}]");
}
@Test
public void mapWithTwoElements() throws Exception {
protocol.writeMapBegin(TType.STRING, TType.I32, 2);
protocol.writeString("key1");
protocol.writeI32(1);
protocol.writeString("key2");
protocol.writeI32(2);
protocol.writeMapEnd();
assertThat(buffer.readUtf8()).isEqualTo("[\"str\",\"i32\",2,{\"key1\":1,\"key2\":2}]");
}
@Test
public void listOfMaps() throws Exception {
protocol.writeListBegin(TType.MAP, 2);
protocol.writeMapBegin(TType.STRING, TType.I32, 1);
protocol.writeString("1");
protocol.writeI32(10);
protocol.writeMapEnd();
protocol.writeMapBegin(TType.STRING, TType.I32, 1);
protocol.writeString("2");
protocol.writeI32(20);
protocol.writeMapEnd();
protocol.writeListEnd();
assertThat(buffer.readUtf8()).isEqualTo("[\"map\",2,[\"str\",\"i32\",1,{\"1\":10}],[\"str\",\"i32\",1,{\"2\":20}]]");
}
@Test
public void structs() throws Exception {
Xtruct xtruct = new Xtruct.Builder()
.byte_thing((byte) 1)
.double_thing(2.0)
.i32_thing(3)
.i64_thing(4L)
.string_thing("five")
.build();
Xtruct.ADAPTER.write(protocol, xtruct);
assertThat(buffer.readUtf8()).isEqualTo("" +
"{" +
"\"1\":{\"str\":\"five\"}," +
"\"4\":{\"i8\":1}," +
"\"9\":{\"i32\":3}," +
"\"11\":{\"i64\":4}," +
"\"13\":{\"dbl\":2.0}" +
"}");
}
@Test
public void binary() throws Exception {
protocol.writeBinary(ByteString.encodeUtf8("foobar"));
assertThat(buffer.readUtf8()).isEqualTo("\"Zm9vYmFy\"");
}
@Test
public void roundtrip() throws Exception {
Xtruct xtruct = new Xtruct.Builder()
.byte_thing((byte) 254)
.i32_thing(0xFFFF)
.i64_thing(0xFFFFFFFFL)
.string_thing("foo")
.double_thing(Math.PI)
.bool_thing(true)
.build();
Buffer buffer = new Buffer();
BufferTransport transport = new BufferTransport(buffer);
JsonProtocol proto = new JsonProtocol(transport);
Xtruct.ADAPTER.write(proto, xtruct);
Xtruct read = Xtruct.ADAPTER.read(new JsonProtocol(transport));
Assert.assertThat(read, equalTo(xtruct));
}
}

Просмотреть файл

@ -23,4 +23,5 @@ package com.microsoft.thrifty.testing;
public enum ServerProtocol {
BINARY,
COMPACT,
JSON
}

Просмотреть файл

@ -24,6 +24,7 @@ import com.microsoft.thrifty.test.gen.ThriftTest;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TJSONProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
@ -162,6 +163,7 @@ public class TestServer implements TestRule {
switch (protocol) {
case BINARY: return new TBinaryProtocol.Factory();
case COMPACT: return new TCompactProtocol.Factory();
case JSON: return new TJSONProtocol.Factory();
default:
throw new AssertionError("Invalid protocol value: " + protocol);
}