You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@thrift.apache.org by jf...@apache.org on 2012/03/22 22:49:13 UTC
svn commit: r1304085 [5/10] - in /thrift/trunk: ./ aclocal/ compiler/cpp/
compiler/cpp/src/generate/ lib/ lib/d/ lib/d/src/ lib/d/src/thrift/
lib/d/src/thrift/async/ lib/d/src/thrift/codegen/
lib/d/src/thrift/internal/ lib/d/src/thrift/internal/test/ l...
Added: thrift/trunk/lib/d/src/thrift/protocol/base.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/protocol/base.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/protocol/base.d (added)
+++ thrift/trunk/lib/d/src/thrift/protocol/base.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,436 @@
+/*
+ * 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.
+ */
+
+/**
+ * Defines the basic interface for a Thrift protocol and associated exception
+ * types.
+ *
+ * Most parts of the protocol API are typically not used in client code, as
+ * the actual serialization code is generated by thrift.codegen.* â the only
+ * interesting thing usually is that there are protocols which can be created
+ * from transports and passed around.
+ */
+module thrift.protocol.base;
+
+import thrift.base;
+import thrift.transport.base;
+
+/**
+ * The field types Thrift protocols support.
+ */
+enum TType : byte {
+ STOP = 0, /// Used to mark the end of a sequence of fields.
+ VOID = 1, ///
+ BOOL = 2, ///
+ BYTE = 3, ///
+ DOUBLE = 4, ///
+ I16 = 6, ///
+ I32 = 8, ///
+ I64 = 10, ///
+ STRING = 11, ///
+ STRUCT = 12, ///
+ MAP = 13, ///
+ SET = 14, ///
+ LIST = 15 ///
+}
+
+/**
+ * Types of Thrift RPC messages.
+ */
+enum TMessageType : byte {
+ CALL = 1, /// Call of a normal, two-way RPC method.
+ REPLY = 2, /// Reply to a normal method call.
+ EXCEPTION = 3, /// Reply to a method call if target raised a TApplicationException.
+ ONEWAY = 4 /// Call of a one-way RPC method which is not followed by a reply.
+}
+
+/**
+ * Descriptions of Thrift entities.
+ */
+struct TField {
+ string name;
+ TType type;
+ short id;
+}
+
+/// ditto
+struct TList {
+ TType elemType;
+ size_t size;
+}
+
+/// ditto
+struct TMap {
+ TType keyType;
+ TType valueType;
+ size_t size;
+}
+
+/// ditto
+struct TMessage {
+ string name;
+ TMessageType type;
+ int seqid;
+}
+
+/// ditto
+struct TSet {
+ TType elemType;
+ size_t size;
+}
+
+/// ditto
+struct TStruct {
+ string name;
+}
+
+/**
+ * Interface for a Thrift protocol implementation. Essentially, it defines
+ * a way of reading and writing all the base types, plus a mechanism for
+ * writing out structs with indexed fields.
+ *
+ * TProtocol objects should not be shared across multiple encoding contexts,
+ * as they may need to maintain internal state in some protocols (e.g. JSON).
+ * Note that is is acceptable for the TProtocol module to do its own internal
+ * buffered reads/writes to the underlying TTransport where appropriate (i.e.
+ * when parsing an input XML stream, reading could be batched rather than
+ * looking ahead character by character for a close tag).
+ */
+interface TProtocol {
+ /// The underlying transport used by the protocol.
+ TTransport transport() @property;
+
+ /*
+ * Writing methods.
+ */
+
+ void writeBool(bool b); ///
+ void writeByte(byte b); ///
+ void writeI16(short i16); ///
+ void writeI32(int i32); ///
+ void writeI64(long i64); ///
+ void writeDouble(double dub); ///
+ void writeString(string str); ///
+ void writeBinary(ubyte[] buf); ///
+
+ void writeMessageBegin(TMessage message); ///
+ void writeMessageEnd(); ///
+ void writeStructBegin(TStruct tstruct); ///
+ void writeStructEnd(); ///
+ void writeFieldBegin(TField field); ///
+ void writeFieldEnd(); ///
+ void writeFieldStop(); ///
+ void writeListBegin(TList list); ///
+ void writeListEnd(); ///
+ void writeMapBegin(TMap map); ///
+ void writeMapEnd(); ///
+ void writeSetBegin(TSet set); ///
+ void writeSetEnd(); ///
+
+ /*
+ * Reading methods.
+ */
+
+ bool readBool(); ///
+ byte readByte(); ///
+ short readI16(); ///
+ int readI32(); ///
+ long readI64(); ///
+ double readDouble(); ///
+ string readString(); ///
+ ubyte[] readBinary(); ///
+
+ TMessage readMessageBegin(); ///
+ void readMessageEnd(); ///
+ TStruct readStructBegin(); ///
+ void readStructEnd(); ///
+ TField readFieldBegin(); ///
+ void readFieldEnd(); ///
+ TList readListBegin(); ///
+ void readListEnd(); ///
+ TMap readMapBegin(); ///
+ void readMapEnd(); ///
+ TSet readSetBegin(); ///
+ void readSetEnd(); ///
+
+ /**
+ * Reset any internal state back to a blank slate, if the protocol is
+ * stateful.
+ */
+ void reset();
+}
+
+/**
+ * true if T is a TProtocol.
+ */
+template isTProtocol(T) {
+ enum isTProtocol = is(T : TProtocol);
+}
+
+unittest {
+ static assert(isTProtocol!TProtocol);
+ static assert(!isTProtocol!void);
+}
+
+/**
+ * Creates a protocol operating on a given transport.
+ */
+interface TProtocolFactory {
+ ///
+ TProtocol getProtocol(TTransport trans);
+}
+
+/**
+ * A protocol-level exception.
+ */
+class TProtocolException : TException {
+ /// The possible exception types.
+ enum Type {
+ UNKNOWN, ///
+ INVALID_DATA, ///
+ NEGATIVE_SIZE, ///
+ SIZE_LIMIT, ///
+ BAD_VERSION, ///
+ NOT_IMPLEMENTED ///
+ }
+
+ ///
+ this(Type type, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
+ static string msgForType(Type type) {
+ switch (type) {
+ case Type.UNKNOWN: return "Unknown protocol exception";
+ case Type.INVALID_DATA: return "Invalid data";
+ case Type.NEGATIVE_SIZE: return "Negative size";
+ case Type.SIZE_LIMIT: return "Exceeded size limit";
+ case Type.BAD_VERSION: return "Invalid version";
+ case Type.NOT_IMPLEMENTED: return "Not implemented";
+ default: return "(Invalid exception type)";
+ }
+ }
+ this(msgForType(type), type, file, line, next);
+ }
+
+ ///
+ this(string msg, string file = __FILE__, size_t line = __LINE__,
+ Throwable next = null)
+ {
+ this(msg, Type.UNKNOWN, file, line, next);
+ }
+
+ ///
+ this(string msg, Type type, string file = __FILE__, size_t line = __LINE__,
+ Throwable next = null)
+ {
+ super(msg, file, line, next);
+ type_ = type;
+ }
+
+ ///
+ Type type() const @property {
+ return type_;
+ }
+
+protected:
+ Type type_;
+}
+
+/**
+ * Skips a field of the given type on the protocol.
+ *
+ * The main purpose of skip() is to allow treating struct and cotainer types,
+ * (where multiple primitive types have to be skipped) the same as scalar types
+ * in generated code.
+ */
+void skip(Protocol)(Protocol prot, TType type) if (is(Protocol : TProtocol)) {
+ final switch (type) {
+ case TType.BOOL:
+ prot.readBool();
+ break;
+
+ case TType.BYTE:
+ prot.readByte();
+ break;
+
+ case TType.I16:
+ prot.readI16();
+ break;
+
+ case TType.I32:
+ prot.readI32();
+ break;
+
+ case TType.I64:
+ prot.readI64();
+ break;
+
+ case TType.DOUBLE:
+ prot.readDouble();
+ break;
+
+ case TType.STRING:
+ prot.readBinary();
+ break;
+
+ case TType.STRUCT:
+ prot.readStructBegin();
+ while (true) {
+ auto f = prot.readFieldBegin();
+ if (f.type == TType.STOP) break;
+ skip(prot, f.type);
+ prot.readFieldEnd();
+ }
+ prot.readStructEnd();
+ break;
+
+ case TType.LIST:
+ auto l = prot.readListBegin();
+ foreach (i; 0 .. l.size) {
+ skip(prot, l.elemType);
+ }
+ prot.readListEnd();
+ break;
+
+ case TType.MAP:
+ auto m = prot.readMapBegin();
+ foreach (i; 0 .. m.size) {
+ skip(prot, m.keyType);
+ skip(prot, m.valueType);
+ }
+ prot.readMapEnd();
+ break;
+
+ case TType.SET:
+ auto s = prot.readSetBegin();
+ foreach (i; 0 .. s.size) {
+ skip(prot, s.elemType);
+ }
+ prot.readSetEnd();
+ break;
+ }
+}
+
+/**
+ * Application-level exception.
+ *
+ * It is thrown if an RPC call went wrong on the application layer, e.g. if
+ * the receiver does not know the method name requested or a method invoked by
+ * the service processor throws an exception not part of the Thrift API.
+ */
+class TApplicationException : TException {
+ /// The possible exception types.
+ enum Type {
+ UNKNOWN = 0, ///
+ UNKNOWN_METHOD = 1, ///
+ INVALID_MESSAGE_TYPE = 2, ///
+ WRONG_METHOD_NAME = 3, ///
+ BAD_SEQUENCE_ID = 4, ///
+ MISSING_RESULT = 5, ///
+ INTERNAL_ERROR = 6, ///
+ PROTOCOL_ERROR = 7 ///
+ }
+
+ ///
+ this(Type type, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
+ static string msgForType(Type type) {
+ switch (type) {
+ case Type.UNKNOWN: return "Unknown application exception";
+ case Type.UNKNOWN_METHOD: return "Unknown method";
+ case Type.INVALID_MESSAGE_TYPE: return "Invalid message type";
+ case Type.WRONG_METHOD_NAME: return "Wrong method name";
+ case Type.BAD_SEQUENCE_ID: return "Bad sequence identifier";
+ case Type.MISSING_RESULT: return "Missing result";
+ default: return "(Invalid exception type)";
+ }
+ }
+ this(msgForType(type), type, file, line, next);
+ }
+
+ ///
+ this(string msg, string file = __FILE__, size_t line = __LINE__,
+ Throwable next = null)
+ {
+ this(msg, Type.UNKNOWN, file, line, next);
+ }
+
+ ///
+ this(string msg, Type type, string file = __FILE__, size_t line = __LINE__,
+ Throwable next = null)
+ {
+ super(msg, file, line, next);
+ type_ = type;
+ }
+
+ ///
+ Type type() @property const {
+ return type_;
+ }
+
+ // TODO: Replace hand-written read()/write() with thrift.codegen templates.
+
+ ///
+ void read(TProtocol iprot) {
+ iprot.readStructBegin();
+ while (true) {
+ auto f = iprot.readFieldBegin();
+ if (f.type == TType.STOP) break;
+
+ switch (f.id) {
+ case 1:
+ if (f.type == TType.STRING) {
+ msg = iprot.readString();
+ } else {
+ skip(iprot, f.type);
+ }
+ break;
+ case 2:
+ if (f.type == TType.I32) {
+ type_ = cast(Type)iprot.readI32();
+ } else {
+ skip(iprot, f.type);
+ }
+ break;
+ default:
+ skip(iprot, f.type);
+ break;
+ }
+ }
+ iprot.readStructEnd();
+ }
+
+ ///
+ void write(TProtocol oprot) const {
+ oprot.writeStructBegin(TStruct("TApplicationException"));
+
+ if (msg != null) {
+ oprot.writeFieldBegin(TField("message", TType.STRING, 1));
+ oprot.writeString(msg);
+ oprot.writeFieldEnd();
+ }
+
+ oprot.writeFieldBegin(TField("type", TType.I32, 2));
+ oprot.writeI32(type_);
+ oprot.writeFieldEnd();
+
+ oprot.writeFieldStop();
+ oprot.writeStructEnd();
+ }
+
+private:
+ Type type_;
+}
Added: thrift/trunk/lib/d/src/thrift/protocol/binary.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/protocol/binary.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/protocol/binary.d (added)
+++ thrift/trunk/lib/d/src/thrift/protocol/binary.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,414 @@
+/*
+ * 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.
+ */
+module thrift.protocol.binary;
+
+import std.array : uninitializedArray;
+import std.typetuple : allSatisfy, TypeTuple;
+import thrift.protocol.base;
+import thrift.transport.base;
+import thrift.internal.endian;
+
+/**
+ * TProtocol implementation of the Binary Thrift protocol.
+ */
+final class TBinaryProtocol(Transport = TTransport) if (
+ isTTransport!Transport
+) : TProtocol {
+
+ /**
+ * Constructs a new instance.
+ *
+ * Params:
+ * trans = The transport to use.
+ * containerSizeLimit = If positive, the container size is limited to the
+ * given number of items.
+ * stringSizeLimit = If positive, the string length is limited to the
+ * given number of bytes.
+ * strictRead = If false, old peers which do not include the protocol
+ * version are tolerated.
+ * strictWrite = Whether to include the protocol version in the header.
+ */
+ this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0,
+ bool strictRead = false, bool strictWrite = true
+ ) {
+ trans_ = trans;
+ this.containerSizeLimit = containerSizeLimit;
+ this.stringSizeLimit = stringSizeLimit;
+ this.strictRead = strictRead;
+ this.strictWrite = strictWrite;
+ }
+
+ Transport transport() @property {
+ return trans_;
+ }
+
+ void reset() {}
+
+ /**
+ * If false, old peers which do not include the protocol version in the
+ * message header are tolerated.
+ *
+ * Defaults to false.
+ */
+ bool strictRead;
+
+ /**
+ * Whether to include the protocol version in the message header (older
+ * versions didn't).
+ *
+ * Defaults to true.
+ */
+ bool strictWrite;
+
+ /**
+ * If positive, limits the number of items of deserialized containers to the
+ * given amount.
+ *
+ * This is useful to avoid allocating excessive amounts of memory when broken
+ * data is received. If the limit is exceeded, a SIZE_LIMIT-type
+ * TProtocolException is thrown.
+ *
+ * Defaults to zero (no limit).
+ */
+ int containerSizeLimit;
+
+ /**
+ * If positive, limits the length of deserialized strings/binary data to the
+ * given number of bytes.
+ *
+ * This is useful to avoid allocating excessive amounts of memory when broken
+ * data is received. If the limit is exceeded, a SIZE_LIMIT-type
+ * TProtocolException is thrown.
+ *
+ * Defaults to zero (no limit).
+ */
+ int stringSizeLimit;
+
+ /*
+ * Writing methods.
+ */
+
+ void writeBool(bool b) {
+ writeByte(b ? 1 : 0);
+ }
+
+ void writeByte(byte b) {
+ trans_.write((cast(ubyte*)&b)[0 .. 1]);
+ }
+
+ void writeI16(short i16) {
+ short net = hostToNet(i16);
+ trans_.write((cast(ubyte*)&net)[0 .. 2]);
+ }
+
+ void writeI32(int i32) {
+ int net = hostToNet(i32);
+ trans_.write((cast(ubyte*)&net)[0 .. 4]);
+ }
+
+ void writeI64(long i64) {
+ long net = hostToNet(i64);
+ trans_.write((cast(ubyte*)&net)[0 .. 8]);
+ }
+
+ void writeDouble(double dub) {
+ static assert(double.sizeof == ulong.sizeof);
+ auto bits = hostToNet(*cast(ulong*)(&dub));
+ trans_.write((cast(ubyte*)&bits)[0 .. 8]);
+ }
+
+ void writeString(string str) {
+ writeBinary(cast(ubyte[])str);
+ }
+
+ void writeBinary(ubyte[] buf) {
+ assert(buf.length <= int.max);
+ writeI32(cast(int)buf.length);
+ trans_.write(buf);
+ }
+
+ void writeMessageBegin(TMessage message) {
+ if (strictWrite) {
+ int versn = VERSION_1 | message.type;
+ writeI32(versn);
+ writeString(message.name);
+ writeI32(message.seqid);
+ } else {
+ writeString(message.name);
+ writeByte(message.type);
+ writeI32(message.seqid);
+ }
+ }
+ void writeMessageEnd() {}
+
+ void writeStructBegin(TStruct tstruct) {}
+ void writeStructEnd() {}
+
+ void writeFieldBegin(TField field) {
+ writeByte(field.type);
+ writeI16(field.id);
+ }
+ void writeFieldEnd() {}
+
+ void writeFieldStop() {
+ writeByte(TType.STOP);
+ }
+
+ void writeListBegin(TList list) {
+ assert(list.size <= int.max);
+ writeByte(list.elemType);
+ writeI32(cast(int)list.size);
+ }
+ void writeListEnd() {}
+
+ void writeMapBegin(TMap map) {
+ assert(map.size <= int.max);
+ writeByte(map.keyType);
+ writeByte(map.valueType);
+ writeI32(cast(int)map.size);
+ }
+ void writeMapEnd() {}
+
+ void writeSetBegin(TSet set) {
+ assert(set.size <= int.max);
+ writeByte(set.elemType);
+ writeI32(cast(int)set.size);
+ }
+ void writeSetEnd() {}
+
+
+ /*
+ * Reading methods.
+ */
+
+ bool readBool() {
+ return readByte() != 0;
+ }
+
+ byte readByte() {
+ ubyte[1] b = void;
+ trans_.readAll(b);
+ return cast(byte)b[0];
+ }
+
+ short readI16() {
+ IntBuf!short b = void;
+ trans_.readAll(b.bytes);
+ return netToHost(b.value);
+ }
+
+ int readI32() {
+ IntBuf!int b = void;
+ trans_.readAll(b.bytes);
+ return netToHost(b.value);
+ }
+
+ long readI64() {
+ IntBuf!long b = void;
+ trans_.readAll(b.bytes);
+ return netToHost(b.value);
+ }
+
+ double readDouble() {
+ IntBuf!long b = void;
+ trans_.readAll(b.bytes);
+ b.value = netToHost(b.value);
+ return *cast(double*)(&b.value);
+ }
+
+ string readString() {
+ return cast(string)readBinary();
+ }
+
+ ubyte[] readBinary() {
+ return readBinaryBody(readSize(stringSizeLimit));
+ }
+
+ TMessage readMessageBegin() {
+ TMessage msg = void;
+
+ int size = readI32();
+ if (size < 0) {
+ int versn = size & VERSION_MASK;
+ if (versn != VERSION_1) {
+ throw new TProtocolException("Bad protocol version.",
+ TProtocolException.Type.BAD_VERSION);
+ }
+
+ msg.type = cast(TMessageType)(size & MESSAGE_TYPE_MASK);
+ msg.name = readString();
+ msg.seqid = readI32();
+ } else {
+ if (strictRead) {
+ throw new TProtocolException(
+ "Protocol version missing, old client?",
+ TProtocolException.Type.BAD_VERSION);
+ } else {
+ if (size < 0) {
+ throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
+ }
+ msg.name = cast(string)readBinaryBody(size);
+ msg.type = cast(TMessageType)(readByte());
+ msg.seqid = readI32();
+ }
+ }
+
+ return msg;
+ }
+ void readMessageEnd() {}
+
+ TStruct readStructBegin() {
+ return TStruct();
+ }
+ void readStructEnd() {}
+
+ TField readFieldBegin() {
+ TField f = void;
+ f.name = null;
+ f.type = cast(TType)readByte();
+ if (f.type == TType.STOP) return f;
+ f.id = readI16();
+ return f;
+ }
+ void readFieldEnd() {}
+
+ TList readListBegin() {
+ return TList(cast(TType)readByte(), readSize(containerSizeLimit));
+ }
+ void readListEnd() {}
+
+ TMap readMapBegin() {
+ return TMap(cast(TType)readByte(), cast(TType)readByte(),
+ readSize(containerSizeLimit));
+ }
+ void readMapEnd() {}
+
+ TSet readSetBegin() {
+ return TSet(cast(TType)readByte(), readSize(containerSizeLimit));
+ }
+ void readSetEnd() {}
+
+private:
+ ubyte[] readBinaryBody(int size) {
+ if (size == 0) {
+ return null;
+ }
+
+ auto buf = uninitializedArray!(ubyte[])(size);
+ trans_.readAll(buf);
+ return buf;
+ }
+
+ int readSize(int limit) {
+ auto size = readI32();
+ if (size < 0) {
+ throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
+ } else if (limit > 0 && size > limit) {
+ throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
+ }
+ return size;
+ }
+
+ enum MESSAGE_TYPE_MASK = 0x000000ff;
+ enum VERSION_MASK = 0xffff0000;
+ enum VERSION_1 = 0x80010000;
+
+ Transport trans_;
+}
+
+/**
+ * TBinaryProtocol construction helper to avoid having to explicitly specify
+ * the transport type, i.e. to allow the constructor being called using IFTI
+ * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla
+ * enhancement requet 6082)).
+ */
+TBinaryProtocol!Transport tBinaryProtocol(Transport)(Transport trans,
+ int containerSizeLimit = 0, int stringSizeLimit = 0,
+ bool strictRead = false, bool strictWrite = true
+) if (isTTransport!Transport) {
+ return new TBinaryProtocol!Transport(trans, containerSizeLimit,
+ stringSizeLimit, strictRead, strictWrite);
+}
+
+unittest {
+ import std.exception;
+ import thrift.transport.memory;
+
+ // Check the message header format.
+ auto buf = new TMemoryBuffer;
+ auto binary = tBinaryProtocol(buf);
+ binary.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0));
+
+ auto header = new ubyte[15];
+ buf.readAll(header);
+ enforce(header == [
+ 128, 1, 0, 1, // Version 1, TMessageType.CALL
+ 0, 0, 0, 3, // Method name length
+ 102, 111, 111, // Method name ("foo")
+ 0, 0, 0, 0, // Sequence id
+ ]);
+}
+
+unittest {
+ import thrift.internal.test.protocol;
+ testContainerSizeLimit!(TBinaryProtocol!())();
+ testStringSizeLimit!(TBinaryProtocol!())();
+}
+
+/**
+ * TProtocolFactory creating a TBinaryProtocol instance for passed in
+ * transports.
+ *
+ * The optional Transports template tuple parameter can be used to specify
+ * one or more TTransport implementations to specifically instantiate
+ * TBinaryProtocol for. If the actual transport types encountered at
+ * runtime match one of the transports in the list, a specialized protocol
+ * instance is created. Otherwise, a generic TTransport version is used.
+ */
+class TBinaryProtocolFactory(Transports...) if (
+ allSatisfy!(isTTransport, Transports)
+) : TProtocolFactory {
+ ///
+ this (int containerSizeLimit = 0, int stringSizeLimit = 0,
+ bool strictRead = false, bool strictWrite = true
+ ) {
+ strictRead_ = strictRead;
+ strictWrite_ = strictWrite;
+ containerSizeLimit_ = containerSizeLimit;
+ stringSizeLimit_ = stringSizeLimit;
+ }
+
+ TProtocol getProtocol(TTransport trans) const {
+ foreach (Transport; TypeTuple!(Transports, TTransport)) {
+ auto concreteTrans = cast(Transport)trans;
+ if (concreteTrans) {
+ return new TBinaryProtocol!Transport(concreteTrans,
+ containerSizeLimit_, stringSizeLimit_, strictRead_, strictWrite_);
+ }
+ }
+ throw new TProtocolException(
+ "Passed null transport to TBinaryProtocolFactoy.");
+ }
+
+protected:
+ bool strictRead_;
+ bool strictWrite_;
+ int containerSizeLimit_;
+ int stringSizeLimit_;
+}
Added: thrift/trunk/lib/d/src/thrift/protocol/compact.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/protocol/compact.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/protocol/compact.d (added)
+++ thrift/trunk/lib/d/src/thrift/protocol/compact.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,695 @@
+/*
+ * 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.
+ */
+module thrift.protocol.compact;
+
+import std.array : uninitializedArray;
+import std.typetuple : allSatisfy, TypeTuple;
+import thrift.protocol.base;
+import thrift.transport.base;
+import thrift.internal.endian;
+
+/**
+ * D implementation of the Compact protocol.
+ *
+ * See THRIFT-110 for a protocol description. This implementation is based on
+ * the C++ one.
+ */
+final class TCompactProtocol(Transport = TTransport) if (
+ isTTransport!Transport
+) : TProtocol {
+ /**
+ * Constructs a new instance.
+ *
+ * Params:
+ * trans = The transport to use.
+ * containerSizeLimit = If positive, the container size is limited to the
+ * given number of items.
+ * stringSizeLimit = If positive, the string length is limited to the
+ * given number of bytes.
+ */
+ this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) {
+ trans_ = trans;
+ this.containerSizeLimit = containerSizeLimit;
+ this.stringSizeLimit = stringSizeLimit;
+ }
+
+ Transport transport() @property {
+ return trans_;
+ }
+
+ void reset() {
+ lastFieldId_ = 0;
+ fieldIdStack_ = null;
+ booleanField_ = TField.init;
+ hasBoolValue_ = false;
+ }
+
+ /**
+ * If positive, limits the number of items of deserialized containers to the
+ * given amount.
+ *
+ * This is useful to avoid allocating excessive amounts of memory when broken
+ * data is received. If the limit is exceeded, a SIZE_LIMIT-type
+ * TProtocolException is thrown.
+ *
+ * Defaults to zero (no limit).
+ */
+ int containerSizeLimit;
+
+ /**
+ * If positive, limits the length of deserialized strings/binary data to the
+ * given number of bytes.
+ *
+ * This is useful to avoid allocating excessive amounts of memory when broken
+ * data is received. If the limit is exceeded, a SIZE_LIMIT-type
+ * TProtocolException is thrown.
+ *
+ * Defaults to zero (no limit).
+ */
+ int stringSizeLimit;
+
+ /*
+ * Writing methods.
+ */
+
+ void writeBool(bool b) {
+ if (booleanField_.name !is null) {
+ // we haven't written the field header yet
+ writeFieldBeginInternal(booleanField_,
+ b ? CType.BOOLEAN_TRUE : CType.BOOLEAN_FALSE);
+ booleanField_.name = null;
+ } else {
+ // we're not part of a field, so just write the value
+ writeByte(b ? CType.BOOLEAN_TRUE : CType.BOOLEAN_FALSE);
+ }
+ }
+
+ void writeByte(byte b) {
+ trans_.write((cast(ubyte*)&b)[0..1]);
+ }
+
+ void writeI16(short i16) {
+ writeVarint32(i32ToZigzag(i16));
+ }
+
+ void writeI32(int i32) {
+ writeVarint32(i32ToZigzag(i32));
+ }
+
+ void writeI64(long i64) {
+ writeVarint64(i64ToZigzag(i64));
+ }
+
+ void writeDouble(double dub) {
+ ulong bits = hostToLe(*cast(ulong*)(&dub));
+ trans_.write((cast(ubyte*)&bits)[0 .. 8]);
+ }
+
+ void writeString(string str) {
+ writeBinary(cast(ubyte[])str);
+ }
+
+ void writeBinary(ubyte[] buf) {
+ assert(buf.length <= int.max);
+ writeVarint32(cast(int)buf.length);
+ trans_.write(buf);
+ }
+
+ void writeMessageBegin(TMessage msg) {
+ writeByte(cast(byte)PROTOCOL_ID);
+ writeByte((VERSION_N & VERSION_MASK) |
+ ((cast(int)msg.type << TYPE_SHIFT_AMOUNT) & TYPE_MASK));
+ writeVarint32(msg.seqid);
+ writeString(msg.name);
+ }
+ void writeMessageEnd() {}
+
+ void writeStructBegin(TStruct tstruct) {
+ fieldIdStack_ ~= lastFieldId_;
+ lastFieldId_ = 0;
+ }
+
+ void writeStructEnd() {
+ lastFieldId_ = fieldIdStack_[$ - 1];
+ fieldIdStack_ = fieldIdStack_[0 .. $ - 1];
+ fieldIdStack_.assumeSafeAppend();
+ }
+
+ void writeFieldBegin(TField field) {
+ if (field.type == TType.BOOL) {
+ booleanField_.name = field.name;
+ booleanField_.type = field.type;
+ booleanField_.id = field.id;
+ } else {
+ return writeFieldBeginInternal(field);
+ }
+ }
+ void writeFieldEnd() {}
+
+ void writeFieldStop() {
+ writeByte(TType.STOP);
+ }
+
+ void writeListBegin(TList list) {
+ writeCollectionBegin(list.elemType, list.size);
+ }
+ void writeListEnd() {}
+
+ void writeMapBegin(TMap map) {
+ if (map.size == 0) {
+ writeByte(0);
+ } else {
+ assert(map.size <= int.max);
+ writeVarint32(cast(int)map.size);
+ writeByte(cast(byte)(toCType(map.keyType) << 4 | toCType(map.valueType)));
+ }
+ }
+ void writeMapEnd() {}
+
+ void writeSetBegin(TSet set) {
+ writeCollectionBegin(set.elemType, set.size);
+ }
+ void writeSetEnd() {}
+
+
+ /*
+ * Reading methods.
+ */
+
+ bool readBool() {
+ if (hasBoolValue_ == true) {
+ hasBoolValue_ = false;
+ return boolValue_;
+ }
+
+ return readByte() == CType.BOOLEAN_TRUE;
+ }
+
+ byte readByte() {
+ ubyte[1] b = void;
+ trans_.readAll(b);
+ return cast(byte)b[0];
+ }
+
+ short readI16() {
+ return cast(short)zigzagToI32(readVarint32());
+ }
+
+ int readI32() {
+ return zigzagToI32(readVarint32());
+ }
+
+ long readI64() {
+ return zigzagToI64(readVarint64());
+ }
+
+ double readDouble() {
+ IntBuf!long b = void;
+ trans_.readAll(b.bytes);
+ b.value = leToHost(b.value);
+ return *cast(double*)(&b.value);
+ }
+
+ string readString() {
+ return cast(string)readBinary();
+ }
+
+ ubyte[] readBinary() {
+ auto size = readVarint32();
+ checkSize(size, stringSizeLimit);
+
+ if (size == 0) {
+ return null;
+ }
+
+ auto buf = uninitializedArray!(ubyte[])(size);
+ trans_.readAll(buf);
+ return buf;
+ }
+
+ TMessage readMessageBegin() {
+ TMessage msg = void;
+
+ auto protocolId = readByte();
+ if (protocolId != cast(byte)PROTOCOL_ID) {
+ throw new TProtocolException("Bad protocol identifier",
+ TProtocolException.Type.BAD_VERSION);
+ }
+
+ auto versionAndType = readByte();
+ auto ver = versionAndType & VERSION_MASK;
+ if (ver != VERSION_N) {
+ throw new TProtocolException("Bad protocol version",
+ TProtocolException.Type.BAD_VERSION);
+ }
+
+ msg.type = cast(TMessageType)((versionAndType >> TYPE_SHIFT_AMOUNT) & 0x03);
+ msg.seqid = readVarint32();
+ msg.name = readString();
+
+ return msg;
+ }
+ void readMessageEnd() {}
+
+ TStruct readStructBegin() {
+ fieldIdStack_ ~= lastFieldId_;
+ lastFieldId_ = 0;
+ return TStruct();
+ }
+
+ void readStructEnd() {
+ lastFieldId_ = fieldIdStack_[$ - 1];
+ fieldIdStack_ = fieldIdStack_[0 .. $ - 1];
+ }
+
+ TField readFieldBegin() {
+ TField f = void;
+ f.name = null;
+
+ auto bite = readByte();
+ auto type = cast(CType)(bite & 0x0f);
+
+ if (type == CType.STOP) {
+ // Struct stop byte, nothing more to do.
+ f.id = 0;
+ f.type = TType.STOP;
+ return f;
+ }
+
+ // Mask off the 4 MSB of the type header, which could contain a field id
+ // delta.
+ auto modifier = cast(short)((bite & 0xf0) >> 4);
+ if (modifier > 0) {
+ f.id = cast(short)(lastFieldId_ + modifier);
+ } else {
+ // Delta encoding not used, just read the id as usual.
+ f.id = readI16();
+ }
+ f.type = getTType(type);
+
+ if (type == CType.BOOLEAN_TRUE || type == CType.BOOLEAN_FALSE) {
+ // For boolean fields, the value is encoded in the type â keep it around
+ // for the readBool() call.
+ hasBoolValue_ = true;
+ boolValue_ = (type == CType.BOOLEAN_TRUE ? true : false);
+ }
+
+ lastFieldId_ = f.id;
+ return f;
+ }
+ void readFieldEnd() {}
+
+ TList readListBegin() {
+ auto sizeAndType = readByte();
+
+ auto lsize = (sizeAndType >> 4) & 0xf;
+ if (lsize == 0xf) {
+ lsize = readVarint32();
+ }
+ checkSize(lsize, containerSizeLimit);
+
+ TList l = void;
+ l.elemType = getTType(cast(CType)(sizeAndType & 0x0f));
+ l.size = cast(size_t)lsize;
+
+ return l;
+ }
+ void readListEnd() {}
+
+ TMap readMapBegin() {
+ TMap m = void;
+
+ auto size = readVarint32();
+ ubyte kvType;
+ if (size != 0) {
+ kvType = readByte();
+ }
+ checkSize(size, containerSizeLimit);
+
+ m.size = size;
+ m.keyType = getTType(cast(CType)(kvType >> 4));
+ m.valueType = getTType(cast(CType)(kvType & 0xf));
+
+ return m;
+ }
+ void readMapEnd() {}
+
+ TSet readSetBegin() {
+ auto sizeAndType = readByte();
+
+ auto lsize = (sizeAndType >> 4) & 0xf;
+ if (lsize == 0xf) {
+ lsize = readVarint32();
+ }
+ checkSize(lsize, containerSizeLimit);
+
+ TSet s = void;
+ s.elemType = getTType(cast(CType)(sizeAndType & 0xf));
+ s.size = cast(size_t)lsize;
+
+ return s;
+ }
+ void readSetEnd() {}
+
+private:
+ void writeFieldBeginInternal(TField field, byte typeOverride = -1) {
+ // If there's a type override, use that.
+ auto typeToWrite = (typeOverride == -1 ? toCType(field.type) : typeOverride);
+
+ // check if we can use delta encoding for the field id
+ if (field.id > lastFieldId_ && (field.id - lastFieldId_) <= 15) {
+ // write them together
+ writeByte(cast(byte)((field.id - lastFieldId_) << 4 | typeToWrite));
+ } else {
+ // write them separate
+ writeByte(cast(byte)typeToWrite);
+ writeI16(field.id);
+ }
+
+ lastFieldId_ = field.id;
+ }
+
+
+ void writeCollectionBegin(TType elemType, size_t size) {
+ if (size <= 14) {
+ writeByte(cast(byte)(size << 4 | toCType(elemType)));
+ } else {
+ assert(size <= int.max);
+ writeByte(0xf0 | toCType(elemType));
+ writeVarint32(cast(int)size);
+ }
+ }
+
+ void writeVarint32(uint n) {
+ ubyte[5] buf = void;
+ ubyte wsize;
+
+ while (true) {
+ if ((n & ~0x7F) == 0) {
+ buf[wsize++] = cast(ubyte)n;
+ break;
+ } else {
+ buf[wsize++] = cast(ubyte)((n & 0x7F) | 0x80);
+ n >>= 7;
+ }
+ }
+
+ trans_.write(buf[0 .. wsize]);
+ }
+
+ /*
+ * Write an i64 as a varint. Results in 1-10 bytes on the wire.
+ */
+ void writeVarint64(ulong n) {
+ ubyte[10] buf = void;
+ ubyte wsize;
+
+ while (true) {
+ if ((n & ~0x7FL) == 0) {
+ buf[wsize++] = cast(ubyte)n;
+ break;
+ } else {
+ buf[wsize++] = cast(ubyte)((n & 0x7F) | 0x80);
+ n >>= 7;
+ }
+ }
+
+ trans_.write(buf[0 .. wsize]);
+ }
+
+ /*
+ * Convert l into a zigzag long. This allows negative numbers to be
+ * represented compactly as a varint.
+ */
+ ulong i64ToZigzag(long l) {
+ return (l << 1) ^ (l >> 63);
+ }
+
+ /*
+ * Convert n into a zigzag int. This allows negative numbers to be
+ * represented compactly as a varint.
+ */
+ uint i32ToZigzag(int n) {
+ return (n << 1) ^ (n >> 31);
+ }
+
+ CType toCType(TType type) {
+ final switch (type) {
+ case TType.STOP:
+ return CType.STOP;
+ case TType.BOOL:
+ return CType.BOOLEAN_TRUE;
+ case TType.BYTE:
+ return CType.BYTE;
+ case TType.DOUBLE:
+ return CType.DOUBLE;
+ case TType.I16:
+ return CType.I16;
+ case TType.I32:
+ return CType.I32;
+ case TType.I64:
+ return CType.I64;
+ case TType.STRING:
+ return CType.BINARY;
+ case TType.STRUCT:
+ return CType.STRUCT;
+ case TType.MAP:
+ return CType.MAP;
+ case TType.SET:
+ return CType.SET;
+ case TType.LIST:
+ return CType.LIST;
+ }
+ }
+
+ int readVarint32() {
+ return cast(int)readVarint64();
+ }
+
+ long readVarint64() {
+ ulong val;
+ ubyte shift;
+ ubyte[10] buf = void; // 64 bits / (7 bits/byte) = 10 bytes.
+ auto bufSize = buf.sizeof;
+ auto borrowed = trans_.borrow(buf.ptr, bufSize);
+
+ ubyte rsize;
+
+ if (borrowed) {
+ // Fast path.
+ while (true) {
+ auto bite = borrowed[rsize];
+ rsize++;
+ val |= cast(ulong)(bite & 0x7f) << shift;
+ shift += 7;
+ if (!(bite & 0x80)) {
+ trans_.consume(rsize);
+ return val;
+ }
+ // Have to check for invalid data so we don't crash.
+ if (rsize == buf.sizeof) {
+ throw new TProtocolException(TProtocolException.Type.INVALID_DATA,
+ "Variable-length int over 10 bytes.");
+ }
+ }
+ } else {
+ // Slow path.
+ while (true) {
+ ubyte[1] bite;
+ trans_.readAll(bite);
+ ++rsize;
+
+ val |= cast(ulong)(bite[0] & 0x7f) << shift;
+ shift += 7;
+ if (!(bite[0] & 0x80)) {
+ return val;
+ }
+
+ // Might as well check for invalid data on the slow path too.
+ if (rsize >= buf.sizeof) {
+ throw new TProtocolException(TProtocolException.Type.INVALID_DATA,
+ "Variable-length int over 10 bytes.");
+ }
+ }
+ }
+ }
+
+ /*
+ * Convert from zigzag int to int.
+ */
+ int zigzagToI32(uint n) {
+ return (n >> 1) ^ -(n & 1);
+ }
+
+ /*
+ * Convert from zigzag long to long.
+ */
+ long zigzagToI64(ulong n) {
+ return (n >> 1) ^ -(n & 1);
+ }
+
+ TType getTType(CType type) {
+ final switch (type) {
+ case CType.STOP:
+ return TType.STOP;
+ case CType.BOOLEAN_FALSE:
+ return TType.BOOL;
+ case CType.BOOLEAN_TRUE:
+ return TType.BOOL;
+ case CType.BYTE:
+ return TType.BYTE;
+ case CType.I16:
+ return TType.I16;
+ case CType.I32:
+ return TType.I32;
+ case CType.I64:
+ return TType.I64;
+ case CType.DOUBLE:
+ return TType.DOUBLE;
+ case CType.BINARY:
+ return TType.STRING;
+ case CType.LIST:
+ return TType.LIST;
+ case CType.SET:
+ return TType.SET;
+ case CType.MAP:
+ return TType.MAP;
+ case CType.STRUCT:
+ return TType.STRUCT;
+ }
+ }
+
+ void checkSize(int size, int limit) {
+ if (size < 0) {
+ throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
+ } else if (limit > 0 && size > limit) {
+ throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
+ }
+ }
+
+ enum PROTOCOL_ID = 0x82;
+ enum VERSION_N = 1;
+ enum VERSION_MASK = 0b0001_1111;
+ enum TYPE_MASK = 0b1110_0000;
+ enum TYPE_SHIFT_AMOUNT = 5;
+
+ // Probably need to implement a better stack at some point.
+ short[] fieldIdStack_;
+ short lastFieldId_;
+
+ TField booleanField_;
+
+ bool hasBoolValue_;
+ bool boolValue_;
+
+ Transport trans_;
+}
+
+/**
+ * TCompactProtocol construction helper to avoid having to explicitly specify
+ * the transport type, i.e. to allow the constructor being called using IFTI
+ * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla
+ * enhancement requet 6082)).
+ */
+TCompactProtocol!Transport tCompactProtocol(Transport)(Transport trans,
+ int containerSizeLimit = 0, int stringSizeLimit = 0
+) if (isTTransport!Transport)
+{
+ return new TCompactProtocol!Transport(trans,
+ containerSizeLimit, stringSizeLimit);
+}
+
+private {
+ enum CType : ubyte {
+ STOP = 0x0,
+ BOOLEAN_TRUE = 0x1,
+ BOOLEAN_FALSE = 0x2,
+ BYTE = 0x3,
+ I16 = 0x4,
+ I32 = 0x5,
+ I64 = 0x6,
+ DOUBLE = 0x7,
+ BINARY = 0x8,
+ LIST = 0x9,
+ SET = 0xa,
+ MAP = 0xb,
+ STRUCT = 0xc
+ }
+ static assert(CType.max <= 0xf,
+ "Compact protocol wire type representation must fit into 4 bits.");
+}
+
+unittest {
+ import std.exception;
+ import thrift.transport.memory;
+
+ // Check the message header format.
+ auto buf = new TMemoryBuffer;
+ auto compact = tCompactProtocol(buf);
+ compact.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0));
+
+ auto header = new ubyte[7];
+ buf.readAll(header);
+ enforce(header == [
+ 130, // Protocol id.
+ 33, // Version/type byte.
+ 0, // Sequence id.
+ 3, 102, 111, 111 // Method name.
+ ]);
+}
+
+unittest {
+ import thrift.internal.test.protocol;
+ testContainerSizeLimit!(TCompactProtocol!())();
+ testStringSizeLimit!(TCompactProtocol!())();
+}
+
+/**
+ * TProtocolFactory creating a TCompactProtocol instance for passed in
+ * transports.
+ *
+ * The optional Transports template tuple parameter can be used to specify
+ * one or more TTransport implementations to specifically instantiate
+ * TCompactProtocol for. If the actual transport types encountered at
+ * runtime match one of the transports in the list, a specialized protocol
+ * instance is created. Otherwise, a generic TTransport version is used.
+ */
+class TCompactProtocolFactory(Transports...) if (
+ allSatisfy!(isTTransport, Transports)
+) : TProtocolFactory {
+ ///
+ this(int containerSizeLimit = 0, int stringSizeLimit = 0) {
+ containerSizeLimit_ = 0;
+ stringSizeLimit_ = 0;
+ }
+
+ TProtocol getProtocol(TTransport trans) const {
+ foreach (Transport; TypeTuple!(Transports, TTransport)) {
+ auto concreteTrans = cast(Transport)trans;
+ if (concreteTrans) {
+ return new TCompactProtocol!Transport(concreteTrans);
+ }
+ }
+ throw new TProtocolException(
+ "Passed null transport to TCompactProtocolFactory.");
+ }
+
+ int containerSizeLimit_;
+ int stringSizeLimit_;
+}
Added: thrift/trunk/lib/d/src/thrift/protocol/json.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/protocol/json.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/protocol/json.d (added)
+++ thrift/trunk/lib/d/src/thrift/protocol/json.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,979 @@
+/*
+ * 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.
+ */
+module thrift.protocol.json;
+
+import std.algorithm;
+import std.array;
+import std.base64;
+import std.conv;
+import std.range;
+import std.string : format;
+import std.traits : isIntegral;
+import std.typetuple : allSatisfy, TypeTuple;
+import thrift.protocol.base;
+import thrift.transport.base;
+
+alias Base64Impl!('+', '/', Base64.NoPadding) Base64NoPad;
+
+/**
+ * Implementation of the Thrift JSON protocol.
+ */
+final class TJsonProtocol(Transport = TTransport) if (
+ isTTransport!Transport
+) : TProtocol {
+ /**
+ * Constructs a new instance.
+ *
+ * Params:
+ * trans = The transport to use.
+ * containerSizeLimit = If positive, the container size is limited to the
+ * given number of items.
+ * stringSizeLimit = If positive, the string length is limited to the
+ * given number of bytes.
+ */
+ this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) {
+ trans_ = trans;
+ this.containerSizeLimit = containerSizeLimit;
+ this.stringSizeLimit = stringSizeLimit;
+
+ context_ = new Context();
+ reader_ = new LookaheadReader(trans);
+ }
+
+ Transport transport() @property {
+ return trans_;
+ }
+
+ void reset() {
+ contextStack_.clear();
+ context_ = new Context();
+ reader_ = new LookaheadReader(trans_);
+ }
+
+ /**
+ * If positive, limits the number of items of deserialized containers to the
+ * given amount.
+ *
+ * This is useful to avoid allocating excessive amounts of memory when broken
+ * data is received. If the limit is exceeded, a SIZE_LIMIT-type
+ * TProtocolException is thrown.
+ *
+ * Defaults to zero (no limit).
+ */
+ int containerSizeLimit;
+
+ /**
+ * If positive, limits the length of deserialized strings/binary data to the
+ * given number of bytes.
+ *
+ * This is useful to avoid allocating excessive amounts of memory when broken
+ * data is received. If the limit is exceeded, a SIZE_LIMIT-type
+ * TProtocolException is thrown.
+ *
+ * Note: For binary data, the limit applies to the length of the
+ * Base64-encoded string data, not the resulting byte array.
+ *
+ * Defaults to zero (no limit).
+ */
+ int stringSizeLimit;
+
+ /*
+ * Writing methods.
+ */
+
+ void writeBool(bool b) {
+ writeJsonInteger(b ? 1 : 0);
+ }
+
+ void writeByte(byte b) {
+ writeJsonInteger(b);
+ }
+
+ void writeI16(short i16) {
+ writeJsonInteger(i16);
+ }
+
+ void writeI32(int i32) {
+ writeJsonInteger(i32);
+ }
+
+ void writeI64(long i64) {
+ writeJsonInteger(i64);
+ }
+
+ void writeDouble(double dub) {
+ context_.write(trans_);
+
+ string value;
+ if (dub is double.nan) {
+ value = NAN_STRING;
+ } else if (dub is double.infinity) {
+ value = INFINITY_STRING;
+ } else if (dub is -double.infinity) {
+ value = NEG_INFINITY_STRING;
+ }
+
+ bool escapeNum = value !is null || context_.escapeNum;
+
+ if (value is null) {
+ value = format("%.16g", dub);
+ }
+
+ if (escapeNum) trans_.write(STRING_DELIMITER);
+ trans_.write(cast(ubyte[])value);
+ if (escapeNum) trans_.write(STRING_DELIMITER);
+ }
+
+ void writeString(string str) {
+ context_.write(trans_);
+ trans_.write(STRING_DELIMITER);
+ foreach (c; str) {
+ writeJsonChar(c);
+ }
+ trans_.write(STRING_DELIMITER);
+ }
+
+ void writeBinary(ubyte[] buf) {
+ context_.write(trans_);
+
+ trans_.write(STRING_DELIMITER);
+ ubyte[4] b;
+ while (!buf.empty) {
+ auto toWrite = take(buf, 3);
+ Base64NoPad.encode(toWrite, b[]);
+ trans_.write(b[0 .. toWrite.length + 1]);
+ buf.popFrontN(toWrite.length);
+ }
+ trans_.write(STRING_DELIMITER);
+ }
+
+ void writeMessageBegin(TMessage msg) {
+ writeJsonArrayBegin();
+ writeJsonInteger(THRIFT_JSON_VERSION);
+ writeString(msg.name);
+ writeJsonInteger(cast(byte)msg.type);
+ writeJsonInteger(msg.seqid);
+ }
+
+ void writeMessageEnd() {
+ writeJsonArrayEnd();
+ }
+
+ void writeStructBegin(TStruct tstruct) {
+ writeJsonObjectBegin();
+ }
+
+ void writeStructEnd() {
+ writeJsonObjectEnd();
+ }
+
+ void writeFieldBegin(TField field) {
+ writeJsonInteger(field.id);
+ writeJsonObjectBegin();
+ writeString(getNameFromTType(field.type));
+ }
+
+ void writeFieldEnd() {
+ writeJsonObjectEnd();
+ }
+
+ void writeFieldStop() {}
+
+ void writeListBegin(TList list) {
+ writeJsonArrayBegin();
+ writeString(getNameFromTType(list.elemType));
+ writeJsonInteger(list.size);
+ }
+
+ void writeListEnd() {
+ writeJsonArrayEnd();
+ }
+
+ void writeMapBegin(TMap map) {
+ writeJsonArrayBegin();
+ writeString(getNameFromTType(map.keyType));
+ writeString(getNameFromTType(map.valueType));
+ writeJsonInteger(map.size);
+ writeJsonObjectBegin();
+ }
+
+ void writeMapEnd() {
+ writeJsonObjectEnd();
+ writeJsonArrayEnd();
+ }
+
+ void writeSetBegin(TSet set) {
+ writeJsonArrayBegin();
+ writeString(getNameFromTType(set.elemType));
+ writeJsonInteger(set.size);
+ }
+
+ void writeSetEnd() {
+ writeJsonArrayEnd();
+ }
+
+
+ /*
+ * Reading methods.
+ */
+
+ bool readBool() {
+ return readJsonInteger!byte() ? true : false;
+ }
+
+ byte readByte() {
+ return readJsonInteger!byte();
+ }
+
+ short readI16() {
+ return readJsonInteger!short();
+ }
+
+ int readI32() {
+ return readJsonInteger!int();
+ }
+
+ long readI64() {
+ return readJsonInteger!long();
+ }
+
+ double readDouble() {
+ context_.read(reader_);
+
+ if (reader_.peek() == STRING_DELIMITER) {
+ auto str = readJsonString(true);
+ if (str == NAN_STRING) {
+ return double.nan;
+ }
+ if (str == INFINITY_STRING) {
+ return double.infinity;
+ }
+ if (str == NEG_INFINITY_STRING) {
+ return -double.infinity;
+ }
+
+ if (!context_.escapeNum) {
+ // Throw exception -- we should not be in a string in this case
+ throw new TProtocolException("Numeric data unexpectedly quoted",
+ TProtocolException.Type.INVALID_DATA);
+ }
+ try {
+ return to!double(str);
+ } catch (ConvException e) {
+ throw new TProtocolException(`Expected numeric value; got "` ~ str ~
+ `".`, TProtocolException.Type.INVALID_DATA);
+ }
+ }
+ else {
+ if (context_.escapeNum) {
+ // This will throw - we should have had a quote if escapeNum == true
+ readJsonSyntaxChar(STRING_DELIMITER);
+ }
+
+ auto str = readJsonNumericChars();
+ try {
+ return to!double(str);
+ } catch (ConvException e) {
+ throw new TProtocolException(`Expected numeric value; got "` ~ str ~
+ `".`, TProtocolException.Type.INVALID_DATA);
+ }
+ }
+ }
+
+ string readString() {
+ return readJsonString(false);
+ }
+
+ ubyte[] readBinary() {
+ return Base64NoPad.decode(readString());
+ }
+
+ TMessage readMessageBegin() {
+ TMessage msg = void;
+
+ readJsonArrayBegin();
+
+ auto ver = readJsonInteger!short();
+ if (ver != THRIFT_JSON_VERSION) {
+ throw new TProtocolException("Message contained bad version.",
+ TProtocolException.Type.BAD_VERSION);
+ }
+
+ msg.name = readString();
+ msg.type = cast(TMessageType)readJsonInteger!byte();
+ msg.seqid = readJsonInteger!short();
+
+ return msg;
+ }
+
+ void readMessageEnd() {
+ readJsonArrayEnd();
+ }
+
+ TStruct readStructBegin() {
+ readJsonObjectBegin();
+ return TStruct();
+ }
+
+ void readStructEnd() {
+ readJsonObjectEnd();
+ }
+
+ TField readFieldBegin() {
+ TField f = void;
+ f.name = null;
+
+ auto ch = reader_.peek();
+ if (ch == OBJECT_END) {
+ f.type = TType.STOP;
+ } else {
+ f.id = readJsonInteger!short();
+ readJsonObjectBegin();
+ f.type = getTTypeFromName(readString());
+ }
+
+ return f;
+ }
+
+ void readFieldEnd() {
+ readJsonObjectEnd();
+ }
+
+ TList readListBegin() {
+ readJsonArrayBegin();
+ auto type = getTTypeFromName(readString());
+ auto size = readContainerSize();
+ return TList(type, size);
+ }
+
+ void readListEnd() {
+ readJsonArrayEnd();
+ }
+
+ TMap readMapBegin() {
+ readJsonArrayBegin();
+ auto keyType = getTTypeFromName(readString());
+ auto valueType = getTTypeFromName(readString());
+ auto size = readContainerSize();
+ readJsonObjectBegin();
+ return TMap(keyType, valueType, size);
+ }
+
+ void readMapEnd() {
+ readJsonObjectEnd();
+ readJsonArrayEnd();
+ }
+
+ TSet readSetBegin() {
+ readJsonArrayBegin();
+ auto type = getTTypeFromName(readString());
+ auto size = readContainerSize();
+ return TSet(type, size);
+ }
+
+ void readSetEnd() {
+ readJsonArrayEnd();
+ }
+
+private:
+ void pushContext(Context c) {
+ contextStack_ ~= context_;
+ context_ = c;
+ }
+
+ void popContext() {
+ context_ = contextStack_.back;
+ contextStack_.popBack();
+ contextStack_.assumeSafeAppend();
+ }
+
+ /*
+ * Writing functions
+ */
+
+ // Write the character ch as a Json escape sequence ("\u00xx")
+ void writeJsonEscapeChar(ubyte ch) {
+ trans_.write(ESCAPE_PREFIX);
+ trans_.write(ESCAPE_PREFIX);
+ auto outCh = hexChar(cast(ubyte)(ch >> 4));
+ trans_.write((&outCh)[0 .. 1]);
+ outCh = hexChar(ch);
+ trans_.write((&outCh)[0 .. 1]);
+ }
+
+ // Write the character ch as part of a Json string, escaping as appropriate.
+ void writeJsonChar(ubyte ch) {
+ if (ch >= 0x30) {
+ if (ch == '\\') { // Only special character >= 0x30 is '\'
+ trans_.write(BACKSLASH);
+ trans_.write(BACKSLASH);
+ } else {
+ trans_.write((&ch)[0 .. 1]);
+ }
+ }
+ else {
+ auto outCh = kJsonCharTable[ch];
+ // Check if regular character, backslash escaped, or Json escaped
+ if (outCh == 1) {
+ trans_.write((&ch)[0 .. 1]);
+ } else if (outCh > 1) {
+ trans_.write(BACKSLASH);
+ trans_.write((&outCh)[0 .. 1]);
+ } else {
+ writeJsonEscapeChar(ch);
+ }
+ }
+ }
+
+ // Convert the given integer type to a Json number, or a string
+ // if the context requires it (eg: key in a map pair).
+ void writeJsonInteger(T)(T num) if (isIntegral!T) {
+ context_.write(trans_);
+
+ auto escapeNum = context_.escapeNum();
+ if (escapeNum) trans_.write(STRING_DELIMITER);
+ trans_.write(cast(ubyte[])to!string(num));
+ if (escapeNum) trans_.write(STRING_DELIMITER);
+ }
+
+ void writeJsonObjectBegin() {
+ context_.write(trans_);
+ trans_.write(OBJECT_BEGIN);
+ pushContext(new PairContext());
+ }
+
+ void writeJsonObjectEnd() {
+ popContext();
+ trans_.write(OBJECT_END);
+ }
+
+ void writeJsonArrayBegin() {
+ context_.write(trans_);
+ trans_.write(ARRAY_BEGIN);
+ pushContext(new ListContext());
+ }
+
+ void writeJsonArrayEnd() {
+ popContext();
+ trans_.write(ARRAY_END);
+ }
+
+ /*
+ * Reading functions
+ */
+
+ int readContainerSize() {
+ auto size = readJsonInteger!int();
+ if (size < 0) {
+ throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
+ } else if (containerSizeLimit > 0 && size > containerSizeLimit) {
+ throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
+ }
+ return size;
+ }
+
+ void readJsonSyntaxChar(ubyte[1] ch) {
+ return readSyntaxChar(reader_, ch);
+ }
+
+ ubyte readJsonEscapeChar() {
+ readJsonSyntaxChar(ZERO_CHAR);
+ readJsonSyntaxChar(ZERO_CHAR);
+ auto a = reader_.read();
+ auto b = reader_.read();
+ return cast(ubyte)((hexVal(a[0]) << 4) + hexVal(b[0]));
+ }
+
+ string readJsonString(bool skipContext = false) {
+ if (!skipContext) context_.read(reader_);
+
+ readJsonSyntaxChar(STRING_DELIMITER);
+ auto buffer = appender!string();
+
+ int bytesRead;
+ while (true) {
+ auto ch = reader_.read();
+ if (ch == STRING_DELIMITER) {
+ break;
+ }
+
+ ++bytesRead;
+ if (stringSizeLimit > 0 && bytesRead > stringSizeLimit) {
+ throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
+ }
+
+ if (ch == BACKSLASH) {
+ ch = reader_.read();
+ if (ch == ESCAPE_CHAR) {
+ ch = readJsonEscapeChar();
+ } else {
+ auto pos = countUntil(kEscapeChars[], ch[0]);
+ if (pos == -1) {
+ throw new TProtocolException("Expected control char, got '" ~
+ cast(char)ch[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
+ }
+ ch = kEscapeCharVals[pos];
+ }
+ }
+ buffer.put(ch[0]);
+ }
+
+ return buffer.data;
+ }
+
+ // Reads a sequence of characters, stopping at the first one that is not
+ // a valid Json numeric character.
+ string readJsonNumericChars() {
+ string str;
+ while (true) {
+ auto ch = reader_.peek();
+ if (!isJsonNumeric(ch[0])) {
+ break;
+ }
+ reader_.read();
+ str ~= ch;
+ }
+ return str;
+ }
+
+ // Reads a sequence of characters and assembles them into a number,
+ // returning them via num
+ T readJsonInteger(T)() if (isIntegral!T) {
+ context_.read(reader_);
+ if (context_.escapeNum()) {
+ readJsonSyntaxChar(STRING_DELIMITER);
+ }
+ auto str = readJsonNumericChars();
+ T num;
+ try {
+ num = to!T(str);
+ } catch (ConvException e) {
+ throw new TProtocolException(`Expected numeric value, got "` ~ str ~ `".`,
+ TProtocolException.Type.INVALID_DATA);
+ }
+ if (context_.escapeNum()) {
+ readJsonSyntaxChar(STRING_DELIMITER);
+ }
+ return num;
+ }
+
+ void readJsonObjectBegin() {
+ context_.read(reader_);
+ readJsonSyntaxChar(OBJECT_BEGIN);
+ pushContext(new PairContext());
+ }
+
+ void readJsonObjectEnd() {
+ readJsonSyntaxChar(OBJECT_END);
+ popContext();
+ }
+
+ void readJsonArrayBegin() {
+ context_.read(reader_);
+ readJsonSyntaxChar(ARRAY_BEGIN);
+ pushContext(new ListContext());
+ }
+
+ void readJsonArrayEnd() {
+ readJsonSyntaxChar(ARRAY_END);
+ popContext();
+ }
+
+ static {
+ final class LookaheadReader {
+ this(Transport trans) {
+ trans_ = trans;
+ }
+
+ ubyte[1] read() {
+ if (hasData_) {
+ hasData_ = false;
+ } else {
+ trans_.readAll(data_);
+ }
+ return data_;
+ }
+
+ ubyte[1] peek() {
+ if (!hasData_) {
+ trans_.readAll(data_);
+ hasData_ = true;
+ }
+ return data_;
+ }
+
+ private:
+ Transport trans_;
+ bool hasData_;
+ ubyte[1] data_;
+ }
+
+ /*
+ * Class to serve as base Json context and as base class for other context
+ * implementations
+ */
+ class Context {
+ /**
+ * Write context data to the transport. Default is to do nothing.
+ */
+ void write(Transport trans) {}
+
+ /**
+ * Read context data from the transport. Default is to do nothing.
+ */
+ void read(LookaheadReader reader) {}
+
+ /**
+ * Return true if numbers need to be escaped as strings in this context.
+ * Default behavior is to return false.
+ */
+ bool escapeNum() @property {
+ return false;
+ }
+ }
+
+ // Context class for object member key-value pairs
+ class PairContext : Context {
+ this() {
+ first_ = true;
+ colon_ = true;
+ }
+
+ override void write(Transport trans) {
+ if (first_) {
+ first_ = false;
+ colon_ = true;
+ } else {
+ trans.write(colon_ ? PAIR_SEP : ELEM_SEP);
+ colon_ = !colon_;
+ }
+ }
+
+ override void read(LookaheadReader reader) {
+ if (first_) {
+ first_ = false;
+ colon_ = true;
+ } else {
+ auto ch = (colon_ ? PAIR_SEP : ELEM_SEP);
+ colon_ = !colon_;
+ return readSyntaxChar(reader, ch);
+ }
+ }
+
+ // Numbers must be turned into strings if they are the key part of a pair
+ override bool escapeNum() @property {
+ return colon_;
+ }
+
+ private:
+ bool first_;
+ bool colon_;
+ }
+
+ class ListContext : Context {
+ this() {
+ first_ = true;
+ }
+
+ override void write(Transport trans) {
+ if (first_) {
+ first_ = false;
+ } else {
+ trans.write(ELEM_SEP);
+ }
+ }
+
+ override void read(LookaheadReader reader) {
+ if (first_) {
+ first_ = false;
+ } else {
+ readSyntaxChar(reader, ELEM_SEP);
+ }
+ }
+
+ private:
+ bool first_;
+ }
+
+ // Read 1 character from the transport trans and verify that it is the
+ // expected character ch.
+ // Throw a protocol exception if it is not.
+ void readSyntaxChar(LookaheadReader reader, ubyte[1] ch) {
+ auto ch2 = reader.read();
+ if (ch2 != ch) {
+ throw new TProtocolException("Expected '" ~ cast(char)ch[0] ~ "', got '" ~
+ cast(char)ch2[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
+ }
+ }
+ }
+
+ // Probably need to implement a better stack at some point.
+ Context[] contextStack_;
+ Context context_;
+
+ Transport trans_;
+ LookaheadReader reader_;
+}
+
+/**
+ * TJsonProtocol construction helper to avoid having to explicitly specify
+ * the transport type, i.e. to allow the constructor being called using IFTI
+ * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla
+ * enhancement requet 6082)).
+ */
+TJsonProtocol!Transport tJsonProtocol(Transport)(Transport trans,
+ int containerSizeLimit = 0, int stringSizeLimit = 0
+) if (isTTransport!Transport) {
+ return new TJsonProtocol!Transport(trans, containerSizeLimit, stringSizeLimit);
+}
+
+unittest {
+ import std.exception;
+ import thrift.transport.memory;
+
+ // Check the message header format.
+ auto buf = new TMemoryBuffer;
+ auto json = tJsonProtocol(buf);
+ json.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0));
+ json.writeMessageEnd();
+
+ auto header = new ubyte[13];
+ buf.readAll(header);
+ enforce(cast(char[])header == `[1,"foo",1,0]`);
+}
+
+unittest {
+ import std.exception;
+ import thrift.transport.memory;
+
+ // Check that short binary data is read correctly (the Thrift JSON format
+ // does not include padding chars in the Base64 encoded data).
+ auto buf = new TMemoryBuffer;
+ auto json = tJsonProtocol(buf);
+ json.writeBinary([1, 2]);
+ json.reset();
+ enforce(json.readBinary() == [1, 2]);
+}
+
+unittest {
+ import thrift.internal.test.protocol;
+ testContainerSizeLimit!(TJsonProtocol!())();
+ testStringSizeLimit!(TJsonProtocol!())();
+}
+
+/**
+ * TProtocolFactory creating a TJsonProtocol instance for passed in
+ * transports.
+ *
+ * The optional Transports template tuple parameter can be used to specify
+ * one or more TTransport implementations to specifically instantiate
+ * TJsonProtocol for. If the actual transport types encountered at
+ * runtime match one of the transports in the list, a specialized protocol
+ * instance is created. Otherwise, a generic TTransport version is used.
+ */
+class TJsonProtocolFactory(Transports...) if (
+ allSatisfy!(isTTransport, Transports)
+) : TProtocolFactory {
+ TProtocol getProtocol(TTransport trans) const {
+ foreach (Transport; TypeTuple!(Transports, TTransport)) {
+ auto concreteTrans = cast(Transport)trans;
+ if (concreteTrans) {
+ auto p = new TJsonProtocol!Transport(concreteTrans);
+ return p;
+ }
+ }
+ throw new TProtocolException(
+ "Passed null transport to TJsonProtocolFactoy.");
+ }
+}
+
+private {
+ immutable ubyte[1] OBJECT_BEGIN = '{';
+ immutable ubyte[1] OBJECT_END = '}';
+ immutable ubyte[1] ARRAY_BEGIN = '[';
+ immutable ubyte[1] ARRAY_END = ']';
+ immutable ubyte[1] NEWLINE = '\n';
+ immutable ubyte[1] PAIR_SEP = ':';
+ immutable ubyte[1] ELEM_SEP = ',';
+ immutable ubyte[1] BACKSLASH = '\\';
+ immutable ubyte[1] STRING_DELIMITER = '"';
+ immutable ubyte[1] ZERO_CHAR = '0';
+ immutable ubyte[1] ESCAPE_CHAR = 'u';
+ immutable ubyte[4] ESCAPE_PREFIX = cast(ubyte[4])r"\u00";
+
+ enum THRIFT_JSON_VERSION = 1;
+
+ immutable NAN_STRING = "NaN";
+ immutable INFINITY_STRING = "Infinity";
+ immutable NEG_INFINITY_STRING = "-Infinity";
+
+ string getNameFromTType(TType typeID) {
+ final switch (typeID) {
+ case TType.BOOL:
+ return "tf";
+ case TType.BYTE:
+ return "i8";
+ case TType.I16:
+ return "i16";
+ case TType.I32:
+ return "i32";
+ case TType.I64:
+ return "i64";
+ case TType.DOUBLE:
+ return "dbl";
+ case TType.STRING:
+ return "str";
+ case TType.STRUCT:
+ return "rec";
+ case TType.MAP:
+ return "map";
+ case TType.LIST:
+ return "lst";
+ case TType.SET:
+ return "set";
+ }
+ }
+
+ TType getTTypeFromName(string name) {
+ TType result;
+ if (name.length > 1) {
+ switch (name[0]) {
+ case 'd':
+ result = TType.DOUBLE;
+ break;
+ case 'i':
+ switch (name[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;
+ default:
+ // Do nothing.
+ }
+ break;
+ case 'l':
+ result = TType.LIST;
+ break;
+ case 'm':
+ result = TType.MAP;
+ break;
+ case 'r':
+ result = TType.STRUCT;
+ break;
+ case 's':
+ if (name[1] == 't') {
+ result = TType.STRING;
+ }
+ else if (name[1] == 'e') {
+ result = TType.SET;
+ }
+ break;
+ case 't':
+ result = TType.BOOL;
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+ if (result == TType.STOP) {
+ throw new TProtocolException("Unrecognized type",
+ TProtocolException.Type.NOT_IMPLEMENTED);
+ }
+ return result;
+ }
+
+ // This table describes the handling for the first 0x30 characters
+ // 0 : escape using "\u00xx" notation
+ // 1 : just output index
+ // <other> : escape using "\<other>" notation
+ immutable ubyte[0x30] kJsonCharTable = [
+ // 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
+ ];
+
+ // This string's characters must match up with the elements in kEscapeCharVals.
+ // I don't have '/' on this list even though it appears on www.json.org --
+ // it is not in the RFC
+ immutable kEscapeChars = cast(ubyte[7]) `"\\bfnrt`;
+
+ // The elements of this array must match up with the sequence of characters in
+ // kEscapeChars
+ immutable ubyte[7] kEscapeCharVals = [
+ '"', '\\', '\b', '\f', '\n', '\r', '\t',
+ ];
+
+ // Return the integer value of a hex character ch.
+ // Throw a protocol exception if the character is not [0-9a-f].
+ ubyte hexVal(ubyte ch) {
+ if ((ch >= '0') && (ch <= '9')) {
+ return cast(ubyte)(ch - '0');
+ } else if ((ch >= 'a') && (ch <= 'f')) {
+ return cast(ubyte)(ch - 'a' + 10);
+ }
+ else {
+ throw new TProtocolException("Expected hex val ([0-9a-f]), got '" ~
+ ch ~ "'.", TProtocolException.Type.INVALID_DATA);
+ }
+ }
+
+ // Return the hex character representing the integer val. The value is masked
+ // to make sure it is in the correct range.
+ ubyte hexChar(ubyte val) {
+ val &= 0x0F;
+ if (val < 10) {
+ return cast(ubyte)(val + '0');
+ } else {
+ return cast(ubyte)(val - 10 + 'a');
+ }
+ }
+
+ // Return true if the character ch is in [-+0-9.Ee]; false otherwise
+ bool isJsonNumeric(ubyte ch) {
+ switch (ch) {
+ 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;
+ default:
+ return false;
+ }
+ }
+}
Added: thrift/trunk/lib/d/src/thrift/protocol/processor.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/protocol/processor.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/protocol/processor.d (added)
+++ thrift/trunk/lib/d/src/thrift/protocol/processor.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+module thrift.protocol.processor;
+
+// Use selective import once DMD @@BUG314@@ is fixed.
+import std.variant /+ : Variant +/;
+import thrift.protocol.base;
+import thrift.transport.base;
+
+/**
+ * A processor is a generic object which operates upon an input stream and
+ * writes to some output stream.
+ *
+ * The definition of this object is loose, though the typical case is for some
+ * sort of server that either generates responses to an input stream or
+ * forwards data from one pipe onto another.
+ *
+ * An implementation can optionally allow one or more TProcessorEventHandlers
+ * to be attached, providing an interface to hook custom code into the
+ * handling process, which can be used e.g. for gathering statistics.
+ */
+interface TProcessor {
+ ///
+ bool process(TProtocol iprot, TProtocol oprot,
+ Variant connectionContext = Variant()
+ ) in {
+ assert(iprot);
+ assert(oprot);
+ }
+
+ ///
+ final bool process(TProtocol prot, Variant connectionContext = Variant()) {
+ return process(prot, prot, connectionContext);
+ }
+}
+
+/**
+ * Handles events from a processor.
+ */
+interface TProcessorEventHandler {
+ /**
+ * Called before calling other callback methods.
+ *
+ * Expected to return some sort of »call context«, which is passed to all
+ * other callbacks for that function invocation.
+ */
+ Variant createContext(string methodName, Variant connectionContext);
+
+ /**
+ * Called when handling the method associated with a context has been
+ * finished â can be used to perform clean up work.
+ */
+ void deleteContext(Variant callContext, string methodName);
+
+ /**
+ * Called before reading arguments.
+ */
+ void preRead(Variant callContext, string methodName);
+
+ /**
+ * Called between reading arguments and calling the handler.
+ */
+ void postRead(Variant callContext, string methodName);
+
+ /**
+ * Called between calling the handler and writing the response.
+ */
+ void preWrite(Variant callContext, string methodName);
+
+ /**
+ * Called after writing the response.
+ */
+ void postWrite(Variant callContext, string methodName);
+
+ /**
+ * Called when handling a one-way function call is completed successfully.
+ */
+ void onewayComplete(Variant callContext, string methodName);
+
+ /**
+ * Called if the handler throws an undeclared exception.
+ */
+ void handlerError(Variant callContext, string methodName, Exception e);
+}
+
+struct TConnectionInfo {
+ /// The input and output protocols.
+ TProtocol input;
+ TProtocol output; /// Ditto.
+
+ /// The underlying transport used for the connection
+ /// This is the transport that was returned by TServerTransport.accept(),
+ /// and it may be different than the transport pointed to by the input and
+ /// output protocols.
+ TTransport transport;
+}
+
+interface TProcessorFactory {
+ /**
+ * Get the TProcessor to use for a particular connection.
+ *
+ * This method is always invoked in the same thread that the connection was
+ * accepted on, which is always the same thread for all current server
+ * implementations.
+ */
+ TProcessor getProcessor(ref const(TConnectionInfo) connInfo);
+}
+
+/**
+ * The default processor factory which always returns the same instance.
+ */
+class TSingletonProcessorFactory : TProcessorFactory {
+ /**
+ * Creates a new instance.
+ *
+ * Params:
+ * processor = The processor object to return from getProcessor().
+ */
+ this(TProcessor processor) {
+ processor_ = processor;
+ }
+
+ override TProcessor getProcessor(ref const(TConnectionInfo) connInfo) {
+ return processor_;
+ }
+
+private:
+ TProcessor processor_;
+}
Added: thrift/trunk/lib/d/src/thrift/server/base.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/server/base.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/server/base.d (added)
+++ thrift/trunk/lib/d/src/thrift/server/base.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+module thrift.server.base;
+
+import std.variant : Variant;
+import thrift.protocol.base;
+import thrift.protocol.binary;
+import thrift.protocol.processor;
+import thrift.server.transport.base;
+import thrift.transport.base;
+import thrift.util.cancellation;
+
+/**
+ * Base class for all Thrift servers.
+ *
+ * By setting the eventHandler property to a TServerEventHandler
+ * implementation, custom code can be integrated into the processing pipeline,
+ * which can be used e.g. for gathering statistics.
+ */
+class TServer {
+ /**
+ * Starts serving.
+ *
+ * Blocks until the server finishes, i.e. a serious problem occured or the
+ * cancellation request has been triggered.
+ *
+ * Server implementations are expected to implement cancellation in a best-
+ * effort way â usually, it should be possible to immediately stop accepting
+ * connections and return after all currently active clients have been
+ * processed, but this might not be the case for every conceivable
+ * implementation.
+ */
+ abstract void serve(TCancellation cancellation = null);
+
+ /// The server event handler to notify. Null by default.
+ TServerEventHandler eventHandler;
+
+protected:
+ this(
+ TProcessor processor,
+ TServerTransport serverTransport,
+ TTransportFactory transportFactory,
+ TProtocolFactory protocolFactory
+ ) {
+ this(processor, serverTransport, transportFactory, transportFactory,
+ protocolFactory, protocolFactory);
+ }
+
+ this(
+ TProcessorFactory processorFactory,
+ TServerTransport serverTransport,
+ TTransportFactory transportFactory,
+ TProtocolFactory protocolFactory
+ ) {
+ this(processorFactory, serverTransport, transportFactory, transportFactory,
+ protocolFactory, protocolFactory);
+ }
+
+ this(
+ TProcessor processor,
+ TServerTransport serverTransport,
+ TTransportFactory inputTransportFactory,
+ TTransportFactory outputTransportFactory,
+ TProtocolFactory inputProtocolFactory,
+ TProtocolFactory outputProtocolFactory
+ ) {
+ this(new TSingletonProcessorFactory(processor), serverTransport,
+ inputTransportFactory, outputTransportFactory,
+ inputProtocolFactory, outputProtocolFactory);
+ }
+
+ this(
+ TProcessorFactory processorFactory,
+ TServerTransport serverTransport,
+ TTransportFactory inputTransportFactory,
+ TTransportFactory outputTransportFactory,
+ TProtocolFactory inputProtocolFactory,
+ TProtocolFactory outputProtocolFactory
+ ) {
+ import std.exception;
+ import thrift.base;
+ enforce(inputTransportFactory,
+ new TException("Input transport factory must not be null."));
+ enforce(outputTransportFactory,
+ new TException("Output transport factory must not be null."));
+ enforce(inputProtocolFactory,
+ new TException("Input protocol factory must not be null."));
+ enforce(outputProtocolFactory,
+ new TException("Output protocol factory must not be null."));
+
+ processorFactory_ = processorFactory;
+ serverTransport_ = serverTransport;
+ inputTransportFactory_ = inputTransportFactory;
+ outputTransportFactory_ = outputTransportFactory;
+ inputProtocolFactory_ = inputProtocolFactory;
+ outputProtocolFactory_ = outputProtocolFactory;
+ }
+
+ TProcessorFactory processorFactory_;
+ TServerTransport serverTransport_;
+ TTransportFactory inputTransportFactory_;
+ TTransportFactory outputTransportFactory_;
+ TProtocolFactory inputProtocolFactory_;
+ TProtocolFactory outputProtocolFactory_;
+}
+
+/**
+ * Handles events from a TServer core.
+ */
+interface TServerEventHandler {
+ /**
+ * Called before the server starts accepting connections.
+ */
+ void preServe();
+
+ /**
+ * Called when a new client has connected and processing is about to begin.
+ */
+ Variant createContext(TProtocol input, TProtocol output);
+
+ /**
+ * Called when request handling for a client has been finished â can be used
+ * to perform clean up work.
+ */
+ void deleteContext(Variant serverContext, TProtocol input, TProtocol output);
+
+ /**
+ * Called when the processor for a client call is about to be invoked.
+ */
+ void preProcess(Variant serverContext, TTransport transport);
+}