Merge pull request #1919 from mpajkowski/to_socket_addrs
THRIFT-4995 Use `ToSocketAddrs` for expressing network addresses
diff --git a/CHANGES.md b/CHANGES.md
index 30622e5..e179a63 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -4,7 +4,7 @@
### Breaking Changes
-- [THRIFT-4990](https://issues.apache.org/jira/browse/THRIFT-4990) - Upgrade to .NET Core 3.0
+- [THRIFT-4990](https://issues.apache.org/jira/browse/THRIFT-4990) - Upgrade to .NET Core 3.1 (LTS)
- [THRIFT-4981](https://issues.apache.org/jira/browse/THRIFT-4981) - Remove deprecated netcore bindings from the code base
- [THRIFT-5006](https://issues.apache.org/jira/browse/THRIFT-5006) - Implement DEFAULT_MAX_LENGTH at TFramedTransport
diff --git a/LANGUAGES.md b/LANGUAGES.md
index 923b045..afd7799 100644
--- a/LANGUAGES.md
+++ b/LANGUAGES.md
@@ -315,7 +315,7 @@
<!-- Since -----------------><td>0.2.0</td>
<!-- Build Systems ---------><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td>
<!-- Language Levels -------><td>2.7.12, 3.5.2</td><td>2.7.15, 3.6.8</td>
-<!-- Low-Level Transports --><td><img src="doc/images/cred.png" alt=""/></td><td><img src="doc/images/cred.png" alt=""/></td><td><img src="doc/images/cred.png" alt=""/></td><td><img src="doc/images/cred.png" alt=""/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td>
+<!-- Low-Level Transports --><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cred.png" alt=""/></td><td><img src="doc/images/cred.png" alt=""/></td><td><img src="doc/images/cred.png" alt=""/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td>
<!-- Transport Wrappers ----><td><img src="doc/images/cred.png" alt=""/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td>
<!-- Protocols -------------><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td>
<!-- Servers ---------------><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cgrn.png" alt="Yes"/></td><td><img src="doc/images/cred.png" alt=""/></td><td><img src="doc/images/cred.png" alt=""/></td>
diff --git a/build/docker/README.md b/build/docker/README.md
index e33c4e0..8c8be22 100644
--- a/build/docker/README.md
+++ b/build/docker/README.md
@@ -177,7 +177,6 @@
| d | 2.075.1 | 2.087.0 | |
| dart | 2.0.0 | 2.4.0 | |
| delphi | | | Not in CI |
-| dotnet | 3.0 | 3.0 | |
| erlang | 18.3 | 22.0 | |
| go | 1.10.8 | 1.12.6 | |
| haskell | 7.10.3 | 8.0.2 | |
@@ -185,6 +184,7 @@
| java | 1.8.0_191 | 11.0.3 | |
| js | | | Unsure how to look for version info? |
| lua | | 5.2.4 | Lua 5.3: see THRIFT-4386 |
+| netstd | 3.1 | 3.1 | LTS version |
| nodejs | 6.16.0 | 10.16.0 | |
| ocaml | | 4.05.0 | THRIFT-4517: ocaml 4.02.3 on xenial appears broken |
| perl | 5.22.1 | 5.26.1 | |
diff --git a/build/docker/ubuntu-bionic/Dockerfile b/build/docker/ubuntu-bionic/Dockerfile
index d38901f..79d698f 100644
--- a/build/docker/ubuntu-bionic/Dockerfile
+++ b/build/docker/ubuntu-bionic/Dockerfile
@@ -130,7 +130,7 @@
RUN apt-get install -y --no-install-recommends \
`# dotnet core dependencies` \
- dotnet-sdk-3.0
+ dotnet-sdk-3.1
RUN apt-get install -y --no-install-recommends \
`# Erlang dependencies` \
diff --git a/build/docker/ubuntu-disco/Dockerfile b/build/docker/ubuntu-disco/Dockerfile
index b63fd58..95a2c78 100644
--- a/build/docker/ubuntu-disco/Dockerfile
+++ b/build/docker/ubuntu-disco/Dockerfile
@@ -130,7 +130,7 @@
RUN apt-get install -y --no-install-recommends \
`# dotnet core dependencies` \
- dotnet-sdk-3.0
+ dotnet-sdk-3.1
RUN apt-get install -y --no-install-recommends \
`# Erlang dependencies` \
diff --git a/build/docker/ubuntu-xenial/Dockerfile b/build/docker/ubuntu-xenial/Dockerfile
index 714a80d..8df0887 100644
--- a/build/docker/ubuntu-xenial/Dockerfile
+++ b/build/docker/ubuntu-xenial/Dockerfile
@@ -127,7 +127,7 @@
RUN apt-get install -y --no-install-recommends \
`# dotnet core dependencies` \
- dotnet-sdk-3.0
+ dotnet-sdk-3.1
RUN apt-get install -y --no-install-recommends \
`# Erlang dependencies` \
diff --git a/compiler/cpp/src/thrift/generate/t_cpp_generator.cc b/compiler/cpp/src/thrift/generate/t_cpp_generator.cc
index d66b6e6..896c43f 100644
--- a/compiler/cpp/src/thrift/generate/t_cpp_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_cpp_generator.cc
@@ -504,6 +504,7 @@
* @param ttypedef The type definition
*/
void t_cpp_generator::generate_typedef(t_typedef* ttypedef) {
+ generate_java_doc(f_types_, ttypedef);
f_types_ << indent() << "typedef " << type_name(ttypedef->get_type(), true) << " "
<< ttypedef->get_symbolic() << ";" << endl << endl;
}
@@ -524,6 +525,7 @@
} else {
f << "," << endl;
}
+ generate_java_doc(f, *c_iter);
indent(f) << prefix << (*c_iter)->get_name() << suffix;
if (include_values) {
f << " = " << (*c_iter)->get_value();
@@ -547,6 +549,7 @@
std::string enum_name = tenum->get_name();
if (!gen_pure_enums_) {
enum_name = "type";
+ generate_java_doc(f_types_, tenum);
f_types_ << indent() << "struct " << tenum->get_name() << " {" << endl;
indent_up();
}
@@ -1075,6 +1078,8 @@
out << endl;
+ generate_java_doc(out, tstruct);
+
// Open struct def
out << indent() << "class " << tstruct->get_name() << extends << " {" << endl << indent()
<< " public:" << endl << endl;
@@ -1147,6 +1152,7 @@
// Declare all fields
for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+ generate_java_doc(out, *m_iter);
indent(out) << declare_field(*m_iter,
false,
(pointers && !(*m_iter)->get_type()->is_xception()),
@@ -1933,6 +1939,9 @@
if (style == "CobCl" && gen_templates_) {
f_header_ << "template <class Protocol_>" << endl;
}
+
+ generate_java_doc(f_header_, tservice);
+
f_header_ << "class " << service_if_name << extends << " {" << endl << " public:" << endl;
indent_up();
f_header_ << indent() << "virtual ~" << service_if_name << "() {}" << endl;
@@ -2225,6 +2234,7 @@
indent_up();
for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+ generate_java_doc(f_header_, *f_iter);
t_struct* arglist = (*f_iter)->get_arglist();
const vector<t_field*>& args = arglist->get_members();
vector<t_field*>::const_iterator a_iter;
@@ -2438,6 +2448,8 @@
}
if (style == "Cob") {
+ generate_java_doc(f_header_, tservice);
+
f_header_ << indent()
<< "::std::shared_ptr< ::apache::thrift::async::TAsyncChannel> getChannel() {" << endl
<< indent() << " return " << _this << "channel_;" << endl << indent() << "}" << endl;
@@ -2449,6 +2461,7 @@
vector<t_function*> functions = tservice->get_functions();
vector<t_function*>::const_iterator f_iter;
for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+ generate_java_doc(f_header_, *f_iter);
indent(f_header_) << function_signature(*f_iter, ifstyle) << ";" << endl;
// TODO(dreiss): Use private inheritance to avoid generating thise in cob-style.
if (style == "Concurrent" && !(*f_iter)->is_oneway()) {
diff --git a/compiler/cpp/src/thrift/generate/t_delphi_generator.cc b/compiler/cpp/src/thrift/generate/t_delphi_generator.cc
index 4a2ebda..cffe305 100644
--- a/compiler/cpp/src/thrift/generate/t_delphi_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_delphi_generator.cc
@@ -152,11 +152,13 @@
void generate_delphi_struct_writer_impl(ostream& out,
std::string cls_prefix,
t_struct* tstruct,
- bool is_exception);
+ bool is_exception,
+ bool is_x_factory);
void generate_delphi_struct_result_writer_impl(ostream& out,
std::string cls_prefix,
t_struct* tstruct,
- bool is_exception);
+ bool is_exception,
+ bool is_x_factory);
void generate_delphi_struct_tostring_impl(ostream& out,
std::string cls_prefix,
@@ -169,7 +171,8 @@
void generate_delphi_struct_reader_impl(ostream& out,
std::string cls_prefix,
t_struct* tstruct,
- bool is_exception);
+ bool is_exception,
+ bool is_x_factory);
void generate_delphi_create_exception_impl(ostream& out,
string cls_prefix,
t_struct* tstruct,
@@ -1532,11 +1535,30 @@
indent_impl(out) << "begin" << endl;
indent_up_impl();
indent_impl(out) << "if F" << exception_factory_name << " = nil" << endl;
- indent_impl(out) << "then F" << exception_factory_name << " := T" << exception_factory_name << "Impl.Create;" << endl;
- indent_impl(out) << endl;
+ indent_impl(out) << "then F" << exception_factory_name << " := T" << exception_factory_name << "Impl.Create;" << endl << endl;
indent_impl(out) << "result := F" << exception_factory_name << ";" << endl;
indent_down_impl();
indent_impl(out) << "end;" << endl << endl;
+ indent_impl(out) << "function " << cls_prefix << cls_nm << ".QueryInterface(const IID: TGUID; out Obj): HRESULT;" << endl;
+ indent_impl(out) << "begin" << endl;
+ indent_up_impl();
+ indent_impl(out) << "if GetInterface(IID, Obj)" << endl;
+ indent_impl(out) << "then result := S_OK" << endl;
+ indent_impl(out) << "else result := E_NOINTERFACE;" << endl;
+ indent_down_impl();
+ indent_impl(out) << "end;" << endl << endl;
+ indent_impl(out) << "function " << cls_prefix << cls_nm << "._AddRef: Integer;" << endl;
+ indent_impl(out) << "begin" << endl;
+ indent_up_impl();
+ indent_impl(out) << "result := -1; // not refcounted" << endl;
+ indent_down_impl();
+ indent_impl(out) << "end;" << endl << endl;
+ indent_impl(out) << "function " << cls_prefix << cls_nm << "._Release: Integer;" << endl;
+ indent_impl(out) << "begin" << endl;
+ indent_up_impl();
+ indent_impl(out) << "result := -1; // not refcounted" << endl;
+ indent_down_impl();
+ indent_impl(out) << "end;" << endl << endl;
}
if (tstruct->is_union()) {
@@ -1586,13 +1608,11 @@
}
}
- if ((!is_exception) || is_x_factory) {
- generate_delphi_struct_reader_impl(out, cls_prefix, tstruct, is_exception);
- if (is_result) {
- generate_delphi_struct_result_writer_impl(out, cls_prefix, tstruct, is_exception);
- } else {
- generate_delphi_struct_writer_impl(out, cls_prefix, tstruct, is_exception);
- }
+ generate_delphi_struct_reader_impl(out, cls_prefix, tstruct, is_exception, is_x_factory);
+ if (is_result) {
+ generate_delphi_struct_result_writer_impl(out, cls_prefix, tstruct, is_exception, is_x_factory);
+ } else {
+ generate_delphi_struct_writer_impl(out, cls_prefix, tstruct, is_exception, is_x_factory);
}
generate_delphi_struct_tostring_impl(out, cls_prefix, tstruct, is_exception, is_x_factory);
@@ -1741,7 +1761,7 @@
}
out << "class(";
if (is_exception && (!is_x_factory)) {
- out << "TException";
+ out << "TException, IInterface, IBase, ISupportsToString";
} else {
out << "TInterfacedObject, IBase, ISupportsToString, " << struct_intf_name;
}
@@ -1801,8 +1821,18 @@
}
}
- indent_down();
+ if (is_exception && (!is_x_factory)) {
+ out << endl;
+ indent_down();
+ indent(out) << "strict protected" << endl;
+ indent_up();
+ indent(out) << "function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;" << endl;
+ indent(out) << "function _AddRef: Integer; stdcall;" << endl;
+ indent(out) << "function _Release: Integer; stdcall;" << endl;
+ out << endl;
+ }
+ indent_down();
indent(out) << "public" << endl;
indent_up();
@@ -1825,12 +1855,10 @@
indent(out) << "function " << exception_factory_name << ": " << struct_intf_name << ";" << endl;
}
- if ((!is_exception) || is_x_factory) {
- out << endl;
- indent(out) << "// IBase" << endl;
- indent(out) << "procedure Read( const iprot: IProtocol);" << endl;
- indent(out) << "procedure Write( const oprot: IProtocol);" << endl;
- }
+ out << endl;
+ indent(out) << "// IBase" << endl;
+ indent(out) << "procedure Read( const iprot: IProtocol);" << endl;
+ indent(out) << "procedure Write( const oprot: IProtocol);" << endl;
if (is_exception && is_x_factory) {
out << endl;
@@ -2163,9 +2191,7 @@
indent_impl(s_service_impl) << "begin" << endl;
indent_up_impl();
indent_impl(s_service_impl) << msgvar << " := iprot_.ReadMessageBegin();" << endl;
- indent_impl(s_service_impl) << "if (" << msgvar << ".Type_ = TMessageType.Exception) then"
- << endl;
- indent_impl(s_service_impl) << "begin" << endl;
+ indent_impl(s_service_impl) << "if (" << msgvar << ".Type_ = TMessageType.Exception) then begin" << endl;
indent_up_impl();
indent_impl(s_service_impl) << appexvar << " := TApplicationException.Read(iprot_);" << endl;
indent_impl(s_service_impl) << "iprot_.ReadMessageEnd();" << endl;
@@ -2178,8 +2204,7 @@
indent_impl(s_service_impl) << "iprot_.ReadMessageEnd();" << endl;
if (!(*f_iter)->get_returntype()->is_void()) {
- indent_impl(s_service_impl) << "if (" << retvar << ".__isset_success) then" << endl;
- indent_impl(s_service_impl) << "begin" << endl;
+ indent_impl(s_service_impl) << "if (" << retvar << ".__isset_success) then begin" << endl;
indent_up_impl();
indent_impl(s_service_impl) << "Result := " << retvar << ".Success;" << endl;
t_type* type = (*f_iter)->get_returntype();
@@ -2195,8 +2220,7 @@
vector<t_field*>::const_iterator x_iter;
for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
indent_impl(s_service_impl) << "if (" << retvar << ".__isset_" << prop_name(*x_iter)
- << ") then" << endl;
- indent_impl(s_service_impl) << "begin" << endl;
+ << ") then begin" << endl;
indent_up_impl();
indent_impl(s_service_impl) << exceptvar << " := " << retvar << "." << prop_name(*x_iter)
<< ".CreateException;" << endl;
@@ -2324,8 +2348,7 @@
indent_impl(s_service_impl) << "msg := iprot.ReadMessageBegin();" << endl;
indent_impl(s_service_impl) << "fn := nil;" << endl;
indent_impl(s_service_impl) << "if not processMap_.TryGetValue(msg.Name, fn)" << endl;
- indent_impl(s_service_impl) << "or not Assigned(fn) then" << endl;
- indent_impl(s_service_impl) << "begin" << endl;
+ indent_impl(s_service_impl) << "or not Assigned(fn) then begin" << endl;
indent_up_impl();
indent_impl(s_service_impl) << "TProtocolUtil.Skip(iprot, TType.Struct);" << endl;
indent_impl(s_service_impl) << "iprot.ReadMessageEnd();" << endl;
@@ -2716,8 +2739,7 @@
indent_impl(out) << obj << " := iprot.ReadListBegin();" << endl;
}
- indent_impl(out) << "for " << counter << " := 0 to " << obj << ".Count - 1 do" << endl;
- indent_impl(out) << "begin" << endl;
+ indent_impl(out) << "for " << counter << " := 0 to " << obj << ".Count - 1 do begin" << endl;
indent_up_impl();
if (ttype->is_map()) {
generate_deserialize_map_element(out, is_xception, (t_map*)ttype, name, local_vars);
@@ -2904,20 +2926,17 @@
string iter = tmp("_iter");
if (ttype->is_map()) {
local_vars << " " << iter << ": " << type_name(((t_map*)ttype)->get_key_type()) << ";" << endl;
- indent_impl(out) << "for " << iter << " in " << prefix << ".Keys do" << endl;
- indent_impl(out) << "begin" << endl;
+ indent_impl(out) << "for " << iter << " in " << prefix << ".Keys do begin" << endl;
indent_up_impl();
} else if (ttype->is_set()) {
local_vars << " " << iter << ": " << type_name(((t_set*)ttype)->get_elem_type()) << ";"
<< endl;
- indent_impl(out) << "for " << iter << " in " << prefix << " do" << endl;
- indent_impl(out) << "begin" << endl;
+ indent_impl(out) << "for " << iter << " in " << prefix << " do begin" << endl;
indent_up_impl();
} else if (ttype->is_list()) {
local_vars << " " << iter << ": " << type_name(((t_list*)ttype)->get_elem_type()) << ";"
<< endl;
- indent_impl(out) << "for " << iter << " in " << prefix << " do" << endl;
- indent_impl(out) << "begin" << endl;
+ indent_impl(out) << "for " << iter << " in " << prefix << " do begin" << endl;
indent_up_impl();
}
@@ -3575,7 +3594,8 @@
void t_delphi_generator::generate_delphi_struct_reader_impl(ostream& out,
string cls_prefix,
t_struct* tstruct,
- bool is_exception) {
+ bool is_exception,
+ bool is_x_factory) {
ostringstream local_vars;
ostringstream code_block;
@@ -3604,32 +3624,28 @@
indent_impl(code_block) << "try" << endl;
indent_up_impl();
- indent_impl(code_block) << "while (true) do" << endl;
- indent_impl(code_block) << "begin" << endl;
+ indent_impl(code_block) << "while (true) do begin" << endl;
indent_up_impl();
indent_impl(code_block) << "field_ := iprot.ReadFieldBegin();" << endl;
- indent_impl(code_block) << "if (field_.Type_ = TType.Stop) then" << endl;
- indent_impl(code_block) << "begin" << endl;
- indent_up_impl();
- indent_impl(code_block) << "Break;" << endl;
- indent_down_impl();
- indent_impl(code_block) << "end;" << endl;
+ indent_impl(code_block) << "if (field_.Type_ = TType.Stop) then Break;" << endl;
bool first = true;
for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
if (first) {
+ code_block << endl;
indent_impl(code_block) << "case field_.ID of" << endl;
indent_up_impl();
}
first = false;
if (f_iter != fields.begin()) {
- code_block << ";" << endl;
+ code_block << endl;
}
+
indent_impl(code_block) << (*f_iter)->get_key() << ": begin" << endl;
indent_up_impl();
indent_impl(code_block) << "if (field_.Type_ = " << type_to_enum((*f_iter)->get_type())
@@ -3652,12 +3668,13 @@
indent_down_impl();
indent_impl(code_block) << "end;" << endl;
indent_down_impl();
- indent_impl(code_block) << "end";
+ indent_impl(code_block) << "end;";
}
if (!first) {
code_block << endl;
- indent_impl(code_block) << "else begin" << endl;
+ indent_down_impl();
+ indent_impl(code_block) << "else" << endl;
indent_up_impl();
}
@@ -3666,8 +3683,6 @@
if (!first) {
indent_down_impl();
indent_impl(code_block) << "end;" << endl;
- indent_down_impl();
- indent_impl(code_block) << "end;" << endl;
}
indent_impl(code_block) << "iprot.ReadFieldEnd;" << endl;
@@ -3684,8 +3699,13 @@
indent_impl(code_block) << "end;" << endl;
// all required fields have been read?
+ first = true;
for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
if ((*f_iter)->get_req() == t_field::T_REQUIRED) {
+ if(first) {
+ code_block << endl;
+ first = false;
+ }
indent_impl(code_block) << "if not _req_isset_" << prop_name(*f_iter, is_exception) << endl;
indent_impl(code_block)
<< "then raise TProtocolExceptionInvalidData.Create("
@@ -3693,13 +3713,17 @@
<< endl;
}
}
-
+
+ if( is_exception && (!is_x_factory)) {
+ code_block << endl;
+ indent_impl(code_block) << "UpdateMessageProperty;" << endl;
+ }
indent_down_impl();
indent_impl(code_block) << "end;" << endl << endl;
string cls_nm;
- cls_nm = type_name(tstruct, true, false, is_exception, is_exception);
+ cls_nm = type_name(tstruct, true, is_exception && (!is_x_factory), is_x_factory, is_x_factory);
indent_impl(out) << "procedure " << cls_prefix << cls_nm << ".Read( const iprot: IProtocol);"
<< endl;
@@ -3715,7 +3739,8 @@
void t_delphi_generator::generate_delphi_struct_result_writer_impl(ostream& out,
string cls_prefix,
t_struct* tstruct,
- bool is_exception) {
+ bool is_exception,
+ bool is_x_factory) {
ostringstream local_vars;
ostringstream code_block;
@@ -3759,7 +3784,7 @@
string cls_nm;
- cls_nm = type_name(tstruct, true, false, is_exception, is_exception);
+ cls_nm = type_name(tstruct, true, is_exception && (!is_x_factory), is_x_factory, is_x_factory);
indent_impl(out) << "procedure " << cls_prefix << cls_nm << ".Write( const oprot: IProtocol);"
<< endl;
@@ -3779,7 +3804,8 @@
void t_delphi_generator::generate_delphi_struct_writer_impl(ostream& out,
string cls_prefix,
t_struct* tstruct,
- bool is_exception) {
+ bool is_exception,
+ bool is_x_factory) {
ostringstream local_vars;
ostringstream code_block;
@@ -3847,7 +3873,7 @@
string cls_nm;
- cls_nm = type_name(tstruct, true, false, is_exception, is_exception);
+ cls_nm = type_name(tstruct, true, is_exception && (!is_x_factory), is_x_factory, is_x_factory);
indent_impl(out) << "procedure " << cls_prefix << cls_nm << ".Write( const oprot: IProtocol);"
<< endl;
diff --git a/compiler/cpp/src/thrift/generate/t_go_generator.cc b/compiler/cpp/src/thrift/generate/t_go_generator.cc
index 2093841..a5d55d7 100644
--- a/compiler/cpp/src/thrift/generate/t_go_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_go_generator.cc
@@ -240,6 +240,8 @@
void generate_go_docstring(std::ostream& out, t_doc* tdoc);
+ void parse_go_tags(map<string,string>* tags, const string in);
+
/**
* Helper rendering functions
*/
@@ -1377,7 +1379,9 @@
t_type* fieldType = (*m_iter)->get_type();
string goType = type_to_go_type_with_opt(fieldType, is_pointer_field(*m_iter));
- string gotag = "db:\"" + escape_string((*m_iter)->get_name()) + "\" ";
+
+ map<string,string>tags;
+ tags["db"]=escape_string((*m_iter)->get_name());
// Only add the `omitempty` tag if this field is optional and has no default value.
// Otherwise a proper value like `false` for a bool field will be ommitted from
@@ -1385,16 +1389,25 @@
bool has_default = (*m_iter)->get_value();
bool is_optional = (*m_iter)->get_req() == t_field::T_OPTIONAL;
if (is_optional && !has_default) {
- gotag += "json:\"" + escape_string((*m_iter)->get_name()) + ",omitempty\"";
+ tags["json"]=escape_string((*m_iter)->get_name())+",omitempty";
} else {
- gotag += "json:\"" + escape_string((*m_iter)->get_name()) + "\"";
+ tags["json"]=escape_string((*m_iter)->get_name());
}
- // Check for user override of db and json tags using "go.tag"
+ // Check for user defined tags and them if there are any. User defined tags
+ // can override the above db and json tags.
std::map<string, string>::iterator it = (*m_iter)->annotations_.find("go.tag");
if (it != (*m_iter)->annotations_.end()) {
- gotag = it->second;
+ parse_go_tags(&tags, it->second);
}
+
+ string gotag;
+ for (auto it = tags.begin(); it != tags.end(); ++it) {
+ gotag += it->first + ":\"" + it->second + "\" ";
+ }
+ // Trailing whitespace
+ gotag.resize(gotag.size()-1);
+
indent(out) << publicize((*m_iter)->get_name()) << " " << goType << " `thrift:\""
<< escape_string((*m_iter)->get_name()) << "," << sorted_keys_pos;
if ((*m_iter)->get_req() == t_field::T_REQUIRED) {
@@ -3735,6 +3748,48 @@
throw "INVALID TYPE IN type_to_spec_args: " + ttype->get_name();
}
+// parses a string of struct tags into key/value pairs and writes them to the given map
+void t_go_generator::parse_go_tags(map<string,string>* tags, const string in) {
+ string key;
+ string value;
+
+ size_t mode=0; // 0/1/2 for key/value/whitespace
+ size_t index=0;
+ for (auto it=in.begin(); it<in.end(); ++it, ++index) {
+ // Normally we start in key mode because the IDL is expected to be in
+ // (go.tag="key:\"value\"") format, but if there is leading whitespace
+ // we need to start in whitespace mode.
+ if (index==0 && mode==0 && in[index]==' ') {
+ mode=2;
+ }
+
+ if (mode==2) {
+ if (in[index]==' ') {
+ continue;
+ }
+ mode=0;
+ }
+
+ if (mode==0) {
+ if (in[index]==':') {
+ mode=1;
+ index++;
+ it++;
+ continue;
+ }
+ key+=in[index];
+ } else if (mode==1) {
+ if (in[index]=='"') {
+ (*tags)[key]=value;
+ key=value="";
+ mode=2;
+ continue;
+ }
+ value+=in[index];
+ }
+ }
+}
+
bool format_go_output(const string& file_path) {
// formatting via gofmt deactivated due to THRIFT-3893
diff --git a/compiler/cpp/src/thrift/generate/t_java_generator.cc b/compiler/cpp/src/thrift/generate/t_java_generator.cc
index 7254e12..1805585 100644
--- a/compiler/cpp/src/thrift/generate/t_java_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_java_generator.cc
@@ -1060,11 +1060,15 @@
}
indent(out) << "public void set" << get_cap_name(field->get_name()) << "("
<< type_name(field->get_type()) << " value) {" << endl;
- if (type_can_be_null(field->get_type())) {
- indent(out) << " if (value == null) throw new java.lang.NullPointerException();" << endl;
- }
+
indent(out) << " setField_ = _Fields." << constant_name(field->get_name()) << ";" << endl;
- indent(out) << " value_ = value;" << endl;
+
+ if (type_can_be_null(field->get_type())) {
+ indent(out) << " value_ = java.util.Objects.requireNonNull(value,\"" << "_Fields." << constant_name(field->get_name()) << "\");" << endl;
+ } else {
+ indent(out) << " value_ = value;" << endl;
+ }
+
indent(out) << "}" << endl;
}
}
@@ -2052,8 +2056,8 @@
vector<t_field*>::const_iterator m_iter;
for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
t_field* field = *m_iter;
- indent(out) << "lastComparison = java.lang.Boolean.valueOf(" << generate_isset_check(field)
- << ").compareTo(other." << generate_isset_check(field) << ");" << endl;
+ indent(out) << "lastComparison = java.lang.Boolean.compare(" << generate_isset_check(field)
+ << ", other." << generate_isset_check(field) << ");" << endl;
indent(out) << "if (lastComparison != 0) {" << endl;
indent(out) << " return lastComparison;" << endl;
indent(out) << "}" << endl;
diff --git a/compiler/cpp/src/thrift/generate/t_py_generator.cc b/compiler/cpp/src/thrift/generate/t_py_generator.cc
index 982bca1..e93bbe1 100644
--- a/compiler/cpp/src/thrift/generate/t_py_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_py_generator.cc
@@ -65,6 +65,7 @@
coding_ = "";
gen_dynbaseclass_ = "";
gen_dynbaseclass_exc_ = "";
+ gen_dynbaseclass_frozen_exc_ = "";
gen_dynbaseclass_frozen_ = "";
import_dynbase_ = "";
package_prefix_ = "";
@@ -94,8 +95,11 @@
if( gen_dynbaseclass_exc_.empty()) {
gen_dynbaseclass_exc_ = "TExceptionBase";
}
+ if( gen_dynbaseclass_frozen_exc_.empty()) {
+ gen_dynbaseclass_frozen_exc_ = "TFrozenExceptionBase";
+ }
if( import_dynbase_.empty()) {
- import_dynbase_ = "from thrift.protocol.TBase import TBase, TFrozenBase, TExceptionBase, TTransport\n";
+ import_dynbase_ = "from thrift.protocol.TBase import TBase, TFrozenBase, TExceptionBase, TFrozenExceptionBase, TTransport\n";
}
} else if( iter->first.compare("dynbase") == 0) {
gen_dynbase_ = true;
@@ -104,6 +108,8 @@
gen_dynbaseclass_frozen_ = (iter->second);
} else if( iter->first.compare("dynexc") == 0) {
gen_dynbaseclass_exc_ = (iter->second);
+ } else if( iter->first.compare("dynfrozenexc") == 0) {
+ gen_dynbaseclass_frozen_exc_ = (iter->second);
} else if( iter->first.compare("dynimport") == 0) {
gen_dynbase_ = true;
import_dynbase_ = (iter->second);
@@ -269,7 +275,16 @@
}
static bool is_immutable(t_type* ttype) {
- return ttype->annotations_.find("python.immutable") != ttype->annotations_.end();
+ std::map<std::string, std::string>::iterator it = ttype->annotations_.find("python.immutable");
+
+ if (it == ttype->annotations_.end()) {
+ // Exceptions are immutable by default.
+ return ttype->is_xception();
+ } else if (it->second == "false") {
+ return false;
+ } else {
+ return true;
+ }
}
private:
@@ -288,6 +303,7 @@
std::string gen_dynbaseclass_;
std::string gen_dynbaseclass_frozen_;
std::string gen_dynbaseclass_exc_;
+ std::string gen_dynbaseclass_frozen_exc_;
std::string import_dynbase_;
@@ -742,7 +758,11 @@
out << endl << endl << "class " << tstruct->get_name();
if (is_exception) {
if (gen_dynamic_) {
- out << "(" << gen_dynbaseclass_exc_ << ")";
+ if (is_immutable(tstruct)) {
+ out << "(" << gen_dynbaseclass_frozen_exc_ << ")";
+ } else {
+ out << "(" << gen_dynbaseclass_exc_ << ")";
+ }
} else {
out << "(TException)";
}
@@ -2774,6 +2794,7 @@
" dynbase=CLS Derive generated classes from class CLS instead of TBase.\n"
" dynfrozen=CLS Derive generated immutable classes from class CLS instead of TFrozenBase.\n"
" dynexc=CLS Derive generated exceptions from CLS instead of TExceptionBase.\n"
+ " dynfrozenexc=CLS Derive generated immutable exceptions from CLS instead of TFrozenExceptionBase.\n"
" dynimport='from foo.bar import CLS'\n"
" Add an import line to generated code to find the dynbase class.\n"
" package_prefix='top.package.'\n"
diff --git a/configure.ac b/configure.ac
index f11ab13..64283a8 100755
--- a/configure.ac
+++ b/configure.ac
@@ -505,7 +505,7 @@
if test "$with_netstd" = "yes"; then
AC_PATH_PROG([DOTNETCORE], [dotnet])
if [[ -x "$DOTNETCORE" ]] ; then
- AX_PROG_DOTNETCORE_VERSION( [3.0.0], have_netstd="yes", have_netstd="no")
+ AX_PROG_DOTNETCORE_VERSION( [3.1.0], have_netstd="yes", have_netstd="no")
fi
fi
AM_CONDITIONAL(WITH_DOTNET, [test "$have_netstd" = "yes"])
diff --git a/doc/specs/thrift-compact-protocol.md b/doc/specs/thrift-compact-protocol.md
index 02467dd..6be2a62 100644
--- a/doc/specs/thrift-compact-protocol.md
+++ b/doc/specs/thrift-compact-protocol.md
@@ -97,8 +97,9 @@
### Double encoding
Values of type `double` are first converted to an int64 according to the IEEE 754 floating-point "double format" bit
-layout. Most run-times provide a library to make this conversion. Both the binary protocol as the compact protocol then
-encode the int64 in 8 bytes in big endian order.
+layout. Most run-times provide a library to make this conversion. But while the binary protocol encodes the int64
+in 8 bytes in big endian order, the compact protocol encodes it in little endian order - this is due to an early
+implementation bug that finally became the de-facto standard.
### Boolean encoding
diff --git a/doc/specs/thrift-tconfiguration.md b/doc/specs/thrift-tconfiguration.md
new file mode 100644
index 0000000..e7736cf
--- /dev/null
+++ b/doc/specs/thrift-tconfiguration.md
@@ -0,0 +1,92 @@
+Thrift TConfiguration
+====================================================================
+
+Last Modified: 2019-Dec-03
+
+<!--
+--------------------------------------------------------------------
+
+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.
+
+--------------------------------------------------------------------
+-->
+
+Starting with THRIFT-5021 the need to centralize certain limit settings that are used throughout the whole protocol / transport stack became an obvious need. Previous patches already added some of these limits, but they were not consistently managed and just randomly distributed across the code base.
+
+# Design goals
+
+Following the tradition of similar experience across languages in Thrift, any implementation should meet these design goals:
+
+ * There MUST be a standard CTOR (or equivalent thereof) that provides a default TConfiguration instance.
+ * The default values used SHOULD be implemented as outlined below.
+ * For backwards compatibility, the protocol / transport stack should accept null TConfiguration argument, in which case it should fallback to a default instance automatically. This is to prevent from code-breaking changes as much as possible.
+
+# Implementation
+
+The new TConfiguration class or struct currently holds three settings:
+
+## MaxMessageSize
+
+The MaxMessageSize member defines the maximum size of a (received) message, in bytes. The default value is represented by a constant named DEFAULT_MAX_MESSAGE_SIZE, whose value is 100 * 1024 * 1024 bytes.
+
+## MaxFrameSize
+
+MaxFrameSize limits the size of one frame of data for the TFramedTransport. Since all implementations currently send messages in one frame only if TFramedTransport is used, this value may interfere with MaxMessageSize. In the case of an conflict, the smaller value of the two is used (see remark below). The default value is called DEFAULT_MAX_FRAME_SIZE and has a value of 16384000 bytes.
+
+## RecursionLimit
+
+The RecursionLimit defines, how deep structures may be nested into each other. The default named DEFAULT_RECURSION_DEPTH allows for structures nested up to 64 levels deep.
+
+# Further considerations
+
+## MaxFrameSize vs. MaxMessageSize
+
+The difference between the two options is, that MaxFrameSize exists much longer and it is used only in conjunction with TFramedTransport. In contrast, MaxMessageSize is intended to be a general device to be used with any transport or protocol.
+
+In order to combine both approaches in the most optimal way when using TFramedTransport, it is recommended that the implementation SHOULD update the remaining number of bytes to read based on the received frame size value for the current message.
+
+For calculation purposes it is important to know, that MaxFrameSize excludes the 4 bytes that hold the frame size, while MaxMessageSize is always looking at the whole data. Hence, when updating the remaining read byte count, the known message size should be set to frameSize + sizeof(i32).
+
+## Error handling
+
+If any limit is exceeded, an error should be thrown. Additionally, it may be helpful to check larger memory allocations against the remaining max number of bytes before the allocation attempt takes place.
+
+# Q&A
+
+## Is this a breaking change or not?
+
+There is actually two answers to that question.
+
+1. If done right, it should not be a breaking change vis-á-vis compiling your source code that uses Thrift.
+
+1. It may, however, be a breaking change in the way it limits the accepted overall size of messages or the accepted frame size. This behaviour is by design. If your application hits any of these limits during normal operation, it may require you to instantiate an actual TConfiguration and tweak the settings according to your needs.
+
+## Is splitting the general transport base class into Endpoint and Layered transport base classes necessary?
+
+No, it's not. However, it turned out that this split is a great help when it comes to managing the TConfiguration instance that is passed through the stack. Having two distinct base classes for each of the different transport types not only allows to implement a shared solution for this.
+
+The added benefit is, that a clear distinction between the two transport types makes the Thrift architectural idea much more clear to "newbie" developers.
+
+## I want to contribute an implementation of TConfiguration and I am not sure whether to pick class or struct?
+
+Short answer: Pick whatever is more efficient in the language of your choice.
+
+Technically, remember that the instance is passed down the stack and should therefore be cheap on copying. To ensure this and to make sure all pieces of the protocol / transport stack are really pointing to the same TConfiguration instance, we want to pass the instance **by reference** rather than by value.
+
+For example, in the C# language a class is a suitable choice for this, because classes are naturally reference parameters, while structs are not.
+
diff --git a/lib/cpp/src/thrift/transport/TBufferTransports.cpp b/lib/cpp/src/thrift/transport/TBufferTransports.cpp
index 4bb8713..534067f 100644
--- a/lib/cpp/src/thrift/transport/TBufferTransports.cpp
+++ b/lib/cpp/src/thrift/transport/TBufferTransports.cpp
@@ -372,7 +372,7 @@
}
// Allocate into a new pointer so we don't bork ours if it fails.
- auto* new_buffer = static_cast<uint8_t*>(std::realloc(buffer_, new_size));
+ auto* new_buffer = static_cast<uint8_t*>(std::realloc(buffer_, static_cast<std::size_t>(new_size)));
if (new_buffer == nullptr) {
throw std::bad_alloc();
}
diff --git a/lib/delphi/src/Thrift.Exception.pas b/lib/delphi/src/Thrift.Exception.pas
index 5d15c36..88b1cfe 100644
--- a/lib/delphi/src/Thrift.Exception.pas
+++ b/lib/delphi/src/Thrift.Exception.pas
@@ -29,6 +29,8 @@
type
// base class for all Thrift exceptions
TException = class( SysUtils.Exception)
+ strict private
+ function GetMessageText : string;
public
function Message : string; // hide inherited property: allow read, but prevent accidental writes
procedure UpdateMessageProperty; // update inherited message property with toString()
@@ -45,17 +47,25 @@
// allow read (exception summary), but prevent accidental writes
// read will return the exception summary
begin
- result := Self.ToString;
+ result := Self.GetMessageText;
end;
+
procedure TException.UpdateMessageProperty;
// Update the inherited Message property to better conform to standard behaviour.
// Nice benefit: The IDE is now able to show the exception message again.
begin
- inherited Message := Self.ToString; // produces a summary text
+ inherited Message := Self.GetMessageText;
end;
+function TException.GetMessageText : string;
+// produces a summary text
+begin
+ result := Self.ToString;
+ if (result <> '') and (result[1] = '(')
+ then result := Copy(result,2,Length(result)-2);
+end;
end.
diff --git a/lib/delphi/src/Thrift.Utils.pas b/lib/delphi/src/Thrift.Utils.pas
index ede2656..bc9b460 100644
--- a/lib/delphi/src/Thrift.Utils.pas
+++ b/lib/delphi/src/Thrift.Utils.pas
@@ -97,7 +97,7 @@
THRIFT_MIMETYPE = 'application/x-thrift';
{$IFDEF Win64}
-function InterlockedExchangeAdd64( var Addend : Int64; Value : Int64) : Int64;
+function InterlockedExchangeAdd64( var Addend : Int64; Value : Int64) : Int64;
{$ENDIF}
@@ -289,8 +289,15 @@
var pType : PTypeInfo;
begin
pType := PTypeInfo(TypeInfo(T));
- if Assigned(pType) and (pType^.Kind = tkEnumeration)
- then result := GetEnumName(pType,value)
+ if Assigned(pType)
+ and (pType^.Kind = tkEnumeration)
+ {$IF CompilerVersion >= 23.0} // TODO: Range correct? What we know is that XE does not offer it, but Rio has it
+ and (pType^.TypeData^.MaxValue >= value)
+ and (pType^.TypeData^.MinValue <= value)
+ {$ELSE}
+ and FALSE // THRIFT-5048: pType^.TypeData^ member not supported -> prevent GetEnumName() from reading outside the legal range
+ {$IFEND}
+ then result := GetEnumName( PTypeInfo(pType), value)
else result := IntToStr(Ord(value));
end;
diff --git a/lib/delphi/src/Thrift.pas b/lib/delphi/src/Thrift.pas
index 716e4d2..1926b11 100644
--- a/lib/delphi/src/Thrift.pas
+++ b/lib/delphi/src/Thrift.pas
@@ -23,6 +23,7 @@
uses
SysUtils,
+ Thrift.Utils,
Thrift.Exception,
Thrift.Protocol;
@@ -34,7 +35,7 @@
TApplicationExceptionSpecializedClass = class of TApplicationExceptionSpecialized;
- TApplicationException = class abstract( TException)
+ TApplicationException = class( TException, IBase, ISupportsToString)
public
type
{$SCOPEDENUMS ON}
@@ -52,10 +53,18 @@
UnsupportedClientType
);
{$SCOPEDENUMS OFF}
+ strict private
+ FExceptionType : TExceptionType;
+
+ strict protected
+ function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
+ function _AddRef: Integer; stdcall;
+ function _Release: Integer; stdcall;
+
strict protected
constructor HiddenCreate(const Msg: string);
- class function GetType: TExceptionType; virtual; abstract;
class function GetSpecializedExceptionType(AType: TExceptionType): TApplicationExceptionSpecializedClass;
+
public
// purposefully hide inherited constructor
class function Create(const Msg: string): TApplicationException; overload; deprecated 'Use specialized TApplicationException types (or regenerate from IDL)';
@@ -63,7 +72,10 @@
class function Create( AType: TExceptionType): TApplicationException; overload; deprecated 'Use specialized TApplicationException types (or regenerate from IDL)';
class function Create( AType: TExceptionType; const msg: string): TApplicationException; overload; deprecated 'Use specialized TApplicationException types (or regenerate from IDL)';
- property Type_: TExceptionType read GetType;
+ function Type_: TExceptionType; virtual;
+
+ procedure IBase_Read( const iprot: IProtocol);
+ procedure IBase.Read = IBase_Read;
class function Read( const iprot: IProtocol): TApplicationException;
procedure Write( const oprot: IProtocol );
@@ -71,8 +83,11 @@
// Needed to remove deprecation warning
TApplicationExceptionSpecialized = class abstract (TApplicationException)
+ strict protected
+ class function GetType: TApplicationException.TExceptionType; virtual; abstract;
public
constructor Create(const Msg: string);
+ function Type_: TApplicationException.TExceptionType; override;
end;
TApplicationExceptionUnknown = class (TApplicationExceptionSpecialized)
@@ -163,6 +178,31 @@
Result := GetSpecializedExceptionType(AType).Create(msg);
end;
+
+function TApplicationException.QueryInterface(const IID: TGUID; out Obj): HResult;
+begin
+ if GetInterface(IID, Obj)
+ then result := S_OK
+ else result := E_NOINTERFACE;
+end;
+
+function TApplicationException._AddRef: Integer;
+begin
+ result := -1; // not refcounted
+end;
+
+function TApplicationException._Release: Integer;
+begin
+ result := -1; // not refcounted
+end;
+
+
+function TApplicationException.Type_: TExceptionType;
+begin
+ result := FExceptionType;
+end;
+
+
class function TApplicationException.GetSpecializedExceptionType(AType: TExceptionType): TApplicationExceptionSpecializedClass;
begin
case AType of
@@ -183,52 +223,60 @@
end;
-class function TApplicationException.Read( const iprot: IProtocol): TApplicationException;
+procedure TApplicationException.IBase_Read( const iprot: IProtocol);
var
field : TThriftField;
- msg : string;
- typ : TExceptionType;
struc : TThriftStruct;
begin
- msg := '';
- typ := TExceptionType.Unknown;
struc := iprot.ReadStructBegin;
while ( True ) do
begin
field := iprot.ReadFieldBegin;
- if ( field.Type_ = TType.Stop) then
- begin
+ if ( field.Type_ = TType.Stop) then begin
Break;
end;
case field.Id of
1 : begin
- if ( field.Type_ = TType.String_) then
- begin
- msg := iprot.ReadString;
- end else
- begin
+ if ( field.Type_ = TType.String_) then begin
+ Exception(Self).Message := iprot.ReadString;
+ end else begin
TProtocolUtil.Skip( iprot, field.Type_ );
end;
end;
2 : begin
- if ( field.Type_ = TType.I32) then
- begin
- typ := TExceptionType( iprot.ReadI32 );
- end else
- begin
+ if ( field.Type_ = TType.I32) then begin
+ FExceptionType := TExceptionType( iprot.ReadI32 );
+ end else begin
TProtocolUtil.Skip( iprot, field.Type_ );
end;
- end else
- begin
+ end else begin
TProtocolUtil.Skip( iprot, field.Type_);
end;
end;
iprot.ReadFieldEnd;
end;
iprot.ReadStructEnd;
- Result := GetSpecializedExceptionType(typ).Create(msg);
+end;
+
+
+class function TApplicationException.Read( const iprot: IProtocol): TApplicationException;
+var instance : TApplicationException;
+ base : IBase;
+begin
+ instance := TApplicationException.CreateFmt('',[]);
+ try
+ if Supports( instance, IBase, base) then try
+ base.Read(iprot);
+ finally
+ base := nil; // clear ref before free
+ end;
+
+ result := GetSpecializedExceptionType(instance.Type_).Create( Exception(instance).Message);
+ finally
+ instance.Free;
+ end;
end;
procedure TApplicationException.Write( const oprot: IProtocol);
@@ -240,8 +288,7 @@
Init(field);
oprot.WriteStructBegin( struc );
- if Message <> '' then
- begin
+ if Message <> '' then begin
field.Name := 'message';
field.Type_ := TType.String_;
field.Id := 1;
@@ -254,7 +301,7 @@
field.Type_ := TType.I32;
field.Id := 2;
oprot.WriteFieldBegin(field);
- oprot.WriteI32(Integer(GetType));
+ oprot.WriteI32(Integer(Type_));
oprot.WriteFieldEnd();
oprot.WriteFieldStop();
oprot.WriteStructEnd();
@@ -267,6 +314,12 @@
inherited HiddenCreate(Msg);
end;
+function TApplicationExceptionSpecialized.Type_: TApplicationException.TExceptionType;
+begin
+ result := GetType;
+end;
+
+
{ specialized TApplicationExceptions }
class function TApplicationExceptionUnknownMethod.GetType : TApplicationException.TExceptionType;
diff --git a/lib/delphi/test/skip/idl/skiptest_version_1.thrift b/lib/delphi/test/skip/idl/skiptest_version_1.thrift
index 8353c5e..4221177 100644
--- a/lib/delphi/test/skip/idl/skiptest_version_1.thrift
+++ b/lib/delphi/test/skip/idl/skiptest_version_1.thrift
@@ -24,12 +24,14 @@
const i32 SKIPTESTSERVICE_VERSION = 1
-struct Pong {
- 1 : optional i32 version1
+enum PingPongEnum {
+ PingOne = 0,
+ PongOne = 1,
}
struct Ping {
1 : optional i32 version1
+ 100 : PingPongEnum EnumTest
}
exception PongFailed {
@@ -38,7 +40,7 @@
service SkipTestService {
- void PingPong( 1: Ping pong) throws (444: PongFailed pof);
+ Ping PingPong( 1: Ping ping) throws (444: PongFailed pof);
}
diff --git a/lib/delphi/test/skip/idl/skiptest_version_2.thrift b/lib/delphi/test/skip/idl/skiptest_version_2.thrift
index f3352d3..3ea69f7 100644
--- a/lib/delphi/test/skip/idl/skiptest_version_2.thrift
+++ b/lib/delphi/test/skip/idl/skiptest_version_2.thrift
@@ -24,9 +24,17 @@
const i32 SKIPTESTSERVICE_VERSION = 2
+enum PingPongEnum {
+ PingOne = 0,
+ PongOne = 1,
+ PingTwo = 2,
+ PongTwo = 3,
+}
+
struct Pong {
1 : optional i32 version1
2 : optional i16 version2
+ 100 : PingPongEnum EnumTest
}
struct Ping {
@@ -40,6 +48,7 @@
16 : optional string strVal
17 : optional Pong structVal
18 : optional map< list< Pong>, set< string>> mapVal
+ 100 : PingPongEnum EnumTest
}
exception PingFailed {
diff --git a/lib/delphi/test/skip/skiptest_version1.dpr b/lib/delphi/test/skip/skiptest_version1.dpr
index c97e50b..f7cde2f 100644
--- a/lib/delphi/test/skip/skiptest_version1.dpr
+++ b/lib/delphi/test/skip/skiptest_version1.dpr
@@ -30,6 +30,7 @@
Thrift.Transport in '..\..\src\Thrift.Transport.pas',
Thrift.Protocol in '..\..\src\Thrift.Protocol.pas',
Thrift.Protocol.JSON in '..\..\src\Thrift.Protocol.JSON.pas',
+ Thrift.Protocol.Compact in '..\..\src\Thrift.Protocol.Compact.pas',
Thrift.Collections in '..\..\src\Thrift.Collections.pas',
Thrift.Configuration in '..\..\src\Thrift.Configuration.pas',
Thrift.Server in '..\..\src\Thrift.Server.pas',
@@ -47,6 +48,7 @@
begin
result := TPingImpl.Create;
result.Version1 := Tskiptest_version_1Constants.SKIPTESTSERVICE_VERSION;
+ result.EnumTest := TPingPongEnum.PingOne;
end;
@@ -54,14 +56,16 @@
TDummyServer = class( TInterfacedObject, TSkipTestService.Iface)
protected
// TSkipTestService.Iface
- procedure PingPong(const ping: IPing);
+ function PingPong(const ping: IPing): IPing;
end;
-procedure TDummyServer.PingPong(const ping: IPing);
+function TDummyServer.PingPong(const ping: IPing): IPing;
// TSkipTestService.Iface
begin
Writeln('- performing request from version '+IntToStr(ping.Version1)+' client');
+ Writeln( ping.ToString);
+ result := CreatePing;
end;
@@ -109,6 +113,7 @@
procedure ReadResponse( protfact : IProtocolFactory; fname : string);
var stm : TFileStream;
+ ping : IPing;
proto : IProtocol;
client : TSkipTestService.TClient; // we need access to send/recv_pingpong()
cliRef : IUnknown; // holds the refcount
@@ -116,11 +121,11 @@
Writeln('- reading response');
stm := TFileStream.Create( fname+RESPONSE_EXT, fmOpenRead);
try
- // save request data
+ // load request data
proto := CreateProtocol( protfact, stm, TRUE);
client := TSkipTestService.TClient.Create( proto, nil);
cliRef := client as IUnknown;
- client.recv_PingPong;
+ ping := client.recv_PingPong;
finally
client := nil; // not Free!
@@ -164,12 +169,14 @@
procedure Test( protfact : IProtocolFactory; fname : string);
begin
// try to read an existing request
+ Writeln('Reading data file '+fname);
if FileExists( fname + REQUEST_EXT) then begin
ProcessFile( protfact, fname);
ReadResponse( protfact, fname);
end;
// create a new request and try to process
+ Writeln('Writing data file '+fname);
CreateRequest( protfact, fname);
ProcessFile( protfact, fname);
ReadResponse( protfact, fname);
@@ -177,8 +184,9 @@
const
- FILE_BINARY = 'pingpong.bin';
- FILE_JSON = 'pingpong.json';
+ FILE_BINARY = 'pingpong.bin';
+ FILE_JSON = 'pingpong.json';
+ FILE_COMPACT = 'pingpong.compact';
begin
try
Writeln( 'Delphi SkipTest '+IntToStr(Tskiptest_version_1Constants.SKIPTESTSERVICE_VERSION)+' using '+Thrift.Version);
@@ -192,6 +200,10 @@
Test( TJSONProtocolImpl.TFactory.Create, FILE_JSON);
Writeln;
+ Writeln('Compact protocol');
+ Test( TCompactProtocolImpl.TFactory.Create, FILE_COMPACT);
+
+ Writeln;
Writeln('Test completed without errors.');
Writeln;
Write('Press ENTER to close ...'); Readln;
diff --git a/lib/delphi/test/skip/skiptest_version2.dpr b/lib/delphi/test/skip/skiptest_version2.dpr
index 07c2c9a..478ea7c 100644
--- a/lib/delphi/test/skip/skiptest_version2.dpr
+++ b/lib/delphi/test/skip/skiptest_version2.dpr
@@ -30,6 +30,7 @@
Thrift.Transport in '..\..\src\Thrift.Transport.pas',
Thrift.Protocol in '..\..\src\Thrift.Protocol.pas',
Thrift.Protocol.JSON in '..\..\src\Thrift.Protocol.JSON.pas',
+ Thrift.Protocol.Compact in '..\..\src\Thrift.Protocol.Compact.pas',
Thrift.Collections in '..\..\src\Thrift.Collections.pas',
Thrift.Configuration in '..\..\src\Thrift.Configuration.pas',
Thrift.Server in '..\..\src\Thrift.Server.pas',
@@ -42,12 +43,15 @@
REQUEST_EXT = '.request';
RESPONSE_EXT = '.response';
+
function CreatePing : IPing;
var list : IThriftList<IPong>;
set_ : IHashSet<string>;
begin
result := TPingImpl.Create;
result.Version1 := Tskiptest_version_2Constants.SKIPTESTSERVICE_VERSION;
+ result.EnumTest := TPingPongEnum.PingTwo;
+
result.BoolVal := TRUE;
result.ByteVal := 2;
result.DbVal := 3;
@@ -59,6 +63,7 @@
result.StructVal := TPongImpl.Create;
result.StructVal.Version1 := -1;
result.StructVal.Version2 := -2;
+ result.StructVal.EnumTest := TPingPongEnum.PongTwo;
list := TThriftListImpl<IPong>.Create;
list.Add( result.StructVal);
@@ -87,6 +92,7 @@
// TSkipTestService.Iface
begin
Writeln('- performing request from version '+IntToStr(ping.Version1)+' client');
+ Writeln( ping.ToString);
result := CreatePing;
end;
@@ -143,7 +149,7 @@
Writeln('- reading response');
stm := TFileStream.Create( fname+RESPONSE_EXT, fmOpenRead);
try
- // save request data
+ // load request data
proto := CreateProtocol( protfact, stm, TRUE);
client := TSkipTestService.TClient.Create( proto, nil);
cliRef := client as IUnknown;
@@ -191,12 +197,16 @@
procedure Test( protfact : IProtocolFactory; fname : string);
begin
// try to read an existing request
+ Writeln;
+ Writeln('Reading data file '+fname);
if FileExists( fname + REQUEST_EXT) then begin
ProcessFile( protfact, fname);
ReadResponse( protfact, fname);
end;
// create a new request and try to process
+ Writeln;
+ Writeln('Writing data file '+fname);
CreateRequest( protfact, fname);
ProcessFile( protfact, fname);
ReadResponse( protfact, fname);
@@ -204,8 +214,9 @@
const
- FILE_BINARY = 'pingpong.bin';
- FILE_JSON = 'pingpong.json';
+ FILE_BINARY = 'pingpong.bin';
+ FILE_JSON = 'pingpong.json';
+ FILE_COMPACT = 'pingpong.compact';
begin
try
Writeln( 'Delphi SkipTest '+IntToStr(Tskiptest_version_2Constants.SKIPTESTSERVICE_VERSION)+' using '+Thrift.Version);
@@ -219,6 +230,10 @@
Test( TJSONProtocolImpl.TFactory.Create, FILE_JSON);
Writeln;
+ Writeln('Compact protocol');
+ Test( TCompactProtocolImpl.TFactory.Create, FILE_COMPACT);
+
+ Writeln;
Writeln('Test completed without errors.');
Writeln;
Write('Press ENTER to close ...'); Readln;
diff --git a/lib/go/test/GoTagTest.thrift b/lib/go/test/GoTagTest.thrift
index 5667c6e..4b6ac31 100644
--- a/lib/go/test/GoTagTest.thrift
+++ b/lib/go/test/GoTagTest.thrift
@@ -19,7 +19,7 @@
struct tagged {
1: string string_thing,
- 2: i64 int_thing (go.tag = "json:\"int_thing,string\""),
+ 2: i64 int_thing (go.tag = "json:\"custom_thing\" mykey:\"myvalue\""),
3: optional i64 optional_int_thing
4: optional bool optional_bool_thing = false
}
diff --git a/lib/go/test/tests/gotag_test.go b/lib/go/test/tests/gotag_test.go
index ff2f14e..4cbea56 100644
--- a/lib/go/test/tests/gotag_test.go
+++ b/lib/go/test/tests/gotag_test.go
@@ -38,8 +38,16 @@
s := gotagtest.Tagged{}
st := reflect.TypeOf(s)
field, ok := st.FieldByName("IntThing")
- if !ok || field.Tag.Get("json") != "int_thing,string" {
- t.Error("Unexpected custom tag value")
+ if !ok {
+ t.Error("Missing field IntThing")
+ return
+ }
+
+ if v := field.Tag.Get("json"); v != "custom_thing" {
+ t.Errorf("Expected custom_thing for tag json, got %s", v)
+ }
+ if v := field.Tag.Get("mykey"); v != "myvalue" {
+ t.Errorf("Expected myvalue for tag mykey, got %s", v)
}
}
diff --git a/lib/java/src/org/apache/thrift/server/TSaslNonblockingServer.java b/lib/java/src/org/apache/thrift/server/TSaslNonblockingServer.java
new file mode 100644
index 0000000..89dbb78
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/server/TSaslNonblockingServer.java
@@ -0,0 +1,480 @@
+/*
+ * 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 org.apache.thrift.server;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import javax.security.auth.callback.CallbackHandler;
+
+import org.apache.thrift.TProcessor;
+import org.apache.thrift.transport.TNonblockingServerSocket;
+import org.apache.thrift.transport.TNonblockingServerTransport;
+import org.apache.thrift.transport.TNonblockingTransport;
+import org.apache.thrift.transport.TTransportException;
+import org.apache.thrift.transport.sasl.NonblockingSaslHandler;
+import org.apache.thrift.transport.sasl.NonblockingSaslHandler.Phase;
+import org.apache.thrift.transport.sasl.TBaseSaslProcessorFactory;
+import org.apache.thrift.transport.sasl.TSaslProcessorFactory;
+import org.apache.thrift.transport.sasl.TSaslServerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * TServer with sasl support, using asynchronous execution and nonblocking io.
+ */
+public class TSaslNonblockingServer extends TServer {
+ private static final Logger LOGGER = LoggerFactory.getLogger(TSaslNonblockingServer.class);
+
+ private static final int DEFAULT_NETWORK_THREADS = 1;
+ private static final int DEFAULT_AUTHENTICATION_THREADS = 1;
+ private static final int DEFAULT_PROCESSING_THREADS = Runtime.getRuntime().availableProcessors();
+
+ private final AcceptorThread acceptor;
+ private final NetworkThreadPool networkThreadPool;
+ private final ExecutorService authenticationExecutor;
+ private final ExecutorService processingExecutor;
+ private final TSaslServerFactory saslServerFactory;
+ private final TSaslProcessorFactory saslProcessorFactory;
+
+ public TSaslNonblockingServer(Args args) throws IOException {
+ super(args);
+ acceptor = new AcceptorThread((TNonblockingServerSocket) serverTransport_);
+ networkThreadPool = new NetworkThreadPool(args.networkThreads);
+ authenticationExecutor = Executors.newFixedThreadPool(args.saslThreads);
+ processingExecutor = Executors.newFixedThreadPool(args.processingThreads);
+ saslServerFactory = args.saslServerFactory;
+ saslProcessorFactory = args.saslProcessorFactory;
+ }
+
+ @Override
+ public void serve() {
+ if (eventHandler_ != null) {
+ eventHandler_.preServe();
+ }
+ networkThreadPool.start();
+ acceptor.start();
+ setServing(true);
+ }
+
+ /**
+ * Trigger a graceful shutdown, but it does not block to wait for the shutdown to finish.
+ */
+ @Override
+ public void stop() {
+ if (!stopped_) {
+ setServing(false);
+ stopped_ = true;
+ acceptor.wakeup();
+ networkThreadPool.wakeupAll();
+ authenticationExecutor.shutdownNow();
+ processingExecutor.shutdownNow();
+ }
+ }
+
+ /**
+ * Gracefully shut down the server and block until all threads are stopped.
+ *
+ * @throws InterruptedException if is interrupted while waiting for shutdown.
+ */
+ public void shutdown() throws InterruptedException {
+ stop();
+ acceptor.join();
+ for (NetworkThread networkThread : networkThreadPool.networkThreads) {
+ networkThread.join();
+ }
+ while (!authenticationExecutor.isTerminated()) {
+ authenticationExecutor.awaitTermination(10, TimeUnit.SECONDS);
+ }
+ while (!processingExecutor.isTerminated()) {
+ processingExecutor.awaitTermination(10, TimeUnit.SECONDS);
+ }
+ }
+
+ private class AcceptorThread extends Thread {
+
+ private final TNonblockingServerTransport serverTransport;
+ private final Selector acceptSelector;
+
+ private AcceptorThread(TNonblockingServerSocket serverTransport) throws IOException {
+ super("acceptor-thread");
+ this.serverTransport = serverTransport;
+ acceptSelector = Selector.open();
+ serverTransport.registerSelector(acceptSelector);
+ }
+
+ @Override
+ public void run() {
+ try {
+ serverTransport.listen();
+ while (!stopped_) {
+ select();
+ acceptNewConnection();
+ }
+ } catch (TTransportException e) {
+ // Failed to listen.
+ LOGGER.error("Failed to listen on server socket, error " + e.getType(), e);
+ } catch (Throwable e) {
+ // Unexpected errors.
+ LOGGER.error("Unexpected error in acceptor thread.", e);
+ } finally {
+ TSaslNonblockingServer.this.stop();
+ close();
+ }
+ }
+
+ void wakeup() {
+ acceptSelector.wakeup();
+ }
+
+ private void acceptNewConnection() {
+ Iterator<SelectionKey> selectedKeyItr = acceptSelector.selectedKeys().iterator();
+ while (!stopped_ && selectedKeyItr.hasNext()) {
+ SelectionKey selected = selectedKeyItr.next();
+ selectedKeyItr.remove();
+ if (selected.isAcceptable()) {
+ try {
+ while (true) {
+ // Accept all available connections from the backlog.
+ TNonblockingTransport connection = serverTransport.accept();
+ if (connection == null) {
+ break;
+ }
+ if (!networkThreadPool.acceptNewConnection(connection)) {
+ LOGGER.error("Network thread does not accept: " + connection);
+ connection.close();
+ }
+ }
+ } catch (TTransportException e) {
+ LOGGER.warn("Failed to accept incoming connection.", e);
+ }
+ } else {
+ LOGGER.error("Not acceptable selection: " + selected.channel());
+ }
+ }
+ }
+
+ private void select() {
+ try {
+ acceptSelector.select();
+ } catch (IOException e) {
+ LOGGER.error("Failed to select on the server socket.", e);
+ }
+ }
+
+ private void close() {
+ LOGGER.info("Closing acceptor thread.");
+ serverTransport.close();
+ try {
+ acceptSelector.close();
+ } catch (IOException e) {
+ LOGGER.error("Failed to close accept selector.", e);
+ }
+ }
+ }
+
+ private class NetworkThread extends Thread {
+ private final BlockingQueue<TNonblockingTransport> incomingConnections = new LinkedBlockingQueue<>();
+ private final BlockingQueue<NonblockingSaslHandler> stateTransitions = new LinkedBlockingQueue<>();
+ private final Selector ioSelector;
+
+ NetworkThread(String name) throws IOException {
+ super(name);
+ ioSelector = Selector.open();
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (!stopped_) {
+ handleIncomingConnections();
+ handleStateChanges();
+ select();
+ handleIO();
+ }
+ } catch (Throwable e) {
+ LOGGER.error("Unreoverable error in " + getName(), e);
+ } finally {
+ close();
+ }
+ }
+
+ private void handleStateChanges() {
+ while (true) {
+ NonblockingSaslHandler statemachine = stateTransitions.poll();
+ if (statemachine == null) {
+ return;
+ }
+ tryRunNextPhase(statemachine);
+ }
+ }
+
+ private void select() {
+ try {
+ ioSelector.select();
+ } catch (IOException e) {
+ LOGGER.error("Failed to select in " + getName(), e);
+ }
+ }
+
+ private void handleIO() {
+ Iterator<SelectionKey> selectedKeyItr = ioSelector.selectedKeys().iterator();
+ while (!stopped_ && selectedKeyItr.hasNext()) {
+ SelectionKey selected = selectedKeyItr.next();
+ selectedKeyItr.remove();
+ if (!selected.isValid()) {
+ closeChannel(selected);
+ }
+ NonblockingSaslHandler saslHandler = (NonblockingSaslHandler) selected.attachment();
+ if (selected.isReadable()) {
+ saslHandler.handleRead();
+ } else if (selected.isWritable()) {
+ saslHandler.handleWrite();
+ } else {
+ LOGGER.error("Invalid intrest op " + selected.interestOps());
+ closeChannel(selected);
+ continue;
+ }
+ if (saslHandler.isCurrentPhaseDone()) {
+ tryRunNextPhase(saslHandler);
+ }
+ }
+ }
+
+ // The following methods are modifying the registered channel set on the selector, which itself
+ // is not thread safe. Thus we need a lock to protect it from race condition.
+
+ private synchronized void handleIncomingConnections() {
+ while (true) {
+ TNonblockingTransport connection = incomingConnections.poll();
+ if (connection == null) {
+ return;
+ }
+ if (!connection.isOpen()) {
+ LOGGER.warn("Incoming connection is already closed");
+ continue;
+ }
+ try {
+ SelectionKey selectionKey = connection.registerSelector(ioSelector, SelectionKey.OP_READ);
+ if (selectionKey.isValid()) {
+ NonblockingSaslHandler saslHandler = new NonblockingSaslHandler(selectionKey, connection,
+ saslServerFactory, saslProcessorFactory, inputProtocolFactory_, outputProtocolFactory_,
+ eventHandler_);
+ selectionKey.attach(saslHandler);
+ }
+ } catch (IOException e) {
+ LOGGER.error("Failed to register connection for the selector, close it.", e);
+ connection.close();
+ }
+ }
+ }
+
+ private synchronized void close() {
+ LOGGER.warn("Closing " + getName());
+ while (true) {
+ TNonblockingTransport incomingConnection = incomingConnections.poll();
+ if (incomingConnection == null) {
+ break;
+ }
+ incomingConnection.close();
+ }
+ Set<SelectionKey> registered = ioSelector.keys();
+ for (SelectionKey selection : registered) {
+ closeChannel(selection);
+ }
+ try {
+ ioSelector.close();
+ } catch (IOException e) {
+ LOGGER.error("Failed to close io selector " + getName(), e);
+ }
+ }
+
+ private synchronized void closeChannel(SelectionKey selectionKey) {
+ if (selectionKey.attachment() == null) {
+ try {
+ selectionKey.channel().close();
+ } catch (IOException e) {
+ LOGGER.error("Failed to close channel.", e);
+ } finally {
+ selectionKey.cancel();
+ }
+ } else {
+ NonblockingSaslHandler saslHandler = (NonblockingSaslHandler) selectionKey.attachment();
+ saslHandler.close();
+ }
+ }
+
+ private void tryRunNextPhase(NonblockingSaslHandler saslHandler) {
+ Phase nextPhase = saslHandler.getNextPhase();
+ saslHandler.stepToNextPhase();
+ switch (nextPhase) {
+ case EVALUATING_SASL_RESPONSE:
+ authenticationExecutor.submit(new Computation(saslHandler));
+ break;
+ case PROCESSING:
+ processingExecutor.submit(new Computation(saslHandler));
+ break;
+ case CLOSING:
+ saslHandler.runCurrentPhase();
+ break;
+ default: // waiting for next io event for the current state machine
+ }
+ }
+
+ public boolean accept(TNonblockingTransport connection) {
+ if (stopped_) {
+ return false;
+ }
+ if (incomingConnections.offer(connection)) {
+ wakeup();
+ return true;
+ }
+ return false;
+ }
+
+ private void wakeup() {
+ ioSelector.wakeup();
+ }
+
+ private class Computation implements Runnable {
+
+ private final NonblockingSaslHandler statemachine;
+
+ private Computation(NonblockingSaslHandler statemachine) {
+ this.statemachine = statemachine;
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (!statemachine.isCurrentPhaseDone()) {
+ statemachine.runCurrentPhase();
+ }
+ stateTransitions.add(statemachine);
+ wakeup();
+ } catch (Throwable e) {
+ LOGGER.error("Damn it!", e);
+ }
+ }
+ }
+ }
+
+ private class NetworkThreadPool {
+ private final List<NetworkThread> networkThreads;
+ private int accepted = 0;
+
+ NetworkThreadPool(int size) throws IOException {
+ networkThreads = new ArrayList<>(size);
+ int digits = (int) Math.log10(size) + 1;
+ String threadNamePattern = "network-thread-%0" + digits + "d";
+ for (int i = 0; i < size; i++) {
+ networkThreads.add(new NetworkThread(String.format(threadNamePattern, i)));
+ }
+ }
+
+ /**
+ * Round robin new connection among all the network threads.
+ *
+ * @param connection incoming connection.
+ * @return true if the incoming connection is accepted by network thread pool.
+ */
+ boolean acceptNewConnection(TNonblockingTransport connection) {
+ return networkThreads.get((accepted ++) % networkThreads.size()).accept(connection);
+ }
+
+ public void start() {
+ for (NetworkThread thread : networkThreads) {
+ thread.start();
+ }
+ }
+
+ void wakeupAll() {
+ for (NetworkThread networkThread : networkThreads) {
+ networkThread.wakeup();
+ }
+ }
+ }
+
+ public static class Args extends AbstractServerArgs<Args> {
+
+ private int networkThreads = DEFAULT_NETWORK_THREADS;
+ private int saslThreads = DEFAULT_AUTHENTICATION_THREADS;
+ private int processingThreads = DEFAULT_PROCESSING_THREADS;
+ private TSaslServerFactory saslServerFactory = new TSaslServerFactory();
+ private TSaslProcessorFactory saslProcessorFactory;
+
+ public Args(TNonblockingServerTransport transport) {
+ super(transport);
+ }
+
+ public Args networkThreads(int networkThreads) {
+ this.networkThreads = networkThreads <= 0 ? DEFAULT_NETWORK_THREADS : networkThreads;
+ return this;
+ }
+
+ public Args saslThreads(int authenticationThreads) {
+ this.saslThreads = authenticationThreads <= 0 ? DEFAULT_AUTHENTICATION_THREADS : authenticationThreads;
+ return this;
+ }
+
+ public Args processingThreads(int processingThreads) {
+ this.processingThreads = processingThreads <= 0 ? DEFAULT_PROCESSING_THREADS : processingThreads;
+ return this;
+ }
+
+ public Args processor(TProcessor processor) {
+ saslProcessorFactory = new TBaseSaslProcessorFactory(processor);
+ return this;
+ }
+
+ public Args saslProcessorFactory(TSaslProcessorFactory saslProcessorFactory) {
+ if (saslProcessorFactory == null) {
+ throw new NullPointerException("Processor factory cannot be null");
+ }
+ this.saslProcessorFactory = saslProcessorFactory;
+ return this;
+ }
+
+ public Args addSaslMechanism(String mechanism, String protocol, String serverName,
+ Map<String, String> props, CallbackHandler cbh) {
+ saslServerFactory.addSaslMechanism(mechanism, protocol, serverName, props, cbh);
+ return this;
+ }
+
+ public Args saslServerFactory(TSaslServerFactory saslServerFactory) {
+ if (saslServerFactory == null) {
+ throw new NullPointerException("saslServerFactory cannot be null");
+ }
+ this.saslServerFactory = saslServerFactory;
+ return this;
+ }
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/server/TServerEventHandler.java b/lib/java/src/org/apache/thrift/server/TServerEventHandler.java
index f069b9b..3bd7959 100644
--- a/lib/java/src/org/apache/thrift/server/TServerEventHandler.java
+++ b/lib/java/src/org/apache/thrift/server/TServerEventHandler.java
@@ -28,6 +28,10 @@
* about. Your subclass can also store local data that you may care about,
* such as additional "arguments" to these methods (stored in the object
* instance's state).
+ *
+ * TODO: It seems this is a custom code entry point created for some resource management purpose in hive.
+ * But when looking into hive code, we see that the argments of TProtocol and TTransport are never used.
+ * We probably should remove these arguments from all the methods.
*/
public interface TServerEventHandler {
@@ -56,4 +60,4 @@
void processContext(ServerContext serverContext,
TTransport inputTransport, TTransport outputTransport);
-}
\ No newline at end of file
+}
diff --git a/lib/java/src/org/apache/thrift/server/TThreadPoolServer.java b/lib/java/src/org/apache/thrift/server/TThreadPoolServer.java
index 87e8733..e2f31d5 100644
--- a/lib/java/src/org/apache/thrift/server/TThreadPoolServer.java
+++ b/lib/java/src/org/apache/thrift/server/TThreadPoolServer.java
@@ -19,9 +19,8 @@
package org.apache.thrift.server;
-import java.util.Arrays;
-import java.util.List;
import java.util.Random;
+import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
@@ -39,8 +38,7 @@
/**
* Server which uses Java's built in ThreadPool management to spawn off
- * a worker pool that
- *
+ * a worker pool that deals with client connections in blocking way.
*/
public class TThreadPoolServer extends TServer {
private static final Logger LOGGER = LoggerFactory.getLogger(TThreadPoolServer.class.getName());
@@ -109,6 +107,7 @@
// Executor service for handling client connections
private ExecutorService executorService_;
+ private WeakHashMap<WorkerProcess, Boolean> activeWorkers = new WeakHashMap<>();
private final TimeUnit stopTimeoutUnit;
@@ -148,7 +147,7 @@
protected ExecutorService getExecutorService() {
return executorService_;
}
-
+
protected boolean preServe() {
try {
serverTransport_.listen();
@@ -163,7 +162,6 @@
}
stopped_ = false;
setServing(true);
-
return true;
}
@@ -173,13 +171,14 @@
}
execute();
- waitForShutdown();
-
+ if (!waitForShutdown()) {
+ LOGGER.error("Shutdown is not done after " + stopTimeoutVal + stopTimeoutUnit);
+ }
+
setServing(false);
}
-
+
protected void execute() {
- int failureCount = 0;
while (!stopped_) {
try {
TTransport client = serverTransport_.accept();
@@ -190,6 +189,7 @@
while(true) {
try {
executorService_.execute(wp);
+ activeWorkers.put(wp, Boolean.TRUE);
break;
} catch(Throwable t) {
if (t instanceof RejectedExecutionException) {
@@ -226,16 +226,13 @@
}
} catch (TTransportException ttx) {
if (!stopped_) {
- ++failureCount;
LOGGER.warn("Transport error occurred during acceptance of message.", ttx);
}
}
}
}
-
- protected void waitForShutdown() {
- executorService_.shutdown();
+ protected boolean waitForShutdown() {
// Loop until awaitTermination finally does return without a interrupted
// exception. If we don't do this, then we'll shut down prematurely. We want
// to let the executorService clear it's task queue, closing client sockets
@@ -245,18 +242,23 @@
while (timeoutMS >= 0) {
try {
executorService_.awaitTermination(timeoutMS, TimeUnit.MILLISECONDS);
- break;
+ return true;
} catch (InterruptedException ix) {
long newnow = System.currentTimeMillis();
timeoutMS -= (newnow - now);
now = newnow;
}
}
+ return false;
}
public void stop() {
stopped_ = true;
serverTransport_.interrupt();
+ executorService_.shutdown();
+ for (WorkerProcess wp : activeWorkers.keySet()) {
+ wp.stop();
+ }
}
private class WorkerProcess implements Runnable {
@@ -355,5 +357,9 @@
}
return false;
}
+
+ private void stop() {
+ client_.close();
+ }
}
}
diff --git a/lib/java/src/org/apache/thrift/transport/TEOFException.java b/lib/java/src/org/apache/thrift/transport/TEOFException.java
new file mode 100644
index 0000000..b5ae6ef
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/TEOFException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 org.apache.thrift.transport;
+
+/**
+ * End of file, especially, the underlying socket is closed.
+ */
+public class TEOFException extends TTransportException {
+
+ public TEOFException(String message) {
+ super(TTransportException.END_OF_FILE, message);
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/TMemoryTransport.java b/lib/java/src/org/apache/thrift/transport/TMemoryTransport.java
new file mode 100644
index 0000000..f41bc09
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/TMemoryTransport.java
@@ -0,0 +1,81 @@
+/*
+ * 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 org.apache.thrift.transport;
+
+import java.nio.ByteBuffer;
+
+import org.apache.thrift.TByteArrayOutputStream;
+
+/**
+ * In memory transport with separate buffers for input and output.
+ */
+public class TMemoryTransport extends TTransport {
+
+ private final ByteBuffer inputBuffer;
+ private final TByteArrayOutputStream outputBuffer;
+
+ public TMemoryTransport(byte[] input) {
+ inputBuffer = ByteBuffer.wrap(input);
+ outputBuffer = new TByteArrayOutputStream(1024);
+ }
+
+ @Override
+ public boolean isOpen() {
+ return true;
+ }
+
+ /**
+ * Opening on an in memory transport should have no effect.
+ */
+ @Override
+ public void open() {
+ // Do nothing.
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+
+ @Override
+ public int read(byte[] buf, int off, int len) throws TTransportException {
+ int remaining = inputBuffer.remaining();
+ if (remaining < len) {
+ throw new TTransportException(TTransportException.END_OF_FILE,
+ "There's only " + remaining + "bytes, but it asks for " + len);
+ }
+ inputBuffer.get(buf, off, len);
+ return len;
+ }
+
+ @Override
+ public void write(byte[] buf, int off, int len) throws TTransportException {
+ outputBuffer.write(buf, off, len);
+ }
+
+ /**
+ * Get all the bytes written by thrift output protocol.
+ *
+ * @return a byte array.
+ */
+ public TByteArrayOutputStream getOutput() {
+ return outputBuffer;
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/TNonblockingServerSocket.java b/lib/java/src/org/apache/thrift/transport/TNonblockingServerSocket.java
index df37cb0..1631892 100644
--- a/lib/java/src/org/apache/thrift/transport/TNonblockingServerSocket.java
+++ b/lib/java/src/org/apache/thrift/transport/TNonblockingServerSocket.java
@@ -108,7 +108,8 @@
}
}
- protected TNonblockingSocket acceptImpl() throws TTransportException {
+ @Override
+ public TNonblockingSocket accept() throws TTransportException {
if (serverSocket_ == null) {
throw new TTransportException(TTransportException.NOT_OPEN, "No underlying server socket.");
}
@@ -160,4 +161,9 @@
return serverSocket_.getLocalPort();
}
+ // Expose it for test purpose.
+ ServerSocketChannel getServerSocketChannel() {
+ return serverSocketChannel;
+ }
+
}
diff --git a/lib/java/src/org/apache/thrift/transport/TNonblockingServerTransport.java b/lib/java/src/org/apache/thrift/transport/TNonblockingServerTransport.java
index ba45b09..daac0d5 100644
--- a/lib/java/src/org/apache/thrift/transport/TNonblockingServerTransport.java
+++ b/lib/java/src/org/apache/thrift/transport/TNonblockingServerTransport.java
@@ -28,4 +28,12 @@
public abstract class TNonblockingServerTransport extends TServerTransport {
public abstract void registerSelector(Selector selector);
+
+ /**
+ *
+ * @return an incoming connection or null if there is none.
+ * @throws TTransportException
+ */
+ @Override
+ public abstract TNonblockingTransport accept() throws TTransportException;
}
diff --git a/lib/java/src/org/apache/thrift/transport/TNonblockingSocket.java b/lib/java/src/org/apache/thrift/transport/TNonblockingSocket.java
index f86a48b..37a66d6 100644
--- a/lib/java/src/org/apache/thrift/transport/TNonblockingSocket.java
+++ b/lib/java/src/org/apache/thrift/transport/TNonblockingSocket.java
@@ -207,4 +207,9 @@
return socketChannel_.finishConnect();
}
+ @Override
+ public String toString() {
+ return "[remote: " + socketChannel_.socket().getRemoteSocketAddress() +
+ ", local: " + socketChannel_.socket().getLocalAddress() + "]" ;
+ }
}
diff --git a/lib/java/src/org/apache/thrift/transport/TSaslClientTransport.java b/lib/java/src/org/apache/thrift/transport/TSaslClientTransport.java
index 4b1ca0a..5fc7cff 100644
--- a/lib/java/src/org/apache/thrift/transport/TSaslClientTransport.java
+++ b/lib/java/src/org/apache/thrift/transport/TSaslClientTransport.java
@@ -27,6 +27,7 @@
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
+import org.apache.thrift.transport.sasl.NegotiationStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/lib/java/src/org/apache/thrift/transport/TSaslServerTransport.java b/lib/java/src/org/apache/thrift/transport/TSaslServerTransport.java
index 39b81ca..31f309e 100644
--- a/lib/java/src/org/apache/thrift/transport/TSaslServerTransport.java
+++ b/lib/java/src/org/apache/thrift/transport/TSaslServerTransport.java
@@ -31,6 +31,8 @@
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
+import org.apache.thrift.transport.sasl.NegotiationStatus;
+import org.apache.thrift.transport.sasl.TSaslServerDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,29 +52,9 @@
private Map<String, TSaslServerDefinition> serverDefinitionMap = new HashMap<String, TSaslServerDefinition>();
/**
- * Contains all the parameters used to define a SASL server implementation.
- */
- private static class TSaslServerDefinition {
- public String mechanism;
- public String protocol;
- public String serverName;
- public Map<String, String> props;
- public CallbackHandler cbh;
-
- public TSaslServerDefinition(String mechanism, String protocol, String serverName,
- Map<String, String> props, CallbackHandler cbh) {
- this.mechanism = mechanism;
- this.protocol = protocol;
- this.serverName = serverName;
- this.props = props;
- this.cbh = cbh;
- }
- }
-
- /**
* Uses the given underlying transport. Assumes that addServerDefinition is
* called later.
- *
+ *
* @param transport
* Transport underlying this one.
*/
@@ -84,7 +66,7 @@
* Creates a <code>SaslServer</code> using the given SASL-specific parameters.
* See the Java documentation for <code>Sasl.createSaslServer</code> for the
* details of the parameters.
- *
+ *
* @param transport
* The underlying Thrift transport.
*/
diff --git a/lib/java/src/org/apache/thrift/transport/TSaslTransport.java b/lib/java/src/org/apache/thrift/transport/TSaslTransport.java
index 4a453b6..d1a3d31 100644
--- a/lib/java/src/org/apache/thrift/transport/TSaslTransport.java
+++ b/lib/java/src/org/apache/thrift/transport/TSaslTransport.java
@@ -20,8 +20,6 @@
package org.apache.thrift.transport;
import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
@@ -30,6 +28,7 @@
import org.apache.thrift.EncodingUtils;
import org.apache.thrift.TByteArrayOutputStream;
+import org.apache.thrift.transport.sasl.NegotiationStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,39 +51,6 @@
}
/**
- * Status bytes used during the initial Thrift SASL handshake.
- */
- protected static enum NegotiationStatus {
- START((byte)0x01),
- OK((byte)0x02),
- BAD((byte)0x03),
- ERROR((byte)0x04),
- COMPLETE((byte)0x05);
-
- private final byte value;
-
- private static final Map<Byte, NegotiationStatus> reverseMap =
- new HashMap<Byte, NegotiationStatus>();
- static {
- for (NegotiationStatus s : NegotiationStatus.class.getEnumConstants()) {
- reverseMap.put(s.getValue(), s);
- }
- }
-
- private NegotiationStatus(byte val) {
- this.value = val;
- }
-
- public byte getValue() {
- return value;
- }
-
- public static NegotiationStatus byValue(byte val) {
- return reverseMap.get(val);
- }
- }
-
- /**
* Transport underlying this one.
*/
protected TTransport underlyingTransport;
@@ -392,7 +358,7 @@
try {
sasl.dispose();
} catch (SaslException e) {
- // Not much we can do here.
+ LOGGER.warn("Failed to dispose sasl participant.", e);
}
}
@@ -427,9 +393,7 @@
} catch (TTransportException transportException) {
// If there is no-data or no-sasl header in the stream, log the failure, and rethrow.
if (transportException.getType() == TTransportException.END_OF_FILE) {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("No data or no sasl data in the stream during negotiation");
- }
+ LOGGER.debug("No data or no sasl data in the stream during negotiation");
}
throw transportException;
}
diff --git a/lib/java/src/org/apache/thrift/transport/TServerSocket.java b/lib/java/src/org/apache/thrift/transport/TServerSocket.java
index 79f7b7f..eb302fd 100644
--- a/lib/java/src/org/apache/thrift/transport/TServerSocket.java
+++ b/lib/java/src/org/apache/thrift/transport/TServerSocket.java
@@ -121,18 +121,23 @@
}
}
- protected TSocket acceptImpl() throws TTransportException {
+ @Override
+ public TSocket accept() throws TTransportException {
if (serverSocket_ == null) {
throw new TTransportException(TTransportException.NOT_OPEN, "No underlying server socket.");
}
+ Socket result;
try {
- Socket result = serverSocket_.accept();
- TSocket result2 = new TSocket(result);
- result2.setTimeout(clientTimeout_);
- return result2;
- } catch (IOException iox) {
- throw new TTransportException(iox);
+ result = serverSocket_.accept();
+ } catch (Exception e) {
+ throw new TTransportException(e);
}
+ if (result == null) {
+ throw new TTransportException("Blocking server's accept() may not return NULL");
+ }
+ TSocket socket = new TSocket(result);
+ socket.setTimeout(clientTimeout_);
+ return socket;
}
public void close() {
diff --git a/lib/java/src/org/apache/thrift/transport/TServerTransport.java b/lib/java/src/org/apache/thrift/transport/TServerTransport.java
index 424e4fa..55ef0c4 100644
--- a/lib/java/src/org/apache/thrift/transport/TServerTransport.java
+++ b/lib/java/src/org/apache/thrift/transport/TServerTransport.java
@@ -56,18 +56,18 @@
public abstract void listen() throws TTransportException;
- public final TTransport accept() throws TTransportException {
- TTransport transport = acceptImpl();
- if (transport == null) {
- throw new TTransportException("accept() may not return NULL");
- }
- return transport;
- }
+ /**
+ * Accept incoming connection on the server socket. When there is no incoming connection available:
+ * either it should block infinitely in a blocking implementation, either it should return null in
+ * a nonblocking implementation.
+ *
+ * @return new connection
+ * @throws TTransportException if IO error.
+ */
+ public abstract TTransport accept() throws TTransportException;
public abstract void close();
- protected abstract TTransport acceptImpl() throws TTransportException;
-
/**
* Optional method implementation. This signals to the server transport
* that it should break out of any accept() or listen() that it is currently
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/DataFrameHeaderReader.java b/lib/java/src/org/apache/thrift/transport/sasl/DataFrameHeaderReader.java
new file mode 100644
index 0000000..2900df9
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/DataFrameHeaderReader.java
@@ -0,0 +1,47 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+/**
+ * The header for data frame, it only contains a 4-byte payload size.
+ */
+public class DataFrameHeaderReader extends FixedSizeHeaderReader {
+ public static final int PAYLOAD_LENGTH_BYTES = 4;
+
+ private int payloadSize;
+
+ @Override
+ protected int headerSize() {
+ return PAYLOAD_LENGTH_BYTES;
+ }
+
+ @Override
+ protected void onComplete() throws TInvalidSaslFrameException {
+ payloadSize = byteBuffer.getInt(0);
+ if (payloadSize < 0) {
+ throw new TInvalidSaslFrameException("Payload size is negative: " + payloadSize);
+ }
+ }
+
+ @Override
+ public int payloadSize() {
+ return payloadSize;
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/DataFrameReader.java b/lib/java/src/org/apache/thrift/transport/sasl/DataFrameReader.java
new file mode 100644
index 0000000..e6900bb
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/DataFrameReader.java
@@ -0,0 +1,30 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+/**
+ * Frames for thrift (serialized) messages.
+ */
+public class DataFrameReader extends FrameReader<DataFrameHeaderReader> {
+
+ public DataFrameReader() {
+ super(new DataFrameHeaderReader());
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/DataFrameWriter.java b/lib/java/src/org/apache/thrift/transport/sasl/DataFrameWriter.java
new file mode 100644
index 0000000..a2dd15a
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/DataFrameWriter.java
@@ -0,0 +1,60 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import java.nio.ByteBuffer;
+
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.utils.StringUtils;
+
+import static org.apache.thrift.transport.sasl.DataFrameHeaderReader.PAYLOAD_LENGTH_BYTES;
+
+/**
+ * Write frames of thrift messages. It expects an empty/null header to be provided with a payload
+ * to be written out. Non empty headers are considered as error.
+ */
+public class DataFrameWriter extends FrameWriter {
+
+ @Override
+ public void withOnlyPayload(byte[] payload, int offset, int length) {
+ if (!isComplete()) {
+ throw new IllegalStateException("Previsous write is not yet complete, with " +
+ frameBytes.remaining() + " bytes left.");
+ }
+ frameBytes = buildFrameWithPayload(payload, offset, length);
+ }
+
+ @Override
+ protected ByteBuffer buildFrame(byte[] header, int headerOffset, int headerLength,
+ byte[] payload, int payloadOffset, int payloadLength) {
+ if (header != null && headerLength > 0) {
+ throw new IllegalArgumentException("Extra header [" + StringUtils.bytesToHexString(header) +
+ "] offset " + payloadOffset + " length " + payloadLength);
+ }
+ return buildFrameWithPayload(payload, payloadOffset, payloadLength);
+ }
+
+ private ByteBuffer buildFrameWithPayload(byte[] payload, int offset, int length) {
+ byte[] bytes = new byte[PAYLOAD_LENGTH_BYTES + length];
+ EncodingUtils.encodeBigEndian(length, bytes, 0);
+ System.arraycopy(payload, offset, bytes, PAYLOAD_LENGTH_BYTES, length);
+ return ByteBuffer.wrap(bytes);
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/FixedSizeHeaderReader.java b/lib/java/src/org/apache/thrift/transport/sasl/FixedSizeHeaderReader.java
new file mode 100644
index 0000000..1cbc0ac
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/FixedSizeHeaderReader.java
@@ -0,0 +1,74 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TTransportException;
+import org.apache.thrift.utils.StringUtils;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Headers' size should be predefined.
+ */
+public abstract class FixedSizeHeaderReader implements FrameHeaderReader {
+
+ protected final ByteBuffer byteBuffer = ByteBuffer.allocate(headerSize());
+
+ @Override
+ public boolean isComplete() {
+ return !byteBuffer.hasRemaining();
+ }
+
+ @Override
+ public void clear() {
+ byteBuffer.clear();
+ }
+
+ @Override
+ public byte[] toBytes() {
+ if (!isComplete()) {
+ throw new IllegalStateException("Header is not yet complete " + StringUtils.bytesToHexString(byteBuffer.array(), 0, byteBuffer.position()));
+ }
+ return byteBuffer.array();
+ }
+
+ @Override
+ public boolean read(TTransport transport) throws TTransportException {
+ FrameReader.readAvailable(transport, byteBuffer);
+ if (byteBuffer.hasRemaining()) {
+ return false;
+ }
+ onComplete();
+ return true;
+ }
+
+ /**
+ * @return Size of the header.
+ */
+ protected abstract int headerSize();
+
+ /**
+ * Actions (e.g. validation) to carry out when the header is complete.
+ *
+ * @throws TTransportException
+ */
+ protected abstract void onComplete() throws TTransportException;
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/FrameHeaderReader.java b/lib/java/src/org/apache/thrift/transport/sasl/FrameHeaderReader.java
new file mode 100644
index 0000000..f7c6593
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/FrameHeaderReader.java
@@ -0,0 +1,64 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TTransportException;
+
+/**
+ * Read headers for a frame. For each frame, the header contains payload size and other metadata.
+ */
+public interface FrameHeaderReader {
+
+ /**
+ * As the thrift sasl specification states, all sasl messages (both for negotiatiing and for
+ * sending data) should have a header to indicate the size of the payload.
+ *
+ * @return size of the payload.
+ */
+ int payloadSize();
+
+ /**
+ *
+ * @return The received bytes for the header.
+ * @throws IllegalStateException if isComplete returns false.
+ */
+ byte[] toBytes();
+
+ /**
+ * @return true if this header has all its fields set.
+ */
+ boolean isComplete();
+
+ /**
+ * Clear the header and make it available to read a new header.
+ */
+ void clear();
+
+ /**
+ * (Nonblocking) Read fields from underlying transport layer.
+ *
+ * @param transport underlying transport.
+ * @return true if header is complete after read.
+ * @throws TSaslNegotiationException if fail to read a valid header of a sasl negotiation message.
+ * @throws TTransportException if io error.
+ */
+ boolean read(TTransport transport) throws TSaslNegotiationException, TTransportException;
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/FrameReader.java b/lib/java/src/org/apache/thrift/transport/sasl/FrameReader.java
new file mode 100644
index 0000000..acb4b73
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/FrameReader.java
@@ -0,0 +1,162 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import org.apache.thrift.transport.TEOFException;
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TTransportException;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Read frames from a transport. Each frame has a header and a payload. A header will indicate
+ * the size of the payload and other informations about how to decode payload.
+ * Implementations should subclass it by providing a header reader implementation.
+ *
+ * @param <T> Header type.
+ */
+public abstract class FrameReader<T extends FrameHeaderReader> {
+ private final T header;
+ private ByteBuffer payload;
+
+ protected FrameReader(T header) {
+ this.header = header;
+ }
+
+ /**
+ * (Nonblocking) Read available bytes out of the transport without blocking to wait for incoming
+ * data.
+ *
+ * @param transport TTransport
+ * @return true if current frame is complete after read.
+ * @throws TSaslNegotiationException if fail to read back a valid sasl negotiation message.
+ * @throws TTransportException if io error.
+ */
+ public boolean read(TTransport transport) throws TSaslNegotiationException, TTransportException {
+ if (!header.isComplete()) {
+ if (readHeader(transport)) {
+ payload = ByteBuffer.allocate(header.payloadSize());
+ } else {
+ return false;
+ }
+ }
+ if (header.payloadSize() == 0) {
+ return true;
+ }
+ return readPayload(transport);
+ }
+
+ /**
+ * (Nonblocking) Try to read available header bytes from transport.
+ *
+ * @return true if header is complete after read.
+ * @throws TSaslNegotiationException if fail to read back a validd sasl negotiation header.
+ * @throws TTransportException if io error.
+ */
+ private boolean readHeader(TTransport transport) throws TSaslNegotiationException, TTransportException {
+ return header.read(transport);
+ }
+
+ /**
+ * (Nonblocking) Try to read available
+ *
+ * @param transport underlying transport.
+ * @return true if payload is complete after read.
+ * @throws TTransportException if io error.
+ */
+ private boolean readPayload(TTransport transport) throws TTransportException {
+ readAvailable(transport, payload);
+ return payload.hasRemaining();
+ }
+
+ /**
+ *
+ * @return header of the frame
+ */
+ public T getHeader() {
+ return header;
+ }
+
+ /**
+ *
+ * @return number of bytes of the header
+ */
+ public int getHeaderSize() {
+ return header.toBytes().length;
+ }
+
+ /**
+ *
+ * @return byte array of the payload
+ */
+ public byte[] getPayload() {
+ return payload.array();
+ }
+
+ /**
+ *
+ * @return size of the payload
+ */
+ public int getPayloadSize() {
+ return header.payloadSize();
+ }
+
+ /**
+ *
+ * @return true if the reader has fully read a frame
+ */
+ public boolean isComplete() {
+ return !(payload == null || payload.hasRemaining());
+ }
+
+ /**
+ * Reset the state of the reader so that it can be reused to read a new frame.
+ */
+ public void clear() {
+ header.clear();
+ payload = null;
+ }
+
+ /**
+ * Read immediately available bytes from the transport into the byte buffer.
+ *
+ * @param transport TTransport
+ * @param recipient ByteBuffer
+ * @return number of bytes read out of the transport
+ * @throws TTransportException if io error
+ */
+ static int readAvailable(TTransport transport, ByteBuffer recipient) throws TTransportException {
+ if (!recipient.hasRemaining()) {
+ throw new IllegalStateException("Trying to fill a full recipient with " + recipient.limit()
+ + " bytes");
+ }
+ int currentPosition = recipient.position();
+ byte[] bytes = recipient.array();
+ int offset = recipient.arrayOffset() + currentPosition;
+ int expectedLength = recipient.remaining();
+ int got = transport.read(bytes, offset, expectedLength);
+ if (got < 0) {
+ throw new TEOFException("Transport is closed, while trying to read " + expectedLength +
+ " bytes");
+ }
+ recipient.position(currentPosition + got);
+ return got;
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/FrameWriter.java b/lib/java/src/org/apache/thrift/transport/sasl/FrameWriter.java
new file mode 100644
index 0000000..5f48121
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/FrameWriter.java
@@ -0,0 +1,122 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.thrift.transport.TNonblockingTransport;
+
+/**
+ * Write frame (header and payload) to transport in a nonblocking way.
+ */
+public abstract class FrameWriter {
+
+ protected ByteBuffer frameBytes;
+
+ /**
+ * Provide (maybe empty) header and payload to the frame. This can be called only when isComplete
+ * returns true (last frame has been written out).
+ *
+ * @param header Some extra header bytes (without the 4 bytes for payload length), which will be
+ * the start of the frame. It can be empty, depending on the message format
+ * @param payload Payload as a byte array
+ * @throws IllegalStateException if it is called when isComplete returns false
+ * @throws IllegalArgumentException if header or payload is invalid
+ */
+ public void withHeaderAndPayload(byte[] header, byte[] payload) {
+ if (payload == null) {
+ payload = new byte[0];
+ }
+ if (header == null) {
+ withOnlyPayload(payload);
+ } else {
+ withHeaderAndPayload(header, 0, header.length, payload, 0, payload.length);
+ }
+ }
+
+ /**
+ * Provide extra header and payload to the frame.
+ *
+ * @param header byte array containing the extra header
+ * @param headerOffset starting offset of the header portition
+ * @param headerLength length of the extra header
+ * @param payload byte array containing the payload
+ * @param payloadOffset starting offset of the payload portion
+ * @param payloadLength length of the payload
+ * @throws IllegalStateException if preivous frame is not yet complete (isComplete returns fals)
+ * @throws IllegalArgumentException if header or payload is invalid
+ */
+ public void withHeaderAndPayload(byte[] header, int headerOffset, int headerLength,
+ byte[] payload, int payloadOffset, int payloadLength) {
+ if (!isComplete()) {
+ throw new IllegalStateException("Previsous write is not yet complete, with " +
+ frameBytes.remaining() + " bytes left.");
+ }
+ frameBytes = buildFrame(header, headerOffset, headerLength, payload, payloadOffset, payloadLength);
+ }
+
+ /**
+ * Provide only payload to the frame. Throws UnsupportedOperationException if the frame expects
+ * a header.
+ *
+ * @param payload payload as a byte array
+ */
+ public void withOnlyPayload(byte[] payload) {
+ withOnlyPayload(payload, 0, payload.length);
+ }
+
+ /**
+ * Provide only payload to the frame. Throws UnsupportedOperationException if the frame expects
+ * a header.
+ *
+ * @param payload The underlying byte array as a recipient of the payload
+ * @param offset The offset in the byte array starting from where the payload is located
+ * @param length The length of the payload
+ */
+ public abstract void withOnlyPayload(byte[] payload, int offset, int length);
+
+ protected abstract ByteBuffer buildFrame(byte[] header, int headerOffset, int headerLength,
+ byte[] payload, int payloadOffset, int payloadeLength);
+
+ /**
+ * Nonblocking write to the underlying transport.
+ *
+ * @throws IOException
+ */
+ public void write(TNonblockingTransport transport) throws IOException {
+ transport.write(frameBytes);
+ }
+
+ /**
+ *
+ * @return true when no more data needs to be written out
+ */
+ public boolean isComplete() {
+ return frameBytes == null || !frameBytes.hasRemaining();
+ }
+
+ /**
+ * Release the byte buffer.
+ */
+ public void clear() {
+ frameBytes = null;
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/NegotiationStatus.java b/lib/java/src/org/apache/thrift/transport/sasl/NegotiationStatus.java
new file mode 100644
index 0000000..ad704a0
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/NegotiationStatus.java
@@ -0,0 +1,61 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.thrift.transport.sasl.TSaslNegotiationException.ErrorType.PROTOCOL_ERROR;
+
+/**
+ * Status bytes used during the initial Thrift SASL handshake.
+ */
+public enum NegotiationStatus {
+ START((byte)0x01),
+ OK((byte)0x02),
+ BAD((byte)0x03),
+ ERROR((byte)0x04),
+ COMPLETE((byte)0x05);
+
+ private static final Map<Byte, NegotiationStatus> reverseMap = new HashMap<>();
+
+ static {
+ for (NegotiationStatus s : NegotiationStatus.values()) {
+ reverseMap.put(s.getValue(), s);
+ }
+ }
+
+ private final byte value;
+
+ NegotiationStatus(byte val) {
+ this.value = val;
+ }
+
+ public byte getValue() {
+ return value;
+ }
+
+ public static NegotiationStatus byValue(byte val) throws TSaslNegotiationException {
+ if (!reverseMap.containsKey(val)) {
+ throw new TSaslNegotiationException(PROTOCOL_ERROR, "Invalid status " + val);
+ }
+ return reverseMap.get(val);
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/NonblockingSaslHandler.java b/lib/java/src/org/apache/thrift/transport/sasl/NonblockingSaslHandler.java
new file mode 100644
index 0000000..4557146
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/NonblockingSaslHandler.java
@@ -0,0 +1,528 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.charset.StandardCharsets;
+
+import javax.security.sasl.SaslServer;
+
+import org.apache.thrift.TByteArrayOutputStream;
+import org.apache.thrift.TProcessor;
+import org.apache.thrift.protocol.TProtocol;
+import org.apache.thrift.protocol.TProtocolFactory;
+import org.apache.thrift.server.ServerContext;
+import org.apache.thrift.server.TServerEventHandler;
+import org.apache.thrift.transport.TMemoryTransport;
+import org.apache.thrift.transport.TNonblockingTransport;
+import org.apache.thrift.transport.TTransportException;
+import org.apache.thrift.transport.sasl.TSaslNegotiationException.ErrorType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.thrift.transport.sasl.NegotiationStatus.COMPLETE;
+import static org.apache.thrift.transport.sasl.NegotiationStatus.OK;
+
+/**
+ * State machine managing one sasl connection in a nonblocking way.
+ */
+public class NonblockingSaslHandler {
+ private static final Logger LOGGER = LoggerFactory.getLogger(NonblockingSaslHandler.class);
+
+ private static final int INTEREST_NONE = 0;
+ private static final int INTEREST_READ = SelectionKey.OP_READ;
+ private static final int INTEREST_WRITE = SelectionKey.OP_WRITE;
+
+ // Tracking the current running phase
+ private Phase currentPhase = Phase.INITIIALIIZING;
+ // Tracking the next phase on the next invocation of the state machine.
+ // It should be the same as current phase if current phase is not yet finished.
+ // Otherwise, if it is different from current phase, the statemachine is in a transition state:
+ // current phase is done, and next phase is not yet started.
+ private Phase nextPhase = currentPhase;
+
+ // Underlying nonblocking transport
+ private SelectionKey selectionKey;
+ private TNonblockingTransport underlyingTransport;
+
+ // APIs for intercepting event / customizing behaviors:
+ // Factories (decorating the base implementations) & EventHandler (intercepting)
+ private TSaslServerFactory saslServerFactory;
+ private TSaslProcessorFactory processorFactory;
+ private TProtocolFactory inputProtocolFactory;
+ private TProtocolFactory outputProtocolFactory;
+ private TServerEventHandler eventHandler;
+ private ServerContext serverContext;
+ // It turns out the event handler implementation in hive sometimes creates a null ServerContext.
+ // In order to know whether TServerEventHandler#createContext is called we use such a flag.
+ private boolean serverContextCreated = false;
+
+ // Wrapper around sasl server
+ private ServerSaslPeer saslPeer;
+
+ // Sasl negotiation io
+ private SaslNegotiationFrameReader saslResponse;
+ private SaslNegotiationFrameWriter saslChallenge;
+ // IO for request from and response to the socket
+ private DataFrameReader requestReader;
+ private DataFrameWriter responseWriter;
+ // If sasl is negotiated for integrity/confidentiality protection
+ private boolean dataProtected;
+
+ public NonblockingSaslHandler(SelectionKey selectionKey, TNonblockingTransport underlyingTransport,
+ TSaslServerFactory saslServerFactory, TSaslProcessorFactory processorFactory,
+ TProtocolFactory inputProtocolFactory, TProtocolFactory outputProtocolFactory,
+ TServerEventHandler eventHandler) {
+ this.selectionKey = selectionKey;
+ this.underlyingTransport = underlyingTransport;
+ this.saslServerFactory = saslServerFactory;
+ this.processorFactory = processorFactory;
+ this.inputProtocolFactory = inputProtocolFactory;
+ this.outputProtocolFactory = outputProtocolFactory;
+ this.eventHandler = eventHandler;
+
+ saslResponse = new SaslNegotiationFrameReader();
+ saslChallenge = new SaslNegotiationFrameWriter();
+ requestReader = new DataFrameReader();
+ responseWriter = new DataFrameWriter();
+ }
+
+ /**
+ * Get current phase of the state machine.
+ *
+ * @return current phase.
+ */
+ public Phase getCurrentPhase() {
+ return currentPhase;
+ }
+
+ /**
+ * Get next phase of the state machine.
+ * It is different from current phase iff current phase is done (and next phase not yet started).
+ *
+ * @return next phase.
+ */
+ public Phase getNextPhase() {
+ return nextPhase;
+ }
+
+ /**
+ *
+ * @return underlying nonblocking socket
+ */
+ public TNonblockingTransport getUnderlyingTransport() {
+ return underlyingTransport;
+ }
+
+ /**
+ *
+ * @return SaslServer instance
+ */
+ public SaslServer getSaslServer() {
+ return saslPeer.getSaslServer();
+ }
+
+ /**
+ *
+ * @return true if current phase is done.
+ */
+ public boolean isCurrentPhaseDone() {
+ return currentPhase != nextPhase;
+ }
+
+ /**
+ * Run state machine.
+ *
+ * @throws IllegalStateException if current state is already done.
+ */
+ public void runCurrentPhase() {
+ currentPhase.runStateMachine(this);
+ }
+
+ /**
+ * When current phase is intrested in read selection, calling this will run the current phase and
+ * its following phases if the following ones are interested to read, until there is nothing
+ * available in the underlying transport.
+ *
+ * @throws IllegalStateException if is called in an irrelevant phase.
+ */
+ public void handleRead() {
+ handleOps(INTEREST_READ);
+ }
+
+ /**
+ * Similiar to handleRead. But it is for write ops.
+ *
+ * @throws IllegalStateException if it is called in an irrelevant phase.
+ */
+ public void handleWrite() {
+ handleOps(INTEREST_WRITE);
+ }
+
+ private void handleOps(int interestOps) {
+ if (currentPhase.selectionInterest != interestOps) {
+ throw new IllegalStateException("Current phase " + currentPhase + " but got interest " +
+ interestOps);
+ }
+ runCurrentPhase();
+ if (isCurrentPhaseDone() && nextPhase.selectionInterest == interestOps) {
+ stepToNextPhase();
+ handleOps(interestOps);
+ }
+ }
+
+ /**
+ * When current phase is finished, it's expected to call this method first before running the
+ * state machine again.
+ * By calling this, "next phase" is marked as started (and not done), thus is ready to run.
+ *
+ * @throws IllegalArgumentException if current phase is not yet done.
+ */
+ public void stepToNextPhase() {
+ if (!isCurrentPhaseDone()) {
+ throw new IllegalArgumentException("Not yet done with current phase: " + currentPhase);
+ }
+ LOGGER.debug("Switch phase {} to {}", currentPhase, nextPhase);
+ switch (nextPhase) {
+ case INITIIALIIZING:
+ throw new IllegalStateException("INITIALIZING cannot be the next phase of " + currentPhase);
+ default:
+ }
+ // If next phase's interest is not the same as current, nor the same as the selection key,
+ // we need to change interest on the selector.
+ if (!(nextPhase.selectionInterest == currentPhase.selectionInterest ||
+ nextPhase.selectionInterest == selectionKey.interestOps())) {
+ changeSelectionInterest(nextPhase.selectionInterest);
+ }
+ currentPhase = nextPhase;
+ }
+
+ private void changeSelectionInterest(int selectionInterest) {
+ selectionKey.interestOps(selectionInterest);
+ }
+
+ // sasl negotiaion failure handling
+ private void failSaslNegotiation(TSaslNegotiationException e) {
+ LOGGER.error("Sasl negotiation failed", e);
+ String errorMsg = e.getDetails();
+ saslChallenge.withHeaderAndPayload(new byte[]{e.getErrorType().code.getValue()},
+ errorMsg.getBytes(StandardCharsets.UTF_8));
+ nextPhase = Phase.WRITING_FAILURE_MESSAGE;
+ }
+
+ private void fail(Exception e) {
+ LOGGER.error("Failed io in " + currentPhase, e);
+ nextPhase = Phase.CLOSING;
+ }
+
+ private void failIO(TTransportException e) {
+ StringBuilder errorMsg = new StringBuilder("IO failure ")
+ .append(e.getType())
+ .append(" in ")
+ .append(currentPhase);
+ if (e.getMessage() != null) {
+ errorMsg.append(": ").append(e.getMessage());
+ }
+ LOGGER.error(errorMsg.toString(), e);
+ nextPhase = Phase.CLOSING;
+ }
+
+ // Read handlings
+
+ private void handleInitializing() {
+ try {
+ saslResponse.read(underlyingTransport);
+ if (saslResponse.isComplete()) {
+ SaslNegotiationHeaderReader startHeader = saslResponse.getHeader();
+ if (startHeader.getStatus() != NegotiationStatus.START) {
+ throw new TInvalidSaslFrameException("Expecting START status but got " + startHeader.getStatus());
+ }
+ String mechanism = new String(saslResponse.getPayload(), StandardCharsets.UTF_8);
+ saslPeer = saslServerFactory.getSaslPeer(mechanism);
+ saslResponse.clear();
+ nextPhase = Phase.READING_SASL_RESPONSE;
+ }
+ } catch (TSaslNegotiationException e) {
+ failSaslNegotiation(e);
+ } catch (TTransportException e) {
+ failIO(e);
+ }
+ }
+
+ private void handleReadingSaslResponse() {
+ try {
+ saslResponse.read(underlyingTransport);
+ if (saslResponse.isComplete()) {
+ nextPhase = Phase.EVALUATING_SASL_RESPONSE;
+ }
+ } catch (TSaslNegotiationException e) {
+ failSaslNegotiation(e);
+ } catch (TTransportException e) {
+ failIO(e);
+ }
+ }
+
+ private void handleReadingRequest() {
+ try {
+ requestReader.read(underlyingTransport);
+ if (requestReader.isComplete()) {
+ nextPhase = Phase.PROCESSING;
+ }
+ } catch (TTransportException e) {
+ failIO(e);
+ }
+ }
+
+ // Computation executions
+
+ private void executeEvaluatingSaslResponse() {
+ if (!(saslResponse.getHeader().getStatus() == OK || saslResponse.getHeader().getStatus() == COMPLETE)) {
+ String error = "Expect status OK or COMPLETE, but got " + saslResponse.getHeader().getStatus();
+ failSaslNegotiation(new TSaslNegotiationException(ErrorType.PROTOCOL_ERROR, error));
+ return;
+ }
+ try {
+ byte[] response = saslResponse.getPayload();
+ saslResponse.clear();
+ byte[] newChallenge = saslPeer.evaluate(response);
+ if (saslPeer.isAuthenticated()) {
+ dataProtected = saslPeer.isDataProtected();
+ saslChallenge.withHeaderAndPayload(new byte[]{COMPLETE.getValue()}, newChallenge);
+ nextPhase = Phase.WRITING_SUCCESS_MESSAGE;
+ } else {
+ saslChallenge.withHeaderAndPayload(new byte[]{OK.getValue()}, newChallenge);
+ nextPhase = Phase.WRITING_SASL_CHALLENGE;
+ }
+ } catch (TSaslNegotiationException e) {
+ failSaslNegotiation(e);
+ }
+ }
+
+ private void executeProcessing() {
+ try {
+ byte[] inputPayload = requestReader.getPayload();
+ requestReader.clear();
+ byte[] rawInput = dataProtected ? saslPeer.unwrap(inputPayload) : inputPayload;
+ TMemoryTransport memoryTransport = new TMemoryTransport(rawInput);
+ TProtocol requestProtocol = inputProtocolFactory.getProtocol(memoryTransport);
+ TProtocol responseProtocol = outputProtocolFactory.getProtocol(memoryTransport);
+
+ if (eventHandler != null) {
+ if (!serverContextCreated) {
+ serverContext = eventHandler.createContext(requestProtocol, responseProtocol);
+ serverContextCreated = true;
+ }
+ eventHandler.processContext(serverContext, memoryTransport, memoryTransport);
+ }
+
+ TProcessor processor = processorFactory.getProcessor(this);
+ processor.process(requestProtocol, responseProtocol);
+ TByteArrayOutputStream rawOutput = memoryTransport.getOutput();
+ if (rawOutput.len() == 0) {
+ // This is a oneway request, no response to send back. Waiting for next incoming request.
+ nextPhase = Phase.READING_REQUEST;
+ return;
+ }
+ if (dataProtected) {
+ byte[] outputPayload = saslPeer.wrap(rawOutput.get(), 0, rawOutput.len());
+ responseWriter.withOnlyPayload(outputPayload);
+ } else {
+ responseWriter.withOnlyPayload(rawOutput.get(), 0 ,rawOutput.len());
+ }
+ nextPhase = Phase.WRITING_RESPONSE;
+ } catch (TTransportException e) {
+ failIO(e);
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ // Write handlings
+
+ private void handleWritingSaslChallenge() {
+ try {
+ saslChallenge.write(underlyingTransport);
+ if (saslChallenge.isComplete()) {
+ saslChallenge.clear();
+ nextPhase = Phase.READING_SASL_RESPONSE;
+ }
+ } catch (IOException e) {
+ fail(e);
+ }
+ }
+
+ private void handleWritingSuccessMessage() {
+ try {
+ saslChallenge.write(underlyingTransport);
+ if (saslChallenge.isComplete()) {
+ LOGGER.debug("Authentication is done.");
+ saslChallenge = null;
+ saslResponse = null;
+ nextPhase = Phase.READING_REQUEST;
+ }
+ } catch (IOException e) {
+ fail(e);
+ }
+ }
+
+ private void handleWritingFailureMessage() {
+ try {
+ saslChallenge.write(underlyingTransport);
+ if (saslChallenge.isComplete()) {
+ nextPhase = Phase.CLOSING;
+ }
+ } catch (IOException e) {
+ fail(e);
+ }
+ }
+
+ private void handleWritingResponse() {
+ try {
+ responseWriter.write(underlyingTransport);
+ if (responseWriter.isComplete()) {
+ responseWriter.clear();
+ nextPhase = Phase.READING_REQUEST;
+ }
+ } catch (IOException e) {
+ fail(e);
+ }
+ }
+
+ /**
+ * Release all the resources managed by this state machine (connection, selection and sasl server).
+ * To avoid being blocked, this should be invoked in the network thread that manages the selector.
+ */
+ public void close() {
+ underlyingTransport.close();
+ selectionKey.cancel();
+ if (saslPeer != null) {
+ saslPeer.dispose();
+ }
+ if (serverContextCreated) {
+ eventHandler.deleteContext(serverContext,
+ inputProtocolFactory.getProtocol(underlyingTransport),
+ outputProtocolFactory.getProtocol(underlyingTransport));
+ }
+ nextPhase = Phase.CLOSED;
+ currentPhase = Phase.CLOSED;
+ LOGGER.trace("Connection closed: {}", underlyingTransport);
+ }
+
+ public enum Phase {
+ INITIIALIIZING(INTEREST_READ) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.handleInitializing();
+ }
+ },
+ READING_SASL_RESPONSE(INTEREST_READ) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.handleReadingSaslResponse();
+ }
+ },
+ EVALUATING_SASL_RESPONSE(INTEREST_NONE) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.executeEvaluatingSaslResponse();
+ }
+ },
+ WRITING_SASL_CHALLENGE(INTEREST_WRITE) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.handleWritingSaslChallenge();
+ }
+ },
+ WRITING_SUCCESS_MESSAGE(INTEREST_WRITE) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.handleWritingSuccessMessage();
+ }
+ },
+ WRITING_FAILURE_MESSAGE(INTEREST_WRITE) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.handleWritingFailureMessage();
+ }
+ },
+ READING_REQUEST(INTEREST_READ) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.handleReadingRequest();
+ }
+ },
+ PROCESSING(INTEREST_NONE) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.executeProcessing();
+ }
+ },
+ WRITING_RESPONSE(INTEREST_WRITE) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.handleWritingResponse();
+ }
+ },
+ CLOSING(INTEREST_NONE) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ statemachine.close();
+ }
+ },
+ CLOSED(INTEREST_NONE) {
+ @Override
+ void unsafeRun(NonblockingSaslHandler statemachine) {
+ // Do nothing.
+ }
+ }
+ ;
+
+ // The interest on the selection key during the phase
+ private int selectionInterest;
+
+ Phase(int selectionInterest) {
+ this.selectionInterest = selectionInterest;
+ }
+
+ /**
+ * Provide the execution to run for the state machine in current phase. The execution should
+ * return the next phase after running on the state machine.
+ *
+ * @param statemachine The state machine to run.
+ * @throws IllegalArgumentException if the state machine's current phase is different.
+ * @throws IllegalStateException if the state machine' current phase is already done.
+ */
+ void runStateMachine(NonblockingSaslHandler statemachine) {
+ if (statemachine.currentPhase != this) {
+ throw new IllegalArgumentException("State machine is " + statemachine.currentPhase +
+ " but is expected to be " + this);
+ }
+ if (statemachine.isCurrentPhaseDone()) {
+ throw new IllegalStateException("State machine should step into " + statemachine.nextPhase);
+ }
+ unsafeRun(statemachine);
+ }
+
+ // Run the state machine without checkiing its own phase
+ // It should not be called direcly by users.
+ abstract void unsafeRun(NonblockingSaslHandler statemachine);
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/SaslNegotiationFrameReader.java b/lib/java/src/org/apache/thrift/transport/sasl/SaslNegotiationFrameReader.java
new file mode 100644
index 0000000..01c1728
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/SaslNegotiationFrameReader.java
@@ -0,0 +1,30 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+/**
+ * Read frames for sasl negotiatiions.
+ */
+public class SaslNegotiationFrameReader extends FrameReader<SaslNegotiationHeaderReader> {
+
+ public SaslNegotiationFrameReader() {
+ super(new SaslNegotiationHeaderReader());
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/SaslNegotiationFrameWriter.java b/lib/java/src/org/apache/thrift/transport/sasl/SaslNegotiationFrameWriter.java
new file mode 100644
index 0000000..1e9ad15
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/SaslNegotiationFrameWriter.java
@@ -0,0 +1,56 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import java.nio.ByteBuffer;
+
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.utils.StringUtils;
+
+import static org.apache.thrift.transport.sasl.SaslNegotiationHeaderReader.PAYLOAD_LENGTH_BYTES;
+import static org.apache.thrift.transport.sasl.SaslNegotiationHeaderReader.STATUS_BYTES;
+
+/**
+ * Writer for sasl negotiation frames. It expect a status byte as header with a payload to be
+ * written out (any header whose size is not equal to 1 would be considered as error).
+ */
+public class SaslNegotiationFrameWriter extends FrameWriter {
+
+ public static final int HEADER_BYTES = STATUS_BYTES + PAYLOAD_LENGTH_BYTES;
+
+ @Override
+ public void withOnlyPayload(byte[] payload, int offset, int length) {
+ throw new UnsupportedOperationException("Status byte is expected for sasl frame header.");
+ }
+
+ @Override
+ protected ByteBuffer buildFrame(byte[] header, int headerOffset, int headerLength,
+ byte[] payload, int payloadOffset, int payloadLength) {
+ if (header == null || headerLength != STATUS_BYTES) {
+ throw new IllegalArgumentException("Header " + StringUtils.bytesToHexString(header) +
+ " does not have expected length " + STATUS_BYTES);
+ }
+ byte[] bytes = new byte[HEADER_BYTES + payloadLength];
+ System.arraycopy(header, headerOffset, bytes, 0, STATUS_BYTES);
+ EncodingUtils.encodeBigEndian(payloadLength, bytes, STATUS_BYTES);
+ System.arraycopy(payload, payloadOffset, bytes, HEADER_BYTES, payloadLength);
+ return ByteBuffer.wrap(bytes);
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/SaslNegotiationHeaderReader.java b/lib/java/src/org/apache/thrift/transport/sasl/SaslNegotiationHeaderReader.java
new file mode 100644
index 0000000..2d76ddb
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/SaslNegotiationHeaderReader.java
@@ -0,0 +1,57 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import static org.apache.thrift.transport.sasl.TSaslNegotiationException.ErrorType.PROTOCOL_ERROR;
+
+/**
+ * Header for sasl negotiation frames. It contains status byte of negotiation and a 4-byte integer
+ * (payload size).
+ */
+public class SaslNegotiationHeaderReader extends FixedSizeHeaderReader {
+ public static final int STATUS_BYTES = 1;
+ public static final int PAYLOAD_LENGTH_BYTES = 4;
+
+ private NegotiationStatus negotiationStatus;
+ private int payloadSize;
+
+ @Override
+ protected int headerSize() {
+ return STATUS_BYTES + PAYLOAD_LENGTH_BYTES;
+ }
+
+ @Override
+ protected void onComplete() throws TSaslNegotiationException {
+ negotiationStatus = NegotiationStatus.byValue(byteBuffer.get(0));
+ payloadSize = byteBuffer.getInt(1);
+ if (payloadSize < 0) {
+ throw new TSaslNegotiationException(PROTOCOL_ERROR, "Payload size is negative: " + payloadSize);
+ }
+ }
+
+ @Override
+ public int payloadSize() {
+ return payloadSize;
+ }
+
+ public NegotiationStatus getStatus() {
+ return negotiationStatus;
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/SaslPeer.java b/lib/java/src/org/apache/thrift/transport/sasl/SaslPeer.java
new file mode 100644
index 0000000..8f81380
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/SaslPeer.java
@@ -0,0 +1,100 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import org.apache.thrift.transport.TTransportException;
+
+/**
+ * A peer in a sasl negotiation.
+ */
+public interface SaslPeer {
+
+ /**
+ * Evaluate and validate the negotiation message (response/challenge) received from peer.
+ *
+ * @param negotiationMessage response/challenge received from peer.
+ * @return new response/challenge to send to peer, can be null if authentication becomes success.
+ * @throws TSaslNegotiationException if sasl authentication fails.
+ */
+ byte[] evaluate(byte[] negotiationMessage) throws TSaslNegotiationException;
+
+ /**
+ * @return true if authentication is done.
+ */
+ boolean isAuthenticated();
+
+ /**
+ * This method can only be called when the negotiation is complete (isAuthenticated returns true).
+ * Otherwise it will throw IllegalStateExceptiion.
+ *
+ * @return if the qop requires some integrity/confidential protection.
+ * @throws IllegalStateException if negotiation is not yet complete.
+ */
+ boolean isDataProtected();
+
+ /**
+ * Wrap raw bytes to protect it.
+ *
+ * @param data raw bytes.
+ * @param offset the start position of the content to wrap.
+ * @param length the length of the content to wrap.
+ * @return bytes with protection to send to peer.
+ * @throws TTransportException if failure.
+ */
+ byte[] wrap(byte[] data, int offset, int length) throws TTransportException;
+
+ /**
+ * Wrap the whole byte array.
+ *
+ * @param data raw bytes.
+ * @return wrapped bytes.
+ * @throws TTransportException if failure.
+ */
+ default byte[] wrap(byte[] data) throws TTransportException {
+ return wrap(data, 0, data.length);
+ }
+
+ /**
+ * Unwrap protected data to raw bytes.
+ *
+ * @param data protected data received from peer.
+ * @param offset the start position of the content to unwrap.
+ * @param length the length of the content to unwrap.
+ * @return raw bytes.
+ * @throws TTransportException if failed.
+ */
+ byte[] unwrap(byte[] data, int offset, int length) throws TTransportException;
+
+ /**
+ * Unwrap the whole byte array.
+ *
+ * @param data wrapped bytes.
+ * @return raw bytes.
+ * @throws TTransportException if failure.
+ */
+ default byte[] unwrap(byte[] data) throws TTransportException {
+ return unwrap(data, 0, data.length);
+ }
+
+ /**
+ * Close this peer and release resources.
+ */
+ void dispose();
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/ServerSaslPeer.java b/lib/java/src/org/apache/thrift/transport/sasl/ServerSaslPeer.java
new file mode 100644
index 0000000..31992e5
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/ServerSaslPeer.java
@@ -0,0 +1,108 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+import org.apache.thrift.transport.TTransportException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.thrift.transport.sasl.TSaslNegotiationException.ErrorType.AUTHENTICATION_FAILURE;
+
+/**
+ * Server side sasl peer, a wrapper around SaslServer to provide some handy methods.
+ */
+public class ServerSaslPeer implements SaslPeer {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ServerSaslPeer.class);
+
+ private static final String QOP_AUTH_INT = "auth-int";
+ private static final String QOP_AUTH_CONF = "auth-conf";
+
+ private final SaslServer saslServer;
+
+ public ServerSaslPeer(SaslServer saslServer) {
+ this.saslServer = saslServer;
+ }
+
+ @Override
+ public byte[] evaluate(byte[] negotiationMessage) throws TSaslNegotiationException {
+ try {
+ return saslServer.evaluateResponse(negotiationMessage);
+ } catch (SaslException e) {
+ throw new TSaslNegotiationException(AUTHENTICATION_FAILURE,
+ "Authentication failed with " + saslServer.getMechanismName(), e);
+ }
+ }
+
+ @Override
+ public boolean isAuthenticated() {
+ return saslServer.isComplete();
+ }
+
+ @Override
+ public boolean isDataProtected() {
+ Object qop = saslServer.getNegotiatedProperty(Sasl.QOP);
+ if (qop == null) {
+ return false;
+ }
+ for (String word : qop.toString().split("\\s*,\\s*")) {
+ String lowerCaseWord = word.toLowerCase();
+ if (QOP_AUTH_INT.equals(lowerCaseWord) || QOP_AUTH_CONF.equals(lowerCaseWord)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public byte[] wrap(byte[] data, int offset, int length) throws TTransportException {
+ try {
+ return saslServer.wrap(data, offset, length);
+ } catch (SaslException e) {
+ throw new TTransportException("Failed to wrap data", e);
+ }
+ }
+
+ @Override
+ public byte[] unwrap(byte[] data, int offset, int length) throws TTransportException {
+ try {
+ return saslServer.unwrap(data, offset, length);
+ } catch (SaslException e) {
+ throw new TTransportException(TTransportException.CORRUPTED_DATA, "Failed to unwrap data", e);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ try {
+ saslServer.dispose();
+ } catch (Exception e) {
+ LOGGER.warn("Failed to close sasl server " + saslServer.getMechanismName(), e);
+ }
+ }
+
+ SaslServer getSaslServer() {
+ return saslServer;
+ }
+
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/TBaseSaslProcessorFactory.java b/lib/java/src/org/apache/thrift/transport/sasl/TBaseSaslProcessorFactory.java
new file mode 100644
index 0000000..c08884c
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/TBaseSaslProcessorFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import org.apache.thrift.TProcessor;
+
+public class TBaseSaslProcessorFactory implements TSaslProcessorFactory {
+
+ private final TProcessor processor;
+
+ public TBaseSaslProcessorFactory(TProcessor processor) {
+ this.processor = processor;
+ }
+
+ @Override
+ public TProcessor getProcessor(NonblockingSaslHandler saslHandler) {
+ return processor;
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/TInvalidSaslFrameException.java b/lib/java/src/org/apache/thrift/transport/sasl/TInvalidSaslFrameException.java
new file mode 100644
index 0000000..ff57ea5
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/TInvalidSaslFrameException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+/**
+ * Got an invalid frame that does not respect the thrift sasl protocol.
+ */
+public class TInvalidSaslFrameException extends TSaslNegotiationException {
+
+ public TInvalidSaslFrameException(String message) {
+ super(ErrorType.PROTOCOL_ERROR, message);
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/TSaslNegotiationException.java b/lib/java/src/org/apache/thrift/transport/sasl/TSaslNegotiationException.java
new file mode 100644
index 0000000..9b1fa06
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/TSaslNegotiationException.java
@@ -0,0 +1,76 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import org.apache.thrift.transport.TTransportException;
+
+/**
+ * Exception for sasl negotiation errors.
+ */
+public class TSaslNegotiationException extends TTransportException {
+
+ private final ErrorType error;
+
+ public TSaslNegotiationException(ErrorType error, String summary) {
+ super(summary);
+ this.error = error;
+ }
+
+ public TSaslNegotiationException(ErrorType error, String summary, Throwable cause) {
+ super(summary, cause);
+ this.error = error;
+ }
+
+ public ErrorType getErrorType() {
+ return error;
+ }
+
+ /**
+ * @return Errory type plus the message.
+ */
+ public String getSummary() {
+ return error.name() + ": " + getMessage();
+ }
+
+ /**
+ * @return Summary and eventually the cause's message.
+ */
+ public String getDetails() {
+ return getCause() == null ? getSummary() : getSummary() + "\nReason: " + getCause().getMessage();
+ }
+
+ public enum ErrorType {
+ // Unexpected system internal error during negotiation (e.g. sasl initialization failure)
+ INTERNAL_ERROR(NegotiationStatus.ERROR),
+ // Cannot read correct sasl frames from the connection => Send "ERROR" status byte to peer
+ PROTOCOL_ERROR(NegotiationStatus.ERROR),
+ // Peer is using unsupported sasl mechanisms => Send "BAD" status byte to peer
+ MECHANISME_MISMATCH(NegotiationStatus.BAD),
+ // Sasl authentication failure => Send "BAD" status byte to peer
+ AUTHENTICATION_FAILURE(NegotiationStatus.BAD),
+ ;
+
+ public final NegotiationStatus code;
+
+ ErrorType(NegotiationStatus code) {
+ this.code = code;
+ }
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/TSaslProcessorFactory.java b/lib/java/src/org/apache/thrift/transport/sasl/TSaslProcessorFactory.java
new file mode 100644
index 0000000..877d049
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/TSaslProcessorFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import org.apache.thrift.TException;
+import org.apache.thrift.TProcessor;
+
+/**
+ * Get processor for a given state machine, so that users can customize the behavior of a TProcessor
+ * by interacting with the state machine.
+ */
+public interface TSaslProcessorFactory {
+
+ TProcessor getProcessor(NonblockingSaslHandler saslHandler) throws TException;
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/TSaslServerDefinition.java b/lib/java/src/org/apache/thrift/transport/sasl/TSaslServerDefinition.java
new file mode 100644
index 0000000..5486641
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/TSaslServerDefinition.java
@@ -0,0 +1,43 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import javax.security.auth.callback.CallbackHandler;
+import java.util.Map;
+
+/**
+ * Contains all the parameters used to define a SASL server implementation.
+ */
+public class TSaslServerDefinition {
+ public final String mechanism;
+ public final String protocol;
+ public final String serverName;
+ public final Map<String, String> props;
+ public final CallbackHandler cbh;
+
+ public TSaslServerDefinition(String mechanism, String protocol, String serverName,
+ Map<String, String> props, CallbackHandler cbh) {
+ this.mechanism = mechanism;
+ this.protocol = protocol;
+ this.serverName = serverName;
+ this.props = props;
+ this.cbh = cbh;
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/transport/sasl/TSaslServerFactory.java b/lib/java/src/org/apache/thrift/transport/sasl/TSaslServerFactory.java
new file mode 100644
index 0000000..06cf534
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/transport/sasl/TSaslServerFactory.java
@@ -0,0 +1,64 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+import static org.apache.thrift.transport.sasl.TSaslNegotiationException.ErrorType.MECHANISME_MISMATCH;
+import static org.apache.thrift.transport.sasl.TSaslNegotiationException.ErrorType.PROTOCOL_ERROR;
+
+/**
+ * Factory to create sasl server. Users can extend this class to customize the SaslServer creation.
+ */
+public class TSaslServerFactory {
+
+ private final Map<String, TSaslServerDefinition> saslMechanisms;
+
+ public TSaslServerFactory() {
+ this.saslMechanisms = new HashMap<>();
+ }
+
+ public void addSaslMechanism(String mechanism, String protocol, String serverName,
+ Map<String, String> props, CallbackHandler cbh) {
+ TSaslServerDefinition definition = new TSaslServerDefinition(mechanism, protocol, serverName,
+ props, cbh);
+ saslMechanisms.put(definition.mechanism, definition);
+ }
+
+ public ServerSaslPeer getSaslPeer(String mechanism) throws TSaslNegotiationException {
+ if (!saslMechanisms.containsKey(mechanism)) {
+ throw new TSaslNegotiationException(MECHANISME_MISMATCH, "Unsupported mechanism " + mechanism);
+ }
+ TSaslServerDefinition saslDef = saslMechanisms.get(mechanism);
+ try {
+ SaslServer saslServer = Sasl.createSaslServer(saslDef.mechanism, saslDef.protocol,
+ saslDef.serverName, saslDef.props, saslDef.cbh);
+ return new ServerSaslPeer(saslServer);
+ } catch (SaslException e) {
+ throw new TSaslNegotiationException(PROTOCOL_ERROR, "Fail to create sasl server " + mechanism, e);
+ }
+ }
+}
diff --git a/lib/java/src/org/apache/thrift/utils/StringUtils.java b/lib/java/src/org/apache/thrift/utils/StringUtils.java
new file mode 100644
index 0000000..15183a3
--- /dev/null
+++ b/lib/java/src/org/apache/thrift/utils/StringUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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 org.apache.thrift.utils;
+
+public final class StringUtils {
+
+ private StringUtils() {
+ // Utility class.
+ }
+
+ private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ /**
+ * Stringify a byte array to the hex representation for each byte.
+ *
+ * @param bytes
+ * @return hex string.
+ */
+ public static String bytesToHexString(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ return bytesToHexString(bytes, 0, bytes.length);
+ }
+
+ /**
+ * Stringify a portion of the byte array.
+ *
+ * @param bytes byte array.
+ * @param offset portion start.
+ * @param length portion length.
+ * @return hex string.
+ */
+ public static String bytesToHexString(byte[] bytes, int offset, int length) {
+ if (length < 0) {
+ throw new IllegalArgumentException("Negative length " + length);
+ }
+ if (offset < 0) {
+ throw new IndexOutOfBoundsException("Negative start offset " + offset);
+ }
+ char[] chars = new char[length * 2];
+ for (int i = 0; i < length; i++) {
+ int unsignedInt = bytes[i + offset] & 0xFF;
+ chars[2 * i] = HEX_CHARS[unsignedInt >>> 4];
+ chars[2 * i + 1] = HEX_CHARS[unsignedInt & 0x0F];
+ }
+ return new String(chars);
+ }
+}
diff --git a/lib/java/test/org/apache/thrift/server/TestSaslNonblockingServer.java b/lib/java/test/org/apache/thrift/server/TestSaslNonblockingServer.java
new file mode 100644
index 0000000..d0a6746
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/server/TestSaslNonblockingServer.java
@@ -0,0 +1,96 @@
+/*
+ * 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 org.apache.thrift.server;
+
+import org.apache.thrift.TProcessor;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TProtocolFactory;
+import org.apache.thrift.transport.TNonblockingServerSocket;
+import org.apache.thrift.transport.TNonblockingServerTransport;
+import org.apache.thrift.transport.TSaslClientTransport;
+import org.apache.thrift.transport.TSocket;
+import org.apache.thrift.transport.TTransportException;
+import org.apache.thrift.transport.TTransportFactory;
+import org.apache.thrift.transport.TestTSaslTransports;
+import org.apache.thrift.transport.TestTSaslTransports.TestSaslCallbackHandler;
+import org.apache.thrift.transport.sasl.TSaslNegotiationException;
+import thrift.test.ThriftTest;
+
+import static org.apache.thrift.transport.sasl.TSaslNegotiationException.ErrorType.AUTHENTICATION_FAILURE;
+
+public class TestSaslNonblockingServer extends TestTSaslTransports.TestTSaslTransportsWithServer {
+
+ private TSaslNonblockingServer server;
+
+ @Override
+ public void startServer(TProcessor processor, TProtocolFactory protoFactory, TTransportFactory factory)
+ throws Exception {
+ TNonblockingServerTransport serverSocket = new TNonblockingServerSocket(
+ new TNonblockingServerSocket.NonblockingAbstractServerSocketArgs().port(PORT));
+ TSaslNonblockingServer.Args args = new TSaslNonblockingServer.Args(serverSocket)
+ .processor(processor)
+ .transportFactory(factory)
+ .protocolFactory(protoFactory)
+ .addSaslMechanism(TestTSaslTransports.WRAPPED_MECHANISM, TestTSaslTransports.SERVICE,
+ TestTSaslTransports.HOST, TestTSaslTransports.WRAPPED_PROPS,
+ new TestSaslCallbackHandler(TestTSaslTransports.PASSWORD));
+ server = new TSaslNonblockingServer(args);
+ server.serve();
+ }
+
+ @Override
+ public void stopServer() throws Exception {
+ server.shutdown();
+ }
+
+ @Override
+ public void testIt() throws Exception {
+ super.testIt();
+ }
+
+ public void testBadPassword() throws Exception {
+ TProtocolFactory protocolFactory = new TBinaryProtocol.Factory();
+ TProcessor processor = new ThriftTest.Processor<>(new TestHandler());
+ startServer(processor, protocolFactory);
+
+ TSocket socket = new TSocket(HOST, PORT);
+ socket.setTimeout(SOCKET_TIMEOUT);
+ TSaslClientTransport client = new TSaslClientTransport(TestTSaslTransports.WRAPPED_MECHANISM,
+ TestTSaslTransports.PRINCIPAL, TestTSaslTransports.SERVICE, TestTSaslTransports.HOST,
+ TestTSaslTransports.WRAPPED_PROPS, new TestSaslCallbackHandler("bad_password"), socket);
+ try {
+ client.open();
+ fail("Client should fail with sasl negotiation.");
+ } catch (TTransportException error) {
+ TSaslNegotiationException serverSideError = new TSaslNegotiationException(AUTHENTICATION_FAILURE,
+ "Authentication failed with " + TestTSaslTransports.WRAPPED_MECHANISM);
+ assertTrue("Server should return error message \"" + serverSideError.getSummary() + "\"",
+ error.getMessage().contains(serverSideError.getSummary()));
+ } finally {
+ stopServer();
+ client.close();
+ }
+ }
+
+ @Override
+ public void testTransportFactory() {
+ // This test is irrelevant here, so skipped.
+ }
+}
diff --git a/lib/java/test/org/apache/thrift/server/TestThreadPoolServer.java b/lib/java/test/org/apache/thrift/server/TestThreadPoolServer.java
new file mode 100644
index 0000000..e81d801
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/server/TestThreadPoolServer.java
@@ -0,0 +1,62 @@
+/*
+ * 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 org.apache.thrift.server;
+
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.transport.TServerSocket;
+import org.apache.thrift.transport.TServerTransport;
+import org.apache.thrift.transport.TSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import thrift.test.ThriftTest;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+public class TestThreadPoolServer {
+
+ /**
+ * Test server is shut down properly even with some open clients.
+ */
+ @Test
+ public void testStopServerWithOpenClient() throws Exception {
+ TServerSocket serverSocket = new TServerSocket(0);
+ TThreadPoolServer server = buildServer(serverSocket);
+ Thread serverThread = new Thread(() -> server.serve());
+ serverThread.start();
+ try (TSocket client = new TSocket("localhost", serverSocket.getServerSocket().getLocalPort())) {
+ client.open();
+ Thread.sleep(1000);
+ // There is a thread listening to the client
+ Assert.assertEquals(1, ((ThreadPoolExecutor) server.getExecutorService()).getActiveCount());
+ server.stop();
+ server.waitForShutdown();
+ // After server is stopped, the executor thread pool should be shut down
+ Assert.assertTrue("Server thread pool should be terminated.", server.getExecutorService().isTerminated());
+ Assert.assertTrue("Client is still open.", client.isOpen());
+ }
+ }
+
+ private TThreadPoolServer buildServer(TServerTransport serverSocket) {
+ TThreadPoolServer.Args args = new TThreadPoolServer.Args(serverSocket)
+ .protocolFactory(new TBinaryProtocol.Factory())
+ .processor(new ThriftTest.Processor<>(new ServerTestBase.TestHandler()));
+ return new TThreadPoolServer(args);
+ }
+}
diff --git a/lib/java/test/org/apache/thrift/transport/TestNonblockingServerSocket.java b/lib/java/test/org/apache/thrift/transport/TestNonblockingServerSocket.java
new file mode 100644
index 0000000..6b28dfd
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/transport/TestNonblockingServerSocket.java
@@ -0,0 +1,36 @@
+/*
+ * 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 org.apache.thrift.transport;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.channels.ServerSocketChannel;
+
+public class TestNonblockingServerSocket {
+
+ @Test
+ public void testSocketChannelBlockingMode() throws TTransportException {
+ try (TNonblockingServerSocket nonblockingServer = new TNonblockingServerSocket(0)){
+ ServerSocketChannel socketChannel = nonblockingServer.getServerSocketChannel();
+ Assert.assertFalse("Socket channel should be nonblocking", socketChannel.isBlocking());
+ }
+ }
+}
diff --git a/lib/java/test/org/apache/thrift/transport/TestTMemoryTransport.java b/lib/java/test/org/apache/thrift/transport/TestTMemoryTransport.java
new file mode 100644
index 0000000..2e20ffe
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/transport/TestTMemoryTransport.java
@@ -0,0 +1,65 @@
+/*
+ * 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 org.apache.thrift.transport;
+
+import org.apache.thrift.TByteArrayOutputStream;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Random;
+
+public class TestTMemoryTransport {
+
+ @Test
+ public void testReadBatches() throws TTransportException {
+ byte[] inputBytes = {0x10, 0x7A, (byte) 0xBF, (byte) 0xFE, 0x53, (byte) 0x82, (byte) 0xFF};
+ TMemoryTransport transport = new TMemoryTransport(inputBytes);
+ byte[] read = new byte[inputBytes.length];
+ int firstBatch = new Random().nextInt(inputBytes.length);
+ int secondBatch = inputBytes.length - firstBatch;
+ transport.read(read, 0, firstBatch);
+ transport.read(read, firstBatch, secondBatch);
+ boolean equal = true;
+ for (int i = 0; i < inputBytes.length; i++) {
+ equal = equal && inputBytes[i] == read[i];
+ }
+ Assert.assertEquals(ByteBuffer.wrap(inputBytes), ByteBuffer.wrap(read));
+ }
+
+ @Test (expected = TTransportException.class)
+ public void testReadMoreThanRemaining() throws TTransportException {
+ TMemoryTransport transport = new TMemoryTransport(new byte[] {0x00, 0x32});
+ byte[] read = new byte[3];
+ transport.read(read, 0, 3);
+ }
+
+ @Test
+ public void testWrite() throws TTransportException {
+ TMemoryTransport transport = new TMemoryTransport(new byte[0]);
+ byte[] output1 = {0x72, 0x56, 0x29, (byte) 0xAF, (byte) 0x9B};
+ transport.write(output1);
+ byte[] output2 = {(byte) 0x83, 0x10, 0x00};
+ transport.write(output2, 0, 2);
+ byte[] expected = {0x72, 0x56, 0x29, (byte) 0xAF, (byte) 0x9B, (byte) 0x83, 0x10};
+ TByteArrayOutputStream outputByteArray = transport.getOutput();
+ Assert.assertEquals(ByteBuffer.wrap(expected), ByteBuffer.wrap(outputByteArray.get(), 0, outputByteArray.len()));
+ }
+}
diff --git a/lib/java/test/org/apache/thrift/transport/TestTSaslTransports.java b/lib/java/test/org/apache/thrift/transport/TestTSaslTransports.java
index 36a06e9..6eb38e7 100644
--- a/lib/java/test/org/apache/thrift/transport/TestTSaslTransports.java
+++ b/lib/java/test/org/apache/thrift/transport/TestTSaslTransports.java
@@ -53,17 +53,17 @@
private static final Logger LOGGER = LoggerFactory.getLogger(TestTSaslTransports.class);
- private static final String HOST = "localhost";
- private static final String SERVICE = "thrift-test";
- private static final String PRINCIPAL = "thrift-test-principal";
- private static final String PASSWORD = "super secret password";
- private static final String REALM = "thrift-test-realm";
+ public static final String HOST = "localhost";
+ public static final String SERVICE = "thrift-test";
+ public static final String PRINCIPAL = "thrift-test-principal";
+ public static final String PASSWORD = "super secret password";
+ public static final String REALM = "thrift-test-realm";
- private static final String UNWRAPPED_MECHANISM = "CRAM-MD5";
- private static final Map<String, String> UNWRAPPED_PROPS = null;
+ public static final String UNWRAPPED_MECHANISM = "CRAM-MD5";
+ public static final Map<String, String> UNWRAPPED_PROPS = null;
- private static final String WRAPPED_MECHANISM = "DIGEST-MD5";
- private static final Map<String, String> WRAPPED_PROPS = new HashMap<String, String>();
+ public static final String WRAPPED_MECHANISM = "DIGEST-MD5";
+ public static final Map<String, String> WRAPPED_PROPS = new HashMap<String, String>();
static {
WRAPPED_PROPS.put(Sasl.QOP, "auth-int");
@@ -80,7 +80,7 @@
+ "'We hold these truths to be self-evident, that all men are created equal.'";
- private static class TestSaslCallbackHandler implements CallbackHandler {
+ public static class TestSaslCallbackHandler implements CallbackHandler {
private final String password;
public TestSaslCallbackHandler(String password) {
@@ -265,7 +265,7 @@
new TestTSaslTransportsWithServer().testIt();
}
- private static class TestTSaslTransportsWithServer extends ServerTestBase {
+ public static class TestTSaslTransportsWithServer extends ServerTestBase {
private Thread serverThread;
private TServer server;
diff --git a/lib/java/test/org/apache/thrift/transport/sasl/TestDataFrameReader.java b/lib/java/test/org/apache/thrift/transport/sasl/TestDataFrameReader.java
new file mode 100644
index 0000000..9ae0e1e
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/transport/sasl/TestDataFrameReader.java
@@ -0,0 +1,61 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import org.apache.thrift.transport.TMemoryInputTransport;
+import org.apache.thrift.transport.TTransportException;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public class TestDataFrameReader {
+
+ @Test
+ public void testRead() throws TTransportException {
+ // Prepare data
+ int payloadSize = 23;
+ ByteBuffer buffer = ByteBuffer.allocate(DataFrameHeaderReader.PAYLOAD_LENGTH_BYTES + payloadSize);
+ buffer.putInt(payloadSize);
+ for (int i = 0; i < payloadSize; i++) {
+ buffer.put((byte) i);
+ }
+ buffer.rewind();
+
+ TMemoryInputTransport transport = new TMemoryInputTransport();
+ DataFrameReader dataFrameReader = new DataFrameReader();
+ // No bytes received.
+ dataFrameReader.read(transport);
+ Assert.assertFalse("No bytes received", dataFrameReader.isComplete());
+ Assert.assertFalse("No bytes received", dataFrameReader.getHeader().isComplete());
+ // Payload size (header) and part of the payload are received.
+ transport.reset(buffer.array(), 0, 6);
+ dataFrameReader.read(transport);
+ Assert.assertFalse("Only header is complete", dataFrameReader.isComplete());
+ Assert.assertTrue("Header should be complete", dataFrameReader.getHeader().isComplete());
+ Assert.assertEquals("Payload size should be " + payloadSize, payloadSize, dataFrameReader.getHeader().payloadSize());
+ // Read the rest of payload.
+ transport.reset(buffer.array(), 6, 21);
+ dataFrameReader.read(transport);
+ Assert.assertTrue("Reader should be complete", dataFrameReader.isComplete());
+ buffer.position(DataFrameHeaderReader.PAYLOAD_LENGTH_BYTES);
+ Assert.assertEquals("Payload should be the same as from the transport", buffer, ByteBuffer.wrap(dataFrameReader.getPayload()));
+ }
+}
diff --git a/lib/java/test/org/apache/thrift/transport/sasl/TestDataFrameWriter.java b/lib/java/test/org/apache/thrift/transport/sasl/TestDataFrameWriter.java
new file mode 100644
index 0000000..d242593
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/transport/sasl/TestDataFrameWriter.java
@@ -0,0 +1,101 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.thrift.EncodingUtils;
+import org.apache.thrift.transport.TNonblockingTransport;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import static org.apache.thrift.transport.sasl.DataFrameHeaderReader.PAYLOAD_LENGTH_BYTES;
+
+public class TestDataFrameWriter {
+
+ private static final byte[] BYTES = new byte[]{0x32, 0x2A, (byte) 0xE1, 0x18, (byte) 0x90, 0x75};
+
+ @Test
+ public void testProvideEntireByteArrayAsPayload() {
+ DataFrameWriter frameWriter = new DataFrameWriter();
+ frameWriter.withOnlyPayload(BYTES);
+ byte[] expectedBytes = new byte[BYTES.length + PAYLOAD_LENGTH_BYTES];
+ EncodingUtils.encodeBigEndian(BYTES.length, expectedBytes);
+ System.arraycopy(BYTES, 0, expectedBytes, PAYLOAD_LENGTH_BYTES, BYTES.length);
+ Assert.assertEquals(ByteBuffer.wrap(expectedBytes), frameWriter.frameBytes);
+ }
+
+ @Test
+ public void testProvideByteArrayPortionAsPayload() {
+ DataFrameWriter frameWriter = new DataFrameWriter();
+ int portionOffset = 2;
+ int portionLength = 3;
+ frameWriter.withOnlyPayload(BYTES, portionOffset, portionLength);
+ byte[] expectedBytes = new byte[portionLength + PAYLOAD_LENGTH_BYTES];
+ EncodingUtils.encodeBigEndian(portionLength, expectedBytes);
+ System.arraycopy(BYTES, portionOffset, expectedBytes, PAYLOAD_LENGTH_BYTES, portionLength);
+ Assert.assertEquals(ByteBuffer.wrap(expectedBytes), frameWriter.frameBytes);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testProvideHeaderAndPayload() {
+ DataFrameWriter frameWriter = new DataFrameWriter();
+ frameWriter.withHeaderAndPayload(new byte[1], new byte[1]);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testProvidePayloadToIncompleteFrame() {
+ DataFrameWriter frameWriter = new DataFrameWriter();
+ frameWriter.withOnlyPayload(BYTES);
+ frameWriter.withOnlyPayload(new byte[1]);
+ }
+
+ @Test
+ public void testWrite() throws IOException {
+ DataFrameWriter frameWriter = new DataFrameWriter();
+ frameWriter.withOnlyPayload(BYTES);
+ // Slow socket which writes one byte per call.
+ TNonblockingTransport transport = Mockito.mock(TNonblockingTransport.class);
+ SlowWriting slowWriting = new SlowWriting();
+ Mockito.when(transport.write(frameWriter.frameBytes)).thenAnswer(slowWriting);
+ frameWriter.write(transport);
+ while (slowWriting.written < frameWriter.frameBytes.limit()) {
+ Assert.assertFalse("Frame writer should not be complete", frameWriter.isComplete());
+ frameWriter.write(transport);
+ }
+ Assert.assertTrue("Frame writer should be complete", frameWriter.isComplete());
+ }
+
+ private static class SlowWriting implements Answer<Integer> {
+ int written = 0;
+
+ @Override
+ public Integer answer(InvocationOnMock invocation) throws Throwable {
+ ByteBuffer bytes = (ByteBuffer) invocation.getArguments()[0];
+ bytes.get();
+ written ++;
+ return 1;
+ }
+ }
+}
diff --git a/lib/java/test/org/apache/thrift/transport/sasl/TestSaslNegotiationFrameReader.java b/lib/java/test/org/apache/thrift/transport/sasl/TestSaslNegotiationFrameReader.java
new file mode 100644
index 0000000..f2abbe6
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/transport/sasl/TestSaslNegotiationFrameReader.java
@@ -0,0 +1,64 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import org.apache.thrift.transport.TMemoryInputTransport;
+import org.apache.thrift.transport.TTransportException;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public class TestSaslNegotiationFrameReader {
+
+ @Test
+ public void testRead() throws TTransportException {
+ TMemoryInputTransport transport = new TMemoryInputTransport();
+ SaslNegotiationFrameReader negotiationReader = new SaslNegotiationFrameReader();
+ // No bytes received
+ negotiationReader.read(transport);
+ Assert.assertFalse("No bytes received", negotiationReader.isComplete());
+ Assert.assertFalse("No bytes received", negotiationReader.getHeader().isComplete());
+ // Read header
+ ByteBuffer buffer = ByteBuffer.allocate(5);
+ buffer.put(0, NegotiationStatus.OK.getValue());
+ buffer.putInt(1, 10);
+ transport.reset(buffer.array());
+ negotiationReader.read(transport);
+ Assert.assertFalse("Only header is complete", negotiationReader.isComplete());
+ Assert.assertTrue("Header should be complete", negotiationReader.getHeader().isComplete());
+ Assert.assertEquals("Payload size should be 10", 10, negotiationReader.getHeader().payloadSize());
+ // Read payload
+ transport.reset(new byte[20]);
+ negotiationReader.read(transport);
+ Assert.assertTrue("Reader should be complete", negotiationReader.isComplete());
+ Assert.assertEquals("Payload length should be 10", 10, negotiationReader.getPayload().length);
+ }
+
+ @Test (expected = TSaslNegotiationException.class)
+ public void testReadInvalidNegotiationStatus() throws TTransportException {
+ byte[] bytes = new byte[5];
+ // Invalid status byte.
+ bytes[0] = -1;
+ TMemoryInputTransport transport = new TMemoryInputTransport(bytes);
+ SaslNegotiationFrameReader negotiationReader = new SaslNegotiationFrameReader();
+ negotiationReader.read(transport);
+ }
+}
diff --git a/lib/java/test/org/apache/thrift/transport/sasl/TestSaslNegotiationFrameWriter.java b/lib/java/test/org/apache/thrift/transport/sasl/TestSaslNegotiationFrameWriter.java
new file mode 100644
index 0000000..ce7ff29
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/transport/sasl/TestSaslNegotiationFrameWriter.java
@@ -0,0 +1,56 @@
+/*
+ * 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 org.apache.thrift.transport.sasl;
+
+import java.nio.ByteBuffer;
+
+import org.apache.thrift.EncodingUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.thrift.transport.sasl.SaslNegotiationFrameWriter.HEADER_BYTES;
+
+public class TestSaslNegotiationFrameWriter {
+
+ private static final byte[] PAYLOAD = {0x11, 0x08, 0x3F, 0x58, 0x73, 0x22, 0x00, (byte) 0xFF};
+
+ @Test
+ public void testWithHeaderAndPayload() {
+ SaslNegotiationFrameWriter frameWriter = new SaslNegotiationFrameWriter();
+ frameWriter.withHeaderAndPayload(new byte[] {NegotiationStatus.OK.getValue()}, PAYLOAD);
+ byte[] expectedBytes = new byte[HEADER_BYTES + PAYLOAD.length];
+ expectedBytes[0] = NegotiationStatus.OK.getValue();
+ EncodingUtils.encodeBigEndian(PAYLOAD.length, expectedBytes, 1);
+ System.arraycopy(PAYLOAD, 0, expectedBytes, HEADER_BYTES, PAYLOAD.length);
+ Assert.assertEquals(ByteBuffer.wrap(expectedBytes), frameWriter.frameBytes);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWithInvalidHeaderLength() {
+ SaslNegotiationFrameWriter frameWriter = new SaslNegotiationFrameWriter();
+ frameWriter.withHeaderAndPayload(new byte[5], 0, 2, PAYLOAD, 0, 1);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testWithOnlyPayload() {
+ SaslNegotiationFrameWriter frameWriter = new SaslNegotiationFrameWriter();
+ frameWriter.withOnlyPayload(new byte[0]);
+ }
+}
diff --git a/lib/java/test/org/apache/thrift/utils/TestStringUtils.java b/lib/java/test/org/apache/thrift/utils/TestStringUtils.java
new file mode 100644
index 0000000..3a8cf39
--- /dev/null
+++ b/lib/java/test/org/apache/thrift/utils/TestStringUtils.java
@@ -0,0 +1,34 @@
+/*
+ * 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 org.apache.thrift.utils;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestStringUtils {
+
+ @Test
+ public void testToHexString() {
+ byte[] bytes = {0x00, 0x1A, (byte) 0xEF, (byte) 0xAB, (byte) 0x92};
+ Assert.assertEquals("001AEFAB92", StringUtils.bytesToHexString(bytes));
+ Assert.assertEquals("EFAB92", StringUtils.bytesToHexString(bytes, 2, 3));
+ Assert.assertNull(StringUtils.bytesToHexString(null));
+ }
+}
diff --git a/lib/js/package.json b/lib/js/package.json
index f578e4b..b75019d 100644
--- a/lib/js/package.json
+++ b/lib/js/package.json
@@ -2,6 +2,7 @@
"name": "thrift",
"version": "0.14.0",
"description": "Thrift is a software framework for scalable cross-language services development.",
+ "main": "./src/thrift",
"author": {
"name": "Apache Thrift Developers",
"email": "dev@thrift.apache.org"
diff --git a/lib/netstd/README.md b/lib/netstd/README.md
index 74f5ed8..d554e38 100644
--- a/lib/netstd/README.md
+++ b/lib/netstd/README.md
@@ -11,7 +11,7 @@
- Build with scripts
## How to build on Unix/Linux
-- Ensure you have .NET Core SDK 3.0 installed, or use the [Ubuntu docker image](../../build/docker/README.md)
+- Ensure you have .NET Core SDK 3.1 (LTS) installed, or use the [Ubuntu docker image](../../build/docker/README.md)
- Follow common automake build practice: `./ bootstrap && ./ configure && make`
## Known issues
diff --git a/lib/netstd/Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj b/lib/netstd/Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj
index c3cdc11..7c5639b 100644
--- a/lib/netstd/Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj
+++ b/lib/netstd/Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj
@@ -19,7 +19,7 @@
-->
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Thrift.IntegrationTests</AssemblyName>
<PackageId>Thrift.IntegrationTests</PackageId>
<OutputType>Exe</OutputType>
@@ -32,11 +32,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="CompareNETObjects" Version="4.58.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
- <PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
- <PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
- <PackageReference Include="System.ServiceModel.Primitives" Version="4.5.3" />
+ <PackageReference Include="CompareNETObjects" Version="4.64.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
+ <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
+ <PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
+ <PackageReference Include="System.ServiceModel.Primitives" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Thrift\Thrift.csproj" />
diff --git a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
index 498191f..d2db348 100644
--- a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
+++ b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
@@ -19,7 +19,7 @@
-->
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Thrift.PublicInterfaces.Compile.Tests</AssemblyName>
<PackageId>Thrift.PublicInterfaces.Compile.Tests</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
@@ -33,7 +33,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="System.ServiceModel.Primitives" Version="4.5.3" />
+ <PackageReference Include="System.ServiceModel.Primitives" Version="4.7.0" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="_GenerateRestoreProjectSpec;Restore;Compile">
diff --git a/lib/netstd/Tests/Thrift.Tests/Thrift.Tests.csproj b/lib/netstd/Tests/Thrift.Tests/Thrift.Tests.csproj
index b060182..20fdfe4 100644
--- a/lib/netstd/Tests/Thrift.Tests/Thrift.Tests.csproj
+++ b/lib/netstd/Tests/Thrift.Tests/Thrift.Tests.csproj
@@ -18,14 +18,14 @@
under the License.
-->
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="CompareNETObjects" Version="4.58.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
- <PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
- <PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
- <PackageReference Include="NSubstitute" Version="4.0.0" />
+ <PackageReference Include="CompareNETObjects" Version="4.64.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
+ <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
+ <PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
+ <PackageReference Include="NSubstitute" Version="4.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Thrift\Thrift.csproj" />
diff --git a/lib/netstd/Thrift/Thrift.csproj b/lib/netstd/Thrift/Thrift.csproj
index 5d8a9c3..e40db00 100644
--- a/lib/netstd/Thrift/Thrift.csproj
+++ b/lib/netstd/Thrift/Thrift.csproj
@@ -44,16 +44,16 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" />
+ <PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.0" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.0" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.0" />
<PackageReference Include="System.IO.Pipes" Version="[4.3,)" />
<PackageReference Include="System.IO.Pipes.AccessControl" Version="4.5.1" />
- <PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.5.2" />
+ <PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.7.0" />
<PackageReference Include="System.Net.NameResolution" Version="[4.3,)" />
<PackageReference Include="System.Net.Requests" Version="[4.3,)" />
<PackageReference Include="System.Net.Security" Version="4.3.2" />
- <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.2" />
+ <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
</ItemGroup>
</Project>
diff --git a/lib/py/src/Thrift.py b/lib/py/src/Thrift.py
index c390cbb..ef655ea 100644
--- a/lib/py/src/Thrift.py
+++ b/lib/py/src/Thrift.py
@@ -17,8 +17,6 @@
# under the License.
#
-import sys
-
class TType(object):
STOP = 0
@@ -90,15 +88,6 @@
class TException(Exception):
"""Base class for all thrift exceptions."""
- # BaseException.message is deprecated in Python v[2.6,3.0)
- if (2, 6, 0) <= sys.version_info < (3, 0):
- def _get_message(self):
- return self._message
-
- def _set_message(self, message):
- self._message = message
- message = property(_get_message, _set_message)
-
def __init__(self, message=None):
Exception.__init__(self, message)
self.message = message
diff --git a/lib/py/src/protocol/TBase.py b/lib/py/src/protocol/TBase.py
index 9ae1b11..6c6ef18 100644
--- a/lib/py/src/protocol/TBase.py
+++ b/lib/py/src/protocol/TBase.py
@@ -80,3 +80,7 @@
[self.__class__, self.thrift_spec])
else:
return iprot.readStruct(cls, cls.thrift_spec, True)
+
+
+class TFrozenExceptionBase(TFrozenBase, TExceptionBase):
+ pass
diff --git a/lib/py/src/protocol/TProtocol.py b/lib/py/src/protocol/TProtocol.py
index 3456e8f..339a283 100644
--- a/lib/py/src/protocol/TProtocol.py
+++ b/lib/py/src/protocol/TProtocol.py
@@ -303,8 +303,14 @@
def readContainerStruct(self, spec):
(obj_class, obj_spec) = spec
- obj = obj_class()
- obj.read(self)
+
+ # If obj_class.read is a classmethod (e.g. in frozen structs),
+ # call it as such.
+ if getattr(obj_class.read, '__self__', None) is obj_class:
+ obj = obj_class.read(self)
+ else:
+ obj = obj_class()
+ obj.read(self)
return obj
def readContainerMap(self, spec):
diff --git a/lib/rs/src/protocol/compact.rs b/lib/rs/src/protocol/compact.rs
index 1750bc4..3e17398 100644
--- a/lib/rs/src/protocol/compact.rs
+++ b/lib/rs/src/protocol/compact.rs
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
+use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use integer_encoding::{VarIntReader, VarIntWriter};
use std::convert::{From, TryFrom};
use std::io;
@@ -247,7 +247,7 @@
}
fn read_double(&mut self) -> ::Result<f64> {
- self.transport.read_f64::<BigEndian>().map_err(From::from)
+ self.transport.read_f64::<LittleEndian>().map_err(From::from)
}
fn read_string(&mut self) -> ::Result<String> {
@@ -521,7 +521,7 @@
}
fn write_double(&mut self, d: f64) -> ::Result<()> {
- self.transport.write_f64::<BigEndian>(d).map_err(From::from)
+ self.transport.write_f64::<LittleEndian>(d).map_err(From::from)
}
fn write_string(&mut self, s: &str) -> ::Result<()> {
@@ -2374,6 +2374,29 @@
(i_prot, o_prot)
}
+ #[test]
+ fn must_read_write_double() {
+ let (mut i_prot, mut o_prot) = test_objects();
+
+ let double = 3.141592653589793238462643;
+ o_prot.write_double(double).unwrap();
+ copy_write_buffer_to_read_buffer!(o_prot);
+
+ assert_eq!(i_prot.read_double().unwrap(), double);
+ }
+
+ #[test]
+ fn must_encode_double_as_other_langs() {
+ let (_, mut o_prot) = test_objects();
+ let expected = [24, 45, 68, 84, 251, 33, 9, 64];
+
+ let double = 3.141592653589793238462643;
+ o_prot.write_double(double).unwrap();
+
+ assert_eq_written_bytes!(o_prot, expected);
+
+ }
+
fn assert_no_write<F>(mut write_fn: F)
where
F: FnMut(&mut TCompactOutputProtocol<WriteHalf<TBufferChannel>>) -> ::Result<()>,
diff --git a/lib/rs/src/protocol/stored.rs b/lib/rs/src/protocol/stored.rs
index faa5128..bf2d8ba 100644
--- a/lib/rs/src/protocol/stored.rs
+++ b/lib/rs/src/protocol/stored.rs
@@ -52,8 +52,8 @@
/// impl TProcessor for ActualProcessor {
/// fn process(
/// &self,
-/// _: &mut TInputProtocol,
-/// _: &mut TOutputProtocol
+/// _: &mut dyn TInputProtocol,
+/// _: &mut dyn TOutputProtocol
/// ) -> thrift::Result<()> {
/// unimplemented!()
/// }
diff --git a/lib/rs/src/server/mod.rs b/lib/rs/src/server/mod.rs
index b719d1b..f24c113 100644
--- a/lib/rs/src/server/mod.rs
+++ b/lib/rs/src/server/mod.rs
@@ -56,7 +56,7 @@
///
/// // `TProcessor` implementation for `SimpleService`
/// impl TProcessor for SimpleServiceSyncProcessor {
-/// fn process(&self, i: &mut TInputProtocol, o: &mut TOutputProtocol) -> thrift::Result<()> {
+/// fn process(&self, i: &mut dyn TInputProtocol, o: &mut dyn TOutputProtocol) -> thrift::Result<()> {
/// unimplemented!();
/// }
/// }
diff --git a/lib/rs/src/server/threaded.rs b/lib/rs/src/server/threaded.rs
index cc658bd..ebd3720 100644
--- a/lib/rs/src/server/threaded.rs
+++ b/lib/rs/src/server/threaded.rs
@@ -64,7 +64,7 @@
///
/// // `TProcessor` implementation for `SimpleService`
/// impl TProcessor for SimpleServiceSyncProcessor {
-/// fn process(&self, i: &mut TInputProtocol, o: &mut TOutputProtocol) -> thrift::Result<()> {
+/// fn process(&self, i: &mut dyn TInputProtocol, o: &mut dyn TOutputProtocol) -> thrift::Result<()> {
/// unimplemented!();
/// }
/// }
@@ -90,10 +90,10 @@
/// let processor = SimpleServiceSyncProcessor::new(SimpleServiceHandlerImpl {});
///
/// // instantiate the server
-/// let i_tr_fact: Box<TReadTransportFactory> = Box::new(TBufferedReadTransportFactory::new());
-/// let i_pr_fact: Box<TInputProtocolFactory> = Box::new(TBinaryInputProtocolFactory::new());
-/// let o_tr_fact: Box<TWriteTransportFactory> = Box::new(TBufferedWriteTransportFactory::new());
-/// let o_pr_fact: Box<TOutputProtocolFactory> = Box::new(TBinaryOutputProtocolFactory::new());
+/// let i_tr_fact: Box<dyn TReadTransportFactory> = Box::new(TBufferedReadTransportFactory::new());
+/// let i_pr_fact: Box<dyn TInputProtocolFactory> = Box::new(TBinaryInputProtocolFactory::new());
+/// let o_tr_fact: Box<dyn TWriteTransportFactory> = Box::new(TBufferedWriteTransportFactory::new());
+/// let o_pr_fact: Box<dyn TOutputProtocolFactory> = Box::new(TBinaryOutputProtocolFactory::new());
///
/// let mut server = TServer::new(
/// i_tr_fact,
diff --git a/lib/rs/src/transport/mem.rs b/lib/rs/src/transport/mem.rs
index 82c4b57..9874257 100644
--- a/lib/rs/src/transport/mem.rs
+++ b/lib/rs/src/transport/mem.rs
@@ -31,7 +31,7 @@
/// `set_readable_bytes(...)`. Callers can then read until the buffer is
/// depleted. No further reads are accepted until the internal read buffer is
/// replenished again.
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub struct TBufferChannel {
read: Arc<Mutex<ReadData>>,
write: Arc<Mutex<WriteData>>,
diff --git a/test/DebugProtoTest.thrift b/test/DebugProtoTest.thrift
index de47ea7..1ab0f6a 100644
--- a/test/DebugProtoTest.thrift
+++ b/test/DebugProtoTest.thrift
@@ -241,6 +241,10 @@
2: map<string, string> map_field;
}
+exception MutableException {
+ 1: string msg;
+} (python.immutable = "false")
+
service ServiceForExceptionWithAMap {
void methodThatThrowsAnException() throws (1: ExceptionWithAMap xwamap);
}
diff --git a/test/known_failures_Linux.json b/test/known_failures_Linux.json
index dd6195a..e165aa4 100644
--- a/test/known_failures_Linux.json
+++ b/test/known_failures_Linux.json
@@ -85,74 +85,109 @@
"cpp-nodejs_multij-json_http-domain",
"cpp-nodejs_multij-json_http-ip",
"cpp-nodejs_multij-json_http-ip-ssl",
+ "cpp-py3_binary-accel_http-domain",
"cpp-py3_binary-accel_http-ip",
"cpp-py3_binary-accel_http-ip-ssl",
+ "cpp-py3_binary_http-domain",
"cpp-py3_binary_http-ip",
"cpp-py3_binary_http-ip-ssl",
+ "cpp-py3_compact-accelc_http-domain",
"cpp-py3_compact-accelc_http-ip",
"cpp-py3_compact-accelc_http-ip-ssl",
+ "cpp-py3_compact_http-domain",
"cpp-py3_compact_http-ip",
"cpp-py3_compact_http-ip-ssl",
+ "cpp-py3_header_http-domain",
"cpp-py3_header_http-ip",
"cpp-py3_header_http-ip-ssl",
+ "cpp-py3_json_http-domain",
"cpp-py3_json_http-ip",
"cpp-py3_json_http-ip-ssl",
+ "cpp-py3_multi-accel_http-domain",
"cpp-py3_multi-accel_http-ip",
"cpp-py3_multi-accel_http-ip-ssl",
+ "cpp-py3_multi-binary_http-domain",
"cpp-py3_multi-binary_http-ip",
"cpp-py3_multi-binary_http-ip-ssl",
+ "cpp-py3_multi-multia_http-domain",
"cpp-py3_multi-multia_http-ip",
"cpp-py3_multi-multia_http-ip-ssl",
+ "cpp-py3_multi_http-domain",
"cpp-py3_multi_http-ip",
"cpp-py3_multi_http-ip-ssl",
+ "cpp-py3_multic-accelc_http-domain",
"cpp-py3_multic-accelc_http-ip",
"cpp-py3_multic-accelc_http-ip-ssl",
+ "cpp-py3_multic-compact_http-domain",
"cpp-py3_multic-compact_http-ip",
"cpp-py3_multic-compact_http-ip-ssl",
+ "cpp-py3_multic-multiac_http-domain",
"cpp-py3_multic-multiac_http-ip",
"cpp-py3_multic-multiac_http-ip-ssl",
+ "cpp-py3_multic_http-domain",
"cpp-py3_multic_http-ip",
"cpp-py3_multic_http-ip-ssl",
+ "cpp-py3_multih-header_http-domain",
"cpp-py3_multih-header_http-ip",
"cpp-py3_multih-header_http-ip-ssl",
+ "cpp-py3_multij-json_http-domain",
"cpp-py3_multij-json_http-ip",
"cpp-py3_multij-json_http-ip-ssl",
+ "cpp-py3_multij_http-domain",
"cpp-py3_multij_http-ip",
"cpp-py3_multij_http-ip-ssl",
+ "cpp-py_binary-accel_http-domain",
"cpp-py_binary-accel_http-ip",
"cpp-py_binary-accel_http-ip-ssl",
+ "cpp-py_binary_http-domain",
"cpp-py_binary_http-ip",
"cpp-py_binary_http-ip-ssl",
+ "cpp-py_compact-accelc_http-domain",
"cpp-py_compact-accelc_http-ip",
"cpp-py_compact-accelc_http-ip-ssl",
+ "cpp-py_compact_http-domain",
"cpp-py_compact_http-ip",
"cpp-py_compact_http-ip-ssl",
+ "cpp-py_header_http-domain",
"cpp-py_header_http-ip",
"cpp-py_header_http-ip-ssl",
+ "cpp-py_json_http-domain",
"cpp-py_json_http-ip",
"cpp-py_json_http-ip-ssl",
+ "cpp-py_multi-accel_http-domain",
"cpp-py_multi-accel_http-ip",
"cpp-py_multi-accel_http-ip-ssl",
+ "cpp-py_multi-binary_http-domain",
"cpp-py_multi-binary_http-ip",
"cpp-py_multi-binary_http-ip-ssl",
+ "cpp-py_multi-multia_http-domain",
"cpp-py_multi-multia_http-ip",
"cpp-py_multi-multia_http-ip-ssl",
+ "cpp-py_multi_http-domain",
"cpp-py_multi_http-ip",
"cpp-py_multi_http-ip-ssl",
+ "cpp-py_multic-accelc_http-domain",
"cpp-py_multic-accelc_http-ip",
"cpp-py_multic-accelc_http-ip-ssl",
+ "cpp-py_multic-compact_http-domain",
"cpp-py_multic-compact_http-ip",
"cpp-py_multic-compact_http-ip-ssl",
+ "cpp-py_multic-multiac_http-domain",
"cpp-py_multic-multiac_http-ip",
"cpp-py_multic-multiac_http-ip-ssl",
+ "cpp-py_multic_http-domain",
"cpp-py_multic_http-ip",
"cpp-py_multic_http-ip-ssl",
+ "cpp-py_multih-header_http-domain",
"cpp-py_multih-header_http-ip",
"cpp-py_multih-header_http-ip-ssl",
+ "cpp-py_multih_http-domain",
"cpp-py_multih_http-ip",
"cpp-py_multih_http-ip-ssl",
+ "cpp-py_multij-json_http-domain",
"cpp-py_multij-json_http-ip",
"cpp-py_multij-json_http-ip-ssl",
+ "cpp-py_multij_http-domain",
"cpp-py_multij_http-ip",
"cpp-py_multij_http-ip-ssl",
"cpp-rs_multi_buffered-ip",
@@ -389,76 +424,112 @@
"nodejs-lua_binary_http-ip",
"nodejs-lua_compact_http-ip",
"nodejs-lua_json_http-ip",
+ "nodejs-py3_binary-accel_http-domain",
"nodejs-py3_binary-accel_http-ip",
"nodejs-py3_binary-accel_http-ip-ssl",
+ "nodejs-py3_binary_http-domain",
"nodejs-py3_binary_http-ip",
"nodejs-py3_binary_http-ip-ssl",
+ "nodejs-py3_compact-accelc_http-domain",
"nodejs-py3_compact-accelc_http-ip",
"nodejs-py3_compact-accelc_http-ip-ssl",
+ "nodejs-py3_compact_http-domain",
"nodejs-py3_compact_http-ip",
"nodejs-py3_compact_http-ip-ssl",
+ "nodejs-py3_header_http-domain",
"nodejs-py3_header_http-ip",
"nodejs-py3_header_http-ip-ssl",
+ "nodejs-py3_json_http-domain",
"nodejs-py3_json_http-ip",
"nodejs-py3_json_http-ip-ssl",
+ "nodejs-py_binary-accel_http-domain",
"nodejs-py_binary-accel_http-ip",
"nodejs-py_binary-accel_http-ip-ssl",
+ "nodejs-py_binary_http-domain",
"nodejs-py_binary_http-ip",
"nodejs-py_binary_http-ip-ssl",
+ "nodejs-py_compact-accelc_http-domain",
"nodejs-py_compact-accelc_http-ip",
"nodejs-py_compact-accelc_http-ip-ssl",
+ "nodejs-py_compact_http-domain",
"nodejs-py_compact_http-ip",
"nodejs-py_compact_http-ip-ssl",
+ "nodejs-py_header_http-domain",
"nodejs-py_header_http-ip",
"nodejs-py_header_http-ip-ssl",
+ "nodejs-py_json_http-domain",
"nodejs-py_json_http-ip",
"nodejs-py_json_http-ip-ssl",
"perl-rs_multi_buffered-ip",
"perl-rs_multi_framed-ip",
+ "py-cpp_accel-binary_http-domain",
"py-cpp_accel-binary_http-ip",
"py-cpp_accel-binary_http-ip-ssl",
+ "py-cpp_accel-binary_zlib-domain",
"py-cpp_accel-binary_zlib-ip",
"py-cpp_accel-binary_zlib-ip-ssl",
+ "py-cpp_accelc-compact_http-domain",
"py-cpp_accelc-compact_http-ip",
"py-cpp_accelc-compact_http-ip-ssl",
+ "py-cpp_accelc-compact_zlib-domain",
"py-cpp_accelc-compact_zlib-ip",
"py-cpp_accelc-compact_zlib-ip-ssl",
+ "py-cpp_binary_http-domain",
"py-cpp_binary_http-ip",
"py-cpp_binary_http-ip-ssl",
+ "py-cpp_compact_http-domain",
"py-cpp_compact_http-ip",
"py-cpp_compact_http-ip-ssl",
+ "py-cpp_header_http-domain",
"py-cpp_header_http-ip",
"py-cpp_header_http-ip-ssl",
+ "py-cpp_json_http-domain",
"py-cpp_json_http-ip",
"py-cpp_json_http-ip-ssl",
+ "py-cpp_multi-binary_http-domain",
"py-cpp_multi-binary_http-ip",
"py-cpp_multi-binary_http-ip-ssl",
+ "py-cpp_multi_http-domain",
"py-cpp_multi_http-ip",
"py-cpp_multi_http-ip-ssl",
+ "py-cpp_multia-binary_http-domain",
"py-cpp_multia-binary_http-ip",
"py-cpp_multia-binary_http-ip-ssl",
+ "py-cpp_multia-binary_zlib-domain",
"py-cpp_multia-binary_zlib-ip",
"py-cpp_multia-binary_zlib-ip-ssl",
+ "py-cpp_multia-multi_http-domain",
"py-cpp_multia-multi_http-ip",
"py-cpp_multia-multi_http-ip-ssl",
+ "py-cpp_multia-multi_zlib-domain",
"py-cpp_multia-multi_zlib-ip",
"py-cpp_multia-multi_zlib-ip-ssl",
+ "py-cpp_multiac-compact_http-domain",
"py-cpp_multiac-compact_http-ip",
"py-cpp_multiac-compact_http-ip-ssl",
+ "py-cpp_multiac-compact_zlib-domain",
"py-cpp_multiac-compact_zlib-ip",
"py-cpp_multiac-compact_zlib-ip-ssl",
+ "py-cpp_multiac-multic_http-domain",
"py-cpp_multiac-multic_http-ip",
"py-cpp_multiac-multic_http-ip-ssl",
+ "py-cpp_multiac-multic_zlib-domain",
"py-cpp_multiac-multic_zlib-ip",
"py-cpp_multiac-multic_zlib-ip-ssl",
+ "py-cpp_multic-compact_http-domain",
"py-cpp_multic-compact_http-ip",
"py-cpp_multic-compact_http-ip-ssl",
+ "py-cpp_multic_http-domain",
"py-cpp_multic_http-ip",
"py-cpp_multic_http-ip-ssl",
+ "py-cpp_multih_http-domain",
+ "py-cpp_multih-header_http-domain",
"py-cpp_multih-header_http-ip",
"py-cpp_multih-header_http-ip-ssl",
+ "py-cpp_multij_http-domain",
"py-cpp_multih_http-ip",
"py-cpp_multih_http-ip-ssl",
+ "py-cpp_multij-json_http-domain",
"py-cpp_multij-json_http-ip",
"py-cpp_multij-json_http-ip-ssl",
"py-cpp_multij_http-ip",
@@ -504,6 +575,12 @@
"py-lua_binary_http-ip",
"py-lua_compact_http-ip",
"py-lua_json_http-ip",
+ "py-nodejs_accel-binary_http-domain",
+ "py-nodejs_accelc-compact_http-domain",
+ "py-nodejs_binary_http-domain",
+ "py-nodejs_compact_http-domain",
+ "py-nodejs_json_http-domain",
+ "py-nodejs_header_http-domain",
"py-rs_multi_buffered-ip",
"py-rs_multi_framed-ip",
"py-rs_multia-multi_buffered-ip",
@@ -512,52 +589,76 @@
"py-rs_multiac-multic_framed-ip",
"py-rs_multic_buffered-ip",
"py-rs_multic_framed-ip",
+ "py3-cpp_accel-binary_http-domain",
"py3-cpp_accel-binary_http-ip",
"py3-cpp_accel-binary_http-ip-ssl",
+ "py3-cpp_accel-binary_zlib-domain",
"py3-cpp_accel-binary_zlib-ip",
"py3-cpp_accel-binary_zlib-ip-ssl",
+ "py3-cpp_accelc-compact_http-domain",
"py3-cpp_accelc-compact_http-ip",
"py3-cpp_accelc-compact_http-ip-ssl",
+ "py3-cpp_accelc-compact_zlib-domain",
"py3-cpp_accelc-compact_zlib-ip",
"py3-cpp_accelc-compact_zlib-ip-ssl",
+ "py3-cpp_binary_http-domain",
"py3-cpp_binary_http-ip",
"py3-cpp_binary_http-ip-ssl",
+ "py3-cpp_compact_http-domain",
"py3-cpp_compact_http-ip",
"py3-cpp_compact_http-ip-ssl",
+ "py3-cpp_header_http-domain",
"py3-cpp_header_http-ip",
"py3-cpp_header_http-ip-ssl",
+ "py3-cpp_json_http-domain",
"py3-cpp_json_http-ip",
"py3-cpp_json_http-ip-ssl",
+ "py3-cpp_multi-binary_http-domain",
"py3-cpp_multi-binary_http-ip",
"py3-cpp_multi-binary_http-ip-ssl",
+ "py3-cpp_multi_http-domain",
"py3-cpp_multi_http-ip",
"py3-cpp_multi_http-ip-ssl",
+ "py3-cpp_multia-binary_http-domain",
"py3-cpp_multia-binary_http-ip",
"py3-cpp_multia-binary_http-ip-ssl",
+ "py3-cpp_multia-binary_zlib-domain",
"py3-cpp_multia-binary_zlib-ip",
"py3-cpp_multia-binary_zlib-ip-ssl",
+ "py3-cpp_multia-multi_http-domain",
"py3-cpp_multia-multi_http-ip",
"py3-cpp_multia-multi_http-ip-ssl",
+ "py3-cpp_multia-multi_zlib-domain",
"py3-cpp_multia-multi_zlib-ip",
"py3-cpp_multia-multi_zlib-ip-ssl",
+ "py3-cpp_multiac-compact_http-domain",
"py3-cpp_multiac-compact_http-ip",
"py3-cpp_multiac-compact_http-ip-ssl",
+ "py3-cpp_multiac-compact_zlib-domain",
"py3-cpp_multiac-compact_zlib-ip",
"py3-cpp_multiac-compact_zlib-ip-ssl",
+ "py3-cpp_multiac-multic_http-domain",
"py3-cpp_multiac-multic_http-ip",
"py3-cpp_multiac-multic_http-ip-ssl",
+ "py3-cpp_multiac-multic_zlib-domain",
"py3-cpp_multiac-multic_zlib-ip",
"py3-cpp_multiac-multic_zlib-ip-ssl",
+ "py3-cpp_multic-compact_http-domain",
"py3-cpp_multic-compact_http-ip",
"py3-cpp_multic-compact_http-ip-ssl",
+ "py3-cpp_multic_http-domain",
"py3-cpp_multic_http-ip",
"py3-cpp_multic_http-ip-ssl",
+ "py3-cpp_multih-header_http-domain",
"py3-cpp_multih-header_http-ip",
"py3-cpp_multih-header_http-ip-ssl",
+ "py3-cpp_multih_http-domain",
"py3-cpp_multih_http-ip",
"py3-cpp_multih_http-ip-ssl",
+ "py3-cpp_multij-json_http-domain",
"py3-cpp_multij-json_http-ip",
"py3-cpp_multij-json_http-ip-ssl",
+ "py3-cpp_multij_http-domain",
"py3-cpp_multij_http-ip",
"py3-cpp_multij_http-ip-ssl",
"py3-d_accel-binary_http-ip",
@@ -601,6 +702,12 @@
"py3-lua_binary_http-ip",
"py3-lua_compact_http-ip",
"py3-lua_json_http-ip",
+ "py3-nodejs_accel-binary_http-domain",
+ "py3-nodejs_accelc-compact_http-domain",
+ "py3-nodejs_binary_http-domain",
+ "py3-nodejs_compact_http-domain",
+ "py3-nodejs_json_http-domain",
+ "py3-nodejs_header_http-domain",
"py3-rs_multi_buffered-ip",
"py3-rs_multi_framed-ip",
"py3-rs_multia-multi_buffered-ip",
@@ -615,4 +722,4 @@
"rb-cpp_json_framed-domain",
"rb-cpp_json_framed-ip",
"rb-cpp_json_framed-ip-ssl"
-]
\ No newline at end of file
+]
diff --git a/test/netstd/Client/Client.csproj b/test/netstd/Client/Client.csproj
index c406f17..4ed57cb 100644
--- a/test/netstd/Client/Client.csproj
+++ b/test/netstd/Client/Client.csproj
@@ -19,7 +19,7 @@
-->
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Client</AssemblyName>
<PackageId>Client</PackageId>
<OutputType>Exe</OutputType>
@@ -31,9 +31,9 @@
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.5.2" />
+ <PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.7.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="[4.3,)" />
- <PackageReference Include="System.ServiceModel.Primitives" Version="4.5.3" />
+ <PackageReference Include="System.ServiceModel.Primitives" Version="4.7.0" />
<PackageReference Include="System.Threading" Version="[4.3,)" />
</ItemGroup>
<ItemGroup>
diff --git a/test/netstd/Client/Program.cs b/test/netstd/Client/Program.cs
index 47dabd3..92000da 100644
--- a/test/netstd/Client/Program.cs
+++ b/test/netstd/Client/Program.cs
@@ -42,6 +42,7 @@
Console.WriteLine("The 'client' argument is no longer required.");
PrintHelp();
return -1;
+ case "--performance":
case "--performance-test":
return Tests.PerformanceTests.Execute();
case "--help":
diff --git a/test/netstd/README.md b/test/netstd/README.md
index 8350728..4ece059 100644
--- a/test/netstd/README.md
+++ b/test/netstd/README.md
@@ -6,7 +6,7 @@
- ThriftTest - tests for Thrift library
# Reused components
-- NET Core SDK 3.0
+- NET Core SDK 3.1 (LTS)
# How to build on Windows
- Get Thrift IDL compiler executable, add to some folder and add path to this folder into PATH variable
diff --git a/test/netstd/Server/Server.csproj b/test/netstd/Server/Server.csproj
index 5d33aa0..fa5ce46 100644
--- a/test/netstd/Server/Server.csproj
+++ b/test/netstd/Server/Server.csproj
@@ -19,7 +19,7 @@
-->
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Server</AssemblyName>
<PackageId>Server</PackageId>
<OutputType>Exe</OutputType>
@@ -33,9 +33,9 @@
<ItemGroup>
<PackageReference Include="System.IO.Pipes" Version="4.3.0" />
<PackageReference Include="System.IO.Pipes.AccessControl" Version="4.5.1" />
- <PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.5.2" />
+ <PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.7.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="[4.3,)" />
- <PackageReference Include="System.ServiceModel.Primitives" Version="4.5.3" />
+ <PackageReference Include="System.ServiceModel.Primitives" Version="4.7.0" />
<PackageReference Include="System.Threading" Version="[4.3,)" />
</ItemGroup>
<ItemGroup>
@@ -47,6 +47,6 @@
</Exec>
<Exec Condition="Exists('$(PathToThrift)')" Command=""$(PathToThrift)" -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
<Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
- <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,pascal -r ./../../ThriftTest.thrift" />
+ <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
</Target>
</Project>
diff --git a/test/netstd/Server/TestServer.cs b/test/netstd/Server/TestServer.cs
index 493d89d..68461dc 100644
--- a/test/netstd/Server/TestServer.cs
+++ b/test/netstd/Server/TestServer.cs
@@ -254,20 +254,20 @@
public Task<Xtruct> testStructAsync(Xtruct thing, CancellationToken cancellationToken)
{
- logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.StringThing, thing.ByteThing, thing.I32Thing, thing.I64Thing);
+ logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.String_thing, thing.Byte_thing, thing.I32_thing, thing.I64_thing);
return Task.FromResult(thing);
}
public Task<Xtruct2> testNestAsync(Xtruct2 nest, CancellationToken cancellationToken)
{
- var thing = nest.StructThing;
+ var thing = nest.Struct_thing;
logger.Invoke("testNest({{{0}, {{\"{1}\", {2}, {3}, {4}, {5}}}}})",
- nest.ByteThing,
- thing.StringThing,
- thing.ByteThing,
- thing.I32Thing,
- thing.I64Thing,
- nest.I32Thing);
+ nest.Byte_thing,
+ thing.String_thing,
+ thing.Byte_thing,
+ thing.I32_thing,
+ thing.I64_thing,
+ nest.I32_thing);
return Task.FromResult(nest);
}
@@ -429,10 +429,10 @@
logger.Invoke("testMulti()");
var hello = new Xtruct(); ;
- hello.StringThing = "Hello2";
- hello.ByteThing = arg0;
- hello.I32Thing = arg1;
- hello.I64Thing = arg2;
+ hello.String_thing = "Hello2";
+ hello.Byte_thing = arg0;
+ hello.I32_thing = arg1;
+ hello.I64_thing = arg2;
return Task.FromResult(hello);
}
@@ -473,12 +473,12 @@
var x = new Xception2
{
ErrorCode = 2002,
- StructThing = new Xtruct { StringThing = "This is an Xception2" }
+ Struct_thing = new Xtruct { String_thing = "This is an Xception2" }
};
throw x;
}
- var result = new Xtruct { StringThing = arg1 };
+ var result = new Xtruct { String_thing = arg1 };
return Task.FromResult(result);
}
diff --git a/test/netstd/ThriftTest.sln b/test/netstd/ThriftTest.sln
index 6bd0855..352576e 100644
--- a/test/netstd/ThriftTest.sln
+++ b/test/netstd/ThriftTest.sln
@@ -4,9 +4,9 @@
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thrift", "..\..\lib\netstd\Thrift\Thrift.csproj", "{C20EA2A9-7660-47DE-9A49-D1EF12FB2895}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{21039F25-6ED7-4E80-A545-EBC93472EBD1}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{21039F25-6ED7-4E80-A545-EBC93472EBD1}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{0C6E8685-F191-4479-9842-882A38961127}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{0C6E8685-F191-4479-9842-882A38961127}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/test/py.tornado/test_suite.py b/test/py.tornado/test_suite.py
index 447fde6..0ee0a9b 100755
--- a/test/py.tornado/test_suite.py
+++ b/test/py.tornado/test_suite.py
@@ -82,10 +82,7 @@
def testException(self, s):
if s == 'Xception':
- x = Xception()
- x.errorCode = 1001
- x.message = s
- raise x
+ raise Xception(1001, s)
elif s == 'throw_undeclared':
raise ValueError('testing undeclared exception')
diff --git a/test/py.twisted/test_suite.py b/test/py.twisted/test_suite.py
index 02eb7f1..6e04493 100755
--- a/test/py.twisted/test_suite.py
+++ b/test/py.twisted/test_suite.py
@@ -76,10 +76,7 @@
def testException(self, s):
if s == 'Xception':
- x = Xception()
- x.errorCode = 1001
- x.message = s
- raise x
+ raise Xception(1001, s)
elif s == "throw_undeclared":
raise ValueError("foo")
diff --git a/test/py/TestClient.py b/test/py/TestClient.py
index e7a9a1a..8a30c3a 100755
--- a/test/py/TestClient.py
+++ b/test/py/TestClient.py
@@ -51,7 +51,7 @@
from thrift.transport import TSSLSocket
socket = TSSLSocket.TSSLSocket(options.host, options.port, validate=False)
else:
- socket = TSocket.TSocket(options.host, options.port)
+ socket = TSocket.TSocket(options.host, options.port, options.domain_socket)
# frame or buffer depending upon args
self.transport = TTransport.TBufferedTransport(socket)
if options.trans == 'framed':
@@ -474,6 +474,8 @@
help="protocol to use, one of: accel, accelc, binary, compact, header, json, multi, multia, multiac, multic, multih, multij")
parser.add_option('--transport', dest="trans", type="string",
help="transport to use, one of: buffered, framed, http")
+ parser.add_option('--domain-socket', dest="domain_socket", type="string",
+ help="Unix domain socket path")
parser.set_defaults(framed=False, http_path=None, verbose=1, host='localhost', port=9090, proto='binary')
options, args = parser.parse_args()
diff --git a/test/py/TestFrozen.py b/test/py/TestFrozen.py
index 6d2595c..ce7425f 100755
--- a/test/py/TestFrozen.py
+++ b/test/py/TestFrozen.py
@@ -19,7 +19,9 @@
# under the License.
#
+from DebugProtoTest import Srv
from DebugProtoTest.ttypes import CompactProtoTestStruct, Empty, Wrapper
+from DebugProtoTest.ttypes import ExceptionWithAMap, MutableException
from thrift.Thrift import TFrozenDict
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol, TCompactProtocol
@@ -94,6 +96,21 @@
x2 = self._roundtrip(x, Wrapper)
self.assertEqual(x2.foo, Empty())
+ def test_frozen_exception(self):
+ exc = ExceptionWithAMap(blah='foo')
+ with self.assertRaises(TypeError):
+ exc.blah = 'bar'
+ mutexc = MutableException(msg='foo')
+ mutexc.msg = 'bar'
+ self.assertEqual(mutexc.msg, 'bar')
+
+ def test_frozen_exception_serialization(self):
+ result = Srv.declaredExceptionMethod_result(
+ xwamap=ExceptionWithAMap(blah="error"))
+ deserialized = self._roundtrip(
+ result, Srv.declaredExceptionMethod_result())
+ self.assertEqual(result, deserialized)
+
class TestFrozen(TestFrozenBase):
def protocol(self, trans):
diff --git a/test/py/TestServer.py b/test/py/TestServer.py
index d0a13e5..4d90f8f 100755
--- a/test/py/TestServer.py
+++ b/test/py/TestServer.py
@@ -307,7 +307,7 @@
from thrift.transport import TSSLSocket
transport = TSSLSocket.TSSLServerSocket(host, options.port, certfile=abs_key_path)
else:
- transport = TSocket.TServerSocket(host, options.port)
+ transport = TSocket.TServerSocket(host, options.port, options.domain_socket)
tfactory = TTransport.TBufferedTransportFactory()
if options.trans == 'buffered':
tfactory = TTransport.TBufferedTransportFactory()
@@ -385,6 +385,8 @@
help="protocol to use, one of: accel, accelc, binary, compact, json, multi, multia, multiac, multic, multih, multij")
parser.add_option('--transport', dest="trans", type="string",
help="transport to use, one of: buffered, framed, http")
+ parser.add_option('--domain-socket', dest="domain_socket", type="string",
+ help="Unix domain socket path")
parser.add_option('--container-limit', dest='container_limit', type='int', default=None)
parser.add_option('--string-limit', dest='string_limit', type='int', default=None)
parser.set_defaults(port=9090, verbose=1, proto='binary', transport='buffered')
diff --git a/test/tests.json b/test/tests.json
index 78d4c0e..b8b85be 100644
--- a/test/tests.json
+++ b/test/tests.json
@@ -264,7 +264,8 @@
],
"sockets": [
"ip",
- "ip-ssl"
+ "ip-ssl",
+ "domain"
],
"protocols": [
"binary",
@@ -313,7 +314,8 @@
],
"sockets": [
"ip",
- "ip-ssl"
+ "ip-ssl",
+ "domain"
],
"protocols": [
"binary",
diff --git a/tutorial/netstd/Client/Client.csproj b/tutorial/netstd/Client/Client.csproj
index 2abf53c..10d5040 100644
--- a/tutorial/netstd/Client/Client.csproj
+++ b/tutorial/netstd/Client/Client.csproj
@@ -19,7 +19,7 @@
-->
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Client</AssemblyName>
<PackageId>Client</PackageId>
<OutputType>Exe</OutputType>
@@ -30,7 +30,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
</ItemGroup>
<ItemGroup>
diff --git a/tutorial/netstd/Interfaces/Interfaces.csproj b/tutorial/netstd/Interfaces/Interfaces.csproj
index 4ebeb4f..c8b2bd8 100644
--- a/tutorial/netstd/Interfaces/Interfaces.csproj
+++ b/tutorial/netstd/Interfaces/Interfaces.csproj
@@ -33,7 +33,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="System.ServiceModel.Primitives" Version="4.5.3" />
+ <PackageReference Include="System.ServiceModel.Primitives" Version="4.7.0" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="_GenerateRestoreProjectSpec;Restore;Compile">
diff --git a/tutorial/netstd/README.md b/tutorial/netstd/README.md
index 11fd541..297f4ee 100644
--- a/tutorial/netstd/README.md
+++ b/tutorial/netstd/README.md
@@ -1,8 +1,7 @@
# Building of samples for different platforms
-# Reused components
-- NET Core Standard 3.0
-- NET Core App 3.0
+# Requirements
+- NET Core Standard 3.1 (LTS) runtime or SDK (see below for further info)
# How to build
- Download and install the latest .NET Core SDK for your platform https://dotnet.microsoft.com/download/dotnet-core
@@ -14,29 +13,26 @@
# How to run
-Notes: dotnet run supports passing arguments to app after -- symbols (https://docs.microsoft.com/en-us/dotnet/articles/core/tools/dotnet-run) - example: **dotnet run -- -h** will show help for app
+Depending on the platform, the name of the generated executables will vary. On Linux, it is just "client" or "server", on Windows it is "Client.exe" and "Server.exe". In the following, we use the abbreviated form "Client" and "Server".
- build
- go to folder (Client/Server)
-- run with specifying of correct parameters **dotnet run -tr:tcp -pr:multiplexed**, **dotnet run -help** (later, after migration to csproj and latest SDK will be possibility to use more usable form **dotnet run -- arguments**)
-
-#Notes
-- Possible adding additional platforms after stabilization of .NET Core (runtimes, platforms (Red Hat Linux, OpenSuse, etc.)
+- run the generated executables: server first, then client from a second console
#Known issues
- In trace logging mode you can see some not important internal exceptions
# Running of samples
-Please install Thrift C# .NET Core library or copy sources and build them to correcly build and run samples
+On machines that do not have the SDK installed, you need to install the NET Core runtime first. The SDK is only needed to build programs, otherwise the runtime is sufficient.
# NetCore Server
Usage:
- Server.exe -h
+ Server -h
will diplay help information
- Server.exe -tr:<transport> -pr:<protocol>
+ Server -tr:<transport> -pr:<protocol>
will run server with specified arguments (tcp transport and binary protocol by default)
Options:
@@ -59,7 +55,7 @@
Sample:
- Server.exe -tr:tcp
+ Server -tr:tcp
**Remarks**:
@@ -72,10 +68,10 @@
Usage:
- Client.exe -h
+ Client -h
will diplay help information
- Client.exe -tr:<transport> -pr:<protocol> -mc:<numClients>
+ Client -tr:<transport> -pr:<protocol> -mc:<numClients>
will run client with specified arguments (tcp transport and binary protocol by default)
Options:
@@ -101,7 +97,7 @@
Sample:
- Client.exe -tr:tcp -pr:binary -mc:10
+ Client -tr:tcp -pr:binary -mc:10
Remarks:
@@ -111,8 +107,8 @@
# How to test communication between NetCore and Python
-* Generate code with the latest **thrift.exe** util
-* Ensure that **thrift.exe** util generated folder **gen-py** with generated code for Python
+* Generate code with the latest **thrift** utility
+* Ensure that **thrift** generated folder **gen-py** with generated code for Python exists
* Create **client.py** and **server.py** from the code examples below and save them to the folder with previosly generated folder **gen-py**
* Run netstd samples (client and server) and python samples (client and server)
diff --git a/tutorial/netstd/Server/Server.csproj b/tutorial/netstd/Server/Server.csproj
index 454f332..b3ff516 100644
--- a/tutorial/netstd/Server/Server.csproj
+++ b/tutorial/netstd/Server/Server.csproj
@@ -19,7 +19,7 @@
-->
<PropertyGroup>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Server</AssemblyName>
<PackageId>Server</PackageId>
<OutputType>Exe</OutputType>
@@ -38,7 +38,7 @@
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="2.2.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.2.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="3.1.0" />
</ItemGroup>
</Project>