diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/jaegertracing/thrift/lib/d | |
parent | Initial commit. (diff) | |
download | ceph-upstream.tar.xz ceph-upstream.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
171 files changed, 45941 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/lib/d/Makefile.am b/src/jaegertracing/thrift/lib/d/Makefile.am new file mode 100644 index 000000000..4787e0a60 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/Makefile.am @@ -0,0 +1,198 @@ +# +# 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. +# + +AUTOMAKE_OPTIONS = serial-tests + +SUBDIRS = . + +if WITH_TESTS +SUBDIRS += test +endif + +# +# Enumeration of all the public and private modules. +# +# We unconditionally install all of them, even if libevent or OpenSSL are +# not available, but build the respective libraries only if the Deimos headers +# could be found. +# +d_thriftmodules = $(addprefix thrift/, base) +d_thriftdir = $(D_IMPORT_PREFIX)/thrift +d_thrift_DATA = $(addprefix src/, $(addsuffix .d, $(d_thriftmodules))) + +d_asyncmodules = $(addprefix thrift/async/, base libevent socket ssl) +d_asyncdir = $(d_thriftdir)/async +d_async_DATA = $(addprefix src/, $(addsuffix .d, $(d_asyncmodules))) + +d_codegenmodules = $(addprefix thrift/codegen/, async_client \ + async_client_pool base client client_pool processor) +#d_codegenmodules = $(addprefix thrift/codegen/, async_client \ +# async_client_pool base client client_pool idlgen processor) + +d_codegendir = $(d_thriftdir)/codegen +d_codegen_DATA = $(addprefix src/, $(addsuffix .d, $(d_codegenmodules))) + +d_protocolmodules = $(addprefix thrift/protocol/, base binary compact json \ + processor) +d_protocoldir = $(d_thriftdir)/protocol +d_protocol_DATA = $(addprefix src/, $(addsuffix .d, $(d_protocolmodules))) + +d_servermodules = $(addprefix thrift/server/, base simple nonblocking \ + taskpool threaded) +d_serverdir = $(d_thriftdir)/server +d_server_DATA = $(addprefix src/, $(addsuffix .d, $(d_servermodules))) + +d_servertransportmodules = $(addprefix thrift/server/transport/, base socket ssl) +d_servertransportdir = $(d_thriftdir)/server/transport +d_servertransport_DATA = $(addprefix src/, $(addsuffix .d, \ + $(d_servertransportmodules))) + +d_transportmodules = $(addprefix thrift/transport/, base buffered file \ + framed http memory piped range socket ssl zlib) +d_transportdir = $(d_thriftdir)/transport +d_transport_DATA = $(addprefix src/, $(addsuffix .d, $(d_transportmodules))) + +d_utilmodules = $(addprefix thrift/util/, awaitable cancellation future \ + hashset) +d_utildir = $(d_thriftdir)/util +d_util_DATA = $(addprefix src/, $(addsuffix .d, $(d_utilmodules))) + +d_internalmodules = $(addprefix thrift/internal/, algorithm codegen ctfe \ + endian resource_pool socket ssl ssl_bio traits) +d_internaldir = $(d_thriftdir)/internal +d_internal_DATA = $(addprefix src/, $(addsuffix .d, $(d_internalmodules))) + +d_testmodules = $(addprefix thrift/internal/test/, protocol server) +d_testdir = $(d_internaldir)/test +d_test_DATA = $(addprefix src/, $(addsuffix .d, $(d_testmodules))) + +d_publicmodules = $(d_thriftmodules) $(d_asyncmodules) \ + $(d_codegenmodules) $(d_protocolmodules) $(d_servermodules) \ + $(d_servertransportmodules) $(d_transportmodules) $(d_utilmodules) +d_publicsources = $(addprefix src/, $(addsuffix .d, $(d_publicmodules))) + +d_modules = $(d_publicmodules) $(d_internalmodules) $(d_testmodules) + +# List modules with external dependencies and remove them from the main list +d_libevent_dependent_modules = thrift/async/libevent thrift/server/nonblocking +d_openssl_dependent_modules = thrift/async/ssl thrift/internal/ssl \ + thrift/internal/ssl_bio thrift/transport/ssl thrift/server/transport/ssl +d_main_modules = $(filter-out $(d_libevent_dependent_modules) \ + $(d_openssl_dependent_modules),$(d_modules)) + + +d_lib_flags = -w -wi -Isrc -lib +all_targets = + +# +# libevent-dependent modules. +# +if HAVE_DEIMOS_EVENT2 +$(D_EVENT_LIB_NAME): $(addprefix src/, $(addsuffix .d, $(d_libevent_dependent_modules))) + $(DMD) -of$(D_EVENT_LIB_NAME) $(d_lib_flags) $^ +all_targets += $(D_EVENT_LIB_NAME) +endif + +# +# OpenSSL-dependent modules. +# +if HAVE_DEIMOS_OPENSSL +$(D_SSL_LIB_NAME): $(addprefix src/, $(addsuffix .d, $(d_openssl_dependent_modules))) + $(DMD) -of$(D_SSL_LIB_NAME) $(d_lib_flags) $^ +all_targets += $(D_SSL_LIB_NAME) +endif + +# +# Main library target. +# +$(D_LIB_NAME): $(addprefix src/, $(addsuffix .d, $(d_main_modules))) + $(DMD) -of$(D_LIB_NAME) $(d_lib_flags) $^ +all_targets += $(D_LIB_NAME) + + +# +# Documentation target (requires Dil). +# +docs: $(d_publicsources) src/thrift/index.d + dil ddoc docs -hl --kandil $^ + + +# +# Hook custom library targets into the automake all/install targets. +# +all-local: $(all_targets) + +install-exec-local: + $(INSTALL_PROGRAM) $(all_targets) $(DESTDIR)$(libdir) + +clean-local: + $(RM) -r docs + $(RM) $(D_LIB_NAME) + $(RM) $(D_EVENT_LIB_NAME) + $(RM) $(D_SSL_LIB_NAME) + $(RM) -r test/gen-d + $(RM) -r unittest + + +# +# Unit tests (built both in debug and release mode). +# +d_test_flags = -unittest -w -wi -I$(top_srcdir)/lib/d/src + +# There just must be some way to reassign a variable without warnings in +# Automake... +d_test_modules__ = $(d_modules) + +if WITH_D_EVENT_TESTS +d_test_flags += $(DMD_LIBEVENT_FLAGS) +d_test_modules_ = $(d_test_modules__) +else +d_test_modules_ = $(filter-out $(d_libevent_dependent_modules), $(d_test_modules__)) +endif + +if WITH_D_SSL_TESTS +d_test_flags += $(DMD_OPENSSL_FLAGS) +d_test_modules = $(d_test_modules_) +else +d_test_modules = $(filter-out $(d_openssl_dependent_modules), $(d_test_modules_)) +endif + +unittest/emptymain.d: unittest/.directory + @echo 'void main(){}' >$@ + +unittest/.directory: + mkdir -p unittest || exists unittest + touch $@ + +unittest/debug/%: src/%.d $(all_targets) unittest/emptymain.d + $(DMD) -g -of$(subst /,$(DMD_OF_DIRSEP),$@) $(d_test_flags) $^ + +unittest/release/%: src/%.d $(all_targets) unittest/emptymain.d + $(DMD) -O -release -of$(subst /,$(DMD_OF_DIRSEP),$@) $(d_test_flags) $^ + +TESTS = $(addprefix unittest/debug/, $(d_test_modules)) \ + $(addprefix unittest/release/, $(d_test_modules)) + +precross: all-local + $(MAKE) -C test precross + +EXTRA_DIST = \ + src \ + test \ + README.md diff --git a/src/jaegertracing/thrift/lib/d/README.md b/src/jaegertracing/thrift/lib/d/README.md new file mode 100644 index 000000000..9b188abf5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/README.md @@ -0,0 +1,49 @@ +Thrift D Software Library +========================= + +License +------- + +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. + +Testing +------- + +D support in Thrift is covered by two sets of tests: first, +the unit test blocks contained in the D source files, and +second, the more extensive testing applications in the test/ +subdirectory, which also make use of the Thrift compiler. +Both are built when running "make check", but only the +unit tests are immediately run, however – the separate test +cases typically run longer or require manual intervention. +It might also be prudent to run the independent tests, +which typically consist of a server and a client part, +against the other language implementations. + +To build the unit tests on Windows, the easiest way might +be to manually create a file containing an empty main() and +invoke the compiler by running the following in the src/ +directory (PowerShell syntax): + +dmd -ofunittest -unittest -w $(dir -r -filter '*.d' -name) + +Async and SSL +------------- +Using SSL with async is experimental (always has been) and +the unit test "async_test --ssl" hangs. Use at your own +risk. diff --git a/src/jaegertracing/thrift/lib/d/coding_standards.md b/src/jaegertracing/thrift/lib/d/coding_standards.md new file mode 100644 index 000000000..fa0390bb5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/coding_standards.md @@ -0,0 +1 @@ +Please follow [General Coding Standards](/doc/coding_standards.md) diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/async/base.d b/src/jaegertracing/thrift/lib/d/src/thrift/async/base.d new file mode 100644 index 000000000..8debc3be0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/async/base.d @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Defines the interface used for client-side handling of asynchronous + * I/O operations, based on coroutines. + * + * The main piece of the »client side« (e.g. for TAsyncClient users) of the + * API is TFuture, which represents an asynchronously executed operation, + * which can have a return value, throw exceptions, and which can be waited + * upon. + * + * On the »implementation side«, the idea is that by using a TAsyncTransport + * instead of a normal TTransport and executing the work through a + * TAsyncManager, the same code as for synchronous I/O can be used for + * asynchronous operation as well, for example: + * + * --- + * auto socket = new TAsyncSocket(someTAsyncSocketManager(), host, port); + * // … + * socket.asyncManager.execute(socket, { + * SomeThriftStruct s; + * + * // Waiting for socket I/O will not block an entire thread but cause + * // the async manager to execute another task in the meantime, because + * // we are using TAsyncSocket instead of TSocket. + * s.read(socket); + * + * // Do something with s, e.g. set a TPromise result to it. + * writeln(s); + * }); + * --- + */ +module thrift.async.base; + +import core.time : Duration, dur; +import std.socket/+ : Socket+/; // DMD @@BUG314@@ +import thrift.base; +import thrift.transport.base; +import thrift.util.cancellation; + +/** + * Manages one or more asynchronous transport resources (e.g. sockets in the + * case of TAsyncSocketManager) and allows work items to be submitted for them. + * + * Implementations will typically run one or more background threads for + * executing the work, which is one of the reasons for a TAsyncManager to be + * used. Each work item is run in its own fiber and is expected to yield() away + * while waiting for time-consuming operations. + * + * The second important purpose of TAsyncManager is to serialize access to + * the transport resources – without taking care of that, e.g. issuing multiple + * RPC calls over the same connection in rapid succession would likely lead to + * more than one request being written at the same time, causing only garbage + * to arrive at the remote end. + * + * All methods are thread-safe. + */ +interface TAsyncManager { + /** + * Submits a work item to be executed asynchronously. + * + * Access to asnyc transports is serialized – if two work items associated + * with the same transport are submitted, the second delegate will not be + * invoked until the first has returned, even it the latter context-switches + * away (because it is waiting for I/O) and the async manager is idle + * otherwise. + * + * Optionally, a TCancellation instance can be specified. If present, + * triggering it will be considered a request to cancel the work item, if it + * is still waiting for the associated transport to become available. + * Delegates which are already being processed (i.e. waiting for I/O) are not + * affected because this would bring the connection into an undefined state + * (as probably half-written request or a half-read response would be left + * behind). + * + * Params: + * transport = The TAsyncTransport the work delegate will operate on. Must + * be associated with this TAsyncManager instance. + * work = The operations to execute on the given transport. Must never + * throw, errors should be handled in another way. nothrow semantics are + * difficult to enforce in combination with fibres though, so currently + * exceptions are just swallowed by TAsyncManager implementations. + * cancellation = If set, can be used to request cancellatinon of this work + * item if it is still waiting to be executed. + * + * Note: The work item will likely be executed in a different thread, so make + * sure the code it relies on is thread-safe. An exception are the async + * transports themselves, to which access is serialized as noted above. + */ + void execute(TAsyncTransport transport, void delegate() work, + TCancellation cancellation = null + ) in { + assert(transport.asyncManager is this, + "The given transport must be associated with this TAsyncManager."); + } + + /** + * Submits a delegate to be executed after a certain amount of time has + * passed. + * + * The actual amount of time elapsed can be higher if the async manager + * instance is busy and thus should not be relied on. The + * + * Params: + * duration = The amount of time to wait before starting to execute the + * work delegate. + * work = The code to execute after the specified amount of time has passed. + * + * Example: + * --- + * // A very basic example – usually, the actuall work item would enqueue + * // some async transport operation. + * auto asyncMangager = someAsyncManager(); + * + * TFuture!int calculate() { + * // Create a promise and asynchronously set its value after three + * // seconds have passed. + * auto promise = new TPromise!int; + * asyncManager.delay(dur!"seconds"(3), { + * promise.succeed(42); + * }); + * + * // Immediately return it to the caller. + * return promise; + * } + * + * // This will wait until the result is available and then print it. + * writeln(calculate().waitGet()); + * --- + */ + void delay(Duration duration, void delegate() work); + + /** + * Shuts down all background threads or other facilities that might have + * been started in order to execute work items. This function is typically + * called during program shutdown. + * + * If there are still tasks to be executed when the timeout expires, any + * currently executed work items will never receive any notifications + * for async transports managed by this instance, queued work items will + * be silently dropped, and implementations are allowed to leak resources. + * + * Params: + * waitFinishTimeout = If positive, waits for all work items to be + * finished for the specified amount of time, if negative, waits for + * completion without ever timing out, if zero, immediately shuts down + * the background facilities. + */ + bool stop(Duration waitFinishTimeout = dur!"hnsecs"(-1)); +} + +/** + * A TTransport which uses a TAsyncManager to schedule non-blocking operations. + * + * The actual type of device is not specified; typically, implementations will + * depend on an interface derived from TAsyncManager to be notified of changes + * in the transport state. + * + * The peeking, reading, writing and flushing methods must always be called + * from within the associated async manager. + */ +interface TAsyncTransport : TTransport { + /** + * The TAsyncManager associated with this transport. + */ + TAsyncManager asyncManager() @property; +} + +/** + * A TAsyncManager providing notificiations for socket events. + */ +interface TAsyncSocketManager : TAsyncManager { + /** + * Adds a listener that is triggered once when an event of the specified type + * occurs, and removed afterwards. + * + * Params: + * socket = The socket to listen for events at. + * eventType = The type of the event to listen for. + * timeout = The period of time after which the listener will be called + * with TAsyncEventReason.TIMED_OUT if no event happened. + * listener = The delegate to call when an event happened. + */ + void addOneshotListener(Socket socket, TAsyncEventType eventType, + Duration timeout, TSocketEventListener listener); + + /// Ditto + void addOneshotListener(Socket socket, TAsyncEventType eventType, + TSocketEventListener listener); +} + +/** + * Types of events that can happen for an asynchronous transport. + */ +enum TAsyncEventType { + READ, /// New data became available to read. + WRITE /// The transport became ready to be written to. +} + +/** + * The type of the delegates used to register socket event handlers. + */ +alias void delegate(TAsyncEventReason callReason) TSocketEventListener; + +/** + * The reason a listener was called. + */ +enum TAsyncEventReason : byte { + NORMAL, /// The event listened for was triggered normally. + TIMED_OUT /// A timeout for the event was set, and it expired. +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/async/libevent.d b/src/jaegertracing/thrift/lib/d/src/thrift/async/libevent.d new file mode 100644 index 000000000..812e4a765 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/async/libevent.d @@ -0,0 +1,461 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.async.libevent; + +import core.atomic; +import core.time : Duration, dur; +import core.exception : onOutOfMemoryError; +import core.memory : GC; +import core.thread : Fiber, Thread; +import core.sync.condition; +import core.sync.mutex; +import core.stdc.stdlib : free, malloc; +import deimos.event2.event; +import std.array : empty, front, popFront; +import std.conv : text, to; +import std.exception : enforce; +import std.socket : Socket, socketPair; +import thrift.base; +import thrift.async.base; +import thrift.internal.socket; +import thrift.internal.traits; +import thrift.util.cancellation; + +// To avoid DMD @@BUG6395@@. +import thrift.internal.algorithm; + +/** + * A TAsyncManager implementation based on libevent. + * + * The libevent loop for handling non-blocking sockets is run in a background + * thread, which is lazily spawned. The thread is not daemonized to avoid + * crashes on program shutdown, it is only stopped when the manager instance + * is destroyed. So, to ensure a clean program teardown, either make sure this + * instance gets destroyed (e.g. by using scope), or manually call stop() at + * the end. + */ +class TLibeventAsyncManager : TAsyncSocketManager { + this() { + eventBase_ = event_base_new(); + + // Set up the socket pair for transferring control messages to the event + // loop. + auto pair = socketPair(); + controlSendSocket_ = pair[0]; + controlReceiveSocket_ = pair[1]; + controlReceiveSocket_.blocking = false; + + // Register an event for receiving control messages. + controlReceiveEvent_ = event_new(eventBase_, controlReceiveSocket_.handle, + EV_READ | EV_PERSIST | EV_ET, assumeNothrow(&controlMsgReceiveCallback), + cast(void*)this); + event_add(controlReceiveEvent_, null); + + queuedCountMutex_ = new Mutex; + zeroQueuedCondition_ = new Condition(queuedCountMutex_); + } + + ~this() { + // stop() should be safe to call, because either we don't have a worker + // thread running and it is a no-op anyway, or it is guaranteed to be + // still running (blocked in event_base_loop), and thus guaranteed not to + // be garbage collected yet. + stop(dur!"hnsecs"(0)); + + event_free(controlReceiveEvent_); + event_base_free(eventBase_); + eventBase_ = null; + } + + override void execute(TAsyncTransport transport, Work work, + TCancellation cancellation = null + ) { + if (cancellation && cancellation.triggered) return; + + // Keep track that there is a new work item to be processed. + incrementQueuedCount(); + + ensureWorkerThreadRunning(); + + // We should be able to send the control message as a whole – we currently + // assume to be able to receive it at once as well. If this proves to be + // unstable (e.g. send could possibly return early if the receiving buffer + // is full and the blocking call gets interrupted by a signal), it could + // be changed to a more sophisticated scheme. + + // Make sure the delegate context doesn't get GCd while the work item is + // on the wire. + GC.addRoot(work.ptr); + + // Send work message. + sendControlMsg(ControlMsg(MsgType.WORK, work, transport)); + + if (cancellation) { + cancellation.triggering.addCallback({ + sendControlMsg(ControlMsg(MsgType.CANCEL, work, transport)); + }); + } + } + + override void delay(Duration duration, void delegate() work) { + incrementQueuedCount(); + + ensureWorkerThreadRunning(); + + const tv = toTimeval(duration); + + // DMD @@BUG@@: Cannot deduce T to void delegate() here. + registerOneshotEvent!(void delegate())( + -1, 0, assumeNothrow(&delayCallback), &tv, + { + work(); + decrementQueuedCount(); + } + ); + } + + override bool stop(Duration waitFinishTimeout = dur!"hnsecs"(-1)) { + bool cleanExit = true; + + synchronized (this) { + if (workerThread_) { + synchronized (queuedCountMutex_) { + if (waitFinishTimeout > dur!"hnsecs"(0)) { + if (queuedCount_ > 0) { + zeroQueuedCondition_.wait(waitFinishTimeout); + } + } else if (waitFinishTimeout < dur!"hnsecs"(0)) { + while (queuedCount_ > 0) zeroQueuedCondition_.wait(); + } else { + // waitFinishTimeout is zero, immediately exit in all cases. + } + cleanExit = (queuedCount_ == 0); + } + + event_base_loopbreak(eventBase_); + sendControlMsg(ControlMsg(MsgType.SHUTDOWN)); + workerThread_.join(); + workQueues_ = null; + // We have nuked all currently enqueued items, so set the count to + // zero. This is safe to do without locking, since the worker thread + // is down. + queuedCount_ = 0; + atomicStore(*(cast(shared)&workerThread_), cast(shared(Thread))null); + } + } + + return cleanExit; + } + + override void addOneshotListener(Socket socket, TAsyncEventType eventType, + TSocketEventListener listener + ) { + addOneshotListenerImpl(socket, eventType, null, listener); + } + + override void addOneshotListener(Socket socket, TAsyncEventType eventType, + Duration timeout, TSocketEventListener listener + ) { + if (timeout <= dur!"hnsecs"(0)) { + addOneshotListenerImpl(socket, eventType, null, listener); + } else { + // This is not really documented well, but libevent does not require to + // keep the timeval around after the event was added. + auto tv = toTimeval(timeout); + addOneshotListenerImpl(socket, eventType, &tv, listener); + } + } + +private: + alias void delegate() Work; + + void addOneshotListenerImpl(Socket socket, TAsyncEventType eventType, + const(timeval)* timeout, TSocketEventListener listener + ) { + registerOneshotEvent(socket.handle, libeventEventType(eventType), + assumeNothrow(&socketCallback), timeout, listener); + } + + void registerOneshotEvent(T)(evutil_socket_t fd, short type, + event_callback_fn callback, const(timeval)* timeout, T payload + ) { + // Create a copy of the payload on the C heap. + auto payloadMem = malloc(payload.sizeof); + if (!payloadMem) onOutOfMemoryError(); + (cast(T*)payloadMem)[0 .. 1] = payload; + GC.addRange(payloadMem, payload.sizeof); + + auto result = event_base_once(eventBase_, fd, type, callback, + payloadMem, timeout); + + // Assuming that we didn't get our arguments wrong above, the only other + // situation in which event_base_once can fail is when it can't allocate + // memory. + if (result != 0) onOutOfMemoryError(); + } + + enum MsgType : ubyte { + SHUTDOWN, + WORK, + CANCEL + } + + struct ControlMsg { + MsgType type; + Work work; + TAsyncTransport transport; + } + + /** + * Starts the worker thread if it is not already running. + */ + void ensureWorkerThreadRunning() { + // Technically, only half barriers would be required here, but adding the + // argument seems to trigger a DMD template argument deduction @@BUG@@. + if (!atomicLoad(*(cast(shared)&workerThread_))) { + synchronized (this) { + if (!workerThread_) { + auto thread = new Thread({ event_base_loop(eventBase_, 0); }); + thread.start(); + atomicStore(*(cast(shared)&workerThread_), cast(shared)thread); + } + } + } + } + + /** + * Sends a control message to the worker thread. + */ + void sendControlMsg(const(ControlMsg) msg) { + auto result = controlSendSocket_.send((&msg)[0 .. 1]); + enum size = msg.sizeof; + enforce(result == size, new TException(text( + "Sending control message of type ", msg.type, " failed (", result, + " bytes instead of ", size, " transmitted)."))); + } + + /** + * Receives messages from the control message socket and acts on them. Called + * from the worker thread. + */ + void receiveControlMsg() { + // Read as many new work items off the socket as possible (at least one + // should be available, as we got notified by libevent). + ControlMsg msg; + ptrdiff_t bytesRead; + while (true) { + bytesRead = controlReceiveSocket_.receive(cast(ubyte[])((&msg)[0 .. 1])); + + if (bytesRead < 0) { + auto errno = getSocketErrno(); + if (errno != WOULD_BLOCK_ERRNO) { + logError("Reading control message, some work item will possibly " ~ + "never be executed: %s", socketErrnoString(errno)); + } + } + if (bytesRead != msg.sizeof) break; + + // Everything went fine, we received a new control message. + final switch (msg.type) { + case MsgType.SHUTDOWN: + // The message was just intended to wake us up for shutdown. + break; + + case MsgType.CANCEL: + // When processing a cancellation, we must not touch the first item, + // since it is already being processed. + auto queue = workQueues_[msg.transport]; + if (queue.length > 0) { + workQueues_[msg.transport] = [queue[0]] ~ + removeEqual(queue[1 .. $], msg.work); + } + break; + + case MsgType.WORK: + // Now that the work item is back in the D world, we don't need the + // extra GC root for the context pointer anymore (see execute()). + GC.removeRoot(msg.work.ptr); + + // Add the work item to the queue and execute it. + auto queue = msg.transport in workQueues_; + if (queue is null || (*queue).empty) { + // If the queue is empty, add the new work item to the queue as well, + // but immediately start executing it. + workQueues_[msg.transport] = [msg.work]; + executeWork(msg.transport, msg.work); + } else { + (*queue) ~= msg.work; + } + break; + } + } + + // If the last read was successful, but didn't read enough bytes, we got + // a problem. + if (bytesRead > 0) { + logError("Unexpected partial control message read (%s byte(s) " ~ + "instead of %s), some work item will possibly never be executed.", + bytesRead, msg.sizeof); + } + } + + /** + * Executes the given work item and all others enqueued for the same + * transport in a new fiber. Called from the worker thread. + */ + void executeWork(TAsyncTransport transport, Work work) { + (new Fiber({ + auto item = work; + while (true) { + try { + // Execute the actual work. It will possibly add listeners to the + // event loop and yield away if it has to wait for blocking + // operations. It is quite possible that another fiber will modify + // the work queue for the current transport. + item(); + } catch (Exception e) { + // This should never happen, just to be sure the worker thread + // doesn't stop working in mysterious ways because of an unhandled + // exception. + logError("Exception thrown by work item: %s", e); + } + + // Remove the item from the work queue. + // Note: Due to the value semantics of array slices, we have to + // re-lookup this on every iteration. This could be solved, but I'd + // rather replace this directly with a queue type once one becomes + // available in Phobos. + auto queue = workQueues_[transport]; + assert(queue.front == item); + queue.popFront(); + workQueues_[transport] = queue; + + // Now that the work item is done, no longer count it as queued. + decrementQueuedCount(); + + if (queue.empty) break; + + // If the queue is not empty, execute the next waiting item. + item = queue.front; + } + })).call(); + } + + /** + * Increments the amount of queued items. + */ + void incrementQueuedCount() { + synchronized (queuedCountMutex_) { + ++queuedCount_; + } + } + + /** + * Decrements the amount of queued items. + */ + void decrementQueuedCount() { + synchronized (queuedCountMutex_) { + assert(queuedCount_ > 0); + --queuedCount_; + if (queuedCount_ == 0) { + zeroQueuedCondition_.notifyAll(); + } + } + } + + static extern(C) void controlMsgReceiveCallback(evutil_socket_t, short, + void *managerThis + ) { + (cast(TLibeventAsyncManager)managerThis).receiveControlMsg(); + } + + static extern(C) void socketCallback(evutil_socket_t, short flags, + void *arg + ) { + auto reason = (flags & EV_TIMEOUT) ? TAsyncEventReason.TIMED_OUT : + TAsyncEventReason.NORMAL; + (*(cast(TSocketEventListener*)arg))(reason); + GC.removeRange(arg); + destroy(arg); + free(arg); + } + + static extern(C) void delayCallback(evutil_socket_t, short flags, + void *arg + ) { + assert(flags & EV_TIMEOUT); + (*(cast(void delegate()*)arg))(); + GC.removeRange(arg); + destroy(arg); + free(arg); + } + + Thread workerThread_; + + event_base* eventBase_; + + /// The socket used for receiving new work items in the event loop. Paired + /// with controlSendSocket_. Invalid (i.e. TAsyncWorkItem.init) items are + /// ignored and can be used to wake up the worker thread. + Socket controlReceiveSocket_; + event* controlReceiveEvent_; + + /// The socket used to send new work items to the event loop. It is + /// expected that work items can always be read at once from it, i.e. that + /// there will never be short reads. + Socket controlSendSocket_; + + /// Queued up work delegates for async transports. This also includes + /// currently active ones, they are removed from the queue on completion, + /// which is relied on by the control message receive fiber (the main one) + /// to decide whether to immediately start executing items or not. + // TODO: This should really be of some queue type, not an array slice, but + // std.container doesn't have anything. + Work[][TAsyncTransport] workQueues_; + + /// The total number of work items not yet finished (queued and currently + /// executed) and delays not yet executed. + uint queuedCount_; + + /// Protects queuedCount_. + Mutex queuedCountMutex_; + + /// Triggered when queuedCount_ reaches zero, protected by queuedCountMutex_. + Condition zeroQueuedCondition_; +} + +private { + timeval toTimeval(const(Duration) dur) { + timeval tv; + dur.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec); + return tv; + } + + /** + * Returns the libevent flags combination to represent a given TAsyncEventType. + */ + short libeventEventType(TAsyncEventType type) { + final switch (type) { + case TAsyncEventType.READ: + return EV_READ | EV_ET; + case TAsyncEventType.WRITE: + return EV_WRITE | EV_ET; + } + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/async/socket.d b/src/jaegertracing/thrift/lib/d/src/thrift/async/socket.d new file mode 100644 index 000000000..a08f51db0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/async/socket.d @@ -0,0 +1,358 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.async.socket; + +import core.stdc.errno: ECONNRESET; +import core.thread : Fiber; +import core.time : dur, Duration; +import std.array : empty; +import std.conv : to; +import std.exception : enforce; +import std.socket; +import thrift.base; +import thrift.async.base; +import thrift.transport.base; +import thrift.transport.socket : TSocketBase; +import thrift.internal.endian; +import thrift.internal.socket; + +version (Windows) { + import std.c.windows.winsock : connect; +} else version (Posix) { + import core.sys.posix.sys.socket : connect; +} else static assert(0, "Don't know connect on this platform."); + +/** + * Non-blocking socket implementation of the TTransport interface. + * + * Whenever a socket operation would block, TAsyncSocket registers a callback + * with the specified TAsyncSocketManager and yields. + * + * As for thrift.transport.socket, due to the limitations of std.socket, + * currently only TCP/IP sockets are supported (i.e. Unix domain sockets are + * not). + */ +class TAsyncSocket : TSocketBase, TAsyncTransport { + /** + * Constructor that takes an already created, connected (!) socket. + * + * Params: + * asyncManager = The TAsyncSocketManager to use for non-blocking I/O. + * socket = Already created, connected socket object. Will be switched to + * non-blocking mode if it isn't already. + */ + this(TAsyncSocketManager asyncManager, Socket socket) { + asyncManager_ = asyncManager; + socket.blocking = false; + super(socket); + } + + /** + * Creates a new unconnected socket that will connect to the given host + * on the given port. + * + * Params: + * asyncManager = The TAsyncSocketManager to use for non-blocking I/O. + * host = Remote host. + * port = Remote port. + */ + this(TAsyncSocketManager asyncManager, string host, ushort port) { + asyncManager_ = asyncManager; + super(host, port); + } + + override TAsyncManager asyncManager() @property { + return asyncManager_; + } + + /** + * Asynchronously connects the socket. + * + * Completes without blocking and defers further operations on the socket + * until the connection is established. If connecting fails, this is + * currently not indicated in any way other than every call to read/write + * failing. + */ + override void open() { + if (isOpen) return; + + enforce(!host_.empty, new TTransportException( + "Cannot open null host.", TTransportException.Type.NOT_OPEN)); + enforce(port_ != 0, new TTransportException( + "Cannot open with null port.", TTransportException.Type.NOT_OPEN)); + + + // Cannot use std.socket.Socket.connect here because it hides away + // EINPROGRESS/WSAWOULDBLOCK. + Address addr; + try { + // Currently, we just go with the first address returned, could be made + // more intelligent though – IPv6? + addr = getAddress(host_, port_)[0]; + } catch (Exception e) { + throw new TTransportException(`Unable to resolve host "` ~ host_ ~ `".`, + TTransportException.Type.NOT_OPEN, __FILE__, __LINE__, e); + } + + socket_ = new TcpSocket(addr.addressFamily); + socket_.blocking = false; + setSocketOpts(); + + auto errorCode = connect(socket_.handle, addr.name(), addr.nameLen()); + if (errorCode == 0) { + // If the connection could be established immediately, just return. I + // don't know if this ever happens. + return; + } + + auto errno = getSocketErrno(); + if (errno != CONNECT_INPROGRESS_ERRNO) { + throw new TTransportException(`Could not establish connection to "` ~ + host_ ~ `": ` ~ socketErrnoString(errno), + TTransportException.Type.NOT_OPEN); + } + + // This is the expected case: connect() signalled that the connection + // is being established in the background. Queue up a work item with the + // async manager which just defers any other operations on this + // TAsyncSocket instance until the socket is ready. + asyncManager_.execute(this, + { + auto fiber = Fiber.getThis(); + TAsyncEventReason reason = void; + asyncManager_.addOneshotListener(socket_, TAsyncEventType.WRITE, + connectTimeout, + scopedDelegate((TAsyncEventReason r){ reason = r; fiber.call(); }) + ); + Fiber.yield(); + + if (reason == TAsyncEventReason.TIMED_OUT) { + // Close the connection, so that subsequent work items fail immediately. + closeImmediately(); + return; + } + + int errorCode = void; + socket_.getOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_ERROR, + errorCode); + + if (errorCode) { + logInfo("Could not connect TAsyncSocket: %s", + socketErrnoString(errorCode)); + + // Close the connection, so that subsequent work items fail immediately. + closeImmediately(); + return; + } + + } + ); + } + + /** + * Closes the socket. + * + * Will block until all currently active operations are finished before the + * socket is closed. + */ + override void close() { + if (!isOpen) return; + + import core.sync.condition; + import core.sync.mutex; + + auto doneMutex = new Mutex; + auto doneCond = new Condition(doneMutex); + synchronized (doneMutex) { + asyncManager_.execute(this, + scopedDelegate( + { + closeImmediately(); + synchronized (doneMutex) doneCond.notifyAll(); + } + ) + ); + doneCond.wait(); + } + } + + override bool peek() { + if (!isOpen) return false; + + ubyte buf; + auto r = socket_.receive((&buf)[0..1], SocketFlags.PEEK); + if (r == Socket.ERROR) { + auto lastErrno = getSocketErrno(); + static if (connresetOnPeerShutdown) { + if (lastErrno == ECONNRESET) { + closeImmediately(); + return false; + } + } + throw new TTransportException("Peeking into socket failed: " ~ + socketErrnoString(lastErrno), TTransportException.Type.UNKNOWN); + } + return (r > 0); + } + + override size_t read(ubyte[] buf) { + enforce(isOpen, new TTransportException( + "Cannot read if socket is not open.", TTransportException.Type.NOT_OPEN)); + + typeof(getSocketErrno()) lastErrno; + + auto r = yieldOnBlock(socket_.receive(cast(void[])buf), + TAsyncEventType.READ); + + // If recv went fine, immediately return. + if (r >= 0) return r; + + // Something went wrong, find out how to handle it. + lastErrno = getSocketErrno(); + + static if (connresetOnPeerShutdown) { + // See top comment. + if (lastErrno == ECONNRESET) { + return 0; + } + } + + throw new TTransportException("Receiving from socket failed: " ~ + socketErrnoString(lastErrno), TTransportException.Type.UNKNOWN); + } + + override void write(in ubyte[] buf) { + size_t sent; + while (sent < buf.length) { + sent += writeSome(buf[sent .. $]); + } + assert(sent == buf.length); + } + + override size_t writeSome(in ubyte[] buf) { + enforce(isOpen, new TTransportException( + "Cannot write if socket is not open.", TTransportException.Type.NOT_OPEN)); + + auto r = yieldOnBlock(socket_.send(buf), TAsyncEventType.WRITE); + + // Everything went well, just return the number of bytes written. + if (r > 0) return r; + + // Handle error conditions. + if (r < 0) { + auto lastErrno = getSocketErrno(); + + auto type = TTransportException.Type.UNKNOWN; + if (isSocketCloseErrno(lastErrno)) { + type = TTransportException.Type.NOT_OPEN; + closeImmediately(); + } + + throw new TTransportException("Sending to socket failed: " ~ + socketErrnoString(lastErrno), type); + } + + // send() should never return 0. + throw new TTransportException("Sending to socket failed (0 bytes written).", + TTransportException.Type.UNKNOWN); + } + + /// The amount of time in which a conncetion must be established before the + /// open() call times out. + Duration connectTimeout = dur!"seconds"(5); + +private: + void closeImmediately() { + socket_.close(); + socket_ = null; + } + + T yieldOnBlock(T)(lazy T call, TAsyncEventType eventType) { + while (true) { + auto result = call(); + if (result != Socket.ERROR || getSocketErrno() != WOULD_BLOCK_ERRNO) return result; + + // We got an EAGAIN result, register a callback to return here once some + // event happens and yield. + + Duration timeout = void; + final switch (eventType) { + case TAsyncEventType.READ: + timeout = recvTimeout_; + break; + case TAsyncEventType.WRITE: + timeout = sendTimeout_; + break; + } + + auto fiber = Fiber.getThis(); + assert(fiber, "Current fiber null – not running in TAsyncManager?"); + TAsyncEventReason eventReason = void; + asyncManager_.addOneshotListener(socket_, eventType, timeout, + scopedDelegate((TAsyncEventReason reason) { + eventReason = reason; + fiber.call(); + }) + ); + + // Yields execution back to the async manager, will return back here once + // the above listener is called. + Fiber.yield(); + + if (eventReason == TAsyncEventReason.TIMED_OUT) { + // If we are cancelling the request due to a timed out operation, the + // connection is in an undefined state, because the server could decide + // to send the requested data later, or we could have already been half- + // way into writing a request. Thus, we close the connection to make any + // possibly queued up work items fail immediately. Besides, the server + // is not very likely to immediately recover after a socket-level + // timeout has expired anyway. + closeImmediately(); + + throw new TTransportException("Timed out while waiting for socket " ~ + "to get ready to " ~ to!string(eventType) ~ ".", + TTransportException.Type.TIMED_OUT); + } + } + } + + /// The TAsyncSocketManager to use for non-blocking I/O. + TAsyncSocketManager asyncManager_; +} + +private { + // std.socket doesn't include SO_ERROR for reasons unknown. + version (linux) { + enum SO_ERROR = 4; + } else version (OSX) { + enum SO_ERROR = 0x1007; + } else version (FreeBSD) { + enum SO_ERROR = 0x1007; + } else version (Win32) { + import std.c.windows.winsock : SO_ERROR; + } else static assert(false, "Don't know SO_ERROR on this platform."); + + // This hack forces a delegate literal to be scoped, even if it is passed to + // a function accepting normal delegates as well. DMD likes to allocate the + // context on the heap anyway, but it seems to work for LDC. + import std.traits : isDelegate; + auto scopedDelegate(D)(scope D d) if (isDelegate!D) { + return d; + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/async/ssl.d b/src/jaegertracing/thrift/lib/d/src/thrift/async/ssl.d new file mode 100644 index 000000000..fe6242613 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/async/ssl.d @@ -0,0 +1,292 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.async.ssl; + +import core.thread : Fiber; +import core.time : Duration; +import std.array : empty; +import std.conv : to; +import std.exception : enforce; +import std.socket; +import deimos.openssl.err; +import deimos.openssl.ssl; +import thrift.base; +import thrift.async.base; +import thrift.async.socket; +import thrift.internal.ssl; +import thrift.internal.ssl_bio; +import thrift.transport.base; +import thrift.transport.ssl; + +/** + * Provides SSL/TLS encryption for async sockets. + * + * This implementation should be considered experimental, as it context-switches + * between fibers from within OpenSSL calls, and the safety of this has not yet + * been verified. + * + * For obvious reasons (the SSL connection is stateful), more than one instance + * should never be used on a given socket at the same time. + */ +// Note: This could easily be extended to other transports in the future as well. +// There are only two parts of the implementation which don't work with a generic +// TTransport: 1) the certificate verification, for which peer name/address are +// needed from the socket, and 2) the connection shutdown, where the associated +// async manager is needed because close() is not usually called from within a +// work item. +final class TAsyncSSLSocket : TBaseTransport { + /** + * Constructor. + * + * Params: + * context = The SSL socket context to use. A reference to it is stored so + * that it does not get cleaned up while the socket is used. + * transport = The underlying async network transport to use for + * communication. + */ + this(TAsyncSocket underlyingSocket, TSSLContext context) { + socket_ = underlyingSocket; + context_ = context; + serverSide_ = context.serverSide; + accessManager_ = context.accessManager; + } + + override bool isOpen() @property { + if (ssl_ is null || !socket_.isOpen) return false; + + auto shutdown = SSL_get_shutdown(ssl_); + bool shutdownReceived = (shutdown & SSL_RECEIVED_SHUTDOWN) != 0; + bool shutdownSent = (shutdown & SSL_SENT_SHUTDOWN) != 0; + return !(shutdownReceived && shutdownSent); + } + + override bool peek() { + if (!isOpen) return false; + checkHandshake(); + + byte bt = void; + auto rc = SSL_peek(ssl_, &bt, bt.sizeof); + sslEnforce(rc >= 0, "SSL_peek"); + + if (rc == 0) { + ERR_clear_error(); + } + return (rc > 0); + } + + override void open() { + enforce(!serverSide_, "Cannot open a server-side SSL socket."); + if (isOpen) return; + + if (ssl_) { + // If the underlying socket was automatically closed because of an error + // (i.e. close() was called from inside a socket method), we can land + // here with the SSL object still allocated; delete it here. + cleanupSSL(); + } + + socket_.open(); + } + + override void close() { + if (!isOpen) return; + + if (ssl_ !is null) { + // SSL needs to send/receive data over the socket as part of the shutdown + // protocol, so we must execute the calls in the context of the associated + // async manager. On the other hand, TTransport clients expect the socket + // to be closed when close() returns, so we have to block until the + // shutdown work item has been executed. + import core.sync.condition; + import core.sync.mutex; + + int rc = void; + auto doneMutex = new Mutex; + auto doneCond = new Condition(doneMutex); + synchronized (doneMutex) { + socket_.asyncManager.execute(socket_, { + rc = SSL_shutdown(ssl_); + if (rc == 0) { + rc = SSL_shutdown(ssl_); + } + synchronized (doneMutex) doneCond.notifyAll(); + }); + doneCond.wait(); + } + + if (rc < 0) { + // Do not throw an exception here as leaving the transport "open" will + // probably produce only more errors, and the chance we can do + // something about the error e.g. by retrying is very low. + logError("Error while shutting down SSL: %s", getSSLException()); + } + + cleanupSSL(); + } + + socket_.close(); + } + + override size_t read(ubyte[] buf) { + checkHandshake(); + auto rc = SSL_read(ssl_, buf.ptr, cast(int)buf.length); + sslEnforce(rc >= 0, "SSL_read"); + return rc; + } + + override void write(in ubyte[] buf) { + checkHandshake(); + + // Loop in case SSL_MODE_ENABLE_PARTIAL_WRITE is set in SSL_CTX. + size_t written = 0; + while (written < buf.length) { + auto bytes = SSL_write(ssl_, buf.ptr + written, + cast(int)(buf.length - written)); + sslEnforce(bytes > 0, "SSL_write"); + written += bytes; + } + } + + override void flush() { + checkHandshake(); + + auto bio = SSL_get_wbio(ssl_); + enforce(bio !is null, new TSSLException("SSL_get_wbio returned null")); + + auto rc = BIO_flush(bio); + sslEnforce(rc == 1, "BIO_flush"); + } + + /** + * Whether to use client or server side SSL handshake protocol. + */ + bool serverSide() @property const { + return serverSide_; + } + + /// Ditto + void serverSide(bool value) @property { + serverSide_ = value; + } + + /** + * The access manager to use. + */ + void accessManager(TAccessManager value) @property { + accessManager_ = value; + } + +private: + /** + * If the condition is false, cleans up the SSL connection and throws the + * exception for the last SSL error. + */ + void sslEnforce(bool condition, string location) { + if (!condition) { + // We need to fetch the error first, as the error stack will be cleaned + // when shutting down SSL. + auto e = getSSLException(location); + cleanupSSL(); + throw e; + } + } + + /** + * Frees the SSL connection object and clears the SSL error state. + */ + void cleanupSSL() { + SSL_free(ssl_); + ssl_ = null; + ERR_remove_state(0); + } + + /** + * Makes sure the SSL connection is up and running, and initializes it if not. + */ + void checkHandshake() { + enforce(socket_.isOpen, new TTransportException( + TTransportException.Type.NOT_OPEN)); + + if (ssl_ !is null) return; + ssl_ = context_.createSSL(); + + auto bio = createTTransportBIO(socket_, false); + SSL_set_bio(ssl_, bio, bio); + + int rc = void; + if (serverSide_) { + rc = SSL_accept(ssl_); + } else { + rc = SSL_connect(ssl_); + } + enforce(rc > 0, getSSLException()); + + auto addr = socket_.getPeerAddress(); + authorize(ssl_, accessManager_, addr, + (serverSide_ ? addr.toHostNameString() : socket_.host)); + } + + TAsyncSocket socket_; + bool serverSide_; + SSL* ssl_; + TSSLContext context_; + TAccessManager accessManager_; +} + +/** + * Wraps passed TAsyncSocket instances into TAsyncSSLSockets. + * + * Typically used with TAsyncClient. As an unfortunate consequence of the + * async client design, the passed transports cannot be statically verified to + * be of type TAsyncSocket. Instead, the type is verified at runtime – if a + * transport of an unexpected type is passed to getTransport(), it fails, + * throwing a TTransportException. + * + * Example: + * --- + * auto context = nwe TSSLContext(); + * ... // Configure SSL context. + * auto factory = new TAsyncSSLSocketFactory(context); + * + * auto socket = new TAsyncSocket(someAsyncManager, host, port); + * socket.open(); + * + * auto client = new TAsyncClient!Service(transport, factory, + * new TBinaryProtocolFactory!()); + * --- + */ +class TAsyncSSLSocketFactory : TTransportFactory { + /// + this(TSSLContext context) { + context_ = context; + } + + override TAsyncSSLSocket getTransport(TTransport transport) { + auto socket = cast(TAsyncSocket)transport; + enforce(socket, new TTransportException( + "TAsyncSSLSocketFactory requires a TAsyncSocket to work on, not a " ~ + to!string(typeid(transport)) ~ ".", + TTransportException.Type.INTERNAL_ERROR + )); + return new TAsyncSSLSocket(socket, context_); + } + +private: + TSSLContext context_; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/base.d b/src/jaegertracing/thrift/lib/d/src/thrift/base.d new file mode 100644 index 000000000..150c3da8e --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/base.d @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.base; + +/** + * Common base class for all Thrift exceptions. + */ +class TException : Exception { + /// + this(string msg = "", string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + super(msg, file, line, next); + } +} + +/** + * An operation failed because one or more sub-tasks failed. + */ +class TCompoundOperationException : TException { + /// + this(string msg, Exception[] exceptions, string file = __FILE__, + size_t line = __LINE__, Throwable next = null) + { + super(msg, file, line, next); + this.exceptions = exceptions; + } + + /// The exceptions thrown by the children of the operation. If applicable, + /// the list is ordered in the same way the exceptions occurred. + Exception[] exceptions; +} + +/// The Thrift version string, used for informative purposes. +// Note: This is currently hardcoded, but will likely be filled in by the build +// system in future versions. +enum VERSION = "0.13.0"; + +/** + * Functions used for logging inside Thrift. + * + * By default, the formatted messages are written to stdout/stderr, but this + * behavior can be overwritten by providing custom g_{Info, Error}LogSink + * handlers. + * + * Examples: + * --- + * logInfo("An informative message."); + * logError("Some error occurred: %s", e); + * --- + */ +alias logFormatted!g_infoLogSink logInfo; +alias logFormatted!g_errorLogSink logError; /// Ditto + +/** + * Error and info log message sinks. + * + * These delegates are called with the log message passed as const(char)[] + * argument, and can be overwritten to hook the Thrift libraries up with a + * custom logging system. By default, they forward all output to stdout/stderr. + */ +__gshared void delegate(const(char)[]) g_infoLogSink; +__gshared void delegate(const(char)[]) g_errorLogSink; /// Ditto + +shared static this() { + import std.stdio; + + g_infoLogSink = (const(char)[] text) { + stdout.writeln(text); + }; + + g_errorLogSink = (const(char)[] text) { + stderr.writeln(text); + }; +} + +// This should be private, if it could still be used through the aliases then. +template logFormatted(alias target) { + void logFormatted(string file = __FILE__, int line = __LINE__, + T...)(string fmt, T args) if ( + __traits(compiles, { target(""); }) + ) { + import std.format, std.stdio; + if (target !is null) { + scope(exit) g_formatBuffer.clear(); + + // Phobos @@BUG@@: If the empty string put() is removed, Appender.data + // stays empty. + g_formatBuffer.put(""); + + formattedWrite(g_formatBuffer, "%s:%s: ", file, line); + + static if (T.length == 0) { + g_formatBuffer.put(fmt); + } else { + formattedWrite(g_formatBuffer, fmt, args); + } + target(g_formatBuffer.data); + } + } +} + +private { + // Use a global, but thread-local buffer for constructing log messages. + import std.array : Appender; + Appender!(char[]) g_formatBuffer; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/codegen/async_client.d b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/async_client.d new file mode 100644 index 000000000..e916dea15 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/async_client.d @@ -0,0 +1,255 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.codegen.async_client; + +import std.conv : text, to; +import std.traits : ParameterStorageClass, ParameterStorageClassTuple, + ParameterTypeTuple, ReturnType; +import thrift.base; +import thrift.async.base; +import thrift.codegen.base; +import thrift.codegen.client; +import thrift.internal.codegen; +import thrift.internal.ctfe; +import thrift.protocol.base; +import thrift.transport.base; +import thrift.util.cancellation; +import thrift.util.future; + +/** + * Asynchronous Thrift service client which returns the results as TFutures an + * uses a TAsyncManager to perform the actual work. + * + * TAsyncClientBase serves as a supertype for all TAsyncClients for the same + * service, which might be instantiated with different concrete protocol types + * (there is no covariance for template type parameters), and extends + * TFutureInterface!Interface. If Interface is derived from another service + * BaseInterface, it also extends TAsyncClientBase!BaseInterface. + * + * TAsyncClient implements TAsyncClientBase and offers two constructors with + * the following signatures: + * --- + * this(TAsyncTransport trans, TTransportFactory tf, TProtocolFactory pf); + * this(TAsyncTransport trans, TTransportFactory itf, TTransportFactory otf, + * TProtocolFactory ipf, TProtocolFactory opf); + * --- + * + * Again, if Interface represents a derived Thrift service, + * TAsyncClient!Interface is also derived from TAsyncClient!BaseInterface. + * + * TAsyncClient can exclusively be used with TAsyncTransports, as it needs to + * access the associated TAsyncManager. To set up any wrapper transports + * (e.g. buffered, framed) on top of it and to instanciate the protocols to use, + * TTransportFactory and TProtocolFactory instances are passed to the + * constructors – the three argument constructor is a shortcut if the same + * transport and protocol are to be used for both input and output, which is + * the most common case. + * + * If the same transport factory is passed for both input and output transports, + * only a single wrapper transport will be created and used for both directions. + * This allows easy implementation of protocols like SSL. + * + * Just as TClient does, TAsyncClient also takes two optional template + * arguments which can be used for specifying the actual TProtocol + * implementation used for optimization purposes, as virtual calls can + * completely be eliminated then. If the actual types of the protocols + * instantiated by the factories used does not match the ones statically + * specified in the template parameters, a TException is thrown during + * construction. + * + * Example: + * --- + * // A simple Thrift service. + * interface Foo { int foo(); } + * + * // Create a TAsyncSocketManager – thrift.async.libevent is used for this + * // example. + * auto manager = new TLibeventAsyncManager; + * + * // Set up an async transport to use. + * auto socket = new TAsyncSocket(manager, host, port); + * + * // Create a client instance. + * auto client = new TAsyncClient!Foo( + * socket, + * new TBufferedTransportFactory, // Wrap the socket in a TBufferedTransport. + * new TBinaryProtocolFactory!() // Use the Binary protocol. + * ); + * + * // Call foo and use the returned future. + * auto result = client.foo(); + * pragma(msg, typeof(result)); // TFuture!int + * int resultValue = result.waitGet(); // Waits until the result is available. + * --- + */ +interface TAsyncClientBase(Interface) if (isBaseService!Interface) : + TFutureInterface!Interface +{ + /** + * The underlying TAsyncTransport used by this client instance. + */ + TAsyncTransport transport() @property; +} + +/// Ditto +interface TAsyncClientBase(Interface) if (isDerivedService!Interface) : + TAsyncClientBase!(BaseService!Interface), TFutureInterface!Interface +{} + +/// Ditto +template TAsyncClient(Interface, InputProtocol = TProtocol, OutputProtocol = void) if ( + isService!Interface && isTProtocol!InputProtocol && + (isTProtocol!OutputProtocol || is(OutputProtocol == void)) +) { + mixin({ + static if (isDerivedService!Interface) { + string code = "class TAsyncClient : " ~ + "TAsyncClient!(BaseService!Interface, InputProtocol, OutputProtocol), " ~ + "TAsyncClientBase!Interface {\n"; + code ~= q{ + this(TAsyncTransport trans, TTransportFactory tf, TProtocolFactory pf) { + this(trans, tf, tf, pf, pf); + } + + this(TAsyncTransport trans, TTransportFactory itf, + TTransportFactory otf, TProtocolFactory ipf, TProtocolFactory opf + ) { + super(trans, itf, otf, ipf, opf); + client_ = new typeof(client_)(iprot_, oprot_); + } + + private TClient!(Interface, IProt, OProt) client_; + }; + } else { + string code = "class TAsyncClient : TAsyncClientBase!Interface {"; + code ~= q{ + alias InputProtocol IProt; + static if (isTProtocol!OutputProtocol) { + alias OutputProtocol OProt; + } else { + static assert(is(OutputProtocol == void)); + alias InputProtocol OProt; + } + + this(TAsyncTransport trans, TTransportFactory tf, TProtocolFactory pf) { + this(trans, tf, tf, pf, pf); + } + + this(TAsyncTransport trans, TTransportFactory itf, + TTransportFactory otf, TProtocolFactory ipf, TProtocolFactory opf + ) { + import std.exception; + transport_ = trans; + + auto ip = itf.getTransport(trans); + TTransport op = void; + if (itf == otf) { + op = ip; + } else { + op = otf.getTransport(trans); + } + + auto iprot = ipf.getProtocol(ip); + iprot_ = cast(IProt)iprot; + enforce(iprot_, new TException(text("Input protocol not of the " ~ + "specified concrete type (", IProt.stringof, ")."))); + + auto oprot = opf.getProtocol(op); + oprot_ = cast(OProt)oprot; + enforce(oprot_, new TException(text("Output protocol not of the " ~ + "specified concrete type (", OProt.stringof, ")."))); + + client_ = new typeof(client_)(iprot_, oprot_); + } + + override TAsyncTransport transport() @property { + return transport_; + } + + protected TAsyncTransport transport_; + protected IProt iprot_; + protected OProt oprot_; + private TClient!(Interface, IProt, OProt) client_; + }; + } + + foreach (methodName; + FilterMethodNames!(Interface, __traits(derivedMembers, Interface)) + ) { + string[] paramList; + string[] paramNames; + foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) { + immutable paramName = "param" ~ to!string(i + 1); + immutable storage = ParameterStorageClassTuple!( + mixin("Interface." ~ methodName))[i]; + + paramList ~= ((storage & ParameterStorageClass.ref_) ? "ref " : "") ~ + "ParameterTypeTuple!(Interface." ~ methodName ~ ")[" ~ + to!string(i) ~ "] " ~ paramName; + paramNames ~= paramName; + } + paramList ~= "TCancellation cancellation = null"; + + immutable returnTypeCode = "ReturnType!(Interface." ~ methodName ~ ")"; + code ~= "TFuture!(" ~ returnTypeCode ~ ") " ~ methodName ~ "(" ~ + ctfeJoin(paramList) ~ ") {\n"; + + // Create the future instance that will repesent the result. + code ~= "auto promise = new TPromise!(" ~ returnTypeCode ~ ");\n"; + + // Prepare delegate which executes the TClient method call. + code ~= "auto work = {\n"; + code ~= "try {\n"; + code ~= "static if (is(ReturnType!(Interface." ~ methodName ~ + ") == void)) {\n"; + code ~= "client_." ~ methodName ~ "(" ~ ctfeJoin(paramNames) ~ ");\n"; + code ~= "promise.succeed();\n"; + code ~= "} else {\n"; + code ~= "auto result = client_." ~ methodName ~ "(" ~ + ctfeJoin(paramNames) ~ ");\n"; + code ~= "promise.succeed(result);\n"; + code ~= "}\n"; + code ~= "} catch (Exception e) {\n"; + code ~= "promise.fail(e);\n"; + code ~= "}\n"; + code ~= "};\n"; + + // If the request is cancelled, set the result promise to cancelled + // as well. This could be moved into an additional TAsyncWorkItem + // delegate parameter. + code ~= q{ + if (cancellation) { + cancellation.triggering.addCallback({ + promise.cancel(); + }); + } + }; + + // Enqueue the work item and immediately return the promise (resp. its + // future interface). + code ~= "transport_.asyncManager.execute(transport_, work, cancellation);\n"; + code ~= "return promise;\n"; + code ~= "}\n"; + + } + + code ~= "}\n"; + return code; + }()); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/codegen/async_client_pool.d b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/async_client_pool.d new file mode 100644 index 000000000..26cb975a3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/async_client_pool.d @@ -0,0 +1,906 @@ +/* + * 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. + */ + +/** + * Utilities for asynchronously querying multiple servers, building on + * TAsyncClient. + * + * Terminology note: The names of the artifacts defined in this module are + * derived from »client pool«, because they operate on a pool of + * TAsyncClients. However, from a architectural point of view, they often + * represent a pool of hosts a Thrift client application communicates with + * using RPC calls. + */ +module thrift.codegen.async_client_pool; + +import core.sync.mutex; +import core.time : Duration, dur; +import std.algorithm : map; +import std.array : array, empty; +import std.exception : enforce; +import std.traits : ParameterTypeTuple, ReturnType; +import thrift.base; +import thrift.codegen.base; +import thrift.codegen.async_client; +import thrift.internal.algorithm; +import thrift.internal.codegen; +import thrift.util.awaitable; +import thrift.util.cancellation; +import thrift.util.future; +import thrift.internal.resource_pool; + +/** + * Represents a generic client pool which implements TFutureInterface!Interface + * using multiple TAsyncClients. + */ +interface TAsyncClientPoolBase(Interface) if (isService!Interface) : + TFutureInterface!Interface +{ + /// Shorthand for the client type this pool operates on. + alias TAsyncClientBase!Interface Client; + + /** + * Adds a client to the pool. + */ + void addClient(Client client); + + /** + * Removes a client from the pool. + * + * Returns: Whether the client was found in the pool. + */ + bool removeClient(Client client); + + /** + * Called to determine whether an exception comes from a client from the + * pool not working properly, or if it an exception thrown at the + * application level. + * + * If the delegate returns true, the server/connection is considered to be + * at fault, if it returns false, the exception is just passed on to the + * caller. + * + * By default, returns true for instances of TTransportException and + * TApplicationException, false otherwise. + */ + bool delegate(Exception) rpcFaultFilter() const @property; + void rpcFaultFilter(bool delegate(Exception)) @property; /// Ditto + + /** + * Whether to open the underlying transports of a client before trying to + * execute a method if they are not open. This is usually desirable + * because it allows e.g. to automatically reconnect to a remote server + * if the network connection is dropped. + * + * Defaults to true. + */ + bool reopenTransports() const @property; + void reopenTransports(bool) @property; /// Ditto +} + +immutable bool delegate(Exception) defaultRpcFaultFilter; +static this() { + defaultRpcFaultFilter = (Exception e) { + import thrift.protocol.base; + import thrift.transport.base; + return ( + (cast(TTransportException)e !is null) || + (cast(TApplicationException)e !is null) + ); + }; +} + +/** + * A TAsyncClientPoolBase implementation which queries multiple servers in a + * row until a request succeeds, the result of which is then returned. + * + * The definition of »success« can be customized using the rpcFaultFilter() + * delegate property. If it is non-null and calling it for an exception set by + * a failed method invocation returns true, the error is considered to be + * caused by the RPC layer rather than the application layer, and the next + * server in the pool is tried. If there are no more clients to try, the + * operation is marked as failed with a TCompoundOperationException. + * + * If a TAsyncClient in the pool fails with an RPC exception for a number of + * consecutive tries, it is temporarily disabled (not tried any longer) for + * a certain duration. Both the limit and the timeout can be configured. If all + * clients fail (and keepTrying is false), the operation fails with a + * TCompoundOperationException which contains the collected RPC exceptions. + */ +final class TAsyncClientPool(Interface) if (isService!Interface) : + TAsyncClientPoolBase!Interface +{ + /// + this(Client[] clients) { + pool_ = new TResourcePool!Client(clients); + rpcFaultFilter_ = defaultRpcFaultFilter; + reopenTransports_ = true; + } + + /+override+/ void addClient(Client client) { + pool_.add(client); + } + + /+override+/ bool removeClient(Client client) { + return pool_.remove(client); + } + + /** + * Whether to keep trying to find a working client if all have failed in a + * row. + * + * Defaults to false. + */ + bool keepTrying() const @property { + return pool_.cycle; + } + + /// Ditto + void keepTrying(bool value) @property { + pool_.cycle = value; + } + + /** + * Whether to use a random permutation of the client pool on every call to + * execute(). This can be used e.g. as a simple form of load balancing. + * + * Defaults to true. + */ + bool permuteClients() const @property { + return pool_.permute; + } + + /// Ditto + void permuteClients(bool value) @property { + pool_.permute = value; + } + + /** + * The number of consecutive faults after which a client is disabled until + * faultDisableDuration has passed. 0 to never disable clients. + * + * Defaults to 0. + */ + ushort faultDisableCount() const @property { + return pool_.faultDisableCount; + } + + /// Ditto + void faultDisableCount(ushort value) @property { + pool_.faultDisableCount = value; + } + + /** + * The duration for which a client is no longer considered after it has + * failed too often. + * + * Defaults to one second. + */ + Duration faultDisableDuration() const @property { + return pool_.faultDisableDuration; + } + + /// Ditto + void faultDisableDuration(Duration value) @property { + pool_.faultDisableDuration = value; + } + + /+override+/ bool delegate(Exception) rpcFaultFilter() const @property { + return rpcFaultFilter_; + } + + /+override+/ void rpcFaultFilter(bool delegate(Exception) value) @property { + rpcFaultFilter_ = value; + } + + /+override+/ bool reopenTransports() const @property { + return reopenTransports_; + } + + /+override+/ void reopenTransports(bool value) @property { + reopenTransports_ = value; + } + + mixin(fallbackPoolForwardCode!Interface()); + +protected: + // The actual worker implementation to which RPC method calls are forwarded. + auto executeOnPool(string method, Args...)(Args args, + TCancellation cancellation + ) { + auto clients = pool_[]; + if (clients.empty) { + throw new TException("No clients available to try."); + } + + auto promise = new TPromise!(ReturnType!(MemberType!(Interface, method))); + Exception[] rpcExceptions; + + void tryNext() { + while (clients.empty) { + Client next; + Duration waitTime; + if (clients.willBecomeNonempty(next, waitTime)) { + if (waitTime > dur!"hnsecs"(0)) { + if (waitTime < dur!"usecs"(10)) { + import core.thread; + Thread.sleep(waitTime); + } else { + next.transport.asyncManager.delay(waitTime, { tryNext(); }); + return; + } + } + } else { + promise.fail(new TCompoundOperationException("All clients failed.", + rpcExceptions)); + return; + } + } + + auto client = clients.front; + clients.popFront; + + if (reopenTransports) { + if (!client.transport.isOpen) { + try { + client.transport.open(); + } catch (Exception e) { + pool_.recordFault(client); + tryNext(); + return; + } + } + } + + auto future = mixin("client." ~ method)(args, cancellation); + future.completion.addCallback({ + if (future.status == TFutureStatus.CANCELLED) { + promise.cancel(); + return; + } + + auto e = future.getException(); + if (e) { + if (rpcFaultFilter_ && rpcFaultFilter_(e)) { + pool_.recordFault(client); + rpcExceptions ~= e; + tryNext(); + return; + } + } + pool_.recordSuccess(client); + promise.complete(future); + }); + } + + tryNext(); + return promise; + } + +private: + TResourcePool!Client pool_; + bool delegate(Exception) rpcFaultFilter_; + bool reopenTransports_; +} + +/** + * TAsyncClientPool construction helper to avoid having to explicitly + * specify the interface type, i.e. to allow the constructor being called + * using IFTI (see $(DMDBUG 6082, D Bugzilla enhancement request 6082)). + */ +TAsyncClientPool!Interface tAsyncClientPool(Interface)( + TAsyncClientBase!Interface[] clients +) if (isService!Interface) { + return new typeof(return)(clients); +} + +private { + // Cannot use an anonymous delegate literal for this because they aren't + // allowed in class scope. + string fallbackPoolForwardCode(Interface)() { + string code = ""; + + foreach (methodName; AllMemberMethodNames!Interface) { + enum qn = "Interface." ~ methodName; + code ~= "TFuture!(ReturnType!(" ~ qn ~ ")) " ~ methodName ~ + "(ParameterTypeTuple!(" ~ qn ~ ") args, TCancellation cancellation = null) {\n"; + code ~= "return executeOnPool!(`" ~ methodName ~ "`)(args, cancellation);\n"; + code ~= "}\n"; + } + + return code; + } +} + +/** + * A TAsyncClientPoolBase implementation which queries multiple servers at + * the same time and returns the first success response. + * + * The definition of »success« can be customized using the rpcFaultFilter() + * delegate property. If it is non-null and calling it for an exception set by + * a failed method invocation returns true, the error is considered to be + * caused by the RPC layer rather than the application layer, and the next + * server in the pool is tried. If all clients fail, the operation is marked + * as failed with a TCompoundOperationException. + */ +final class TAsyncFastestClientPool(Interface) if (isService!Interface) : + TAsyncClientPoolBase!Interface +{ + /// + this(Client[] clients) { + clients_ = clients; + rpcFaultFilter_ = defaultRpcFaultFilter; + reopenTransports_ = true; + } + + /+override+/ void addClient(Client client) { + clients_ ~= client; + } + + /+override+/ bool removeClient(Client client) { + auto oldLength = clients_.length; + clients_ = removeEqual(clients_, client); + return clients_.length < oldLength; + } + + + /+override+/ bool delegate(Exception) rpcFaultFilter() const @property { + return rpcFaultFilter_; + } + + /+override+/ void rpcFaultFilter(bool delegate(Exception) value) @property { + rpcFaultFilter_ = value; + } + + /+override+/bool reopenTransports() const @property { + return reopenTransports_; + } + + /+override+/ void reopenTransports(bool value) @property { + reopenTransports_ = value; + } + + mixin(fastestPoolForwardCode!Interface()); + +private: + Client[] clients_; + bool delegate(Exception) rpcFaultFilter_; + bool reopenTransports_; +} + +/** + * TAsyncFastestClientPool construction helper to avoid having to explicitly + * specify the interface type, i.e. to allow the constructor being called + * using IFTI (see $(DMDBUG 6082, D Bugzilla enhancement request 6082)). + */ +TAsyncFastestClientPool!Interface tAsyncFastestClientPool(Interface)( + TAsyncClientBase!Interface[] clients +) if (isService!Interface) { + return new typeof(return)(clients); +} + +private { + // Cannot use an anonymous delegate literal for this because they aren't + // allowed in class scope. + string fastestPoolForwardCode(Interface)() { + string code = ""; + + foreach (methodName; AllMemberMethodNames!Interface) { + enum qn = "Interface." ~ methodName; + code ~= "TFuture!(ReturnType!(" ~ qn ~ ")) " ~ methodName ~ + "(ParameterTypeTuple!(" ~ qn ~ ") args, " ~ + "TCancellation cancellation = null) {\n"; + code ~= "enum methodName = `" ~ methodName ~ "`;\n"; + code ~= q{ + alias ReturnType!(MemberType!(Interface, methodName)) ResultType; + + auto childCancellation = new TCancellationOrigin; + + TFuture!ResultType[] futures; + futures.reserve(clients_.length); + + foreach (c; clients_) { + if (reopenTransports) { + if (!c.transport.isOpen) { + try { + c.transport.open(); + } catch (Exception e) { + continue; + } + } + } + futures ~= mixin("c." ~ methodName)(args, childCancellation); + } + + return new FastestPoolJob!(ResultType)( + futures, rpcFaultFilter, cancellation, childCancellation); + }; + code ~= "}\n"; + } + + return code; + } + + final class FastestPoolJob(Result) : TFuture!Result { + this(TFuture!Result[] poolFutures, bool delegate(Exception) rpcFaultFilter, + TCancellation cancellation, TCancellationOrigin childCancellation + ) { + resultPromise_ = new TPromise!Result; + poolFutures_ = poolFutures; + rpcFaultFilter_ = rpcFaultFilter; + childCancellation_ = childCancellation; + + foreach (future; poolFutures) { + future.completion.addCallback({ + auto f = future; + return { completionCallback(f); }; + }()); + if (future.status != TFutureStatus.RUNNING) { + // If the current future is already completed, we are done, don't + // bother adding callbacks for the others (they would just return + // immediately after acquiring the lock). + return; + } + } + + if (cancellation) { + cancellation.triggering.addCallback({ + resultPromise_.cancel(); + childCancellation.trigger(); + }); + } + } + + TFutureStatus status() const @property { + return resultPromise_.status; + } + + TAwaitable completion() @property { + return resultPromise_.completion; + } + + Result get() { + return resultPromise_.get(); + } + + Exception getException() { + return resultPromise_.getException(); + } + + private: + void completionCallback(TFuture!Result future) { + synchronized { + if (future.status == TFutureStatus.CANCELLED) { + assert(resultPromise_.status != TFutureStatus.RUNNING); + return; + } + + if (resultPromise_.status != TFutureStatus.RUNNING) { + // The operation has already been completed. This can happen if + // another client completed first, but this callback was already + // waiting for the lock when it called cancel(). + return; + } + + if (future.status == TFutureStatus.FAILED) { + auto e = future.getException(); + if (rpcFaultFilter_ && rpcFaultFilter_(e)) { + rpcExceptions_ ~= e; + + if (rpcExceptions_.length == poolFutures_.length) { + resultPromise_.fail(new TCompoundOperationException( + "All child operations failed, unable to retrieve a result.", + rpcExceptions_ + )); + } + + return; + } + } + + // Store the result to the target promise. + resultPromise_.complete(future); + + // Cancel the other futures, we would just discard their results. + // Note: We do this after we have stored the results to our promise, + // see the assert at the top of the function. + childCancellation_.trigger(); + } + } + + TPromise!Result resultPromise_; + TFuture!Result[] poolFutures_; + Exception[] rpcExceptions_; + bool delegate(Exception) rpcFaultFilter_; + TCancellationOrigin childCancellation_; + } +} + +/** + * Allows easily aggregating results from a number of TAsyncClients. + * + * Contrary to TAsync{Fallback, Fastest}ClientPool, this class does not + * simply implement TFutureInterface!Interface. It manages a pool of clients, + * but allows the user to specify a custom accumulator function to use or to + * iterate over the results using a TFutureAggregatorRange. + * + * For each service method, TAsyncAggregator offers a method + * accepting the same arguments, and an optional TCancellation instance, just + * like with TFutureInterface. The return type, however, is a proxy object + * that offers the following methods: + * --- + * /++ + * + Returns a thrift.util.future.TFutureAggregatorRange for the results of + * + the client pool method invocations. + * + + * + The [] (slicing) operator can also be used to obtain the range. + * + + * + Params: + * + timeout = A timeout to pass to the TFutureAggregatorRange constructor, + * + defaults to zero (no timeout). + * +/ + * TFutureAggregatorRange!ReturnType range(Duration timeout = dur!"hnsecs"(0)); + * auto opSlice() { return range(); } /// Ditto + * + * /++ + * + Returns a future that gathers the results from the clients in the pool + * + and invokes a user-supplied accumulator function on them, returning its + * + return value to the client. + * + + * + In addition to the TFuture!AccumulatedType interface (where + * + AccumulatedType is the return type of the accumulator function), the + * + returned object also offers two additional methods, finish() and + * + finishGet(): By default, the accumulator functions is called after all + * + the results from the pool clients have become available. Calling finish() + * + causes the accumulator future to stop waiting for other results and + * + immediately invoking the accumulator function on the results currently + * + available. If all results are already available, finish() is a no-op. + * + finishGet() is a convenience shortcut for combining it with + * + a call to get() immediately afterwards, like waitGet() is for wait(). + * + + * + The acc alias can point to any callable accepting either an array of + * + return values or an array of return values and an array of exceptions; + * + see isAccumulator!() for details. The default accumulator concatenates + * + return values that can be concatenated with each others (e.g. arrays), + * + and simply returns an array of values otherwise, failing with a + * + TCompoundOperationException no values were returned. + * + + * + The accumulator function is not executed in any of the async manager + * + worker threads associated with the async clients, but instead it is + * + invoked when the actual result is requested for the first time after the + * + operation has been completed. This also includes checking the status + * + of the operation once it is no longer running, since the accumulator + * + has to be run to determine whether the operation succeeded or failed. + * +/ + * auto accumulate(alias acc = defaultAccumulator)() if (isAccumulator!acc); + * --- + * + * Example: + * --- + * // Some Thrift service. + * interface Foo { + * int foo(string name); + * byte[] bar(); + * } + * + * // Create the aggregator pool – client0, client1, client2 are some + * // TAsyncClient!Foo instances, but in theory could also be other + * // TFutureInterface!Foo implementations (e.g. some async client pool). + * auto pool = new TAsyncAggregator!Foo([client0, client1, client2]); + * + * foreach (val; pool.foo("baz").range(dur!"seconds"(1))) { + * // Process all the results that are available before a second has passed, + * // in the order they arrive. + * writeln(val); + * } + * + * auto sumRoots = pool.foo("baz").accumulate!((int[] vals, Exceptions[] exs){ + * if (vals.empty) { + * throw new TCompoundOperationException("All clients failed", exs); + * } + * + * // Just to illustrate that the type of the values can change, convert the + * // numbers to double and sum up their roots. + * double result = 0; + * foreach (v; vals) result += sqrt(cast(double)v); + * return result; + * })(); + * + * // Wait up to three seconds for the result, and then accumulate what has + * // arrived so far. + * sumRoots.completion.wait(dur!"seconds"(3)); + * writeln(sumRoots.finishGet()); + * + * // For scalars, the default accumulator returns an array of the values. + * pragma(msg, typeof(pool.foo("").accumulate().get()); // int[]. + * + * // For lists, etc., it concatenates the results together. + * pragma(msg, typeof(pool.bar().accumulate().get())); // byte[]. + * --- + * + * Note: For the accumulate!() interface, you might currently hit a »cannot use + * local '…' as parameter to non-global template accumulate«-error, see + * $(DMDBUG 5710, DMD issue 5710). If your accumulator function does not need + * to access the surrounding scope, you might want to use a function literal + * instead of a delegate to avoid the issue. + */ +class TAsyncAggregator(Interface) if (isBaseService!Interface) { + /// Shorthand for the client type this instance operates on. + alias TAsyncClientBase!Interface Client; + + /// + this(Client[] clients) { + clients_ = clients; + } + + /// Whether to open the underlying transports of a client before trying to + /// execute a method if they are not open. This is usually desirable + /// because it allows e.g. to automatically reconnect to a remote server + /// if the network connection is dropped. + /// + /// Defaults to true. + bool reopenTransports = true; + + mixin AggregatorOpDispatch!(); + +private: + Client[] clients_; +} + +/// Ditto +class TAsyncAggregator(Interface) if (isDerivedService!Interface) : + TAsyncAggregator!(BaseService!Interface) +{ + /// Shorthand for the client type this instance operates on. + alias TAsyncClientBase!Interface Client; + + /// + this(Client[] clients) { + super(cast(TAsyncClientBase!(BaseService!Interface)[])clients); + } + + mixin AggregatorOpDispatch!(); +} + +/** + * Whether fun is a valid accumulator function for values of type ValueType. + * + * For this to be true, fun must be a callable matching one of the following + * argument lists: + * --- + * fun(ValueType[] values); + * fun(ValueType[] values, Exception[] exceptions); + * --- + * + * The second version is passed the collected array exceptions from all the + * clients in the pool. + * + * The return value of the accumulator function is passed to the client (via + * the result future). If it throws an exception, the operation is marked as + * failed with the given exception instead. + */ +template isAccumulator(ValueType, alias fun) { + enum isAccumulator = is(typeof(fun(cast(ValueType[])[]))) || + is(typeof(fun(cast(ValueType[])[], cast(Exception[])[]))); +} + +/** + * TAsyncAggregator construction helper to avoid having to explicitly + * specify the interface type, i.e. to allow the constructor being called + * using IFTI (see $(DMDBUG 6082, D Bugzilla enhancement request 6082)). + */ +TAsyncAggregator!Interface tAsyncAggregator(Interface)( + TAsyncClientBase!Interface[] clients +) if (isService!Interface) { + return new typeof(return)(clients); +} + +private { + mixin template AggregatorOpDispatch() { + auto opDispatch(string name, Args...)(Args args) if ( + is(typeof(mixin("Interface.init." ~ name)(args))) + ) { + alias ReturnType!(MemberType!(Interface, name)) ResultType; + + auto childCancellation = new TCancellationOrigin; + + TFuture!ResultType[] futures; + futures.reserve(clients_.length); + + foreach (c; cast(Client[])clients_) { + if (reopenTransports) { + if (!c.transport.isOpen) { + try { + c.transport.open(); + } catch (Exception e) { + continue; + } + } + } + futures ~= mixin("c." ~ name)(args, childCancellation); + } + + return AggregationResult!ResultType(futures, childCancellation); + } + } + + struct AggregationResult(T) { + auto opSlice() { + return range(); + } + + auto range(Duration timeout = dur!"hnsecs"(0)) { + return tFutureAggregatorRange(futures_, childCancellation_, timeout); + } + + auto accumulate(alias acc = defaultAccumulator)() if (isAccumulator!(T, acc)) { + return new AccumulatorJob!(T, acc)(futures_, childCancellation_); + } + + private: + TFuture!T[] futures_; + TCancellationOrigin childCancellation_; + } + + auto defaultAccumulator(T)(T[] values, Exception[] exceptions) { + if (values.empty) { + throw new TCompoundOperationException("All clients failed", + exceptions); + } + + static if (is(typeof(T.init ~ T.init))) { + import std.algorithm; + return reduce!"a ~ b"(values); + } else { + return values; + } + } + + final class AccumulatorJob(T, alias accumulator) if ( + isAccumulator!(T, accumulator) + ) : TFuture!(AccumulatorResult!(T, accumulator)) { + this(TFuture!T[] futures, TCancellationOrigin childCancellation) { + futures_ = futures; + childCancellation_ = childCancellation; + resultMutex_ = new Mutex; + completionEvent_ = new TOneshotEvent; + + foreach (future; futures) { + future.completion.addCallback({ + auto f = future; + return { + synchronized (resultMutex_) { + if (f.status == TFutureStatus.CANCELLED) { + if (!finished_) { + status_ = TFutureStatus.CANCELLED; + finished_ = true; + } + return; + } + + if (f.status == TFutureStatus.FAILED) { + exceptions_ ~= f.getException(); + } else { + results_ ~= f.get(); + } + + if (results_.length + exceptions_.length == futures_.length) { + finished_ = true; + completionEvent_.trigger(); + } + } + }; + }()); + } + } + + TFutureStatus status() @property { + synchronized (resultMutex_) { + if (!finished_) return TFutureStatus.RUNNING; + if (status_ != TFutureStatus.RUNNING) return status_; + + try { + result_ = invokeAccumulator!accumulator(results_, exceptions_); + status_ = TFutureStatus.SUCCEEDED; + } catch (Exception e) { + exception_ = e; + status_ = TFutureStatus.FAILED; + } + + return status_; + } + } + + TAwaitable completion() @property { + return completionEvent_; + } + + AccumulatorResult!(T, accumulator) get() { + auto s = status; + + enforce(s != TFutureStatus.RUNNING, + new TFutureException("Operation not yet completed.")); + + if (s == TFutureStatus.CANCELLED) throw new TCancelledException; + if (s == TFutureStatus.FAILED) throw exception_; + return result_; + } + + Exception getException() { + auto s = status; + enforce(s != TFutureStatus.RUNNING, + new TFutureException("Operation not yet completed.")); + + if (s == TFutureStatus.CANCELLED) throw new TCancelledException; + + if (s == TFutureStatus.SUCCEEDED) { + return null; + } + return exception_; + } + + void finish() { + synchronized (resultMutex_) { + if (!finished_) { + finished_ = true; + childCancellation_.trigger(); + completionEvent_.trigger(); + } + } + } + + auto finishGet() { + finish(); + return get(); + } + + private: + TFuture!T[] futures_; + TCancellationOrigin childCancellation_; + + bool finished_; + T[] results_; + Exception[] exceptions_; + + TFutureStatus status_; + Mutex resultMutex_; + union { + AccumulatorResult!(T, accumulator) result_; + Exception exception_; + } + TOneshotEvent completionEvent_; + } + + auto invokeAccumulator(alias accumulator, T)( + T[] values, Exception[] exceptions + ) if ( + isAccumulator!(T, accumulator) + ) { + static if (is(typeof(accumulator(values, exceptions)))) { + return accumulator(values, exceptions); + } else { + return accumulator(values); + } + } + + template AccumulatorResult(T, alias acc) { + alias typeof(invokeAccumulator!acc(cast(T[])[], cast(Exception[])[])) + AccumulatorResult; + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/codegen/base.d b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/base.d new file mode 100644 index 000000000..db549928c --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/base.d @@ -0,0 +1,1021 @@ +/* + * 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. + */ + +/** + * Code generation metadata and templates used for implementing struct + * serialization. + * + * Many templates can be customized using field meta data, which is read from + * a manifest constant member of the given type called fieldMeta (if present), + * and is concatenated with the elements from the optional fieldMetaData + * template alias parameter. + * + * Some code generation templates take account of the optional TVerboseCodegen + * version declaration, which causes warning messages to be emitted if no + * metadata for a field/method has been found and the default behavior is + * used instead. If this version is not defined, the templates just silently + * behave like the Thrift compiler does in this situation, i.e. automatically + * assign negative ids (starting at -1) for fields and assume TReq.AUTO as + * requirement level. + */ +// Implementation note: All the templates in here taking a field metadata +// parameter should ideally have a constraint that restricts the alias to +// TFieldMeta[]-typed values, but the is() expressions seems to always fail. +module thrift.codegen.base; + +import std.algorithm : find; +import std.array : empty, front; +import std.conv : to; +import std.exception : enforce; +import std.traits : BaseTypeTuple, isPointer, isSomeFunction, PointerTarget, + ReturnType; +import thrift.base; +import thrift.internal.codegen; +import thrift.protocol.base; +import thrift.util.hashset; + +/* + * Thrift struct/service meta data, which is used to store information from + * the interface definition files not representable in plain D, i.e. field + * requirement levels, Thrift field IDs, etc. + */ + +/** + * Struct field requirement levels. + */ +enum TReq { + /// Detect the requiredness from the field type: if it is nullable, treat + /// the field as optional, if it is non-nullable, treat the field as + /// required. This is the default used for handling structs not generated + /// from an IDL file, and never emitted by the Thrift compiler. TReq.AUTO + /// shouldn't be specified explicitly. + // Implementation note: thrift.codegen templates use + // thrift.internal.codegen.memberReq to resolve AUTO to REQUIRED/OPTIONAL + // instead of handling it directly. + AUTO, + + /// The field is treated as optional when deserializing/receiving the struct + /// and as required when serializing/sending. This is the Thrift default if + /// neither "required" nor "optional" are specified in the IDL file. + OPT_IN_REQ_OUT, + + /// The field is optional. + OPTIONAL, + + /// The field is required. + REQUIRED, + + /// Ignore the struct field when serializing/deserializing. + IGNORE +} + +/** + * The way how methods are called. + */ +enum TMethodType { + /// Called in the normal two-way scheme consisting of a request and a + /// response. + REGULAR, + + /// A fire-and-forget one-way method, where no response is sent and the + /// client immediately returns. + ONEWAY +} + +/** + * Compile-time metadata for a struct field. + */ +struct TFieldMeta { + /// The name of the field. Used for matching a TFieldMeta with the actual + /// D struct member during code generation. + string name; + + /// The (Thrift) id of the field. + short id; + + /// Whether the field is requried. + TReq req; + + /// A code string containing a D expression for the default value, if there + /// is one. + string defaultValue; +} + +/** + * Compile-time metadata for a service method. + */ +struct TMethodMeta { + /// The name of the method. Used for matching a TMethodMeta with the actual + /// method during code generation. + string name; + + /// Meta information for the parameteres. + TParamMeta[] params; + + /// Specifies which exceptions can be thrown by the method. All other + /// exceptions are converted to a TApplicationException instead. + TExceptionMeta[] exceptions; + + /// The fundamental type of the method. + TMethodType type; +} + +/** + * Compile-time metadata for a service method parameter. + */ +struct TParamMeta { + /// The name of the parameter. Contrary to TFieldMeta, it only serves + /// decorative purposes here. + string name; + + /// The Thrift id of the parameter in the param struct. + short id; + + /// A code string containing a D expression for the default value for the + /// parameter, if any. + string defaultValue; +} + +/** + * Compile-time metadata for a service method exception annotation. + */ +struct TExceptionMeta { + /// The name of the exception »return value«. Contrary to TFieldMeta, it + /// only serves decorative purposes here, as it is only used in code not + /// visible to processor implementations/service clients. + string name; + + /// The Thrift id of the exception field in the return value struct. + short id; + + /// The name of the exception type. + string type; +} + +/** + * A pair of two TPorotocols. To be used in places where a list of protocols + * is expected, for specifying different protocols for input and output. + */ +struct TProtocolPair(InputProtocol, OutputProtocol) if ( + isTProtocol!InputProtocol && isTProtocol!OutputProtocol +) {} + +/** + * true if T is a TProtocolPair. + */ +template isTProtocolPair(T) { + static if (is(T _ == TProtocolPair!(I, O), I, O)) { + enum isTProtocolPair = true; + } else { + enum isTProtocolPair = false; + } +} + +unittest { + static assert(isTProtocolPair!(TProtocolPair!(TProtocol, TProtocol))); + static assert(!isTProtocolPair!TProtocol); +} + +/** + * true if T is a TProtocol or a TProtocolPair. + */ +template isTProtocolOrPair(T) { + enum isTProtocolOrPair = isTProtocol!T || isTProtocolPair!T; +} + +unittest { + static assert(isTProtocolOrPair!TProtocol); + static assert(isTProtocolOrPair!(TProtocolPair!(TProtocol, TProtocol))); + static assert(!isTProtocolOrPair!void); +} + +/** + * true if T represents a Thrift service. + */ +template isService(T) { + enum isService = isBaseService!T || isDerivedService!T; +} + +/** + * true if T represents a Thrift service not derived from another service. + */ +template isBaseService(T) { + static if(is(T _ == interface) && + (!is(T TBases == super) || TBases.length == 0) + ) { + enum isBaseService = true; + } else { + enum isBaseService = false; + } +} + +/** + * true if T represents a Thrift service derived from another service. + */ +template isDerivedService(T) { + static if(is(T _ == interface) && + is(T TBases == super) && TBases.length == 1 + ) { + enum isDerivedService = isService!(TBases[0]); + } else { + enum isDerivedService = false; + } +} + +/** + * For derived services, gets the base service interface. + */ +template BaseService(T) if (isDerivedService!T) { + alias BaseTypeTuple!T[0] BaseService; +} + + +/* + * Code generation templates. + */ + +/** + * Mixin template defining additional helper methods for using a struct with + * Thrift, and a member called isSetFlags if the struct contains any fields + * for which an »is set« flag is needed. + * + * It can only be used inside structs or Exception classes. + * + * For example, consider the following struct definition: + * --- + * struct Foo { + * string a; + * int b; + * int c; + * + * mixin TStructHelpers!([ + * TFieldMeta("a", 1), // Implicitly optional (nullable). + * TFieldMeta("b", 2), // Implicitly required (non-nullable). + * TFieldMeta("c", 3, TReq.REQUIRED, "4") + * ]); + * } + * --- + * + * TStructHelper adds the following methods to the struct: + * --- + * /++ + * + Sets member fieldName to the given value and marks it as set. + * + + * + Examples: + * + --- + * + auto f = Foo(); + * + f.set!"b"(12345); + * + assert(f.isSet!"b"); + * + --- + * +/ + * void set(string fieldName)(MemberType!(This, fieldName) value); + * + * /++ + * + Resets member fieldName to the init property of its type and marks it as + * + not set. + * + + * + Examples: + * + --- + * + // Set f.b to some value. + * + auto f = Foo(); + * + f.set!"b"(12345); + * + + * + f.unset!b(); + * + + * + // f.b is now unset again. + * + assert(!f.isSet!"b"); + * + --- + * +/ + * void unset(string fieldName)(); + * + * /++ + * + Returns whether member fieldName is set. + * + + * + Examples: + * + --- + * + auto f = Foo(); + * + assert(!f.isSet!"b"); + * + f.set!"b"(12345); + * + assert(f.isSet!"b"); + * + --- + * +/ + * bool isSet(string fieldName)() const @property; + * + * /++ + * + Returns a string representation of the struct. + * + + * + Examples: + * + --- + * + auto f = Foo(); + * + f.a = "a string"; + * + assert(f.toString() == `Foo("a string", 0 (unset), 4)`); + * + --- + * +/ + * string toString() const; + * + * /++ + * + Deserializes the struct, setting its members to the values read from the + * + protocol. Forwards to readStruct(this, proto); + * +/ + * void read(Protocol)(Protocol proto) if (isTProtocol!Protocol); + * + * /++ + * + Serializes the struct to the target protocol. Forwards to + * + writeStruct(this, proto); + * +/ + * void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol); + * --- + * + * Additionally, an opEquals() implementation is provided which simply + * compares all fields, but disregards the is set struct, if any (the exact + * signature obviously differs between structs and exception classes). The + * metadata is stored in a manifest constant called fieldMeta. + * + * Note: To set the default values for fields where one has been specified in + * the field metadata, a parameterless static opCall is generated, because D + * does not allow parameterless (default) constructors for structs. Thus, be + * always to use to initialize structs: + * --- + * Foo foo; // Wrong! + * auto foo = Foo(); // Correct. + * --- + */ +mixin template TStructHelpers(alias fieldMetaData = cast(TFieldMeta[])null) if ( + is(typeof(fieldMetaData) : TFieldMeta[]) +) { + import std.algorithm : any; + import thrift.codegen.base; + import thrift.internal.codegen : isNullable, MemberType, mergeFieldMeta, + FieldNames; + import thrift.protocol.base : TProtocol, isTProtocol; + + alias typeof(this) This; + static assert(is(This == struct) || is(This : Exception), + "TStructHelpers can only be used inside a struct or an Exception class."); + + static if (TIsSetFlags!(This, fieldMetaData).tupleof.length > 0) { + // If we need to keep isSet flags around, create an instance of the + // container struct. + TIsSetFlags!(This, fieldMetaData) isSetFlags; + enum fieldMeta = fieldMetaData ~ [TFieldMeta("isSetFlags", 0, TReq.IGNORE)]; + } else { + enum fieldMeta = fieldMetaData; + } + + void set(string fieldName)(MemberType!(This, fieldName) value) if ( + is(MemberType!(This, fieldName)) + ) { + __traits(getMember, this, fieldName) = value; + static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) { + __traits(getMember, this.isSetFlags, fieldName) = true; + } + } + + void unset(string fieldName)() if (is(MemberType!(This, fieldName))) { + static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) { + __traits(getMember, this.isSetFlags, fieldName) = false; + } + __traits(getMember, this, fieldName) = MemberType!(This, fieldName).init; + } + + bool isSet(string fieldName)() const @property if ( + is(MemberType!(This, fieldName)) + ) { + static if (isNullable!(MemberType!(This, fieldName))) { + return __traits(getMember, this, fieldName) !is null; + } else static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) { + return __traits(getMember, this.isSetFlags, fieldName); + } else { + // This is a required field, which is always set. + return true; + } + } + + static if (is(This _ == class)) { + override string toString() const { + return thriftToStringImpl(); + } + + override bool opEquals(Object other) const { + auto rhs = cast(This)other; + if (rhs) { + return thriftOpEqualsImpl(rhs); + } + + return (cast()super).opEquals(other); + } + + override size_t toHash() const { + return thriftToHashImpl(); + } + } else { + string toString() const { + return thriftToStringImpl(); + } + + bool opEquals(ref const This other) const { + return thriftOpEqualsImpl(other); + } + + size_t toHash() const @safe nothrow { + return thriftToHashImpl(); + } + } + + private string thriftToStringImpl() const { + import std.conv : to; + string result = This.stringof ~ "("; + mixin({ + string code = ""; + bool first = true; + foreach (name; FieldNames!(This, fieldMeta)) { + if (first) { + first = false; + } else { + code ~= "result ~= `, `;\n"; + } + code ~= "result ~= `" ~ name ~ ": ` ~ to!string(cast()this." ~ name ~ ");\n"; + code ~= "if (!isSet!q{" ~ name ~ "}) {\n"; + code ~= "result ~= ` (unset)`;\n"; + code ~= "}\n"; + } + return code; + }()); + result ~= ")"; + return result; + } + + private bool thriftOpEqualsImpl(const ref This rhs) const { + foreach (name; FieldNames!This) { + if (mixin("this." ~ name) != mixin("rhs." ~ name)) return false; + } + return true; + } + + private size_t thriftToHashImpl() const @trusted nothrow { + size_t hash = 0; + foreach (i, _; this.tupleof) { + auto val = this.tupleof[i]; + hash += typeid(val).getHash(&val); + } + return hash; + } + + static if (any!`!a.defaultValue.empty`(mergeFieldMeta!(This, fieldMetaData))) { + static if (is(This _ == class)) { + this() { + mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("this")); + } + } else { + // DMD @@BUG@@: Have to use auto here to avoid »no size yet for forward + // reference« errors. + static auto opCall() { + auto result = This.init; + mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("result")); + return result; + } + } + } + + void read(Protocol)(Protocol proto) if (isTProtocol!Protocol) { + // Need to explicitly specify fieldMetaData here, since it isn't already + // picked up in some situations (e.g. the TArgs struct for methods with + // multiple parameters in async_test_servers) otherwise. Due to a DMD + // @@BUG@@, we need to explicitly specify the other template parameters + // as well. + readStruct!(This, Protocol, fieldMetaData, false)(this, proto); + } + + void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol) { + writeStruct!(This, Protocol, fieldMetaData, false)(this, proto); + } +} + +// DMD @@BUG@@: Having this inside TStructHelpers leads to weird lookup errors +// (e.g. for std.arry.empty). +string thriftFieldInitCode(alias fieldMeta)(string thisName) { + string code = ""; + foreach (field; fieldMeta) { + if (field.defaultValue.empty) continue; + code ~= thisName ~ "." ~ field.name ~ " = " ~ field.defaultValue ~ ";\n"; + } + return code; +} + +unittest { + // Cannot make this nested in the unittest block due to a »no size yet for + // forward reference« error. + static struct Foo { + string a; + int b; + int c; + + mixin TStructHelpers!([ + TFieldMeta("a", 1), + TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT), + TFieldMeta("c", 3, TReq.REQUIRED, "4") + ]); + } + + auto f = Foo(); + + f.set!"b"(12345); + assert(f.isSet!"b"); + f.unset!"b"(); + assert(!f.isSet!"b"); + f.set!"b"(12345); + assert(f.isSet!"b"); + f.unset!"b"(); + + f.a = "a string"; + assert(f.toString() == `Foo(a: a string, b: 0 (unset), c: 4)`); +} + + +/** + * Generates an eponymous struct with boolean flags for the non-required + * non-nullable fields of T. + * + * Nullable fields are just set to null to signal »not set«, so no flag is + * emitted for them, even if they are optional. + * + * In most cases, you do not want to use this directly, but via TStructHelpers + * instead. + */ +template TIsSetFlags(T, alias fieldMetaData) { + mixin({ + string code = "struct TIsSetFlags {\n"; + foreach (meta; fieldMetaData) { + code ~= "static if (!is(MemberType!(T, `" ~ meta.name ~ "`))) {\n"; + code ~= q{ + static assert(false, "Field '" ~ meta.name ~ + "' referenced in metadata not present in struct '" ~ T.stringof ~ "'."); + }; + code ~= "}"; + if (meta.req == TReq.OPTIONAL || meta.req == TReq.OPT_IN_REQ_OUT) { + code ~= "else static if (!isNullable!(MemberType!(T, `" ~ meta.name ~ "`))) {\n"; + code ~= " bool " ~ meta.name ~ ";\n"; + code ~= "}\n"; + } + } + code ~= "}"; + return code; + }()); +} + +/** + * Deserializes a Thrift struct from a protocol. + * + * Using the Protocol template parameter, the concrete TProtocol to use can be + * be specified. If the pointerStruct parameter is set to true, the struct + * fields are expected to be pointers to the actual data. This is used + * internally (combined with TPResultStruct) and usually should not be used in + * user code. + * + * This is a free function to make it possible to read exisiting structs from + * the wire without altering their definitions. + */ +void readStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null, + bool pointerStruct = false)(auto ref T s, Protocol p) if (isTProtocol!Protocol) +{ + mixin({ + string code; + + // Check that all fields for which there is meta info are actually in the + // passed struct type. + foreach (field; mergeFieldMeta!(T, fieldMetaData)) { + code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n"; + } + + // Returns the code string for reading a value of type F off the wire and + // assigning it to v. The level parameter is used to make sure that there + // are no conflicting variable names on recursive calls. + string readValueCode(ValueType)(string v, size_t level = 0) { + // Some non-ambigous names to use (shadowing is not allowed in D). + immutable i = "i" ~ to!string(level); + immutable elem = "elem" ~ to!string(level); + immutable key = "key" ~ to!string(level); + immutable list = "list" ~ to!string(level); + immutable map = "map" ~ to!string(level); + immutable set = "set" ~ to!string(level); + immutable value = "value" ~ to!string(level); + + alias FullyUnqual!ValueType F; + + static if (is(F == bool)) { + return v ~ " = p.readBool();"; + } else static if (is(F == byte)) { + return v ~ " = p.readByte();"; + } else static if (is(F == double)) { + return v ~ " = p.readDouble();"; + } else static if (is(F == short)) { + return v ~ " = p.readI16();"; + } else static if (is(F == int)) { + return v ~ " = p.readI32();"; + } else static if (is(F == long)) { + return v ~ " = p.readI64();"; + } else static if (is(F : string)) { + return v ~ " = p.readString();"; + } else static if (is(F == enum)) { + return v ~ " = cast(typeof(" ~ v ~ "))p.readI32();"; + } else static if (is(F _ : E[], E)) { + return "{\n" ~ + "auto " ~ list ~ " = p.readListBegin();\n" ~ + // TODO: Check element type here? + v ~ " = new typeof(" ~ v ~ "[0])[" ~ list ~ ".size];\n" ~ + "foreach (" ~ i ~ "; 0 .. " ~ list ~ ".size) {\n" ~ + readValueCode!E(v ~ "[" ~ i ~ "]", level + 1) ~ "\n" ~ + "}\n" ~ + "p.readListEnd();\n" ~ + "}"; + } else static if (is(F _ : V[K], K, V)) { + return "{\n" ~ + "auto " ~ map ~ " = p.readMapBegin();" ~ + v ~ " = null;\n" ~ + // TODO: Check key/value types here? + "foreach (" ~ i ~ "; 0 .. " ~ map ~ ".size) {\n" ~ + "FullyUnqual!(typeof(" ~ v ~ ".keys[0])) " ~ key ~ ";\n" ~ + readValueCode!K(key, level + 1) ~ "\n" ~ + "typeof(" ~ v ~ ".values[0]) " ~ value ~ ";\n" ~ + readValueCode!V(value, level + 1) ~ "\n" ~ + v ~ "[cast(typeof(" ~ v ~ ".keys[0]))" ~ key ~ "] = " ~ value ~ ";\n" ~ + "}\n" ~ + "p.readMapEnd();" ~ + "}"; + } else static if (is(F _ : HashSet!(E), E)) { + return "{\n" ~ + "auto " ~ set ~ " = p.readSetBegin();" ~ + // TODO: Check element type here? + v ~ " = new typeof(" ~ v ~ ")();\n" ~ + "foreach (" ~ i ~ "; 0 .. " ~ set ~ ".size) {\n" ~ + "typeof(" ~ v ~ "[][0]) " ~ elem ~ ";\n" ~ + readValueCode!E(elem, level + 1) ~ "\n" ~ + v ~ " ~= " ~ elem ~ ";\n" ~ + "}\n" ~ + "p.readSetEnd();" ~ + "}"; + } else static if (is(F == struct) || is(F : TException)) { + static if (is(F == struct)) { + auto result = v ~ " = typeof(" ~ v ~ ")();\n"; + } else { + auto result = v ~ " = new typeof(" ~ v ~ ")();\n"; + } + + static if (__traits(compiles, F.init.read(TProtocol.init))) { + result ~= v ~ ".read(p);"; + } else { + result ~= "readStruct(" ~ v ~ ", p);"; + } + return result; + } else { + static assert(false, "Cannot represent type in Thrift: " ~ F.stringof); + } + } + + string readFieldCode(FieldType)(string name, short id, TReq req) { + static if (pointerStruct && isPointer!FieldType) { + immutable v = "(*s." ~ name ~ ")"; + alias PointerTarget!FieldType F; + } else { + immutable v = "s." ~ name; + alias FieldType F; + } + + string code = "case " ~ to!string(id) ~ ":\n"; + code ~= "if (f.type == " ~ dToTTypeString!F ~ ") {\n"; + code ~= readValueCode!F(v) ~ "\n"; + if (req == TReq.REQUIRED) { + // For required fields, set the corresponding local isSet variable. + code ~= "isSet_" ~ name ~ " = true;\n"; + } else if (!isNullable!F){ + code ~= "s.isSetFlags." ~ name ~ " = true;\n"; + } + code ~= "} else skip(p, f.type);\n"; + code ~= "break;\n"; + return code; + } + + // Code for the local boolean flags used to make sure required fields have + // been found. + string isSetFlagCode = ""; + + // Code for checking whether the flags for the required fields are true. + string isSetCheckCode = ""; + + /// Code for the case statements storing the fields to the result struct. + string readMembersCode = ""; + + // The last automatically assigned id – fields with no meta information + // are assigned (in lexical order) descending negative ids, starting with + // -1, just like the Thrift compiler does. + short lastId; + + foreach (name; FieldNames!T) { + enum req = memberReq!(T, name, fieldMetaData); + if (req == TReq.REQUIRED) { + // For required fields, generate local bool flags to keep track + // whether the field has been encountered. + immutable n = "isSet_" ~ name; + isSetFlagCode ~= "bool " ~ n ~ ";\n"; + isSetCheckCode ~= "enforce(" ~ n ~ ", new TProtocolException(" ~ + "`Required field '" ~ name ~ "' not found in serialized data`, " ~ + "TProtocolException.Type.INVALID_DATA));\n"; + } + + enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name); + static if (meta.empty) { + --lastId; + version (TVerboseCodegen) { + code ~= "pragma(msg, `[thrift.codegen.base.readStruct] Warning: No " ~ + "meta information for field '" ~ name ~ "' in struct '" ~ + T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n"; + } + readMembersCode ~= readFieldCode!(MemberType!(T, name))( + name, lastId, req); + } else static if (req != TReq.IGNORE) { + readMembersCode ~= readFieldCode!(MemberType!(T, name))( + name, meta.front.id, req); + } + } + + code ~= isSetFlagCode; + code ~= "p.readStructBegin();\n"; + code ~= "while (true) {\n"; + code ~= "auto f = p.readFieldBegin();\n"; + code ~= "if (f.type == TType.STOP) break;\n"; + code ~= "switch(f.id) {\n"; + code ~= readMembersCode; + code ~= "default: skip(p, f.type);\n"; + code ~= "}\n"; + code ~= "p.readFieldEnd();\n"; + code ~= "}\n"; + code ~= "p.readStructEnd();\n"; + code ~= isSetCheckCode; + + return code; + }()); +} + +/** + * Serializes a struct to the target protocol. + * + * Using the Protocol template parameter, the concrete TProtocol to use can be + * be specified. If the pointerStruct parameter is set to true, the struct + * fields are expected to be pointers to the actual data. This is used + * internally (combined with TPargsStruct) and usually should not be used in + * user code. + * + * This is a free function to make it possible to read exisiting structs from + * the wire without altering their definitions. + */ +void writeStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null, + bool pointerStruct = false) (const T s, Protocol p) if (isTProtocol!Protocol) +{ + mixin({ + // Check that all fields for which there is meta info are actually in the + // passed struct type. + string code = ""; + foreach (field; mergeFieldMeta!(T, fieldMetaData)) { + code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n"; + } + + // Check that required nullable members are non-null. + // WORKAROUND: To stop LDC from emitting the manifest constant »meta« below + // into the writeStruct function body this is inside the string mixin + // block – the code wouldn't depend on it (this is an LDC bug, and because + // of it a new array would be allocated on each method invocation at runtime). + foreach (name; StaticFilter!( + Compose!(isNullable, PApply!(MemberType, T)), + FieldNames!T + )) { + static if (memberReq!(T, name, fieldMetaData) == TReq.REQUIRED) { + code ~= "enforce(__traits(getMember, s, `" ~ name ~ "`) !is null, + new TException(`Required field '" ~ name ~ "' is null.`));\n"; + } + } + + return code; + }()); + + p.writeStructBegin(TStruct(T.stringof)); + mixin({ + string writeValueCode(ValueType)(string v, size_t level = 0) { + // Some non-ambigous names to use (shadowing is not allowed in D). + immutable elem = "elem" ~ to!string(level); + immutable key = "key" ~ to!string(level); + immutable value = "value" ~ to!string(level); + + alias FullyUnqual!ValueType F; + static if (is(F == bool)) { + return "p.writeBool(" ~ v ~ ");"; + } else static if (is(F == byte)) { + return "p.writeByte(" ~ v ~ ");"; + } else static if (is(F == double)) { + return "p.writeDouble(" ~ v ~ ");"; + } else static if (is(F == short)) { + return "p.writeI16(" ~ v ~ ");"; + } else static if (is(F == int)) { + return "p.writeI32(" ~ v ~ ");"; + } else static if (is(F == long)) { + return "p.writeI64(" ~ v ~ ");"; + } else static if (is(F : string)) { + return "p.writeString(" ~ v ~ ");"; + } else static if (is(F == enum)) { + return "p.writeI32(cast(int)" ~ v ~ ");"; + } else static if (is(F _ : E[], E)) { + return "p.writeListBegin(TList(" ~ dToTTypeString!E ~ ", " ~ v ~ + ".length));\n" ~ + "foreach (" ~ elem ~ "; " ~ v ~ ") {\n" ~ + writeValueCode!E(elem, level + 1) ~ "\n" ~ + "}\n" ~ + "p.writeListEnd();"; + } else static if (is(F _ : V[K], K, V)) { + return "p.writeMapBegin(TMap(" ~ dToTTypeString!K ~ ", " ~ + dToTTypeString!V ~ ", " ~ v ~ ".length));\n" ~ + "foreach (" ~ key ~ ", " ~ value ~ "; " ~ v ~ ") {\n" ~ + writeValueCode!K(key, level + 1) ~ "\n" ~ + writeValueCode!V(value, level + 1) ~ "\n" ~ + "}\n" ~ + "p.writeMapEnd();"; + } else static if (is(F _ : HashSet!E, E)) { + return "p.writeSetBegin(TSet(" ~ dToTTypeString!E ~ ", " ~ v ~ + ".length));\n" ~ + "foreach (" ~ elem ~ "; " ~ v ~ "[]) {\n" ~ + writeValueCode!E(elem, level + 1) ~ "\n" ~ + "}\n" ~ + "p.writeSetEnd();"; + } else static if (is(F == struct) || is(F : TException)) { + static if (__traits(compiles, F.init.write(TProtocol.init))) { + return v ~ ".write(p);"; + } else { + return "writeStruct(" ~ v ~ ", p);"; + } + } else { + static assert(false, "Cannot represent type in Thrift: " ~ F.stringof); + } + } + + string writeFieldCode(FieldType)(string name, short id, TReq req) { + string code; + if (!pointerStruct && req == TReq.OPTIONAL) { + code ~= "if (s.isSet!`" ~ name ~ "`) {\n"; + } + + static if (pointerStruct && isPointer!FieldType) { + immutable v = "(*s." ~ name ~ ")"; + alias PointerTarget!FieldType F; + } else { + immutable v = "s." ~ name; + alias FieldType F; + } + + code ~= "p.writeFieldBegin(TField(`" ~ name ~ "`, " ~ dToTTypeString!F ~ + ", " ~ to!string(id) ~ "));\n"; + code ~= writeValueCode!F(v) ~ "\n"; + code ~= "p.writeFieldEnd();\n"; + + if (!pointerStruct && req == TReq.OPTIONAL) { + code ~= "}\n"; + } + return code; + } + + // The last automatically assigned id – fields with no meta information + // are assigned (in lexical order) descending negative ids, starting with + // -1, just like the Thrift compiler does. + short lastId; + + string code = ""; + foreach (name; FieldNames!T) { + alias MemberType!(T, name) F; + enum req = memberReq!(T, name, fieldMetaData); + enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name); + if (meta.empty) { + --lastId; + version (TVerboseCodegen) { + code ~= "pragma(msg, `[thrift.codegen.base.writeStruct] Warning: No " ~ + "meta information for field '" ~ name ~ "' in struct '" ~ + T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n"; + } + code ~= writeFieldCode!F(name, lastId, req); + } else if (req != TReq.IGNORE) { + code ~= writeFieldCode!F(name, meta.front.id, req); + } + } + + return code; + }()); + p.writeFieldStop(); + p.writeStructEnd(); +} + +unittest { + // Ensure that the generated code at least compiles for the basic field type + // combinations. Functionality checks are covered by the rest of the test + // suite. + + static struct Test { + // Non-nullable. + int a1; + int a2; + int a3; + int a4; + + // Nullable. + string b1; + string b2; + string b3; + string b4; + + mixin TStructHelpers!([ + TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT), + TFieldMeta("a2", 2, TReq.OPTIONAL), + TFieldMeta("a3", 3, TReq.REQUIRED), + TFieldMeta("a4", 4, TReq.IGNORE), + TFieldMeta("b1", 5, TReq.OPT_IN_REQ_OUT), + TFieldMeta("b2", 6, TReq.OPTIONAL), + TFieldMeta("b3", 7, TReq.REQUIRED), + TFieldMeta("b4", 8, TReq.IGNORE), + ]); + } + + static assert(__traits(compiles, { Test t; t.read(cast(TProtocol)null); })); + static assert(__traits(compiles, { Test t; t.write(cast(TProtocol)null); })); +} + +// Ensure opEquals and toHash consistency. +unittest { + struct TestEquals { + int a1; + + mixin TStructHelpers!([ + TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT), + ]); + } + + TestEquals a, b; + assert(a == b); + assert(a.toHash() == b.toHash()); + + a.a1 = 42; + assert(a != b); + assert(a.toHash() != b.toHash()); + + b.a1 = 42; + assert(a == b); + assert(a.toHash() == b.toHash()); +} + +private { + /* + * Returns a D code string containing the matching TType value for a passed + * D type, e.g. dToTTypeString!byte == "TType.BYTE". + */ + template dToTTypeString(T) { + static if (is(FullyUnqual!T == bool)) { + enum dToTTypeString = "TType.BOOL"; + } else static if (is(FullyUnqual!T == byte)) { + enum dToTTypeString = "TType.BYTE"; + } else static if (is(FullyUnqual!T == double)) { + enum dToTTypeString = "TType.DOUBLE"; + } else static if (is(FullyUnqual!T == short)) { + enum dToTTypeString = "TType.I16"; + } else static if (is(FullyUnqual!T == int)) { + enum dToTTypeString = "TType.I32"; + } else static if (is(FullyUnqual!T == long)) { + enum dToTTypeString = "TType.I64"; + } else static if (is(FullyUnqual!T : string)) { + enum dToTTypeString = "TType.STRING"; + } else static if (is(FullyUnqual!T == enum)) { + enum dToTTypeString = "TType.I32"; + } else static if (is(FullyUnqual!T _ : U[], U)) { + enum dToTTypeString = "TType.LIST"; + } else static if (is(FullyUnqual!T _ : V[K], K, V)) { + enum dToTTypeString = "TType.MAP"; + } else static if (is(FullyUnqual!T _ : HashSet!E, E)) { + enum dToTTypeString = "TType.SET"; + } else static if (is(FullyUnqual!T == struct)) { + enum dToTTypeString = "TType.STRUCT"; + } else static if (is(FullyUnqual!T : TException)) { + enum dToTTypeString = "TType.STRUCT"; + } else { + static assert(false, "Cannot represent type in Thrift: " ~ T.stringof); + } + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/codegen/client.d b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/client.d new file mode 100644 index 000000000..117b07660 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/client.d @@ -0,0 +1,486 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.codegen.client; + +import std.algorithm : find; +import std.array : empty, front; +import std.conv : to; +import std.traits : isSomeFunction, ParameterStorageClass, + ParameterStorageClassTuple, ParameterTypeTuple, ReturnType; +import thrift.codegen.base; +import thrift.internal.codegen; +import thrift.internal.ctfe; +import thrift.protocol.base; + +/** + * Thrift service client, which implements an interface by synchronously + * calling a server over a TProtocol. + * + * TClientBase simply extends Interface with generic input/output protocol + * properties to serve as a supertype for all TClients for the same service, + * which might be instantiated with different concrete protocol types (there + * is no covariance for template type parameters). If Interface is derived + * from another interface BaseInterface, it also extends + * TClientBase!BaseInterface. + * + * TClient is the class that actually implements TClientBase. Just as + * TClientBase, it is also derived from TClient!BaseInterface for inheriting + * services. + * + * TClient takes two optional template arguments which can be used for + * specifying the actual TProtocol implementation used for optimization + * purposes, as virtual calls can completely be eliminated then. If + * OutputProtocol is not specified, it is assumed to be the same as + * InputProtocol. The protocol properties defined by TClientBase are exposed + * with their concrete type (return type covariance). + * + * In addition to implementing TClientBase!Interface, TClient offers the + * following constructors: + * --- + * this(InputProtocol iprot, OutputProtocol oprot); + * // Only if is(InputProtocol == OutputProtocol), to use the same protocol + * // for both input and output: + * this(InputProtocol prot); + * --- + * + * The sequence id of the method calls starts at zero and is automatically + * incremented. + */ +interface TClientBase(Interface) if (isBaseService!Interface) : Interface { + /** + * The input protocol used by the client. + */ + TProtocol inputProtocol() @property; + + /** + * The output protocol used by the client. + */ + TProtocol outputProtocol() @property; +} + +/// Ditto +interface TClientBase(Interface) if (isDerivedService!Interface) : + TClientBase!(BaseService!Interface), Interface {} + +/// Ditto +template TClient(Interface, InputProtocol = TProtocol, OutputProtocol = void) if ( + isService!Interface && isTProtocol!InputProtocol && + (isTProtocol!OutputProtocol || is(OutputProtocol == void)) +) { + mixin({ + static if (isDerivedService!Interface) { + string code = "class TClient : TClient!(BaseService!Interface, " ~ + "InputProtocol, OutputProtocol), TClientBase!Interface {\n"; + code ~= q{ + this(IProt iprot, OProt oprot) { + super(iprot, oprot); + } + + static if (is(IProt == OProt)) { + this(IProt prot) { + super(prot); + } + } + + // DMD @@BUG@@: If these are not present in this class (would be) + // inherited anyway, »not implemented« errors are raised. + override IProt inputProtocol() @property { + return super.inputProtocol; + } + override OProt outputProtocol() @property { + return super.outputProtocol; + } + }; + } else { + string code = "class TClient : TClientBase!Interface {"; + code ~= q{ + alias InputProtocol IProt; + static if (isTProtocol!OutputProtocol) { + alias OutputProtocol OProt; + } else { + static assert(is(OutputProtocol == void)); + alias InputProtocol OProt; + } + + this(IProt iprot, OProt oprot) { + iprot_ = iprot; + oprot_ = oprot; + } + + static if (is(IProt == OProt)) { + this(IProt prot) { + this(prot, prot); + } + } + + IProt inputProtocol() @property { + return iprot_; + } + + OProt outputProtocol() @property { + return oprot_; + } + + protected IProt iprot_; + protected OProt oprot_; + protected int seqid_; + }; + } + + foreach (methodName; __traits(derivedMembers, Interface)) { + static if (isSomeFunction!(mixin("Interface." ~ methodName))) { + bool methodMetaFound; + TMethodMeta methodMeta; + static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) { + enum meta = find!`a.name == b`(Interface.methodMeta, methodName); + if (!meta.empty) { + methodMetaFound = true; + methodMeta = meta.front; + } + } + + // Generate the code for sending. + string[] paramList; + string paramAssignCode; + foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) { + // Use the param name speficied in the meta information if any – + // just cosmetics in this case. + string paramName; + if (methodMetaFound && i < methodMeta.params.length) { + paramName = methodMeta.params[i].name; + } else { + paramName = "param" ~ to!string(i + 1); + } + + immutable storage = ParameterStorageClassTuple!( + mixin("Interface." ~ methodName))[i]; + paramList ~= ((storage & ParameterStorageClass.ref_) ? "ref " : "") ~ + "ParameterTypeTuple!(Interface." ~ methodName ~ ")[" ~ + to!string(i) ~ "] " ~ paramName; + paramAssignCode ~= "args." ~ paramName ~ " = &" ~ paramName ~ ";\n"; + } + code ~= "ReturnType!(Interface." ~ methodName ~ ") " ~ methodName ~ + "(" ~ ctfeJoin(paramList) ~ ") {\n"; + + code ~= "immutable methodName = `" ~ methodName ~ "`;\n"; + + immutable paramStructType = + "TPargsStruct!(Interface, `" ~ methodName ~ "`)"; + code ~= paramStructType ~ " args = " ~ paramStructType ~ "();\n"; + code ~= paramAssignCode; + code ~= "oprot_.writeMessageBegin(TMessage(`" ~ methodName ~ "`, "; + code ~= ((methodMetaFound && methodMeta.type == TMethodType.ONEWAY) + ? "TMessageType.ONEWAY" : "TMessageType.CALL"); + code ~= ", ++seqid_));\n"; + code ~= "args.write(oprot_);\n"; + code ~= "oprot_.writeMessageEnd();\n"; + code ~= "oprot_.transport.flush();\n"; + + // If this is not a oneway method, generate the receiving code. + if (!methodMetaFound || methodMeta.type != TMethodType.ONEWAY) { + code ~= "TPresultStruct!(Interface, `" ~ methodName ~ "`) result;\n"; + + if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) { + code ~= "ReturnType!(Interface." ~ methodName ~ ") _return;\n"; + code ~= "result.success = &_return;\n"; + } + + // TODO: The C++ implementation checks for matching method name here, + // should we do as well? + code ~= q{ + auto msg = iprot_.readMessageBegin(); + scope (exit) { + iprot_.readMessageEnd(); + iprot_.transport.readEnd(); + } + + if (msg.type == TMessageType.EXCEPTION) { + auto x = new TApplicationException(null); + x.read(iprot_); + iprot_.transport.readEnd(); + throw x; + } + if (msg.type != TMessageType.REPLY) { + skip(iprot_, TType.STRUCT); + iprot_.transport.readEnd(); + } + if (msg.seqid != seqid_) { + throw new TApplicationException( + methodName ~ " failed: Out of sequence response.", + TApplicationException.Type.BAD_SEQUENCE_ID + ); + } + result.read(iprot_); + }; + + if (methodMetaFound) { + foreach (e; methodMeta.exceptions) { + code ~= "if (result.isSet!`" ~ e.name ~ "`) throw result." ~ + e.name ~ ";\n"; + } + } + + if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) { + code ~= q{ + if (result.isSet!`success`) return _return; + throw new TApplicationException( + methodName ~ " failed: Unknown result.", + TApplicationException.Type.MISSING_RESULT + ); + }; + } + } + code ~= "}\n"; + } + } + + code ~= "}\n"; + return code; + }()); +} + +/** + * TClient construction helper to avoid having to explicitly specify + * the protocol types, i.e. to allow the constructor being called using IFTI + * (see $(DMDBUG 6082, D Bugzilla enhancement requet 6082)). + */ +TClient!(Interface, Prot) tClient(Interface, Prot)(Prot prot) if ( + isService!Interface && isTProtocol!Prot +) { + return new TClient!(Interface, Prot)(prot); +} + +/// Ditto +TClient!(Interface, IProt, Oprot) tClient(Interface, IProt, OProt) + (IProt iprot, OProt oprot) if ( + isService!Interface && isTProtocol!IProt && isTProtocol!OProt +) { + return new TClient!(Interface, IProt, OProt)(iprot, oprot); +} + +/** + * Represents the arguments of a Thrift method call, as pointers to the (const) + * parameter type to avoid copying. + * + * There should usually be no reason to use this struct directly without the + * help of TClient, but it is documented publicly to help debugging in case + * of CTFE errors. + * + * Consider this example: + * --- + * interface Foo { + * int bar(string a, bool b); + * + * enum methodMeta = [ + * TMethodMeta("bar", [TParamMeta("a", 1), TParamMeta("b", 2)]) + * ]; + * } + * + * alias TPargsStruct!(Foo, "bar") FooBarPargs; + * --- + * + * The definition of FooBarPargs is equivalent to (ignoring the necessary + * metadata to assign the field IDs): + * --- + * struct FooBarPargs { + * const(string)* a; + * const(bool)* b; + * + * void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol); + * } + * --- + */ +template TPargsStruct(Interface, string methodName) { + static assert(is(typeof(mixin("Interface." ~ methodName))), + "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'."); + mixin({ + bool methodMetaFound; + TMethodMeta methodMeta; + static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) { + auto meta = find!`a.name == b`(Interface.methodMeta, methodName); + if (!meta.empty) { + methodMetaFound = true; + methodMeta = meta.front; + } + } + + string memberCode; + string[] fieldMetaCodes; + foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) { + // If we have no meta information, just use param1, param2, etc. as + // field names, it shouldn't really matter anyway. 1-based »indexing« + // is used to match the common scheme in the Thrift world. + string memberId; + string memberName; + if (methodMetaFound && i < methodMeta.params.length) { + memberId = to!string(methodMeta.params[i].id); + memberName = methodMeta.params[i].name; + } else { + memberId = to!string(i + 1); + memberName = "param" ~ to!string(i + 1); + } + + // Workaround for DMD @@BUG@@ 6056: make an intermediary alias for the + // parameter type, and declare the member using const(memberNameType)*. + memberCode ~= "alias ParameterTypeTuple!(Interface." ~ methodName ~ + ")[" ~ to!string(i) ~ "] " ~ memberName ~ "Type;\n"; + memberCode ~= "const(" ~ memberName ~ "Type)* " ~ memberName ~ ";\n"; + + fieldMetaCodes ~= "TFieldMeta(`" ~ memberName ~ "`, " ~ memberId ~ + ", TReq.OPT_IN_REQ_OUT)"; + } + + string code = "struct TPargsStruct {\n"; + code ~= memberCode; + version (TVerboseCodegen) { + if (!methodMetaFound && + ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0) + { + code ~= "pragma(msg, `[thrift.codegen.base.TPargsStruct] Warning: No " ~ + "meta information for method '" ~ methodName ~ "' in service '" ~ + Interface.stringof ~ "' found.`);\n"; + } + } + code ~= "void write(P)(P proto) const if (isTProtocol!P) {\n"; + code ~= "writeStruct!(typeof(this), P, [" ~ ctfeJoin(fieldMetaCodes) ~ + "], true)(this, proto);\n"; + code ~= "}\n"; + code ~= "}\n"; + return code; + }()); +} + +/** + * Represents the result of a Thrift method call, using a pointer to the return + * value to avoid copying. + * + * There should usually be no reason to use this struct directly without the + * help of TClient, but it is documented publicly to help debugging in case + * of CTFE errors. + * + * Consider this example: + * --- + * interface Foo { + * int bar(string a); + * + * alias .FooException FooException; + * + * enum methodMeta = [ + * TMethodMeta("bar", + * [TParamMeta("a", 1)], + * [TExceptionMeta("fooe", 1, "FooException")] + * ) + * ]; + * } + * alias TPresultStruct!(Foo, "bar") FooBarPresult; + * --- + * + * The definition of FooBarPresult is equivalent to (ignoring the necessary + * metadata to assign the field IDs): + * --- + * struct FooBarPresult { + * int* success; + * Foo.FooException fooe; + * + * struct IsSetFlags { + * bool success; + * } + * IsSetFlags isSetFlags; + * + * bool isSet(string fieldName)() const @property; + * void read(Protocol)(Protocol proto) if (isTProtocol!Protocol); + * } + * --- + */ +template TPresultStruct(Interface, string methodName) { + static assert(is(typeof(mixin("Interface." ~ methodName))), + "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'."); + + mixin({ + string code = "struct TPresultStruct {\n"; + + string[] fieldMetaCodes; + + alias ReturnType!(mixin("Interface." ~ methodName)) ResultType; + static if (!is(ResultType == void)) { + code ~= q{ + ReturnType!(mixin("Interface." ~ methodName))* success; + }; + fieldMetaCodes ~= "TFieldMeta(`success`, 0, TReq.OPTIONAL)"; + + static if (!isNullable!ResultType) { + code ~= q{ + struct IsSetFlags { + bool success; + } + IsSetFlags isSetFlags; + }; + fieldMetaCodes ~= "TFieldMeta(`isSetFlags`, 0, TReq.IGNORE)"; + } + } + + bool methodMetaFound; + static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) { + auto meta = find!`a.name == b`(Interface.methodMeta, methodName); + if (!meta.empty) { + foreach (e; meta.front.exceptions) { + code ~= "Interface." ~ e.type ~ " " ~ e.name ~ ";\n"; + fieldMetaCodes ~= "TFieldMeta(`" ~ e.name ~ "`, " ~ to!string(e.id) ~ + ", TReq.OPTIONAL)"; + } + methodMetaFound = true; + } + } + + version (TVerboseCodegen) { + if (!methodMetaFound && + ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0) + { + code ~= "pragma(msg, `[thrift.codegen.base.TPresultStruct] Warning: No " ~ + "meta information for method '" ~ methodName ~ "' in service '" ~ + Interface.stringof ~ "' found.`);\n"; + } + } + + code ~= q{ + bool isSet(string fieldName)() const @property if ( + is(MemberType!(typeof(this), fieldName)) + ) { + static if (fieldName == "success") { + static if (isNullable!(typeof(*success))) { + return *success !is null; + } else { + return isSetFlags.success; + } + } else { + // We are dealing with an exception member, which, being a nullable + // type (exceptions are always classes), has no isSet flag. + return __traits(getMember, this, fieldName) !is null; + } + } + }; + + code ~= "void read(P)(P proto) if (isTProtocol!P) {\n"; + code ~= "readStruct!(typeof(this), P, [" ~ ctfeJoin(fieldMetaCodes) ~ + "], true)(this, proto);\n"; + code ~= "}\n"; + code ~= "}\n"; + return code; + }()); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/codegen/client_pool.d b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/client_pool.d new file mode 100644 index 000000000..c46b74344 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/client_pool.d @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.codegen.client_pool; + +import core.time : dur, Duration, TickDuration; +import std.traits : ParameterTypeTuple, ReturnType; +import thrift.base; +import thrift.codegen.base; +import thrift.codegen.client; +import thrift.internal.codegen; +import thrift.internal.resource_pool; + +/** + * Manages a pool of TClients for the given interface, forwarding RPC calls to + * members of the pool. + * + * If a request fails, another client from the pool is tried, and optionally, + * a client is disabled for a configurable amount of time if it fails too + * often. If all clients fail (and keepTrying is false), a + * TCompoundOperationException is thrown, containing all the collected RPC + * exceptions. + */ +class TClientPool(Interface) if (isService!Interface) : Interface { + /// Shorthand for TClientBase!Interface, the client type this instance + /// operates on. + alias TClientBase!Interface Client; + + /** + * Creates a new instance and adds the given clients to the pool. + */ + this(Client[] clients) { + pool_ = new TResourcePool!Client(clients); + + rpcFaultFilter = (Exception e) { + import thrift.protocol.base; + import thrift.transport.base; + return ( + (cast(TTransportException)e !is null) || + (cast(TApplicationException)e !is null) + ); + }; + } + + /** + * Executes an operation on the first currently active client. + * + * If the operation fails (throws an exception for which rpcFaultFilter is + * true), the failure is recorded and the next client in the pool is tried. + * + * Throws: Any non-rpc exception that occurs, a TCompoundOperationException + * if all clients failed with an rpc exception (if keepTrying is false). + * + * Example: + * --- + * interface Foo { string bar(); } + * auto poolClient = tClientPool([tClient!Foo(someProtocol)]); + * auto result = poolClient.execute((c){ return c.bar(); }); + * --- + */ + ResultType execute(ResultType)(scope ResultType delegate(Client) work) { + return executeOnPool!Client(work); + } + + /** + * Adds a client to the pool. + */ + void addClient(Client client) { + pool_.add(client); + } + + /** + * Removes a client from the pool. + * + * Returns: Whether the client was found in the pool. + */ + bool removeClient(Client client) { + return pool_.remove(client); + } + + mixin(poolForwardCode!Interface()); + + /// Whether to open the underlying transports of a client before trying to + /// execute a method if they are not open. This is usually desirable + /// because it allows e.g. to automatically reconnect to a remote server + /// if the network connection is dropped. + /// + /// Defaults to true. + bool reopenTransports = true; + + /// Called to determine whether an exception comes from a client from the + /// pool not working properly, or if it an exception thrown at the + /// application level. + /// + /// If the delegate returns true, the server/connection is considered to be + /// at fault, if it returns false, the exception is just passed on to the + /// caller. + /// + /// By default, returns true for instances of TTransportException and + /// TApplicationException, false otherwise. + bool delegate(Exception) rpcFaultFilter; + + /** + * Whether to keep trying to find a working client if all have failed in a + * row. + * + * Defaults to false. + */ + bool keepTrying() const @property { + return pool_.cycle; + } + + /// Ditto + void keepTrying(bool value) @property { + pool_.cycle = value; + } + + /** + * Whether to use a random permutation of the client pool on every call to + * execute(). This can be used e.g. as a simple form of load balancing. + * + * Defaults to true. + */ + bool permuteClients() const @property { + return pool_.permute; + } + + /// Ditto + void permuteClients(bool value) @property { + pool_.permute = value; + } + + /** + * The number of consecutive faults after which a client is disabled until + * faultDisableDuration has passed. 0 to never disable clients. + * + * Defaults to 0. + */ + ushort faultDisableCount() @property { + return pool_.faultDisableCount; + } + + /// Ditto + void faultDisableCount(ushort value) @property { + pool_.faultDisableCount = value; + } + + /** + * The duration for which a client is no longer considered after it has + * failed too often. + * + * Defaults to one second. + */ + Duration faultDisableDuration() @property { + return pool_.faultDisableDuration; + } + + /// Ditto + void faultDisableDuration(Duration value) @property { + pool_.faultDisableDuration = value; + } + +protected: + ResultType executeOnPool(ResultType)(scope ResultType delegate(Client) work) { + auto clients = pool_[]; + if (clients.empty) { + throw new TException("No clients available to try."); + } + + while (true) { + Exception[] rpcExceptions; + while (!clients.empty) { + auto c = clients.front; + clients.popFront; + try { + scope (success) { + pool_.recordSuccess(c); + } + + if (reopenTransports) { + c.inputProtocol.transport.open(); + c.outputProtocol.transport.open(); + } + + return work(c); + } catch (Exception e) { + if (rpcFaultFilter && rpcFaultFilter(e)) { + pool_.recordFault(c); + rpcExceptions ~= e; + } else { + // We are dealing with a normal exception thrown by the + // server-side method, just pass it on. As far as we are + // concerned, the method call succeeded. + pool_.recordSuccess(c); + throw e; + } + } + } + + // If we get here, no client succeeded during the current iteration. + Duration waitTime; + Client dummy; + if (clients.willBecomeNonempty(dummy, waitTime)) { + if (waitTime > dur!"hnsecs"(0)) { + import core.thread; + Thread.sleep(waitTime); + } + } else { + throw new TCompoundOperationException("All clients failed.", + rpcExceptions); + } + } + } + +private: + TResourcePool!Client pool_; +} + +private { + // Cannot use an anonymous delegate literal for this because they aren't + // allowed in class scope. + string poolForwardCode(Interface)() { + string code = ""; + + foreach (methodName; AllMemberMethodNames!Interface) { + enum qn = "Interface." ~ methodName; + code ~= "ReturnType!(" ~ qn ~ ") " ~ methodName ~ + "(ParameterTypeTuple!(" ~ qn ~ ") args) {\n"; + code ~= "return executeOnPool((Client c){ return c." ~ + methodName ~ "(args); });\n"; + code ~= "}\n"; + } + + return code; + } +} + +/** + * TClientPool construction helper to avoid having to explicitly specify + * the interface type, i.e. to allow the constructor being called using IFTI + * (see $(DMDBUG 6082, D Bugzilla enhancement requet 6082)). + */ +TClientPool!Interface tClientPool(Interface)( + TClientBase!Interface[] clients +) if (isService!Interface) { + return new typeof(return)(clients); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/codegen/idlgen.d b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/idlgen.d new file mode 100644 index 000000000..9f889368c --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/idlgen.d @@ -0,0 +1,770 @@ +/* + * 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. + */ + +/** + * Contains <b>experimental</b> functionality for generating Thrift IDL files + * (.thrift) from existing D data structures, i.e. the reverse of what the + * Thrift compiler does. + */ +module thrift.codegen.idlgen; + +import std.algorithm : find; +import std.array : empty, front; +import std.conv : to; +import std.traits : EnumMembers, isSomeFunction, OriginalType, + ParameterTypeTuple, ReturnType; +import std.typetuple : allSatisfy, staticIndexOf, staticMap, NoDuplicates, + TypeTuple; +import thrift.base; +import thrift.codegen.base; +import thrift.internal.codegen; +import thrift.internal.ctfe; +import thrift.util.hashset; + +/** + * True if the passed type is a Thrift entity (struct, exception, enum, + * service). + */ +alias Any!(isStruct, isException, isEnum, isService) isThriftEntity; + +/** + * Returns an IDL string describing the passed »root« entities and all types + * they depend on. + */ +template idlString(Roots...) if (allSatisfy!(isThriftEntity, Roots)) { + enum idlString = idlStringImpl!Roots.result; +} + +private { + template idlStringImpl(Roots...) if (allSatisfy!(isThriftEntity, Roots)) { + alias ForAllWithList!( + ConfinedTuple!(StaticFilter!(isService, Roots)), + AddBaseServices + ) Services; + + alias TypeTuple!( + StaticFilter!(isEnum, Roots), + ForAllWithList!( + ConfinedTuple!( + StaticFilter!(Any!(isException, isStruct), Roots), + staticMap!(CompositeTypeDeps, staticMap!(ServiceTypeDeps, Services)) + ), + AddStructWithDeps + ) + ) Types; + + enum result = ctfeJoin( + [ + staticMap!( + enumIdlString, + StaticFilter!(isEnum, Types) + ), + staticMap!( + structIdlString, + StaticFilter!(Any!(isStruct, isException), Types) + ), + staticMap!( + serviceIdlString, + Services + ) + ], + "\n" + ); + } + + template ServiceTypeDeps(T) if (isService!T) { + alias staticMap!( + PApply!(MethodTypeDeps, T), + FilterMethodNames!(T, __traits(derivedMembers, T)) + ) ServiceTypeDeps; + } + + template MethodTypeDeps(T, string name) if ( + isService!T && isSomeFunction!(MemberType!(T, name)) + ) { + alias TypeTuple!( + ReturnType!(MemberType!(T, name)), + ParameterTypeTuple!(MemberType!(T, name)), + ExceptionTypes!(T, name) + ) MethodTypeDeps; + } + + template ExceptionTypes(T, string name) if ( + isService!T && isSomeFunction!(MemberType!(T, name)) + ) { + mixin({ + enum meta = find!`a.name == b`(getMethodMeta!T, name); + if (meta.empty) return "alias TypeTuple!() ExceptionTypes;"; + + string result = "alias TypeTuple!("; + foreach (i, e; meta.front.exceptions) { + if (i > 0) result ~= ", "; + result ~= "mixin(`T." ~ e.type ~ "`)"; + } + result ~= ") ExceptionTypes;"; + return result; + }()); + } + + template AddBaseServices(T, List...) { + static if (staticIndexOf!(T, List) == -1) { + alias NoDuplicates!(BaseServices!T, List) AddBaseServices; + } else { + alias List AddStructWithDeps; + } + } + + unittest { + interface A {} + interface B : A {} + interface C : B {} + interface D : A {} + + static assert(is(AddBaseServices!(C) == TypeTuple!(A, B, C))); + static assert(is(ForAllWithList!(ConfinedTuple!(C, D), AddBaseServices) == + TypeTuple!(A, D, B, C))); + } + + template BaseServices(T, Rest...) if (isService!T) { + static if (isDerivedService!T) { + alias BaseServices!(BaseService!T, T, Rest) BaseServices; + } else { + alias TypeTuple!(T, Rest) BaseServices; + } + } + + template AddStructWithDeps(T, List...) { + static if (staticIndexOf!(T, List) == -1) { + // T is not already in the List, so add T and the types it depends on in + // the front. Because with the Thrift compiler types can only depend on + // other types that have already been defined, we collect all the + // dependencies, prepend them to the list, and then prune the duplicates + // (keeping the first occurrences). If this requirement should ever be + // dropped from Thrift, this could be easily adapted to handle circular + // dependencies by passing TypeTuple!(T, List) to ForAllWithList instead + // of appending List afterwards, and removing the now unnecessary + // NoDuplicates. + alias NoDuplicates!( + ForAllWithList!( + ConfinedTuple!( + staticMap!( + CompositeTypeDeps, + staticMap!( + PApply!(MemberType, T), + FieldNames!T + ) + ) + ), + .AddStructWithDeps, + T + ), + List + ) AddStructWithDeps; + } else { + alias List AddStructWithDeps; + } + } + + version (unittest) { + struct A {} + struct B { + A a; + int b; + A c; + string d; + } + struct C { + B b; + A a; + } + + static assert(is(AddStructWithDeps!C == TypeTuple!(A, B, C))); + + struct D { + C c; + mixin TStructHelpers!([TFieldMeta("c", 0, TReq.IGNORE)]); + } + static assert(is(AddStructWithDeps!D == TypeTuple!(D))); + } + + version (unittest) { + // Circles in the type dependency graph are not allowed in Thrift, but make + // sure we fail in a sane way instead of crashing the compiler. + + struct Rec1 { + Rec2[] other; + } + + struct Rec2 { + Rec1[] other; + } + + static assert(!__traits(compiles, AddStructWithDeps!Rec1)); + } + + /* + * Returns the non-primitive types T directly depends on. + * + * For example, CompositeTypeDeps!int would yield an empty type tuple, + * CompositeTypeDeps!SomeStruct would give SomeStruct, and + * CompositeTypeDeps!(A[B]) both CompositeTypeDeps!A and CompositeTypeDeps!B. + */ + template CompositeTypeDeps(T) { + static if (is(FullyUnqual!T == bool) || is(FullyUnqual!T == byte) || + is(FullyUnqual!T == short) || is(FullyUnqual!T == int) || + is(FullyUnqual!T == long) || is(FullyUnqual!T : string) || + is(FullyUnqual!T == double) || is(FullyUnqual!T == void) + ) { + alias TypeTuple!() CompositeTypeDeps; + } else static if (is(FullyUnqual!T _ : U[], U)) { + alias CompositeTypeDeps!U CompositeTypeDeps; + } else static if (is(FullyUnqual!T _ : HashSet!E, E)) { + alias CompositeTypeDeps!E CompositeTypeDeps; + } else static if (is(FullyUnqual!T _ : V[K], K, V)) { + alias TypeTuple!(CompositeTypeDeps!K, CompositeTypeDeps!V) CompositeTypeDeps; + } else static if (is(FullyUnqual!T == enum) || is(FullyUnqual!T == struct) || + is(FullyUnqual!T : TException) + ) { + alias TypeTuple!(FullyUnqual!T) CompositeTypeDeps; + } else { + static assert(false, "Cannot represent type in Thrift: " ~ T.stringof); + } + } +} + +/** + * Returns an IDL string describing the passed service. IDL code for any type + * dependcies is not included. + */ +template serviceIdlString(T) if (isService!T) { + enum serviceIdlString = { + string result = "service " ~ T.stringof; + static if (isDerivedService!T) { + result ~= " extends " ~ BaseService!T.stringof; + } + result ~= " {\n"; + + foreach (methodName; FilterMethodNames!(T, __traits(derivedMembers, T))) { + result ~= " "; + + enum meta = find!`a.name == b`(T.methodMeta, methodName); + + static if (!meta.empty && meta.front.type == TMethodType.ONEWAY) { + result ~= "oneway "; + } + + alias ReturnType!(MemberType!(T, methodName)) RT; + static if (is(RT == void)) { + // We special-case this here instead of adding void to dToIdlType to + // avoid accepting things like void[]. + result ~= "void "; + } else { + result ~= dToIdlType!RT ~ " "; + } + result ~= methodName ~ "("; + + short lastId; + foreach (i, ParamType; ParameterTypeTuple!(MemberType!(T, methodName))) { + static if (!meta.empty && i < meta.front.params.length) { + enum havePM = true; + } else { + enum havePM = false; + } + + short id; + static if (havePM) { + id = meta.front.params[i].id; + } else { + id = --lastId; + } + + string paramName; + static if (havePM) { + paramName = meta.front.params[i].name; + } else { + paramName = "param" ~ to!string(i + 1); + } + + result ~= to!string(id) ~ ": " ~ dToIdlType!ParamType ~ " " ~ paramName; + + static if (havePM && !meta.front.params[i].defaultValue.empty) { + result ~= " = " ~ dToIdlConst(mixin(meta.front.params[i].defaultValue)); + } else { + // Unfortunately, getting the default value for parameters from a + // function alias isn't possible – we can't transfer the default + // value to the IDL e.g. for interface Foo { void foo(int a = 5); } + // without the user explicitly declaring it in metadata. + } + result ~= ", "; + } + result ~= ")"; + + static if (!meta.empty && !meta.front.exceptions.empty) { + result ~= " throws ("; + foreach (e; meta.front.exceptions) { + result ~= to!string(e.id) ~ ": " ~ e.type ~ " " ~ e.name ~ ", "; + } + result ~= ")"; + } + + result ~= ",\n"; + } + + result ~= "}\n"; + return result; + }(); +} + +/** + * Returns an IDL string describing the passed enum. IDL code for any type + * dependcies is not included. + */ +template enumIdlString(T) if (isEnum!T) { + enum enumIdlString = { + static assert(is(OriginalType!T : long), + "Can only have integer enums in Thrift (not " ~ OriginalType!T.stringof ~ + ", for " ~ T.stringof ~ ")."); + + string result = "enum " ~ T.stringof ~ " {\n"; + + foreach (name; __traits(derivedMembers, T)) { + result ~= " " ~ name ~ " = " ~ dToIdlConst(GetMember!(T, name)) ~ ",\n"; + } + + result ~= "}\n"; + return result; + }(); +} + +/** + * Returns an IDL string describing the passed struct. IDL code for any type + * dependcies is not included. + */ +template structIdlString(T) if (isStruct!T || isException!T) { + enum structIdlString = { + mixin({ + string code = ""; + foreach (field; getFieldMeta!T) { + code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n"; + } + return code; + }()); + + string result; + static if (isException!T) { + result = "exception "; + } else { + result = "struct "; + } + result ~= T.stringof ~ " {\n"; + + // The last automatically assigned id – fields with no meta information + // are assigned (in lexical order) descending negative ids, starting with + // -1, just like the Thrift compiler does. + short lastId; + + foreach (name; FieldNames!T) { + enum meta = find!`a.name == b`(getFieldMeta!T, name); + + static if (meta.empty || meta.front.req != TReq.IGNORE) { + short id; + static if (meta.empty) { + id = --lastId; + } else { + id = meta.front.id; + } + + result ~= " " ~ to!string(id) ~ ":"; + static if (!meta.empty) { + result ~= dToIdlReq(meta.front.req); + } + result ~= " " ~ dToIdlType!(MemberType!(T, name)) ~ " " ~ name; + + static if (!meta.empty && !meta.front.defaultValue.empty) { + result ~= " = " ~ dToIdlConst(mixin(meta.front.defaultValue)); + } else static if (__traits(compiles, fieldInitA!(T, name))) { + static if (is(typeof(fieldInitA!(T, name))) && + !is(typeof(fieldInitA!(T, name)) == void) + ) { + result ~= " = " ~ dToIdlConst(fieldInitA!(T, name)); + } + } else static if (is(typeof(fieldInitB!(T, name))) && + !is(typeof(fieldInitB!(T, name)) == void) + ) { + result ~= " = " ~ dToIdlConst(fieldInitB!(T, name)); + } + result ~= ",\n"; + } + } + + result ~= "}\n"; + return result; + }(); +} + +private { + // This very convoluted way of doing things was chosen because putting the + // static if directly into structIdlString caused »not evaluatable at compile + // time« errors to slip through even though typeof() was used, resp. the + // condition to be true even though the value couldn't actually be read at + // compile time due to a @@BUG@@ in DMD 2.055. + // The extra »compiled« field in fieldInitA is needed because we must not try + // to use != if !is compiled as well (but was false), e.g. for floating point + // types. + template fieldInitA(T, string name) { + static if (mixin("T.init." ~ name) !is MemberType!(T, name).init) { + enum fieldInitA = mixin("T.init." ~ name); + } + } + + template fieldInitB(T, string name) { + static if (mixin("T.init." ~ name) != MemberType!(T, name).init) { + enum fieldInitB = mixin("T.init." ~ name); + } + } + + template dToIdlType(T) { + static if (is(FullyUnqual!T == bool)) { + enum dToIdlType = "bool"; + } else static if (is(FullyUnqual!T == byte)) { + enum dToIdlType = "byte"; + } else static if (is(FullyUnqual!T == double)) { + enum dToIdlType = "double"; + } else static if (is(FullyUnqual!T == short)) { + enum dToIdlType = "i16"; + } else static if (is(FullyUnqual!T == int)) { + enum dToIdlType = "i32"; + } else static if (is(FullyUnqual!T == long)) { + enum dToIdlType = "i64"; + } else static if (is(FullyUnqual!T : string)) { + enum dToIdlType = "string"; + } else static if (is(FullyUnqual!T _ : U[], U)) { + enum dToIdlType = "list<" ~ dToIdlType!U ~ ">"; + } else static if (is(FullyUnqual!T _ : V[K], K, V)) { + enum dToIdlType = "map<" ~ dToIdlType!K ~ ", " ~ dToIdlType!V ~ ">"; + } else static if (is(FullyUnqual!T _ : HashSet!E, E)) { + enum dToIdlType = "set<" ~ dToIdlType!E ~ ">"; + } else static if (is(FullyUnqual!T == struct) || is(FullyUnqual!T == enum) || + is(FullyUnqual!T : TException) + ) { + enum dToIdlType = FullyUnqual!(T).stringof; + } else { + static assert(false, "Cannot represent type in Thrift: " ~ T.stringof); + } + } + + string dToIdlReq(TReq req) { + switch (req) { + case TReq.REQUIRED: return " required"; + case TReq.OPTIONAL: return " optional"; + default: return ""; + } + } + + string dToIdlConst(T)(T value) { + static if (is(FullyUnqual!T == bool)) { + return value ? "1" : "0"; + } else static if (is(FullyUnqual!T == byte) || + is(FullyUnqual!T == short) || is(FullyUnqual!T == int) || + is(FullyUnqual!T == long) + ) { + return to!string(value); + } else static if (is(FullyUnqual!T : string)) { + return `"` ~ to!string(value) ~ `"`; + } else static if (is(FullyUnqual!T == double)) { + return ctfeToString(value); + } else static if (is(FullyUnqual!T _ : U[], U) || + is(FullyUnqual!T _ : HashSet!E, E) + ) { + string result = "["; + foreach (e; value) { + result ~= dToIdlConst(e) ~ ", "; + } + result ~= "]"; + return result; + } else static if (is(FullyUnqual!T _ : V[K], K, V)) { + string result = "{"; + foreach (key, val; value) { + result ~= dToIdlConst(key) ~ ": " ~ dToIdlConst(val) ~ ", "; + } + result ~= "}"; + return result; + } else static if (is(FullyUnqual!T == enum)) { + import std.conv; + import std.traits; + return to!string(cast(OriginalType!T)value); + } else static if (is(FullyUnqual!T == struct) || + is(FullyUnqual!T : TException) + ) { + string result = "{"; + foreach (name; __traits(derivedMembers, T)) { + static if (memberReq!(T, name) != TReq.IGNORE) { + result ~= name ~ ": " ~ dToIdlConst(mixin("value." ~ name)) ~ ", "; + } + } + result ~= "}"; + return result; + } else { + static assert(false, "Cannot represent type in Thrift: " ~ T.stringof); + } + } +} + +version (unittest) { + enum Foo { + a = 1, + b = 10, + c = 5 + } + + static assert(enumIdlString!Foo == +`enum Foo { + a = 1, + b = 10, + c = 5, +} +`); +} + + +version (unittest) { + struct WithoutMeta { + string a; + int b; + } + + struct WithDefaults { + string a = "asdf"; + double b = 3.1415; + WithoutMeta c; + + mixin TStructHelpers!([ + TFieldMeta("c", 1, TReq.init, `WithoutMeta("foo", 3)`) + ]); + } + + // These are from DebugProtoTest.thrift. + struct OneOfEach { + bool im_true; + bool im_false; + byte a_bite; + short integer16; + int integer32; + long integer64; + double double_precision; + string some_characters; + string zomg_unicode; + bool what_who; + string base64; + byte[] byte_list; + short[] i16_list; + long[] i64_list; + + mixin TStructHelpers!([ + TFieldMeta(`im_true`, 1), + TFieldMeta(`im_false`, 2), + TFieldMeta(`a_bite`, 3, TReq.OPT_IN_REQ_OUT, q{cast(byte)127}), + TFieldMeta(`integer16`, 4, TReq.OPT_IN_REQ_OUT, q{cast(short)32767}), + TFieldMeta(`integer32`, 5), + TFieldMeta(`integer64`, 6, TReq.OPT_IN_REQ_OUT, q{10000000000L}), + TFieldMeta(`double_precision`, 7), + TFieldMeta(`some_characters`, 8), + TFieldMeta(`zomg_unicode`, 9), + TFieldMeta(`what_who`, 10), + TFieldMeta(`base64`, 11), + TFieldMeta(`byte_list`, 12, TReq.OPT_IN_REQ_OUT, q{{ + byte[] v; + v ~= cast(byte)1; + v ~= cast(byte)2; + v ~= cast(byte)3; + return v; + }()}), + TFieldMeta(`i16_list`, 13, TReq.OPT_IN_REQ_OUT, q{{ + short[] v; + v ~= cast(short)1; + v ~= cast(short)2; + v ~= cast(short)3; + return v; + }()}), + TFieldMeta(`i64_list`, 14, TReq.OPT_IN_REQ_OUT, q{{ + long[] v; + v ~= 1L; + v ~= 2L; + v ~= 3L; + return v; + }()}) + ]); + } + + struct Bonk { + int type; + string message; + + mixin TStructHelpers!([ + TFieldMeta(`type`, 1), + TFieldMeta(`message`, 2) + ]); + } + + struct HolyMoley { + OneOfEach[] big; + HashSet!(string[]) contain; + Bonk[][string] bonks; + + mixin TStructHelpers!([ + TFieldMeta(`big`, 1), + TFieldMeta(`contain`, 2), + TFieldMeta(`bonks`, 3) + ]); + } + + static assert(structIdlString!WithoutMeta == +`struct WithoutMeta { + -1: string a, + -2: i32 b, +} +`); + +import std.algorithm; + static assert(structIdlString!WithDefaults.startsWith( +`struct WithDefaults { + -1: string a = "asdf", + -2: double b = 3.141`)); + + static assert(structIdlString!WithDefaults.endsWith( +`1: WithoutMeta c = {a: "foo", b: 3, }, +} +`)); + + static assert(structIdlString!OneOfEach == +`struct OneOfEach { + 1: bool im_true, + 2: bool im_false, + 3: byte a_bite = 127, + 4: i16 integer16 = 32767, + 5: i32 integer32, + 6: i64 integer64 = 10000000000, + 7: double double_precision, + 8: string some_characters, + 9: string zomg_unicode, + 10: bool what_who, + 11: string base64, + 12: list<byte> byte_list = [1, 2, 3, ], + 13: list<i16> i16_list = [1, 2, 3, ], + 14: list<i64> i64_list = [1, 2, 3, ], +} +`); + + static assert(structIdlString!Bonk == +`struct Bonk { + 1: i32 type, + 2: string message, +} +`); + + static assert(structIdlString!HolyMoley == +`struct HolyMoley { + 1: list<OneOfEach> big, + 2: set<list<string>> contain, + 3: map<string, list<Bonk>> bonks, +} +`); +} + +version (unittest) { + class ExceptionWithAMap : TException { + string blah; + string[string] map_field; + + mixin TStructHelpers!([ + TFieldMeta(`blah`, 1), + TFieldMeta(`map_field`, 2) + ]); + } + + interface Srv { + void voidMethod(); + int primitiveMethod(); + OneOfEach structMethod(); + void methodWithDefaultArgs(int something); + void onewayMethod(); + void exceptionMethod(); + + alias .ExceptionWithAMap ExceptionWithAMap; + + enum methodMeta = [ + TMethodMeta(`methodWithDefaultArgs`, + [TParamMeta(`something`, 1, q{2})] + ), + TMethodMeta(`onewayMethod`, + [], + [], + TMethodType.ONEWAY + ), + TMethodMeta(`exceptionMethod`, + [], + [ + TExceptionMeta("a", 1, "ExceptionWithAMap"), + TExceptionMeta("b", 2, "ExceptionWithAMap") + ] + ) + ]; + } + + interface ChildSrv : Srv { + int childMethod(int arg); + } + + static assert(idlString!ChildSrv == +`exception ExceptionWithAMap { + 1: string blah, + 2: map<string, string> map_field, +} + +struct OneOfEach { + 1: bool im_true, + 2: bool im_false, + 3: byte a_bite = 127, + 4: i16 integer16 = 32767, + 5: i32 integer32, + 6: i64 integer64 = 10000000000, + 7: double double_precision, + 8: string some_characters, + 9: string zomg_unicode, + 10: bool what_who, + 11: string base64, + 12: list<byte> byte_list = [1, 2, 3, ], + 13: list<i16> i16_list = [1, 2, 3, ], + 14: list<i64> i64_list = [1, 2, 3, ], +} + +service Srv { + void voidMethod(), + i32 primitiveMethod(), + OneOfEach structMethod(), + void methodWithDefaultArgs(1: i32 something = 2, ), + oneway void onewayMethod(), + void exceptionMethod() throws (1: ExceptionWithAMap a, 2: ExceptionWithAMap b, ), +} + +service ChildSrv extends Srv { + i32 childMethod(-1: i32 param1, ), +} +`); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/codegen/processor.d b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/processor.d new file mode 100644 index 000000000..5ce7ac605 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/codegen/processor.d @@ -0,0 +1,497 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.codegen.processor; + +import std.algorithm : find; +import std.array : empty, front; +import std.conv : to; +import std.traits : ParameterTypeTuple, ReturnType, Unqual; +import std.typetuple : allSatisfy, TypeTuple; +import std.variant : Variant; +import thrift.base; +import thrift.codegen.base; +import thrift.internal.codegen; +import thrift.internal.ctfe; +import thrift.protocol.base; +import thrift.protocol.processor; + +/** + * Service processor for Interface, which implements TProcessor by + * synchronously forwarding requests for the service methods to a handler + * implementing Interface. + * + * The generated class implements TProcessor and additionally allows a + * TProcessorEventHandler to be specified via the public eventHandler property. + * The constructor takes a single argument of type Interface, which is the + * handler to forward the requests to: + * --- + * this(Interface iface); + * TProcessorEventHandler eventHandler; + * --- + * + * If Interface is derived from another service BaseInterface, this class is + * also derived from TServiceProcessor!BaseInterface. + * + * The optional Protocols template tuple parameter can be used to specify + * one or more TProtocol implementations to specifically generate code for. If + * the actual types of the protocols passed to process() at runtime match one + * of the items from the list, the optimized code paths are taken, otherwise, + * a generic TProtocol version is used as fallback. For cases where the input + * and output protocols differ, TProtocolPair!(InputProtocol, OutputProtocol) + * can be used in the Protocols list: + * --- + * interface FooService { void foo(); } + * class FooImpl { override void foo {} } + * + * // Provides fast path if TBinaryProtocol!TBufferedTransport is used for + * // both input and output: + * alias TServiceProcessor!(FooService, TBinaryProtocol!TBufferedTransport) + * BinaryProcessor; + * + * auto proc = new BinaryProcessor(new FooImpl()); + * + * // Low overhead. + * proc.process(tBinaryProtocol(tBufferTransport(someSocket))); + * + * // Not in the specialization list – higher overhead. + * proc.process(tBinaryProtocol(tFramedTransport(someSocket))); + * + * // Same as above, but optimized for the Compact protocol backed by a + * // TPipedTransport for input and a TBufferedTransport for output. + * alias TServiceProcessor!(FooService, TProtocolPair!( + * TCompactProtocol!TPipedTransport, TCompactProtocol!TBufferedTransport) + * ) MixedProcessor; + * --- + */ +template TServiceProcessor(Interface, Protocols...) if ( + isService!Interface && allSatisfy!(isTProtocolOrPair, Protocols) +) { + mixin({ + static if (is(Interface BaseInterfaces == super) && BaseInterfaces.length > 0) { + static assert(BaseInterfaces.length == 1, + "Services cannot be derived from more than one parent."); + + string code = "class TServiceProcessor : " ~ + "TServiceProcessor!(BaseService!Interface) {\n"; + code ~= "private Interface iface_;\n"; + + string constructorCode = "this(Interface iface) {\n"; + constructorCode ~= "super(iface);\n"; + constructorCode ~= "iface_ = iface;\n"; + } else { + string code = "class TServiceProcessor : TProcessor {"; + code ~= q{ + override bool process(TProtocol iprot, TProtocol oprot, + Variant context = Variant() + ) { + auto msg = iprot.readMessageBegin(); + + void writeException(TApplicationException e) { + oprot.writeMessageBegin(TMessage(msg.name, TMessageType.EXCEPTION, + msg.seqid)); + e.write(oprot); + oprot.writeMessageEnd(); + oprot.transport.writeEnd(); + oprot.transport.flush(); + } + + if (msg.type != TMessageType.CALL && msg.type != TMessageType.ONEWAY) { + skip(iprot, TType.STRUCT); + iprot.readMessageEnd(); + iprot.transport.readEnd(); + + writeException(new TApplicationException( + TApplicationException.Type.INVALID_MESSAGE_TYPE)); + return false; + } + + auto dg = msg.name in processMap_; + if (!dg) { + skip(iprot, TType.STRUCT); + iprot.readMessageEnd(); + iprot.transport.readEnd(); + + writeException(new TApplicationException("Invalid method name: '" ~ + msg.name ~ "'.", TApplicationException.Type.INVALID_MESSAGE_TYPE)); + + return false; + } + + (*dg)(msg.seqid, iprot, oprot, context); + return true; + } + + TProcessorEventHandler eventHandler; + + alias void delegate(int, TProtocol, TProtocol, Variant) ProcessFunc; + protected ProcessFunc[string] processMap_; + private Interface iface_; + }; + + string constructorCode = "this(Interface iface) {\n"; + constructorCode ~= "iface_ = iface;\n"; + } + + // Generate the handling code for each method, consisting of the dispatch + // function, registering it in the constructor, and the actual templated + // handler function. + foreach (methodName; + FilterMethodNames!(Interface, __traits(derivedMembers, Interface)) + ) { + // Register the processing function in the constructor. + immutable procFuncName = "process_" ~ methodName; + immutable dispatchFuncName = procFuncName ~ "_protocolDispatch"; + constructorCode ~= "processMap_[`" ~ methodName ~ "`] = &" ~ + dispatchFuncName ~ ";\n"; + + bool methodMetaFound; + TMethodMeta methodMeta; + static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) { + enum meta = find!`a.name == b`(Interface.methodMeta, methodName); + if (!meta.empty) { + methodMetaFound = true; + methodMeta = meta.front; + } + } + + // The dispatch function to call the specialized handler functions. We + // test the protocols if they can be converted to one of the passed + // protocol types, and if not, fall back to the generic TProtocol + // version of the processing function. + code ~= "void " ~ dispatchFuncName ~ + "(int seqid, TProtocol iprot, TProtocol oprot, Variant context) {\n"; + code ~= "foreach (Protocol; TypeTuple!(Protocols, TProtocol)) {\n"; + code ~= q{ + static if (is(Protocol _ : TProtocolPair!(I, O), I, O)) { + alias I IProt; + alias O OProt; + } else { + alias Protocol IProt; + alias Protocol OProt; + } + auto castedIProt = cast(IProt)iprot; + auto castedOProt = cast(OProt)oprot; + }; + code ~= "if (castedIProt && castedOProt) {\n"; + code ~= procFuncName ~ + "!(IProt, OProt)(seqid, castedIProt, castedOProt, context);\n"; + code ~= "return;\n"; + code ~= "}\n"; + code ~= "}\n"; + code ~= "throw new TException(`Internal error: Null iprot/oprot " ~ + "passed to processor protocol dispatch function.`);\n"; + code ~= "}\n"; + + // The actual handler function, templated on the input and output + // protocol types. + code ~= "void " ~ procFuncName ~ "(IProt, OProt)(int seqid, IProt " ~ + "iprot, OProt oprot, Variant connectionContext) " ~ + "if (isTProtocol!IProt && isTProtocol!OProt) {\n"; + code ~= "TArgsStruct!(Interface, `" ~ methodName ~ "`) args;\n"; + + // Store the (qualified) method name in a manifest constant to avoid + // having to litter the code below with lots of string manipulation. + code ~= "enum methodName = `" ~ methodName ~ "`;\n"; + + code ~= q{ + enum qName = Interface.stringof ~ "." ~ methodName; + + Variant callContext; + if (eventHandler) { + callContext = eventHandler.createContext(qName, connectionContext); + } + + scope (exit) { + if (eventHandler) { + eventHandler.deleteContext(callContext, qName); + } + } + + if (eventHandler) eventHandler.preRead(callContext, qName); + + args.read(iprot); + iprot.readMessageEnd(); + iprot.transport.readEnd(); + + if (eventHandler) eventHandler.postRead(callContext, qName); + }; + + code ~= "TResultStruct!(Interface, `" ~ methodName ~ "`) result;\n"; + code ~= "try {\n"; + + // Generate the parameter list to pass to the called iface function. + string[] paramList; + foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) { + string paramName; + if (methodMetaFound && i < methodMeta.params.length) { + paramName = methodMeta.params[i].name; + } else { + paramName = "param" ~ to!string(i + 1); + } + paramList ~= "args." ~ paramName; + } + + immutable call = "iface_." ~ methodName ~ "(" ~ ctfeJoin(paramList) ~ ")"; + if (is(ReturnType!(mixin("Interface." ~ methodName)) == void)) { + code ~= call ~ ";\n"; + } else { + code ~= "result.set!`success`(" ~ call ~ ");\n"; + } + + // If this is not a oneway method, generate the receiving code. + if (!methodMetaFound || methodMeta.type != TMethodType.ONEWAY) { + if (methodMetaFound) { + foreach (e; methodMeta.exceptions) { + code ~= "} catch (Interface." ~ e.type ~ " " ~ e.name ~ ") {\n"; + code ~= "result.set!`" ~ e.name ~ "`(" ~ e.name ~ ");\n"; + } + } + code ~= "}\n"; + + code ~= q{ + catch (Exception e) { + if (eventHandler) { + eventHandler.handlerError(callContext, qName, e); + } + + auto x = new TApplicationException(to!string(e)); + oprot.writeMessageBegin( + TMessage(methodName, TMessageType.EXCEPTION, seqid)); + x.write(oprot); + oprot.writeMessageEnd(); + oprot.transport.writeEnd(); + oprot.transport.flush(); + return; + } + + if (eventHandler) eventHandler.preWrite(callContext, qName); + + oprot.writeMessageBegin(TMessage(methodName, + TMessageType.REPLY, seqid)); + result.write(oprot); + oprot.writeMessageEnd(); + oprot.transport.writeEnd(); + oprot.transport.flush(); + + if (eventHandler) eventHandler.postWrite(callContext, qName); + }; + } else { + // For oneway methods, we obviously cannot notify the client of any + // exceptions, just call the event handler if one is set. + code ~= "}\n"; + code ~= q{ + catch (Exception e) { + if (eventHandler) { + eventHandler.handlerError(callContext, qName, e); + } + return; + } + + if (eventHandler) eventHandler.onewayComplete(callContext, qName); + }; + } + code ~= "}\n"; + + } + + code ~= constructorCode ~ "}\n"; + code ~= "}\n"; + + return code; + }()); +} + +/** + * A struct representing the arguments of a Thrift method call. + * + * There should usually be no reason to use this directly without the help of + * TServiceProcessor, but it is documented publicly to help debugging in case + * of CTFE errors. + * + * Consider this example: + * --- + * interface Foo { + * int bar(string a, bool b); + * + * enum methodMeta = [ + * TMethodMeta("bar", [TParamMeta("a", 1), TParamMeta("b", 2)]) + * ]; + * } + * + * alias TArgsStruct!(Foo, "bar") FooBarArgs; + * --- + * + * The definition of FooBarArgs is equivalent to: + * --- + * struct FooBarArgs { + * string a; + * bool b; + * + * mixin TStructHelpers!([TFieldMeta("a", 1, TReq.OPT_IN_REQ_OUT), + * TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT)]); + * } + * --- + * + * If the TVerboseCodegen version is defined, a warning message is issued at + * compilation if no TMethodMeta for Interface.methodName is found. + */ +template TArgsStruct(Interface, string methodName) { + static assert(is(typeof(mixin("Interface." ~ methodName))), + "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'."); + mixin({ + bool methodMetaFound; + TMethodMeta methodMeta; + static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) { + auto meta = find!`a.name == b`(Interface.methodMeta, methodName); + if (!meta.empty) { + methodMetaFound = true; + methodMeta = meta.front; + } + } + + string memberCode; + string[] fieldMetaCodes; + foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) { + // If we have no meta information, just use param1, param2, etc. as + // field names, it shouldn't really matter anyway. 1-based »indexing« + // is used to match the common scheme in the Thrift world. + string memberId; + string memberName; + if (methodMetaFound && i < methodMeta.params.length) { + memberId = to!string(methodMeta.params[i].id); + memberName = methodMeta.params[i].name; + } else { + memberId = to!string(i + 1); + memberName = "param" ~ to!string(i + 1); + } + + // Unqual!() is needed to generate mutable fields for ref const() + // struct parameters. + memberCode ~= "Unqual!(ParameterTypeTuple!(Interface." ~ methodName ~ + ")[" ~ to!string(i) ~ "])" ~ memberName ~ ";\n"; + + fieldMetaCodes ~= "TFieldMeta(`" ~ memberName ~ "`, " ~ memberId ~ + ", TReq.OPT_IN_REQ_OUT)"; + } + + string code = "struct TArgsStruct {\n"; + code ~= memberCode; + version (TVerboseCodegen) { + if (!methodMetaFound && + ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0) + { + code ~= "pragma(msg, `[thrift.codegen.processor.TArgsStruct] Warning: No " ~ + "meta information for method '" ~ methodName ~ "' in service '" ~ + Interface.stringof ~ "' found.`);\n"; + } + } + immutable fieldMetaCode = + fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]"; + code ~= "mixin TStructHelpers!(" ~ fieldMetaCode ~ ");\n"; + code ~= "}\n"; + return code; + }()); +} + +/** + * A struct representing the result of a Thrift method call. + * + * It contains a field called "success" for the return value of the function + * (with id 0), and additional fields for the exceptions declared for the + * method, if any. + * + * There should usually be no reason to use this directly without the help of + * TServiceProcessor, but it is documented publicly to help debugging in case + * of CTFE errors. + * + * Consider the following example: + * --- + * interface Foo { + * int bar(string a); + * + * alias .FooException FooException; + * + * enum methodMeta = [ + * TMethodMeta("bar", + * [TParamMeta("a", 1)], + * [TExceptionMeta("fooe", 1, "FooException")] + * ) + * ]; + * } + * alias TResultStruct!(Foo, "bar") FooBarResult; + * --- + * + * The definition of FooBarResult is equivalent to: + * --- + * struct FooBarResult { + * int success; + * FooException fooe; + * + * mixin(TStructHelpers!([TFieldMeta("success", 0, TReq.OPTIONAL), + * TFieldMeta("fooe", 1, TReq.OPTIONAL)])); + * } + * --- + * + * If the TVerboseCodegen version is defined, a warning message is issued at + * compilation if no TMethodMeta for Interface.methodName is found. + */ +template TResultStruct(Interface, string methodName) { + static assert(is(typeof(mixin("Interface." ~ methodName))), + "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'."); + + mixin({ + string code = "struct TResultStruct {\n"; + + string[] fieldMetaCodes; + + static if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) { + code ~= "ReturnType!(Interface." ~ methodName ~ ") success;\n"; + fieldMetaCodes ~= "TFieldMeta(`success`, 0, TReq.OPTIONAL)"; + } + + bool methodMetaFound; + static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) { + auto meta = find!`a.name == b`(Interface.methodMeta, methodName); + if (!meta.empty) { + foreach (e; meta.front.exceptions) { + code ~= "Interface." ~ e.type ~ " " ~ e.name ~ ";\n"; + fieldMetaCodes ~= "TFieldMeta(`" ~ e.name ~ "`, " ~ to!string(e.id) ~ + ", TReq.OPTIONAL)"; + } + methodMetaFound = true; + } + } + + version (TVerboseCodegen) { + if (!methodMetaFound && + ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0) + { + code ~= "pragma(msg, `[thrift.codegen.processor.TResultStruct] Warning: No " ~ + "meta information for method '" ~ methodName ~ "' in service '" ~ + Interface.stringof ~ "' found.`);\n"; + } + } + + immutable fieldMetaCode = + fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]"; + code ~= "mixin TStructHelpers!(" ~ fieldMetaCode ~ ");\n"; + code ~= "}\n"; + return code; + }()); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/index.d b/src/jaegertracing/thrift/lib/d/src/thrift/index.d new file mode 100644 index 000000000..12914b625 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/index.d @@ -0,0 +1,33 @@ +Ddoc + +<h2>Package overview</h2> + +<dl> + <dt>$(D_CODE thrift.async)</dt> + <dd>Support infrastructure for handling client-side asynchronous operations using non-blocking I/O and coroutines.</dd> + + <dt>$(D_CODE thrift.codegen)</dt> + <dd> + <p>Templates used for generating Thrift clients/processors from regular D struct and interface definitions.</p> + <p><strong>Note:</strong> Several artifacts in these modules have options for specifying the exact protocol types used. In this case, the amount of virtual calls can be greatly reduced and as a result, the code also can be optimized better. If performance is not a concern or the actual protocol type is not known at compile time, these parameters can just be left at their defaults. + </p> + </dd> + + <dt>$(D_CODE thrift.internal)</dt> + <dd>Internal helper modules used by the Thrift library. This package is not part of the public API, and no stability guarantees are given whatsoever.</dd> + + <dt>$(D_CODE thrift.protocol)</dt> + <dd>The Thrift protocol implemtations which specify how to pass messages over a TTransport.</dd> + + <dt>$(D_CODE thrift.server)</dt> + <dd>Generic Thrift server implementations handling clients over a TTransport interface and forwarding requests to a TProcessor (which is in turn usually provided by thrift.codegen).</dd> + + <dt>$(D_CODE thrift.transport)</dt> + <dd>The TTransport data source/sink interface used in the Thrift library and its imiplementations.</dd> + + <dt>$(D_CODE thrift.util)</dt> + <dd>General-purpose utility modules not specific to Thrift, part of the public API.</dd> +</dl> + +Macros: + TITLE = Thrift D Software Library diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/algorithm.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/algorithm.d new file mode 100644 index 000000000..0938ac269 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/algorithm.d @@ -0,0 +1,55 @@ +/** + * Contains a modified version of std.algorithm.remove that doesn't take an + * alias parameter to avoid DMD @@BUG6395@@. + */ +module thrift.internal.algorithm; + +import std.algorithm : move; +import std.exception; +import std.functional; +import std.range; +import std.traits; + +enum SwapStrategy +{ + unstable, + semistable, + stable, +} + +Range removeEqual(SwapStrategy s = SwapStrategy.stable, Range, E)(Range range, E e) +if (isBidirectionalRange!Range) +{ + auto result = range; + static if (s != SwapStrategy.stable) + { + for (;!range.empty;) + { + if (range.front !is e) + { + range.popFront; + continue; + } + move(range.back, range.front); + range.popBack; + result.popBack; + } + } + else + { + auto tgt = range; + for (; !range.empty; range.popFront) + { + if (range.front is e) + { + // yank this guy + result.popBack; + continue; + } + // keep this guy + move(range.front, tgt.front); + tgt.popFront; + } + } + return result; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/codegen.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/codegen.d new file mode 100644 index 000000000..85f9d1891 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/codegen.d @@ -0,0 +1,451 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module thrift.internal.codegen; + +import std.algorithm : canFind; +import std.traits : InterfacesTuple, isSomeFunction, isSomeString; +import std.typetuple : staticIndexOf, staticMap, NoDuplicates, TypeTuple; +import thrift.codegen.base; + +/** + * Removes all type qualifiers from T. + * + * In contrast to std.traits.Unqual, FullyUnqual also removes qualifiers from + * array elements (e.g. immutable(byte[]) -> byte[], not immutable(byte)[]), + * excluding strings (string isn't reduced to char[]). + */ +template FullyUnqual(T) { + static if (is(T _ == const(U), U)) { + alias FullyUnqual!U FullyUnqual; + } else static if (is(T _ == immutable(U), U)) { + alias FullyUnqual!U FullyUnqual; + } else static if (is(T _ == shared(U), U)) { + alias FullyUnqual!U FullyUnqual; + } else static if (is(T _ == U[], U) && !isSomeString!T) { + alias FullyUnqual!(U)[] FullyUnqual; + } else static if (is(T _ == V[K], K, V)) { + alias FullyUnqual!(V)[FullyUnqual!K] FullyUnqual; + } else { + alias T FullyUnqual; + } +} + +/** + * true if null can be assigned to the passed type, false if not. + */ +template isNullable(T) { + enum isNullable = __traits(compiles, { T t = null; }); +} + +template isStruct(T) { + enum isStruct = is(T == struct); +} + +template isException(T) { + enum isException = is(T : Exception); +} + +template isEnum(T) { + enum isEnum = is(T == enum); +} + +/** + * Aliases itself to T.name. + */ +template GetMember(T, string name) { + mixin("alias T." ~ name ~ " GetMember;"); +} + +/** + * Aliases itself to typeof(symbol). + */ +template TypeOf(alias symbol) { + alias typeof(symbol) TypeOf; +} + +/** + * Aliases itself to the type of the T member called name. + */ +alias Compose!(TypeOf, GetMember) MemberType; + +/** + * Returns the field metadata array for T if any, or an empty array otherwise. + */ +template getFieldMeta(T) if (isStruct!T || isException!T) { + static if (is(typeof(T.fieldMeta) == TFieldMeta[])) { + enum getFieldMeta = T.fieldMeta; + } else { + enum TFieldMeta[] getFieldMeta = []; + } +} + +/** + * Merges the field metadata array for D with the passed array. + */ +template mergeFieldMeta(T, alias fieldMetaData = cast(TFieldMeta[])null) { + // Note: We don't use getFieldMeta here to avoid bug if it is instantiated + // from TIsSetFlags, see comment there. + static if (is(typeof(T.fieldMeta) == TFieldMeta[])) { + enum mergeFieldMeta = T.fieldMeta ~ fieldMetaData; + } else { + enum TFieldMeta[] mergeFieldMeta = fieldMetaData; + } +} + +/** + * Returns the field requirement level for T.name. + */ +template memberReq(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) { + enum memberReq = memberReqImpl!(T, name, fieldMetaData).result; +} + +private { + import std.algorithm : find; + // DMD @@BUG@@: Missing import leads to failing build without error + // message in unittest/debug/thrift/codegen/async_client. + import std.array : empty, front; + + template memberReqImpl(T, string name, alias fieldMetaData) { + enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name); + static if (meta.empty || meta.front.req == TReq.AUTO) { + static if (isNullable!(MemberType!(T, name))) { + enum result = TReq.OPTIONAL; + } else { + enum result = TReq.REQUIRED; + } + } else { + enum result = meta.front.req; + } + } +} + + +template notIgnored(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) { + enum notIgnored = memberReq!(T, name, fieldMetaData) != TReq.IGNORE; +} + +/** + * Returns the method metadata array for T if any, or an empty array otherwise. + */ +template getMethodMeta(T) if (isService!T) { + static if (is(typeof(T.methodMeta) == TMethodMeta[])) { + enum getMethodMeta = T.methodMeta; + } else { + enum TMethodMeta[] getMethodMeta = []; + } +} + + +/** + * true if T.name is a member variable. Exceptions include methods, static + * members, artifacts like package aliases, … + */ +template isValueMember(T, string name) { + static if (!is(MemberType!(T, name))) { + enum isValueMember = false; + } else static if ( + is(MemberType!(T, name) == void) || + isSomeFunction!(MemberType!(T, name)) || + __traits(compiles, { return mixin("T." ~ name); }()) + ) { + enum isValueMember = false; + } else { + enum isValueMember = true; + } +} + +/** + * Returns a tuple containing the names of the fields of T, not including + * inherited fields. If a member is marked as TReq.IGNORE, it is not included + * as well. + */ +template FieldNames(T, alias fieldMetaData = cast(TFieldMeta[])null) { + alias StaticFilter!( + All!( + doesNotReadMembers, + PApply!(isValueMember, T), + PApply!(notIgnored, T, PApplySkip, fieldMetaData) + ), + __traits(derivedMembers, T) + ) FieldNames; +} + +/* + * true if the passed member name is not a method generated by the + * TStructHelpers template that in its implementations queries the struct + * members. + * + * Kludge used internally to break a cycle caused a DMD forward reference + * regression, see THRIFT-2130. + */ +enum doesNotReadMembers(string name) = !["opEquals", "thriftOpEqualsImpl", + "toString", "thriftToStringImpl"].canFind(name); + +template derivedMembers(T) { + alias TypeTuple!(__traits(derivedMembers, T)) derivedMembers; +} + +template AllMemberMethodNames(T) if (isService!T) { + alias NoDuplicates!( + FilterMethodNames!( + T, + staticMap!( + derivedMembers, + TypeTuple!(T, InterfacesTuple!T) + ) + ) + ) AllMemberMethodNames; +} + +template FilterMethodNames(T, MemberNames...) { + alias StaticFilter!( + CompilesAndTrue!( + Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T)) + ), + MemberNames + ) FilterMethodNames; +} + +/** + * Returns a type tuple containing only the elements of T for which the + * eponymous template predicate pred is true. + * + * Example: + * --- + * alias StaticFilter!(isIntegral, int, string, long, float[]) Filtered; + * static assert(is(Filtered == TypeTuple!(int, long))); + * --- + */ +template StaticFilter(alias pred, T...) { + static if (T.length == 0) { + alias TypeTuple!() StaticFilter; + } else static if (pred!(T[0])) { + alias TypeTuple!(T[0], StaticFilter!(pred, T[1 .. $])) StaticFilter; + } else { + alias StaticFilter!(pred, T[1 .. $]) StaticFilter; + } +} + +/** + * Binds the first n arguments of a template to a particular value (where n is + * the number of arguments passed to PApply). + * + * The passed arguments are always applied starting from the left. However, + * the special PApplySkip marker template can be used to indicate that an + * argument should be skipped, so that e.g. the first and third argument + * to a template can be fixed, but the second and remaining arguments would + * still be left undefined. + * + * Skipping a number of parameters, but not providing enough arguments to + * assign all of them during instantiation of the resulting template is an + * error. + * + * Example: + * --- + * struct Foo(T, U, V) {} + * alias PApply!(Foo, int, long) PartialFoo; + * static assert(is(PartialFoo!float == Foo!(int, long, float))); + * + * alias PApply!(Test, int, PApplySkip, float) SkippedTest; + * static assert(is(SkippedTest!long == Test!(int, long, float))); + * --- + */ +template PApply(alias Target, T...) { + template PApply(U...) { + alias Target!(PApplyMergeArgs!(ConfinedTuple!T, U).Result) PApply; + } +} + +/// Ditto. +template PApplySkip() {} + +private template PApplyMergeArgs(alias Preset, Args...) { + static if (Preset.length == 0) { + alias Args Result; + } else { + enum nextSkip = staticIndexOf!(PApplySkip, Preset.Tuple); + static if (nextSkip == -1) { + alias TypeTuple!(Preset.Tuple, Args) Result; + } else static if (Args.length == 0) { + // Have to use a static if clause instead of putting the condition + // directly into the assert to avoid DMD trying to access Args[0] + // nevertheless below. + static assert(false, + "PArgsSkip encountered, but no argument left to bind."); + } else { + alias TypeTuple!( + Preset.Tuple[0 .. nextSkip], + Args[0], + PApplyMergeArgs!( + ConfinedTuple!(Preset.Tuple[nextSkip + 1 .. $]), + Args[1 .. $] + ).Result + ) Result; + } + } +} + +unittest { + struct Test(T, U, V) {} + alias PApply!(Test, int, long) PartialTest; + static assert(is(PartialTest!float == Test!(int, long, float))); + + alias PApply!(Test, int, PApplySkip, float) SkippedTest; + static assert(is(SkippedTest!long == Test!(int, long, float))); + + alias PApply!(Test, int, PApplySkip, PApplySkip) TwoSkipped; + static assert(!__traits(compiles, TwoSkipped!long)); +} + + +/** + * Composes a number of templates. The result is a template equivalent to + * all the passed templates evaluated from right to left, akin to the + * mathematical function composition notation: Instantiating Compose!(A, B, C) + * is the same as instantiating A!(B!(C!(…))). + * + * This is especially useful for creating a template to use with staticMap/ + * StaticFilter, as demonstrated below. + * + * Example: + * --- + * template AllMethodNames(T) { + * alias StaticFilter!( + * CompilesAndTrue!( + * Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T)) + * ), + * __traits(allMembers, T) + * ) AllMethodNames; + * } + * + * pragma(msg, AllMethodNames!Object); + * --- + */ +template Compose(T...) { + static if (T.length == 0) { + template Compose(U...) { + alias U Compose; + } + } else { + template Compose(U...) { + alias Instantiate!(T[0], Instantiate!(.Compose!(T[1 .. $]), U)) Compose; + } + } +} + +/** + * Instantiates the given template with the given list of parameters. + * + * Used to work around syntactic limiations of D with regard to instantiating + * a template from a type tuple (e.g. T[0]!(...) is not valid) or a template + * returning another template (e.g. Foo!(Bar)!(Baz) is not allowed). + */ +template Instantiate(alias Template, Params...) { + alias Template!Params Instantiate; +} + +/** + * Combines several template predicates using logical AND, i.e. instantiating + * All!(a, b, c) with parameters P for some templates a, b, c is equivalent to + * a!P && b!P && c!P. + * + * The templates are evaluated from left to right, aborting evaluation in a + * shurt-cut manner if a false result is encountered, in which case the latter + * instantiations do not need to compile. + */ +template All(T...) { + static if (T.length == 0) { + template All(U...) { + enum All = true; + } + } else { + template All(U...) { + static if (Instantiate!(T[0], U)) { + alias Instantiate!(.All!(T[1 .. $]), U) All; + } else { + enum All = false; + } + } + } +} + +/** + * Combines several template predicates using logical OR, i.e. instantiating + * Any!(a, b, c) with parameters P for some templates a, b, c is equivalent to + * a!P || b!P || c!P. + * + * The templates are evaluated from left to right, aborting evaluation in a + * shurt-cut manner if a true result is encountered, in which case the latter + * instantiations do not need to compile. + */ +template Any(T...) { + static if (T.length == 0) { + template Any(U...) { + enum Any = false; + } + } else { + template Any(U...) { + static if (Instantiate!(T[0], U)) { + enum Any = true; + } else { + alias Instantiate!(.Any!(T[1 .. $]), U) Any; + } + } + } +} + +template ConfinedTuple(T...) { + alias T Tuple; + enum length = T.length; +} + +/* + * foreach (Item; Items) { + * List = Operator!(Item, List); + * } + * where Items is a ConfinedTuple and List is a type tuple. + */ +template ForAllWithList(alias Items, alias Operator, List...) if ( + is(typeof(Items.length) : size_t) +){ + static if (Items.length == 0) { + alias List ForAllWithList; + } else { + alias .ForAllWithList!( + ConfinedTuple!(Items.Tuple[1 .. $]), + Operator, + Operator!(Items.Tuple[0], List) + ) ForAllWithList; + } +} + +/** + * Wraps the passed template predicate so it returns true if it compiles and + * evaluates to true, false it it doesn't compile or evaluates to false. + */ +template CompilesAndTrue(alias T) { + template CompilesAndTrue(U...) { + static if (is(typeof(T!U) : bool)) { + enum bool CompilesAndTrue = T!U; + } else { + enum bool CompilesAndTrue = false; + } + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/ctfe.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/ctfe.d new file mode 100644 index 000000000..974db01e3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/ctfe.d @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module thrift.internal.ctfe; + +import std.conv : to; +import std.traits; + +/* + * Simple eager join() for strings, std.algorithm.join isn't CTFEable yet. + */ +string ctfeJoin(string[] strings, string separator = ", ") { + string result; + if (strings.length > 0) { + result ~= strings[0]; + foreach (s; strings[1..$]) { + result ~= separator ~ s; + } + } + return result; +} + +/* + * A very primitive to!string() implementation for floating point numbers that + * is evaluatable at compile time. + * + * There is a wealth of problems associated with the algorithm used (e.g. 5.0 + * prints as 4.999…, incorrect rounding, etc.), but a better alternative should + * be included with the D standard library instead of implementing it here. + */ +string ctfeToString(T)(T val) if (isFloatingPoint!T) { + if (val is T.nan) return "nan"; + if (val is T.infinity) return "inf"; + if (val is -T.infinity) return "-inf"; + if (val is 0.0) return "0"; + if (val is -0.0) return "-0"; + + auto b = val; + + string result; + if (b < 0) { + result ~= '-'; + b *= -1; + } + + short magnitude; + while (b >= 10) { + ++magnitude; + b /= 10; + } + while (b < 1) { + --magnitude; + b *= 10; + } + + foreach (i; 0 .. T.dig) { + if (i == 1) result ~= '.'; + + auto first = cast(ubyte)b; + result ~= to!string(first); + + b -= first; + import std.math; + if (b < pow(10.0, i - T.dig)) break; + b *= 10; + } + + if (magnitude != 0) result ~= "e" ~ to!string(magnitude); + return result; +} + +unittest { + import std.algorithm; + static assert(ctfeToString(double.infinity) == "inf"); + static assert(ctfeToString(-double.infinity) == "-inf"); + static assert(ctfeToString(double.nan) == "nan"); + static assert(ctfeToString(0.0) == "0"); + static assert(ctfeToString(-0.0) == "-0"); + static assert(ctfeToString(2.5) == "2.5"); + static assert(ctfeToString(3.1415).startsWith("3.141")); + static assert(ctfeToString(2e-200) == "2e-200"); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/endian.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/endian.d new file mode 100644 index 000000000..31b9814ef --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/endian.d @@ -0,0 +1,75 @@ +/* + * 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. + */ + +/* + * Simple helpers for handling typical byte order-related issues. + */ +module thrift.internal.endian; + +import core.bitop : bswap; +import std.traits : isIntegral; + +union IntBuf(T) { + ubyte[T.sizeof] bytes; + T value; +} + +T byteSwap(T)(T t) pure nothrow @trusted if (isIntegral!T) { + static if (T.sizeof == 2) { + return cast(T)((t & 0xff) << 8) | cast(T)((t & 0xff00) >> 8); + } else static if (T.sizeof == 4) { + return cast(T)bswap(cast(uint)t); + } else static if (T.sizeof == 8) { + return cast(T)byteSwap(cast(uint)(t & 0xffffffff)) << 32 | + cast(T)bswap(cast(uint)(t >> 32)); + } else static assert(false, "Type of size " ~ to!string(T.sizeof) ~ " not supported."); +} + +T doNothing(T)(T val) { return val; } + +version (BigEndian) { + alias doNothing hostToNet; + alias doNothing netToHost; + alias byteSwap hostToLe; + alias byteSwap leToHost; +} else { + alias byteSwap hostToNet; + alias byteSwap netToHost; + alias doNothing hostToLe; + alias doNothing leToHost; +} + +unittest { + import std.exception; + + IntBuf!short s; + s.bytes = [1, 2]; + s.value = byteSwap(s.value); + enforce(s.bytes == [2, 1]); + + IntBuf!int i; + i.bytes = [1, 2, 3, 4]; + i.value = byteSwap(i.value); + enforce(i.bytes == [4, 3, 2, 1]); + + IntBuf!long l; + l.bytes = [1, 2, 3, 4, 5, 6, 7, 8]; + l.value = byteSwap(l.value); + enforce(l.bytes == [8, 7, 6, 5, 4, 3, 2, 1]); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/resource_pool.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/resource_pool.d new file mode 100644 index 000000000..c0820a342 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/resource_pool.d @@ -0,0 +1,431 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.internal.resource_pool; + +import core.time : Duration, dur, TickDuration; +import std.algorithm : minPos, reduce, remove; +import std.array : array, empty; +import std.exception : enforce; +import std.conv : to; +import std.random : randomCover, rndGen; +import std.range : zip; +import thrift.internal.algorithm : removeEqual; + +/** + * A pool of resources, which can be iterated over, and where resources that + * have failed too often can be temporarily disabled. + * + * This class is oblivious to the actual resource type managed. + */ +final class TResourcePool(Resource) { + /** + * Constructs a new instance. + * + * Params: + * resources = The initial members of the pool. + */ + this(Resource[] resources) { + resources_ = resources; + } + + /** + * Adds a resource to the pool. + */ + void add(Resource resource) { + resources_ ~= resource; + } + + /** + * Removes a resource from the pool. + * + * Returns: Whether the resource could be found in the pool. + */ + bool remove(Resource resource) { + auto oldLength = resources_.length; + resources_ = removeEqual(resources_, resource); + return resources_.length < oldLength; + } + + /** + * Returns an »enriched« input range to iterate over the pool members. + */ + static struct Range { + /** + * Whether the range is empty. + * + * This is the case if all members of the pool have been popped (or skipped + * because they were disabled) and TResourcePool.cycle is false, or there + * is no element to return in cycle mode because all have been temporarily + * disabled. + */ + bool empty() @property { + // If no resources are in the pool, the range will never become non-empty. + if (resources_.empty) return true; + + // If we already got the next resource in the cache, it doesn't matter + // whether there are more. + if (cached_) return false; + + size_t examineCount; + if (parent_.cycle) { + // We want to check all the resources, but not iterate more than once + // to avoid spinning in a loop if nothing is available. + examineCount = resources_.length; + } else { + // When not in cycle mode, we just iterate the list exactly once. If all + // items have been consumed, the interval below is empty. + examineCount = resources_.length - nextIndex_; + } + + foreach (i; 0 .. examineCount) { + auto r = resources_[(nextIndex_ + i) % resources_.length]; + auto fi = r in parent_.faultInfos_; + + if (fi && fi.resetTime != fi.resetTime.init) { + if (fi.resetTime < parent_.getCurrentTick_()) { + // The timeout expired, remove the resource from the list and go + // ahead trying it. + parent_.faultInfos_.remove(r); + } else { + // The timeout didn't expire yet, try the next resource. + continue; + } + } + + cache_ = r; + cached_ = true; + nextIndex_ = nextIndex_ + i + 1; + return false; + } + + // If we get here, all resources are currently inactive or the non-cycle + // pool has been exhausted, so there is nothing we can do. + nextIndex_ = nextIndex_ + examineCount; + return true; + } + + /** + * Returns the first resource in the range. + */ + Resource front() @property { + enforce(!empty); + return cache_; + } + + /** + * Removes the first resource from the range. + * + * Usually, this is combined with a call to TResourcePool.recordSuccess() + * or recordFault(). + */ + void popFront() { + enforce(!empty); + cached_ = false; + } + + /** + * Returns whether the range will become non-empty at some point in the + * future, and provides additional information when this will happen and + * what will be the next resource. + * + * Makes only sense to call on empty ranges. + * + * Params: + * next = The next resource that will become available. + * waitTime = The duration until that resource will become available. + */ + bool willBecomeNonempty(out Resource next, out Duration waitTime) { + // If no resources are in the pool, the range will never become non-empty. + if (resources_.empty) return false; + + // If cycle mode is not enabled, a range never becomes non-empty after + // being empty once, because all the elements have already been + // used/skipped in order to become empty. + if (!parent_.cycle) return false; + + auto fi = parent_.faultInfos_; + auto nextPair = minPos!"a[1].resetTime < b[1].resetTime"( + zip(fi.keys, fi.values) + ).front; + + next = nextPair[0]; + waitTime = to!Duration(nextPair[1].resetTime - parent_.getCurrentTick_()); + + return true; + } + + private: + this(TResourcePool parent, Resource[] resources) { + parent_ = parent; + resources_ = resources; + } + + TResourcePool parent_; + + /// All available resources. We keep a copy of it as to not get confused + /// when resources are added to/removed from the parent pool. + Resource[] resources_; + + /// After we have determined the next element in empty(), we store it here. + Resource cache_; + + /// Whether there is currently something in the cache. + bool cached_; + + /// The index to start searching from at the next call to empty(). + size_t nextIndex_; + } + + /// Ditto + Range opSlice() { + auto res = resources_; + if (permute) { + res = array(randomCover(res, rndGen)); + } + return Range(this, res); + } + + /** + * Records a success for an operation on the given resource, cancelling a + * fault streak, if any. + */ + void recordSuccess(Resource resource) { + if (resource in faultInfos_) { + faultInfos_.remove(resource); + } + } + + /** + * Records a fault for the given resource. + * + * If a resource fails consecutively for more than faultDisableCount times, + * it is temporarily disabled (no longer considered) until + * faultDisableDuration has passed. + */ + void recordFault(Resource resource) { + auto fi = resource in faultInfos_; + + if (!fi) { + faultInfos_[resource] = FaultInfo(); + fi = resource in faultInfos_; + } + + ++fi.count; + if (fi.count >= faultDisableCount) { + // If the resource has hit the fault count limit, disable it for + // specified duration. + fi.resetTime = getCurrentTick_() + cast(TickDuration)faultDisableDuration; + } + } + + /** + * Whether to randomly permute the order of the resources in the pool when + * taking a range using opSlice(). + * + * This can be used e.g. as a simple form of load balancing. + */ + bool permute = true; + + /** + * Whether to keep iterating over the pool members after all have been + * returned/have failed once. + */ + bool cycle = false; + + /** + * The number of consecutive faults after which a resource is disabled until + * faultDisableDuration has passed. Zero to never disable resources. + * + * Defaults to zero. + */ + ushort faultDisableCount = 0; + + /** + * The duration for which a resource is no longer considered after it has + * failed too often. + * + * Defaults to one second. + */ + Duration faultDisableDuration = dur!"seconds"(1); + +private: + Resource[] resources_; + FaultInfo[Resource] faultInfos_; + + /// Function to get the current timestamp from some monotonic system clock. + /// + /// This is overridable to be able to write timing-insensitive unit tests. + /// The extra indirection should not matter much performance-wise compared to + /// the actual system call, and by its very nature thisshould not be on a hot + /// path anyway. + typeof(&TickDuration.currSystemTick) getCurrentTick_ = + &TickDuration.currSystemTick; +} + +private { + struct FaultInfo { + ushort count; + TickDuration resetTime; + } +} + +unittest { + auto pool = new TResourcePool!Object([]); + enforce(pool[].empty); + Object dummyRes; + Duration dummyDur; + enforce(!pool[].willBecomeNonempty(dummyRes, dummyDur)); +} + +unittest { + import std.datetime; + import thrift.base; + + auto a = new Object; + auto b = new Object; + auto c = new Object; + auto objs = [a, b, c]; + auto pool = new TResourcePool!Object(objs); + pool.permute = false; + + static Duration fakeClock; + pool.getCurrentTick_ = () => cast(TickDuration)fakeClock; + + Object dummyRes = void; + Duration dummyDur = void; + + { + auto r = pool[]; + + foreach (i, o; objs) { + enforce(!r.empty); + enforce(r.front == o); + r.popFront(); + } + + enforce(r.empty); + enforce(!r.willBecomeNonempty(dummyRes, dummyDur)); + } + + { + pool.faultDisableCount = 2; + + enforce(pool[].front == a); + pool.recordFault(a); + enforce(pool[].front == a); + pool.recordSuccess(a); + enforce(pool[].front == a); + pool.recordFault(a); + enforce(pool[].front == a); + pool.recordFault(a); + + auto r = pool[]; + enforce(r.front == b); + r.popFront(); + enforce(r.front == c); + r.popFront(); + enforce(r.empty); + enforce(!r.willBecomeNonempty(dummyRes, dummyDur)); + + fakeClock += 2.seconds; + // Not in cycle mode, has to be still empty after the timeouts expired. + enforce(r.empty); + enforce(!r.willBecomeNonempty(dummyRes, dummyDur)); + + foreach (o; objs) pool.recordSuccess(o); + } + + { + pool.faultDisableCount = 1; + + pool.recordFault(a); + pool.recordFault(b); + pool.recordFault(c); + + auto r = pool[]; + enforce(r.empty); + enforce(!r.willBecomeNonempty(dummyRes, dummyDur)); + + foreach (o; objs) pool.recordSuccess(o); + } + + pool.cycle = true; + + { + auto r = pool[]; + + foreach (o; objs ~ objs) { + enforce(!r.empty); + enforce(r.front == o); + r.popFront(); + } + } + + { + pool.faultDisableCount = 2; + + enforce(pool[].front == a); + pool.recordFault(a); + enforce(pool[].front == a); + pool.recordSuccess(a); + enforce(pool[].front == a); + pool.recordFault(a); + enforce(pool[].front == a); + pool.recordFault(a); + + auto r = pool[]; + enforce(r.front == b); + r.popFront(); + enforce(r.front == c); + r.popFront(); + enforce(r.front == b); + + fakeClock += 2.seconds; + + r.popFront(); + enforce(r.front == c); + + r.popFront(); + enforce(r.front == a); + + enforce(pool[].front == a); + + foreach (o; objs) pool.recordSuccess(o); + } + + { + pool.faultDisableCount = 1; + + pool.recordFault(a); + fakeClock += 1.msecs; + pool.recordFault(b); + fakeClock += 1.msecs; + pool.recordFault(c); + + auto r = pool[]; + enforce(r.empty); + + // Make sure willBecomeNonempty gets the order right. + enforce(r.willBecomeNonempty(dummyRes, dummyDur)); + enforce(dummyRes == a); + enforce(dummyDur > Duration.zero); + + foreach (o; objs) pool.recordSuccess(o); + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/socket.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/socket.d new file mode 100644 index 000000000..6ca0a970e --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/socket.d @@ -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. + */ + +/** + * Abstractions over OS-dependent socket functionality. + */ +module thrift.internal.socket; + +import std.conv : to; + +// FreeBSD and OS X return -1 and set ECONNRESET if socket was closed by +// the other side, we need to check for that before throwing an exception. +version (FreeBSD) { + enum connresetOnPeerShutdown = true; +} else version (OSX) { + enum connresetOnPeerShutdown = true; +} else { + enum connresetOnPeerShutdown = false; +} + +version (Win32) { + import std.c.windows.winsock : WSAGetLastError, WSAEINTR, WSAEWOULDBLOCK; + import std.windows.syserror : sysErrorString; + + // These are unfortunately not defined in std.c.windows.winsock, see + // http://msdn.microsoft.com/en-us/library/ms740668.aspx. + enum WSAECONNRESET = 10054; + enum WSAENOTCONN = 10057; + enum WSAETIMEDOUT = 10060; +} else { + import core.stdc.errno : errno, EAGAIN, ECONNRESET, EINPROGRESS, EINTR, + ENOTCONN, EPIPE; + import core.stdc.string : strerror; +} + +/* + * CONNECT_INPROGRESS_ERRNO: set by connect() for non-blocking sockets if the + * connection could not be immediately established. + * INTERRUPTED_ERRNO: set when blocking system calls are interrupted by + * signals or similar. + * TIMEOUT_ERRNO: set when a socket timeout has been exceeded. + * WOULD_BLOCK_ERRNO: set when send/recv would block on non-blocking sockets. + * + * isSocetCloseErrno(errno): returns true if errno indicates that the socket + * is logically in closed state now. + */ +version (Win32) { + alias WSAGetLastError getSocketErrno; + enum CONNECT_INPROGRESS_ERRNO = WSAEWOULDBLOCK; + enum INTERRUPTED_ERRNO = WSAEINTR; + enum TIMEOUT_ERRNO = WSAETIMEDOUT; + enum WOULD_BLOCK_ERRNO = WSAEWOULDBLOCK; + + bool isSocketCloseErrno(typeof(getSocketErrno()) errno) { + return (errno == WSAECONNRESET || errno == WSAENOTCONN); + } +} else { + alias errno getSocketErrno; + enum CONNECT_INPROGRESS_ERRNO = EINPROGRESS; + enum INTERRUPTED_ERRNO = EINTR; + enum WOULD_BLOCK_ERRNO = EAGAIN; + + // TODO: The C++ TSocket implementation mentions that EAGAIN can also be + // set (undocumentedly) in out of resource conditions; it would be a good + // idea to contact the original authors of the C++ code for details and adapt + // the code accordingly. + enum TIMEOUT_ERRNO = EAGAIN; + + bool isSocketCloseErrno(typeof(getSocketErrno()) errno) { + return (errno == EPIPE || errno == ECONNRESET || errno == ENOTCONN); + } +} + +string socketErrnoString(uint errno) { + version (Win32) { + return sysErrorString(errno); + } else { + return to!string(strerror(errno)); + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/ssl.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/ssl.d new file mode 100644 index 000000000..3af54b582 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/ssl.d @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.internal.ssl; + +import core.memory : GC; +import core.stdc.config; +import core.stdc.errno : errno; +import core.stdc.string : strerror; +import deimos.openssl.err; +import deimos.openssl.ssl; +import deimos.openssl.x509v3; +import std.array : empty, appender; +import std.conv : to; +import std.socket : Address; +import thrift.transport.ssl; + +/** + * Checks if the peer is authorized after the SSL handshake has been + * completed on the given conncetion and throws an TSSLException if not. + * + * Params: + * ssl = The SSL connection to check. + * accessManager = The access manager to check the peer againts. + * peerAddress = The (IP) address of the peer. + * hostName = The host name of the peer. + */ +void authorize(SSL* ssl, TAccessManager accessManager, + Address peerAddress, lazy string hostName +) { + alias TAccessManager.Decision Decision; + + auto rc = SSL_get_verify_result(ssl); + if (rc != X509_V_OK) { + throw new TSSLException("SSL_get_verify_result(): " ~ + to!string(X509_verify_cert_error_string(rc))); + } + + auto cert = SSL_get_peer_certificate(ssl); + if (cert is null) { + // Certificate is not present. + if (SSL_get_verify_mode(ssl) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) { + throw new TSSLException( + "Authorize: Required certificate not present."); + } + + // If we don't have an access manager set, we don't intend to authorize + // the client, so everything's fine. + if (accessManager) { + throw new TSSLException( + "Authorize: Certificate required for authorization."); + } + return; + } + + if (accessManager is null) { + // No access manager set, can return immediately as the cert is valid + // and all peers are authorized. + X509_free(cert); + return; + } + + // both certificate and access manager are present + auto decision = accessManager.verify(peerAddress); + + if (decision != Decision.SKIP) { + X509_free(cert); + if (decision != Decision.ALLOW) { + throw new TSSLException("Authorize: Access denied based on remote IP."); + } + return; + } + + // Check subjectAltName(s), if present. + auto alternatives = cast(STACK_OF!(GENERAL_NAME)*) + X509_get_ext_d2i(cert, NID_subject_alt_name, null, null); + if (alternatives != null) { + auto count = sk_GENERAL_NAME_num(alternatives); + for (int i = 0; decision == Decision.SKIP && i < count; i++) { + auto name = sk_GENERAL_NAME_value(alternatives, i); + if (name is null) { + continue; + } + auto data = ASN1_STRING_data(name.d.ia5); + auto length = ASN1_STRING_length(name.d.ia5); + switch (name.type) { + case GENERAL_NAME.GEN_DNS: + decision = accessManager.verify(hostName, cast(char[])data[0 .. length]); + break; + case GENERAL_NAME.GEN_IPADD: + decision = accessManager.verify(peerAddress, data[0 .. length]); + break; + default: + // Do nothing. + } + } + + // DMD @@BUG@@: Empty template arguments parens should not be needed. + sk_GENERAL_NAME_pop_free!()(alternatives, &GENERAL_NAME_free); + } + + // If we are alredy done, return. + if (decision != Decision.SKIP) { + X509_free(cert); + if (decision != Decision.ALLOW) { + throw new TSSLException("Authorize: Access denied."); + } + return; + } + + // Check commonName. + auto name = X509_get_subject_name(cert); + if (name !is null) { + X509_NAME_ENTRY* entry; + char* utf8; + int last = -1; + while (decision == Decision.SKIP) { + last = X509_NAME_get_index_by_NID(name, NID_commonName, last); + if (last == -1) + break; + entry = X509_NAME_get_entry(name, last); + if (entry is null) + continue; + auto common = X509_NAME_ENTRY_get_data(entry); + auto size = ASN1_STRING_to_UTF8(&utf8, common); + decision = accessManager.verify(hostName, utf8[0 .. size]); + CRYPTO_free(utf8); + } + } + X509_free(cert); + if (decision != Decision.ALLOW) { + throw new TSSLException("Authorize: Could not authorize peer."); + } +} + +/* + * OpenSSL error information used for storing D exceptions on the OpenSSL + * error stack. + */ +enum ERR_LIB_D_EXCEPTION = ERR_LIB_USER; +enum ERR_F_D_EXCEPTION = 0; // function id - what to use here? +enum ERR_R_D_EXCEPTION = 1234; // 99 and above are reserved for applications +enum ERR_FILE_D_EXCEPTION = "d_exception"; +enum ERR_LINE_D_EXCEPTION = 0; +enum ERR_FLAGS_D_EXCEPTION = 0; + +/** + * Returns an exception for the last. + * + * Params: + * location = An optional "location" to add to the error message (typically + * the last SSL API call). + */ +Exception getSSLException(string location = null, string clientFile = __FILE__, + size_t clientLine = __LINE__ +) { + // We can return either an exception saved from D BIO code, or a "true" + // OpenSSL error. Because there can possibly be more than one error on the + // error stack, we have to fetch all of them, and pick the last, i.e. newest + // one. We concatenate multiple successive OpenSSL error messages into a + // single one, but always just return the last D expcetion. + string message; // Probably better use an Appender here. + bool hadMessage; + Exception exception; + + void initMessage() { + message.destroy(); + hadMessage = false; + if (!location.empty) { + message ~= location; + message ~= ": "; + } + } + initMessage(); + + auto errn = errno; + + const(char)* file = void; + int line = void; + const(char)* data = void; + int flags = void; + c_ulong code = void; + while ((code = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) { + if (ERR_GET_REASON(code) == ERR_R_D_EXCEPTION) { + initMessage(); + GC.removeRoot(cast(void*)data); + exception = cast(Exception)data; + } else { + exception = null; + + if (hadMessage) { + message ~= ", "; + } + + auto reason = ERR_reason_error_string(code); + if (reason) { + message ~= "SSL error: " ~ to!string(reason); + } else { + message ~= "SSL error #" ~ to!string(code); + } + + hadMessage = true; + } + } + + // If the last item from the stack was a D exception, throw it. + if (exception) return exception; + + // We are dealing with an OpenSSL error that doesn't root in a D exception. + if (!hadMessage) { + // If we didn't get an actual error from the stack yet, try errno. + string errnString; + if (errn != 0) { + errnString = to!string(strerror(errn)); + } + if (errnString.empty) { + message ~= "Unknown error"; + } else { + message ~= errnString; + } + } + + message ~= "."; + return new TSSLException(message, clientFile, clientLine); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/ssl_bio.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/ssl_bio.d new file mode 100644 index 000000000..ae850275a --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/ssl_bio.d @@ -0,0 +1,190 @@ +/* + * 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. + */ + +/** + * Provides a SSL BIO implementation wrapping a Thrift transport. + * + * This way, SSL I/O can be relayed over Thrift transport without introducing + * an additional layer of buffering, especially for the non-blocking + * transports. + * + * For the Thrift transport incarnations of the SSL entities, "tt" is used as + * prefix for clarity. + */ +module thrift.internal.ssl_bio; + +import core.stdc.config; +import core.stdc.string : strlen; +import core.memory : GC; +import deimos.openssl.bio; +import deimos.openssl.err; +import thrift.base; +import thrift.internal.ssl; +import thrift.transport.base; + +/** + * Creates an SSL BIO object wrapping the given transport. + * + * Exceptions thrown by the transport are pushed onto the OpenSSL error stack, + * using the location/reason values from thrift.internal.ssl.ERR_*_D_EXCEPTION. + * + * The transport is assumed to be ready for reading and writing when the BIO + * functions are called, it is not opened by the implementation. + * + * Params: + * transport = The transport to wrap. + * closeTransport = Whether the close the transport when the SSL BIO is + * closed. + */ +BIO* createTTransportBIO(TTransport transport, bool closeTransport) { + auto result = BIO_new(cast(BIO_METHOD*)&ttBioMethod); + if (!result) return null; + + GC.addRoot(cast(void*)transport); + BIO_set_fd(result, closeTransport, cast(c_long)cast(void*)transport); + + return result; +} + +private { + // Helper to get the Thrift transport assigned with the given BIO. + TTransport trans(BIO* b) nothrow { + auto result = cast(TTransport)b.ptr; + assert(result); + return result; + } + + void setError(Exception e) nothrow { + ERR_put_error(ERR_LIB_D_EXCEPTION, ERR_F_D_EXCEPTION, ERR_R_D_EXCEPTION, + ERR_FILE_D_EXCEPTION, ERR_LINE_D_EXCEPTION); + try { GC.addRoot(cast(void*)e); } catch (Throwable) {} + ERR_set_error_data(cast(char*)e, ERR_FLAGS_D_EXCEPTION); + } + + extern(C) int ttWrite(BIO* b, const(char)* data, int length) nothrow { + assert(b); + if (!data || length <= 0) return 0; + try { + trans(b).write((cast(ubyte*)data)[0 .. length]); + return length; + } catch (Exception e) { + setError(e); + return -1; + } + } + + extern(C) int ttRead(BIO* b, char* data, int length) nothrow { + assert(b); + if (!data || length <= 0) return 0; + try { + return cast(int)trans(b).read((cast(ubyte*)data)[0 .. length]); + } catch (Exception e) { + setError(e); + return -1; + } + } + + extern(C) int ttPuts(BIO* b, const(char)* str) nothrow { + return ttWrite(b, str, cast(int)strlen(str)); + } + + extern(C) c_long ttCtrl(BIO* b, int cmd, c_long num, void* ptr) nothrow { + assert(b); + + switch (cmd) { + case BIO_C_SET_FD: + // Note that close flag and "fd" are actually reversed here because we + // need 64 bit width for the pointer – should probably drop BIO_set_fd + // altogether. + ttDestroy(b); + b.ptr = cast(void*)num; + b.shutdown = cast(int)ptr; + b.init_ = 1; + return 1; + case BIO_C_GET_FD: + if (!b.init_) return -1; + *(cast(void**)ptr) = b.ptr; + return cast(c_long)b.ptr; + case BIO_CTRL_GET_CLOSE: + return b.shutdown; + case BIO_CTRL_SET_CLOSE: + b.shutdown = cast(int)num; + return 1; + case BIO_CTRL_FLUSH: + try { + trans(b).flush(); + return 1; + } catch (Exception e) { + setError(e); + return -1; + } + case BIO_CTRL_DUP: + // Seems like we have nothing to do on duplication, but couldn't find + // any documentation if this actually ever happens during normal SSL + // usage. + return 1; + default: + return 0; + } + } + + extern(C) int ttCreate(BIO* b) nothrow { + assert(b); + b.init_ = 0; + b.num = 0; // User-defined number field, unused here. + b.ptr = null; + b.flags = 0; + return 1; + } + + extern(C) int ttDestroy(BIO* b) nothrow { + if (!b) return 0; + + int rc = 1; + if (b.shutdown) { + if (b.init_) { + try { + trans(b).close(); + GC.removeRoot(cast(void*)trans(b)); + b.ptr = null; + } catch (Exception e) { + setError(e); + rc = -1; + } + } + b.init_ = 0; + b.flags = 0; + } + + return rc; + } + + immutable BIO_METHOD ttBioMethod = { + BIO_TYPE_SOURCE_SINK, + "TTransport", + &ttWrite, + &ttRead, + &ttPuts, + null, // gets + &ttCtrl, + &ttCreate, + &ttDestroy, + null // callback_ctrl + }; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/test/protocol.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/test/protocol.d new file mode 100644 index 000000000..2d25154de --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/test/protocol.d @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.internal.test.protocol; + +import std.exception; +import thrift.transport.memory; +import thrift.protocol.base; + +version (unittest): + +void testContainerSizeLimit(Protocol)() if (isTProtocol!Protocol) { + auto buffer = new TMemoryBuffer; + auto prot = new Protocol(buffer); + + // Make sure reading fails if a container larger than the size limit is read. + prot.containerSizeLimit = 3; + + { + prot.writeListBegin(TList(TType.I32, 4)); + prot.writeI32(0); // Make sure size can be read e.g. for JSON protocol. + prot.reset(); + + auto e = cast(TProtocolException)collectException(prot.readListBegin()); + enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT); + prot.reset(); + buffer.reset(); + } + + { + prot.writeMapBegin(TMap(TType.I32, TType.I32, 4)); + prot.writeI32(0); // Make sure size can be read e.g. for JSON protocol. + prot.reset(); + + auto e = cast(TProtocolException)collectException(prot.readMapBegin()); + enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT); + prot.reset(); + buffer.reset(); + } + + { + prot.writeSetBegin(TSet(TType.I32, 4)); + prot.writeI32(0); // Make sure size can be read e.g. for JSON protocol. + prot.reset(); + + auto e = cast(TProtocolException)collectException(prot.readSetBegin()); + enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT); + prot.reset(); + buffer.reset(); + } + + // Make sure reading works if the containers are smaller than the limit or + // no limit is set. + foreach (limit; [3, 0, -1]) { + prot.containerSizeLimit = limit; + + { + prot.writeListBegin(TList(TType.I32, 2)); + prot.writeI32(0); + prot.writeI32(1); + prot.writeListEnd(); + prot.reset(); + + auto list = prot.readListBegin(); + enforce(list.elemType == TType.I32); + enforce(list.size == 2); + enforce(prot.readI32() == 0); + enforce(prot.readI32() == 1); + prot.readListEnd(); + + prot.reset(); + buffer.reset(); + } + + { + prot.writeMapBegin(TMap(TType.I32, TType.I32, 2)); + prot.writeI32(0); + prot.writeI32(1); + prot.writeI32(2); + prot.writeI32(3); + prot.writeMapEnd(); + prot.reset(); + + auto map = prot.readMapBegin(); + enforce(map.keyType == TType.I32); + enforce(map.valueType == TType.I32); + enforce(map.size == 2); + enforce(prot.readI32() == 0); + enforce(prot.readI32() == 1); + enforce(prot.readI32() == 2); + enforce(prot.readI32() == 3); + prot.readMapEnd(); + + prot.reset(); + buffer.reset(); + } + + { + prot.writeSetBegin(TSet(TType.I32, 2)); + prot.writeI32(0); + prot.writeI32(1); + prot.writeSetEnd(); + prot.reset(); + + auto set = prot.readSetBegin(); + enforce(set.elemType == TType.I32); + enforce(set.size == 2); + enforce(prot.readI32() == 0); + enforce(prot.readI32() == 1); + prot.readSetEnd(); + + prot.reset(); + buffer.reset(); + } + } +} + +void testStringSizeLimit(Protocol)() if (isTProtocol!Protocol) { + auto buffer = new TMemoryBuffer; + auto prot = new Protocol(buffer); + + // Make sure reading fails if a string larger than the size limit is read. + prot.stringSizeLimit = 3; + + { + prot.writeString("asdf"); + prot.reset(); + + auto e = cast(TProtocolException)collectException(prot.readString()); + enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT); + prot.reset(); + buffer.reset(); + } + + { + prot.writeBinary([1, 2, 3, 4]); + prot.reset(); + + auto e = cast(TProtocolException)collectException(prot.readBinary()); + enforce(e && e.type == TProtocolException.Type.SIZE_LIMIT); + prot.reset(); + buffer.reset(); + } + + // Make sure reading works if the containers are smaller than the limit or + // no limit is set. + foreach (limit; [3, 0, -1]) { + prot.containerSizeLimit = limit; + + { + prot.writeString("as"); + prot.reset(); + + enforce(prot.readString() == "as"); + prot.reset(); + buffer.reset(); + } + + { + prot.writeBinary([1, 2]); + prot.reset(); + + enforce(prot.readBinary() == [1, 2]); + prot.reset(); + buffer.reset(); + } + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/test/server.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/test/server.d new file mode 100644 index 000000000..fc5e86bbc --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/test/server.d @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.internal.test.server; + +import core.sync.condition; +import core.sync.mutex; +import core.thread : Thread; +import std.datetime; +import std.exception : enforce; +import std.typecons : WhiteHole; +import std.variant : Variant; +import thrift.protocol.base; +import thrift.protocol.binary; +import thrift.protocol.processor; +import thrift.server.base; +import thrift.server.transport.socket; +import thrift.transport.base; +import thrift.util.cancellation; + +version(unittest): + +/** + * Tests if serving is stopped correctly if the cancellation passed to serve() + * is triggered. + * + * Because the tests are run many times in a loop, this is indirectly also a + * test whether socket, etc. handles are cleaned up correctly, because the + * application will likely run out of handles otherwise. + */ +void testServeCancel(Server)(void delegate(Server) serverSetup = null) if ( + is(Server : TServer) +) { + auto proc = new WhiteHole!TProcessor; + auto tf = new TTransportFactory; + auto pf = new TBinaryProtocolFactory!(); + + // Need a special case for TNonblockingServer which doesn't use + // TServerTransport. + static if (__traits(compiles, new Server(proc, 0, tf, pf))) { + auto server = new Server(proc, 0, tf, pf); + } else { + auto server = new Server(proc, new TServerSocket(0), tf, pf); + } + + // On Windows, we use TCP sockets to replace socketpair(). Since they stay + // in TIME_WAIT for some time even if they are properly closed, we have to use + // a lower number of iterations to avoid running out of ports/buffer space. + version (Windows) { + enum ITERATIONS = 100; + } else { + enum ITERATIONS = 10000; + } + + if (serverSetup) serverSetup(server); + + auto servingMutex = new Mutex; + auto servingCondition = new Condition(servingMutex); + auto doneMutex = new Mutex; + auto doneCondition = new Condition(doneMutex); + + class CancellingHandler : TServerEventHandler { + void preServe() { + synchronized (servingMutex) { + servingCondition.notifyAll(); + } + } + Variant createContext(TProtocol input, TProtocol output) { return Variant.init; } + void deleteContext(Variant serverContext, TProtocol input, TProtocol output) {} + void preProcess(Variant serverContext, TTransport transport) {} + } + server.eventHandler = new CancellingHandler; + + foreach (i; 0 .. ITERATIONS) { + synchronized (servingMutex) { + auto cancel = new TCancellationOrigin; + synchronized (doneMutex) { + auto serverThread = new Thread({ + server.serve(cancel); + synchronized (doneMutex) { + doneCondition.notifyAll(); + } + }); + serverThread.isDaemon = true; + serverThread.start(); + + servingCondition.wait(); + + cancel.trigger(); + enforce(doneCondition.wait(dur!"msecs"(3*1000))); + serverThread.join(); + } + } + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/internal/traits.d b/src/jaegertracing/thrift/lib/d/src/thrift/internal/traits.d new file mode 100644 index 000000000..8ce1089e8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/internal/traits.d @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.internal.traits; + +import std.traits; + +/** + * Adds »nothrow« to the type of the passed function pointer/delegate, if it + * is not already present. + * + * Technically, assumeNothrow just performs a cast, but using it has the + * advantage of being explicitly about the operation that is performed. + */ +auto assumeNothrow(T)(T t) if (isFunctionPointer!T || isDelegate!T) { + enum attrs = functionAttributes!T | FunctionAttribute.nothrow_; + return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/protocol/base.d b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/base.d new file mode 100644 index 000000000..5b6d84514 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/base.d @@ -0,0 +1,449 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Defines the basic interface for a Thrift protocol and associated exception + * types. + * + * Most parts of the protocol API are typically not used in client code, as + * the actual serialization code is generated by thrift.codegen.* – the only + * interesting thing usually is that there are protocols which can be created + * from transports and passed around. + */ +module thrift.protocol.base; + +import thrift.base; +import thrift.transport.base; + +/** + * The field types Thrift protocols support. + */ +enum TType : byte { + STOP = 0, /// Used to mark the end of a sequence of fields. + VOID = 1, /// + BOOL = 2, /// + BYTE = 3, /// + DOUBLE = 4, /// + I16 = 6, /// + I32 = 8, /// + I64 = 10, /// + STRING = 11, /// + STRUCT = 12, /// + MAP = 13, /// + SET = 14, /// + LIST = 15 /// +} + +/** + * Types of Thrift RPC messages. + */ +enum TMessageType : byte { + CALL = 1, /// Call of a normal, two-way RPC method. + REPLY = 2, /// Reply to a normal method call. + EXCEPTION = 3, /// Reply to a method call if target raised a TApplicationException. + ONEWAY = 4 /// Call of a one-way RPC method which is not followed by a reply. +} + +/** + * Descriptions of Thrift entities. + */ +struct TField { + string name; + TType type; + short id; +} + +/// ditto +struct TList { + TType elemType; + size_t size; +} + +/// ditto +struct TMap { + TType keyType; + TType valueType; + size_t size; +} + +/// ditto +struct TMessage { + string name; + TMessageType type; + int seqid; +} + +/// ditto +struct TSet { + TType elemType; + size_t size; +} + +/// ditto +struct TStruct { + string name; +} + +/** + * Interface for a Thrift protocol implementation. Essentially, it defines + * a way of reading and writing all the base types, plus a mechanism for + * writing out structs with indexed fields. + * + * TProtocol objects should not be shared across multiple encoding contexts, + * as they may need to maintain internal state in some protocols (e.g. JSON). + * Note that is is acceptable for the TProtocol module to do its own internal + * buffered reads/writes to the underlying TTransport where appropriate (i.e. + * when parsing an input XML stream, reading could be batched rather than + * looking ahead character by character for a close tag). + */ +interface TProtocol { + /// The underlying transport used by the protocol. + TTransport transport() @property; + + /* + * Writing methods. + */ + + void writeBool(bool b); /// + void writeByte(byte b); /// + void writeI16(short i16); /// + void writeI32(int i32); /// + void writeI64(long i64); /// + void writeDouble(double dub); /// + void writeString(string str); /// + void writeBinary(ubyte[] buf); /// + + void writeMessageBegin(TMessage message); /// + void writeMessageEnd(); /// + void writeStructBegin(TStruct tstruct); /// + void writeStructEnd(); /// + void writeFieldBegin(TField field); /// + void writeFieldEnd(); /// + void writeFieldStop(); /// + void writeListBegin(TList list); /// + void writeListEnd(); /// + void writeMapBegin(TMap map); /// + void writeMapEnd(); /// + void writeSetBegin(TSet set); /// + void writeSetEnd(); /// + + /* + * Reading methods. + */ + + bool readBool(); /// + byte readByte(); /// + short readI16(); /// + int readI32(); /// + long readI64(); /// + double readDouble(); /// + string readString(); /// + ubyte[] readBinary(); /// + + TMessage readMessageBegin(); /// + void readMessageEnd(); /// + TStruct readStructBegin(); /// + void readStructEnd(); /// + TField readFieldBegin(); /// + void readFieldEnd(); /// + TList readListBegin(); /// + void readListEnd(); /// + TMap readMapBegin(); /// + void readMapEnd(); /// + TSet readSetBegin(); /// + void readSetEnd(); /// + + /** + * Reset any internal state back to a blank slate, if the protocol is + * stateful. + */ + void reset(); +} + +/** + * true if T is a TProtocol. + */ +template isTProtocol(T) { + enum isTProtocol = is(T : TProtocol); +} + +unittest { + static assert(isTProtocol!TProtocol); + static assert(!isTProtocol!void); +} + +/** + * Creates a protocol operating on a given transport. + */ +interface TProtocolFactory { + /// + TProtocol getProtocol(TTransport trans); +} + +/** + * A protocol-level exception. + */ +class TProtocolException : TException { + /// The possible exception types. + enum Type { + UNKNOWN, /// + INVALID_DATA, /// + NEGATIVE_SIZE, /// + SIZE_LIMIT, /// + BAD_VERSION, /// + NOT_IMPLEMENTED, /// + DEPTH_LIMIT /// + } + + /// + this(Type type, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { + static string msgForType(Type type) { + switch (type) { + case Type.UNKNOWN: return "Unknown protocol exception"; + case Type.INVALID_DATA: return "Invalid data"; + case Type.NEGATIVE_SIZE: return "Negative size"; + case Type.SIZE_LIMIT: return "Exceeded size limit"; + case Type.BAD_VERSION: return "Invalid version"; + case Type.NOT_IMPLEMENTED: return "Not implemented"; + case Type.DEPTH_LIMIT: return "Exceeded size limit"; + default: return "(Invalid exception type)"; + } + } + this(msgForType(type), type, file, line, next); + } + + /// + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + this(msg, Type.UNKNOWN, file, line, next); + } + + /// + this(string msg, Type type, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + super(msg, file, line, next); + type_ = type; + } + + /// + Type type() const @property { + return type_; + } + +protected: + Type type_; +} + +/** + * Skips a field of the given type on the protocol. + * + * The main purpose of skip() is to allow treating struct and container types, + * (where multiple primitive types have to be skipped) the same as scalar types + * in generated code. + */ +void skip(Protocol)(Protocol prot, TType type) if (is(Protocol : TProtocol)) { + switch (type) { + case TType.BOOL: + prot.readBool(); + break; + + case TType.BYTE: + prot.readByte(); + break; + + case TType.I16: + prot.readI16(); + break; + + case TType.I32: + prot.readI32(); + break; + + case TType.I64: + prot.readI64(); + break; + + case TType.DOUBLE: + prot.readDouble(); + break; + + case TType.STRING: + prot.readBinary(); + break; + + case TType.STRUCT: + prot.readStructBegin(); + while (true) { + auto f = prot.readFieldBegin(); + if (f.type == TType.STOP) break; + skip(prot, f.type); + prot.readFieldEnd(); + } + prot.readStructEnd(); + break; + + case TType.LIST: + auto l = prot.readListBegin(); + foreach (i; 0 .. l.size) { + skip(prot, l.elemType); + } + prot.readListEnd(); + break; + + case TType.MAP: + auto m = prot.readMapBegin(); + foreach (i; 0 .. m.size) { + skip(prot, m.keyType); + skip(prot, m.valueType); + } + prot.readMapEnd(); + break; + + case TType.SET: + auto s = prot.readSetBegin(); + foreach (i; 0 .. s.size) { + skip(prot, s.elemType); + } + prot.readSetEnd(); + break; + + default: + throw new TProtocolException(TProtocolException.Type.INVALID_DATA); + } +} + +/** + * Application-level exception. + * + * It is thrown if an RPC call went wrong on the application layer, e.g. if + * the receiver does not know the method name requested or a method invoked by + * the service processor throws an exception not part of the Thrift API. + */ +class TApplicationException : TException { + /// The possible exception types. + enum Type { + UNKNOWN = 0, /// + UNKNOWN_METHOD = 1, /// + INVALID_MESSAGE_TYPE = 2, /// + WRONG_METHOD_NAME = 3, /// + BAD_SEQUENCE_ID = 4, /// + MISSING_RESULT = 5, /// + INTERNAL_ERROR = 6, /// + PROTOCOL_ERROR = 7, /// + INVALID_TRANSFORM = 8, /// + INVALID_PROTOCOL = 9, /// + UNSUPPORTED_CLIENT_TYPE = 10 /// + } + + /// + this(Type type, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { + static string msgForType(Type type) { + switch (type) { + case Type.UNKNOWN: return "Unknown application exception"; + case Type.UNKNOWN_METHOD: return "Unknown method"; + case Type.INVALID_MESSAGE_TYPE: return "Invalid message type"; + case Type.WRONG_METHOD_NAME: return "Wrong method name"; + case Type.BAD_SEQUENCE_ID: return "Bad sequence identifier"; + case Type.MISSING_RESULT: return "Missing result"; + case Type.INTERNAL_ERROR: return "Internal error"; + case Type.PROTOCOL_ERROR: return "Protocol error"; + case Type.INVALID_TRANSFORM: return "Invalid transform"; + case Type.INVALID_PROTOCOL: return "Invalid protocol"; + case Type.UNSUPPORTED_CLIENT_TYPE: return "Unsupported client type"; + default: return "(Invalid exception type)"; + } + } + this(msgForType(type), type, file, line, next); + } + + /// + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + this(msg, Type.UNKNOWN, file, line, next); + } + + /// + this(string msg, Type type, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + super(msg, file, line, next); + type_ = type; + } + + /// + Type type() @property const { + return type_; + } + + // TODO: Replace hand-written read()/write() with thrift.codegen templates. + + /// + void read(TProtocol iprot) { + iprot.readStructBegin(); + while (true) { + auto f = iprot.readFieldBegin(); + if (f.type == TType.STOP) break; + + switch (f.id) { + case 1: + if (f.type == TType.STRING) { + msg = iprot.readString(); + } else { + skip(iprot, f.type); + } + break; + case 2: + if (f.type == TType.I32) { + type_ = cast(Type)iprot.readI32(); + } else { + skip(iprot, f.type); + } + break; + default: + skip(iprot, f.type); + break; + } + } + iprot.readStructEnd(); + } + + /// + void write(TProtocol oprot) const { + oprot.writeStructBegin(TStruct("TApplicationException")); + + if (msg != null) { + oprot.writeFieldBegin(TField("message", TType.STRING, 1)); + oprot.writeString(msg); + oprot.writeFieldEnd(); + } + + oprot.writeFieldBegin(TField("type", TType.I32, 2)); + oprot.writeI32(type_); + oprot.writeFieldEnd(); + + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + +private: + Type type_; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/protocol/binary.d b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/binary.d new file mode 100644 index 000000000..13d8fe88e --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/binary.d @@ -0,0 +1,414 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.protocol.binary; + +import std.array : uninitializedArray; +import std.typetuple : allSatisfy, TypeTuple; +import thrift.protocol.base; +import thrift.transport.base; +import thrift.internal.endian; + +/** + * TProtocol implementation of the Binary Thrift protocol. + */ +final class TBinaryProtocol(Transport = TTransport) if ( + isTTransport!Transport +) : TProtocol { + + /** + * Constructs a new instance. + * + * Params: + * trans = The transport to use. + * containerSizeLimit = If positive, the container size is limited to the + * given number of items. + * stringSizeLimit = If positive, the string length is limited to the + * given number of bytes. + * strictRead = If false, old peers which do not include the protocol + * version are tolerated. + * strictWrite = Whether to include the protocol version in the header. + */ + this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0, + bool strictRead = false, bool strictWrite = true + ) { + trans_ = trans; + this.containerSizeLimit = containerSizeLimit; + this.stringSizeLimit = stringSizeLimit; + this.strictRead = strictRead; + this.strictWrite = strictWrite; + } + + Transport transport() @property { + return trans_; + } + + void reset() {} + + /** + * If false, old peers which do not include the protocol version in the + * message header are tolerated. + * + * Defaults to false. + */ + bool strictRead; + + /** + * Whether to include the protocol version in the message header (older + * versions didn't). + * + * Defaults to true. + */ + bool strictWrite; + + /** + * If positive, limits the number of items of deserialized containers to the + * given amount. + * + * This is useful to avoid allocating excessive amounts of memory when broken + * data is received. If the limit is exceeded, a SIZE_LIMIT-type + * TProtocolException is thrown. + * + * Defaults to zero (no limit). + */ + int containerSizeLimit; + + /** + * If positive, limits the length of deserialized strings/binary data to the + * given number of bytes. + * + * This is useful to avoid allocating excessive amounts of memory when broken + * data is received. If the limit is exceeded, a SIZE_LIMIT-type + * TProtocolException is thrown. + * + * Defaults to zero (no limit). + */ + int stringSizeLimit; + + /* + * Writing methods. + */ + + void writeBool(bool b) { + writeByte(b ? 1 : 0); + } + + void writeByte(byte b) { + trans_.write((cast(ubyte*)&b)[0 .. 1]); + } + + void writeI16(short i16) { + short net = hostToNet(i16); + trans_.write((cast(ubyte*)&net)[0 .. 2]); + } + + void writeI32(int i32) { + int net = hostToNet(i32); + trans_.write((cast(ubyte*)&net)[0 .. 4]); + } + + void writeI64(long i64) { + long net = hostToNet(i64); + trans_.write((cast(ubyte*)&net)[0 .. 8]); + } + + void writeDouble(double dub) { + static assert(double.sizeof == ulong.sizeof); + auto bits = hostToNet(*cast(ulong*)(&dub)); + trans_.write((cast(ubyte*)&bits)[0 .. 8]); + } + + void writeString(string str) { + writeBinary(cast(ubyte[])str); + } + + void writeBinary(ubyte[] buf) { + assert(buf.length <= int.max); + writeI32(cast(int)buf.length); + trans_.write(buf); + } + + void writeMessageBegin(TMessage message) { + if (strictWrite) { + int versn = VERSION_1 | message.type; + writeI32(versn); + writeString(message.name); + writeI32(message.seqid); + } else { + writeString(message.name); + writeByte(message.type); + writeI32(message.seqid); + } + } + void writeMessageEnd() {} + + void writeStructBegin(TStruct tstruct) {} + void writeStructEnd() {} + + void writeFieldBegin(TField field) { + writeByte(field.type); + writeI16(field.id); + } + void writeFieldEnd() {} + + void writeFieldStop() { + writeByte(TType.STOP); + } + + void writeListBegin(TList list) { + assert(list.size <= int.max); + writeByte(list.elemType); + writeI32(cast(int)list.size); + } + void writeListEnd() {} + + void writeMapBegin(TMap map) { + assert(map.size <= int.max); + writeByte(map.keyType); + writeByte(map.valueType); + writeI32(cast(int)map.size); + } + void writeMapEnd() {} + + void writeSetBegin(TSet set) { + assert(set.size <= int.max); + writeByte(set.elemType); + writeI32(cast(int)set.size); + } + void writeSetEnd() {} + + + /* + * Reading methods. + */ + + bool readBool() { + return readByte() != 0; + } + + byte readByte() { + ubyte[1] b = void; + trans_.readAll(b); + return cast(byte)b[0]; + } + + short readI16() { + IntBuf!short b = void; + trans_.readAll(b.bytes); + return netToHost(b.value); + } + + int readI32() { + IntBuf!int b = void; + trans_.readAll(b.bytes); + return netToHost(b.value); + } + + long readI64() { + IntBuf!long b = void; + trans_.readAll(b.bytes); + return netToHost(b.value); + } + + double readDouble() { + IntBuf!long b = void; + trans_.readAll(b.bytes); + b.value = netToHost(b.value); + return *cast(double*)(&b.value); + } + + string readString() { + return cast(string)readBinary(); + } + + ubyte[] readBinary() { + return readBinaryBody(readSize(stringSizeLimit)); + } + + TMessage readMessageBegin() { + TMessage msg = void; + + int size = readI32(); + if (size < 0) { + int versn = size & VERSION_MASK; + if (versn != VERSION_1) { + throw new TProtocolException("Bad protocol version.", + TProtocolException.Type.BAD_VERSION); + } + + msg.type = cast(TMessageType)(size & MESSAGE_TYPE_MASK); + msg.name = readString(); + msg.seqid = readI32(); + } else { + if (strictRead) { + throw new TProtocolException( + "Protocol version missing, old client?", + TProtocolException.Type.BAD_VERSION); + } else { + if (size < 0) { + throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE); + } + msg.name = cast(string)readBinaryBody(size); + msg.type = cast(TMessageType)(readByte()); + msg.seqid = readI32(); + } + } + + return msg; + } + void readMessageEnd() {} + + TStruct readStructBegin() { + return TStruct(); + } + void readStructEnd() {} + + TField readFieldBegin() { + TField f = void; + f.name = null; + f.type = cast(TType)readByte(); + if (f.type == TType.STOP) return f; + f.id = readI16(); + return f; + } + void readFieldEnd() {} + + TList readListBegin() { + return TList(cast(TType)readByte(), readSize(containerSizeLimit)); + } + void readListEnd() {} + + TMap readMapBegin() { + return TMap(cast(TType)readByte(), cast(TType)readByte(), + readSize(containerSizeLimit)); + } + void readMapEnd() {} + + TSet readSetBegin() { + return TSet(cast(TType)readByte(), readSize(containerSizeLimit)); + } + void readSetEnd() {} + +private: + ubyte[] readBinaryBody(int size) { + if (size == 0) { + return null; + } + + auto buf = uninitializedArray!(ubyte[])(size); + trans_.readAll(buf); + return buf; + } + + int readSize(int limit) { + auto size = readI32(); + if (size < 0) { + throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE); + } else if (limit > 0 && size > limit) { + throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT); + } + return size; + } + + enum MESSAGE_TYPE_MASK = 0x000000ff; + enum VERSION_MASK = 0xffff0000; + enum VERSION_1 = 0x80010000; + + Transport trans_; +} + +/** + * TBinaryProtocol construction helper to avoid having to explicitly specify + * the transport type, i.e. to allow the constructor being called using IFTI + * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla + * enhancement requet 6082)). + */ +TBinaryProtocol!Transport tBinaryProtocol(Transport)(Transport trans, + int containerSizeLimit = 0, int stringSizeLimit = 0, + bool strictRead = false, bool strictWrite = true +) if (isTTransport!Transport) { + return new TBinaryProtocol!Transport(trans, containerSizeLimit, + stringSizeLimit, strictRead, strictWrite); +} + +unittest { + import std.exception; + import thrift.transport.memory; + + // Check the message header format. + auto buf = new TMemoryBuffer; + auto binary = tBinaryProtocol(buf); + binary.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0)); + + auto header = new ubyte[15]; + buf.readAll(header); + enforce(header == [ + 128, 1, 0, 1, // Version 1, TMessageType.CALL + 0, 0, 0, 3, // Method name length + 102, 111, 111, // Method name ("foo") + 0, 0, 0, 0, // Sequence id + ]); +} + +unittest { + import thrift.internal.test.protocol; + testContainerSizeLimit!(TBinaryProtocol!())(); + testStringSizeLimit!(TBinaryProtocol!())(); +} + +/** + * TProtocolFactory creating a TBinaryProtocol instance for passed in + * transports. + * + * The optional Transports template tuple parameter can be used to specify + * one or more TTransport implementations to specifically instantiate + * TBinaryProtocol for. If the actual transport types encountered at + * runtime match one of the transports in the list, a specialized protocol + * instance is created. Otherwise, a generic TTransport version is used. + */ +class TBinaryProtocolFactory(Transports...) if ( + allSatisfy!(isTTransport, Transports) +) : TProtocolFactory { + /// + this (int containerSizeLimit = 0, int stringSizeLimit = 0, + bool strictRead = false, bool strictWrite = true + ) { + strictRead_ = strictRead; + strictWrite_ = strictWrite; + containerSizeLimit_ = containerSizeLimit; + stringSizeLimit_ = stringSizeLimit; + } + + TProtocol getProtocol(TTransport trans) const { + foreach (Transport; TypeTuple!(Transports, TTransport)) { + auto concreteTrans = cast(Transport)trans; + if (concreteTrans) { + return new TBinaryProtocol!Transport(concreteTrans, + containerSizeLimit_, stringSizeLimit_, strictRead_, strictWrite_); + } + } + throw new TProtocolException( + "Passed null transport to TBinaryProtocolFactoy."); + } + +protected: + bool strictRead_; + bool strictWrite_; + int containerSizeLimit_; + int stringSizeLimit_; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/protocol/compact.d b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/compact.d new file mode 100644 index 000000000..9155c8199 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/compact.d @@ -0,0 +1,698 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.protocol.compact; + +import std.array : uninitializedArray; +import std.typetuple : allSatisfy, TypeTuple; +import thrift.protocol.base; +import thrift.transport.base; +import thrift.internal.endian; + +/** + * D implementation of the Compact protocol. + * + * See THRIFT-110 for a protocol description. This implementation is based on + * the C++ one. + */ +final class TCompactProtocol(Transport = TTransport) if ( + isTTransport!Transport +) : TProtocol { + /** + * Constructs a new instance. + * + * Params: + * trans = The transport to use. + * containerSizeLimit = If positive, the container size is limited to the + * given number of items. + * stringSizeLimit = If positive, the string length is limited to the + * given number of bytes. + */ + this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) { + trans_ = trans; + this.containerSizeLimit = containerSizeLimit; + this.stringSizeLimit = stringSizeLimit; + } + + Transport transport() @property { + return trans_; + } + + void reset() { + lastFieldId_ = 0; + fieldIdStack_ = null; + booleanField_ = TField.init; + hasBoolValue_ = false; + } + + /** + * If positive, limits the number of items of deserialized containers to the + * given amount. + * + * This is useful to avoid allocating excessive amounts of memory when broken + * data is received. If the limit is exceeded, a SIZE_LIMIT-type + * TProtocolException is thrown. + * + * Defaults to zero (no limit). + */ + int containerSizeLimit; + + /** + * If positive, limits the length of deserialized strings/binary data to the + * given number of bytes. + * + * This is useful to avoid allocating excessive amounts of memory when broken + * data is received. If the limit is exceeded, a SIZE_LIMIT-type + * TProtocolException is thrown. + * + * Defaults to zero (no limit). + */ + int stringSizeLimit; + + /* + * Writing methods. + */ + + void writeBool(bool b) { + if (booleanField_.name !is null) { + // we haven't written the field header yet + writeFieldBeginInternal(booleanField_, + b ? CType.BOOLEAN_TRUE : CType.BOOLEAN_FALSE); + booleanField_.name = null; + } else { + // we're not part of a field, so just write the value + writeByte(b ? CType.BOOLEAN_TRUE : CType.BOOLEAN_FALSE); + } + } + + void writeByte(byte b) { + trans_.write((cast(ubyte*)&b)[0..1]); + } + + void writeI16(short i16) { + writeVarint32(i32ToZigzag(i16)); + } + + void writeI32(int i32) { + writeVarint32(i32ToZigzag(i32)); + } + + void writeI64(long i64) { + writeVarint64(i64ToZigzag(i64)); + } + + void writeDouble(double dub) { + ulong bits = hostToLe(*cast(ulong*)(&dub)); + trans_.write((cast(ubyte*)&bits)[0 .. 8]); + } + + void writeString(string str) { + writeBinary(cast(ubyte[])str); + } + + void writeBinary(ubyte[] buf) { + assert(buf.length <= int.max); + writeVarint32(cast(int)buf.length); + trans_.write(buf); + } + + void writeMessageBegin(TMessage msg) { + writeByte(cast(byte)PROTOCOL_ID); + writeByte(cast(byte)((VERSION_N & VERSION_MASK) | + ((cast(int)msg.type << TYPE_SHIFT_AMOUNT) & TYPE_MASK))); + writeVarint32(msg.seqid); + writeString(msg.name); + } + void writeMessageEnd() {} + + void writeStructBegin(TStruct tstruct) { + fieldIdStack_ ~= lastFieldId_; + lastFieldId_ = 0; + } + + void writeStructEnd() { + lastFieldId_ = fieldIdStack_[$ - 1]; + fieldIdStack_ = fieldIdStack_[0 .. $ - 1]; + fieldIdStack_.assumeSafeAppend(); + } + + void writeFieldBegin(TField field) { + if (field.type == TType.BOOL) { + booleanField_.name = field.name; + booleanField_.type = field.type; + booleanField_.id = field.id; + } else { + return writeFieldBeginInternal(field); + } + } + void writeFieldEnd() {} + + void writeFieldStop() { + writeByte(TType.STOP); + } + + void writeListBegin(TList list) { + writeCollectionBegin(list.elemType, list.size); + } + void writeListEnd() {} + + void writeMapBegin(TMap map) { + if (map.size == 0) { + writeByte(0); + } else { + assert(map.size <= int.max); + writeVarint32(cast(int)map.size); + writeByte(cast(byte)(toCType(map.keyType) << 4 | toCType(map.valueType))); + } + } + void writeMapEnd() {} + + void writeSetBegin(TSet set) { + writeCollectionBegin(set.elemType, set.size); + } + void writeSetEnd() {} + + + /* + * Reading methods. + */ + + bool readBool() { + if (hasBoolValue_ == true) { + hasBoolValue_ = false; + return boolValue_; + } + + return readByte() == CType.BOOLEAN_TRUE; + } + + byte readByte() { + ubyte[1] b = void; + trans_.readAll(b); + return cast(byte)b[0]; + } + + short readI16() { + return cast(short)zigzagToI32(readVarint32()); + } + + int readI32() { + return zigzagToI32(readVarint32()); + } + + long readI64() { + return zigzagToI64(readVarint64()); + } + + double readDouble() { + IntBuf!long b = void; + trans_.readAll(b.bytes); + b.value = leToHost(b.value); + return *cast(double*)(&b.value); + } + + string readString() { + return cast(string)readBinary(); + } + + ubyte[] readBinary() { + auto size = readVarint32(); + checkSize(size, stringSizeLimit); + + if (size == 0) { + return null; + } + + auto buf = uninitializedArray!(ubyte[])(size); + trans_.readAll(buf); + return buf; + } + + TMessage readMessageBegin() { + TMessage msg = void; + + auto protocolId = readByte(); + if (protocolId != cast(byte)PROTOCOL_ID) { + throw new TProtocolException("Bad protocol identifier", + TProtocolException.Type.BAD_VERSION); + } + + auto versionAndType = readByte(); + auto ver = versionAndType & VERSION_MASK; + if (ver != VERSION_N) { + throw new TProtocolException("Bad protocol version", + TProtocolException.Type.BAD_VERSION); + } + + msg.type = cast(TMessageType)((versionAndType >> TYPE_SHIFT_AMOUNT) & TYPE_BITS); + msg.seqid = readVarint32(); + msg.name = readString(); + + return msg; + } + void readMessageEnd() {} + + TStruct readStructBegin() { + fieldIdStack_ ~= lastFieldId_; + lastFieldId_ = 0; + return TStruct(); + } + + void readStructEnd() { + lastFieldId_ = fieldIdStack_[$ - 1]; + fieldIdStack_ = fieldIdStack_[0 .. $ - 1]; + } + + TField readFieldBegin() { + TField f = void; + f.name = null; + + auto bite = readByte(); + auto type = cast(CType)(bite & 0x0f); + + if (type == CType.STOP) { + // Struct stop byte, nothing more to do. + f.id = 0; + f.type = TType.STOP; + return f; + } + + // Mask off the 4 MSB of the type header, which could contain a field id + // delta. + auto modifier = cast(short)((bite & 0xf0) >> 4); + if (modifier > 0) { + f.id = cast(short)(lastFieldId_ + modifier); + } else { + // Delta encoding not used, just read the id as usual. + f.id = readI16(); + } + f.type = getTType(type); + + if (type == CType.BOOLEAN_TRUE || type == CType.BOOLEAN_FALSE) { + // For boolean fields, the value is encoded in the type – keep it around + // for the readBool() call. + hasBoolValue_ = true; + boolValue_ = (type == CType.BOOLEAN_TRUE ? true : false); + } + + lastFieldId_ = f.id; + return f; + } + void readFieldEnd() {} + + TList readListBegin() { + auto sizeAndType = readByte(); + + auto lsize = (sizeAndType >> 4) & 0xf; + if (lsize == 0xf) { + lsize = readVarint32(); + } + checkSize(lsize, containerSizeLimit); + + TList l = void; + l.elemType = getTType(cast(CType)(sizeAndType & 0x0f)); + l.size = cast(size_t)lsize; + + return l; + } + void readListEnd() {} + + TMap readMapBegin() { + TMap m = void; + + auto size = readVarint32(); + ubyte kvType; + if (size != 0) { + kvType = readByte(); + } + checkSize(size, containerSizeLimit); + + m.size = size; + m.keyType = getTType(cast(CType)(kvType >> 4)); + m.valueType = getTType(cast(CType)(kvType & 0xf)); + + return m; + } + void readMapEnd() {} + + TSet readSetBegin() { + auto sizeAndType = readByte(); + + auto lsize = (sizeAndType >> 4) & 0xf; + if (lsize == 0xf) { + lsize = readVarint32(); + } + checkSize(lsize, containerSizeLimit); + + TSet s = void; + s.elemType = getTType(cast(CType)(sizeAndType & 0xf)); + s.size = cast(size_t)lsize; + + return s; + } + void readSetEnd() {} + +private: + void writeFieldBeginInternal(TField field, byte typeOverride = -1) { + // If there's a type override, use that. + auto typeToWrite = (typeOverride == -1 ? toCType(field.type) : typeOverride); + + // check if we can use delta encoding for the field id + if (field.id > lastFieldId_ && (field.id - lastFieldId_) <= 15) { + // write them together + writeByte(cast(byte)((field.id - lastFieldId_) << 4 | typeToWrite)); + } else { + // write them separate + writeByte(cast(byte)typeToWrite); + writeI16(field.id); + } + + lastFieldId_ = field.id; + } + + + void writeCollectionBegin(TType elemType, size_t size) { + if (size <= 14) { + writeByte(cast(byte)(size << 4 | toCType(elemType))); + } else { + assert(size <= int.max); + writeByte(cast(byte)(0xf0 | toCType(elemType))); + writeVarint32(cast(int)size); + } + } + + void writeVarint32(uint n) { + ubyte[5] buf = void; + ubyte wsize; + + while (true) { + if ((n & ~0x7F) == 0) { + buf[wsize++] = cast(ubyte)n; + break; + } else { + buf[wsize++] = cast(ubyte)((n & 0x7F) | 0x80); + n >>= 7; + } + } + + trans_.write(buf[0 .. wsize]); + } + + /* + * Write an i64 as a varint. Results in 1-10 bytes on the wire. + */ + void writeVarint64(ulong n) { + ubyte[10] buf = void; + ubyte wsize; + + while (true) { + if ((n & ~0x7FL) == 0) { + buf[wsize++] = cast(ubyte)n; + break; + } else { + buf[wsize++] = cast(ubyte)((n & 0x7F) | 0x80); + n >>= 7; + } + } + + trans_.write(buf[0 .. wsize]); + } + + /* + * Convert l into a zigzag long. This allows negative numbers to be + * represented compactly as a varint. + */ + ulong i64ToZigzag(long l) { + return (l << 1) ^ (l >> 63); + } + + /* + * Convert n into a zigzag int. This allows negative numbers to be + * represented compactly as a varint. + */ + uint i32ToZigzag(int n) { + return (n << 1) ^ (n >> 31); + } + + CType toCType(TType type) { + final switch (type) { + case TType.STOP: + return CType.STOP; + case TType.BOOL: + return CType.BOOLEAN_TRUE; + case TType.BYTE: + return CType.BYTE; + case TType.DOUBLE: + return CType.DOUBLE; + case TType.I16: + return CType.I16; + case TType.I32: + return CType.I32; + case TType.I64: + return CType.I64; + case TType.STRING: + return CType.BINARY; + case TType.STRUCT: + return CType.STRUCT; + case TType.MAP: + return CType.MAP; + case TType.SET: + return CType.SET; + case TType.LIST: + return CType.LIST; + case TType.VOID: + assert(false, "Invalid type passed."); + } + } + + int readVarint32() { + return cast(int)readVarint64(); + } + + long readVarint64() { + ulong val; + ubyte shift; + ubyte[10] buf = void; // 64 bits / (7 bits/byte) = 10 bytes. + auto bufSize = buf.sizeof; + auto borrowed = trans_.borrow(buf.ptr, bufSize); + + ubyte rsize; + + if (borrowed) { + // Fast path. + while (true) { + auto bite = borrowed[rsize]; + rsize++; + val |= cast(ulong)(bite & 0x7f) << shift; + shift += 7; + if (!(bite & 0x80)) { + trans_.consume(rsize); + return val; + } + // Have to check for invalid data so we don't crash. + if (rsize == buf.sizeof) { + throw new TProtocolException(TProtocolException.Type.INVALID_DATA, + "Variable-length int over 10 bytes."); + } + } + } else { + // Slow path. + while (true) { + ubyte[1] bite; + trans_.readAll(bite); + ++rsize; + + val |= cast(ulong)(bite[0] & 0x7f) << shift; + shift += 7; + if (!(bite[0] & 0x80)) { + return val; + } + + // Might as well check for invalid data on the slow path too. + if (rsize >= buf.sizeof) { + throw new TProtocolException(TProtocolException.Type.INVALID_DATA, + "Variable-length int over 10 bytes."); + } + } + } + } + + /* + * Convert from zigzag int to int. + */ + int zigzagToI32(uint n) { + return (n >> 1) ^ -(n & 1); + } + + /* + * Convert from zigzag long to long. + */ + long zigzagToI64(ulong n) { + return (n >> 1) ^ -(n & 1); + } + + TType getTType(CType type) { + final switch (type) { + case CType.STOP: + return TType.STOP; + case CType.BOOLEAN_FALSE: + return TType.BOOL; + case CType.BOOLEAN_TRUE: + return TType.BOOL; + case CType.BYTE: + return TType.BYTE; + case CType.I16: + return TType.I16; + case CType.I32: + return TType.I32; + case CType.I64: + return TType.I64; + case CType.DOUBLE: + return TType.DOUBLE; + case CType.BINARY: + return TType.STRING; + case CType.LIST: + return TType.LIST; + case CType.SET: + return TType.SET; + case CType.MAP: + return TType.MAP; + case CType.STRUCT: + return TType.STRUCT; + } + } + + void checkSize(int size, int limit) { + if (size < 0) { + throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE); + } else if (limit > 0 && size > limit) { + throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT); + } + } + + enum PROTOCOL_ID = 0x82; + enum VERSION_N = 1; + enum VERSION_MASK = 0b0001_1111; + enum TYPE_MASK = 0b1110_0000; + enum TYPE_BITS = 0b0000_0111; + enum TYPE_SHIFT_AMOUNT = 5; + + // Probably need to implement a better stack at some point. + short[] fieldIdStack_; + short lastFieldId_; + + TField booleanField_; + + bool hasBoolValue_; + bool boolValue_; + + Transport trans_; +} + +/** + * TCompactProtocol construction helper to avoid having to explicitly specify + * the transport type, i.e. to allow the constructor being called using IFTI + * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla + * enhancement requet 6082)). + */ +TCompactProtocol!Transport tCompactProtocol(Transport)(Transport trans, + int containerSizeLimit = 0, int stringSizeLimit = 0 +) if (isTTransport!Transport) +{ + return new TCompactProtocol!Transport(trans, + containerSizeLimit, stringSizeLimit); +} + +private { + enum CType : ubyte { + STOP = 0x0, + BOOLEAN_TRUE = 0x1, + BOOLEAN_FALSE = 0x2, + BYTE = 0x3, + I16 = 0x4, + I32 = 0x5, + I64 = 0x6, + DOUBLE = 0x7, + BINARY = 0x8, + LIST = 0x9, + SET = 0xa, + MAP = 0xb, + STRUCT = 0xc + } + static assert(CType.max <= 0xf, + "Compact protocol wire type representation must fit into 4 bits."); +} + +unittest { + import std.exception; + import thrift.transport.memory; + + // Check the message header format. + auto buf = new TMemoryBuffer; + auto compact = tCompactProtocol(buf); + compact.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0)); + + auto header = new ubyte[7]; + buf.readAll(header); + enforce(header == [ + 130, // Protocol id. + 33, // Version/type byte. + 0, // Sequence id. + 3, 102, 111, 111 // Method name. + ]); +} + +unittest { + import thrift.internal.test.protocol; + testContainerSizeLimit!(TCompactProtocol!())(); + testStringSizeLimit!(TCompactProtocol!())(); +} + +/** + * TProtocolFactory creating a TCompactProtocol instance for passed in + * transports. + * + * The optional Transports template tuple parameter can be used to specify + * one or more TTransport implementations to specifically instantiate + * TCompactProtocol for. If the actual transport types encountered at + * runtime match one of the transports in the list, a specialized protocol + * instance is created. Otherwise, a generic TTransport version is used. + */ +class TCompactProtocolFactory(Transports...) if ( + allSatisfy!(isTTransport, Transports) +) : TProtocolFactory { + /// + this(int containerSizeLimit = 0, int stringSizeLimit = 0) { + containerSizeLimit_ = 0; + stringSizeLimit_ = 0; + } + + TProtocol getProtocol(TTransport trans) const { + foreach (Transport; TypeTuple!(Transports, TTransport)) { + auto concreteTrans = cast(Transport)trans; + if (concreteTrans) { + return new TCompactProtocol!Transport(concreteTrans); + } + } + throw new TProtocolException( + "Passed null transport to TCompactProtocolFactory."); + } + + int containerSizeLimit_; + int stringSizeLimit_; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/protocol/json.d b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/json.d new file mode 100644 index 000000000..56a71dacc --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/json.d @@ -0,0 +1,1037 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.protocol.json; + +import std.algorithm; +import std.array; +import std.base64; +import std.conv; +import std.range; +import std.string : format; +import std.traits : isIntegral; +import std.typetuple : allSatisfy, TypeTuple; +import std.utf : toUTF8; +import thrift.protocol.base; +import thrift.transport.base; + +alias Base64Impl!('+', '/', Base64.NoPadding) Base64NoPad; + +/** + * Implementation of the Thrift JSON protocol. + */ +final class TJsonProtocol(Transport = TTransport) if ( + isTTransport!Transport +) : TProtocol { + /** + * Constructs a new instance. + * + * Params: + * trans = The transport to use. + * containerSizeLimit = If positive, the container size is limited to the + * given number of items. + * stringSizeLimit = If positive, the string length is limited to the + * given number of bytes. + */ + this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) { + trans_ = trans; + this.containerSizeLimit = containerSizeLimit; + this.stringSizeLimit = stringSizeLimit; + + context_ = new Context(); + reader_ = new LookaheadReader(trans); + } + + Transport transport() @property { + return trans_; + } + + void reset() { + destroy(contextStack_); + context_ = new Context(); + reader_ = new LookaheadReader(trans_); + } + + /** + * If positive, limits the number of items of deserialized containers to the + * given amount. + * + * This is useful to avoid allocating excessive amounts of memory when broken + * data is received. If the limit is exceeded, a SIZE_LIMIT-type + * TProtocolException is thrown. + * + * Defaults to zero (no limit). + */ + int containerSizeLimit; + + /** + * If positive, limits the length of deserialized strings/binary data to the + * given number of bytes. + * + * This is useful to avoid allocating excessive amounts of memory when broken + * data is received. If the limit is exceeded, a SIZE_LIMIT-type + * TProtocolException is thrown. + * + * Note: For binary data, the limit applies to the length of the + * Base64-encoded string data, not the resulting byte array. + * + * Defaults to zero (no limit). + */ + int stringSizeLimit; + + /* + * Writing methods. + */ + + void writeBool(bool b) { + writeJsonInteger(b ? 1 : 0); + } + + void writeByte(byte b) { + writeJsonInteger(b); + } + + void writeI16(short i16) { + writeJsonInteger(i16); + } + + void writeI32(int i32) { + writeJsonInteger(i32); + } + + void writeI64(long i64) { + writeJsonInteger(i64); + } + + void writeDouble(double dub) { + context_.write(trans_); + + string value; + if (dub is double.nan) { + value = NAN_STRING; + } else if (dub is double.infinity) { + value = INFINITY_STRING; + } else if (dub is -double.infinity) { + value = NEG_INFINITY_STRING; + } + + bool escapeNum = value !is null || context_.escapeNum; + + if (value is null) { + /* precision is 17 */ + value = format("%.17g", dub); + } + + if (escapeNum) trans_.write(STRING_DELIMITER); + trans_.write(cast(ubyte[])value); + if (escapeNum) trans_.write(STRING_DELIMITER); + } + + void writeString(string str) { + context_.write(trans_); + trans_.write(STRING_DELIMITER); + foreach (c; str) { + writeJsonChar(c); + } + trans_.write(STRING_DELIMITER); + } + + void writeBinary(ubyte[] buf) { + context_.write(trans_); + + trans_.write(STRING_DELIMITER); + ubyte[4] b; + while (!buf.empty) { + auto toWrite = take(buf, 3); + Base64NoPad.encode(toWrite, b[]); + trans_.write(b[0 .. toWrite.length + 1]); + buf.popFrontN(toWrite.length); + } + trans_.write(STRING_DELIMITER); + } + + void writeMessageBegin(TMessage msg) { + writeJsonArrayBegin(); + writeJsonInteger(THRIFT_JSON_VERSION); + writeString(msg.name); + writeJsonInteger(cast(byte)msg.type); + writeJsonInteger(msg.seqid); + } + + void writeMessageEnd() { + writeJsonArrayEnd(); + } + + void writeStructBegin(TStruct tstruct) { + writeJsonObjectBegin(); + } + + void writeStructEnd() { + writeJsonObjectEnd(); + } + + void writeFieldBegin(TField field) { + writeJsonInteger(field.id); + writeJsonObjectBegin(); + writeString(getNameFromTType(field.type)); + } + + void writeFieldEnd() { + writeJsonObjectEnd(); + } + + void writeFieldStop() {} + + void writeListBegin(TList list) { + writeJsonArrayBegin(); + writeString(getNameFromTType(list.elemType)); + writeJsonInteger(list.size); + } + + void writeListEnd() { + writeJsonArrayEnd(); + } + + void writeMapBegin(TMap map) { + writeJsonArrayBegin(); + writeString(getNameFromTType(map.keyType)); + writeString(getNameFromTType(map.valueType)); + writeJsonInteger(map.size); + writeJsonObjectBegin(); + } + + void writeMapEnd() { + writeJsonObjectEnd(); + writeJsonArrayEnd(); + } + + void writeSetBegin(TSet set) { + writeJsonArrayBegin(); + writeString(getNameFromTType(set.elemType)); + writeJsonInteger(set.size); + } + + void writeSetEnd() { + writeJsonArrayEnd(); + } + + + /* + * Reading methods. + */ + + bool readBool() { + return readJsonInteger!byte() ? true : false; + } + + byte readByte() { + return readJsonInteger!byte(); + } + + short readI16() { + return readJsonInteger!short(); + } + + int readI32() { + return readJsonInteger!int(); + } + + long readI64() { + return readJsonInteger!long(); + } + + double readDouble() { + context_.read(reader_); + + if (reader_.peek() == STRING_DELIMITER) { + auto str = readJsonString(true); + if (str == NAN_STRING) { + return double.nan; + } + if (str == INFINITY_STRING) { + return double.infinity; + } + if (str == NEG_INFINITY_STRING) { + return -double.infinity; + } + + if (!context_.escapeNum) { + // Throw exception -- we should not be in a string in this case + throw new TProtocolException("Numeric data unexpectedly quoted", + TProtocolException.Type.INVALID_DATA); + } + try { + return to!double(str); + } catch (ConvException e) { + throw new TProtocolException(`Expected numeric value; got "` ~ str ~ + `".`, TProtocolException.Type.INVALID_DATA); + } + } + else { + if (context_.escapeNum) { + // This will throw - we should have had a quote if escapeNum == true + readJsonSyntaxChar(STRING_DELIMITER); + } + + auto str = readJsonNumericChars(); + try { + return to!double(str); + } catch (ConvException e) { + throw new TProtocolException(`Expected numeric value; got "` ~ str ~ + `".`, TProtocolException.Type.INVALID_DATA); + } + } + } + + string readString() { + return readJsonString(false); + } + + ubyte[] readBinary() { + return Base64NoPad.decode(readString()); + } + + TMessage readMessageBegin() { + TMessage msg = void; + + readJsonArrayBegin(); + + auto ver = readJsonInteger!short(); + if (ver != THRIFT_JSON_VERSION) { + throw new TProtocolException("Message contained bad version.", + TProtocolException.Type.BAD_VERSION); + } + + msg.name = readString(); + msg.type = cast(TMessageType)readJsonInteger!byte(); + msg.seqid = readJsonInteger!short(); + + return msg; + } + + void readMessageEnd() { + readJsonArrayEnd(); + } + + TStruct readStructBegin() { + readJsonObjectBegin(); + return TStruct(); + } + + void readStructEnd() { + readJsonObjectEnd(); + } + + TField readFieldBegin() { + TField f = void; + f.name = null; + + auto ch = reader_.peek(); + if (ch == OBJECT_END) { + f.type = TType.STOP; + } else { + f.id = readJsonInteger!short(); + readJsonObjectBegin(); + f.type = getTTypeFromName(readString()); + } + + return f; + } + + void readFieldEnd() { + readJsonObjectEnd(); + } + + TList readListBegin() { + readJsonArrayBegin(); + auto type = getTTypeFromName(readString()); + auto size = readContainerSize(); + return TList(type, size); + } + + void readListEnd() { + readJsonArrayEnd(); + } + + TMap readMapBegin() { + readJsonArrayBegin(); + auto keyType = getTTypeFromName(readString()); + auto valueType = getTTypeFromName(readString()); + auto size = readContainerSize(); + readJsonObjectBegin(); + return TMap(keyType, valueType, size); + } + + void readMapEnd() { + readJsonObjectEnd(); + readJsonArrayEnd(); + } + + TSet readSetBegin() { + readJsonArrayBegin(); + auto type = getTTypeFromName(readString()); + auto size = readContainerSize(); + return TSet(type, size); + } + + void readSetEnd() { + readJsonArrayEnd(); + } + +private: + void pushContext(Context c) { + contextStack_ ~= context_; + context_ = c; + } + + void popContext() { + context_ = contextStack_.back; + contextStack_.popBack(); + contextStack_.assumeSafeAppend(); + } + + /* + * Writing functions + */ + + // Write the character ch as a Json escape sequence ("\u00xx") + void writeJsonEscapeChar(ubyte ch) { + trans_.write(ESCAPE_PREFIX); + trans_.write(ESCAPE_PREFIX); + auto outCh = hexChar(cast(ubyte)(ch >> 4)); + trans_.write((&outCh)[0 .. 1]); + outCh = hexChar(ch); + trans_.write((&outCh)[0 .. 1]); + } + + // Write the character ch as part of a Json string, escaping as appropriate. + void writeJsonChar(ubyte ch) { + if (ch >= 0x30) { + if (ch == '\\') { // Only special character >= 0x30 is '\' + trans_.write(BACKSLASH); + trans_.write(BACKSLASH); + } else { + trans_.write((&ch)[0 .. 1]); + } + } + else { + auto outCh = kJsonCharTable[ch]; + // Check if regular character, backslash escaped, or Json escaped + if (outCh == 1) { + trans_.write((&ch)[0 .. 1]); + } else if (outCh > 1) { + trans_.write(BACKSLASH); + trans_.write((&outCh)[0 .. 1]); + } else { + writeJsonEscapeChar(ch); + } + } + } + + // Convert the given integer type to a Json number, or a string + // if the context requires it (eg: key in a map pair). + void writeJsonInteger(T)(T num) if (isIntegral!T) { + context_.write(trans_); + + auto escapeNum = context_.escapeNum(); + if (escapeNum) trans_.write(STRING_DELIMITER); + trans_.write(cast(ubyte[])to!string(num)); + if (escapeNum) trans_.write(STRING_DELIMITER); + } + + void writeJsonObjectBegin() { + context_.write(trans_); + trans_.write(OBJECT_BEGIN); + pushContext(new PairContext()); + } + + void writeJsonObjectEnd() { + popContext(); + trans_.write(OBJECT_END); + } + + void writeJsonArrayBegin() { + context_.write(trans_); + trans_.write(ARRAY_BEGIN); + pushContext(new ListContext()); + } + + void writeJsonArrayEnd() { + popContext(); + trans_.write(ARRAY_END); + } + + /* + * Reading functions + */ + + int readContainerSize() { + auto size = readJsonInteger!int(); + if (size < 0) { + throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE); + } else if (containerSizeLimit > 0 && size > containerSizeLimit) { + throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT); + } + return size; + } + + void readJsonSyntaxChar(ubyte[1] ch) { + return readSyntaxChar(reader_, ch); + } + + wchar readJsonEscapeChar() { + auto a = reader_.read(); + auto b = reader_.read(); + auto c = reader_.read(); + auto d = reader_.read(); + return cast(ushort)( + (hexVal(a[0]) << 12) + (hexVal(b[0]) << 8) + + (hexVal(c[0]) << 4) + hexVal(d[0]) + ); + } + + string readJsonString(bool skipContext = false) { + if (!skipContext) context_.read(reader_); + + readJsonSyntaxChar(STRING_DELIMITER); + auto buffer = appender!string(); + + wchar[] wchs; + int bytesRead; + while (true) { + auto ch = reader_.read(); + if (ch == STRING_DELIMITER) { + break; + } + + ++bytesRead; + if (stringSizeLimit > 0 && bytesRead > stringSizeLimit) { + throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT); + } + + if (ch == BACKSLASH) { + ch = reader_.read(); + if (ch == ESCAPE_CHAR) { + auto wch = readJsonEscapeChar(); + if (wch >= 0xD800 && wch <= 0xDBFF) { + wchs ~= wch; + } else if (wch >= 0xDC00 && wch <= 0xDFFF && wchs.length == 0) { + throw new TProtocolException("Missing UTF-16 high surrogate.", + TProtocolException.Type.INVALID_DATA); + } else { + wchs ~= wch; + buffer.put(wchs.toUTF8); + wchs = []; + } + continue; + } else { + auto pos = countUntil(kEscapeChars[], ch[0]); + if (pos == -1) { + throw new TProtocolException("Expected control char, got '" ~ + cast(char)ch[0] ~ "'.", TProtocolException.Type.INVALID_DATA); + } + ch = kEscapeCharVals[pos]; + } + } + if (wchs.length != 0) { + throw new TProtocolException("Missing UTF-16 low surrogate.", + TProtocolException.Type.INVALID_DATA); + } + buffer.put(ch[0]); + } + + if (wchs.length != 0) { + throw new TProtocolException("Missing UTF-16 low surrogate.", + TProtocolException.Type.INVALID_DATA); + } + return buffer.data; + } + + // Reads a sequence of characters, stopping at the first one that is not + // a valid Json numeric character. + string readJsonNumericChars() { + string str; + while (true) { + auto ch = reader_.peek(); + if (!isJsonNumeric(ch[0])) { + break; + } + reader_.read(); + str ~= ch; + } + return str; + } + + // Reads a sequence of characters and assembles them into a number, + // returning them via num + T readJsonInteger(T)() if (isIntegral!T) { + context_.read(reader_); + if (context_.escapeNum()) { + readJsonSyntaxChar(STRING_DELIMITER); + } + auto str = readJsonNumericChars(); + T num; + try { + num = to!T(str); + } catch (ConvException e) { + throw new TProtocolException(`Expected numeric value, got "` ~ str ~ `".`, + TProtocolException.Type.INVALID_DATA); + } + if (context_.escapeNum()) { + readJsonSyntaxChar(STRING_DELIMITER); + } + return num; + } + + void readJsonObjectBegin() { + context_.read(reader_); + readJsonSyntaxChar(OBJECT_BEGIN); + pushContext(new PairContext()); + } + + void readJsonObjectEnd() { + readJsonSyntaxChar(OBJECT_END); + popContext(); + } + + void readJsonArrayBegin() { + context_.read(reader_); + readJsonSyntaxChar(ARRAY_BEGIN); + pushContext(new ListContext()); + } + + void readJsonArrayEnd() { + readJsonSyntaxChar(ARRAY_END); + popContext(); + } + + static { + final class LookaheadReader { + this(Transport trans) { + trans_ = trans; + } + + ubyte[1] read() { + if (hasData_) { + hasData_ = false; + } else { + trans_.readAll(data_); + } + return data_; + } + + ubyte[1] peek() { + if (!hasData_) { + trans_.readAll(data_); + hasData_ = true; + } + return data_; + } + + private: + Transport trans_; + bool hasData_; + ubyte[1] data_; + } + + /* + * Class to serve as base Json context and as base class for other context + * implementations + */ + class Context { + /** + * Write context data to the transport. Default is to do nothing. + */ + void write(Transport trans) {} + + /** + * Read context data from the transport. Default is to do nothing. + */ + void read(LookaheadReader reader) {} + + /** + * Return true if numbers need to be escaped as strings in this context. + * Default behavior is to return false. + */ + bool escapeNum() @property { + return false; + } + } + + // Context class for object member key-value pairs + class PairContext : Context { + this() { + first_ = true; + colon_ = true; + } + + override void write(Transport trans) { + if (first_) { + first_ = false; + colon_ = true; + } else { + trans.write(colon_ ? PAIR_SEP : ELEM_SEP); + colon_ = !colon_; + } + } + + override void read(LookaheadReader reader) { + if (first_) { + first_ = false; + colon_ = true; + } else { + auto ch = (colon_ ? PAIR_SEP : ELEM_SEP); + colon_ = !colon_; + return readSyntaxChar(reader, ch); + } + } + + // Numbers must be turned into strings if they are the key part of a pair + override bool escapeNum() @property { + return colon_; + } + + private: + bool first_; + bool colon_; + } + + class ListContext : Context { + this() { + first_ = true; + } + + override void write(Transport trans) { + if (first_) { + first_ = false; + } else { + trans.write(ELEM_SEP); + } + } + + override void read(LookaheadReader reader) { + if (first_) { + first_ = false; + } else { + readSyntaxChar(reader, ELEM_SEP); + } + } + + private: + bool first_; + } + + // Read 1 character from the transport trans and verify that it is the + // expected character ch. + // Throw a protocol exception if it is not. + void readSyntaxChar(LookaheadReader reader, ubyte[1] ch) { + auto ch2 = reader.read(); + if (ch2 != ch) { + throw new TProtocolException("Expected '" ~ cast(char)ch[0] ~ "', got '" ~ + cast(char)ch2[0] ~ "'.", TProtocolException.Type.INVALID_DATA); + } + } + } + + // Probably need to implement a better stack at some point. + Context[] contextStack_; + Context context_; + + Transport trans_; + LookaheadReader reader_; +} + +/** + * TJsonProtocol construction helper to avoid having to explicitly specify + * the transport type, i.e. to allow the constructor being called using IFTI + * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla + * enhancement requet 6082)). + */ +TJsonProtocol!Transport tJsonProtocol(Transport)(Transport trans, + int containerSizeLimit = 0, int stringSizeLimit = 0 +) if (isTTransport!Transport) { + return new TJsonProtocol!Transport(trans, containerSizeLimit, stringSizeLimit); +} + +unittest { + import std.exception; + import thrift.transport.memory; + + // Check the message header format. + auto buf = new TMemoryBuffer; + auto json = tJsonProtocol(buf); + json.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0)); + json.writeMessageEnd(); + + auto header = new ubyte[13]; + buf.readAll(header); + enforce(cast(char[])header == `[1,"foo",1,0]`); +} + +unittest { + import std.exception; + import thrift.transport.memory; + + // Check that short binary data is read correctly (the Thrift JSON format + // does not include padding chars in the Base64 encoded data). + auto buf = new TMemoryBuffer; + auto json = tJsonProtocol(buf); + json.writeBinary([1, 2]); + json.reset(); + enforce(json.readBinary() == [1, 2]); +} + +unittest { + import std.exception; + import thrift.transport.memory; + + auto buf = new TMemoryBuffer(cast(ubyte[])"\"\\u0e01 \\ud835\\udd3e\""); + auto json = tJsonProtocol(buf); + auto str = json.readString(); + enforce(str == "ภð”¾"); +} + +unittest { + // Thrown if low surrogate is missing. + import std.exception; + import thrift.transport.memory; + + auto buf = new TMemoryBuffer(cast(ubyte[])"\"\\u0e01 \\ud835\""); + auto json = tJsonProtocol(buf); + assertThrown!TProtocolException(json.readString()); +} + +unittest { + // Thrown if high surrogate is missing. + import std.exception; + import thrift.transport.memory; + + auto buf = new TMemoryBuffer(cast(ubyte[])"\"\\u0e01 \\udd3e\""); + auto json = tJsonProtocol(buf); + assertThrown!TProtocolException(json.readString()); +} + +unittest { + import thrift.internal.test.protocol; + testContainerSizeLimit!(TJsonProtocol!())(); + testStringSizeLimit!(TJsonProtocol!())(); +} + +/** + * TProtocolFactory creating a TJsonProtocol instance for passed in + * transports. + * + * The optional Transports template tuple parameter can be used to specify + * one or more TTransport implementations to specifically instantiate + * TJsonProtocol for. If the actual transport types encountered at + * runtime match one of the transports in the list, a specialized protocol + * instance is created. Otherwise, a generic TTransport version is used. + */ +class TJsonProtocolFactory(Transports...) if ( + allSatisfy!(isTTransport, Transports) +) : TProtocolFactory { + TProtocol getProtocol(TTransport trans) const { + foreach (Transport; TypeTuple!(Transports, TTransport)) { + auto concreteTrans = cast(Transport)trans; + if (concreteTrans) { + auto p = new TJsonProtocol!Transport(concreteTrans); + return p; + } + } + throw new TProtocolException( + "Passed null transport to TJsonProtocolFactoy."); + } +} + +private { + immutable ubyte[1] OBJECT_BEGIN = '{'; + immutable ubyte[1] OBJECT_END = '}'; + immutable ubyte[1] ARRAY_BEGIN = '['; + immutable ubyte[1] ARRAY_END = ']'; + immutable ubyte[1] NEWLINE = '\n'; + immutable ubyte[1] PAIR_SEP = ':'; + immutable ubyte[1] ELEM_SEP = ','; + immutable ubyte[1] BACKSLASH = '\\'; + immutable ubyte[1] STRING_DELIMITER = '"'; + immutable ubyte[1] ZERO_CHAR = '0'; + immutable ubyte[1] ESCAPE_CHAR = 'u'; + immutable ubyte[4] ESCAPE_PREFIX = cast(ubyte[4])r"\u00"; + + enum THRIFT_JSON_VERSION = 1; + + immutable NAN_STRING = "NaN"; + immutable INFINITY_STRING = "Infinity"; + immutable NEG_INFINITY_STRING = "-Infinity"; + + string getNameFromTType(TType typeID) { + final switch (typeID) { + case TType.BOOL: + return "tf"; + case TType.BYTE: + return "i8"; + case TType.I16: + return "i16"; + case TType.I32: + return "i32"; + case TType.I64: + return "i64"; + case TType.DOUBLE: + return "dbl"; + case TType.STRING: + return "str"; + case TType.STRUCT: + return "rec"; + case TType.MAP: + return "map"; + case TType.LIST: + return "lst"; + case TType.SET: + return "set"; + case TType.STOP: goto case; + case TType.VOID: + assert(false, "Invalid type passed."); + } + } + + TType getTTypeFromName(string name) { + TType result; + if (name.length > 1) { + switch (name[0]) { + case 'd': + result = TType.DOUBLE; + break; + case 'i': + switch (name[1]) { + case '8': + result = TType.BYTE; + break; + case '1': + result = TType.I16; + break; + case '3': + result = TType.I32; + break; + case '6': + result = TType.I64; + break; + default: + // Do nothing. + } + break; + case 'l': + result = TType.LIST; + break; + case 'm': + result = TType.MAP; + break; + case 'r': + result = TType.STRUCT; + break; + case 's': + if (name[1] == 't') { + result = TType.STRING; + } + else if (name[1] == 'e') { + result = TType.SET; + } + break; + case 't': + result = TType.BOOL; + break; + default: + // Do nothing. + } + } + if (result == TType.STOP) { + throw new TProtocolException("Unrecognized type", + TProtocolException.Type.NOT_IMPLEMENTED); + } + return result; + } + + // This table describes the handling for the first 0x30 characters + // 0 : escape using "\u00xx" notation + // 1 : just output index + // <other> : escape using "\<other>" notation + immutable ubyte[0x30] kJsonCharTable = [ + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 0, 0, 0, 0, 0, 0, 0,'b','t','n', 0,'f','r', 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + 1, 1,'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + ]; + + // This string's characters must match up with the elements in kEscapeCharVals. + // I don't have '/' on this list even though it appears on www.json.org -- + // it is not in the RFC + immutable kEscapeChars = cast(ubyte[7]) `"\\bfnrt`; + + // The elements of this array must match up with the sequence of characters in + // kEscapeChars + immutable ubyte[7] kEscapeCharVals = [ + '"', '\\', '\b', '\f', '\n', '\r', '\t', + ]; + + // Return the integer value of a hex character ch. + // Throw a protocol exception if the character is not [0-9a-f]. + ubyte hexVal(ubyte ch) { + if ((ch >= '0') && (ch <= '9')) { + return cast(ubyte)(ch - '0'); + } else if ((ch >= 'a') && (ch <= 'f')) { + return cast(ubyte)(ch - 'a' + 10); + } + else { + throw new TProtocolException("Expected hex val ([0-9a-f]), got '" ~ + ch ~ "'.", TProtocolException.Type.INVALID_DATA); + } + } + + // Return the hex character representing the integer val. The value is masked + // to make sure it is in the correct range. + ubyte hexChar(ubyte val) { + val &= 0x0F; + if (val < 10) { + return cast(ubyte)(val + '0'); + } else { + return cast(ubyte)(val - 10 + 'a'); + } + } + + // Return true if the character ch is in [-+0-9.Ee]; false otherwise + bool isJsonNumeric(ubyte ch) { + switch (ch) { + case '+': + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'E': + case 'e': + return true; + default: + return false; + } + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/protocol/processor.d b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/processor.d new file mode 100644 index 000000000..887421cc8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/protocol/processor.d @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.protocol.processor; + +// Use selective import once DMD @@BUG314@@ is fixed. +import std.variant /+ : Variant +/; +import thrift.protocol.base; +import thrift.transport.base; + +/** + * A processor is a generic object which operates upon an input stream and + * writes to some output stream. + * + * The definition of this object is loose, though the typical case is for some + * sort of server that either generates responses to an input stream or + * forwards data from one pipe onto another. + * + * An implementation can optionally allow one or more TProcessorEventHandlers + * to be attached, providing an interface to hook custom code into the + * handling process, which can be used e.g. for gathering statistics. + */ +interface TProcessor { + /// + bool process(TProtocol iprot, TProtocol oprot, + Variant connectionContext = Variant() + ) in { + assert(iprot); + assert(oprot); + } + + /// + final bool process(TProtocol prot, Variant connectionContext = Variant()) { + return process(prot, prot, connectionContext); + } +} + +/** + * Handles events from a processor. + */ +interface TProcessorEventHandler { + /** + * Called before calling other callback methods. + * + * Expected to return some sort of »call context«, which is passed to all + * other callbacks for that function invocation. + */ + Variant createContext(string methodName, Variant connectionContext); + + /** + * Called when handling the method associated with a context has been + * finished – can be used to perform clean up work. + */ + void deleteContext(Variant callContext, string methodName); + + /** + * Called before reading arguments. + */ + void preRead(Variant callContext, string methodName); + + /** + * Called between reading arguments and calling the handler. + */ + void postRead(Variant callContext, string methodName); + + /** + * Called between calling the handler and writing the response. + */ + void preWrite(Variant callContext, string methodName); + + /** + * Called after writing the response. + */ + void postWrite(Variant callContext, string methodName); + + /** + * Called when handling a one-way function call is completed successfully. + */ + void onewayComplete(Variant callContext, string methodName); + + /** + * Called if the handler throws an undeclared exception. + */ + void handlerError(Variant callContext, string methodName, Exception e); +} + +struct TConnectionInfo { + /// The input and output protocols. + TProtocol input; + TProtocol output; /// Ditto. + + /// The underlying transport used for the connection + /// This is the transport that was returned by TServerTransport.accept(), + /// and it may be different than the transport pointed to by the input and + /// output protocols. + TTransport transport; +} + +interface TProcessorFactory { + /** + * Get the TProcessor to use for a particular connection. + * + * This method is always invoked in the same thread that the connection was + * accepted on, which is always the same thread for all current server + * implementations. + */ + TProcessor getProcessor(ref const(TConnectionInfo) connInfo); +} + +/** + * The default processor factory which always returns the same instance. + */ +class TSingletonProcessorFactory : TProcessorFactory { + /** + * Creates a new instance. + * + * Params: + * processor = The processor object to return from getProcessor(). + */ + this(TProcessor processor) { + processor_ = processor; + } + + override TProcessor getProcessor(ref const(TConnectionInfo) connInfo) { + return processor_; + } + +private: + TProcessor processor_; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/server/base.d b/src/jaegertracing/thrift/lib/d/src/thrift/server/base.d new file mode 100644 index 000000000..a23b1c7f2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/server/base.d @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.server.base; + +import std.variant : Variant; +import thrift.protocol.base; +import thrift.protocol.binary; +import thrift.protocol.processor; +import thrift.server.transport.base; +import thrift.transport.base; +import thrift.util.cancellation; + +/** + * Base class for all Thrift servers. + * + * By setting the eventHandler property to a TServerEventHandler + * implementation, custom code can be integrated into the processing pipeline, + * which can be used e.g. for gathering statistics. + */ +class TServer { + /** + * Starts serving. + * + * Blocks until the server finishes, i.e. a serious problem occurred or the + * cancellation request has been triggered. + * + * Server implementations are expected to implement cancellation in a best- + * effort way – usually, it should be possible to immediately stop accepting + * connections and return after all currently active clients have been + * processed, but this might not be the case for every conceivable + * implementation. + */ + abstract void serve(TCancellation cancellation = null); + + /// The server event handler to notify. Null by default. + TServerEventHandler eventHandler; + +protected: + this( + TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory + ) { + this(processor, serverTransport, transportFactory, transportFactory, + protocolFactory, protocolFactory); + } + + this( + TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory + ) { + this(processorFactory, serverTransport, transportFactory, transportFactory, + protocolFactory, protocolFactory); + } + + this( + TProcessor processor, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory + ) { + this(new TSingletonProcessorFactory(processor), serverTransport, + inputTransportFactory, outputTransportFactory, + inputProtocolFactory, outputProtocolFactory); + } + + this( + TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory + ) { + import std.exception; + import thrift.base; + enforce(inputTransportFactory, + new TException("Input transport factory must not be null.")); + enforce(outputTransportFactory, + new TException("Output transport factory must not be null.")); + enforce(inputProtocolFactory, + new TException("Input protocol factory must not be null.")); + enforce(outputProtocolFactory, + new TException("Output protocol factory must not be null.")); + + processorFactory_ = processorFactory; + serverTransport_ = serverTransport; + inputTransportFactory_ = inputTransportFactory; + outputTransportFactory_ = outputTransportFactory; + inputProtocolFactory_ = inputProtocolFactory; + outputProtocolFactory_ = outputProtocolFactory; + } + + TProcessorFactory processorFactory_; + TServerTransport serverTransport_; + TTransportFactory inputTransportFactory_; + TTransportFactory outputTransportFactory_; + TProtocolFactory inputProtocolFactory_; + TProtocolFactory outputProtocolFactory_; + +public: + + @property TProcessorFactory processorFactory() + { + return processorFactory_; + } + + @property TServerTransport serverTransport() + { + return serverTransport_; + } + + @property TTransportFactory inputTransportFactory() + { + return inputTransportFactory_; + } + + @property TTransportFactory outputTransportFactory() + { + return outputTransportFactory_; + } + + @property TProtocolFactory inputProtocolFactory() + { + return inputProtocolFactory_; + } + + @property TProtocolFactory outputProtocolFactory() + { + return outputProtocolFactory_; + } +} + +/** + * Handles events from a TServer core. + */ +interface TServerEventHandler { + /** + * Called before the server starts accepting connections. + */ + void preServe(); + + /** + * Called when a new client has connected and processing is about to begin. + */ + Variant createContext(TProtocol input, TProtocol output); + + /** + * Called when request handling for a client has been finished – can be used + * to perform clean up work. + */ + void deleteContext(Variant serverContext, TProtocol input, TProtocol output); + + /** + * Called when the processor for a client call is about to be invoked. + */ + void preProcess(Variant serverContext, TTransport transport); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/server/nonblocking.d b/src/jaegertracing/thrift/lib/d/src/thrift/server/nonblocking.d new file mode 100644 index 000000000..5860c0c42 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/server/nonblocking.d @@ -0,0 +1,1397 @@ +/* + * 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. + */ + +/** + * A non-blocking server implementation that operates a set of I/O threads (by + * default only one) and either does processing »in-line« or off-loads it to a + * task pool. + * + * It *requires* TFramedTransport to be used on the client side, as it expects + * a 4 byte length indicator and writes out responses using the same framing. + * + * Because I/O is done asynchronous/event based, unfortunately + * TServerTransport can't be used. + * + * This implementation is based on the C++ one, with the exception of request + * timeouts and the drain task queue overload handling strategy not being + * implemented yet. + */ +// This really should use a D non-blocking I/O library, once one becomes +// available. +module thrift.server.nonblocking; + +import core.atomic : atomicLoad, atomicStore, atomicOp; +import core.exception : onOutOfMemoryError; +import core.memory : GC; +import core.sync.mutex; +import core.stdc.stdlib : free, realloc; +import core.time : Duration, dur; +import core.thread : Thread, ThreadGroup; +import deimos.event2.event; +import std.array : empty; +import std.conv : emplace, to; +import std.exception : enforce; +import std.parallelism : TaskPool, task; +import std.socket : Socket, socketPair, SocketAcceptException, + SocketException, TcpSocket; +import std.variant : Variant; +import thrift.base; +import thrift.internal.endian; +import thrift.internal.socket; +import thrift.internal.traits; +import thrift.protocol.base; +import thrift.protocol.binary; +import thrift.protocol.processor; +import thrift.server.base; +import thrift.server.transport.socket; +import thrift.transport.base; +import thrift.transport.memory; +import thrift.transport.range; +import thrift.transport.socket; +import thrift.util.cancellation; + +/** + * Possible actions taken on new incoming connections when the server is + * overloaded. + */ +enum TOverloadAction { + /// Do not take any special actions while the server is overloaded, just + /// continue accepting connections. + NONE, + + /// Immediately drop new connections after they have been accepted if the + /// server is overloaded. + CLOSE_ON_ACCEPT +} + +/// +class TNonblockingServer : TServer { + /// + this(TProcessor processor, ushort port, TTransportFactory transportFactory, + TProtocolFactory protocolFactory, TaskPool taskPool = null + ) { + this(new TSingletonProcessorFactory(processor), port, transportFactory, + transportFactory, protocolFactory, protocolFactory, taskPool); + } + + /// + this(TProcessorFactory processorFactory, ushort port, + TTransportFactory transportFactory, TProtocolFactory protocolFactory, + TaskPool taskPool = null + ) { + this(processorFactory, port, transportFactory, transportFactory, + protocolFactory, protocolFactory, taskPool); + } + + /// + this( + TProcessor processor, + ushort port, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + TaskPool taskPool = null + ) { + this(new TSingletonProcessorFactory(processor), port, + inputTransportFactory, outputTransportFactory, + inputProtocolFactory, outputProtocolFactory, taskPool); + } + + /// + this( + TProcessorFactory processorFactory, + ushort port, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + TaskPool taskPool = null + ) { + super(processorFactory, null, inputTransportFactory, + outputTransportFactory, inputProtocolFactory, outputProtocolFactory); + port_ = port; + + this.taskPool = taskPool; + + connectionMutex_ = new Mutex; + + connectionStackLimit = DEFAULT_CONNECTION_STACK_LIMIT; + maxActiveProcessors = DEFAULT_MAX_ACTIVE_PROCESSORS; + maxConnections = DEFAULT_MAX_CONNECTIONS; + overloadHysteresis = DEFAULT_OVERLOAD_HYSTERESIS; + overloadAction = DEFAULT_OVERLOAD_ACTION; + writeBufferDefaultSize = DEFAULT_WRITE_BUFFER_DEFAULT_SIZE; + idleReadBufferLimit = DEFAULT_IDLE_READ_BUFFER_LIMIT; + idleWriteBufferLimit = DEFAULT_IDLE_WRITE_BUFFER_LIMIT; + resizeBufferEveryN = DEFAULT_RESIZE_BUFFER_EVERY_N; + maxFrameSize = DEFAULT_MAX_FRAME_SIZE; + numIOThreads_ = DEFAULT_NUM_IO_THREADS; + } + + override void serve(TCancellation cancellation = null) { + if (cancellation && cancellation.triggered) return; + + // Initialize the listening socket. + // TODO: SO_KEEPALIVE, TCP_LOW_MIN_RTO, etc. + listenSocket_ = makeSocketAndListen(port_, TServerSocket.ACCEPT_BACKLOG, + BIND_RETRY_LIMIT, BIND_RETRY_DELAY, 0, 0, ipv6Only_); + listenSocket_.blocking = false; + + logInfo("Using %s I/O thread(s).", numIOThreads_); + if (taskPool_) { + logInfo("Using task pool with size: %s.", numIOThreads_, taskPool_.size); + } + + assert(numIOThreads_ > 0); + assert(ioLoops_.empty); + foreach (id; 0 .. numIOThreads_) { + // The IO loop on the first IO thread (this thread, i.e. the one serve() + // is called from) also accepts new connections. + auto listenSocket = (id == 0 ? listenSocket_ : null); + ioLoops_ ~= new IOLoop(this, listenSocket); + } + + if (cancellation) { + cancellation.triggering.addCallback({ + foreach (i, loop; ioLoops_) loop.stop(); + + // Stop accepting new connections right away. + listenSocket_.close(); + listenSocket_ = null; + }); + } + + // Start the IO helper threads for all but the first loop, which we will run + // ourselves. Note that the threads run forever, only terminating if stop() + // is called. + auto threads = new ThreadGroup(); + foreach (loop; ioLoops_[1 .. $]) { + auto t = new Thread(&loop.run); + threads.add(t); + t.start(); + } + + if (eventHandler) eventHandler.preServe(); + + // Run the primary (listener) IO thread loop in our main thread; this will + // block until the server is shutting down. + ioLoops_[0].run(); + + // Ensure all threads are finished before leaving serve(). + threads.joinAll(); + + ioLoops_ = null; + } + + /** + * Returns the number of currently active connections, i.e. open sockets. + */ + size_t numConnections() const @property { + return numConnections_; + } + + /** + * Returns the number of connection objects allocated, but not in use. + */ + size_t numIdleConnections() const @property { + return connectionStack_.length; + } + + /** + * Return count of number of connections which are currently processing. + * + * This is defined as a connection where all data has been received, and the + * processor was invoked but has not yet completed. + */ + size_t numActiveProcessors() const @property { + return numActiveProcessors_; + } + + /// Number of bind() retries. + enum BIND_RETRY_LIMIT = 0; + + /// Duration between bind() retries. + enum BIND_RETRY_DELAY = dur!"hnsecs"(0); + + /// Whether to listen on IPv6 only, if IPv6 support is detected + // (default: false). + void ipv6Only(bool value) @property { + ipv6Only_ = value; + } + + /** + * The task pool to use for processing requests. If null, no additional + * threads are used and request are processed »inline«. + * + * Can safely be set even when the server is already running. + */ + TaskPool taskPool() @property { + return taskPool_; + } + + /// ditto + void taskPool(TaskPool pool) @property { + taskPool_ = pool; + } + + /** + * Hysteresis for overload state. + * + * This is the fraction of the overload value that needs to be reached + * before the overload state is cleared. It must be between 0 and 1, + * practical choices probably lie between 0.5 and 0.9. + */ + double overloadHysteresis() const @property { + return overloadHysteresis_; + } + + /// Ditto + void overloadHysteresis(double value) @property { + enforce(0 < value && value <= 1, + "Invalid value for overload hysteresis: " ~ to!string(value)); + overloadHysteresis_ = value; + } + + /// Ditto + enum DEFAULT_OVERLOAD_HYSTERESIS = 0.8; + + /** + * The action which will be taken on overload. + */ + TOverloadAction overloadAction; + + /// Ditto + enum DEFAULT_OVERLOAD_ACTION = TOverloadAction.NONE; + + /** + * The write buffer is initialized (and when idleWriteBufferLimit_ is checked + * and found to be exceeded, reinitialized) to this size. + */ + size_t writeBufferDefaultSize; + + /// Ditto + enum size_t DEFAULT_WRITE_BUFFER_DEFAULT_SIZE = 1024; + + /** + * Max read buffer size for an idle Connection. When we place an idle + * Connection into connectionStack_ or on every resizeBufferEveryN_ calls, + * we will free the buffer (such that it will be reinitialized by the next + * received frame) if it has exceeded this limit. 0 disables this check. + */ + size_t idleReadBufferLimit; + + /// Ditto + enum size_t DEFAULT_IDLE_READ_BUFFER_LIMIT = 1024; + + /** + * Max write buffer size for an idle connection. When we place an idle + * Connection into connectionStack_ or on every resizeBufferEveryN_ calls, + * we ensure that its write buffer is <= to this size; otherwise we + * replace it with a new one of writeBufferDefaultSize_ bytes to ensure that + * idle connections don't hog memory. 0 disables this check. + */ + size_t idleWriteBufferLimit; + + /// Ditto + enum size_t DEFAULT_IDLE_WRITE_BUFFER_LIMIT = 1024; + + /** + * Every N calls we check the buffer size limits on a connected Connection. + * 0 disables (i.e. the checks are only done when a connection closes). + */ + uint resizeBufferEveryN; + + /// Ditto + enum uint DEFAULT_RESIZE_BUFFER_EVERY_N = 512; + + /// Limit for how many Connection objects to cache. + size_t connectionStackLimit; + + /// Ditto + enum size_t DEFAULT_CONNECTION_STACK_LIMIT = 1024; + + /// Limit for number of open connections before server goes into overload + /// state. + size_t maxConnections; + + /// Ditto + enum size_t DEFAULT_MAX_CONNECTIONS = int.max; + + /// Limit for number of connections processing or waiting to process + size_t maxActiveProcessors; + + /// Ditto + enum size_t DEFAULT_MAX_ACTIVE_PROCESSORS = int.max; + + /// Maximum frame size, in bytes. + /// + /// If a client tries to send a message larger than this limit, its + /// connection will be closed. This helps to avoid allocating huge buffers + /// on bogous input. + uint maxFrameSize; + + /// Ditto + enum uint DEFAULT_MAX_FRAME_SIZE = 256 * 1024 * 1024; + + + size_t numIOThreads() @property { + return numIOThreads_; + } + + void numIOThreads(size_t value) @property { + enforce(value >= 1, new TException("Must use at least one I/O thread.")); + numIOThreads_ = value; + } + + enum DEFAULT_NUM_IO_THREADS = 1; + +private: + /** + * C callback wrapper around acceptConnections(). Expects the custom argument + * to be the this pointer of the associated server instance. + */ + extern(C) static void acceptConnectionsCallback(int fd, short which, + void* serverThis + ) { + (cast(TNonblockingServer)serverThis).acceptConnections(fd, which); + } + + /** + * Called by libevent (IO loop 0/serve() thread only) when something + * happened on the listening socket. + */ + void acceptConnections(int fd, short eventFlags) { + if (atomicLoad(ioLoops_[0].shuttingDown_)) return; + + assert(!!listenSocket_, + "Server should be shutting down if listen socket is null."); + assert(fd == listenSocket_.handle); + assert(eventFlags & EV_READ); + + // Accept as many new clients as possible, even though libevent signaled + // only one. This helps the number of calls into libevent space. + while (true) { + // It is lame to use exceptions for regular control flow (failing is + // excepted due to non-blocking mode of operation), but that's the + // interface std.socket offers… + Socket clientSocket; + try { + clientSocket = listenSocket_.accept(); + } catch (SocketAcceptException e) { + if (e.errorCode != WOULD_BLOCK_ERRNO) { + logError("Error accepting connection: %s", e); + } + break; + } + + // If the server is overloaded, this is the point to take the specified + // action. + if (overloadAction != TOverloadAction.NONE && checkOverloaded()) { + nConnectionsDropped_++; + nTotalConnectionsDropped_++; + if (overloadAction == TOverloadAction.CLOSE_ON_ACCEPT) { + clientSocket.close(); + return; + } + } + + try { + clientSocket.blocking = false; + } catch (SocketException e) { + logError("Couldn't set client socket to non-blocking mode: %s", e); + clientSocket.close(); + return; + } + + // Create a new Connection for this client socket. + Connection conn = void; + IOLoop loop = void; + bool thisThread = void; + synchronized (connectionMutex_) { + // Assign an I/O loop to the connection (round-robin). + assert(nextIOLoop_ >= 0); + assert(nextIOLoop_ < ioLoops_.length); + auto selectedThreadIdx = nextIOLoop_; + nextIOLoop_ = (nextIOLoop_ + 1) % ioLoops_.length; + + loop = ioLoops_[selectedThreadIdx]; + thisThread = (selectedThreadIdx == 0); + + // Check the connection stack to see if we can re-use an existing one. + if (connectionStack_.empty) { + ++numConnections_; + conn = new Connection(clientSocket, loop); + + // Make sure the connection does not get collected while it is active, + // i.e. hooked up with libevent. + GC.addRoot(cast(void*)conn); + } else { + conn = connectionStack_[$ - 1]; + connectionStack_ = connectionStack_[0 .. $ - 1]; + connectionStack_.assumeSafeAppend(); + conn.init(clientSocket, loop); + } + } + + loop.addConnection(); + + // Either notify the ioThread that is assigned this connection to + // start processing, or if it is us, we'll just ask this + // connection to do its initial state change here. + // + // (We need to avoid writing to our own notification pipe, to + // avoid possible deadlocks if the pipe is full.) + if (thisThread) { + conn.transition(); + } else { + loop.notifyCompleted(conn); + } + } + } + + /// Increment the count of connections currently processing. + void incrementActiveProcessors() { + atomicOp!"+="(numActiveProcessors_, 1); + } + + /// Decrement the count of connections currently processing. + void decrementActiveProcessors() { + assert(numActiveProcessors_ > 0); + atomicOp!"-="(numActiveProcessors_, 1); + } + + /** + * Determines if the server is currently overloaded. + * + * If the number of open connections or »processing« connections is over the + * respective limit, the server will enter overload handling mode and a + * warning will be logged. If below values are below the hysteresis curve, + * this will cause the server to exit it again. + * + * Returns: Whether the server is currently overloaded. + */ + bool checkOverloaded() { + auto activeConnections = numConnections_ - connectionStack_.length; + if (numActiveProcessors_ > maxActiveProcessors || + activeConnections > maxConnections) { + if (!overloaded_) { + logInfo("Entering overloaded state."); + overloaded_ = true; + } + } else { + if (overloaded_ && + (numActiveProcessors_ <= overloadHysteresis_ * maxActiveProcessors) && + (activeConnections <= overloadHysteresis_ * maxConnections)) + { + logInfo("Exiting overloaded state, %s connection(s) dropped (% total).", + nConnectionsDropped_, nTotalConnectionsDropped_); + nConnectionsDropped_ = 0; + overloaded_ = false; + } + } + + return overloaded_; + } + + /** + * Marks a connection as inactive and either puts it back into the + * connection pool or leaves it for garbage collection. + */ + void disposeConnection(Connection connection) { + synchronized (connectionMutex_) { + if (!connectionStackLimit || + (connectionStack_.length < connectionStackLimit)) + { + connection.checkIdleBufferLimit(idleReadBufferLimit, + idleWriteBufferLimit); + connectionStack_ ~= connection; + } else { + assert(numConnections_ > 0); + --numConnections_; + + // Leave the connection object for collection now. + GC.removeRoot(cast(void*)connection); + } + } + } + + /// Socket used to listen for connections and accepting them. + Socket listenSocket_; + + /// Port to listen on. + ushort port_; + + /// Whether to listen on IPv6 only. + bool ipv6Only_; + + /// The total number of connections existing, both active and idle. + size_t numConnections_; + + /// The number of connections which are currently waiting for the processor + /// to return. + shared size_t numActiveProcessors_; + + /// Hysteresis for leaving overload state. + double overloadHysteresis_; + + /// Whether the server is currently overloaded. + bool overloaded_; + + /// Number of connections dropped since the server entered the current + /// overloaded state. + uint nConnectionsDropped_; + + /// Number of connections dropped due to overload since the server started. + ulong nTotalConnectionsDropped_; + + /// The task pool used for processing requests. + TaskPool taskPool_; + + /// Number of IO threads this server will use (>= 1). + size_t numIOThreads_; + + /// The IOLoops among which socket handling work is distributed. + IOLoop[] ioLoops_; + + /// The index of the loop in ioLoops_ which will handle the next accepted + /// connection. + size_t nextIOLoop_; + + /// All the connection objects which have been created but are not currently + /// in use. When a connection is closed, it it placed here to enable object + /// (resp. buffer) reuse. + Connection[] connectionStack_; + + /// This mutex protects the connection stack. + Mutex connectionMutex_; +} + +private { + /* + * Encapsulates a libevent event loop. + * + * The design is a bit of a mess, since the first loop is actually run on the + * server thread itself and is special because it is the only instance for + * which listenSocket_ is not null. + */ + final class IOLoop { + /** + * Creates a new instance and set up the event base. + * + * If listenSocket is not null, the thread will also accept new + * connections itself. + */ + this(TNonblockingServer server, Socket listenSocket) { + server_ = server; + listenSocket_ = listenSocket; + initMutex_ = new Mutex; + } + + /** + * Runs the event loop; only returns after a call to stop(). + */ + void run() { + assert(!atomicLoad(initialized_), "IOLoop already running?!"); + + synchronized (initMutex_) { + if (atomicLoad(shuttingDown_)) return; + atomicStore(initialized_, true); + + assert(!eventBase_); + eventBase_ = event_base_new(); + + if (listenSocket_) { + // Log the libevent version and backend. + logInfo("libevent version %s, using method %s.", + to!string(event_get_version()), to!string(event_base_get_method(eventBase_))); + + // Register the event for the listening socket. + listenEvent_ = event_new(eventBase_, listenSocket_.handle, + EV_READ | EV_PERSIST | EV_ET, + assumeNothrow(&TNonblockingServer.acceptConnectionsCallback), + cast(void*)server_); + if (event_add(listenEvent_, null) == -1) { + throw new TException("event_add for the listening socket event failed."); + } + } + + auto pair = socketPair(); + foreach (s; pair) s.blocking = false; + completionSendSocket_ = pair[0]; + completionReceiveSocket_ = pair[1]; + + // Register an event for the task completion notification socket. + completionEvent_ = event_new(eventBase_, completionReceiveSocket_.handle, + EV_READ | EV_PERSIST | EV_ET, assumeNothrow(&completedCallback), + cast(void*)this); + + if (event_add(completionEvent_, null) == -1) { + throw new TException("event_add for the notification socket failed."); + } + } + + // Run libevent engine, returns only after stop(). + event_base_dispatch(eventBase_); + + if (listenEvent_) { + event_free(listenEvent_); + listenEvent_ = null; + } + + event_free(completionEvent_); + completionEvent_ = null; + + completionSendSocket_.close(); + completionSendSocket_ = null; + + completionReceiveSocket_.close(); + completionReceiveSocket_ = null; + + event_base_free(eventBase_); + eventBase_ = null; + + atomicStore(shuttingDown_, false); + + initialized_ = false; + } + + /** + * Adds a new connection handled by this loop. + */ + void addConnection() { + ++numActiveConnections_; + } + + /** + * Disposes a connection object (typically after it has been closed). + */ + void disposeConnection(Connection conn) { + server_.disposeConnection(conn); + assert(numActiveConnections_ > 0); + --numActiveConnections_; + if (numActiveConnections_ == 0) { + if (atomicLoad(shuttingDown_)) { + event_base_loopbreak(eventBase_); + } + } + } + + /** + * Notifies the event loop that the current step (initialization, + * processing of a request) on a certain connection has been completed. + * + * This function is thread-safe, but should never be called from the + * thread running the loop itself. + */ + void notifyCompleted(Connection conn) { + assert(!!completionSendSocket_); + auto bytesSent = completionSendSocket_.send(cast(ubyte[])((&conn)[0 .. 1])); + + if (bytesSent != Connection.sizeof) { + logError("Sending completion notification failed, connection will " ~ + "not be properly terminated."); + } + } + + /** + * Exits the event loop after all currently active connections have been + * closed. + * + * This function is thread-safe. + */ + void stop() { + // There is a bug in either libevent or its documentation, having no + // events registered doesn't actually terminate the loop, because + // event_base_new() registers some internal one by calling + // evthread_make_base_notifiable(). + // Due to this, we can't simply remove all events and expect the event + // loop to terminate. Instead, we ping the event loop using a null + // completion message. This way, we make sure to wake up the libevent + // thread if it not currently processing any connections. It will break + // out of the loop in disposeConnection() after the last active + // connection has been closed. + synchronized (initMutex_) { + atomicStore(shuttingDown_, true); + if (atomicLoad(initialized_)) notifyCompleted(null); + } + } + + private: + /** + * C callback to call completed() from libevent. + * + * Expects the custom argument to be the this pointer of the associated + * IOLoop instance. + */ + extern(C) static void completedCallback(int fd, short what, void* loopThis) { + assert(what & EV_READ); + auto loop = cast(IOLoop)loopThis; + assert(fd == loop.completionReceiveSocket_.handle); + loop.completed(); + } + + /** + * Reads from the completion receive socket and appropriately transitions + * the connections and shuts down the loop if requested. + */ + void completed() { + Connection connection; + ptrdiff_t bytesRead; + while (true) { + bytesRead = completionReceiveSocket_.receive( + cast(ubyte[])((&connection)[0 .. 1])); + if (bytesRead < 0) { + auto errno = getSocketErrno(); + + if (errno != WOULD_BLOCK_ERRNO) { + logError("Reading from completion socket failed, some connection " ~ + "will never be properly terminated: %s", socketErrnoString(errno)); + } + } + + if (bytesRead != Connection.sizeof) break; + + if (!connection) { + assert(atomicLoad(shuttingDown_)); + if (numActiveConnections_ == 0) { + event_base_loopbreak(eventBase_); + } + continue; + } + + connection.transition(); + } + + if (bytesRead > 0) { + logError("Unexpected partial read from completion socket " ~ + "(%s bytes instead of %s).", bytesRead, Connection.sizeof); + } + } + + /// associated server + TNonblockingServer server_; + + /// The managed listening socket, if any. + Socket listenSocket_; + + /// The libevent event base for the loop. + event_base* eventBase_; + + /// Triggered on listen socket events. + event* listenEvent_; + + /// Triggered on completion receive socket events. + event* completionEvent_; + + /// Socket used to send completion notification messages. Paired with + /// completionReceiveSocket_. + Socket completionSendSocket_; + + /// Socket used to send completion notification messages. Paired with + /// completionSendSocket_. + Socket completionReceiveSocket_; + + /// Whether the server is currently shutting down (i.e. the cancellation has + /// been triggered, but not all client connections have been closed yet). + shared bool shuttingDown_; + + /// The number of currently active client connections. + size_t numActiveConnections_; + + /// Guards loop startup so that the loop can be reliably shut down even if + /// another thread has just started to execute run(). Locked during + /// initialization in run(). When unlocked, the completion mechanism is + /// expected to be fully set up. + Mutex initMutex_; + shared bool initialized_; /// Ditto + } + + /* + * I/O states a socket can be in. + */ + enum SocketState { + RECV_FRAME_SIZE, /// The frame size is received. + RECV, /// The payload is received. + SEND /// The response is written back out. + } + + /* + * States a connection can be in. + */ + enum ConnectionState { + INIT, /// The connection will be initialized. + READ_FRAME_SIZE, /// The four frame size bytes are being read. + READ_REQUEST, /// The request payload itself is being read. + WAIT_PROCESSOR, /// The connection waits for the processor to finish. + SEND_RESULT /// The result is written back out. + } + + /* + * A connection that is handled via libevent. + * + * Data received is buffered until the request is complete (returning back to + * libevent if not), at which point the processor is invoked. + */ + final class Connection { + /** + * Constructs a new instance. + * + * To reuse a connection object later on, the init() function can be used + * to the same effect on the internal state. + */ + this(Socket socket, IOLoop loop) { + // The input and output transport objects are reused between clients + // connections, so initialize them here rather than in init(). + inputTransport_ = new TInputRangeTransport!(ubyte[])([]); + outputTransport_ = new TMemoryBuffer(loop.server_.writeBufferDefaultSize); + + init(socket, loop); + } + + /** + * Initializes the connection. + * + * Params: + * socket = The socket to work on. + * eventFlags = Any flags to pass to libevent. + * s = The server this connection is part of. + */ + void init(Socket socket, IOLoop loop) { + // TODO: This allocation could be avoided. + socket_ = new TSocket(socket); + + loop_ = loop; + server_ = loop_.server_; + connState_ = ConnectionState.INIT; + eventFlags_ = 0; + + readBufferPos_ = 0; + readWant_ = 0; + + writeBuffer_ = null; + writeBufferPos_ = 0; + largestWriteBufferSize_ = 0; + + socketState_ = SocketState.RECV_FRAME_SIZE; + callsSinceResize_ = 0; + + factoryInputTransport_ = + server_.inputTransportFactory.getTransport(inputTransport_); + factoryOutputTransport_ = + server_.outputTransportFactory.getTransport(outputTransport_); + + inputProtocol_ = + server_.inputProtocolFactory.getProtocol(factoryInputTransport_); + outputProtocol_ = + server_.outputProtocolFactory.getProtocol(factoryOutputTransport_); + + if (server_.eventHandler) { + connectionContext_ = + server_.eventHandler.createContext(inputProtocol_, outputProtocol_); + } + + auto info = TConnectionInfo(inputProtocol_, outputProtocol_, socket_); + processor_ = server_.processorFactory.getProcessor(info); + } + + ~this() { + free(readBuffer_); + if (event_) { + event_free(event_); + event_ = null; + } + } + + /** + * Check buffers against the size limits and shrink them if exceeded. + * + * Params: + * readLimit = Read buffer size limit (in bytes, 0 to ignore). + * writeLimit = Write buffer size limit (in bytes, 0 to ignore). + */ + void checkIdleBufferLimit(size_t readLimit, size_t writeLimit) { + if (readLimit > 0 && readBufferSize_ > readLimit) { + free(readBuffer_); + readBuffer_ = null; + readBufferSize_ = 0; + } + + if (writeLimit > 0 && largestWriteBufferSize_ > writeLimit) { + // just start over + outputTransport_.reset(server_.writeBufferDefaultSize); + largestWriteBufferSize_ = 0; + } + } + + /** + * Transitions the connection to the next state. + * + * This is called e.g. when the request has been read completely or all + * the data has been written back. + */ + void transition() { + assert(!!loop_); + assert(!!server_); + + // Switch upon the state that we are currently in and move to a new state + final switch (connState_) { + case ConnectionState.READ_REQUEST: + // We are done reading the request, package the read buffer into transport + // and get back some data from the dispatch function + inputTransport_.reset(readBuffer_[0 .. readBufferPos_]); + outputTransport_.reset(); + + // Prepend four bytes of blank space to the buffer so we can + // write the frame size there later. + // Strictly speaking, we wouldn't have to write anything, just + // increment the TMemoryBuffer writeOffset_. This would yield a tiny + // performance gain. + ubyte[4] space = void; + outputTransport_.write(space); + + server_.incrementActiveProcessors(); + + taskPool_ = server_.taskPool; + if (taskPool_) { + // Create a new task and add it to the task pool queue. + auto processingTask = task!processRequest(this); + connState_ = ConnectionState.WAIT_PROCESSOR; + taskPool_.put(processingTask); + + // We don't want to process any more data while the task is active. + unregisterEvent(); + return; + } + + // Just process it right now if there is no task pool set. + processRequest(this); + goto case; + case ConnectionState.WAIT_PROCESSOR: + // We have now finished processing the request, set the frame size + // for the outputTransport_ contents and set everything up to write + // it out via libevent. + server_.decrementActiveProcessors(); + + // Acquire the data written to the transport. + // KLUDGE: To avoid copying, we simply cast the const away and + // modify the internal buffer of the TMemoryBuffer – works with the + // current implementation, but isn't exactly beautiful. + writeBuffer_ = cast(ubyte[])outputTransport_.getContents(); + + assert(writeBuffer_.length >= 4, "The write buffer should have " ~ + "least the initially added dummy length bytes."); + if (writeBuffer_.length == 4) { + // The request was one-way, no response to write. + goto case ConnectionState.INIT; + } + + // Write the frame size into the four bytes reserved for it. + auto size = hostToNet(cast(uint)(writeBuffer_.length - 4)); + writeBuffer_[0 .. 4] = cast(ubyte[])((&size)[0 .. 1]); + + writeBufferPos_ = 0; + socketState_ = SocketState.SEND; + connState_ = ConnectionState.SEND_RESULT; + registerEvent(EV_WRITE | EV_PERSIST); + + return; + case ConnectionState.SEND_RESULT: + // The result has been sent back to the client, we don't need the + // buffers anymore. + if (writeBuffer_.length > largestWriteBufferSize_) { + largestWriteBufferSize_ = writeBuffer_.length; + } + + if (server_.resizeBufferEveryN > 0 && + ++callsSinceResize_ >= server_.resizeBufferEveryN + ) { + checkIdleBufferLimit(server_.idleReadBufferLimit, + server_.idleWriteBufferLimit); + callsSinceResize_ = 0; + } + + goto case; + case ConnectionState.INIT: + writeBuffer_ = null; + writeBufferPos_ = 0; + socketState_ = SocketState.RECV_FRAME_SIZE; + connState_ = ConnectionState.READ_FRAME_SIZE; + readBufferPos_ = 0; + registerEvent(EV_READ | EV_PERSIST); + + return; + case ConnectionState.READ_FRAME_SIZE: + // We just read the request length, set up the buffers for reading + // the payload. + if (readWant_ > readBufferSize_) { + // The current buffer is too small, exponentially grow the buffer + // until it is big enough. + + if (readBufferSize_ == 0) { + readBufferSize_ = 1; + } + + auto newSize = readBufferSize_; + while (readWant_ > newSize) { + newSize *= 2; + } + + auto newBuffer = cast(ubyte*)realloc(readBuffer_, newSize); + if (!newBuffer) onOutOfMemoryError(); + + readBuffer_ = newBuffer; + readBufferSize_ = newSize; + } + + readBufferPos_= 0; + + socketState_ = SocketState.RECV; + connState_ = ConnectionState.READ_REQUEST; + + return; + } + } + + private: + /** + * C callback to call workSocket() from libevent. + * + * Expects the custom argument to be the this pointer of the associated + * connection. + */ + extern(C) static void workSocketCallback(int fd, short flags, void* connThis) { + auto conn = cast(Connection)connThis; + assert(fd == conn.socket_.socketHandle); + conn.workSocket(); + } + + /** + * Invoked by libevent when something happens on the socket. + */ + void workSocket() { + final switch (socketState_) { + case SocketState.RECV_FRAME_SIZE: + // If some bytes have already been read, they have been kept in + // readWant_. + auto frameSize = readWant_; + + try { + // Read from the socket + auto bytesRead = socket_.read( + (cast(ubyte[])((&frameSize)[0 .. 1]))[readBufferPos_ .. $]); + if (bytesRead == 0) { + // Couldn't read anything, but we have been notified – client + // has disconnected. + close(); + return; + } + + readBufferPos_ += bytesRead; + } catch (TTransportException te) { + logError("Failed to read frame size from client connection: %s", te); + close(); + return; + } + + if (readBufferPos_ < frameSize.sizeof) { + // Frame size not complete yet, save the current buffer in + // readWant_ so that the remaining bytes can be read later. + readWant_ = frameSize; + return; + } + + auto size = netToHost(frameSize); + if (size > server_.maxFrameSize) { + logError("Frame size too large (%s > %s), client %s not using " ~ + "TFramedTransport?", size, server_.maxFrameSize, + socket_.getPeerAddress().toHostNameString()); + close(); + return; + } + readWant_ = size; + + // Now we know the frame size, set everything up for reading the + // payload. + transition(); + return; + + case SocketState.RECV: + // If we already got all the data, we should be in the SEND state. + assert(readBufferPos_ < readWant_); + + size_t bytesRead; + try { + // Read as much as possible from the socket. + bytesRead = socket_.read(readBuffer_[readBufferPos_ .. readWant_]); + } catch (TTransportException te) { + logError("Failed to read from client socket: %s", te); + close(); + return; + } + + if (bytesRead == 0) { + // We were notified, but no bytes could be read -> the client + // disconnected. + close(); + return; + } + + readBufferPos_ += bytesRead; + assert(readBufferPos_ <= readWant_); + + if (readBufferPos_ == readWant_) { + // The payload has been read completely, move on. + transition(); + } + + return; + case SocketState.SEND: + assert(writeBufferPos_ <= writeBuffer_.length); + + if (writeBufferPos_ == writeBuffer_.length) { + // Nothing left to send – this shouldn't happen, just move on. + logInfo("WARNING: In send state, but no data to send.\n"); + transition(); + return; + } + + size_t bytesSent; + try { + bytesSent = socket_.writeSome(writeBuffer_[writeBufferPos_ .. $]); + } catch (TTransportException te) { + logError("Failed to write to client socket: %s", te); + close(); + return; + } + + writeBufferPos_ += bytesSent; + assert(writeBufferPos_ <= writeBuffer_.length); + + if (writeBufferPos_ == writeBuffer_.length) { + // The whole response has been written out, we are done. + transition(); + } + + return; + } + } + + /** + * Registers a libevent event for workSocket() with the passed flags, + * unregistering the previous one (if any). + */ + void registerEvent(short eventFlags) { + if (eventFlags_ == eventFlags) { + // Nothing to do if flags are the same. + return; + } + + // Delete the previously existing event. + unregisterEvent(); + + eventFlags_ = eventFlags; + + if (eventFlags == 0) return; + + if (!event_) { + // If the event was not already allocated, do it now. + event_ = event_new(loop_.eventBase_, socket_.socketHandle, + eventFlags_, assumeNothrow(&workSocketCallback), cast(void*)this); + } else { + event_assign(event_, loop_.eventBase_, socket_.socketHandle, + eventFlags_, assumeNothrow(&workSocketCallback), cast(void*)this); + } + + // Add the event + if (event_add(event_, null) == -1) { + logError("event_add() for client socket failed."); + } + } + + /** + * Unregisters the current libevent event, if any. + */ + void unregisterEvent() { + if (event_ && eventFlags_ != 0) { + eventFlags_ = 0; + if (event_del(event_) == -1) { + logError("event_del() for client socket failed."); + return; + } + } + } + + /** + * Closes this connection and returns it back to the server. + */ + void close() { + unregisterEvent(); + + if (server_.eventHandler) { + server_.eventHandler.deleteContext( + connectionContext_, inputProtocol_, outputProtocol_); + } + + // Close the socket + socket_.close(); + + // close any factory produced transports. + factoryInputTransport_.close(); + factoryOutputTransport_.close(); + + // This connection object can now be reused. + loop_.disposeConnection(this); + } + + /// The server this connection belongs to. + TNonblockingServer server_; + + /// The task pool used for this connection. This is cached instead of + /// directly using server_.taskPool to avoid confusion if it is changed in + /// another thread while the request is processed. + TaskPool taskPool_; + + /// The I/O thread handling this connection. + IOLoop loop_; + + /// The socket managed by this connection. + TSocket socket_; + + /// The libevent object used for registering the workSocketCallback. + event* event_; + + /// Libevent flags + short eventFlags_; + + /// Socket mode + SocketState socketState_; + + /// Application state + ConnectionState connState_; + + /// The size of the frame to read. If still in READ_FRAME_SIZE state, some + /// of the bytes might not have been written, and the value might still be + /// in network byte order. An uint (not a size_t) because the frame size on + /// the wire is specified as one. + uint readWant_; + + /// The position in the read buffer, i.e. the number of payload bytes + /// already received from the socket in READ_REQUEST state, resp. the + /// number of size bytes in READ_FRAME_SIZE state. + uint readBufferPos_; + + /// Read buffer + ubyte* readBuffer_; + + /// Read buffer size + size_t readBufferSize_; + + /// Write buffer + ubyte[] writeBuffer_; + + /// How far through writing are we? + size_t writeBufferPos_; + + /// Largest size of write buffer seen since buffer was constructed + size_t largestWriteBufferSize_; + + /// Number of calls since the last time checkIdleBufferLimit has been + /// invoked (see TServer.resizeBufferEveryN). + uint callsSinceResize_; + + /// Base transports the processor reads from/writes to. + TInputRangeTransport!(ubyte[]) inputTransport_; + TMemoryBuffer outputTransport_; + + /// The actual transports passed to the processor obtained via the + /// transport factory. + TTransport factoryInputTransport_; + TTransport factoryOutputTransport_; /// Ditto + + /// Input/output protocols, connected to factory{Input, Output}Transport. + TProtocol inputProtocol_; + TProtocol outputProtocol_; /// Ditto. + + /// Connection context optionally created by the server event handler. + Variant connectionContext_; + + /// The processor used for this connection. + TProcessor processor_; + } +} + +/* + * The request processing function, which invokes the processor for the server + * for all the RPC messages received over a connection. + * + * Must be public because it is passed as alias to std.parallelism.task(). + */ +void processRequest(Connection connection) { + try { + while (true) { + with (connection) { + if (server_.eventHandler) { + server_.eventHandler.preProcess(connectionContext_, socket_); + } + + if (!processor_.process(inputProtocol_, outputProtocol_, + connectionContext_) || !inputProtocol_.transport.peek() + ) { + // Something went fundamentally wrong or there is nothing more to + // process, close the connection. + break; + } + } + } + } catch (TTransportException ttx) { + logError("Client died: %s", ttx); + } catch (Exception e) { + logError("Uncaught exception: %s", e); + } + + if (connection.taskPool_) connection.loop_.notifyCompleted(connection); +} + +unittest { + import thrift.internal.test.server; + + // Temporarily disable info log output in order not to spam the test results + // with startup info messages. + auto oldInfoLogSink = g_infoLogSink; + g_infoLogSink = null; + scope (exit) g_infoLogSink = oldInfoLogSink; + + // Test in-line processing shutdown with one as well as several I/O threads. + testServeCancel!(TNonblockingServer)(); + testServeCancel!(TNonblockingServer)((TNonblockingServer s) { + s.numIOThreads = 4; + }); + + // Test task pool processing shutdown with one as well as several I/O threads. + auto tp = new TaskPool(4); + tp.isDaemon = true; + testServeCancel!(TNonblockingServer)((TNonblockingServer s) { + s.taskPool = tp; + }); + testServeCancel!(TNonblockingServer)((TNonblockingServer s) { + s.taskPool = tp; + s.numIOThreads = 4; + }); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/server/simple.d b/src/jaegertracing/thrift/lib/d/src/thrift/server/simple.d new file mode 100644 index 000000000..5aba4c169 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/server/simple.d @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.server.simple; + +import std.variant : Variant; +import thrift.base; +import thrift.protocol.base; +import thrift.protocol.processor; +import thrift.server.base; +import thrift.server.transport.base; +import thrift.transport.base; +import thrift.util.cancellation; + +/** + * The most basic server. + * + * It is single-threaded and after it accepts a connections, it processes + * requests on it until it closes, then waiting for the next connection. + * + * It is not so much of use in production than it is for writing unittests, or + * as an example on how to provide a custom TServer implementation. + */ +class TSimpleServer : TServer { + /// + this( + TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory + ) { + super(processor, serverTransport, transportFactory, protocolFactory); + } + + /// + this( + TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory + ) { + super(processorFactory, serverTransport, transportFactory, protocolFactory); + } + + /// + this( + TProcessor processor, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory + ) { + super(processor, serverTransport, inputTransportFactory, + outputTransportFactory, inputProtocolFactory, outputProtocolFactory); + } + + this( + TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory + ) { + super(processorFactory, serverTransport, inputTransportFactory, + outputTransportFactory, inputProtocolFactory, outputProtocolFactory); + } + + override void serve(TCancellation cancellation = null) { + serverTransport_.listen(); + + if (eventHandler) eventHandler.preServe(); + + while (true) { + TTransport client; + TTransport inputTransport; + TTransport outputTransport; + TProtocol inputProtocol; + TProtocol outputProtocol; + + try { + client = serverTransport_.accept(cancellation); + scope(failure) client.close(); + + inputTransport = inputTransportFactory_.getTransport(client); + scope(failure) inputTransport.close(); + + outputTransport = outputTransportFactory_.getTransport(client); + scope(failure) outputTransport.close(); + + inputProtocol = inputProtocolFactory_.getProtocol(inputTransport); + outputProtocol = outputProtocolFactory_.getProtocol(outputTransport); + } catch (TCancelledException tcx) { + break; + } catch (TTransportException ttx) { + logError("TServerTransport failed on accept: %s", ttx); + continue; + } catch (TException tx) { + logError("Caught TException on accept: %s", tx); + continue; + } + + auto info = TConnectionInfo(inputProtocol, outputProtocol, client); + auto processor = processorFactory_.getProcessor(info); + + Variant connectionContext; + if (eventHandler) { + connectionContext = + eventHandler.createContext(inputProtocol, outputProtocol); + } + + try { + while (true) { + if (eventHandler) { + eventHandler.preProcess(connectionContext, client); + } + + if (!processor.process(inputProtocol, outputProtocol, + connectionContext) || !inputProtocol.transport.peek() + ) { + // Something went fundamentlly wrong or there is nothing more to + // process, close the connection. + break; + } + } + } catch (TTransportException ttx) { + if (ttx.type() != TTransportException.Type.END_OF_FILE) { + logError("Client died unexpectedly: %s", ttx); + } + } catch (Exception e) { + logError("Uncaught exception: %s", e); + } + + if (eventHandler) { + eventHandler.deleteContext(connectionContext, inputProtocol, + outputProtocol); + } + + try { + inputTransport.close(); + } catch (TTransportException ttx) { + logError("Input close failed: %s", ttx); + } + try { + outputTransport.close(); + } catch (TTransportException ttx) { + logError("Output close failed: %s", ttx); + } + try { + client.close(); + } catch (TTransportException ttx) { + logError("Client close failed: %s", ttx); + } + } + + try { + serverTransport_.close(); + } catch (TServerTransportException e) { + logError("Server transport failed to close(): %s", e); + } + } +} + +unittest { + import thrift.internal.test.server; + testServeCancel!TSimpleServer(); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/server/taskpool.d b/src/jaegertracing/thrift/lib/d/src/thrift/server/taskpool.d new file mode 100644 index 000000000..670e720fc --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/server/taskpool.d @@ -0,0 +1,304 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.server.taskpool; + +import core.sync.condition; +import core.sync.mutex; +import std.exception : enforce; +import std.parallelism; +import std.variant : Variant; +import thrift.base; +import thrift.protocol.base; +import thrift.protocol.processor; +import thrift.server.base; +import thrift.server.transport.base; +import thrift.transport.base; +import thrift.util.cancellation; + +/** + * A server which dispatches client requests to a std.parallelism TaskPool. + */ +class TTaskPoolServer : TServer { + /// + this( + TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory, + TaskPool taskPool = null + ) { + this(processor, serverTransport, transportFactory, transportFactory, + protocolFactory, protocolFactory, taskPool); + } + + /// + this( + TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory, + TaskPool taskPool = null + ) { + this(processorFactory, serverTransport, transportFactory, transportFactory, + protocolFactory, protocolFactory, taskPool); + } + + /// + this( + TProcessor processor, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + TaskPool taskPool = null + ) { + this(new TSingletonProcessorFactory(processor), serverTransport, + inputTransportFactory, outputTransportFactory, + inputProtocolFactory, outputProtocolFactory); + } + + /// + this( + TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + TaskPool taskPool = null + ) { + super(processorFactory, serverTransport, inputTransportFactory, + outputTransportFactory, inputProtocolFactory, outputProtocolFactory); + + if (taskPool) { + this.taskPool = taskPool; + } else { + auto ptp = std.parallelism.taskPool; + if (ptp.size > 0) { + taskPool_ = ptp; + } else { + // If the global task pool is empty (default on a single-core machine), + // create a new one with a single worker thread. The rationale for this + // is to avoid that an application which worked fine with no task pool + // explicitly set on the multi-core developer boxes suddenly fails on a + // single-core user machine. + taskPool_ = new TaskPool(1); + taskPool_.isDaemon = true; + } + } + } + + override void serve(TCancellation cancellation = null) { + serverTransport_.listen(); + + if (eventHandler) eventHandler.preServe(); + + auto queueState = QueueState(); + + while (true) { + // Check if we can still handle more connections. + if (maxActiveConns) { + synchronized (queueState.mutex) { + while (queueState.activeConns >= maxActiveConns) { + queueState.connClosed.wait(); + } + } + } + + TTransport client; + TTransport inputTransport; + TTransport outputTransport; + TProtocol inputProtocol; + TProtocol outputProtocol; + + try { + client = serverTransport_.accept(cancellation); + scope(failure) client.close(); + + inputTransport = inputTransportFactory_.getTransport(client); + scope(failure) inputTransport.close(); + + outputTransport = outputTransportFactory_.getTransport(client); + scope(failure) outputTransport.close(); + + inputProtocol = inputProtocolFactory_.getProtocol(inputTransport); + outputProtocol = outputProtocolFactory_.getProtocol(outputTransport); + } catch (TCancelledException tce) { + break; + } catch (TTransportException ttx) { + logError("TServerTransport failed on accept: %s", ttx); + continue; + } catch (TException tx) { + logError("Caught TException on accept: %s", tx); + continue; + } + + auto info = TConnectionInfo(inputProtocol, outputProtocol, client); + auto processor = processorFactory_.getProcessor(info); + + synchronized (queueState.mutex) { + ++queueState.activeConns; + } + taskPool_.put(task!worker(queueState, client, inputProtocol, + outputProtocol, processor, eventHandler)); + } + + // First, stop accepting new connections. + try { + serverTransport_.close(); + } catch (TServerTransportException e) { + logError("Server transport failed to close: %s", e); + } + + // Then, wait until all active connections are finished. + synchronized (queueState.mutex) { + while (queueState.activeConns > 0) { + queueState.connClosed.wait(); + } + } + } + + /** + * Sets the task pool to use. + * + * By default, the global std.parallelism taskPool instance is used, which + * might not be appropriate for many applications, e.g. where tuning the + * number of worker threads is desired. (On single-core systems, a private + * task pool with a single thread is used by default, since the global + * taskPool instance has no worker threads then.) + * + * Note: TTaskPoolServer expects that tasks are never dropped from the pool, + * e.g. by calling TaskPool.close() while there are still tasks in the + * queue. If this happens, serve() will never return. + */ + void taskPool(TaskPool pool) @property { + enforce(pool !is null, "Cannot use a null task pool."); + enforce(pool.size > 0, "Cannot use a task pool with no worker threads."); + taskPool_ = pool; + } + + /** + * The maximum number of client connections open at the same time. Zero for + * no limit, which is the default. + * + * If this limit is reached, no clients are accept()ed from the server + * transport any longer until another connection has been closed again. + */ + size_t maxActiveConns; + +protected: + TaskPool taskPool_; +} + +// Cannot be private as worker has to be passed as alias parameter to +// another module. +// private { + /* + * The state of the »connection queue«, i.e. used for keeping track of how + * many client connections are currently processed. + */ + struct QueueState { + /// Protects the queue state. + Mutex mutex; + + /// The number of active connections (from the time they are accept()ed + /// until they are closed when the worked task finishes). + size_t activeConns; + + /// Signals that the number of active connections has been decreased, i.e. + /// that a connection has been closed. + Condition connClosed; + + /// Returns an initialized instance. + static QueueState opCall() { + QueueState q; + q.mutex = new Mutex; + q.connClosed = new Condition(q.mutex); + return q; + } + } + + void worker(ref QueueState queueState, TTransport client, + TProtocol inputProtocol, TProtocol outputProtocol, + TProcessor processor, TServerEventHandler eventHandler) + { + scope (exit) { + synchronized (queueState.mutex) { + assert(queueState.activeConns > 0); + --queueState.activeConns; + queueState.connClosed.notifyAll(); + } + } + + Variant connectionContext; + if (eventHandler) { + connectionContext = + eventHandler.createContext(inputProtocol, outputProtocol); + } + + try { + while (true) { + if (eventHandler) { + eventHandler.preProcess(connectionContext, client); + } + + if (!processor.process(inputProtocol, outputProtocol, + connectionContext) || !inputProtocol.transport.peek() + ) { + // Something went fundamentlly wrong or there is nothing more to + // process, close the connection. + break; + } + } + } catch (TTransportException ttx) { + if (ttx.type() != TTransportException.Type.END_OF_FILE) { + logError("Client died unexpectedly: %s", ttx); + } + } catch (Exception e) { + logError("Uncaught exception: %s", e); + } + + if (eventHandler) { + eventHandler.deleteContext(connectionContext, inputProtocol, + outputProtocol); + } + + try { + inputProtocol.transport.close(); + } catch (TTransportException ttx) { + logError("Input close failed: %s", ttx); + } + try { + outputProtocol.transport.close(); + } catch (TTransportException ttx) { + logError("Output close failed: %s", ttx); + } + try { + client.close(); + } catch (TTransportException ttx) { + logError("Client close failed: %s", ttx); + } + } +// } + +unittest { + import thrift.internal.test.server; + testServeCancel!TTaskPoolServer(); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/server/threaded.d b/src/jaegertracing/thrift/lib/d/src/thrift/server/threaded.d new file mode 100644 index 000000000..300cc8457 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/server/threaded.d @@ -0,0 +1,217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.server.threaded; + +import core.thread; +import std.variant : Variant; +import thrift.base; +import thrift.protocol.base; +import thrift.protocol.processor; +import thrift.server.base; +import thrift.server.transport.base; +import thrift.transport.base; +import thrift.util.cancellation; + +/** + * A simple threaded server which spawns a new thread per connection. + */ +class TThreadedServer : TServer { + /// + this( + TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory + ) { + super(processor, serverTransport, transportFactory, protocolFactory); + } + + /// + this( + TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory + ) { + super(processorFactory, serverTransport, transportFactory, protocolFactory); + } + + /// + this( + TProcessor processor, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory + ) { + super(processor, serverTransport, inputTransportFactory, + outputTransportFactory, inputProtocolFactory, outputProtocolFactory); + } + + /// + this( + TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory + ) { + super(processorFactory, serverTransport, inputTransportFactory, + outputTransportFactory, inputProtocolFactory, outputProtocolFactory); + } + + override void serve(TCancellation cancellation = null) { + try { + // Start the server listening + serverTransport_.listen(); + } catch (TTransportException ttx) { + logError("listen() failed: %s", ttx); + return; + } + + if (eventHandler) eventHandler.preServe(); + + auto workerThreads = new ThreadGroup(); + + while (true) { + TTransport client; + TTransport inputTransport; + TTransport outputTransport; + TProtocol inputProtocol; + TProtocol outputProtocol; + + try { + client = serverTransport_.accept(cancellation); + scope(failure) client.close(); + + inputTransport = inputTransportFactory_.getTransport(client); + scope(failure) inputTransport.close(); + + outputTransport = outputTransportFactory_.getTransport(client); + scope(failure) outputTransport.close(); + + inputProtocol = inputProtocolFactory_.getProtocol(inputTransport); + outputProtocol = outputProtocolFactory_.getProtocol(outputTransport); + } catch (TCancelledException tce) { + break; + } catch (TTransportException ttx) { + logError("TServerTransport failed on accept: %s", ttx); + continue; + } catch (TException tx) { + logError("Caught TException on accept: %s", tx); + continue; + } + + auto info = TConnectionInfo(inputProtocol, outputProtocol, client); + auto processor = processorFactory_.getProcessor(info); + auto worker = new WorkerThread(client, inputProtocol, outputProtocol, + processor, eventHandler); + workerThreads.add(worker); + worker.start(); + } + + try { + serverTransport_.close(); + } catch (TServerTransportException e) { + logError("Server transport failed to close: %s", e); + } + workerThreads.joinAll(); + } +} + +// The worker thread handling a client connection. +private class WorkerThread : Thread { + this(TTransport client, TProtocol inputProtocol, TProtocol outputProtocol, + TProcessor processor, TServerEventHandler eventHandler) + { + client_ = client; + inputProtocol_ = inputProtocol; + outputProtocol_ = outputProtocol; + processor_ = processor; + eventHandler_ = eventHandler; + + super(&run); + } + + void run() { + Variant connectionContext; + if (eventHandler_) { + connectionContext = + eventHandler_.createContext(inputProtocol_, outputProtocol_); + } + + try { + while (true) { + if (eventHandler_) { + eventHandler_.preProcess(connectionContext, client_); + } + + if (!processor_.process(inputProtocol_, outputProtocol_, + connectionContext) || !inputProtocol_.transport.peek() + ) { + // Something went fundamentlly wrong or there is nothing more to + // process, close the connection. + break; + } + } + } catch (TTransportException ttx) { + if (ttx.type() != TTransportException.Type.END_OF_FILE) { + logError("Client died unexpectedly: %s", ttx); + } + } catch (Exception e) { + logError("Uncaught exception: %s", e); + } + + if (eventHandler_) { + eventHandler_.deleteContext(connectionContext, inputProtocol_, + outputProtocol_); + } + + try { + inputProtocol_.transport.close(); + } catch (TTransportException ttx) { + logError("Input close failed: %s", ttx); + } + try { + outputProtocol_.transport.close(); + } catch (TTransportException ttx) { + logError("Output close failed: %s", ttx); + } + try { + client_.close(); + } catch (TTransportException ttx) { + logError("Client close failed: %s", ttx); + } + } + +private: + TTransport client_; + TProtocol inputProtocol_; + TProtocol outputProtocol_; + TProcessor processor_; + TServerEventHandler eventHandler_; +} + +unittest { + import thrift.internal.test.server; + testServeCancel!TThreadedServer(); +} + diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/server/transport/base.d b/src/jaegertracing/thrift/lib/d/src/thrift/server/transport/base.d new file mode 100644 index 000000000..704e16d21 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/server/transport/base.d @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.server.transport.base; + +import thrift.base; +import thrift.transport.base; +import thrift.util.cancellation; + +/** + * Some kind of I/O device enabling servers to listen for incoming client + * connections and communicate with them via a TTransport interface. + */ +interface TServerTransport { + /** + * Starts listening for server connections. + * + * Just as simliar functions commonly found in socket libraries, this + * function does not block. + * + * If the socket is already listening, nothing happens. + * + * Throws: TServerTransportException if listening failed or the transport + * was already listening. + */ + void listen(); + + /** + * Closes the server transport, causing it to stop listening. + * + * Throws: TServerTransportException if the transport was not listening. + */ + void close(); + + /** + * Returns whether the server transport is currently listening. + */ + bool isListening() @property; + + /** + * Accepts a client connection and returns an opened TTransport for it, + * never returning null. + * + * Blocks until a client connection is available. + * + * Params: + * cancellation = If triggered, requests the call to stop blocking and + * return with a TCancelledException. Implementations are free to + * ignore this if they cannot provide a reasonable. + * + * Throws: TServerTransportException if accepting failed, + * TCancelledException if it was cancelled. + */ + TTransport accept(TCancellation cancellation = null) out (result) { + assert(result !is null); + } +} + +/** + * Server transport exception. + */ +class TServerTransportException : TException { + /** + * Error codes for the various types of exceptions. + */ + enum Type { + /// + UNKNOWN, + + /// The server socket is not listening, but excepted to be. + NOT_LISTENING, + + /// The server socket is already listening, but expected not to be. + ALREADY_LISTENING, + + /// An operation on the primary underlying resource, e.g. a socket used + /// for accepting connections, failed. + RESOURCE_FAILED + } + + /// + this(Type type, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { + this(errorMsg(type), type, file, line, next); + } + + /// + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + this(msg, Type.UNKNOWN, file, line, next); + } + + /// + this(string msg, Type type, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + super(msg, file, line, next); + type_ = type; + } + + /// + Type type() const nothrow @property { + return type_; + } + +protected: + Type type_; + +private: + string errorMsg(Type type) { + string msg = "TTransportException: "; + switch (type) { + case Type.UNKNOWN: msg ~= "Unknown server transport exception"; break; + case Type.NOT_LISTENING: msg ~= "Server transport not listening"; break; + case Type.ALREADY_LISTENING: msg ~= "Server transport already listening"; break; + case Type.RESOURCE_FAILED: msg ~= "An underlying resource failed"; break; + default: msg ~= "(Invalid exception type)"; break; + } + return msg; + } +} + diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/server/transport/socket.d b/src/jaegertracing/thrift/lib/d/src/thrift/server/transport/socket.d new file mode 100644 index 000000000..e66d80e32 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/server/transport/socket.d @@ -0,0 +1,380 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.server.transport.socket; + +import core.thread : dur, Duration, Thread; +import core.stdc.string : strerror; +import std.array : empty; +import std.conv : text, to; +import std.exception : enforce; +import std.socket; +import thrift.base; +import thrift.internal.socket; +import thrift.server.transport.base; +import thrift.transport.base; +import thrift.transport.socket; +import thrift.util.awaitable; +import thrift.util.cancellation; + +private alias TServerTransportException STE; + +/** + * Server socket implementation of TServerTransport. + * + * Maps to std.socket listen()/accept(); only provides TCP/IP sockets (i.e. no + * Unix sockets) for now, because they are not supported in std.socket. + */ +class TServerSocket : TServerTransport { + /** + * Constructs a new instance. + * + * Params: + * port = The TCP port to listen at (host is always 0.0.0.0). + * sendTimeout = The socket sending timeout. + * recvTimout = The socket receiving timeout. + */ + this(ushort port, Duration sendTimeout = dur!"hnsecs"(0), + Duration recvTimeout = dur!"hnsecs"(0)) + { + port_ = port; + sendTimeout_ = sendTimeout; + recvTimeout_ = recvTimeout; + + cancellationNotifier_ = new TSocketNotifier; + + socketSet_ = new SocketSet; + } + + /// The port the server socket listens at. + ushort port() const @property { + return port_; + } + + /// The socket sending timeout, zero to block infinitely. + void sendTimeout(Duration sendTimeout) @property { + sendTimeout_ = sendTimeout; + } + + /// The socket receiving timeout, zero to block infinitely. + void recvTimeout(Duration recvTimeout) @property { + recvTimeout_ = recvTimeout; + } + + /// The maximum number of listening retries if it fails. + void retryLimit(ushort retryLimit) @property { + retryLimit_ = retryLimit; + } + + /// The delay between a listening attempt failing and retrying it. + void retryDelay(Duration retryDelay) @property { + retryDelay_ = retryDelay; + } + + /// The size of the TCP send buffer, in bytes. + void tcpSendBuffer(int tcpSendBuffer) @property { + tcpSendBuffer_ = tcpSendBuffer; + } + + /// The size of the TCP receiving buffer, in bytes. + void tcpRecvBuffer(int tcpRecvBuffer) @property { + tcpRecvBuffer_ = tcpRecvBuffer; + } + + /// Whether to listen on IPv6 only, if IPv6 support is detected + /// (default: false). + void ipv6Only(bool value) @property { + ipv6Only_ = value; + } + + override void listen() { + enforce(!isListening, new STE(STE.Type.ALREADY_LISTENING)); + + serverSocket_ = makeSocketAndListen(port_, ACCEPT_BACKLOG, retryLimit_, + retryDelay_, tcpSendBuffer_, tcpRecvBuffer_, ipv6Only_); + } + + override void close() { + enforce(isListening, new STE(STE.Type.NOT_LISTENING)); + + serverSocket_.shutdown(SocketShutdown.BOTH); + serverSocket_.close(); + serverSocket_ = null; + } + + override bool isListening() @property { + return serverSocket_ !is null; + } + + /// Number of connections listen() backlogs. + enum ACCEPT_BACKLOG = 1024; + + override TTransport accept(TCancellation cancellation = null) { + enforce(isListening, new STE(STE.Type.NOT_LISTENING)); + + if (cancellation) cancellationNotifier_.attach(cancellation.triggering); + scope (exit) if (cancellation) cancellationNotifier_.detach(); + + + // Too many EINTRs is a fault condition and would need to be handled + // manually by our caller, but we can tolerate a certain number. + enum MAX_EINTRS = 10; + uint numEintrs; + + while (true) { + socketSet_.reset(); + socketSet_.add(serverSocket_); + socketSet_.add(cancellationNotifier_.socket); + + auto ret = Socket.select(socketSet_, null, null); + enforce(ret != 0, new STE("Socket.select() returned 0.", + STE.Type.RESOURCE_FAILED)); + + if (ret < 0) { + // Select itself failed, check if it was just due to an interrupted + // syscall. + if (getSocketErrno() == INTERRUPTED_ERRNO) { + if (numEintrs++ < MAX_EINTRS) { + continue; + } else { + throw new STE("Socket.select() was interrupted by a signal (EINTR) " ~ + "more than " ~ to!string(MAX_EINTRS) ~ " times.", + STE.Type.RESOURCE_FAILED + ); + } + } + throw new STE("Unknown error on Socket.select(): " ~ + socketErrnoString(getSocketErrno()), STE.Type.RESOURCE_FAILED); + } else { + // Check for a ping on the interrupt socket. + if (socketSet_.isSet(cancellationNotifier_.socket)) { + cancellation.throwIfTriggered(); + } + + // Check for the actual server socket having a connection waiting. + if (socketSet_.isSet(serverSocket_)) { + break; + } + } + } + + try { + auto client = createTSocket(serverSocket_.accept()); + client.sendTimeout = sendTimeout_; + client.recvTimeout = recvTimeout_; + return client; + } catch (SocketException e) { + throw new STE("Unknown error on accepting: " ~ to!string(e), + STE.Type.RESOURCE_FAILED); + } + } + +protected: + /** + * Allows derived classes to create a different TSocket type. + */ + TSocket createTSocket(Socket socket) { + return new TSocket(socket); + } + +private: + ushort port_; + Duration sendTimeout_; + Duration recvTimeout_; + ushort retryLimit_; + Duration retryDelay_; + uint tcpSendBuffer_; + uint tcpRecvBuffer_; + bool ipv6Only_; + + Socket serverSocket_; + TSocketNotifier cancellationNotifier_; + + // Keep socket set between accept() calls to avoid reallocating. + SocketSet socketSet_; +} + +Socket makeSocketAndListen(ushort port, int backlog, ushort retryLimit, + Duration retryDelay, uint tcpSendBuffer = 0, uint tcpRecvBuffer = 0, + bool ipv6Only = false +) { + Address localAddr; + try { + // null represents the wildcard address. + auto addrInfos = getAddressInfo(null, to!string(port), + AddressInfoFlags.PASSIVE, SocketType.STREAM, ProtocolType.TCP); + foreach (i, ai; addrInfos) { + // Prefer to bind to IPv6 addresses, because then IPv4 is listened to as + // well, but not the other way round. + if (ai.family == AddressFamily.INET6 || i == (addrInfos.length - 1)) { + localAddr = ai.address; + break; + } + } + } catch (Exception e) { + throw new STE("Could not determine local address to listen on.", + STE.Type.RESOURCE_FAILED, __FILE__, __LINE__, e); + } + + Socket socket; + try { + socket = new Socket(localAddr.addressFamily, SocketType.STREAM, + ProtocolType.TCP); + } catch (SocketException e) { + throw new STE("Could not create accepting socket: " ~ to!string(e), + STE.Type.RESOURCE_FAILED); + } + + try { + socket.setOption(SocketOptionLevel.IPV6, SocketOption.IPV6_V6ONLY, ipv6Only); + } catch (SocketException e) { + // This is somewhat expected on older systems (e.g. pre-Vista Windows), + // which do not support the IPV6_V6ONLY flag yet. Racy flag just to avoid + // log spew in unit tests. + shared static warned = false; + if (!warned) { + logError("Could not set IPV6_V6ONLY socket option: %s", e); + warned = true; + } + } + + alias SocketOptionLevel.SOCKET lvlSock; + + // Prevent 2 maximum segement lifetime delay on accept. + try { + socket.setOption(lvlSock, SocketOption.REUSEADDR, true); + } catch (SocketException e) { + throw new STE("Could not set REUSEADDR socket option: " ~ to!string(e), + STE.Type.RESOURCE_FAILED); + } + + // Set TCP buffer sizes. + if (tcpSendBuffer > 0) { + try { + socket.setOption(lvlSock, SocketOption.SNDBUF, tcpSendBuffer); + } catch (SocketException e) { + throw new STE("Could not set socket send buffer size: " ~ to!string(e), + STE.Type.RESOURCE_FAILED); + } + } + + if (tcpRecvBuffer > 0) { + try { + socket.setOption(lvlSock, SocketOption.RCVBUF, tcpRecvBuffer); + } catch (SocketException e) { + throw new STE("Could not set receive send buffer size: " ~ to!string(e), + STE.Type.RESOURCE_FAILED); + } + } + + // Turn linger off to avoid blocking on socket close. + try { + Linger l; + l.on = 0; + l.time = 0; + socket.setOption(lvlSock, SocketOption.LINGER, l); + } catch (SocketException e) { + throw new STE("Could not disable socket linger: " ~ to!string(e), + STE.Type.RESOURCE_FAILED); + } + + // Set TCP_NODELAY. + try { + socket.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, true); + } catch (SocketException e) { + throw new STE("Could not disable Nagle's algorithm: " ~ to!string(e), + STE.Type.RESOURCE_FAILED); + } + + ushort retries; + while (true) { + try { + socket.bind(localAddr); + break; + } catch (SocketException) {} + + // If bind() worked, we breaked outside the loop above. + retries++; + if (retries < retryLimit) { + Thread.sleep(retryDelay); + } else { + throw new STE(text("Could not bind to address: ", localAddr), + STE.Type.RESOURCE_FAILED); + } + } + + socket.listen(backlog); + return socket; +} + +unittest { + // Test interrupt(). + { + auto sock = new TServerSocket(0); + sock.listen(); + scope (exit) sock.close(); + + auto cancellation = new TCancellationOrigin; + + auto intThread = new Thread({ + // Sleep for a bit until the socket is accepting. + Thread.sleep(dur!"msecs"(50)); + cancellation.trigger(); + }); + intThread.start(); + + import std.exception; + assertThrown!TCancelledException(sock.accept(cancellation)); + } + + // Test receive() timeout on accepted client sockets. + { + immutable port = 11122; + auto timeout = dur!"msecs"(500); + auto serverSock = new TServerSocket(port, timeout, timeout); + serverSock.listen(); + scope (exit) serverSock.close(); + + auto clientSock = new TSocket("127.0.0.1", port); + clientSock.open(); + scope (exit) clientSock.close(); + + shared bool hasTimedOut; + auto recvThread = new Thread({ + auto sock = serverSock.accept(); + ubyte[1] data; + try { + sock.read(data); + } catch (TTransportException e) { + if (e.type == TTransportException.Type.TIMED_OUT) { + hasTimedOut = true; + } else { + import std.stdio; + stderr.writeln(e); + } + } + }); + recvThread.isDaemon = true; + recvThread.start(); + + // Wait for the timeout, with a little bit of spare time. + Thread.sleep(timeout + dur!"msecs"(50)); + enforce(hasTimedOut, + "Client socket receive() blocked for longer than recvTimeout."); + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/server/transport/ssl.d b/src/jaegertracing/thrift/lib/d/src/thrift/server/transport/ssl.d new file mode 100644 index 000000000..2dd9d2366 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/server/transport/ssl.d @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.server.transport.ssl; + +import std.datetime : Duration; +import std.exception : enforce; +import std.socket : Socket; +import thrift.server.transport.socket; +import thrift.transport.base; +import thrift.transport.socket; +import thrift.transport.ssl; + +/** + * A server transport implementation using SSL-encrypted sockets. + * + * Note: + * On Posix systems which do not have the BSD-specific SO_NOSIGPIPE flag, you + * might want to ignore the SIGPIPE signal, as OpenSSL might try to write to + * a closed socket if the peer disconnects abruptly: + * --- + * import core.stdc.signal; + * import core.sys.posix.signal; + * signal(SIGPIPE, SIG_IGN); + * --- + * + * See: thrift.transport.ssl. + */ +class TSSLServerSocket : TServerSocket { + /** + * Creates a new TSSLServerSocket. + * + * Params: + * port = The port on which to listen. + * sslContext = The TSSLContext to use for creating client + * sockets. Must be in server-side mode. + */ + this(ushort port, TSSLContext sslContext) { + super(port); + setSSLContext(sslContext); + } + + /** + * Creates a new TSSLServerSocket. + * + * Params: + * port = The port on which to listen. + * sendTimeout = The send timeout to set on the client sockets. + * recvTimeout = The receive timeout to set on the client sockets. + * sslContext = The TSSLContext to use for creating client + * sockets. Must be in server-side mode. + */ + this(ushort port, Duration sendTimeout, Duration recvTimeout, + TSSLContext sslContext) + { + super(port, sendTimeout, recvTimeout); + setSSLContext(sslContext); + } + +protected: + override TSocket createTSocket(Socket socket) { + return new TSSLSocket(sslContext_, socket); + } + +private: + void setSSLContext(TSSLContext sslContext) { + enforce(sslContext.serverSide, new TTransportException( + "Need server-side SSL socket factory for TSSLServerSocket")); + sslContext_ = sslContext; + } + + TSSLContext sslContext_; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/base.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/base.d new file mode 100644 index 000000000..7e76a5948 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/base.d @@ -0,0 +1,370 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.transport.base; + +import core.stdc.string : strerror; +import std.conv : text; +import thrift.base; + +/** + * An entity data can be read from and/or written to. + * + * A TTransport implementation may capable of either reading or writing, but + * not necessarily both. + */ +interface TTransport { + /** + * Whether this transport is open. + * + * If a transport is closed, it can be opened by calling open(), and vice + * versa for close(). + * + * While a transport should always be open when trying to read/write data, + * the related functions do not necessarily fail when called for a closed + * transport. Situations like this could occur e.g. with a wrapper + * transport which buffers data when the underlying transport has already + * been closed (possibly because the connection was abruptly closed), but + * there is still data left to be read in the buffers. This choice has been + * made to simplify transport implementations, in terms of both code + * complexity and runtime overhead. + */ + bool isOpen() @property; + + /** + * Tests whether there is more data to read or if the remote side is + * still open. + * + * A typical use case would be a server checking if it should process + * another request on the transport. + */ + bool peek(); + + /** + * Opens the transport for communications. + * + * If the transport is already open, nothing happens. + * + * Throws: TTransportException if opening fails. + */ + void open(); + + /** + * Closes the transport. + * + * If the transport is not open, nothing happens. + * + * Throws: TTransportException if closing fails. + */ + void close(); + + /** + * Attempts to fill the given buffer by reading data. + * + * For potentially blocking data sources (e.g. sockets), read() will only + * block if no data is available at all. If there is some data available, + * but waiting for new data to arrive would be required to fill the whole + * buffer, the readily available data will be immediately returned – use + * readAll() if you want to wait until the whole buffer is filled. + * + * Params: + * buf = Slice to use as buffer. + * + * Returns: How many bytes were actually read + * + * Throws: TTransportException if an error occurs. + */ + size_t read(ubyte[] buf); + + /** + * Fills the given buffer by reading data into it, failing if not enough + * data is available. + * + * Params: + * buf = Slice to use as buffer. + * + * Throws: TTransportException if insufficient data is available or reading + * fails altogether. + */ + void readAll(ubyte[] buf); + + /** + * Must be called by clients when read is completed. + * + * Implementations can choose to perform a transport-specific action, e.g. + * logging the request to a file. + * + * Returns: The number of bytes read if available, 0 otherwise. + */ + size_t readEnd(); + + /** + * Writes the passed slice of data. + * + * Note: You must call flush() to ensure the data is actually written, + * and available to be read back in the future. Destroying a TTransport + * object does not automatically flush pending data – if you destroy a + * TTransport object with written but unflushed data, that data may be + * discarded. + * + * Params: + * buf = Slice of data to write. + * + * Throws: TTransportException if an error occurs. + */ + void write(in ubyte[] buf); + + /** + * Must be called by clients when write is completed. + * + * Implementations can choose to perform a transport-specific action, e.g. + * logging the request to a file. + * + * Returns: The number of bytes written if available, 0 otherwise. + */ + size_t writeEnd(); + + /** + * Flushes any pending data to be written. + * + * Must be called before destruction to ensure writes are actually complete, + * otherwise pending data may be discarded. Typically used with buffered + * transport mechanisms. + * + * Throws: TTransportException if an error occurs. + */ + void flush(); + + /** + * Attempts to return a slice of <code>len</code> bytes of incoming data, + * possibly copied into buf, not consuming them (i.e.: a later read will + * return the same data). + * + * This method is meant to support protocols that need to read variable- + * length fields. They can attempt to borrow the maximum amount of data that + * they will need, then <code>consume()</code> what they actually use. Some + * transports will not support this method and others will fail occasionally, + * so protocols must be prepared to fall back to <code>read()</code> if + * borrow fails. + * + * The transport must be open when calling this. + * + * Params: + * buf = A buffer where the data can be stored if needed, or null to + * indicate that the caller is not supplying storage, but would like a + * slice of an internal buffer, if available. + * len = The number of bytes to borrow. + * + * Returns: If the borrow succeeds, a slice containing the borrowed data, + * null otherwise. The slice will be at least as long as requested, but + * may be longer if the returned slice points into an internal buffer + * rather than buf. + * + * Throws: TTransportException if an error occurs. + */ + const(ubyte)[] borrow(ubyte* buf, size_t len) out (result) { + // FIXME: Commented out because len gets corrupted in + // thrift.transport.memory borrow() unittest. + version(none) assert(result is null || result.length >= len, + "Buffer returned by borrow() too short."); + } + + /** + * Remove len bytes from the transport. This must always follow a borrow + * of at least len bytes, and should always succeed. + * + * The transport must be open when calling this. + * + * Params: + * len = Number of bytes to consume. + * + * Throws: TTransportException if an error occurs. + */ + void consume(size_t len); +} + +/** + * Provides basic fall-back implementations of the TTransport interface. + */ +class TBaseTransport : TTransport { + override bool isOpen() @property { + return false; + } + + override bool peek() { + return isOpen; + } + + override void open() { + throw new TTransportException("Cannot open TBaseTransport.", + TTransportException.Type.NOT_IMPLEMENTED); + } + + override void close() { + throw new TTransportException("Cannot close TBaseTransport.", + TTransportException.Type.NOT_IMPLEMENTED); + } + + override size_t read(ubyte[] buf) { + throw new TTransportException("Cannot read from a TBaseTransport.", + TTransportException.Type.NOT_IMPLEMENTED); + } + + override void readAll(ubyte[] buf) { + size_t have; + while (have < buf.length) { + size_t get = read(buf[have..$]); + if (get <= 0) { + throw new TTransportException(text("Could not readAll() ", buf.length, + " bytes as no more data was available after ", have, " bytes."), + TTransportException.Type.END_OF_FILE); + } + have += get; + } + } + + override size_t readEnd() { + // Do nothing by default, not needed by all implementations. + return 0; + } + + override void write(in ubyte[] buf) { + throw new TTransportException("Cannot write to a TBaseTransport.", + TTransportException.Type.NOT_IMPLEMENTED); + } + + override size_t writeEnd() { + // Do nothing by default, not needed by all implementations. + return 0; + } + + override void flush() { + // Do nothing by default, not needed by all implementations. + } + + override const(ubyte)[] borrow(ubyte* buf, size_t len) { + // borrow() is allowed to fail anyway, so just return null. + return null; + } + + override void consume(size_t len) { + throw new TTransportException("Cannot consume from a TBaseTransport.", + TTransportException.Type.NOT_IMPLEMENTED); + } + +protected: + this() {} +} + +/** + * Makes a TTransport which wraps a given source transport in some way. + * + * A common use case is inside server implementations, where the raw client + * connections accepted from e.g. TServerSocket need to be wrapped into + * buffered or compressed transports. + */ +class TTransportFactory { + /** + * Default implementation does nothing, just returns the transport given. + */ + TTransport getTransport(TTransport trans) { + return trans; + } +} + +/** + * Transport factory for transports which simply wrap an underlying TTransport + * without requiring additional configuration. + */ +class TWrapperTransportFactory(T) if ( + is(T : TTransport) && __traits(compiles, new T(TTransport.init)) +) : TTransportFactory { + override T getTransport(TTransport trans) { + return new T(trans); + } +} + +/** + * Transport-level exception. + */ +class TTransportException : TException { + /** + * Error codes for the various types of exceptions. + */ + enum Type { + UNKNOWN, /// + NOT_OPEN, /// + TIMED_OUT, /// + END_OF_FILE, /// + INTERRUPTED, /// + BAD_ARGS, /// + CORRUPTED_DATA, /// + INTERNAL_ERROR, /// + NOT_IMPLEMENTED /// + } + + /// + this(Type type, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { + static string msgForType(Type type) { + switch (type) { + case Type.UNKNOWN: return "Unknown transport exception"; + case Type.NOT_OPEN: return "Transport not open"; + case Type.TIMED_OUT: return "Timed out"; + case Type.END_OF_FILE: return "End of file"; + case Type.INTERRUPTED: return "Interrupted"; + case Type.BAD_ARGS: return "Invalid arguments"; + case Type.CORRUPTED_DATA: return "Corrupted Data"; + case Type.INTERNAL_ERROR: return "Internal error"; + case Type.NOT_IMPLEMENTED: return "Not implemented"; + default: return "(Invalid exception type)"; + } + } + this(msgForType(type), type, file, line, next); + } + + /// + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + this(msg, Type.UNKNOWN, file, line, next); + } + + /// + this(string msg, Type type, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + super(msg, file, line, next); + type_ = type; + } + + /// + Type type() const nothrow @property { + return type_; + } + +protected: + Type type_; +} + +/** + * Meta-programming helper returning whether the passed type is a TTransport + * implementation. + */ +template isTTransport(T) { + enum isTTransport = is(T : TTransport); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/buffered.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/buffered.d new file mode 100644 index 000000000..cabfbdc03 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/buffered.d @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.transport.buffered; + +import std.algorithm : min; +import std.array : empty; +import std.exception : enforce; +import thrift.transport.base; + +/** + * Wraps another transport and buffers reads and writes until the internal + * buffers are exhausted, at which point new data is fetched resp. the + * accumulated data is written out at once. + */ +final class TBufferedTransport : TBaseTransport { + /** + * Constructs a new instance, using the default buffer sizes. + * + * Params: + * transport = The underlying transport to wrap. + */ + this(TTransport transport) { + this(transport, DEFAULT_BUFFER_SIZE); + } + + /** + * Constructs a new instance, using the specified buffer size. + * + * Params: + * transport = The underlying transport to wrap. + * bufferSize = The size of the read and write buffers to use, in bytes. + */ + this(TTransport transport, size_t bufferSize) { + this(transport, bufferSize, bufferSize); + } + + /** + * Constructs a new instance, using the specified buffer size. + * + * Params: + * transport = The underlying transport to wrap. + * readBufferSize = The size of the read buffer to use, in bytes. + * writeBufferSize = The size of the write buffer to use, in bytes. + */ + this(TTransport transport, size_t readBufferSize, size_t writeBufferSize) { + transport_ = transport; + readBuffer_ = new ubyte[readBufferSize]; + writeBuffer_ = new ubyte[writeBufferSize]; + writeAvail_ = writeBuffer_; + } + + /// The default size of the read/write buffers, in bytes. + enum int DEFAULT_BUFFER_SIZE = 512; + + override bool isOpen() @property { + return transport_.isOpen(); + } + + override bool peek() { + if (readAvail_.empty) { + // If there is nothing available to read, see if we can get something + // from the underlying transport. + auto bytesRead = transport_.read(readBuffer_); + readAvail_ = readBuffer_[0 .. bytesRead]; + } + + return !readAvail_.empty; + } + + override void open() { + transport_.open(); + } + + override void close() { + if (!isOpen) return; + flush(); + transport_.close(); + } + + override size_t read(ubyte[] buf) { + if (readAvail_.empty) { + // No data left in our buffer, fetch some from the underlying transport. + + if (buf.length > readBuffer_.length) { + // If the amount of data requested is larger than our reading buffer, + // directly read to the passed buffer. This probably doesn't occur too + // often in practice (and even if it does, the underlying transport + // probably cannot fulfill the request at once anyway), but it can't + // harm to try… + return transport_.read(buf); + } + + auto bytesRead = transport_.read(readBuffer_); + readAvail_ = readBuffer_[0 .. bytesRead]; + } + + // Hand over whatever we have. + auto give = min(readAvail_.length, buf.length); + buf[0 .. give] = readAvail_[0 .. give]; + readAvail_ = readAvail_[give .. $]; + return give; + } + + /** + * Shortcut version of readAll. + */ + override void readAll(ubyte[] buf) { + if (readAvail_.length >= buf.length) { + buf[] = readAvail_[0 .. buf.length]; + readAvail_ = readAvail_[buf.length .. $]; + return; + } + + super.readAll(buf); + } + + override void write(in ubyte[] buf) { + if (writeAvail_.length >= buf.length) { + // If the data fits in the buffer, just save it there. + writeAvail_[0 .. buf.length] = buf; + writeAvail_ = writeAvail_[buf.length .. $]; + return; + } + + // We have to decide if we copy data from buf to our internal buffer, or + // just directly write them out. The same considerations about avoiding + // syscalls as for C++ apply here. + auto bytesAvail = writeAvail_.ptr - writeBuffer_.ptr; + if ((bytesAvail + buf.length >= 2 * writeBuffer_.length) || (bytesAvail == 0)) { + // We would immediately need two syscalls anyway (or we don't have + // anything) in our buffer to write, so just write out both buffers. + if (bytesAvail > 0) { + transport_.write(writeBuffer_[0 .. bytesAvail]); + writeAvail_ = writeBuffer_; + } + + transport_.write(buf); + return; + } + + // Fill up our internal buffer for a write. + writeAvail_[] = buf[0 .. writeAvail_.length]; + auto left = buf[writeAvail_.length .. $]; + transport_.write(writeBuffer_); + + // Copy the rest into our buffer. + writeBuffer_[0 .. left.length] = left[]; + writeAvail_ = writeBuffer_[left.length .. $]; + } + + override void flush() { + // Write out any data waiting in the write buffer. + auto bytesAvail = writeAvail_.ptr - writeBuffer_.ptr; + if (bytesAvail > 0) { + // Note that we reset writeAvail_ prior to calling the underlying protocol + // to make sure the buffer is cleared even if the transport throws an + // exception. + writeAvail_ = writeBuffer_; + transport_.write(writeBuffer_[0 .. bytesAvail]); + } + + // Flush the underlying transport. + transport_.flush(); + } + + override const(ubyte)[] borrow(ubyte* buf, size_t len) { + if (len <= readAvail_.length) { + return readAvail_; + } + return null; + } + + override void consume(size_t len) { + enforce(len <= readBuffer_.length, new TTransportException( + "Invalid consume length.", TTransportException.Type.BAD_ARGS)); + readAvail_ = readAvail_[len .. $]; + } + + /** + * The wrapped transport. + */ + TTransport underlyingTransport() @property { + return transport_; + } + +private: + TTransport transport_; + + ubyte[] readBuffer_; + ubyte[] writeBuffer_; + + ubyte[] readAvail_; + ubyte[] writeAvail_; +} + +/** + * Wraps given transports into TBufferedTransports. + */ +alias TWrapperTransportFactory!TBufferedTransport TBufferedTransportFactory; diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/file.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/file.d new file mode 100644 index 000000000..fe88e7306 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/file.d @@ -0,0 +1,1101 @@ +/* + * 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. + */ + +/** + * Transports for reading from/writing to Thrift »log files«. + * + * These transports are not »stupid« sources and sinks just reading and + * writing bytes from a file verbatim, but organize the contents in the form + * of so-called »events«, which refers to the data written between two flush() + * calls. + * + * Chunking is supported, events are guaranteed to never span chunk boundaries. + * As a consequence, an event can never be larger than the chunk size. The + * chunk size used is not saved with the file, so care has to be taken to make + * sure the same chunk size is used for reading and writing. + */ +module thrift.transport.file; + +import core.thread : Thread; +import std.array : empty; +import std.algorithm : min, max; +import std.concurrency; +import std.conv : to; +import std.datetime : dur, Duration; +import std.datetime.stopwatch : AutoStart, StopWatch; +import std.exception; +import std.stdio : File; +import thrift.base; +import thrift.transport.base; + +/// The default chunk size, in bytes. +enum DEFAULT_CHUNK_SIZE = 16 * 1024 * 1024; + +/// The type used to represent event sizes in the file. +alias uint EventSize; + +version (BigEndian) { + static assert(false, + "Little endian byte order is assumed in thrift.transport.file."); +} + +/** + * A transport used to read log files. It can never be written to, calling + * write() throws. + * + * Contrary to the C++ design, explicitly opening the transport/file before + * using is necessary to allow manually closing the file without relying on the + * object lifetime. Otherwise, it's a straight port of the C++ implementation. + */ +final class TFileReaderTransport : TBaseTransport { + /** + * Creates a new file writer transport. + * + * Params: + * path = Path of the file to opperate on. + */ + this(string path) { + path_ = path; + chunkSize_ = DEFAULT_CHUNK_SIZE; + readBufferSize_ = DEFAULT_READ_BUFFER_SIZE; + readTimeout_ = DEFAULT_READ_TIMEOUT; + corruptedEventSleepDuration_ = DEFAULT_CORRUPTED_EVENT_SLEEP_DURATION; + maxEventSize = DEFAULT_MAX_EVENT_SIZE; + } + + override bool isOpen() @property { + return isOpen_; + } + + override bool peek() { + if (!isOpen) return false; + + // If there is no event currently processed, try fetching one from the + // file. + if (!currentEvent_) { + currentEvent_ = readEvent(); + + if (!currentEvent_) { + // Still nothing there, couldn't read a new event. + return false; + } + } + // check if there is anything to read + return (currentEvent_.length - currentEventPos_) > 0; + } + + override void open() { + if (isOpen) return; + try { + file_ = File(path_, "rb"); + } catch (Exception e) { + throw new TTransportException("Error on opening input file.", + TTransportException.Type.NOT_OPEN, __FILE__, __LINE__, e); + } + isOpen_ = true; + } + + override void close() { + if (!isOpen) return; + + file_.close(); + isOpen_ = false; + readState_.resetAllValues(); + } + + override size_t read(ubyte[] buf) { + enforce(isOpen, new TTransportException( + "Cannot read if file is not open.", TTransportException.Type.NOT_OPEN)); + + // If there is no event currently processed, try fetching one from the + // file. + if (!currentEvent_) { + currentEvent_ = readEvent(); + + if (!currentEvent_) { + // Still nothing there, couldn't read a new event. + return 0; + } + } + + auto len = buf.length; + auto remaining = currentEvent_.length - currentEventPos_; + + if (remaining <= len) { + // If less than the requested length is available, read as much as + // possible. + buf[0 .. remaining] = currentEvent_[currentEventPos_ .. $]; + currentEvent_ = null; + currentEventPos_ = 0; + return remaining; + } + + // There will still be data left in the buffer after reading, pass out len + // bytes. + buf[] = currentEvent_[currentEventPos_ .. currentEventPos_ + len]; + currentEventPos_ += len; + return len; + } + + ulong getNumChunks() { + enforce(isOpen, new TTransportException( + "Cannot get number of chunks if file not open.", + TTransportException.Type.NOT_OPEN)); + + try { + auto fileSize = file_.size(); + if (fileSize == 0) { + // Empty files have no chunks. + return 0; + } + return ((fileSize)/chunkSize_) + 1; + } catch (Exception e) { + throw new TTransportException("Error getting file size.", __FILE__, + __LINE__, e); + } + } + + ulong getCurChunk() { + return offset_ / chunkSize_; + } + + void seekToChunk(long chunk) { + enforce(isOpen, new TTransportException( + "Cannot get number of chunks if file not open.", + TTransportException.Type.NOT_OPEN)); + + auto numChunks = getNumChunks(); + + if (chunk < 0) { + // Count negative indices from the end. + chunk += numChunks; + } + + if (chunk < 0) { + logError("Incorrect chunk number for reverse seek, seeking to " ~ + "beginning instead: %s", chunk); + chunk = 0; + } + + bool seekToEnd; + long minEndOffset; + if (chunk >= numChunks) { + logError("Trying to seek to non-existing chunk, seeking to " ~ + "end of file instead: %s", chunk); + seekToEnd = true; + chunk = numChunks - 1; + // this is the min offset to process events till + minEndOffset = file_.size(); + } + + readState_.resetAllValues(); + currentEvent_ = null; + + try { + file_.seek(chunk * chunkSize_); + offset_ = chunk * chunkSize_; + } catch (Exception e) { + throw new TTransportException("Error seeking to chunk", __FILE__, + __LINE__, e); + } + + if (seekToEnd) { + // Never wait on the end of the file for new content, we just want to + // find the last one. + auto oldReadTimeout = readTimeout_; + scope (exit) readTimeout_ = oldReadTimeout; + readTimeout_ = dur!"hnsecs"(0); + + // Keep on reading unti the last event at point of seekToChunk call. + while ((offset_ + readState_.bufferPos_) < minEndOffset) { + if (readEvent() is null) { + break; + } + } + } + } + + void seekToEnd() { + seekToChunk(getNumChunks()); + } + + /** + * The size of the chunks the file is divided into, in bytes. + */ + ulong chunkSize() @property const { + return chunkSize_; + } + + /// ditto + void chunkSize(ulong value) @property { + enforce(!isOpen, new TTransportException( + "Cannot set chunk size after TFileReaderTransport has been opened.")); + enforce(value > EventSize.sizeof, new TTransportException("Chunks must " ~ + "be large enough to accommodate at least a single byte of payload data.")); + chunkSize_ = value; + } + + /** + * If positive, wait the specified duration for new data when arriving at + * end of file. If negative, wait forever (tailing mode), waking up to check + * in the specified interval. If zero, do not wait at all. + * + * Defaults to 500 ms. + */ + Duration readTimeout() @property const { + return readTimeout_; + } + + /// ditto + void readTimeout(Duration value) @property { + readTimeout_ = value; + } + + /// ditto + enum DEFAULT_READ_TIMEOUT = dur!"msecs"(500); + + /** + * Read buffer size, in bytes. + * + * Defaults to 1 MiB. + */ + size_t readBufferSize() @property const { + return readBufferSize_; + } + + /// ditto + void readBufferSize(size_t value) @property { + if (readBuffer_) { + enforce(value <= readBufferSize_, + "Cannot shrink read buffer after first read."); + readBuffer_.length = value; + } + readBufferSize_ = value; + } + + /// ditto + enum DEFAULT_READ_BUFFER_SIZE = 1 * 1024 * 1024; + + /** + * Arbitrary event size limit, in bytes. Must be smaller than chunk size. + * + * Defaults to zero (no limit). + */ + size_t maxEventSize() @property const { + return maxEventSize_; + } + + /// ditto + void maxEventSize(size_t value) @property { + enforce(value <= chunkSize_ - EventSize.sizeof, "Events cannot span " ~ + "mutiple chunks, maxEventSize must be smaller than chunk size."); + maxEventSize_ = value; + } + + /// ditto + enum DEFAULT_MAX_EVENT_SIZE = 0; + + /** + * The interval at which the thread wakes up to check for the next chunk + * in tailing mode. + * + * Defaults to one second. + */ + Duration corruptedEventSleepDuration() const { + return corruptedEventSleepDuration_; + } + + /// ditto + void corruptedEventSleepDuration(Duration value) { + corruptedEventSleepDuration_ = value; + } + + /// ditto + enum DEFAULT_CORRUPTED_EVENT_SLEEP_DURATION = dur!"seconds"(1); + + /** + * The maximum number of corrupted events tolerated before the whole chunk + * is skipped. + * + * Defaults to zero. + */ + uint maxCorruptedEvents() @property const { + return maxCorruptedEvents_; + } + + /// ditto + void maxCorruptedEvents(uint value) @property { + maxCorruptedEvents_ = value; + } + + /// ditto + enum DEFAULT_MAX_CORRUPTED_EVENTS = 0; + +private: + ubyte[] readEvent() { + if (!readBuffer_) { + readBuffer_ = new ubyte[readBufferSize_]; + } + + bool timeoutExpired; + while (1) { + // read from the file if read buffer is exhausted + if (readState_.bufferPos_ == readState_.bufferLen_) { + // advance the offset pointer + offset_ += readState_.bufferLen_; + + try { + // Need to clear eof flag before reading, otherwise tailing a file + // does not work. + file_.clearerr(); + + auto usedBuf = file_.rawRead(readBuffer_); + readState_.bufferLen_ = usedBuf.length; + } catch (Exception e) { + readState_.resetAllValues(); + throw new TTransportException("Error while reading from file", + __FILE__, __LINE__, e); + } + + readState_.bufferPos_ = 0; + readState_.lastDispatchPos_ = 0; + + if (readState_.bufferLen_ == 0) { + // Reached end of file. + if (readTimeout_ < dur!"hnsecs"(0)) { + // Tailing mode, sleep for the specified duration and try again. + Thread.sleep(-readTimeout_); + continue; + } else if (readTimeout_ == dur!"hnsecs"(0) || timeoutExpired) { + // Either no timeout set, or it has already expired. + readState_.resetState(0); + return null; + } else { + // Timeout mode, sleep for the specified amount of time and retry. + Thread.sleep(readTimeout_); + timeoutExpired = true; + continue; + } + } + } + + // Attempt to read an event from the buffer. + while (readState_.bufferPos_ < readState_.bufferLen_) { + if (readState_.readingSize_) { + if (readState_.eventSizeBuffPos_ == 0) { + if ((offset_ + readState_.bufferPos_)/chunkSize_ != + ((offset_ + readState_.bufferPos_ + 3)/chunkSize_)) + { + readState_.bufferPos_++; + continue; + } + } + + readState_.eventSizeBuff_[readState_.eventSizeBuffPos_++] = + readBuffer_[readState_.bufferPos_++]; + + if (readState_.eventSizeBuffPos_ == 4) { + auto size = (cast(uint[])readState_.eventSizeBuff_)[0]; + + if (size == 0) { + // This is part of the zero padding between chunks. + readState_.resetState(readState_.lastDispatchPos_); + continue; + } + + // got a valid event + readState_.readingSize_ = false; + readState_.eventLen_ = size; + readState_.eventPos_ = 0; + + // check if the event is corrupted and perform recovery if required + if (isEventCorrupted()) { + performRecovery(); + // start from the top + break; + } + } + } else { + if (!readState_.event_) { + readState_.event_ = new ubyte[readState_.eventLen_]; + } + + // take either the entire event or the remaining bytes in the buffer + auto reclaimBuffer = min(readState_.bufferLen_ - readState_.bufferPos_, + readState_.eventLen_ - readState_.eventPos_); + + // copy data from read buffer into event buffer + readState_.event_[ + readState_.eventPos_ .. readState_.eventPos_ + reclaimBuffer + ] = readBuffer_[ + readState_.bufferPos_ .. readState_.bufferPos_ + reclaimBuffer + ]; + + // increment position ptrs + readState_.eventPos_ += reclaimBuffer; + readState_.bufferPos_ += reclaimBuffer; + + // check if the event has been read in full + if (readState_.eventPos_ == readState_.eventLen_) { + // Reset the read state and return the completed event. + auto completeEvent = readState_.event_; + readState_.event_ = null; + readState_.resetState(readState_.bufferPos_); + return completeEvent; + } + } + } + } + } + + bool isEventCorrupted() { + if ((maxEventSize_ > 0) && (readState_.eventLen_ > maxEventSize_)) { + // Event size is larger than user-speficied max-event size + logError("Corrupt event read: Event size (%s) greater than max " ~ + "event size (%s)", readState_.eventLen_, maxEventSize_); + return true; + } else if (readState_.eventLen_ > chunkSize_) { + // Event size is larger than chunk size + logError("Corrupt event read: Event size (%s) greater than chunk " ~ + "size (%s)", readState_.eventLen_, chunkSize_); + return true; + } else if (((offset_ + readState_.bufferPos_ - EventSize.sizeof) / chunkSize_) != + ((offset_ + readState_.bufferPos_ + readState_.eventLen_ - EventSize.sizeof) / chunkSize_)) + { + // Size indicates that event crosses chunk boundary + logError("Read corrupt event. Event crosses chunk boundary. " ~ + "Event size: %s. Offset: %s", readState_.eventLen_, + (offset_ + readState_.bufferPos_ + EventSize.sizeof) + ); + + return true; + } + + return false; + } + + void performRecovery() { + // perform some kickass recovery + auto curChunk = getCurChunk(); + if (lastBadChunk_ == curChunk) { + numCorruptedEventsInChunk_++; + } else { + lastBadChunk_ = curChunk; + numCorruptedEventsInChunk_ = 1; + } + + if (numCorruptedEventsInChunk_ < maxCorruptedEvents_) { + // maybe there was an error in reading the file from disk + // seek to the beginning of chunk and try again + seekToChunk(curChunk); + } else { + // Just skip ahead to the next chunk if we not already at the last chunk. + if (curChunk != (getNumChunks() - 1)) { + seekToChunk(curChunk + 1); + } else if (readTimeout_ < dur!"hnsecs"(0)) { + // We are in tailing mode, wait until there is enough data to start + // the next chunk. + while(curChunk == (getNumChunks() - 1)) { + Thread.sleep(corruptedEventSleepDuration_); + } + seekToChunk(curChunk + 1); + } else { + // Pretty hosed at this stage, rewind the file back to the last + // successful point and punt on the error. + readState_.resetState(readState_.lastDispatchPos_); + currentEvent_ = null; + currentEventPos_ = 0; + + throw new TTransportException("File corrupted at offset: " ~ + to!string(offset_ + readState_.lastDispatchPos_), + TTransportException.Type.CORRUPTED_DATA); + } + } + } + + string path_; + File file_; + bool isOpen_; + long offset_; + ubyte[] currentEvent_; + size_t currentEventPos_; + ulong chunkSize_; + Duration readTimeout_; + size_t maxEventSize_; + + // Read buffer – lazily allocated on the first read(). + ubyte[] readBuffer_; + size_t readBufferSize_; + + static struct ReadState { + ubyte[] event_; + size_t eventLen_; + size_t eventPos_; + + // keep track of event size + ubyte[4] eventSizeBuff_; + ubyte eventSizeBuffPos_; + bool readingSize_ = true; + + // read buffer variables + size_t bufferPos_; + size_t bufferLen_; + + // last successful dispatch point + size_t lastDispatchPos_; + + void resetState(size_t lastDispatchPos) { + readingSize_ = true; + eventSizeBuffPos_ = 0; + lastDispatchPos_ = lastDispatchPos; + } + + void resetAllValues() { + resetState(0); + bufferPos_ = 0; + bufferLen_ = 0; + event_ = null; + } + } + ReadState readState_; + + ulong lastBadChunk_; + uint maxCorruptedEvents_; + uint numCorruptedEventsInChunk_; + Duration corruptedEventSleepDuration_; +} + +/** + * A transport used to write log files. It can never be read from, calling + * read() throws. + * + * Contrary to the C++ design, explicitly opening the transport/file before + * using is necessary to allow manually closing the file without relying on the + * object lifetime. + */ +final class TFileWriterTransport : TBaseTransport { + /** + * Creates a new file writer transport. + * + * Params: + * path = Path of the file to opperate on. + */ + this(string path) { + path_ = path; + + chunkSize_ = DEFAULT_CHUNK_SIZE; + eventBufferSize_ = DEFAULT_EVENT_BUFFER_SIZE; + ioErrorSleepDuration = DEFAULT_IO_ERROR_SLEEP_DURATION; + maxFlushBytes_ = DEFAULT_MAX_FLUSH_BYTES; + maxFlushInterval_ = DEFAULT_MAX_FLUSH_INTERVAL; + } + + override bool isOpen() @property { + return isOpen_; + } + + /** + * A file writer transport can never be read from. + */ + override bool peek() { + return false; + } + + override void open() { + if (isOpen) return; + + writerThread_ = spawn( + &writerThread, + path_, + chunkSize_, + maxFlushBytes_, + maxFlushInterval_, + ioErrorSleepDuration_ + ); + setMaxMailboxSize(writerThread_, eventBufferSize_, OnCrowding.block); + isOpen_ = true; + } + + /** + * Closes the transport, i.e. the underlying file and the writer thread. + */ + override void close() { + if (!isOpen) return; + + send(writerThread_, ShutdownMessage(), thisTid); + receive((ShutdownMessage msg, Tid tid){}); + isOpen_ = false; + } + + /** + * Enqueues the passed slice of data for writing and immediately returns. + * write() only blocks if the event buffer has been exhausted. + * + * The transport must be open when calling this. + * + * Params: + * buf = Slice of data to write. + */ + override void write(in ubyte[] buf) { + enforce(isOpen, new TTransportException( + "Cannot write to non-open file.", TTransportException.Type.NOT_OPEN)); + + if (buf.empty) { + logError("Cannot write empty event, skipping."); + return; + } + + auto maxSize = chunkSize - EventSize.sizeof; + enforce(buf.length <= maxSize, new TTransportException( + "Cannot write more than " ~ to!string(maxSize) ~ + "bytes at once due to chunk size.")); + + send(writerThread_, buf.idup); + } + + /** + * Flushes any pending data to be written. + * + * The transport must be open when calling this. + * + * Throws: TTransportException if an error occurs. + */ + override void flush() { + enforce(isOpen, new TTransportException( + "Cannot flush file if not open.", TTransportException.Type.NOT_OPEN)); + + send(writerThread_, FlushMessage(), thisTid); + receive((FlushMessage msg, Tid tid){}); + } + + /** + * The size of the chunks the file is divided into, in bytes. + * + * A single event (write call) never spans multiple chunks – this + * effectively limits the event size to chunkSize - EventSize.sizeof. + */ + ulong chunkSize() @property { + return chunkSize_; + } + + /// ditto + void chunkSize(ulong value) @property { + enforce(!isOpen, new TTransportException( + "Cannot set chunk size after TFileWriterTransport has been opened.")); + chunkSize_ = value; + } + + /** + * The maximum number of write() calls buffered, or zero for no limit. + * + * If the buffer is exhausted, write() will block until space becomes + * available. + */ + size_t eventBufferSize() @property { + return eventBufferSize_; + } + + /// ditto + void eventBufferSize(size_t value) @property { + eventBufferSize_ = value; + if (isOpen) { + setMaxMailboxSize(writerThread_, value, OnCrowding.throwException); + } + } + + /// ditto + enum DEFAULT_EVENT_BUFFER_SIZE = 10_000; + + /** + * Maximum number of bytes buffered before writing and flushing the file + * to disk. + * + * Currently cannot be set after the first call to write(). + */ + size_t maxFlushBytes() @property { + return maxFlushBytes_; + } + + /// ditto + void maxFlushBytes(size_t value) @property { + maxFlushBytes_ = value; + if (isOpen) { + send(writerThread_, FlushBytesMessage(value)); + } + } + + /// ditto + enum DEFAULT_MAX_FLUSH_BYTES = 1000 * 1024; + + /** + * Maximum interval between flushing the file to disk. + * + * Currenlty cannot be set after the first call to write(). + */ + Duration maxFlushInterval() @property { + return maxFlushInterval_; + } + + /// ditto + void maxFlushInterval(Duration value) @property { + maxFlushInterval_ = value; + if (isOpen) { + send(writerThread_, FlushIntervalMessage(value)); + } + } + + /// ditto + enum DEFAULT_MAX_FLUSH_INTERVAL = dur!"seconds"(3); + + /** + * When the writer thread encounteres an I/O error, it goes pauses for a + * short time before trying to reopen the output file. This controls the + * sleep duration. + */ + Duration ioErrorSleepDuration() @property { + return ioErrorSleepDuration_; + } + + /// ditto + void ioErrorSleepDuration(Duration value) @property { + ioErrorSleepDuration_ = value; + if (isOpen) { + send(writerThread_, FlushIntervalMessage(value)); + } + } + + /// ditto + enum DEFAULT_IO_ERROR_SLEEP_DURATION = dur!"msecs"(500); + +private: + string path_; + ulong chunkSize_; + size_t eventBufferSize_; + Duration ioErrorSleepDuration_; + size_t maxFlushBytes_; + Duration maxFlushInterval_; + bool isOpen_; + Tid writerThread_; +} + +private { + // Signals that the file should be flushed on disk. Sent to the writer + // thread and sent back along with the tid for confirmation. + struct FlushMessage {} + + // Signals that the writer thread should close the file and shut down. Sent + // to the writer thread and sent back along with the tid for confirmation. + struct ShutdownMessage {} + + struct FlushBytesMessage { + size_t value; + } + + struct FlushIntervalMessage { + Duration value; + } + + struct IoErrorSleepDurationMessage { + Duration value; + } + + void writerThread( + string path, + ulong chunkSize, + size_t maxFlushBytes, + Duration maxFlushInterval, + Duration ioErrorSleepDuration + ) { + bool errorOpening; + File file; + ulong offset; + try { + // Open file in appending and binary mode. + file = File(path, "ab"); + offset = file.tell(); + } catch (Exception e) { + logError("Error on opening output file in writer thread: %s", e); + errorOpening = true; + } + + auto flushTimer = StopWatch(AutoStart.yes); + size_t unflushedByteCount; + + Tid shutdownRequestTid; + bool shutdownRequested; + while (true) { + if (shutdownRequested) break; + + bool forceFlush; + Tid flushRequestTid; + receiveTimeout(max(dur!"hnsecs"(0), maxFlushInterval - flushTimer.peek()), + (immutable(ubyte)[] data) { + while (errorOpening) { + logError("Writer thread going to sleep for %s µs due to IO errors", + ioErrorSleepDuration.total!"usecs"); + + // Sleep for ioErrorSleepDuration, being ready to be interrupted + // by shutdown requests. + auto timedOut = receiveTimeout(ioErrorSleepDuration, + (ShutdownMessage msg, Tid tid){ shutdownRequestTid = tid; }); + if (!timedOut) { + // We got a shutdown request, just drop all events and exit the + // main loop as to not block application shutdown with our tries + // which we must assume to fail. + break; + } + + try { + file = File(path, "ab"); + unflushedByteCount = 0; + errorOpening = false; + logError("Output file %s reopened during writer thread error " ~ + "recovery", path); + } catch (Exception e) { + logError("Unable to reopen output file %s during writer " ~ + "thread error recovery", path); + } + } + + // Make sure the event does not cross the chunk boundary by writing + // a padding consisting of zeroes if it would. + auto chunk1 = offset / chunkSize; + auto chunk2 = (offset + EventSize.sizeof + data.length - 1) / chunkSize; + + if (chunk1 != chunk2) { + // TODO: The C++ implementation refetches the offset here to »keep + // in sync« – why would this be needed? + auto padding = cast(size_t) + ((((offset / chunkSize) + 1) * chunkSize) - offset); + auto zeroes = new ubyte[padding]; + file.rawWrite(zeroes); + unflushedByteCount += padding; + offset += padding; + } + + // TODO: 2 syscalls here, is this a problem performance-wise? + // Probably abysmal performance on Windows due to rawWrite + // implementation. + uint len = cast(uint)data.length; + file.rawWrite(cast(ubyte[])(&len)[0..1]); + file.rawWrite(data); + + auto bytesWritten = EventSize.sizeof + data.length; + unflushedByteCount += bytesWritten; + offset += bytesWritten; + }, (FlushBytesMessage msg) { + maxFlushBytes = msg.value; + }, (FlushIntervalMessage msg) { + maxFlushInterval = msg.value; + }, (IoErrorSleepDurationMessage msg) { + ioErrorSleepDuration = msg.value; + }, (FlushMessage msg, Tid tid) { + forceFlush = true; + flushRequestTid = tid; + }, (OwnerTerminated msg) { + shutdownRequested = true; + }, (ShutdownMessage msg, Tid tid) { + shutdownRequested = true; + shutdownRequestTid = tid; + } + ); + + if (errorOpening) continue; + + bool flush; + if (forceFlush || shutdownRequested || unflushedByteCount > maxFlushBytes) { + flush = true; + } else if (cast(Duration)flushTimer.peek() > maxFlushInterval) { + if (unflushedByteCount == 0) { + // If the flush timer is due, but no data has been written, don't + // needlessly fsync, but do reset the timer. + flushTimer.reset(); + } else { + flush = true; + } + } + + if (flush) { + file.flush(); + flushTimer.reset(); + unflushedByteCount = 0; + if (forceFlush) send(flushRequestTid, FlushMessage(), thisTid); + } + } + + file.close(); + + if (shutdownRequestTid != Tid.init) { + send(shutdownRequestTid, ShutdownMessage(), thisTid); + } + } +} + +version (unittest) { + import core.memory : GC; + import std.file; +} + +unittest { + void tryRemove(string fileName) { + try { + remove(fileName); + } catch (Exception) {} + } + + immutable fileName = "unittest.dat.tmp"; + enforce(!exists(fileName), "Unit test output file " ~ fileName ~ + " already exists."); + + /* + * Check the most basic reading/writing operations. + */ + { + scope (exit) tryRemove(fileName); + + auto writer = new TFileWriterTransport(fileName); + writer.open(); + scope (exit) writer.close(); + + writer.write([1, 2]); + writer.write([3, 4]); + writer.write([5, 6, 7]); + writer.flush(); + + auto reader = new TFileReaderTransport(fileName); + reader.open(); + scope (exit) reader.close(); + + auto buf = new ubyte[7]; + reader.readAll(buf); + enforce(buf == [1, 2, 3, 4, 5, 6, 7]); + } + + /* + * Check that chunking works as expected. + */ + { + scope (exit) tryRemove(fileName); + + static assert(EventSize.sizeof == 4); + enum CHUNK_SIZE = 10; + + // Write some contents to the file. + { + auto writer = new TFileWriterTransport(fileName); + writer.chunkSize = CHUNK_SIZE; + writer.open(); + scope (exit) writer.close(); + + writer.write([0xde]); + writer.write([0xad]); + // Chunk boundary here. + writer.write([0xbe]); + // The next write doesn't fit in the five bytes remaining, so we expect + // padding zero bytes to be written. + writer.write([0xef, 0x12]); + + try { + writer.write(new ubyte[CHUNK_SIZE]); + enforce(false, "Could write event not fitting in a single chunk."); + } catch (TTransportException e) {} + + writer.flush(); + } + + // Check the raw contents of the file to see if chunk padding was written + // as expected. + auto file = File(fileName, "r"); + enforce(file.size == 26); + auto written = new ubyte[26]; + file.rawRead(written); + enforce(written == [ + 1, 0, 0, 0, 0xde, + 1, 0, 0, 0, 0xad, + 1, 0, 0, 0, 0xbe, + 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0xef, 0x12 + ]); + + // Read the data back in, getting all the events at once. + { + auto reader = new TFileReaderTransport(fileName); + reader.chunkSize = CHUNK_SIZE; + reader.open(); + scope (exit) reader.close(); + + auto buf = new ubyte[5]; + reader.readAll(buf); + enforce(buf == [0xde, 0xad, 0xbe, 0xef, 0x12]); + } + } + + /* + * Make sure that close() exits "quickly", i.e. that there is no problem + * with the worker thread waking up. + */ + { + import std.conv : text; + enum NUM_ITERATIONS = 1000; + + uint numOver = 0; + foreach (n; 0 .. NUM_ITERATIONS) { + scope (exit) tryRemove(fileName); + + auto transport = new TFileWriterTransport(fileName); + transport.open(); + + // Write something so that the writer thread gets started. + transport.write(cast(ubyte[])"foo"); + + // Every other iteration, also call flush(), just in case that potentially + // has any effect on how the writer thread wakes up. + if (n & 0x1) { + transport.flush(); + } + + // Time the call to close(). + auto sw = StopWatch(AutoStart.yes); + transport.close(); + sw.stop(); + + // If any attempt takes more than 500ms, treat that as a fatal failure to + // avoid looping over a potentially very slow operation. + enforce(sw.peek().total!"msecs" < 1500, + text("close() took ", sw.peek().total!"msecs", "ms.")); + + // Normally, it takes less than 5ms on my dev box. + // However, if the box is heavily loaded, some of the test runs can take + // longer. Additionally, on a Windows Server 2008 instance running in + // a VirtualBox VM, it has been observed that about a quarter of the runs + // takes (217 ± 1) ms, for reasons not yet known. + if (sw.peek().total!"msecs" > 50) { + ++numOver; + } + + // Force garbage collection runs every now and then to make sure we + // don't run out of OS thread handles. + if (!(n % 100)) GC.collect(); + } + + // Make sure fewer than a third of the runs took longer than 5ms. + enforce(numOver < NUM_ITERATIONS / 3, + text(numOver, " iterations took more than 10 ms.")); + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/framed.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/framed.d new file mode 100644 index 000000000..94effbbaf --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/framed.d @@ -0,0 +1,334 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module thrift.transport.framed; + +import core.bitop : bswap; +import std.algorithm : min; +import std.array : empty; +import std.exception : enforce; +import thrift.transport.base; + +/** + * Framed transport. + * + * All writes go into an in-memory buffer until flush is called, at which point + * the transport writes the length of the entire binary chunk followed by the + * data payload. The receiver on the other end then performs a single + * »fixed-length« read to get the whole message off the wire. + */ +final class TFramedTransport : TBaseTransport { + /** + * Constructs a new framed transport. + * + * Params: + * transport = The underlying transport to wrap. + */ + this(TTransport transport) { + transport_ = transport; + } + + /** + * Returns the wrapped transport. + */ + TTransport underlyingTransport() @property { + return transport_; + } + + override bool isOpen() @property { + return transport_.isOpen; + } + + override bool peek() { + return rBuf_.length > 0 || transport_.peek(); + } + + override void open() { + transport_.open(); + } + + override void close() { + flush(); + transport_.close(); + } + + /** + * Attempts to read data into the given buffer, stopping when the buffer is + * exhausted or the frame end is reached. + * + * TODO: Contrary to the C++ implementation, this never does cross-frame + * reads – is there actually a valid use case for that? + * + * Params: + * buf = Slice to use as buffer. + * + * Returns: How many bytes were actually read. + * + * Throws: TTransportException if an error occurs. + */ + override size_t read(ubyte[] buf) { + // If the buffer is empty, read a new frame off the wire. + if (rBuf_.empty) { + bool gotFrame = readFrame(); + if (!gotFrame) return 0; + } + + auto size = min(rBuf_.length, buf.length); + buf[0..size] = rBuf_[0..size]; + rBuf_ = rBuf_[size..$]; + return size; + } + + override void write(in ubyte[] buf) { + wBuf_ ~= buf; + } + + override void flush() { + if (wBuf_.empty) return; + + // Properly reset the write buffer even some of the protocol operations go + // wrong. + scope (exit) { + wBuf_.length = 0; + wBuf_.assumeSafeAppend(); + } + + int len = bswap(cast(int)wBuf_.length); + transport_.write(cast(ubyte[])(&len)[0..1]); + transport_.write(wBuf_); + transport_.flush(); + } + + override const(ubyte)[] borrow(ubyte* buf, size_t len) { + if (len <= rBuf_.length) { + return rBuf_; + } else { + // Don't try attempting cross-frame borrows, trying that does not make + // much sense anyway. + return null; + } + } + + override void consume(size_t len) { + enforce(len <= rBuf_.length, new TTransportException( + "Invalid consume length", TTransportException.Type.BAD_ARGS)); + rBuf_ = rBuf_[len .. $]; + } + +private: + bool readFrame() { + // Read the size of the next frame. We can't use readAll() since that + // always throws an exception on EOF, but want to throw an exception only + // if EOF occurs after partial size data. + int size; + size_t size_read; + while (size_read < size.sizeof) { + auto data = (cast(ubyte*)&size)[size_read..size.sizeof]; + auto read = transport_.read(data); + if (read == 0) { + if (size_read == 0) { + // EOF before any data was read. + return false; + } else { + // EOF after a partial frame header – illegal. + throw new TTransportException( + "No more data to read after partial frame header", + TTransportException.Type.END_OF_FILE + ); + } + } + size_read += read; + } + + size = bswap(size); + enforce(size >= 0, new TTransportException("Frame size has negative value", + TTransportException.Type.CORRUPTED_DATA)); + + // TODO: Benchmark this. + rBuf_.length = size; + rBuf_.assumeSafeAppend(); + + transport_.readAll(rBuf_); + return true; + } + + TTransport transport_; + ubyte[] rBuf_; + ubyte[] wBuf_; +} + +/** + * Wraps given transports into TFramedTransports. + */ +alias TWrapperTransportFactory!TFramedTransport TFramedTransportFactory; + +version (unittest) { + import std.random : Mt19937, uniform; + import thrift.transport.memory; +} + +// Some basic random testing, always starting with the same seed for +// deterministic unit test results – more tests in transport_test. +unittest { + auto randGen = Mt19937(42); + + // 32 kiB of data to work with. + auto data = new ubyte[1 << 15]; + foreach (ref b; data) { + b = uniform!"[]"(cast(ubyte)0, cast(ubyte)255, randGen); + } + + // Generate a list of chunk sizes to split the data into. A uniform + // distribution is not quite realistic, but std.random doesn't have anything + // else yet. + enum MAX_FRAME_LENGTH = 512; + auto chunkSizesList = new size_t[][2]; + foreach (ref chunkSizes; chunkSizesList) { + size_t sum; + while (true) { + auto curLen = uniform(0, MAX_FRAME_LENGTH, randGen); + sum += curLen; + if (sum > data.length) break; + chunkSizes ~= curLen; + } + } + chunkSizesList ~= [data.length]; // Also test whole chunk at once. + + // Test writing data. + { + foreach (chunkSizes; chunkSizesList) { + auto buf = new TMemoryBuffer; + auto framed = new TFramedTransport(buf); + + auto remainingData = data; + foreach (chunkSize; chunkSizes) { + framed.write(remainingData[0..chunkSize]); + remainingData = remainingData[chunkSize..$]; + } + framed.flush(); + + auto writtenData = data[0..($ - remainingData.length)]; + auto actualData = buf.getContents(); + + // Check frame size. + int frameSize = bswap((cast(int[])(actualData[0..int.sizeof]))[0]); + enforce(frameSize == writtenData.length); + + // Check actual data. + enforce(actualData[int.sizeof..$] == writtenData); + } + } + + // Test reading data. + { + foreach (chunkSizes; chunkSizesList) { + auto buf = new TMemoryBuffer; + + auto size = bswap(cast(int)data.length); + buf.write(cast(ubyte[])(&size)[0..1]); + buf.write(data); + + auto framed = new TFramedTransport(buf); + ubyte[] readData; + readData.reserve(data.length); + foreach (chunkSize; chunkSizes) { + // This should work with read because we have one huge frame. + auto oldReadLen = readData.length; + readData.length += chunkSize; + framed.read(readData[oldReadLen..$]); + } + + enforce(readData == data[0..readData.length]); + } + } + + // Test combined reading/writing of multiple frames. + foreach (flushProbability; [1, 2, 4, 8, 16, 32]) { + foreach (chunkSizes; chunkSizesList) { + auto buf = new TMemoryBuffer; + auto framed = new TFramedTransport(buf); + + size_t[] frameSizes; + + // Write the data. + size_t frameSize; + auto remainingData = data; + foreach (chunkSize; chunkSizes) { + framed.write(remainingData[0..chunkSize]); + remainingData = remainingData[chunkSize..$]; + + frameSize += chunkSize; + if (frameSize > 0 && uniform(0, flushProbability, randGen) == 0) { + frameSizes ~= frameSize; + frameSize = 0; + framed.flush(); + } + } + if (frameSize > 0) { + frameSizes ~= frameSize; + frameSize = 0; + framed.flush(); + } + + // Read it back. + auto readData = new ubyte[data.length - remainingData.length]; + auto remainToRead = readData; + foreach (fSize; frameSizes) { + // We are exploiting an implementation detail of TFramedTransport: + // The read buffer starts empty and it will never return more than one + // frame per read, so by just requesting all of the data, we should + // always get exactly one frame. + auto got = framed.read(remainToRead); + enforce(got == fSize); + remainToRead = remainToRead[fSize..$]; + } + + enforce(remainToRead.empty); + enforce(readData == data[0..readData.length]); + } + } +} + +// Test flush()ing an empty buffer. +unittest { + auto buf = new TMemoryBuffer(); + auto framed = new TFramedTransport(buf); + immutable out1 = [0, 0, 0, 1, 'a']; + immutable out2 = [0, 0, 0, 1, 'a', 0, 0, 0, 2, 'b', 'c']; + + framed.flush(); + enforce(buf.getContents() == []); + framed.flush(); + framed.flush(); + enforce(buf.getContents() == []); + framed.write(cast(ubyte[])"a"); + enforce(buf.getContents() == []); + framed.flush(); + enforce(buf.getContents() == out1); + framed.flush(); + framed.flush(); + enforce(buf.getContents() == out1); + framed.write(cast(ubyte[])"bc"); + enforce(buf.getContents() == out1); + framed.flush(); + enforce(buf.getContents() == out2); + framed.flush(); + framed.flush(); + enforce(buf.getContents() == out2); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/http.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/http.d new file mode 100644 index 000000000..0e58deeb6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/http.d @@ -0,0 +1,459 @@ +/* + * 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. + */ + +/** + * HTTP tranpsort implementation, modelled after the C++ one. + * + * Unfortunately, libcurl is quite heavyweight and supports only client-side + * applications. This is an implementation of the basic HTTP/1.1 parts + * supporting HTTP 100 Continue, chunked transfer encoding, keepalive, etc. + */ +module thrift.transport.http; + +import std.algorithm : canFind, countUntil, endsWith, findSplit, min, startsWith; +import std.ascii : toLower; +import std.array : empty; +import std.conv : parse, to; +import std.datetime : Clock, UTC; +import std.string : stripLeft; +import thrift.base : VERSION; +import thrift.transport.base; +import thrift.transport.memory; +import thrift.transport.socket; + +/** + * Base class for both client- and server-side HTTP transports. + */ +abstract class THttpTransport : TBaseTransport { + this(TTransport transport) { + transport_ = transport; + readHeaders_ = true; + httpBuf_ = new ubyte[HTTP_BUFFER_SIZE]; + httpBufRemaining_ = httpBuf_[0 .. 0]; + readBuffer_ = new TMemoryBuffer; + writeBuffer_ = new TMemoryBuffer; + } + + override bool isOpen() { + return transport_.isOpen(); + } + + override bool peek() { + return transport_.peek(); + } + + override void open() { + transport_.open(); + } + + override void close() { + transport_.close(); + } + + override size_t read(ubyte[] buf) { + if (!readBuffer_.peek()) { + readBuffer_.reset(); + + if (!refill()) return 0; + + if (readHeaders_) { + readHeaders(); + } + + size_t got; + if (chunked_) { + got = readChunked(); + } else { + got = readContent(contentLength_); + } + readHeaders_ = true; + + if (got == 0) return 0; + } + return readBuffer_.read(buf); + } + + override size_t readEnd() { + // Read any pending chunked data (footers etc.) + if (chunked_) { + while (!chunkedDone_) { + readChunked(); + } + } + return 0; + } + + override void write(in ubyte[] buf) { + writeBuffer_.write(buf); + } + + override void flush() { + auto data = writeBuffer_.getContents(); + string header = getHeader(data.length); + + transport_.write(cast(const(ubyte)[]) header); + transport_.write(data); + transport_.flush(); + + // Reset the buffer and header variables. + writeBuffer_.reset(); + readHeaders_ = true; + } + + /** + * The size of the buffer to read HTTP requests into, in bytes. Will expand + * as required. + */ + enum HTTP_BUFFER_SIZE = 1024; + +protected: + abstract string getHeader(size_t dataLength); + abstract bool parseStatusLine(const(ubyte)[] status); + + void parseHeader(const(ubyte)[] header) { + auto split = findSplit(header, [':']); + if (split[1].empty) { + // No colon found. + return; + } + + static bool compToLower(ubyte a, ubyte b) { + return toLower(cast(char)a) == toLower(cast(char)b); + } + + if (startsWith!compToLower(split[0], cast(ubyte[])"transfer-encoding")) { + if (endsWith!compToLower(split[2], cast(ubyte[])"chunked")) { + chunked_ = true; + } + } else if (startsWith!compToLower(split[0], cast(ubyte[])"content-length")) { + chunked_ = false; + auto lengthString = stripLeft(cast(const(char)[])split[2]); + contentLength_ = parse!size_t(lengthString); + } + } + +private: + ubyte[] readLine() { + while (true) { + auto split = findSplit(httpBufRemaining_, cast(ubyte[])"\r\n"); + + if (split[1].empty) { + // No CRLF yet, move whatever we have now to front and refill. + if (httpBufRemaining_.empty) { + httpBufRemaining_ = httpBuf_[0 .. 0]; + } else { + httpBuf_[0 .. httpBufRemaining_.length] = httpBufRemaining_; + httpBufRemaining_ = httpBuf_[0 .. httpBufRemaining_.length]; + } + + if (!refill()) { + auto buf = httpBufRemaining_; + httpBufRemaining_ = httpBufRemaining_[$ - 1 .. $ - 1]; + return buf; + } + } else { + // Set the remaining buffer to the part after \r\n and return the part + // (line) before it. + httpBufRemaining_ = split[2]; + return split[0]; + } + } + } + + void readHeaders() { + // Initialize headers state variables + contentLength_ = 0; + chunked_ = false; + chunkedDone_ = false; + chunkSize_ = 0; + + // Control state flow + bool statusLine = true; + bool finished; + + // Loop until headers are finished + while (true) { + auto line = readLine(); + + if (line.length == 0) { + if (finished) { + readHeaders_ = false; + return; + } else { + // Must have been an HTTP 100, keep going for another status line + statusLine = true; + } + } else { + if (statusLine) { + statusLine = false; + finished = parseStatusLine(line); + } else { + parseHeader(line); + } + } + } + } + + size_t readChunked() { + size_t length; + + auto line = readLine(); + size_t chunkSize; + try { + auto charLine = cast(char[])line; + chunkSize = parse!size_t(charLine, 16); + } catch (Exception e) { + throw new TTransportException("Invalid chunk size: " ~ to!string(line), + TTransportException.Type.CORRUPTED_DATA); + } + + if (chunkSize == 0) { + readChunkedFooters(); + } else { + // Read data content + length += readContent(chunkSize); + // Read trailing CRLF after content + readLine(); + } + return length; + } + + void readChunkedFooters() { + while (true) { + auto line = readLine(); + if (line.length == 0) { + chunkedDone_ = true; + break; + } + } + } + + size_t readContent(size_t size) { + auto need = size; + while (need > 0) { + if (httpBufRemaining_.length == 0) { + // We have given all the data, reset position to head of the buffer. + httpBufRemaining_ = httpBuf_[0 .. 0]; + if (!refill()) return size - need; + } + + auto give = min(httpBufRemaining_.length, need); + readBuffer_.write(cast(ubyte[])httpBufRemaining_[0 .. give]); + httpBufRemaining_ = httpBufRemaining_[give .. $]; + need -= give; + } + return size; + } + + bool refill() { + // Is there a nicer way to do this? + auto indexBegin = httpBufRemaining_.ptr - httpBuf_.ptr; + auto indexEnd = indexBegin + httpBufRemaining_.length; + + if (httpBuf_.length - indexEnd <= (httpBuf_.length / 4)) { + httpBuf_.length *= 2; + } + + // Read more data. + auto got = transport_.read(cast(ubyte[])httpBuf_[indexEnd .. $]); + if (got == 0) return false; + httpBufRemaining_ = httpBuf_[indexBegin .. indexEnd + got]; + return true; + } + + TTransport transport_; + + TMemoryBuffer writeBuffer_; + TMemoryBuffer readBuffer_; + + bool readHeaders_; + bool chunked_; + bool chunkedDone_; + size_t chunkSize_; + size_t contentLength_; + + ubyte[] httpBuf_; + ubyte[] httpBufRemaining_; +} + +/** + * HTTP client transport. + */ +final class TClientHttpTransport : THttpTransport { + /** + * Constructs a client http transport operating on the passed underlying + * transport. + * + * Params: + * transport = The underlying transport used for the actual I/O. + * host = The HTTP host string. + * path = The HTTP path string. + */ + this(TTransport transport, string host, string path) { + super(transport); + host_ = host; + path_ = path; + } + + /** + * Convenience overload for constructing a client HTTP transport using a + * TSocket connecting to the specified host and port. + * + * Params: + * host = The server to connect to, also used as HTTP host string. + * port = The port to connect to. + * path = The HTTP path string. + */ + this(string host, ushort port, string path) { + this(new TSocket(host, port), host, path); + } + +protected: + override string getHeader(size_t dataLength) { + return "POST " ~ path_ ~ " HTTP/1.1\r\n" ~ + "Host: " ~ host_ ~ "\r\n" ~ + "Content-Type: application/x-thrift\r\n" ~ + "Content-Length: " ~ to!string(dataLength) ~ "\r\n" ~ + "Accept: application/x-thrift\r\n" ~ + "User-Agent: Thrift/" ~ VERSION ~ " (D/TClientHttpTransport)\r\n" ~ + "\r\n"; + } + + override bool parseStatusLine(const(ubyte)[] status) { + // HTTP-Version SP Status-Code SP Reason-Phrase CRLF + auto firstSplit = findSplit(status, [' ']); + if (firstSplit[1].empty) { + throw new TTransportException("Bad status: " ~ to!string(status), + TTransportException.Type.CORRUPTED_DATA); + } + + auto codeReason = firstSplit[2][countUntil!"a != b"(firstSplit[2], ' ') .. $]; + auto secondSplit = findSplit(codeReason, [' ']); + if (secondSplit[1].empty) { + throw new TTransportException("Bad status: " ~ to!string(status), + TTransportException.Type.CORRUPTED_DATA); + } + + if (secondSplit[0] == "200") { + // HTTP 200 = OK, we got the response + return true; + } else if (secondSplit[0] == "100") { + // HTTP 100 = continue, just keep reading + return false; + } + + throw new TTransportException("Bad status (unhandled status code): " ~ + to!string(cast(const(char[]))status), TTransportException.Type.CORRUPTED_DATA); + } + +private: + string host_; + string path_; +} + +/** + * HTTP server transport. + */ +final class TServerHttpTransport : THttpTransport { + /** + * Constructs a new instance. + * + * Param: + * transport = The underlying transport used for the actual I/O. + */ + this(TTransport transport) { + super(transport); + } + +protected: + override string getHeader(size_t dataLength) { + return "HTTP/1.1 200 OK\r\n" ~ + "Date: " ~ getRFC1123Time() ~ "\r\n" ~ + "Server: Thrift/" ~ VERSION ~ "\r\n" ~ + "Content-Type: application/x-thrift\r\n" ~ + "Content-Length: " ~ to!string(dataLength) ~ "\r\n" ~ + "Connection: Keep-Alive\r\n" ~ + "\r\n"; + } + + override bool parseStatusLine(const(ubyte)[] status) { + // Method SP Request-URI SP HTTP-Version CRLF. + auto split = findSplit(status, [' ']); + if (split[1].empty) { + throw new TTransportException("Bad status: " ~ to!string(status), + TTransportException.Type.CORRUPTED_DATA); + } + + auto uriVersion = split[2][countUntil!"a != b"(split[2], ' ') .. $]; + if (!canFind(uriVersion, ' ')) { + throw new TTransportException("Bad status: " ~ to!string(status), + TTransportException.Type.CORRUPTED_DATA); + } + + if (split[0] == "POST") { + // POST method ok, looking for content. + return true; + } + + throw new TTransportException("Bad status (unsupported method): " ~ + to!string(status), TTransportException.Type.CORRUPTED_DATA); + } +} + +/** + * Wraps a transport into a HTTP server protocol. + */ +alias TWrapperTransportFactory!TServerHttpTransport TServerHttpTransportFactory; + +private { + import std.string : format; + string getRFC1123Time() { + auto sysTime = Clock.currTime(UTC()); + + auto dayName = capMemberName(sysTime.dayOfWeek); + auto monthName = capMemberName(sysTime.month); + + return format("%s, %s %s %s %s:%s:%s GMT", dayName, sysTime.day, + monthName, sysTime.year, sysTime.hour, sysTime.minute, sysTime.second); + } + + import std.ascii : toUpper; + import std.traits : EnumMembers; + string capMemberName(T)(T val) if (is(T == enum)) { + foreach (i, e; EnumMembers!T) { + enum name = __traits(derivedMembers, T)[i]; + enum capName = cast(char) toUpper(name[0]) ~ name [1 .. $]; + if (val == e) { + return capName; + } + } + throw new Exception("Not a member of " ~ T.stringof ~ ": " ~ to!string(val)); + } + + unittest { + enum Foo { + bar, + bAZ + } + + import std.exception; + enforce(capMemberName(Foo.bar) == "Bar"); + enforce(capMemberName(Foo.bAZ) == "BAZ"); + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/memory.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/memory.d new file mode 100644 index 000000000..cdf0807ab --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/memory.d @@ -0,0 +1,233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.transport.memory; + +import core.exception : onOutOfMemoryError; +import core.stdc.stdlib : free, realloc; +import std.algorithm : min; +import std.conv : text; +import thrift.transport.base; + +/** + * A transport that simply reads from and writes to an in-memory buffer. Every + * time you call write on it, the data is simply placed into a buffer, and + * every time you call read, data is consumed from that buffer. + * + * Currently, the storage for written data is never reclaimed, even if the + * buffer contents have already been read out again. + */ +final class TMemoryBuffer : TBaseTransport { + /** + * Constructs a new memory transport with an empty internal buffer. + */ + this() {} + + /** + * Constructs a new memory transport with an empty internal buffer, + * reserving space for capacity bytes in advance. + * + * If the amount of data which will be written to the buffer is already + * known on construction, this can better performance over the default + * constructor because reallocations can be avoided. + * + * If the preallocated buffer is exhausted, data can still be written to the + * transport, but reallocations will happen. + * + * Params: + * capacity = Size of the initially reserved buffer (in bytes). + */ + this(size_t capacity) { + reset(capacity); + } + + /** + * Constructs a new memory transport initially containing the passed data. + * + * For now, the passed buffer is not intelligently used, the data is just + * copied to the internal buffer. + * + * Params: + * buffer = Initial contents available to be read. + */ + this(in ubyte[] contents) { + auto size = contents.length; + reset(size); + buffer_[0 .. size] = contents[]; + writeOffset_ = size; + } + + /** + * Destructor, frees the internally allocated buffer. + */ + ~this() { + free(buffer_); + } + + /** + * Returns a read-only view of the current buffer contents. + * + * Note: For performance reasons, the returned slice is only valid for the + * life of this object, and may be invalidated on the next write() call at + * will – you might want to immediately .dup it if you intend to keep it + * around. + */ + const(ubyte)[] getContents() { + return buffer_[readOffset_ .. writeOffset_]; + } + + /** + * A memory transport is always open. + */ + override bool isOpen() @property { + return true; + } + + override bool peek() { + return writeOffset_ - readOffset_ > 0; + } + + /** + * Opening is a no-op() for a memory buffer. + */ + override void open() {} + + /** + * Closing is a no-op() for a memory buffer, it is always open. + */ + override void close() {} + + override size_t read(ubyte[] buf) { + auto size = min(buf.length, writeOffset_ - readOffset_); + buf[0 .. size] = buffer_[readOffset_ .. readOffset_ + size]; + readOffset_ += size; + return size; + } + + /** + * Shortcut version of readAll() – using this over TBaseTransport.readAll() + * can give us a nice speed increase because gives us a nice speed increase + * because it is typically a very hot path during deserialization. + */ + override void readAll(ubyte[] buf) { + auto available = writeOffset_ - readOffset_; + if (buf.length > available) { + throw new TTransportException(text("Cannot readAll() ", buf.length, + " bytes of data because only ", available, " bytes are available."), + TTransportException.Type.END_OF_FILE); + } + + buf[] = buffer_[readOffset_ .. readOffset_ + buf.length]; + readOffset_ += buf.length; + } + + override void write(in ubyte[] buf) { + auto need = buf.length; + if (bufferLen_ - writeOffset_ < need) { + // Exponential growth. + auto newLen = bufferLen_ + 1; + while (newLen - writeOffset_ < need) newLen *= 2; + cRealloc(buffer_, newLen); + bufferLen_ = newLen; + } + + buffer_[writeOffset_ .. writeOffset_ + need] = buf[]; + writeOffset_ += need; + } + + override const(ubyte)[] borrow(ubyte* buf, size_t len) { + if (len <= writeOffset_ - readOffset_) { + return buffer_[readOffset_ .. writeOffset_]; + } else { + return null; + } + } + + override void consume(size_t len) { + readOffset_ += len; + } + + void reset() { + readOffset_ = 0; + writeOffset_ = 0; + } + + void reset(size_t capacity) { + readOffset_ = 0; + writeOffset_ = 0; + if (bufferLen_ < capacity) { + cRealloc(buffer_, capacity); + bufferLen_ = capacity; + } + } + +private: + ubyte* buffer_; + size_t bufferLen_; + size_t readOffset_; + size_t writeOffset_; +} + +private { + void cRealloc(ref ubyte* data, size_t newSize) { + auto result = realloc(data, newSize); + if (result is null) onOutOfMemoryError(); + data = cast(ubyte*)result; + } +} + +version (unittest) { + import std.exception; +} + +unittest { + auto a = new TMemoryBuffer(5); + immutable(ubyte[]) testData = [1, 2, 3, 4]; + auto buf = new ubyte[testData.length]; + enforce(a.isOpen); + + // a should be empty. + enforce(!a.peek()); + enforce(a.read(buf) == 0); + assertThrown!TTransportException(a.readAll(buf)); + + // Write some data and read it back again. + a.write(testData); + enforce(a.peek()); + enforce(a.getContents() == testData); + enforce(a.read(buf) == testData.length); + enforce(buf == testData); + + // a should be empty again. + enforce(!a.peek()); + enforce(a.read(buf) == 0); + assertThrown!TTransportException(a.readAll(buf)); + + // Test the constructor which directly accepts initial data. + auto b = new TMemoryBuffer(testData); + enforce(b.isOpen); + enforce(b.peek()); + enforce(b.getContents() == testData); + + // Test borrow(). + auto borrowed = b.borrow(null, testData.length); + enforce(borrowed == testData); + enforce(b.peek()); + b.consume(testData.length); + enforce(!b.peek()); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/piped.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/piped.d new file mode 100644 index 000000000..9fe143278 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/piped.d @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.transport.piped; + +import thrift.transport.base; +import thrift.transport.memory; + +/** + * Pipes data request from one transport to another when readEnd() + * or writeEnd() is called. + * + * A typical use case would be to log requests on e.g. a socket to + * disk (i. e. pipe them to a TFileWriterTransport). + * + * The implementation keeps an internal buffer which expands to + * hold the whole amount of data read/written until the corresponding *End() + * method is called. + * + * Contrary to the C++ implementation, this doesn't introduce yet another layer + * of input/output buffering, all calls are passed to the underlying source + * transport verbatim. + */ +final class TPipedTransport(Source = TTransport) if ( + isTTransport!Source +) : TBaseTransport { + /// The default initial buffer size if not explicitly specified, in bytes. + enum DEFAULT_INITIAL_BUFFER_SIZE = 512; + + /** + * Constructs a new instance. + * + * By default, only reads are piped (pipeReads = true, pipeWrites = false). + * + * Params: + * srcTrans = The transport to which all requests are forwarded. + * dstTrans = The transport the read/written data is copied to. + * initialBufferSize = The default size of the read/write buffers, for + * performance tuning. + */ + this(Source srcTrans, TTransport dstTrans, + size_t initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE + ) { + srcTrans_ = srcTrans; + dstTrans_ = dstTrans; + + readBuffer_ = new TMemoryBuffer(initialBufferSize); + writeBuffer_ = new TMemoryBuffer(initialBufferSize); + + pipeReads_ = true; + pipeWrites_ = false; + } + + bool pipeReads() @property const { + return pipeReads_; + } + + void pipeReads(bool value) @property { + if (!value) { + readBuffer_.reset(); + } + pipeReads_ = value; + } + + bool pipeWrites() @property const { + return pipeWrites_; + } + + void pipeWrites(bool value) @property { + if (!value) { + writeBuffer_.reset(); + } + pipeWrites_ = value; + } + + override bool isOpen() { + return srcTrans_.isOpen(); + } + + override bool peek() { + return srcTrans_.peek(); + } + + override void open() { + srcTrans_.open(); + } + + override void close() { + srcTrans_.close(); + } + + override size_t read(ubyte[] buf) { + auto bytesRead = srcTrans_.read(buf); + + if (pipeReads_) { + readBuffer_.write(buf[0 .. bytesRead]); + } + + return bytesRead; + } + + override size_t readEnd() { + if (pipeReads_) { + auto data = readBuffer_.getContents(); + dstTrans_.write(data); + dstTrans_.flush(); + readBuffer_.reset(); + + srcTrans_.readEnd(); + + // Return data.length instead of the readEnd() result of the source + // transports because it might not be available from it. + return data.length; + } + + return srcTrans_.readEnd(); + } + + override void write(in ubyte[] buf) { + if (pipeWrites_) { + writeBuffer_.write(buf); + } + + srcTrans_.write(buf); + } + + override size_t writeEnd() { + if (pipeWrites_) { + auto data = writeBuffer_.getContents(); + dstTrans_.write(data); + dstTrans_.flush(); + writeBuffer_.reset(); + + srcTrans_.writeEnd(); + + // Return data.length instead of the readEnd() result of the source + // transports because it might not be available from it. + return data.length; + } + + return srcTrans_.writeEnd(); + } + + override void flush() { + srcTrans_.flush(); + } + +private: + Source srcTrans_; + TTransport dstTrans_; + + TMemoryBuffer readBuffer_; + TMemoryBuffer writeBuffer_; + + bool pipeReads_; + bool pipeWrites_; +} + +/** + * TPipedTransport construction helper to avoid having to explicitly + * specify the transport types, i.e. to allow the constructor being called + * using IFTI (see $(DMDBUG 6082, D Bugzilla enhancement request 6082)). + */ +TPipedTransport!Source tPipedTransport(Source)( + Source srcTrans, TTransport dstTrans +) if (isTTransport!Source) { + return new typeof(return)(srcTrans, dstTrans); +} + +version (unittest) { + // DMD @@BUG@@: UFCS for std.array.empty doesn't work when import is moved + // into unittest block. + import std.array; + import std.exception : enforce; +} + +unittest { + auto underlying = new TMemoryBuffer; + auto pipeTarget = new TMemoryBuffer; + auto trans = tPipedTransport(underlying, pipeTarget); + + underlying.write(cast(ubyte[])"abcd"); + + ubyte[4] buffer; + trans.readAll(buffer[0 .. 2]); + enforce(buffer[0 .. 2] == "ab"); + enforce(pipeTarget.getContents().empty); + + trans.readEnd(); + enforce(pipeTarget.getContents() == "ab"); + pipeTarget.reset(); + + underlying.write(cast(ubyte[])"ef"); + trans.readAll(buffer[0 .. 2]); + enforce(buffer[0 .. 2] == "cd"); + enforce(pipeTarget.getContents().empty); + + trans.readAll(buffer[0 .. 2]); + enforce(buffer[0 .. 2] == "ef"); + enforce(pipeTarget.getContents().empty); + + trans.readEnd(); + enforce(pipeTarget.getContents() == "cdef"); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/range.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/range.d new file mode 100644 index 000000000..761cea132 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/range.d @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Transports which operate on generic D ranges. + */ +module thrift.transport.range; + +import std.array : empty; +import std.range; +import std.traits : Unqual; +import thrift.transport.base; + +/** + * Adapts an ubyte input range for reading via the TTransport interface. + * + * The case where R is a plain ubyte[] is reasonably optimized, so a possible + * use case for TInputRangeTransport would be to deserialize some data held in + * a memory buffer. + */ +final class TInputRangeTransport(R) if ( + isInputRange!(Unqual!R) && is(ElementType!R : const(ubyte)) +) : TBaseTransport { + /** + * Constructs a new instance. + * + * Params: + * data = The input range to use as data. + */ + this(R data) { + data_ = data; + } + + /** + * An input range transport is always open. + */ + override bool isOpen() @property { + return true; + } + + override bool peek() { + return !data_.empty; + } + + /** + * Opening is a no-op() for an input range transport. + */ + override void open() {} + + /** + * Closing is a no-op() for a memory buffer. + */ + override void close() {} + + override size_t read(ubyte[] buf) { + auto data = data_.take(buf.length); + auto bytes = data.length; + + static if (is(typeof(R.init[1 .. 2]) : const(ubyte)[])) { + // put() is currently unnecessarily slow if both ranges are sliceable. + buf[0 .. bytes] = data[]; + data_ = data_[bytes .. $]; + } else { + buf.put(data); + } + + return bytes; + } + + /** + * Shortcut version of readAll() for slicable ranges. + * + * Because readAll() is typically a very hot path during deserialization, + * using this over TBaseTransport.readAll() gives us a nice increase in + * speed due to the reduced amount of indirections. + */ + override void readAll(ubyte[] buf) { + static if (is(typeof(R.init[1 .. 2]) : const(ubyte)[])) { + if (buf.length <= data_.length) { + buf[] = data_[0 .. buf.length]; + data_ = data_[buf.length .. $]; + return; + } + } + super.readAll(buf); + } + + override const(ubyte)[] borrow(ubyte* buf, size_t len) { + static if (is(R : const(ubyte)[])) { + // Can only borrow if our data type is actually an ubyte array. + if (len <= data_.length) { + return data_; + } + } + return null; + } + + override void consume(size_t len) { + static if (is(R : const(ubyte)[])) { + if (len > data_.length) { + throw new TTransportException("Invalid consume length", + TTransportException.Type.BAD_ARGS); + } + data_ = data_[len .. $]; + } else { + super.consume(len); + } + } + + /** + * Sets a new data range to use. + */ + void reset(R data) { + data_ = data; + } + +private: + R data_; +} + +/** + * TInputRangeTransport construction helper to avoid having to explicitly + * specify the argument type, i.e. to allow the constructor being called using + * IFTI (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D + * Bugzilla enhancement requet 6082)). + */ +TInputRangeTransport!R tInputRangeTransport(R)(R data) if ( + is (TInputRangeTransport!R) +) { + return new TInputRangeTransport!R(data); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/socket.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/socket.d new file mode 100644 index 000000000..fcb38da36 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/socket.d @@ -0,0 +1,454 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.transport.socket; + +import core.stdc.errno: ECONNRESET; +import core.thread : Thread; +import core.time : dur, Duration; +import std.array : empty; +import std.conv : text, to; +import std.exception : enforce; +import std.socket; +import thrift.base; +import thrift.transport.base; +import thrift.internal.socket; + +/** + * Common parts of a socket TTransport implementation, regardless of how the + * actual I/O is performed (sync/async). + */ +abstract class TSocketBase : TBaseTransport { + /** + * Constructor that takes an already created, connected (!) socket. + * + * Params: + * socket = Already created, connected socket object. + */ + this(Socket socket) { + socket_ = socket; + setSocketOpts(); + } + + /** + * Creates a new unconnected socket that will connect to the given host + * on the given port. + * + * Params: + * host = Remote host. + * port = Remote port. + */ + this(string host, ushort port) { + host_ = host; + port_ = port; + } + + /** + * Checks whether the socket is connected. + */ + override bool isOpen() @property { + return socket_ !is null; + } + + /** + * Writes as much data to the socket as there can be in a single OS call. + * + * Params: + * buf = Data to write. + * + * Returns: The actual number of bytes written. Never more than buf.length. + */ + abstract size_t writeSome(in ubyte[] buf) out (written) { + // DMD @@BUG@@: Enabling this e.g. fails the contract in the + // async_test_server, because buf.length evaluates to 0 here, even though + // in the method body it correctly is 27 (equal to the return value). + version (none) assert(written <= buf.length, text("Implementation wrote " ~ + "more data than requested to?! (", written, " vs. ", buf.length, ")")); + } body { + assert(0, "DMD bug? – Why would contracts work for interfaces, but not " ~ + "for abstract methods? " ~ + "(Error: function […] in and out contracts require function body"); + } + + /** + * Returns the actual address of the peer the socket is connected to. + * + * In contrast, the host and port properties contain the address used to + * establish the connection, and are not updated after the connection. + * + * The socket must be open when calling this. + */ + Address getPeerAddress() { + enforce(isOpen, new TTransportException("Cannot get peer host for " ~ + "closed socket.", TTransportException.Type.NOT_OPEN)); + + if (!peerAddress_) { + peerAddress_ = socket_.remoteAddress(); + assert(peerAddress_); + } + + return peerAddress_; + } + + /** + * The host the socket is connected to or will connect to. Null if an + * already connected socket was used to construct the object. + */ + string host() const @property { + return host_; + } + + /** + * The port the socket is connected to or will connect to. Zero if an + * already connected socket was used to construct the object. + */ + ushort port() const @property { + return port_; + } + + /// The socket send timeout. + Duration sendTimeout() const @property { + return sendTimeout_; + } + + /// Ditto + void sendTimeout(Duration value) @property { + sendTimeout_ = value; + } + + /// The socket receiving timeout. Values smaller than 500 ms are not + /// supported on Windows. + Duration recvTimeout() const @property { + return recvTimeout_; + } + + /// Ditto + void recvTimeout(Duration value) @property { + recvTimeout_ = value; + } + + /** + * Returns the OS handle of the underlying socket. + * + * Should not usually be used directly, but access to it can be necessary + * to interface with C libraries. + */ + typeof(socket_.handle()) socketHandle() @property { + return socket_.handle(); + } + +protected: + /** + * Sets the needed socket options. + */ + void setSocketOpts() { + try { + alias SocketOptionLevel.SOCKET lvlSock; + Linger l; + l.on = 0; + l.time = 0; + socket_.setOption(lvlSock, SocketOption.LINGER, l); + } catch (SocketException e) { + logError("Could not set socket option: %s", e); + } + + // Just try to disable Nagle's algorithm – this will fail if we are passed + // in a non-TCP socket via the Socket-accepting constructor. + try { + socket_.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, true); + } catch (SocketException e) {} + } + + /// Remote host. + string host_; + + /// Remote port. + ushort port_; + + /// Timeout for sending. + Duration sendTimeout_; + + /// Timeout for receiving. + Duration recvTimeout_; + + /// Cached peer address. + Address peerAddress_; + + /// Cached peer host name. + string peerHost_; + + /// Cached peer port. + ushort peerPort_; + + /// Wrapped socket object. + Socket socket_; +} + +/** + * Socket implementation of the TTransport interface. + * + * Due to the limitations of std.socket, currently only TCP/IP sockets are + * supported (i.e. Unix domain sockets are not). + */ +class TSocket : TSocketBase { + /// + this(Socket socket) { + super(socket); + } + + /// + this(string host, ushort port) { + super(host, port); + } + + /** + * Connects the socket. + */ + override void open() { + if (isOpen) return; + + enforce(!host_.empty, new TTransportException( + "Cannot open socket to null host.", TTransportException.Type.NOT_OPEN)); + enforce(port_ != 0, new TTransportException( + "Cannot open socket to port zero.", TTransportException.Type.NOT_OPEN)); + + Address[] addrs; + try { + addrs = getAddress(host_, port_); + } catch (SocketException e) { + throw new TTransportException("Could not resolve given host string.", + TTransportException.Type.NOT_OPEN, __FILE__, __LINE__, e); + } + + Exception[] errors; + foreach (addr; addrs) { + try { + socket_ = new TcpSocket(addr.addressFamily); + setSocketOpts(); + socket_.connect(addr); + break; + } catch (SocketException e) { + errors ~= e; + } + } + if (errors.length == addrs.length) { + socket_ = null; + // Need to throw a TTransportException to abide the TTransport API. + import std.algorithm, std.range; + throw new TTransportException( + text("Failed to connect to ", host_, ":", port_, "."), + TTransportException.Type.NOT_OPEN, + __FILE__, __LINE__, + new TCompoundOperationException( + text( + "All addresses tried failed (", + joiner(map!q{text(a[0], `: "`, a[1].msg, `"`)}(zip(addrs, errors)), ", "), + ")." + ), + errors + ) + ); + } + } + + /** + * Closes the socket. + */ + override void close() { + if (!isOpen) return; + + socket_.close(); + socket_ = null; + } + + override bool peek() { + if (!isOpen) return false; + + ubyte buf; + auto r = socket_.receive((&buf)[0 .. 1], SocketFlags.PEEK); + if (r == -1) { + auto lastErrno = getSocketErrno(); + static if (connresetOnPeerShutdown) { + if (lastErrno == ECONNRESET) { + close(); + return false; + } + } + throw new TTransportException("Peeking into socket failed: " ~ + socketErrnoString(lastErrno), TTransportException.Type.UNKNOWN); + } + return (r > 0); + } + + override size_t read(ubyte[] buf) { + enforce(isOpen, new TTransportException( + "Cannot read if socket is not open.", TTransportException.Type.NOT_OPEN)); + + typeof(getSocketErrno()) lastErrno; + ushort tries; + while (tries++ <= maxRecvRetries_) { + auto r = socket_.receive(cast(void[])buf); + + // If recv went fine, immediately return. + if (r >= 0) return r; + + // Something went wrong, find out how to handle it. + lastErrno = getSocketErrno(); + + if (lastErrno == INTERRUPTED_ERRNO) { + // If the syscall was interrupted, just try again. + continue; + } + + static if (connresetOnPeerShutdown) { + // See top comment. + if (lastErrno == ECONNRESET) { + return 0; + } + } + + // Not an error which is handled in a special way, just leave the loop. + break; + } + + if (isSocketCloseErrno(lastErrno)) { + close(); + throw new TTransportException("Receiving failed, closing socket: " ~ + socketErrnoString(lastErrno), TTransportException.Type.NOT_OPEN); + } else if (lastErrno == TIMEOUT_ERRNO) { + throw new TTransportException(TTransportException.Type.TIMED_OUT); + } else { + throw new TTransportException("Receiving from socket failed: " ~ + socketErrnoString(lastErrno), TTransportException.Type.UNKNOWN); + } + } + + override void write(in ubyte[] buf) { + size_t sent; + while (sent < buf.length) { + auto b = writeSome(buf[sent .. $]); + if (b == 0) { + // This should only happen if the timeout set with SO_SNDTIMEO expired. + throw new TTransportException("send() timeout expired.", + TTransportException.Type.TIMED_OUT); + } + sent += b; + } + assert(sent == buf.length); + } + + override size_t writeSome(in ubyte[] buf) { + enforce(isOpen, new TTransportException( + "Cannot write if file is not open.", TTransportException.Type.NOT_OPEN)); + + auto r = socket_.send(buf); + + // Everything went well, just return the number of bytes written. + if (r > 0) return r; + + // Handle error conditions. + if (r < 0) { + auto lastErrno = getSocketErrno(); + + if (lastErrno == WOULD_BLOCK_ERRNO) { + // Not an exceptional error per se – even with blocking sockets, + // EAGAIN apparently is returned sometimes on out-of-resource + // conditions (see the C++ implementation for details). Also, this + // allows using TSocket with non-blocking sockets e.g. in + // TNonblockingServer. + return 0; + } + + auto type = TTransportException.Type.UNKNOWN; + if (isSocketCloseErrno(lastErrno)) { + type = TTransportException.Type.NOT_OPEN; + close(); + } + + throw new TTransportException("Sending to socket failed: " ~ + socketErrnoString(lastErrno), type); + } + + // send() should never return 0. + throw new TTransportException("Sending to socket failed (0 bytes written).", + TTransportException.Type.UNKNOWN); + } + + override void sendTimeout(Duration value) @property { + super.sendTimeout(value); + setTimeout(SocketOption.SNDTIMEO, value); + } + + override void recvTimeout(Duration value) @property { + super.recvTimeout(value); + setTimeout(SocketOption.RCVTIMEO, value); + } + + /** + * Maximum number of retries for receiving from socket on read() in case of + * EAGAIN/EINTR. + */ + ushort maxRecvRetries() @property const { + return maxRecvRetries_; + } + + /// Ditto + void maxRecvRetries(ushort value) @property { + maxRecvRetries_ = value; + } + + /// Ditto + enum DEFAULT_MAX_RECV_RETRIES = 5; + +protected: + override void setSocketOpts() { + super.setSocketOpts(); + setTimeout(SocketOption.SNDTIMEO, sendTimeout_); + setTimeout(SocketOption.RCVTIMEO, recvTimeout_); + } + + void setTimeout(SocketOption type, Duration value) { + assert(type == SocketOption.SNDTIMEO || type == SocketOption.RCVTIMEO); + version (Win32) { + if (value > dur!"hnsecs"(0) && value < dur!"msecs"(500)) { + logError( + "Socket %s timeout of %s ms might be raised to 500 ms on Windows.", + (type == SocketOption.SNDTIMEO) ? "send" : "receive", + value.total!"msecs" + ); + } + } + + if (socket_) { + try { + socket_.setOption(SocketOptionLevel.SOCKET, type, value); + } catch (SocketException e) { + throw new TTransportException( + "Could not set timeout.", + TTransportException.Type.UNKNOWN, + __FILE__, + __LINE__, + e + ); + } + } + } + + /// Maximum number of recv() retries. + ushort maxRecvRetries_ = DEFAULT_MAX_RECV_RETRIES; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/ssl.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/ssl.d new file mode 100644 index 000000000..f8ce40eb7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/ssl.d @@ -0,0 +1,690 @@ +/* + * 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. + */ + +/** + * OpenSSL socket implementation, in large parts ported from C++. + */ +module thrift.transport.ssl; + +import core.exception : onOutOfMemoryError; +import core.stdc.errno : errno, EINTR; +import core.sync.mutex : Mutex; +import core.memory : GC; +import core.stdc.config; +import core.stdc.stdlib : free, malloc; +import std.ascii : toUpper; +import std.array : empty, front, popFront; +import std.conv : emplace, to; +import std.exception : enforce; +import std.socket : Address, InternetAddress, Internet6Address, Socket; +import std.string : toStringz; +import deimos.openssl.err; +import deimos.openssl.rand; +import deimos.openssl.ssl; +import deimos.openssl.x509v3; +import thrift.base; +import thrift.internal.ssl; +import thrift.transport.base; +import thrift.transport.socket; + +/** + * SSL encrypted socket implementation using OpenSSL. + * + * Note: + * On Posix systems which do not have the BSD-specific SO_NOSIGPIPE flag, you + * might want to ignore the SIGPIPE signal, as OpenSSL might try to write to + * a closed socket if the peer disconnects abruptly: + * --- + * import core.stdc.signal; + * import core.sys.posix.signal; + * signal(SIGPIPE, SIG_IGN); + * --- + */ +final class TSSLSocket : TSocket { + /** + * Creates an instance that wraps an already created, connected (!) socket. + * + * Params: + * context = The SSL socket context to use. A reference to it is stored so + * that it doesn't get cleaned up while the socket is used. + * socket = Already created, connected socket object. + */ + this(TSSLContext context, Socket socket) { + super(socket); + context_ = context; + serverSide_ = context.serverSide; + accessManager_ = context.accessManager; + } + + /** + * Creates a new unconnected socket that will connect to the given host + * on the given port. + * + * Params: + * context = The SSL socket context to use. A reference to it is stored so + * that it doesn't get cleaned up while the socket is used. + * host = Remote host. + * port = Remote port. + */ + this(TSSLContext context, string host, ushort port) { + super(host, port); + context_ = context; + serverSide_ = context.serverSide; + accessManager_ = context.accessManager; + } + + override bool isOpen() @property { + if (ssl_ is null || !super.isOpen()) return false; + + auto shutdown = SSL_get_shutdown(ssl_); + bool shutdownReceived = (shutdown & SSL_RECEIVED_SHUTDOWN) != 0; + bool shutdownSent = (shutdown & SSL_SENT_SHUTDOWN) != 0; + return !(shutdownReceived && shutdownSent); + } + + override bool peek() { + if (!isOpen) return false; + checkHandshake(); + + byte bt; + auto rc = SSL_peek(ssl_, &bt, bt.sizeof); + enforce(rc >= 0, getSSLException("SSL_peek")); + + if (rc == 0) { + ERR_clear_error(); + } + return (rc > 0); + } + + override void open() { + enforce(!serverSide_, "Cannot open a server-side SSL socket."); + if (isOpen) return; + super.open(); + } + + override void close() { + if (!isOpen) return; + + if (ssl_ !is null) { + // Two-step SSL shutdown. + auto rc = SSL_shutdown(ssl_); + if (rc == 0) { + rc = SSL_shutdown(ssl_); + } + if (rc < 0) { + // Do not throw an exception here as leaving the transport "open" will + // probably produce only more errors, and the chance we can do + // something about the error e.g. by retrying is very low. + logError("Error shutting down SSL: %s", getSSLException()); + } + + SSL_free(ssl_); + ssl_ = null; + ERR_remove_state(0); + } + super.close(); + } + + override size_t read(ubyte[] buf) { + checkHandshake(); + + int bytes; + foreach (_; 0 .. maxRecvRetries) { + bytes = SSL_read(ssl_, buf.ptr, cast(int)buf.length); + if (bytes >= 0) break; + + auto errnoCopy = errno; + if (SSL_get_error(ssl_, bytes) == SSL_ERROR_SYSCALL) { + if (ERR_get_error() == 0 && errnoCopy == EINTR) { + // FIXME: Windows. + continue; + } + } + throw getSSLException("SSL_read"); + } + return bytes; + } + + override void write(in ubyte[] buf) { + checkHandshake(); + + // Loop in case SSL_MODE_ENABLE_PARTIAL_WRITE is set in SSL_CTX. + size_t written = 0; + while (written < buf.length) { + auto bytes = SSL_write(ssl_, buf.ptr + written, + cast(int)(buf.length - written)); + if (bytes <= 0) { + throw getSSLException("SSL_write"); + } + written += bytes; + } + } + + override void flush() { + checkHandshake(); + + auto bio = SSL_get_wbio(ssl_); + enforce(bio !is null, new TSSLException("SSL_get_wbio returned null")); + + auto rc = BIO_flush(bio); + enforce(rc == 1, getSSLException("BIO_flush")); + } + + /** + * Whether to use client or server side SSL handshake protocol. + */ + bool serverSide() @property const { + return serverSide_; + } + + /// Ditto + void serverSide(bool value) @property { + serverSide_ = value; + } + + /** + * The access manager to use. + */ + void accessManager(TAccessManager value) @property { + accessManager_ = value; + } + +private: + void checkHandshake() { + enforce(super.isOpen(), new TTransportException( + TTransportException.Type.NOT_OPEN)); + + if (ssl_ !is null) return; + ssl_ = context_.createSSL(); + + SSL_set_fd(ssl_, socketHandle); + int rc; + if (serverSide_) { + rc = SSL_accept(ssl_); + } else { + rc = SSL_connect(ssl_); + } + enforce(rc > 0, getSSLException()); + authorize(ssl_, accessManager_, getPeerAddress(), + (serverSide_ ? getPeerAddress().toHostNameString() : host)); + } + + bool serverSide_; + SSL* ssl_; + TSSLContext context_; + TAccessManager accessManager_; +} + +/** + * Represents an OpenSSL context with certification settings, etc. and handles + * initialization/teardown. + * + * OpenSSL is initialized when the first instance of this class is created + * and shut down when the last one is destroyed (thread-safe). + */ +class TSSLContext { + this() { + initMutex_.lock(); + scope(exit) initMutex_.unlock(); + + if (count_ == 0) { + initializeOpenSSL(); + randomize(); + } + count_++; + + static if (OPENSSL_VERSION_NUMBER >= 0x1010000f) { // OPENSSL_VERSION_AT_LEAST(1, 1)) { + ctx_ = SSL_CTX_new(TLS_method()); + } else { + ctx_ = SSL_CTX_new(SSLv23_method()); + SSL_CTX_set_options(ctx_, SSL_OP_NO_SSLv2); + } + SSL_CTX_set_options(ctx_, SSL_OP_NO_SSLv3); // THRIFT-3164 + enforce(ctx_, getSSLException("SSL_CTX_new")); + SSL_CTX_set_mode(ctx_, SSL_MODE_AUTO_RETRY); + } + + ~this() { + initMutex_.lock(); + scope(exit) initMutex_.unlock(); + + if (ctx_ !is null) { + SSL_CTX_free(ctx_); + ctx_ = null; + } + + count_--; + if (count_ == 0) { + cleanupOpenSSL(); + } + } + + /** + * Ciphers to be used in SSL handshake process. + * + * The string must be in the colon-delimited OpenSSL notation described in + * ciphers(1), for example: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH". + */ + void ciphers(string enable) @property { + auto rc = SSL_CTX_set_cipher_list(ctx_, toStringz(enable)); + + enforce(ERR_peek_error() == 0, getSSLException("SSL_CTX_set_cipher_list")); + enforce(rc > 0, new TSSLException("None of specified ciphers are supported")); + } + + /** + * Whether peer is required to present a valid certificate. + */ + void authenticate(bool required) @property { + int mode; + if (required) { + mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | + SSL_VERIFY_CLIENT_ONCE; + } else { + mode = SSL_VERIFY_NONE; + } + SSL_CTX_set_verify(ctx_, mode, null); + } + + /** + * Load server certificate. + * + * Params: + * path = Path to the certificate file. + * format = Certificate file format. Defaults to PEM, which is currently + * the only one supported. + */ + void loadCertificate(string path, string format = "PEM") { + enforce(path !is null && format !is null, new TTransportException( + "loadCertificateChain: either <path> or <format> is null", + TTransportException.Type.BAD_ARGS)); + + if (format == "PEM") { + enforce(SSL_CTX_use_certificate_chain_file(ctx_, toStringz(path)), + getSSLException( + `Could not load SSL server certificate from file "` ~ path ~ `"` + ) + ); + } else { + throw new TSSLException("Unsupported certificate format: " ~ format); + } + } + + /* + * Load private key. + * + * Params: + * path = Path to the certificate file. + * format = Private key file format. Defaults to PEM, which is currently + * the only one supported. + */ + void loadPrivateKey(string path, string format = "PEM") { + enforce(path !is null && format !is null, new TTransportException( + "loadPrivateKey: either <path> or <format> is NULL", + TTransportException.Type.BAD_ARGS)); + + if (format == "PEM") { + enforce(SSL_CTX_use_PrivateKey_file(ctx_, toStringz(path), SSL_FILETYPE_PEM), + getSSLException( + `Could not load SSL private key from file "` ~ path ~ `"` + ) + ); + } else { + throw new TSSLException("Unsupported certificate format: " ~ format); + } + } + + /** + * Load trusted certificates from specified file (in PEM format). + * + * Params. + * path = Path to the file containing the trusted certificates. + */ + void loadTrustedCertificates(string path) { + enforce(path !is null, new TTransportException( + "loadTrustedCertificates: <path> is NULL", + TTransportException.Type.BAD_ARGS)); + + enforce(SSL_CTX_load_verify_locations(ctx_, toStringz(path), null), + getSSLException( + `Could not load SSL trusted certificate list from file "` ~ path ~ `"` + ) + ); + } + + /** + * Called during OpenSSL initialization to seed the OpenSSL entropy pool. + * + * Defaults to simply calling RAND_poll(), but it can be overwritten if a + * different, perhaps more secure implementation is desired. + */ + void randomize() { + RAND_poll(); + } + + /** + * Whether to use client or server side SSL handshake protocol. + */ + bool serverSide() @property const { + return serverSide_; + } + + /// Ditto + void serverSide(bool value) @property { + serverSide_ = value; + } + + /** + * The access manager to use. + */ + TAccessManager accessManager() @property { + if (!serverSide_ && !accessManager_) { + accessManager_ = new TDefaultClientAccessManager; + } + return accessManager_; + } + + /// Ditto + void accessManager(TAccessManager value) @property { + accessManager_ = value; + } + + SSL* createSSL() out (result) { + assert(result); + } body { + auto result = SSL_new(ctx_); + enforce(result, getSSLException("SSL_new")); + return result; + } + +protected: + /** + * Override this method for custom password callback. It may be called + * multiple times at any time during a session as necessary. + * + * Params: + * size = Maximum length of password, including null byte. + */ + string getPassword(int size) nothrow out(result) { + assert(result.length < size); + } body { + return ""; + } + + /** + * Notifies OpenSSL to use getPassword() instead of the default password + * callback with getPassword(). + */ + void overrideDefaultPasswordCallback() { + SSL_CTX_set_default_passwd_cb(ctx_, &passwordCallback); + SSL_CTX_set_default_passwd_cb_userdata(ctx_, cast(void*)this); + } + + SSL_CTX* ctx_; + +private: + bool serverSide_; + TAccessManager accessManager_; + + shared static this() { + initMutex_ = new Mutex(); + } + + static void initializeOpenSSL() { + if (initialized_) { + return; + } + initialized_ = true; + + static if (OPENSSL_VERSION_NUMBER < 0x1010000f) { // OPENSSL_VERSION_BEFORE(1, 1)) { + SSL_library_init(); + SSL_load_error_strings(); + + mutexes_ = new Mutex[CRYPTO_num_locks()]; + foreach (ref m; mutexes_) { + m = new Mutex; + } + + import thrift.internal.traits; + // As per the OpenSSL threads manpage, this isn't needed on Windows. + version (Posix) { + CRYPTO_set_id_callback(assumeNothrow(&threadIdCallback)); + } + CRYPTO_set_locking_callback(assumeNothrow(&lockingCallback)); + CRYPTO_set_dynlock_create_callback(assumeNothrow(&dynlockCreateCallback)); + CRYPTO_set_dynlock_lock_callback(assumeNothrow(&dynlockLockCallback)); + CRYPTO_set_dynlock_destroy_callback(assumeNothrow(&dynlockDestroyCallback)); + } + } + + static void cleanupOpenSSL() { + if (!initialized_) return; + + initialized_ = false; + static if (OPENSSL_VERSION_NUMBER < 0x1010000f) { // OPENSSL_VERSION_BEFORE(1, 1)) { + CRYPTO_set_locking_callback(null); + CRYPTO_set_dynlock_create_callback(null); + CRYPTO_set_dynlock_lock_callback(null); + CRYPTO_set_dynlock_destroy_callback(null); + CRYPTO_cleanup_all_ex_data(); + ERR_free_strings(); + ERR_remove_state(0); + } + } + + static extern(C) { + version (Posix) { + import core.sys.posix.pthread : pthread_self; + c_ulong threadIdCallback() { + return cast(c_ulong)pthread_self(); + } + } + + void lockingCallback(int mode, int n, const(char)* file, int line) { + if (mode & CRYPTO_LOCK) { + mutexes_[n].lock(); + } else { + mutexes_[n].unlock(); + } + } + + CRYPTO_dynlock_value* dynlockCreateCallback(const(char)* file, int line) { + enum size = __traits(classInstanceSize, Mutex); + auto mem = malloc(size)[0 .. size]; + if (!mem) onOutOfMemoryError(); + GC.addRange(mem.ptr, size); + auto mutex = emplace!Mutex(mem); + return cast(CRYPTO_dynlock_value*)mutex; + } + + void dynlockLockCallback(int mode, CRYPTO_dynlock_value* l, + const(char)* file, int line) + { + if (l is null) return; + if (mode & CRYPTO_LOCK) { + (cast(Mutex)l).lock(); + } else { + (cast(Mutex)l).unlock(); + } + } + + void dynlockDestroyCallback(CRYPTO_dynlock_value* l, + const(char)* file, int line) + { + GC.removeRange(l); + destroy(cast(Mutex)l); + free(l); + } + + int passwordCallback(char* password, int size, int, void* data) nothrow { + auto context = cast(TSSLContext) data; + auto userPassword = context.getPassword(size); + auto len = userPassword.length; + if (len > size) { + len = size; + } + password[0 .. len] = userPassword[0 .. len]; // TODO: \0 handling correct? + return cast(int)len; + } + } + + static __gshared bool initialized_; + static __gshared Mutex initMutex_; + static __gshared Mutex[] mutexes_; + static __gshared uint count_; +} + +/** + * Decides whether a remote host is legitimate or not. + * + * It is usually set at a TSSLContext, which then passes it to all the created + * TSSLSockets. + */ +class TAccessManager { + /// + enum Decision { + DENY = -1, /// Deny access. + SKIP = 0, /// Cannot decide, move on to next check (deny if last). + ALLOW = 1 /// Allow access. + } + + /** + * Determines whether a peer should be granted access or not based on its + * IP address. + * + * Called once after SSL handshake is completes successfully and before peer + * certificate is examined. + * + * If a valid decision (ALLOW or DENY) is returned, the peer certificate + * will not be verified. + */ + Decision verify(Address address) { + return Decision.DENY; + } + + /** + * Determines whether a peer should be granted access or not based on a + * name from its certificate. + * + * Called every time a DNS subjectAltName/common name is extracted from the + * peer's certificate. + * + * Params: + * host = The actual host name string from the socket connection. + * certHost = A host name string from the certificate. + */ + Decision verify(string host, const(char)[] certHost) { + return Decision.DENY; + } + + /** + * Determines whether a peer should be granted access or not based on an IP + * address from its certificate. + * + * Called every time an IP subjectAltName is extracted from the peer's + * certificate. + * + * Params: + * address = The actual address from the socket connection. + * certHost = A host name string from the certificate. + */ + Decision verify(Address address, ubyte[] certAddress) { + return Decision.DENY; + } +} + +/** + * Default access manager implementation, which just checks the host name + * resp. IP address of the connection against the certificate. + */ +class TDefaultClientAccessManager : TAccessManager { + override Decision verify(Address address) { + return Decision.SKIP; + } + + override Decision verify(string host, const(char)[] certHost) { + if (host.empty || certHost.empty) { + return Decision.SKIP; + } + return (matchName(host, certHost) ? Decision.ALLOW : Decision.SKIP); + } + + override Decision verify(Address address, ubyte[] certAddress) { + bool match; + if (certAddress.length == 4) { + if (auto ia = cast(InternetAddress)address) { + match = ((cast(ubyte*)ia.addr())[0 .. 4] == certAddress[]); + } + } else if (certAddress.length == 16) { + if (auto ia = cast(Internet6Address)address) { + match = (ia.addr() == certAddress[]); + } + } + return (match ? Decision.ALLOW : Decision.SKIP); + } +} + +private { + /** + * Matches a name with a pattern. The pattern may include wildcard. A single + * wildcard "*" can match up to one component in the domain name. + * + * Params: + * host = Host name to match, typically the SSL remote peer. + * pattern = Host name pattern, typically from the SSL certificate. + * + * Returns: true if host matches pattern, false otherwise. + */ + bool matchName(const(char)[] host, const(char)[] pattern) { + while (!host.empty && !pattern.empty) { + if (toUpper(pattern.front) == toUpper(host.front)) { + host.popFront; + pattern.popFront; + } else if (pattern.front == '*') { + while (!host.empty && host.front != '.') { + host.popFront; + } + pattern.popFront; + } else { + break; + } + } + return (host.empty && pattern.empty); + } + + unittest { + enforce(matchName("thrift.apache.org", "*.apache.org")); + enforce(!matchName("thrift.apache.org", "apache.org")); + enforce(matchName("thrift.apache.org", "thrift.*.*")); + enforce(matchName("", "")); + enforce(!matchName("", "*")); + } +} + +/** + * SSL-level exception. + */ +class TSSLException : TTransportException { + /// + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + super(msg, TTransportException.Type.INTERNAL_ERROR, file, line, next); + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/transport/zlib.d b/src/jaegertracing/thrift/lib/d/src/thrift/transport/zlib.d new file mode 100644 index 000000000..9496f9bf6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/transport/zlib.d @@ -0,0 +1,497 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module thrift.transport.zlib; + +import core.bitop : bswap; +import etc.c.zlib; +import std.algorithm : min; +import std.array : empty; +import std.conv : to; +import std.exception : enforce; +import thrift.base; +import thrift.transport.base; + +/** + * zlib transport. Compresses (deflates) data before writing it to the + * underlying transport, and decompresses (inflates) it after reading. + */ +final class TZlibTransport : TBaseTransport { + // These defaults have yet to be optimized. + enum DEFAULT_URBUF_SIZE = 128; + enum DEFAULT_CRBUF_SIZE = 1024; + enum DEFAULT_UWBUF_SIZE = 128; + enum DEFAULT_CWBUF_SIZE = 1024; + + /** + * Constructs a new zlib transport. + * + * Params: + * transport = The underlying transport to wrap. + * urbufSize = The size of the uncompressed reading buffer, in bytes. + * crbufSize = The size of the compressed reading buffer, in bytes. + * uwbufSize = The size of the uncompressed writing buffer, in bytes. + * cwbufSize = The size of the compressed writing buffer, in bytes. + */ + this( + TTransport transport, + size_t urbufSize = DEFAULT_URBUF_SIZE, + size_t crbufSize = DEFAULT_CRBUF_SIZE, + size_t uwbufSize = DEFAULT_UWBUF_SIZE, + size_t cwbufSize = DEFAULT_CWBUF_SIZE + ) { + transport_ = transport; + + enforce(uwbufSize >= MIN_DIRECT_DEFLATE_SIZE, new TTransportException( + "TZLibTransport: uncompressed write buffer must be at least " ~ + to!string(MIN_DIRECT_DEFLATE_SIZE) ~ "bytes in size.", + TTransportException.Type.BAD_ARGS)); + + urbuf_ = new ubyte[urbufSize]; + crbuf_ = new ubyte[crbufSize]; + uwbuf_ = new ubyte[uwbufSize]; + cwbuf_ = new ubyte[cwbufSize]; + + rstream_ = new z_stream; + rstream_.next_in = crbuf_.ptr; + rstream_.avail_in = 0; + rstream_.next_out = urbuf_.ptr; + rstream_.avail_out = to!uint(urbuf_.length); + + wstream_ = new z_stream; + wstream_.next_in = uwbuf_.ptr; + wstream_.avail_in = 0; + wstream_.next_out = cwbuf_.ptr; + wstream_.avail_out = to!uint(crbuf_.length); + + zlibEnforce(inflateInit(rstream_), rstream_); + scope (failure) { + zlibLogError(inflateEnd(rstream_), rstream_); + } + + zlibEnforce(deflateInit(wstream_, Z_DEFAULT_COMPRESSION), wstream_); + } + + ~this() { + zlibLogError(inflateEnd(rstream_), rstream_); + + auto result = deflateEnd(wstream_); + // Z_DATA_ERROR may indicate unflushed data, so just ignore it. + if (result != Z_DATA_ERROR) { + zlibLogError(result, wstream_); + } + } + + /** + * Returns the wrapped transport. + */ + TTransport underlyingTransport() @property { + return transport_; + } + + override bool isOpen() @property { + return readAvail > 0 || transport_.isOpen; + } + + override bool peek() { + return readAvail > 0 || transport_.peek(); + } + + override void open() { + transport_.open(); + } + + override void close() { + transport_.close(); + } + + override size_t read(ubyte[] buf) { + // The C++ implementation suggests to skip urbuf on big reads in future + // versions, we would benefit from it as well. + auto origLen = buf.length; + while (true) { + auto give = min(readAvail, buf.length); + + // If std.range.put was optimized for slicable ranges, it could be used + // here as well. + buf[0 .. give] = urbuf_[urpos_ .. urpos_ + give]; + buf = buf[give .. $]; + urpos_ += give; + + auto need = buf.length; + if (need == 0) { + // We could manage to get the all the data requested. + return origLen; + } + + if (inputEnded_ || (need < origLen && rstream_.avail_in == 0)) { + // We didn't fill buf completely, but there is no more data available. + return origLen - need; + } + + // Refill our buffer by reading more data through zlib. + rstream_.next_out = urbuf_.ptr; + rstream_.avail_out = to!uint(urbuf_.length); + urpos_ = 0; + + if (!readFromZlib()) { + // Couldn't get more data from the underlying transport. + return origLen - need; + } + } + } + + override void write(in ubyte[] buf) { + enforce(!outputFinished_, new TTransportException( + "write() called after finish()", TTransportException.Type.BAD_ARGS)); + + auto len = buf.length; + if (len > MIN_DIRECT_DEFLATE_SIZE) { + flushToZlib(uwbuf_[0 .. uwpos_], Z_NO_FLUSH); + uwpos_ = 0; + flushToZlib(buf, Z_NO_FLUSH); + } else if (len > 0) { + if (uwbuf_.length - uwpos_ < len) { + flushToZlib(uwbuf_[0 .. uwpos_], Z_NO_FLUSH); + uwpos_ = 0; + } + uwbuf_[uwpos_ .. uwpos_ + len] = buf[]; + uwpos_ += len; + } + } + + override void flush() { + enforce(!outputFinished_, new TTransportException( + "flush() called after finish()", TTransportException.Type.BAD_ARGS)); + + flushToTransport(Z_SYNC_FLUSH); + } + + override const(ubyte)[] borrow(ubyte* buf, size_t len) { + if (len <= readAvail) { + return urbuf_[urpos_ .. $]; + } + return null; + } + + override void consume(size_t len) { + enforce(readAvail >= len, new TTransportException( + "consume() did not follow a borrow().", TTransportException.Type.BAD_ARGS)); + urpos_ += len; + } + + /** + * Finalize the zlib stream. + * + * This causes zlib to flush any pending write data and write end-of-stream + * information, including the checksum. Once finish() has been called, no + * new data can be written to the stream. + */ + void finish() { + enforce(!outputFinished_, new TTransportException( + "flush() called on already finished TZlibTransport", + TTransportException.Type.BAD_ARGS)); + flushToTransport(Z_FINISH); + } + + /** + * Verify the checksum at the end of the zlib stream (by finish()). + * + * May only be called after all data has been read. + * + * Throws: TTransportException when the checksum is corrupted or there is + * still unread data left. + */ + void verifyChecksum() { + // If zlib has already reported the end of the stream, the checksum has + // been verified, no. + if (inputEnded_) return; + + enforce(!readAvail, new TTransportException( + "verifyChecksum() called before end of zlib stream", + TTransportException.Type.CORRUPTED_DATA)); + + rstream_.next_out = urbuf_.ptr; + rstream_.avail_out = to!uint(urbuf_.length); + urpos_ = 0; + + // readFromZlib() will throw an exception if the checksum is bad. + enforce(readFromZlib(), new TTransportException( + "checksum not available yet in verifyChecksum()", + TTransportException.Type.CORRUPTED_DATA)); + + enforce(inputEnded_, new TTransportException( + "verifyChecksum() called before end of zlib stream", + TTransportException.Type.CORRUPTED_DATA)); + + // If we get here, we are at the end of the stream and thus zlib has + // successfully verified the checksum. + } + +private: + size_t readAvail() const @property { + return urbuf_.length - rstream_.avail_out - urpos_; + } + + bool readFromZlib() { + assert(!inputEnded_); + + if (rstream_.avail_in == 0) { + // zlib has used up all the compressed data we provided in crbuf, read + // some more from the underlying transport. + auto got = transport_.read(crbuf_); + if (got == 0) return false; + rstream_.next_in = crbuf_.ptr; + rstream_.avail_in = to!uint(got); + } + + // We have some compressed data now, uncompress it. + auto zlib_result = inflate(rstream_, Z_SYNC_FLUSH); + if (zlib_result == Z_STREAM_END) { + inputEnded_ = true; + } else { + zlibEnforce(zlib_result, rstream_); + } + + return true; + } + + void flushToTransport(int type) { + // Compress remaining data in uwbuf_ to cwbuf_. + flushToZlib(uwbuf_[0 .. uwpos_], type); + uwpos_ = 0; + + // Write all compressed data to the transport. + transport_.write(cwbuf_[0 .. $ - wstream_.avail_out]); + wstream_.next_out = cwbuf_.ptr; + wstream_.avail_out = to!uint(cwbuf_.length); + + // Flush the transport. + transport_.flush(); + } + + void flushToZlib(in ubyte[] buf, int type) { + wstream_.next_in = cast(ubyte*)buf.ptr; // zlib only reads, cast is safe. + wstream_.avail_in = to!uint(buf.length); + + while (true) { + if (type == Z_NO_FLUSH && wstream_.avail_in == 0) { + break; + } + + if (wstream_.avail_out == 0) { + // cwbuf has been exhausted by zlib, flush to the underlying transport. + transport_.write(cwbuf_); + wstream_.next_out = cwbuf_.ptr; + wstream_.avail_out = to!uint(cwbuf_.length); + } + + auto zlib_result = deflate(wstream_, type); + + if (type == Z_FINISH && zlib_result == Z_STREAM_END) { + assert(wstream_.avail_in == 0); + outputFinished_ = true; + break; + } + + zlibEnforce(zlib_result, wstream_); + + if ((type == Z_SYNC_FLUSH || type == Z_FULL_FLUSH) && + wstream_.avail_in == 0 && wstream_.avail_out != 0) { + break; + } + } + } + + static void zlibEnforce(int status, z_stream* stream) { + if (status != Z_OK) { + throw new TZlibException(status, stream.msg); + } + } + + static void zlibLogError(int status, z_stream* stream) { + if (status != Z_OK) { + logError("TZlibTransport: zlib failure in destructor: %s", + TZlibException.errorMessage(status, stream.msg)); + } + } + + // Writes smaller than this are buffered up (due to zlib handling overhead). + // Larger (or equal) writes are dumped straight to zlib. + enum MIN_DIRECT_DEFLATE_SIZE = 32; + + TTransport transport_; + z_stream* rstream_; + z_stream* wstream_; + + /// Whether zlib has reached the end of the input stream. + bool inputEnded_; + + /// Whether the output stream was already finish()ed. + bool outputFinished_; + + /// Compressed input data buffer. + ubyte[] crbuf_; + + /// Uncompressed input data buffer. + ubyte[] urbuf_; + size_t urpos_; + + /// Uncompressed output data buffer (where small writes are accumulated + /// before handing over to zlib). + ubyte[] uwbuf_; + size_t uwpos_; + + /// Compressed output data buffer (filled by zlib, we flush it to the + /// underlying transport). + ubyte[] cwbuf_; +} + +/** + * Wraps given transports into TZlibTransports. + */ +alias TWrapperTransportFactory!TZlibTransport TZlibTransportFactory; + +/** + * An INTERNAL_ERROR-type TTransportException originating from an error + * signaled by zlib. + */ +class TZlibException : TTransportException { + this(int statusCode, const(char)* msg) { + super(errorMessage(statusCode, msg), TTransportException.Type.INTERNAL_ERROR); + zlibStatusCode = statusCode; + zlibMsg = msg ? to!string(msg) : "(null)"; + } + + int zlibStatusCode; + string zlibMsg; + + static string errorMessage(int statusCode, const(char)* msg) { + string result = "zlib error: "; + + if (msg) { + result ~= to!string(msg); + } else { + result ~= "(no message)"; + } + + result ~= " (status code = " ~ to!string(statusCode) ~ ")"; + return result; + } +} + +version (unittest) { + import std.exception : collectException; + import thrift.transport.memory; +} + +// Make sure basic reading/writing works. +unittest { + auto buf = new TMemoryBuffer; + auto zlib = new TZlibTransport(buf); + + immutable ubyte[] data = [1, 2, 3, 4, 5]; + zlib.write(data); + zlib.finish(); + + auto result = new ubyte[data.length]; + zlib.readAll(result); + enforce(data == result); + zlib.verifyChecksum(); +} + +// Make sure there is no data is written if write() is never called. +unittest { + auto buf = new TMemoryBuffer; + { + scope zlib = new TZlibTransport(buf); + } + enforce(buf.getContents().length == 0); +} + +// Make sure calling write()/flush()/finish() again after finish() throws. +unittest { + auto buf = new TMemoryBuffer; + auto zlib = new TZlibTransport(buf); + + zlib.write([1, 2, 3, 4, 5]); + zlib.finish(); + + auto ex = collectException!TTransportException(zlib.write([6])); + enforce(ex && ex.type == TTransportException.Type.BAD_ARGS); + + ex = collectException!TTransportException(zlib.flush()); + enforce(ex && ex.type == TTransportException.Type.BAD_ARGS); + + ex = collectException!TTransportException(zlib.finish()); + enforce(ex && ex.type == TTransportException.Type.BAD_ARGS); +} + +// Make sure verifying the checksum works even if it requires starting a new +// reading buffer after reading the payload has already been completed. +unittest { + auto buf = new TMemoryBuffer; + auto zlib = new TZlibTransport(buf); + + immutable ubyte[] data = [1, 2, 3, 4, 5]; + zlib.write(data); + zlib.finish(); + + zlib = new TZlibTransport(buf, TZlibTransport.DEFAULT_URBUF_SIZE, + buf.getContents().length - 1); // The last byte belongs to the checksum. + + auto result = new ubyte[data.length]; + zlib.readAll(result); + enforce(data == result); + + zlib.verifyChecksum(); +} + +// Make sure verifyChecksum() throws if we messed with the checksum. +unittest { + import std.stdio; + import thrift.transport.range; + + auto buf = new TMemoryBuffer; + auto zlib = new TZlibTransport(buf); + + immutable ubyte[] data = [1, 2, 3, 4, 5]; + zlib.write(data); + zlib.finish(); + + void testCorrupted(const(ubyte)[] corruptedData) { + auto reader = new TZlibTransport(tInputRangeTransport(corruptedData)); + auto result = new ubyte[data.length]; + try { + reader.readAll(result); + + // If it does read without complaining, the result should be correct. + enforce(result == data); + } catch (TZlibException e) {} + + auto ex = collectException!TTransportException(reader.verifyChecksum()); + enforce(ex && ex.type == TTransportException.Type.CORRUPTED_DATA); + } + + testCorrupted(buf.getContents()[0 .. $ - 1]); + + auto modified = buf.getContents().dup; + ++modified[$ - 1]; + testCorrupted(modified); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/util/awaitable.d b/src/jaegertracing/thrift/lib/d/src/thrift/util/awaitable.d new file mode 100644 index 000000000..38436ee38 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/util/awaitable.d @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.util.awaitable; + +import core.sync.condition; +import core.sync.mutex; +import core.time : Duration; +import std.exception : enforce; +import std.socket/+ : Socket, socketPair+/; // DMD @@BUG314@@ +import thrift.base; + +// To avoid DMD @@BUG6395@@. +import thrift.internal.algorithm; + +/** + * An event that can occur at some point in the future and which can be + * awaited, either by blocking until it occurs, or by registering a callback + * delegate. + */ +interface TAwaitable { + /** + * Waits until the event occurs. + * + * Calling wait() for an event that has already occurred is a no-op. + */ + void wait(); + + /** + * Waits until the event occurs or the specified timeout expires. + * + * Calling wait() for an event that has already occurred is a no-op. + * + * Returns: Whether the event was triggered before the timeout expired. + */ + bool wait(Duration timeout); + + /** + * Registers a callback that is called if the event occurs. + * + * The delegate will likely be invoked from a different thread, and is + * expected not to perform expensive work as it will usually be invoked + * synchronously by the notifying thread. The order in which registered + * callbacks are invoked is not specified. + * + * The callback must never throw, but nothrow semantics are difficult to + * enforce, so currently exceptions are just swallowed by + * TAwaitable implementations. + * + * If the event has already occurred, the delegate is immediately executed + * in the current thread. + */ + void addCallback(void delegate() dg); + + /** + * Removes a previously added callback. + * + * Returns: Whether the callback could be found in the list, i.e. whether it + * was previously added. + */ + bool removeCallback(void delegate() dg); +} + +/** + * A simple TAwaitable event triggered by just calling a trigger() method. + */ +class TOneshotEvent : TAwaitable { + this() { + mutex_ = new Mutex; + condition_ = new Condition(mutex_); + } + + override void wait() { + synchronized (mutex_) { + while (!triggered_) condition_.wait(); + } + } + + override bool wait(Duration timeout) { + synchronized (mutex_) { + if (triggered_) return true; + condition_.wait(timeout); + return triggered_; + } + } + + override void addCallback(void delegate() dg) { + mutex_.lock(); + scope (failure) mutex_.unlock(); + + callbacks_ ~= dg; + + if (triggered_) { + mutex_.unlock(); + dg(); + return; + } + + mutex_.unlock(); + } + + override bool removeCallback(void delegate() dg) { + synchronized (mutex_) { + auto oldLength = callbacks_.length; + callbacks_ = removeEqual(callbacks_, dg); + return callbacks_.length < oldLength; + } + } + + /** + * Triggers the event. + * + * Any registered event callbacks are executed synchronously before the + * function returns. + */ + void trigger() { + synchronized (mutex_) { + if (!triggered_) { + triggered_ = true; + condition_.notifyAll(); + foreach (c; callbacks_) c(); + } + } + } + +private: + bool triggered_; + Mutex mutex_; + Condition condition_; + void delegate()[] callbacks_; +} + +/** + * Translates TAwaitable events into dummy messages on a socket that can be + * used e.g. to wake up from a select() call. + */ +final class TSocketNotifier { + this() { + auto socks = socketPair(); + foreach (s; socks) s.blocking = false; + sendSocket_ = socks[0]; + recvSocket_ = socks[1]; + } + + /** + * The socket the messages will be sent to. + */ + Socket socket() @property { + return recvSocket_; + } + + /** + * Atatches the socket notifier to the specified awaitable, causing it to + * write a byte to the notification socket when the awaitable callbacks are + * invoked. + * + * If the event has already been triggered, the dummy byte is written + * immediately to the socket. + * + * A socket notifier can only be attached to a single awaitable at a time. + * + * Throws: TException if the socket notifier is already attached. + */ + void attach(TAwaitable awaitable) { + enforce(!awaitable_, new TException("Already attached.")); + awaitable.addCallback(¬ify); + awaitable_ = awaitable; + } + + /** + * Detaches the socket notifier from the awaitable it is currently attached + * to. + * + * Throws: TException if the socket notifier is not currently attached. + */ + void detach() { + enforce(awaitable_, new TException("Not attached.")); + + // Soak up any not currently read notification bytes. + ubyte[1] dummy = void; + while (recvSocket_.receive(dummy) != Socket.ERROR) {} + + auto couldRemove = awaitable_.removeCallback(¬ify); + assert(couldRemove); + awaitable_ = null; + } + +private: + void notify() { + ubyte[1] zero; + sendSocket_.send(zero); + } + + TAwaitable awaitable_; + Socket sendSocket_; + Socket recvSocket_; +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/util/cancellation.d b/src/jaegertracing/thrift/lib/d/src/thrift/util/cancellation.d new file mode 100644 index 000000000..62552364d --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/util/cancellation.d @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.util.cancellation; + +import core.atomic; +import thrift.base; +import thrift.util.awaitable; + +/** + * A cancellation request for asynchronous or blocking synchronous operations. + * + * It is passed to the entity creating an operation, which will usually monitor + * it either by polling or by adding event handlers, and cancel the operation + * if it is triggered. + * + * For synchronous operations, this usually means either throwing a + * TCancelledException or immediately returning, depending on whether + * cancellation is an expected part of the task outcome or not. For + * asynchronous operations, cancellation typically entails stopping background + * work and cancelling a result future, if not already completed. + * + * An operation accepting a TCancellation does not need to guarantee that it + * will actually be able to react to the cancellation request. + */ +interface TCancellation { + /** + * Whether the cancellation request has been triggered. + */ + bool triggered() const @property; + + /** + * Throws a TCancelledException if the cancellation request has already been + * triggered. + */ + void throwIfTriggered() const; + + /** + * A TAwaitable that can be used to wait for cancellation triggering. + */ + TAwaitable triggering() @property; +} + +/** + * The origin of a cancellation request, which provides a way to actually + * trigger it. + * + * This design allows operations to pass the TCancellation on to sub-tasks, + * while making sure that the cancellation can only be triggered by the + * »outermost« instance waiting for the result. + */ +final class TCancellationOrigin : TCancellation { + this() { + event_ = new TOneshotEvent; + } + + /** + * Triggers the cancellation request. + */ + void trigger() { + atomicStore(triggered_, true); + event_.trigger(); + } + + /+override+/ bool triggered() const @property { + return atomicLoad(triggered_); + } + + /+override+/ void throwIfTriggered() const { + if (triggered) throw new TCancelledException; + } + + /+override+/ TAwaitable triggering() @property { + return event_; + } + +private: + shared bool triggered_; + TOneshotEvent event_; +} + +/// +class TCancelledException : TException { + /// + this(string msg = null, string file = __FILE__, size_t line = __LINE__, + Throwable next = null + ) { + super(msg ? msg : "The operation has been cancelled.", file, line, next); + } +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/util/future.d b/src/jaegertracing/thrift/lib/d/src/thrift/util/future.d new file mode 100644 index 000000000..2b32a01f3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/util/future.d @@ -0,0 +1,549 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.util.future; + +import core.atomic; +import core.sync.condition; +import core.sync.mutex; +import core.time : Duration; +import std.array : empty, front, popFront; +import std.conv : to; +import std.exception : enforce; +import std.traits : BaseTypeTuple, isSomeFunction, ParameterTypeTuple, ReturnType; +import thrift.base; +import thrift.util.awaitable; +import thrift.util.cancellation; + +/** + * Represents an operation which is executed asynchronously and the result of + * which will become available at some point in the future. + * + * Once a operation is completed, the result of the operation can be fetched + * via the get() family of methods. There are three possible cases: Either the + * operation succeeded, then its return value is returned, or it failed by + * throwing, in which case the exception is rethrown, or it was cancelled + * before, then a TCancelledException is thrown. There might be TFuture + * implementations which never possibly enter the cancelled state. + * + * All methods are thread-safe, but keep in mind that any exception object or + * result (if it is a reference type, of course) is shared between all + * get()-family invocations. + */ +interface TFuture(ResultType) { + /** + * The status the operation is currently in. + * + * An operation starts out in RUNNING status, and changes state to one of the + * others at most once afterwards. + */ + TFutureStatus status() @property; + + /** + * A TAwaitable triggered when the operation leaves the RUNNING status. + */ + TAwaitable completion() @property; + + /** + * Convenience shorthand for waiting until the result is available and then + * get()ing it. + * + * If the operation has already completed, the result is immediately + * returned. + * + * The result of this method is »alias this«'d to the interface, so that + * TFuture can be used as a drop-in replacement for a simple value in + * synchronous code. + */ + final ResultType waitGet() { + completion.wait(); + return get(); + } + final @property auto waitGetProperty() { return waitGet(); } + alias waitGetProperty this; + + /** + * Convenience shorthand for waiting until the result is available and then + * get()ing it. + * + * If the operation completes in time, returns its result (resp. throws an + * exception for the failed/cancelled cases). If not, throws a + * TFutureException. + */ + final ResultType waitGet(Duration timeout) { + enforce(completion.wait(timeout), new TFutureException( + "Operation did not complete in time.")); + return get(); + } + + /** + * Returns the result of the operation. + * + * Throws: TFutureException if the operation has been cancelled, + * TCancelledException if it is not yet done; the set exception if it + * failed. + */ + ResultType get(); + + /** + * Returns the captured exception if the operation failed, or null otherwise. + * + * Throws: TFutureException if not yet done, TCancelledException if the + * operation has been cancelled. + */ + Exception getException(); +} + +/** + * The states the operation offering a future interface can be in. + */ +enum TFutureStatus : byte { + RUNNING, /// The operation is still running. + SUCCEEDED, /// The operation completed without throwing an exception. + FAILED, /// The operation completed by throwing an exception. + CANCELLED /// The operation was cancelled. +} + +/** + * A TFuture covering the simple but common case where the result is simply + * set by a call to succeed()/fail(). + * + * All methods are thread-safe, but usually, succeed()/fail() are only called + * from a single thread (different from the thread(s) waiting for the result + * using the TFuture interface, though). + */ +class TPromise(ResultType) : TFuture!ResultType { + this() { + statusMutex_ = new Mutex; + completionEvent_ = new TOneshotEvent; + } + + override S status() const @property { + return atomicLoad(status_); + } + + override TAwaitable completion() @property { + return completionEvent_; + } + + override ResultType get() { + auto s = atomicLoad(status_); + enforce(s != S.RUNNING, + new TFutureException("Operation not yet completed.")); + + if (s == S.CANCELLED) throw new TCancelledException; + if (s == S.FAILED) throw exception_; + + static if (!is(ResultType == void)) { + return result_; + } + } + + override Exception getException() { + auto s = atomicLoad(status_); + enforce(s != S.RUNNING, + new TFutureException("Operation not yet completed.")); + + if (s == S.CANCELLED) throw new TCancelledException; + if (s == S.SUCCEEDED) return null; + + return exception_; + } + + static if (!is(ResultType == void)) { + /** + * Sets the result of the operation, marks it as done, and notifies any + * waiters. + * + * If the operation has been cancelled before, nothing happens. + * + * Throws: TFutureException if the operation is already completed. + */ + void succeed(ResultType result) { + synchronized (statusMutex_) { + auto s = atomicLoad(status_); + if (s == S.CANCELLED) return; + + enforce(s == S.RUNNING, + new TFutureException("Operation already completed.")); + result_ = result; + + atomicStore(status_, S.SUCCEEDED); + } + + completionEvent_.trigger(); + } + } else { + void succeed() { + synchronized (statusMutex_) { + auto s = atomicLoad(status_); + if (s == S.CANCELLED) return; + + enforce(s == S.RUNNING, + new TFutureException("Operation already completed.")); + + atomicStore(status_, S.SUCCEEDED); + } + + completionEvent_.trigger(); + } + } + + /** + * Marks the operation as failed with the specified exception and notifies + * any waiters. + * + * If the operation was already cancelled, nothing happens. + * + * Throws: TFutureException if the operation is already completed. + */ + void fail(Exception exception) { + synchronized (statusMutex_) { + auto status = atomicLoad(status_); + if (status == S.CANCELLED) return; + + enforce(status == S.RUNNING, + new TFutureException("Operation already completed.")); + exception_ = exception; + + atomicStore(status_, S.FAILED); + } + + completionEvent_.trigger(); + } + + + /** + * Marks this operation as completed and takes over the outcome of another + * TFuture of the same type. + * + * If this operation was already cancelled, nothing happens. If the other + * operation was cancelled, this operation is marked as failed with a + * TCancelledException. + * + * Throws: TFutureException if the passed in future was not completed or + * this operation is already completed. + */ + void complete(TFuture!ResultType future) { + synchronized (statusMutex_) { + auto status = atomicLoad(status_); + if (status == S.CANCELLED) return; + enforce(status == S.RUNNING, + new TFutureException("Operation already completed.")); + + enforce(future.status != S.RUNNING, new TFutureException( + "The passed TFuture is not yet completed.")); + + status = future.status; + if (status == S.CANCELLED) { + status = S.FAILED; + exception_ = new TCancelledException; + } else if (status == S.FAILED) { + exception_ = future.getException(); + } else static if (!is(ResultType == void)) { + result_ = future.get(); + } + + atomicStore(status_, status); + } + + completionEvent_.trigger(); + } + + /** + * Marks this operation as cancelled and notifies any waiters. + * + * If the operation is already completed, nothing happens. + */ + void cancel() { + synchronized (statusMutex_) { + auto status = atomicLoad(status_); + if (status == S.RUNNING) atomicStore(status_, S.CANCELLED); + } + + completionEvent_.trigger(); + } + +private: + // Convenience alias because TFutureStatus is ubiquitous in this class. + alias TFutureStatus S; + + // The status the promise is currently in. + shared S status_; + + union { + static if (!is(ResultType == void)) { + // Set if status_ is SUCCEEDED. + ResultType result_; + } + // Set if status_ is FAILED. + Exception exception_; + } + + // Protects status_. + // As for result_ and exception_: They are only set once, while status_ is + // still RUNNING, so given that the operation has already completed, reading + // them is safe without holding some kind of lock. + Mutex statusMutex_; + + // Triggered when the event completes. + TOneshotEvent completionEvent_; +} + +/// +class TFutureException : TException { + /// + this(string msg = "", string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + super(msg, file, line, next); + } +} + +/** + * Creates an interface that is similar to a given one, but accepts an + * additional, optional TCancellation parameter each method, and returns + * TFutures instead of plain return values. + * + * For example, given the following declarations: + * --- + * interface Foo { + * void bar(); + * string baz(int a); + * } + * alias TFutureInterface!Foo FutureFoo; + * --- + * + * FutureFoo would be equivalent to: + * --- + * interface FutureFoo { + * TFuture!void bar(TCancellation cancellation = null); + * TFuture!string baz(int a, TCancellation cancellation = null); + * } + * --- + */ +template TFutureInterface(Interface) if (is(Interface _ == interface)) { + mixin({ + string code = "interface TFutureInterface \n"; + + static if (is(Interface Bases == super) && Bases.length > 0) { + code ~= ": "; + foreach (i; 0 .. Bases.length) { + if (i > 0) code ~= ", "; + code ~= "TFutureInterface!(BaseTypeTuple!Interface[" ~ to!string(i) ~ "]) "; + } + } + + code ~= "{\n"; + + foreach (methodName; __traits(derivedMembers, Interface)) { + enum qn = "Interface." ~ methodName; + static if (isSomeFunction!(mixin(qn))) { + code ~= "TFuture!(ReturnType!(" ~ qn ~ ")) " ~ methodName ~ + "(ParameterTypeTuple!(" ~ qn ~ "), TCancellation cancellation = null);\n"; + } + } + + code ~= "}\n"; + return code; + }()); +} + +/** + * An input range that aggregates results from multiple asynchronous operations, + * returning them in the order they arrive. + * + * Additionally, a timeout can be set after which results from not yet finished + * futures will no longer be waited for, e.g. to ensure the time it takes to + * iterate over a set of results is limited. + */ +final class TFutureAggregatorRange(T) { + /** + * Constructs a new instance. + * + * Params: + * futures = The set of futures to collect results from. + * timeout = If positive, not yet finished futures will be cancelled and + * their results will not be taken into account. + */ + this(TFuture!T[] futures, TCancellationOrigin childCancellation, + Duration timeout = dur!"hnsecs"(0) + ) { + if (timeout > dur!"hnsecs"(0)) { + timeoutSysTick_ = TickDuration.currSystemTick + + TickDuration.from!"hnsecs"(timeout.total!"hnsecs"); + } else { + timeoutSysTick_ = TickDuration(0); + } + + queueMutex_ = new Mutex; + queueNonEmptyCondition_ = new Condition(queueMutex_); + futures_ = futures; + childCancellation_ = childCancellation; + + foreach (future; futures_) { + future.completion.addCallback({ + auto f = future; + return { + if (f.status == TFutureStatus.CANCELLED) return; + assert(f.status != TFutureStatus.RUNNING); + + synchronized (queueMutex_) { + completedQueue_ ~= f; + + if (completedQueue_.length == 1) { + queueNonEmptyCondition_.notifyAll(); + } + } + }; + }()); + } + } + + /** + * Whether the range is empty. + * + * This is the case if the results from the completed futures not having + * failed have already been popped and either all future have been finished + * or the timeout has expired. + * + * Potentially blocks until a new result is available or the timeout has + * expired. + */ + bool empty() @property { + if (finished_) return true; + if (bufferFilled_) return false; + + while (true) { + TFuture!T future; + synchronized (queueMutex_) { + // The while loop is just being cautious about spurious wakeups, in + // case they should be possible. + while (completedQueue_.empty) { + auto remaining = to!Duration(timeoutSysTick_ - + TickDuration.currSystemTick); + + if (remaining <= dur!"hnsecs"(0)) { + // No time left, but still no element received – we are empty now. + finished_ = true; + childCancellation_.trigger(); + return true; + } + + queueNonEmptyCondition_.wait(remaining); + } + + future = completedQueue_.front; + completedQueue_.popFront(); + } + + ++completedCount_; + if (completedCount_ == futures_.length) { + // This was the last future in the list, there is no possibility + // another result could ever become available. + finished_ = true; + } + + if (future.status == TFutureStatus.FAILED) { + // This one failed, loop again and try getting another item from + // the queue. + exceptions_ ~= future.getException(); + } else { + resultBuffer_ = future.get(); + bufferFilled_ = true; + return false; + } + } + } + + /** + * Returns the first element from the range. + * + * Potentially blocks until a new result is available or the timeout has + * expired. + * + * Throws: TException if the range is empty. + */ + T front() { + enforce(!empty, new TException( + "Cannot get front of an empty future aggregator range.")); + return resultBuffer_; + } + + /** + * Removes the first element from the range. + * + * Potentially blocks until a new result is available or the timeout has + * expired. + * + * Throws: TException if the range is empty. + */ + void popFront() { + enforce(!empty, new TException( + "Cannot pop front of an empty future aggregator range.")); + bufferFilled_ = false; + } + + /** + * The number of futures the result of which has been returned or which have + * failed so far. + */ + size_t completedCount() @property const { + return completedCount_; + } + + /** + * The exceptions collected from failed TFutures so far. + */ + Exception[] exceptions() @property { + return exceptions_; + } + +private: + TFuture!T[] futures_; + TCancellationOrigin childCancellation_; + + // The system tick this operation will time out, or zero if no timeout has + // been set. + TickDuration timeoutSysTick_; + + bool finished_; + + bool bufferFilled_; + T resultBuffer_; + + Exception[] exceptions_; + size_t completedCount_; + + // The queue of completed futures. This (and the associated condition) are + // the only parts of this class that are accessed by multiple threads. + TFuture!T[] completedQueue_; + Mutex queueMutex_; + Condition queueNonEmptyCondition_; +} + +/** + * TFutureAggregatorRange construction helper to avoid having to explicitly + * specify the value type, i.e. to allow the constructor being called using IFTI + * (see $(DMDBUG 6082, D Bugzilla enhancement requet 6082)). + */ +TFutureAggregatorRange!T tFutureAggregatorRange(T)(TFuture!T[] futures, + TCancellationOrigin childCancellation, Duration timeout = dur!"hnsecs"(0) +) { + return new TFutureAggregatorRange!T(futures, childCancellation, timeout); +} diff --git a/src/jaegertracing/thrift/lib/d/src/thrift/util/hashset.d b/src/jaegertracing/thrift/lib/d/src/thrift/util/hashset.d new file mode 100644 index 000000000..ede122ef1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/src/thrift/util/hashset.d @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift.util.hashset; + +import std.algorithm : joiner, map; +import std.conv : to; +import std.traits : isImplicitlyConvertible, ParameterTypeTuple; +import std.range : ElementType, isInputRange; + +struct Void {} + +/** + * A quickly hacked together hash set implementation backed by built-in + * associative arrays to have something to compile Thrift's set<> to until + * std.container gains something suitable. + */ +// Note: The funky pointer casts (i.e. *(cast(immutable(E)*)&e) instead of +// just cast(immutable(E))e) are a workaround for LDC 2 compatibility. +final class HashSet(E) { + /// + this() {} + + /// + this(E[] elems...) { + insert(elems); + } + + /// + void insert(Stuff)(Stuff stuff) if (isImplicitlyConvertible!(Stuff, E)) { + aa_[*(cast(immutable(E)*)&stuff)] = Void.init; + } + + /// + void insert(Stuff)(Stuff stuff) if ( + isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, E) + ) { + foreach (e; stuff) { + aa_[*(cast(immutable(E)*)&e)] = Void.init; + } + } + + /// + void opOpAssign(string op : "~", Stuff)(Stuff stuff) { + insert(stuff); + } + + /// + void remove(E e) { + aa_.remove(*(cast(immutable(E)*)&e)); + } + alias remove removeKey; + + /// + void removeAll() { + aa_ = null; + } + + /// + size_t length() @property const { + return aa_.length; + } + + /// + size_t empty() @property const { + return !aa_.length; + } + + /// + bool opBinaryRight(string op : "in")(E e) const { + return (e in aa_) !is null; + } + + /// + auto opSlice() const { + // TODO: Implement using AA key range once available in release DMD/druntime + // to avoid allocation. + return cast(E[])(aa_.keys); + } + + /// + override string toString() const { + // Only provide toString() if to!string() is available for E (exceptions are + // e.g. delegates). + static if (is(typeof(to!string(E.init)) : string)) { + return "{" ~ to!string(joiner(map!`to!string(a)`(aa_.keys), ", ")) ~ "}"; + } else { + // Cast to work around Object not being const-correct. + return (cast()super).toString(); + } + } + + /// + override bool opEquals(Object other) const { + auto rhs = cast(const(HashSet))other; + if (rhs) { + return aa_ == rhs.aa_; + } + + // Cast to work around Object not being const-correct. + return (cast()super).opEquals(other); + } + +private: + Void[immutable(E)] aa_; +} + +/// Ditto +auto hashSet(E)(E[] elems...) { + return new HashSet!E(elems); +} + +unittest { + import std.exception; + + auto a = hashSet(1, 2, 2, 3); + enforce(a.length == 3); + enforce(2 in a); + enforce(5 !in a); + enforce(a.toString().length == 9); + a.remove(2); + enforce(a.length == 2); + enforce(2 !in a); + a.removeAll(); + enforce(a.empty); + enforce(a.toString() == "{}"); + + void delegate() dg; + auto b = hashSet(dg); + static assert(__traits(compiles, b.toString())); +} diff --git a/src/jaegertracing/thrift/lib/d/test/Makefile.am b/src/jaegertracing/thrift/lib/d/test/Makefile.am new file mode 100755 index 000000000..5ec8255bb --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/Makefile.am @@ -0,0 +1,112 @@ +# +# 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. +# + +AUTOMAKE_OPTIONS = serial-tests + +# Thrift compiler rules + +debug_proto_gen = $(addprefix gen-d/, DebugProtoTest_types.d) + +$(debug_proto_gen): $(top_srcdir)/test/DebugProtoTest.thrift + $(THRIFT) --gen d -nowarn $< + +stress_test_gen = $(addprefix gen-d/thrift/test/stress/, Service.d \ + StressTest_types.d) + +$(stress_test_gen): $(top_srcdir)/test/StressTest.thrift + $(THRIFT) --gen d $< + +thrift_test_gen = $(addprefix gen-d/thrift/test/, SecondService.d \ + ThriftTest.d ThriftTest_constants.d ThriftTest_types.d) + +$(thrift_test_gen): $(top_srcdir)/test/ThriftTest.thrift + $(THRIFT) --gen d $< + + +# The actual test targets. +# There just must be some way to reassign a variable without warnings in +# Automake... +targets__ = async_test client_pool_test serialization_benchmark \ + stress_test_server thrift_test_client thrift_test_server transport_test +ran_tests__ = client_pool_test \ + transport_test \ + async_test_runner.sh \ + thrift_test_runner.sh + +libevent_dependent_targets = async_test_client client_pool_test \ + stress_test_server thrift_test_server +libevent_dependent_ran_tests = client_pool_test async_test_runner.sh thrift_test_runner.sh + +openssl_dependent_targets = async_test thrift_test_client thrift_test_server +openssl_dependent_ran_tests = async_test_runner.sh thrift_test_runner.sh + +d_test_flags = + +if WITH_D_EVENT_TESTS +d_test_flags += $(DMD_LIBEVENT_FLAGS) ../$(D_EVENT_LIB_NAME) +targets_ = $(targets__) +ran_tests_ = $(ran_tests__) +else +targets_ = $(filter-out $(libevent_dependent_targets), $(targets__)) +ran_tests_ = $(filter-out $(libevent_dependent_ran_tests), $(ran_tests__)) +endif + +if WITH_D_SSL_TESTS +d_test_flags += $(DMD_OPENSSL_FLAGS) ../$(D_SSL_LIB_NAME) +targets = $(targets_) +ran_tests = $(ran_tests_) +else +targets = $(filter-out $(openssl_dependent_targets), $(targets_)) +ran_tests = $(filter-out $(openssl_dependent_ran_tests), $(ran_tests_)) +endif + +d_test_flags += -w -wi -O -release -inline -I$(top_srcdir)/lib/d/src -Igen-d \ + $(top_builddir)/lib/d/$(D_LIB_NAME) + + +async_test client_pool_test transport_test: %: %.d + $(DMD) $(d_test_flags) -of$@ $^ + +serialization_benchmark: %: %.d $(debug_proto_gen) + $(DMD) $(d_test_flags) -of$@ $^ + +stress_test_server: %: %.d test_utils.d $(stress_test_gen) + $(DMD) $(d_test_flags) -of$@ $^ + +thrift_test_client: %: %.d thrift_test_common.d $(thrift_test_gen) + $(DMD) $(d_test_flags) -of$@ $^ + +thrift_test_server: %: %.d thrift_test_common.d test_utils.d $(thrift_test_gen) + $(DMD) $(d_test_flags) -of$@ $^ + + +check-local: $(targets) + +clean-local: + $(RM) -rf gen-d $(targets) $(addsuffix .o, $(targets)) + + +# Tests ran as part of make check. + +async_test_runner.sh: async_test +thrift_test_runner.sh: thrift_test_client thrift_test_server + +TESTS = $(ran_tests) + +precross: $(targets) diff --git a/src/jaegertracing/thrift/lib/d/test/async_test.d b/src/jaegertracing/thrift/lib/d/test/async_test.d new file mode 100644 index 000000000..51529ba86 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/async_test.d @@ -0,0 +1,396 @@ +/* + * 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 enforced by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module async_test; + +import core.atomic; +import core.sync.condition : Condition; +import core.sync.mutex : Mutex; +import core.thread : dur, Thread, ThreadGroup; +import std.conv : text; +import std.datetime; +import std.getopt; +import std.exception : collectException, enforce; +import std.parallelism : TaskPool; +import std.stdio; +import std.string; +import std.variant : Variant; +import thrift.base; +import thrift.async.base; +import thrift.async.libevent; +import thrift.async.socket; +import thrift.async.ssl; +import thrift.codegen.async_client; +import thrift.codegen.async_client_pool; +import thrift.codegen.base; +import thrift.codegen.processor; +import thrift.protocol.base; +import thrift.protocol.binary; +import thrift.server.base; +import thrift.server.simple; +import thrift.server.transport.socket; +import thrift.server.transport.ssl; +import thrift.transport.base; +import thrift.transport.buffered; +import thrift.transport.ssl; +import thrift.util.cancellation; + +version (Posix) { + import core.stdc.signal; + import core.sys.posix.signal; + + // Disable SIGPIPE because SSL server will write to broken socket after + // client disconnected (see TSSLSocket docs). + shared static this() { + signal(SIGPIPE, SIG_IGN); + } +} + +interface AsyncTest { + string echo(string value); + string delayedEcho(string value, long milliseconds); + + void fail(string reason); + void delayedFail(string reason, long milliseconds); + + enum methodMeta = [ + TMethodMeta("fail", [], [TExceptionMeta("ate", 1, "AsyncTestException")]), + TMethodMeta("delayedFail", [], [TExceptionMeta("ate", 1, "AsyncTestException")]) + ]; + alias .AsyncTestException AsyncTestException; +} + +class AsyncTestException : TException { + string reason; + mixin TStructHelpers!(); +} + +void main(string[] args) { + ushort port = 9090; + ushort managerCount = 2; + ushort serversPerManager = 5; + ushort threadsPerServer = 10; + uint iterations = 10; + bool ssl; + bool trace; + + getopt(args, + "iterations", &iterations, + "managers", &managerCount, + "port", &port, + "servers-per-manager", &serversPerManager, + "ssl", &ssl, + "threads-per-server", &threadsPerServer, + "trace", &trace, + ); + + TTransportFactory clientTransportFactory; + TSSLContext serverSSLContext; + if (ssl) { + auto clientSSLContext = new TSSLContext(); + with (clientSSLContext) { + authenticate = true; + ciphers = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; + loadTrustedCertificates("../../../test/keys/CA.pem"); + } + clientTransportFactory = new TAsyncSSLSocketFactory(clientSSLContext); + + serverSSLContext = new TSSLContext(); + with (serverSSLContext) { + serverSide = true; + ciphers = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; + loadCertificate("../../../test/keys/server.crt"); + loadPrivateKey("../../../test/keys/server.key"); + } + } else { + clientTransportFactory = new TBufferedTransportFactory; + } + + + auto serverCancel = new TCancellationOrigin; + scope(exit) { + writeln("Triggering server shutdown..."); + serverCancel.trigger(); + writeln("done."); + } + + auto managers = new TLibeventAsyncManager[managerCount]; + scope (exit) foreach (ref m; managers) destroy(m); + + auto clientsThreads = new ThreadGroup; + foreach (managerIndex, ref manager; managers) { + manager = new TLibeventAsyncManager; + foreach (serverIndex; 0 .. serversPerManager) { + auto currentPort = cast(ushort) + (port + managerIndex * serversPerManager + serverIndex); + + // Start the server and wait until it is up and running. + auto servingMutex = new Mutex; + auto servingCondition = new Condition(servingMutex); + auto handler = new PreServeNotifyHandler(servingMutex, servingCondition); + synchronized (servingMutex) { + (new ServerThread!TSimpleServer(currentPort, serverSSLContext, trace, + serverCancel, handler)).start(); + servingCondition.wait(); + } + + // We only run the timing tests for the first server on each async + // manager, so that we don't get spurious timing errors becaue of + // ordering issues. + auto runTimingTests = (serverIndex == 0); + + auto c = new ClientsThread(manager, currentPort, clientTransportFactory, + threadsPerServer, iterations, runTimingTests, trace); + clientsThreads.add(c); + c.start(); + } + } + clientsThreads.joinAll(); +} + +class AsyncTestHandler : AsyncTest { + this(bool trace) { + trace_ = trace; + } + + override string echo(string value) { + if (trace_) writefln(`echo("%s")`, value); + return value; + } + + override string delayedEcho(string value, long milliseconds) { + if (trace_) writef(`delayedEcho("%s", %s ms)... `, value, milliseconds); + Thread.sleep(dur!"msecs"(milliseconds)); + if (trace_) writeln("returning."); + + return value; + } + + override void fail(string reason) { + if (trace_) writefln(`fail("%s")`, reason); + auto ate = new AsyncTestException; + ate.reason = reason; + throw ate; + } + + override void delayedFail(string reason, long milliseconds) { + if (trace_) writef(`delayedFail("%s", %s ms)... `, reason, milliseconds); + Thread.sleep(dur!"msecs"(milliseconds)); + if (trace_) writeln("returning."); + + auto ate = new AsyncTestException; + ate.reason = reason; + throw ate; + } + +private: + bool trace_; + AsyncTestException ate_; +} + +class PreServeNotifyHandler : TServerEventHandler { + this(Mutex servingMutex, Condition servingCondition) { + servingMutex_ = servingMutex; + servingCondition_ = servingCondition; + } + + void preServe() { + synchronized (servingMutex_) { + servingCondition_.notifyAll(); + } + } + Variant createContext(TProtocol input, TProtocol output) { return Variant.init; } + void deleteContext(Variant serverContext, TProtocol input, TProtocol output) {} + void preProcess(Variant serverContext, TTransport transport) {} + +private: + Mutex servingMutex_; + Condition servingCondition_; +} + +class ServerThread(ServerType) : Thread { + this(ushort port, TSSLContext sslContext, bool trace, + TCancellation cancellation, TServerEventHandler eventHandler + ) { + port_ = port; + sslContext_ = sslContext; + trace_ = trace; + cancellation_ = cancellation; + eventHandler_ = eventHandler; + + super(&run); + } + + void run() { + TServerSocket serverSocket; + if (sslContext_) { + serverSocket = new TSSLServerSocket(port_, sslContext_); + } else { + serverSocket = new TServerSocket(port_); + } + auto transportFactory = new TBufferedTransportFactory; + auto protocolFactory = new TBinaryProtocolFactory!(); + auto processor = new TServiceProcessor!AsyncTest(new AsyncTestHandler(trace_)); + + auto server = new ServerType(processor, serverSocket, transportFactory, + protocolFactory); + server.eventHandler = eventHandler_; + + writefln("Starting server on port %s...", port_); + server.serve(cancellation_); + writefln("Server thread on port %s done.", port_); + } + +private: + ushort port_; + bool trace_; + TCancellation cancellation_; + TSSLContext sslContext_; + TServerEventHandler eventHandler_; +} + +class ClientsThread : Thread { + this(TAsyncSocketManager manager, ushort port, TTransportFactory tf, + ushort threads, uint iterations, bool runTimingTests, bool trace + ) { + manager_ = manager; + port_ = port; + transportFactory_ = tf; + threads_ = threads; + iterations_ = iterations; + runTimingTests_ = runTimingTests; + trace_ = trace; + super(&run); + } + + void run() { + auto transport = new TAsyncSocket(manager_, "localhost", port_); + + { + auto client = new TAsyncClient!AsyncTest( + transport, + transportFactory_, + new TBinaryProtocolFactory!() + ); + transport.open(); + auto clientThreads = new ThreadGroup; + foreach (clientId; 0 .. threads_) { + clientThreads.create({ + auto c = clientId; + return { + foreach (i; 0 .. iterations_) { + immutable id = text(port_, ":", c, ":", i); + + { + if (trace_) writefln(`Calling echo("%s")... `, id); + auto a = client.echo(id); + enforce(a == id); + if (trace_) writefln(`echo("%s") done.`, id); + } + + { + if (trace_) writefln(`Calling fail("%s")... `, id); + auto a = cast(AsyncTestException)collectException(client.fail(id).waitGet()); + enforce(a && a.reason == id); + if (trace_) writefln(`fail("%s") done.`, id); + } + } + }; + }()); + } + clientThreads.joinAll(); + transport.close(); + } + + if (runTimingTests_) { + auto client = new TAsyncClient!AsyncTest( + transport, + transportFactory_, + new TBinaryProtocolFactory!TBufferedTransport + ); + + // Temporarily redirect error logs to stdout, as SSL errors on the server + // side are expected when the client terminates aburptly (as is the case + // in the timeout test). + auto oldErrorLogSink = g_errorLogSink; + g_errorLogSink = g_infoLogSink; + scope (exit) g_errorLogSink = oldErrorLogSink; + + foreach (i; 0 .. iterations_) { + transport.open(); + + immutable id = text(port_, ":", i); + + { + if (trace_) writefln(`Calling delayedEcho("%s", 100 ms)...`, id); + auto a = client.delayedEcho(id, 100); + enforce(!a.completion.wait(dur!"usecs"(1)), + text("wait() succeeded early (", a.get(), ", ", id, ").")); + enforce(!a.completion.wait(dur!"usecs"(1)), + text("wait() succeeded early (", a.get(), ", ", id, ").")); + enforce(a.completion.wait(dur!"msecs"(200)), + text("wait() didn't succeed as expected (", id, ").")); + enforce(a.get() == id); + if (trace_) writefln(`... delayedEcho("%s") done.`, id); + } + + { + if (trace_) writefln(`Calling delayedFail("%s", 100 ms)... `, id); + auto a = client.delayedFail(id, 100); + enforce(!a.completion.wait(dur!"usecs"(1)), + text("wait() succeeded early (", id, ", ", collectException(a.get()), ").")); + enforce(!a.completion.wait(dur!"usecs"(1)), + text("wait() succeeded early (", id, ", ", collectException(a.get()), ").")); + enforce(a.completion.wait(dur!"msecs"(200)), + text("wait() didn't succeed as expected (", id, ").")); + auto e = cast(AsyncTestException)collectException(a.get()); + enforce(e && e.reason == id); + if (trace_) writefln(`... delayedFail("%s") done.`, id); + } + + { + transport.recvTimeout = dur!"msecs"(50); + + if (trace_) write(`Calling delayedEcho("socketTimeout", 100 ms)... `); + auto a = client.delayedEcho("socketTimeout", 100); + auto e = cast(TTransportException)collectException(a.waitGet()); + enforce(e, text("Operation didn't fail as expected (", id, ").")); + enforce(e.type == TTransportException.Type.TIMED_OUT, + text("Wrong timeout exception type (", id, "): ", e)); + if (trace_) writeln(`timed out as expected.`); + + // Wait until the server thread reset before the next iteration. + Thread.sleep(dur!"msecs"(50)); + transport.recvTimeout = dur!"hnsecs"(0); + } + + transport.close(); + } + } + + writefln("Clients thread for port %s done.", port_); + } + + TAsyncSocketManager manager_; + ushort port_; + TTransportFactory transportFactory_; + ushort threads_; + uint iterations_; + bool runTimingTests_; + bool trace_; +} diff --git a/src/jaegertracing/thrift/lib/d/test/async_test_runner.sh b/src/jaegertracing/thrift/lib/d/test/async_test_runner.sh new file mode 100755 index 000000000..d56654f50 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/async_test_runner.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# +# 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. +# + +CUR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Runs the async test in both SSL and non-SSL mode. +${CUR}/async_test > /dev/null || exit 1 +echo "Non-SSL tests done." + +# THRIFT-4905: disabled the following test as it deadlocks / hangs +# ${CUR}/async_test --ssl > /dev/null || exit 1 +# echo "SSL tests done." +echo "THRIFT-4905: SSL tests are disabled. Fix them." diff --git a/src/jaegertracing/thrift/lib/d/test/client_pool_test.d b/src/jaegertracing/thrift/lib/d/test/client_pool_test.d new file mode 100644 index 000000000..b24c97afd --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/client_pool_test.d @@ -0,0 +1,442 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module client_pool_test; + +import core.sync.semaphore : Semaphore; +import core.time : Duration, dur; +import core.thread : Thread; +import std.algorithm; +import std.array; +import std.conv; +import std.exception; +import std.getopt; +import std.range; +import std.stdio; +import std.typecons; +import std.variant : Variant; +import thrift.base; +import thrift.async.libevent; +import thrift.async.socket; +import thrift.codegen.base; +import thrift.codegen.async_client; +import thrift.codegen.async_client_pool; +import thrift.codegen.client; +import thrift.codegen.client_pool; +import thrift.codegen.processor; +import thrift.protocol.base; +import thrift.protocol.binary; +import thrift.server.base; +import thrift.server.simple; +import thrift.server.transport.socket; +import thrift.transport.base; +import thrift.transport.buffered; +import thrift.transport.socket; +import thrift.util.cancellation; +import thrift.util.future; + +// We use this as our RPC-layer exception here to make sure socket/… problems +// (that would usually considered to be RPC layer faults) cause the tests to +// fail, even though we are testing the RPC exception handling. +class TestServiceException : TException { + int port; +} + +interface TestService { + int getPort(); + alias .TestServiceException TestServiceException; + enum methodMeta = [TMethodMeta("getPort", [], + [TExceptionMeta("a", 1, "TestServiceException")])]; +} + +// Use some derived service, just to check that the pools handle inheritance +// correctly. +interface ExTestService : TestService { + int[] getPortInArray(); + enum methodMeta = [TMethodMeta("getPortInArray", [], + [TExceptionMeta("a", 1, "TestServiceException")])]; +} + +class ExTestHandler : ExTestService { + this(ushort port, Duration delay, bool failing, bool trace) { + this.port = port; + this.delay = delay; + this.failing = failing; + this.trace = trace; + } + + override int getPort() { + if (trace) { + stderr.writefln("getPort() called on %s (delay: %s, failing: %s)", port, + delay, failing); + } + sleep(); + failIfEnabled(); + return port; + } + + override int[] getPortInArray() { + return [getPort()]; + } + + ushort port; + Duration delay; + bool failing; + bool trace; + +private: + void sleep() { + if (delay > dur!"hnsecs"(0)) Thread.sleep(delay); + } + + void failIfEnabled() { + if (!failing) return; + + auto e = new TestServiceException; + e.port = port; + throw e; + } +} + +class ServerPreServeHandler : TServerEventHandler { + this(Semaphore sem) { + sem_ = sem; + } + + override void preServe() { + sem_.notify(); + } + + Variant createContext(TProtocol input, TProtocol output) { return Variant.init; } + void deleteContext(Variant serverContext, TProtocol input, TProtocol output) {} + void preProcess(Variant serverContext, TTransport transport) {} + +private: + Semaphore sem_; +} + +class ServerThread : Thread { + this(ExTestHandler handler, ServerPreServeHandler serverHandler, TCancellation cancellation) { + super(&run); + handler_ = handler; + cancellation_ = cancellation; + serverHandler_ = serverHandler; + } +private: + void run() { + try { + auto protocolFactory = new TBinaryProtocolFactory!(); + auto processor = new TServiceProcessor!ExTestService(handler_); + auto serverTransport = new TServerSocket(handler_.port); + serverTransport.recvTimeout = dur!"seconds"(3); + auto transportFactory = new TBufferedTransportFactory; + + auto server = new TSimpleServer(processor, serverTransport, transportFactory, protocolFactory); + server.eventHandler = serverHandler_; + server.serve(cancellation_); + } catch (Exception e) { + writefln("Server thread on port %s failed: %s", handler_.port, e); + } + } + + ExTestHandler handler_; + ServerPreServeHandler serverHandler_; + TCancellation cancellation_; +} + +void main(string[] args) { + bool trace; + ushort port = 9090; + getopt(args, "port", &port, "trace", &trace); + + auto serverCancellation = new TCancellationOrigin; + scope (exit) serverCancellation.trigger(); + + immutable ports = cast(immutable)array(map!"cast(ushort)a"(iota(port, port + 6))); + + // semaphore that will be incremented whenever each server thread has bound and started listening + Semaphore sem = new Semaphore(0); + +version (none) { + // Cannot use this due to multiple DMD @@BUG@@s: + // 1. »function D main is a nested function and cannot be accessed from array« + // when calling array() on the result of the outer map() – would have to + // manually do the eager evaluation/array conversion. + // 2. »Zip.opSlice cannot get frame pointer to map« for the delay argument, + // can be worked around by calling array() on the map result first. + // 3. Even when using the workarounds for the last two points, the DMD-built + // executable crashes when building without (sic!) inlining enabled, + // the backtrace points into the first delegate literal. + auto handlers = array(map!((args){ + return new ExTestHandler(args._0, args._1, args._2, trace); + })(zip( + ports, + map!((a){ return dur!`msecs`(a); })([1, 10, 100, 1, 10, 100]), + [false, false, false, true, true, true] + ))); +} else { + auto handlers = [ + new ExTestHandler(cast(ushort)(port + 0), dur!"msecs"(1), false, trace), + new ExTestHandler(cast(ushort)(port + 1), dur!"msecs"(10), false, trace), + new ExTestHandler(cast(ushort)(port + 2), dur!"msecs"(100), false, trace), + new ExTestHandler(cast(ushort)(port + 3), dur!"msecs"(1), true, trace), + new ExTestHandler(cast(ushort)(port + 4), dur!"msecs"(10), true, trace), + new ExTestHandler(cast(ushort)(port + 5), dur!"msecs"(100), true, trace) + ]; +} + + // Fire up the server threads. + foreach (h; handlers) (new ServerThread(h, new ServerPreServeHandler(sem), serverCancellation)).start(); + + // wait until all the handlers signal that they're ready to serve + foreach (h; handlers) (sem.wait(dur!`seconds`(1))); + + syncClientPoolTest(ports, handlers); + asyncClientPoolTest(ports, handlers); + asyncFastestClientPoolTest(ports, handlers); + asyncAggregatorTest(ports, handlers); +} + + +void syncClientPoolTest(const(ushort)[] ports, ExTestHandler[] handlers) { + auto clients = array(map!((a){ + return cast(TClientBase!ExTestService)tClient!ExTestService( + tBinaryProtocol(new TSocket("127.0.0.1", a)) + ); + })(ports)); + + scope(exit) foreach (c; clients) c.outputProtocol.transport.close(); + + // Try the case where the first client succeeds. + { + enforce(makePool(clients).getPort() == ports[0]); + } + + // Try the case where all clients fail. + { + auto pool = makePool(clients[3 .. $]); + auto e = cast(TCompoundOperationException)collectException(pool.getPort()); + enforce(e); + enforce(equal(map!"a.port"(cast(TestServiceException[])e.exceptions), + ports[3 .. $])); + } + + // Try the case where the first clients fail, but a later one succeeds. + { + auto pool = makePool(clients[3 .. $] ~ clients[0 .. 3]); + enforce(pool.getPortInArray() == [ports[0]]); + } + + // Make sure a client is properly deactivated when it has failed too often. + { + auto pool = makePool(clients); + pool.faultDisableCount = 1; + pool.faultDisableDuration = dur!"msecs"(50); + + handlers[0].failing = true; + enforce(pool.getPort() == ports[1]); + + handlers[0].failing = false; + enforce(pool.getPort() == ports[1]); + + Thread.sleep(dur!"msecs"(50)); + enforce(pool.getPort() == ports[0]); + } +} + +auto makePool(TClientBase!ExTestService[] clients) { + auto p = tClientPool(clients); + p.permuteClients = false; + p.rpcFaultFilter = (Exception e) { + return (cast(TestServiceException)e !is null); + }; + return p; +} + + +void asyncClientPoolTest(const(ushort)[] ports, ExTestHandler[] handlers) { + auto manager = new TLibeventAsyncManager; + scope (exit) manager.stop(dur!"hnsecs"(0)); + + auto clients = makeAsyncClients(manager, ports); + scope(exit) foreach (c; clients) c.transport.close(); + + // Try the case where the first client succeeds. + { + enforce(makeAsyncPool(clients).getPort() == ports[0]); + } + + // Try the case where all clients fail. + { + auto pool = makeAsyncPool(clients[3 .. $]); + auto e = cast(TCompoundOperationException)collectException(pool.getPort().waitGet()); + enforce(e); + enforce(equal(map!"a.port"(cast(TestServiceException[])e.exceptions), + ports[3 .. $])); + } + + // Try the case where the first clients fail, but a later one succeeds. + { + auto pool = makeAsyncPool(clients[3 .. $] ~ clients[0 .. 3]); + enforce(pool.getPortInArray() == [ports[0]]); + } + + // Make sure a client is properly deactivated when it has failed too often. + { + auto pool = makeAsyncPool(clients); + pool.faultDisableCount = 1; + pool.faultDisableDuration = dur!"msecs"(50); + + handlers[0].failing = true; + enforce(pool.getPort() == ports[1]); + + handlers[0].failing = false; + enforce(pool.getPort() == ports[1]); + + Thread.sleep(dur!"msecs"(50)); + enforce(pool.getPort() == ports[0]); + } +} + +auto makeAsyncPool(TAsyncClientBase!ExTestService[] clients) { + auto p = tAsyncClientPool(clients); + p.permuteClients = false; + p.rpcFaultFilter = (Exception e) { + return (cast(TestServiceException)e !is null); + }; + return p; +} + +auto makeAsyncClients(TLibeventAsyncManager manager, in ushort[] ports) { + // DMD @@BUG@@ workaround: Using array on the lazyHandlers map result leads + // to »function D main is a nested function and cannot be accessed from array«. + // Thus, we manually do the array conversion. + auto lazyClients = map!((a){ + return new TAsyncClient!ExTestService( + new TAsyncSocket(manager, "127.0.0.1", a), + new TBufferedTransportFactory, + new TBinaryProtocolFactory!(TBufferedTransport) + ); + })(ports); + TAsyncClientBase!ExTestService[] clients; + foreach (c; lazyClients) clients ~= c; + return clients; +} + + +void asyncFastestClientPoolTest(const(ushort)[] ports, ExTestHandler[] handlers) { + auto manager = new TLibeventAsyncManager; + scope (exit) manager.stop(dur!"hnsecs"(0)); + + auto clients = makeAsyncClients(manager, ports); + scope(exit) foreach (c; clients) c.transport.close(); + + // Make sure the fastest client wins, even if they are called in some other + // order. + { + auto result = makeAsyncFastestPool(array(retro(clients))).getPort().waitGet(); + enforce(result == ports[0]); + } + + // Try the case where all clients fail. + { + auto pool = makeAsyncFastestPool(clients[3 .. $]); + auto e = cast(TCompoundOperationException)collectException(pool.getPort().waitGet()); + enforce(e); + enforce(equal(map!"a.port"(cast(TestServiceException[])e.exceptions), + ports[3 .. $])); + } + + // Try the case where the first clients fail, but a later one succeeds. + { + auto pool = makeAsyncFastestPool(clients[1 .. $]); + enforce(pool.getPortInArray() == [ports[1]]); + } +} + +auto makeAsyncFastestPool(TAsyncClientBase!ExTestService[] clients) { + auto p = tAsyncFastestClientPool(clients); + p.rpcFaultFilter = (Exception e) { + return (cast(TestServiceException)e !is null); + }; + return p; +} + + +void asyncAggregatorTest(const(ushort)[] ports, ExTestHandler[] handlers) { + auto manager = new TLibeventAsyncManager; + scope (exit) manager.stop(dur!"hnsecs"(0)); + + auto clients = makeAsyncClients(manager, ports); + scope(exit) foreach (c; clients) c.transport.close(); + + auto aggregator = tAsyncAggregator( + cast(TAsyncClientBase!ExTestService[])clients); + + // Test aggregator range interface. + { + auto range = aggregator.getPort().range(dur!"msecs"(50)); + enforce(equal(range, ports[0 .. 2][])); + enforce(equal(map!"a.port"(cast(TestServiceException[])range.exceptions), + ports[3 .. $ - 1])); + enforce(range.completedCount == 4); + } + + // Test default accumulator for scalars. + { + auto fullResult = aggregator.getPort().accumulate(); + enforce(fullResult.waitGet() == ports[0 .. 3]); + + auto partialResult = aggregator.getPort().accumulate(); + Thread.sleep(dur!"msecs"(20)); + enforce(partialResult.finishGet() == ports[0 .. 2]); + + } + + // Test default accumulator for arrays. + { + auto fullResult = aggregator.getPortInArray().accumulate(); + enforce(fullResult.waitGet() == ports[0 .. 3]); + + auto partialResult = aggregator.getPortInArray().accumulate(); + Thread.sleep(dur!"msecs"(20)); + enforce(partialResult.finishGet() == ports[0 .. 2]); + } + + // Test custom accumulator. + { + auto fullResult = aggregator.getPort().accumulate!(function(int[] results){ + return reduce!"a + b"(results); + })(); + enforce(fullResult.waitGet() == ports[0] + ports[1] + ports[2]); + + auto partialResult = aggregator.getPort().accumulate!( + function(int[] results, Exception[] exceptions) { + // Return a tuple of the parameters so we can check them outside of + // this function (to verify the values, we need access to »ports«, but + // due to DMD @@BUG5710@@, we can't use a delegate literal).f + return tuple(results, exceptions); + } + )(); + Thread.sleep(dur!"msecs"(20)); + auto resultTuple = partialResult.finishGet(); + enforce(resultTuple[0] == ports[0 .. 2]); + enforce(equal(map!"a.port"(cast(TestServiceException[])resultTuple[1]), + ports[3 .. $ - 1])); + } +} diff --git a/src/jaegertracing/thrift/lib/d/test/serialization_benchmark.d b/src/jaegertracing/thrift/lib/d/test/serialization_benchmark.d new file mode 100644 index 000000000..40d048094 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/serialization_benchmark.d @@ -0,0 +1,70 @@ +/** + * An implementation of the mini serialization benchmark also available for + * C++ and Java. + * + * For meaningful results, you might want to make sure that + * the Thrift library is compiled with release build flags, + * e.g. by including the source files with the build instead + * of linking libthriftd: + * + dmd -w -O -release -inline -I../src -Igen-d -ofserialization_benchmark \ + $(find ../src/thrift -name '*.d' -not -name index.d) \ + gen-d/DebugProtoTest_types.d serialization_benchmark.d + */ +module serialization_benchmark; + +import std.datetime.stopwatch : AutoStart, StopWatch; +import std.math : PI; +import std.stdio; +import thrift.protocol.binary; +import thrift.transport.memory; +import thrift.transport.range; +import DebugProtoTest_types; + +void main() { + auto buf = new TMemoryBuffer; + enum ITERATIONS = 10_000_000; + + { + auto ooe = OneOfEach(); + ooe.im_true = true; + ooe.im_false = false; + ooe.a_bite = 0x7f; + ooe.integer16 = 27_000; + ooe.integer32 = 1 << 24; + ooe.integer64 = 6_000_000_000; + ooe.double_precision = PI; + ooe.some_characters = "JSON THIS! \"\1"; + ooe.zomg_unicode = "\xd7\n\a\t"; + ooe.base64 = "\1\2\3\255"; + + auto prot = tBinaryProtocol(buf); + auto sw = StopWatch(AutoStart.yes); + foreach (i; 0 .. ITERATIONS) { + buf.reset(120); + ooe.write(prot); + } + sw.stop(); + + auto msecs = sw.peek().total!"msecs"; + writefln("Write: %s ms (%s kHz)", msecs, ITERATIONS / msecs); + } + + auto data = buf.getContents().dup; + + { + auto readBuf = tInputRangeTransport(data); + auto prot = tBinaryProtocol(readBuf); + auto ooe = OneOfEach(); + + auto sw = StopWatch(AutoStart.yes); + foreach (i; 0 .. ITERATIONS) { + readBuf.reset(data); + ooe.read(prot); + } + sw.stop(); + + auto msecs = sw.peek().total!"msecs"; + writefln(" Read: %s ms (%s kHz)", msecs, ITERATIONS / msecs); + } +} diff --git a/src/jaegertracing/thrift/lib/d/test/stress_test_server.d b/src/jaegertracing/thrift/lib/d/test/stress_test_server.d new file mode 100644 index 000000000..ddda098b3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/stress_test_server.d @@ -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. + */ +module stress_test_server; + +import std.getopt; +import std.parallelism : totalCPUs; +import std.stdio; +import std.typetuple; +import thrift.codegen.processor; +import thrift.protocol.binary; +import thrift.server.base; +import thrift.server.transport.socket; +import thrift.transport.buffered; +import thrift.transport.memory; +import thrift.transport.socket; +import thrift.util.hashset; +import test_utils; + +import thrift.test.stress.Service; + +class ServiceHandler : Service { + void echoVoid() { return; } + byte echoByte(byte arg) { return arg; } + int echoI32(int arg) { return arg; } + long echoI64(long arg) { return arg; } + byte[] echoList(byte[] arg) { return arg; } + HashSet!byte echoSet(HashSet!byte arg) { return arg; } + byte[byte] echoMap(byte[byte] arg) { return arg; } + + string echoString(string arg) { + if (arg != "hello") { + stderr.writefln(`Wrong string received: %s instead of "hello"`, arg); + throw new Exception("Wrong string received."); + } + return arg; + } +} + +void main(string[] args) { + ushort port = 9091; + auto serverType = ServerType.threaded; + TransportType transportType; + size_t numIOThreads = 1; + size_t taskPoolSize = totalCPUs; + + getopt(args, "port", &port, "server-type", &serverType, + "transport-type", &transportType, "task-pool-size", &taskPoolSize, + "num-io-threads", &numIOThreads); + + alias TypeTuple!(TBufferedTransport, TMemoryBuffer) AvailableTransports; + + auto processor = new TServiceProcessor!(Service, + staticMap!(TBinaryProtocol, AvailableTransports))(new ServiceHandler()); + auto serverSocket = new TServerSocket(port); + auto transportFactory = createTransportFactory(transportType); + auto protocolFactory = new TBinaryProtocolFactory!AvailableTransports; + + auto server = createServer(serverType, taskPoolSize, numIOThreads, + processor, serverSocket, transportFactory, protocolFactory); + + writefln("Starting %s %s StressTest server on port %s...", transportType, + serverType, port); + server.serve(); + writeln("done."); +} diff --git a/src/jaegertracing/thrift/lib/d/test/test_utils.d b/src/jaegertracing/thrift/lib/d/test/test_utils.d new file mode 100644 index 000000000..174100b79 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/test_utils.d @@ -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. + */ + +/** + * Various helpers used by more than a single test. + */ +module test_utils; + +import std.parallelism : TaskPool; +import thrift.protocol.base; +import thrift.protocol.processor; +import thrift.server.base; +import thrift.server.nonblocking; +import thrift.server.simple; +import thrift.server.taskpool; +import thrift.server.threaded; +import thrift.server.transport.socket; +import thrift.transport.base; +import thrift.transport.buffered; +import thrift.transport.framed; +import thrift.transport.http; + +// This is a likely victim of @@BUG4744@@ when used with command argument +// parsing. +enum ServerType { + simple, + nonblocking, + pooledNonblocking, + taskpool, + threaded +} + +TServer createServer(ServerType type, size_t taskPoolSize, size_t numIOThreads, + TProcessor processor, TServerSocket serverTransport, + TTransportFactory transportFactory, TProtocolFactory protocolFactory) +{ + final switch (type) { + case ServerType.simple: + return new TSimpleServer(processor, serverTransport, + transportFactory, protocolFactory); + case ServerType.nonblocking: + auto nb = new TNonblockingServer(processor, serverTransport.port, + transportFactory, protocolFactory); + nb.numIOThreads = numIOThreads; + return nb; + case ServerType.pooledNonblocking: + auto nb = new TNonblockingServer(processor, serverTransport.port, + transportFactory, protocolFactory, new TaskPool(taskPoolSize)); + nb.numIOThreads = numIOThreads; + return nb; + case ServerType.taskpool: + auto tps = new TTaskPoolServer(processor, serverTransport, + transportFactory, protocolFactory); + tps.taskPool = new TaskPool(taskPoolSize); + return tps; + case ServerType.threaded: + return new TThreadedServer(processor, serverTransport, + transportFactory, protocolFactory); + } +} + +enum TransportType { + buffered, + framed, + http, + raw +} + +TTransportFactory createTransportFactory(TransportType type) { + final switch (type) { + case TransportType.buffered: + return new TBufferedTransportFactory; + case TransportType.framed: + return new TFramedTransportFactory; + case TransportType.http: + return new TServerHttpTransportFactory; + case TransportType.raw: + return new TTransportFactory; + } +} diff --git a/src/jaegertracing/thrift/lib/d/test/thrift_test_client.d b/src/jaegertracing/thrift/lib/d/test/thrift_test_client.d new file mode 100644 index 000000000..49419f71a --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/thrift_test_client.d @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +module thrift_test_client; + +import std.conv; +import std.datetime; +import std.exception : enforce; +import std.getopt; +import std.stdio; +import std.string; +import std.traits; +import thrift.base; +import thrift.codegen.client; +import thrift.protocol.base; +import thrift.protocol.binary; +import thrift.protocol.compact; +import thrift.protocol.json; +import thrift.transport.base; +import thrift.transport.buffered; +import thrift.transport.framed; +import thrift.transport.http; +import thrift.transport.socket; +import thrift.transport.ssl; +import thrift.util.hashset; + +import thrift_test_common; +import thrift.test.ThriftTest; +import thrift.test.ThriftTest_types; + +enum TransportType { + buffered, + framed, + http, + raw +} + +TProtocol createProtocol(T)(T trans, ProtocolType type) { + final switch (type) { + case ProtocolType.binary: + return tBinaryProtocol(trans); + case ProtocolType.compact: + return tCompactProtocol(trans); + case ProtocolType.json: + return tJsonProtocol(trans); + } +} + +void main(string[] args) { + string host = "localhost"; + ushort port = 9090; + uint numTests = 1; + bool ssl; + ProtocolType protocolType; + TransportType transportType; + bool trace; + + getopt(args, + "numTests|n", &numTests, + "protocol", &protocolType, + "ssl", &ssl, + "transport", &transportType, + "trace", &trace, + "port", &port, + "host", (string _, string value) { + auto parts = split(value, ":"); + if (parts.length > 1) { + // IPv6 addresses can contain colons, so take the last part for the + // port. + host = join(parts[0 .. $ - 1], ":"); + port = to!ushort(parts[$ - 1]); + } else { + host = value; + } + } + ); + port = to!ushort(port); + + TSocket socket; + if (ssl) { + auto sslContext = new TSSLContext(); + sslContext.ciphers = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; + sslContext.authenticate = true; + sslContext.loadTrustedCertificates("../../../test/keys/CA.pem"); + socket = new TSSLSocket(sslContext, host, port); + } else { + socket = new TSocket(host, port); + } + + TProtocol protocol; + final switch (transportType) { + case TransportType.buffered: + protocol = createProtocol(new TBufferedTransport(socket), protocolType); + break; + case TransportType.framed: + protocol = createProtocol(new TFramedTransport(socket), protocolType); + break; + case TransportType.http: + protocol = createProtocol( + new TClientHttpTransport(socket, host, "/service"), protocolType); + break; + case TransportType.raw: + protocol = createProtocol(socket, protocolType); + break; + } + + auto client = tClient!ThriftTest(protocol); + + ulong time_min; + ulong time_max; + ulong time_tot; + + StopWatch sw; + foreach(test; 0 .. numTests) { + sw.start(); + + protocol.transport.open(); + + if (trace) writefln("Test #%s, connect %s:%s", test + 1, host, port); + + if (trace) write("testVoid()"); + client.testVoid(); + if (trace) writeln(" = void"); + + if (trace) write("testString(\"Test\")"); + string s = client.testString("Test"); + if (trace) writefln(" = \"%s\"", s); + enforce(s == "Test"); + + if (trace) write("testByte(1)"); + byte u8 = client.testByte(1); + if (trace) writefln(" = %s", u8); + enforce(u8 == 1); + + if (trace) write("testI32(-1)"); + int i32 = client.testI32(-1); + if (trace) writefln(" = %s", i32); + enforce(i32 == -1); + + if (trace) write("testI64(-34359738368)"); + long i64 = client.testI64(-34359738368L); + if (trace) writefln(" = %s", i64); + enforce(i64 == -34359738368L); + + if (trace) write("testDouble(-5.2098523)"); + double dub = client.testDouble(-5.2098523); + if (trace) writefln(" = %s", dub); + enforce(dub == -5.2098523); + + // TODO: add testBinary() call + + Xtruct out1; + out1.string_thing = "Zero"; + out1.byte_thing = 1; + out1.i32_thing = -3; + out1.i64_thing = -5; + if (trace) writef("testStruct(%s)", out1); + auto in1 = client.testStruct(out1); + if (trace) writefln(" = %s", in1); + enforce(in1 == out1); + + if (trace) write("testNest({1, {\"Zero\", 1, -3, -5}), 5}"); + Xtruct2 out2; + out2.byte_thing = 1; + out2.struct_thing = out1; + out2.i32_thing = 5; + auto in2 = client.testNest(out2); + in1 = in2.struct_thing; + if (trace) writefln(" = {%s, {\"%s\", %s, %s, %s}, %s}", in2.byte_thing, + in1.string_thing, in1.byte_thing, in1.i32_thing, in1.i64_thing, + in2.i32_thing); + enforce(in2 == out2); + + int[int] mapout; + for (int i = 0; i < 5; ++i) { + mapout[i] = i - 10; + } + if (trace) writef("testMap({%s})", mapout); + auto mapin = client.testMap(mapout); + if (trace) writefln(" = {%s}", mapin); + enforce(mapin == mapout); + + auto setout = new HashSet!int; + for (int i = -2; i < 3; ++i) { + setout ~= i; + } + if (trace) writef("testSet(%s)", setout); + auto setin = client.testSet(setout); + if (trace) writefln(" = %s", setin); + enforce(setin == setout); + + int[] listout; + for (int i = -2; i < 3; ++i) { + listout ~= i; + } + if (trace) writef("testList(%s)", listout); + auto listin = client.testList(listout); + if (trace) writefln(" = %s", listin); + enforce(listin == listout); + + { + if (trace) write("testEnum(ONE)"); + auto ret = client.testEnum(Numberz.ONE); + if (trace) writefln(" = %s", ret); + enforce(ret == Numberz.ONE); + + if (trace) write("testEnum(TWO)"); + ret = client.testEnum(Numberz.TWO); + if (trace) writefln(" = %s", ret); + enforce(ret == Numberz.TWO); + + if (trace) write("testEnum(THREE)"); + ret = client.testEnum(Numberz.THREE); + if (trace) writefln(" = %s", ret); + enforce(ret == Numberz.THREE); + + if (trace) write("testEnum(FIVE)"); + ret = client.testEnum(Numberz.FIVE); + if (trace) writefln(" = %s", ret); + enforce(ret == Numberz.FIVE); + + if (trace) write("testEnum(EIGHT)"); + ret = client.testEnum(Numberz.EIGHT); + if (trace) writefln(" = %s", ret); + enforce(ret == Numberz.EIGHT); + } + + if (trace) write("testTypedef(309858235082523)"); + UserId uid = client.testTypedef(309858235082523L); + if (trace) writefln(" = %s", uid); + enforce(uid == 309858235082523L); + + if (trace) write("testMapMap(1)"); + auto mm = client.testMapMap(1); + if (trace) writefln(" = {%s}", mm); + // Simply doing == doesn't seem to work for nested AAs. + foreach (key, value; mm) { + enforce(testMapMapReturn[key] == value); + } + foreach (key, value; testMapMapReturn) { + enforce(mm[key] == value); + } + + Insanity insane; + insane.userMap[Numberz.FIVE] = 5000; + Xtruct truck; + truck.string_thing = "Truck"; + truck.byte_thing = 8; + truck.i32_thing = 8; + truck.i64_thing = 8; + insane.xtructs ~= truck; + if (trace) write("testInsanity()"); + auto whoa = client.testInsanity(insane); + if (trace) writefln(" = %s", whoa); + + // Commented for now, this is cumbersome to write without opEqual getting + // called on AA comparison. + // enforce(whoa == testInsanityReturn); + + { + try { + if (trace) write("client.testException(\"Xception\") =>"); + client.testException("Xception"); + if (trace) writeln(" void\nFAILURE"); + throw new Exception("testException failed."); + } catch (Xception e) { + if (trace) writefln(" {%s, \"%s\"}", e.errorCode, e.message); + } + + try { + if (trace) write("client.testException(\"TException\") =>"); + client.testException("Xception"); + if (trace) writeln(" void\nFAILURE"); + throw new Exception("testException failed."); + } catch (TException e) { + if (trace) writefln(" {%s}", e.msg); + } + + try { + if (trace) write("client.testException(\"success\") =>"); + client.testException("success"); + if (trace) writeln(" void"); + } catch (Exception e) { + if (trace) writeln(" exception\nFAILURE"); + throw new Exception("testException failed."); + } + } + + { + try { + if (trace) write("client.testMultiException(\"Xception\", \"test 1\") =>"); + auto result = client.testMultiException("Xception", "test 1"); + if (trace) writeln(" result\nFAILURE"); + throw new Exception("testMultiException failed."); + } catch (Xception e) { + if (trace) writefln(" {%s, \"%s\"}", e.errorCode, e.message); + } + + try { + if (trace) write("client.testMultiException(\"Xception2\", \"test 2\") =>"); + auto result = client.testMultiException("Xception2", "test 2"); + if (trace) writeln(" result\nFAILURE"); + throw new Exception("testMultiException failed."); + } catch (Xception2 e) { + if (trace) writefln(" {%s, {\"%s\"}}", + e.errorCode, e.struct_thing.string_thing); + } + + try { + if (trace) writef("client.testMultiException(\"success\", \"test 3\") =>"); + auto result = client.testMultiException("success", "test 3"); + if (trace) writefln(" {{\"%s\"}}", result.string_thing); + } catch (Exception e) { + if (trace) writeln(" exception\nFAILURE"); + throw new Exception("testMultiException failed."); + } + } + + // Do not run oneway test when doing multiple iterations, as it blocks the + // server for three seconds. + if (numTests == 1) { + if (trace) writef("client.testOneway(3) =>"); + auto onewayWatch = StopWatch(AutoStart.yes); + client.testOneway(3); + onewayWatch.stop(); + if (onewayWatch.peek().msecs > 200) { + if (trace) { + writefln(" FAILURE - took %s ms", onewayWatch.peek().usecs / 1000.0); + } + throw new Exception("testOneway failed."); + } else { + if (trace) { + writefln(" success - took %s ms", onewayWatch.peek().usecs / 1000.0); + } + } + + // Redo a simple test after the oneway to make sure we aren't "off by + // one", which would be the case if the server treated oneway methods + // like normal ones. + if (trace) write("re-test testI32(-1)"); + i32 = client.testI32(-1); + if (trace) writefln(" = %s", i32); + } + + // Time metering. + sw.stop(); + + immutable tot = sw.peek().usecs; + if (trace) writefln("Total time: %s us\n", tot); + + time_tot += tot; + if (time_min == 0 || tot < time_min) { + time_min = tot; + } + if (tot > time_max) { + time_max = tot; + } + protocol.transport.close(); + + sw.reset(); + } + + writeln("All tests done."); + + if (numTests > 1) { + auto time_avg = time_tot / numTests; + writefln("Min time: %s us", time_min); + writefln("Max time: %s us", time_max); + writefln("Avg time: %s us", time_avg); + } +} diff --git a/src/jaegertracing/thrift/lib/d/test/thrift_test_common.d b/src/jaegertracing/thrift/lib/d/test/thrift_test_common.d new file mode 100644 index 000000000..13a568613 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/thrift_test_common.d @@ -0,0 +1,92 @@ +module thrift_test_common; + +import std.stdio; +import thrift.test.ThriftTest_types; + +enum ProtocolType { + binary, + compact, + json +} + +void writeInsanityReturn(in Insanity[Numberz][UserId] insane) { + write("{"); + foreach(key1, value1; insane) { + writef("%s => {", key1); + foreach(key2, value2; value1) { + writef("%s => {", key2); + write("{"); + foreach(key3, value3; value2.userMap) { + writef("%s => %s, ", key3, value3); + } + write("}, "); + + write("{"); + foreach (x; value2.xtructs) { + writef("{\"%s\", %s, %s, %s}, ", + x.string_thing, x.byte_thing, x.i32_thing, x.i64_thing); + } + write("}"); + + write("}, "); + } + write("}, "); + } + write("}"); +} + +Insanity[Numberz][UserId] testInsanityReturn; +int[int][int] testMapMapReturn; + +static this() { + testInsanityReturn = { + Insanity[Numberz][UserId] insane; + + Xtruct hello; + hello.string_thing = "Hello2"; + hello.byte_thing = 2; + hello.i32_thing = 2; + hello.i64_thing = 2; + + Xtruct goodbye; + goodbye.string_thing = "Goodbye4"; + goodbye.byte_thing = 4; + goodbye.i32_thing = 4; + goodbye.i64_thing = 4; + + Insanity crazy; + crazy.userMap[Numberz.EIGHT] = 8; + crazy.xtructs ~= goodbye; + + Insanity looney; + // The C++ TestServer also assigns these to crazy, but that is probably + // an oversight. + looney.userMap[Numberz.FIVE] = 5; + looney.xtructs ~= hello; + + Insanity[Numberz] first_map; + first_map[Numberz.TWO] = crazy; + first_map[Numberz.THREE] = crazy; + insane[1] = first_map; + + Insanity[Numberz] second_map; + second_map[Numberz.SIX] = looney; + insane[2] = second_map; + return insane; + }(); + + testMapMapReturn = { + int[int] pos; + int[int] neg; + + for (int i = 1; i < 5; i++) { + pos[i] = i; + neg[-i] = -i; + } + + int[int][int] result; + result[4] = pos; + result[-4] = neg; + return result; + }(); +} diff --git a/src/jaegertracing/thrift/lib/d/test/thrift_test_runner.sh b/src/jaegertracing/thrift/lib/d/test/thrift_test_runner.sh new file mode 100755 index 000000000..51bfe9999 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/thrift_test_runner.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# +# 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. +# + +# Runs the D ThriftTest client and servers for all combinations of transport, +# protocol, SSL-mode and server type. +# Pass -k to keep going after failed tests. + +CUR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +protocols="binary compact json" +# TODO: fix and enable http +# transports="buffered framed raw http" +transports="buffered framed raw" +servers="simple taskpool threaded" +framed_only_servers="nonblocking pooledNonblocking" + +# Don't leave any server instances behind when interrupted (e.g. by Ctrl+C) +# or terminated. +trap "kill $(jobs -p) 2>/dev/null" INT TERM + +for protocol in $protocols; do + for ssl in "" " --ssl"; do + for transport in $transports; do + for server in $servers $framed_only_servers; do + case $framed_only_servers in + *$server*) if [ $transport != "framed" ] || [ $ssl != "" ]; then continue; fi;; + esac + + args="--transport=$transport --protocol=$protocol$ssl" + ${CUR}/thrift_test_server $args --server-type=$server > /dev/null & + server_pid=$! + + # Give the server some time to get up and check if it runs (yes, this + # is a huge kludge, should add a connect timeout to test client). + client_rc=-1 + if [ "$server" = "taskpool" ]; then + sleep 0.5 + else + sleep 0.02 + fi + kill -0 $server_pid 2>/dev/null + if [ $? -eq 0 ]; then + ${CUR}/thrift_test_client $args --numTests=10 > /dev/null + client_rc=$? + + # Temporarily redirect stderr to null to avoid job control messages, + # restore it afterwards. + exec 3>&2 + exec 2>/dev/null + kill $server_pid + exec 3>&2 + fi + + # Get the server exit code (wait should immediately return). + wait $server_pid + server_rc=$? + + if [ $client_rc -ne 0 -o $server_rc -eq 1 ]; then + echo -e "\nTests failed for: $args --server-type=$server" + failed="true" + if [ "$1" != "-k" ]; then + exit 1 + fi + else + echo -n "." + fi + done + done + done +done + +echo +if [ -z "$failed" ]; then + echo "All tests passed." +fi diff --git a/src/jaegertracing/thrift/lib/d/test/thrift_test_server.d b/src/jaegertracing/thrift/lib/d/test/thrift_test_server.d new file mode 100644 index 000000000..ce820d699 --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/thrift_test_server.d @@ -0,0 +1,337 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module thrift_test_server; + +import core.stdc.errno : errno; +import core.stdc.signal : signal, SIGINT, SIG_DFL, SIG_ERR; +import core.thread : dur, Thread; +import std.algorithm; +import std.exception : enforce; +import std.getopt; +import std.parallelism : totalCPUs; +import std.string; +import std.stdio; +import std.typetuple : TypeTuple, staticMap; +import thrift.base; +import thrift.codegen.processor; +import thrift.protocol.base; +import thrift.protocol.binary; +import thrift.protocol.compact; +import thrift.protocol.json; +import thrift.server.base; +import thrift.server.transport.socket; +import thrift.server.transport.ssl; +import thrift.transport.base; +import thrift.transport.buffered; +import thrift.transport.framed; +import thrift.transport.http; +import thrift.transport.ssl; +import thrift.util.cancellation; +import thrift.util.hashset; +import test_utils; + +import thrift_test_common; +import thrift.test.ThriftTest_types; +import thrift.test.ThriftTest; + +class TestHandler : ThriftTest { + this(bool trace) { + trace_ = trace; + } + + override void testVoid() { + if (trace_) writeln("testVoid()"); + } + + override string testString(string thing) { + if (trace_) writefln("testString(\"%s\")", thing); + return thing; + } + + override byte testByte(byte thing) { + if (trace_) writefln("testByte(%s)", thing); + return thing; + } + + override int testI32(int thing) { + if (trace_) writefln("testI32(%s)", thing); + return thing; + } + + override long testI64(long thing) { + if (trace_) writefln("testI64(%s)", thing); + return thing; + } + + override double testDouble(double thing) { + if (trace_) writefln("testDouble(%s)", thing); + return thing; + } + + override string testBinary(string thing) { + if (trace_) writefln("testBinary(\"%s\")", thing); + return thing; + } + + override bool testBool(bool thing) { + if (trace_) writefln("testBool(\"%s\")", thing); + return thing; + } + + override Xtruct testStruct(ref const(Xtruct) thing) { + if (trace_) writefln("testStruct({\"%s\", %s, %s, %s})", + thing.string_thing, thing.byte_thing, thing.i32_thing, thing.i64_thing); + return thing; + } + + override Xtruct2 testNest(ref const(Xtruct2) nest) { + auto thing = nest.struct_thing; + if (trace_) writefln("testNest({%s, {\"%s\", %s, %s, %s}, %s})", + nest.byte_thing, thing.string_thing, thing.byte_thing, thing.i32_thing, + thing.i64_thing, nest.i32_thing); + return nest; + } + + override int[int] testMap(int[int] thing) { + if (trace_) writefln("testMap({%s})", thing); + return thing; + } + + override HashSet!int testSet(HashSet!int thing) { + if (trace_) writefln("testSet({%s})", + join(map!`to!string(a)`(thing[]), ", ")); + return thing; + } + + override int[] testList(int[] thing) { + if (trace_) writefln("testList(%s)", thing); + return thing; + } + + override Numberz testEnum(Numberz thing) { + if (trace_) writefln("testEnum(%s)", thing); + return thing; + } + + override UserId testTypedef(UserId thing) { + if (trace_) writefln("testTypedef(%s)", thing); + return thing; + } + + override string[string] testStringMap(string[string] thing) { + if (trace_) writefln("testStringMap(%s)", thing); + return thing; + } + + override int[int][int] testMapMap(int hello) { + if (trace_) writefln("testMapMap(%s)", hello); + return testMapMapReturn; + } + + override Insanity[Numberz][UserId] testInsanity(ref const(Insanity) argument) { + if (trace_) writeln("testInsanity()"); + Insanity[Numberz][UserId] ret; + Insanity[Numberz] m1; + Insanity[Numberz] m2; + Insanity tmp; + tmp = cast(Insanity)argument; + m1[Numberz.TWO] = tmp; + m1[Numberz.THREE] = tmp; + m2[Numberz.SIX] = Insanity(); + ret[1] = m1; + ret[2] = m2; + return ret; + } + + override Xtruct testMulti(byte arg0, int arg1, long arg2, string[short] arg3, + Numberz arg4, UserId arg5) + { + if (trace_) writeln("testMulti()"); + return Xtruct("Hello2", arg0, arg1, arg2); + } + + override void testException(string arg) { + if (trace_) writefln("testException(%s)", arg); + if (arg == "Xception") { + auto e = new Xception(); + e.errorCode = 1001; + e.message = arg; + throw e; + } else if (arg == "TException") { + throw new TException(); + } else if (arg == "ApplicationException") { + throw new TException(); + } + } + + override Xtruct testMultiException(string arg0, string arg1) { + if (trace_) writefln("testMultiException(%s, %s)", arg0, arg1); + + if (arg0 == "Xception") { + auto e = new Xception(); + e.errorCode = 1001; + e.message = "This is an Xception"; + throw e; + } else if (arg0 == "Xception2") { + auto e = new Xception2(); + e.errorCode = 2002; + e.struct_thing.string_thing = "This is an Xception2"; + throw e; + } else { + return Xtruct(arg1); + } + } + + override void testOneway(int sleepFor) { + if (trace_) writefln("testOneway(%s): Sleeping...", sleepFor); + Thread.sleep(dur!"seconds"(sleepFor)); + if (trace_) writefln("testOneway(%s): done sleeping!", sleepFor); + } + +private: + bool trace_; +} + +shared(bool) gShutdown = false; + +nothrow @nogc extern(C) void handleSignal(int sig) { + gShutdown = true; +} + +// Runs a thread that waits for shutdown to be +// signaled and then triggers cancellation, +// causing the server to stop. While we could +// use a signalfd for this purpose, we are instead +// opting for a busy waiting scheme for maximum +// portability since signalfd is a linux thing. + +class ShutdownThread : Thread { + this(TCancellationOrigin cancellation) { + cancellation_ = cancellation; + super(&run); + } + +private: + void run() { + while (!gShutdown) { + Thread.sleep(dur!("msecs")(25)); + } + cancellation_.trigger(); + } + + TCancellationOrigin cancellation_; +} + +void main(string[] args) { + ushort port = 9090; + ServerType serverType; + ProtocolType protocolType; + size_t numIOThreads = 1; + TransportType transportType; + bool ssl = false; + bool trace = true; + size_t taskPoolSize = totalCPUs; + + getopt(args, "port", &port, "protocol", &protocolType, "server-type", + &serverType, "ssl", &ssl, "num-io-threads", &numIOThreads, + "task-pool-size", &taskPoolSize, "trace", &trace, + "transport", &transportType); + + if (serverType == ServerType.nonblocking || + serverType == ServerType.pooledNonblocking + ) { + enforce(transportType == TransportType.framed, + "Need to use framed transport with non-blocking server."); + enforce(!ssl, "The non-blocking server does not support SSL yet."); + + // Don't wrap the contents into another layer of framing. + transportType = TransportType.raw; + } + + version (ThriftTestTemplates) { + // Only exercise the specialized template code paths if explicitly enabled + // to reduce memory consumption on regular test suite runs – there should + // not be much that can go wrong with that specifically anyway. + alias TypeTuple!(TBufferedTransport, TFramedTransport, TServerHttpTransport) + AvailableTransports; + alias TypeTuple!( + staticMap!(TBinaryProtocol, AvailableTransports), + staticMap!(TCompactProtocol, AvailableTransports) + ) AvailableProtocols; + } else { + alias TypeTuple!() AvailableTransports; + alias TypeTuple!() AvailableProtocols; + } + + TProtocolFactory protocolFactory; + final switch (protocolType) { + case ProtocolType.binary: + protocolFactory = new TBinaryProtocolFactory!AvailableTransports; + break; + case ProtocolType.compact: + protocolFactory = new TCompactProtocolFactory!AvailableTransports; + break; + case ProtocolType.json: + protocolFactory = new TJsonProtocolFactory!AvailableTransports; + break; + } + + auto processor = new TServiceProcessor!(ThriftTest, AvailableProtocols)( + new TestHandler(trace)); + + TServerSocket serverSocket; + if (ssl) { + auto sslContext = new TSSLContext(); + sslContext.serverSide = true; + sslContext.loadCertificate("../../../test/keys/server.crt"); + sslContext.loadPrivateKey("../../../test/keys/server.key"); + sslContext.ciphers = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; + serverSocket = new TSSLServerSocket(port, sslContext); + } else { + serverSocket = new TServerSocket(port); + } + + auto transportFactory = createTransportFactory(transportType); + + auto server = createServer(serverType, numIOThreads, taskPoolSize, + processor, serverSocket, transportFactory, protocolFactory); + + // Set up SIGINT signal handling + enforce(signal(SIGINT, &handleSignal) != SIG_ERR, + "Could not replace the SIGINT signal handler: errno {0}".format(errno())); + + // Set up a server cancellation trigger + auto cancel = new TCancellationOrigin(); + + // Set up a listener for the shutdown condition - this will + // wake up when the signal occurs and trigger cancellation. + auto shutdown = new ShutdownThread(cancel); + shutdown.start(); + + // Serve from this thread; the signal will stop the server + // and control will return here + writefln("Starting %s/%s %s ThriftTest server %son port %s...", protocolType, + transportType, serverType, ssl ? "(using SSL) ": "", port); + server.serve(cancel); + shutdown.join(); + signal(SIGINT, SIG_DFL); + + writeln("done."); +} diff --git a/src/jaegertracing/thrift/lib/d/test/transport_test.d b/src/jaegertracing/thrift/lib/d/test/transport_test.d new file mode 100644 index 000000000..623e03f0e --- /dev/null +++ b/src/jaegertracing/thrift/lib/d/test/transport_test.d @@ -0,0 +1,803 @@ +/* + * 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. + */ + +/** + * Exercises various transports, combined with the buffered/framed wrappers. + * + * Originally ported from the C++ version, with Windows support code added. + */ +module transport_test; + +import core.atomic; +import core.time : Duration; +import core.thread : Thread; +import std.conv : to; +import std.datetime; +import std.exception : enforce; +static import std.file; +import std.getopt; +import std.random : rndGen, uniform, unpredictableSeed; +import std.socket; +import std.stdio; +import std.string; +import std.typetuple; +import thrift.transport.base; +import thrift.transport.buffered; +import thrift.transport.framed; +import thrift.transport.file; +import thrift.transport.http; +import thrift.transport.memory; +import thrift.transport.socket; +import thrift.transport.zlib; + +/* + * Size generation helpers – used to be able to run the same testing code + * with both constant and random total/chunk sizes. + */ + +interface SizeGenerator { + size_t nextSize(); + string toString(); +} + +class ConstantSizeGenerator : SizeGenerator { + this(size_t value) { + value_ = value; + } + + override size_t nextSize() { + return value_; + } + + override string toString() const { + return to!string(value_); + } + +private: + size_t value_; +} + +class RandomSizeGenerator : SizeGenerator { + this(size_t min, size_t max) { + min_ = min; + max_ = max; + } + + override size_t nextSize() { + return uniform!"[]"(min_, max_); + } + + override string toString() const { + return format("rand(%s, %s)", min_, max_); + } + + size_t min() const @property { + return min_; + } + + size_t max() const @property { + return max_; + } + +private: + size_t min_; + size_t max_; +} + + +/* + * Classes to set up coupled transports + */ + +/** + * Helper class to represent a coupled pair of transports. + * + * Data written to the output transport can be read from the input transport. + * + * This is used as the base class for the various coupled transport + * implementations. It shouldn't be used directly. + */ +class CoupledTransports(Transport) if (isTTransport!Transport) { + Transport input; + Transport output; +} + +template isCoupledTransports(T) { + static if (is(T _ : CoupledTransports!U, U)) { + enum isCoupledTransports = true; + } else { + enum isCoupledTransports = false; + } +} + +/** + * Helper template class for creating coupled transports that wrap + * another transport. + */ +class CoupledWrapperTransports(WrapperTransport, InnerCoupledTransports) if ( + isTTransport!WrapperTransport && isCoupledTransports!InnerCoupledTransports +) : CoupledTransports!WrapperTransport { + this() { + inner_ = new InnerCoupledTransports(); + if (inner_.input) { + input = new WrapperTransport(inner_.input); + } + if (inner_.output) { + output = new WrapperTransport(inner_.output); + } + } + + ~this() { + destroy(inner_); + } + +private: + InnerCoupledTransports inner_; +} + +import thrift.internal.codegen : PApply; +alias PApply!(CoupledWrapperTransports, TBufferedTransport) CoupledBufferedTransports; +alias PApply!(CoupledWrapperTransports, TFramedTransport) CoupledFramedTransports; +alias PApply!(CoupledWrapperTransports, TZlibTransport) CoupledZlibTransports; + +/** + * Coupled TMemoryBuffers. + */ +class CoupledMemoryBuffers : CoupledTransports!TMemoryBuffer { + this() { + buf = new TMemoryBuffer; + input = buf; + output = buf; + } + + TMemoryBuffer buf; +} + +/** + * Coupled TSockets. + */ +class CoupledSocketTransports : CoupledTransports!TSocket { + this() { + auto sockets = socketPair(); + input = new TSocket(sockets[0]); + output = new TSocket(sockets[1]); + } + + ~this() { + input.close(); + output.close(); + } +} + +/** + * Coupled TFileTransports + */ +class CoupledFileTransports : CoupledTransports!TTransport { + this() { + // We actually need the file name of the temp file here, so we can't just + // use the usual tempfile facilities. + do { + fileName_ = tmpDir ~ "/thrift.transport_test." ~ to!string(rndGen().front); + rndGen().popFront(); + } while (std.file.exists(fileName_)); + + writefln("Using temp file: %s", fileName_); + + auto writer = new TFileWriterTransport(fileName_); + writer.open(); + output = writer; + + // Wait until the file has been created. + writer.flush(); + + auto reader = new TFileReaderTransport(fileName_); + reader.open(); + reader.readTimeout(dur!"msecs"(-1)); + input = reader; + } + + ~this() { + input.close(); + output.close(); + std.file.remove(fileName_); + } + + static string tmpDir; + +private: + string fileName_; +} + + +/* + * Test functions + */ + +/** + * Test interleaved write and read calls. + * + * Generates a buffer totalSize bytes long, then writes it to the transport, + * and verifies the written data can be read back correctly. + * + * Mode of operation: + * - call wChunkGenerator to figure out how large of a chunk to write + * - call wSizeGenerator to get the size for individual write() calls, + * and do this repeatedly until the entire chunk is written. + * - call rChunkGenerator to figure out how large of a chunk to read + * - call rSizeGenerator to get the size for individual read() calls, + * and do this repeatedly until the entire chunk is read. + * - repeat until the full buffer is written and read back, + * then compare the data read back against the original buffer + * + * + * - If any of the size generators return 0, this means to use the maximum + * possible size. + * + * - If maxOutstanding is non-zero, write chunk sizes will be chosen such that + * there are never more than maxOutstanding bytes waiting to be read back. + */ +void testReadWrite(CoupledTransports)( + size_t totalSize, + SizeGenerator wSizeGenerator, + SizeGenerator rSizeGenerator, + SizeGenerator wChunkGenerator, + SizeGenerator rChunkGenerator, + size_t maxOutstanding +) if ( + isCoupledTransports!CoupledTransports +) { + scope transports = new CoupledTransports; + assert(transports.input); + assert(transports.output); + + auto wbuf = new ubyte[totalSize]; + auto rbuf = new ubyte[totalSize]; + + // Store some data in wbuf. + foreach (i, ref b; wbuf) { + b = i & 0xff; + } + + size_t totalWritten; + size_t totalRead; + while (totalRead < totalSize) { + // Determine how large a chunk of data to write. + auto wChunkSize = wChunkGenerator.nextSize(); + if (wChunkSize == 0 || wChunkSize > totalSize - totalWritten) { + wChunkSize = totalSize - totalWritten; + } + + // Make sure (totalWritten - totalRead) + wChunkSize is less than + // maxOutstanding. + if (maxOutstanding > 0 && + wChunkSize > maxOutstanding - (totalWritten - totalRead)) { + wChunkSize = maxOutstanding - (totalWritten - totalRead); + } + + // Write the chunk. + size_t chunkWritten = 0; + while (chunkWritten < wChunkSize) { + auto writeSize = wSizeGenerator.nextSize(); + if (writeSize == 0 || writeSize > wChunkSize - chunkWritten) { + writeSize = wChunkSize - chunkWritten; + } + + transports.output.write(wbuf[totalWritten .. totalWritten + writeSize]); + chunkWritten += writeSize; + totalWritten += writeSize; + } + + // Flush the data, so it will be available in the read transport + // Don't flush if wChunkSize is 0. (This should only happen if + // totalWritten == totalSize already, and we're only reading now.) + if (wChunkSize > 0) { + transports.output.flush(); + } + + // Determine how large a chunk of data to read back. + auto rChunkSize = rChunkGenerator.nextSize(); + if (rChunkSize == 0 || rChunkSize > totalWritten - totalRead) { + rChunkSize = totalWritten - totalRead; + } + + // Read the chunk. + size_t chunkRead; + while (chunkRead < rChunkSize) { + auto readSize = rSizeGenerator.nextSize(); + if (readSize == 0 || readSize > rChunkSize - chunkRead) { + readSize = rChunkSize - chunkRead; + } + + size_t bytesRead; + try { + bytesRead = transports.input.read( + rbuf[totalRead .. totalRead + readSize]); + } catch (TTransportException e) { + throw new Exception(format(`read(pos = %s, size = %s) threw ` ~ + `exception "%s"; written so far: %s/%s bytes`, totalRead, readSize, + e.msg, totalWritten, totalSize)); + } + + enforce(bytesRead > 0, format(`read(pos = %s, size = %s) returned %s; ` ~ + `written so far: %s/%s bytes`, totalRead, readSize, bytesRead, + totalWritten, totalSize)); + + chunkRead += bytesRead; + totalRead += bytesRead; + } + } + + // make sure the data read back is identical to the data written + if (rbuf != wbuf) { + stderr.writefln("%s vs. %s", wbuf[$ - 4 .. $], rbuf[$ - 4 .. $]); + stderr.writefln("rbuf: %s vs. wbuf: %s", rbuf.length, wbuf.length); + } + enforce(rbuf == wbuf); +} + +void testReadPartAvailable(CoupledTransports)() if ( + isCoupledTransports!CoupledTransports +) { + scope transports = new CoupledTransports; + assert(transports.input); + assert(transports.output); + + ubyte[10] writeBuf = 'a'; + ubyte[10] readBuf; + + // Attemping to read 10 bytes when only 9 are available should return 9 + // immediately. + transports.output.write(writeBuf[0 .. 9]); + transports.output.flush(); + + auto t = Trigger(dur!"seconds"(3), transports.output, 1); + auto bytesRead = transports.input.read(readBuf); + enforce(t.fired == 0); + enforce(bytesRead == 9); +} + +void testReadPartialMidframe(CoupledTransports)() if ( + isCoupledTransports!CoupledTransports +) { + scope transports = new CoupledTransports; + assert(transports.input); + assert(transports.output); + + ubyte[13] writeBuf = 'a'; + ubyte[14] readBuf; + + // Attempt to read 10 bytes, when only 9 are available, but after we have + // already read part of the data that is available. This exercises a + // different code path for several of the transports. + // + // For transports that add their own framing (e.g., TFramedTransport and + // TFileTransport), the two flush calls break up the data in to a 10 byte + // frame and a 3 byte frame. The first read then puts us partway through the + // first frame, and then we attempt to read past the end of that frame, and + // through the next frame, too. + // + // For buffered transports that perform read-ahead (e.g., + // TBufferedTransport), the read-ahead will most likely see all 13 bytes + // written on the first read. The next read will then attempt to read past + // the end of the read-ahead buffer. + // + // Flush 10 bytes, then 3 bytes. This creates 2 separate frames for + // transports that track framing internally. + transports.output.write(writeBuf[0 .. 10]); + transports.output.flush(); + transports.output.write(writeBuf[10 .. 13]); + transports.output.flush(); + + // Now read 4 bytes, so that we are partway through the written data. + auto bytesRead = transports.input.read(readBuf[0 .. 4]); + enforce(bytesRead == 4); + + // Now attempt to read 10 bytes. Only 9 more are available. + // + // We should be able to get all 9 bytes, but it might take multiple read + // calls, since it is valid for read() to return fewer bytes than requested. + // (Most transports do immediately return 9 bytes, but the framing transports + // tend to only return to the end of the current frame, which is 6 bytes in + // this case.) + size_t totalRead = 0; + while (totalRead < 9) { + auto t = Trigger(dur!"seconds"(3), transports.output, 1); + bytesRead = transports.input.read(readBuf[4 + totalRead .. 14]); + enforce(t.fired == 0); + enforce(bytesRead > 0); + totalRead += bytesRead; + enforce(totalRead <= 9); + } + + enforce(totalRead == 9); +} + +void testBorrowPartAvailable(CoupledTransports)() if ( + isCoupledTransports!CoupledTransports +) { + scope transports = new CoupledTransports; + assert(transports.input); + assert(transports.output); + + ubyte[9] writeBuf = 'a'; + ubyte[10] readBuf; + + // Attemping to borrow 10 bytes when only 9 are available should return NULL + // immediately. + transports.output.write(writeBuf); + transports.output.flush(); + + auto t = Trigger(dur!"seconds"(3), transports.output, 1); + auto borrowLen = readBuf.length; + auto borrowedBuf = transports.input.borrow(readBuf.ptr, borrowLen); + enforce(t.fired == 0); + enforce(borrowedBuf is null); +} + +void testReadNoneAvailable(CoupledTransports)() if ( + isCoupledTransports!CoupledTransports +) { + scope transports = new CoupledTransports; + assert(transports.input); + assert(transports.output); + + // Attempting to read when no data is available should either block until + // some data is available, or fail immediately. (e.g., TSocket blocks, + // TMemoryBuffer just fails.) + // + // If the transport blocks, it should succeed once some data is available, + // even if less than the amount requested becomes available. + ubyte[10] readBuf; + + auto t = Trigger(dur!"seconds"(1), transports.output, 2); + t.add(dur!"seconds"(1), transports.output, 8); + + auto bytesRead = transports.input.read(readBuf); + if (bytesRead == 0) { + enforce(t.fired == 0); + } else { + enforce(t.fired == 1); + enforce(bytesRead == 2); + } +} + +void testBorrowNoneAvailable(CoupledTransports)() if ( + isCoupledTransports!CoupledTransports +) { + scope transports = new CoupledTransports; + assert(transports.input); + assert(transports.output); + + ubyte[16] writeBuf = 'a'; + + // Attempting to borrow when no data is available should fail immediately + auto t = Trigger(dur!"seconds"(1), transports.output, 10); + + auto borrowLen = 10; + auto borrowedBuf = transports.input.borrow(null, borrowLen); + enforce(borrowedBuf is null); + enforce(t.fired == 0); +} + + +void doRwTest(CoupledTransports)( + size_t totalSize, + SizeGenerator wSizeGen, + SizeGenerator rSizeGen, + SizeGenerator wChunkSizeGen = new ConstantSizeGenerator(0), + SizeGenerator rChunkSizeGen = new ConstantSizeGenerator(0), + size_t maxOutstanding = 0 +) if ( + isCoupledTransports!CoupledTransports +) { + totalSize = cast(size_t)(totalSize * g_sizeMultiplier); + + scope(failure) { + writefln("Test failed for %s: testReadWrite(%s, %s, %s, %s, %s, %s)", + CoupledTransports.stringof, totalSize, wSizeGen, rSizeGen, + wChunkSizeGen, rChunkSizeGen, maxOutstanding); + } + + testReadWrite!CoupledTransports(totalSize, wSizeGen, rSizeGen, + wChunkSizeGen, rChunkSizeGen, maxOutstanding); +} + +void doBlockingTest(CoupledTransports)() if ( + isCoupledTransports!CoupledTransports +) { + void writeFailure(string name) { + writefln("Test failed for %s: %s()", CoupledTransports.stringof, name); + } + + { + scope(failure) writeFailure("testReadPartAvailable"); + testReadPartAvailable!CoupledTransports(); + } + + { + scope(failure) writeFailure("testReadPartialMidframe"); + testReadPartialMidframe!CoupledTransports(); + } + + { + scope(failure) writeFailure("testReadNoneAvaliable"); + testReadNoneAvailable!CoupledTransports(); + } + + { + scope(failure) writeFailure("testBorrowPartAvailable"); + testBorrowPartAvailable!CoupledTransports(); + } + + { + scope(failure) writeFailure("testBorrowNoneAvailable"); + testBorrowNoneAvailable!CoupledTransports(); + } +} + +SizeGenerator getGenerator(T)(T t) { + static if (is(T : SizeGenerator)) { + return t; + } else { + return new ConstantSizeGenerator(t); + } +} + +template WrappedTransports(T) if (isCoupledTransports!T) { + alias TypeTuple!( + T, + CoupledBufferedTransports!T, + CoupledFramedTransports!T, + CoupledZlibTransports!T + ) WrappedTransports; +} + +void testRw(C, R, S)( + size_t totalSize, + R wSize, + S rSize +) if ( + isCoupledTransports!C && is(typeof(getGenerator(wSize))) && + is(typeof(getGenerator(rSize))) +) { + testRw!C(totalSize, wSize, rSize, 0, 0, 0); +} + +void testRw(C, R, S, T, U)( + size_t totalSize, + R wSize, + S rSize, + T wChunkSize, + U rChunkSize, + size_t maxOutstanding = 0 +) if ( + isCoupledTransports!C && is(typeof(getGenerator(wSize))) && + is(typeof(getGenerator(rSize))) && is(typeof(getGenerator(wChunkSize))) && + is(typeof(getGenerator(rChunkSize))) +) { + foreach (T; WrappedTransports!C) { + doRwTest!T( + totalSize, + getGenerator(wSize), + getGenerator(rSize), + getGenerator(wChunkSize), + getGenerator(rChunkSize), + maxOutstanding + ); + } +} + +void testBlocking(C)() if (isCoupledTransports!C) { + foreach (T; WrappedTransports!C) { + doBlockingTest!T(); + } +} + +// A quick hack, for the sake of brevity… +float g_sizeMultiplier = 1; + +version (Posix) { + immutable defaultTempDir = "/tmp"; +} else version (Windows) { + import core.sys.windows.windows; + extern(Windows) DWORD GetTempPathA(DWORD nBufferLength, LPTSTR lpBuffer); + + string defaultTempDir() @property { + char[MAX_PATH + 1] dir; + enforce(GetTempPathA(dir.length, dir.ptr)); + return to!string(dir.ptr)[0 .. $ - 1]; + } +} else static assert(false); + +void main(string[] args) { + int seed = unpredictableSeed(); + string tmpDir = defaultTempDir; + + getopt(args, "seed", &seed, "size-multiplier", &g_sizeMultiplier, + "tmp-dir", &tmpDir); + enforce(g_sizeMultiplier >= 0, "Size multiplier must not be negative."); + + writefln("Using seed: %s", seed); + rndGen().seed(seed); + CoupledFileTransports.tmpDir = tmpDir; + + auto rand4k = new RandomSizeGenerator(1, 4096); + + /* + * We do the basically the same set of tests for each transport type, + * although we tweak the parameters in some places. + */ + + // TMemoryBuffer tests + testRw!CoupledMemoryBuffers(1024 * 1024, 0, 0); + testRw!CoupledMemoryBuffers(1024 * 256, rand4k, rand4k); + testRw!CoupledMemoryBuffers(1024 * 256, 167, 163); + testRw!CoupledMemoryBuffers(1024 * 16, 1, 1); + + testRw!CoupledMemoryBuffers(1024 * 256, 0, 0, rand4k, rand4k); + testRw!CoupledMemoryBuffers(1024 * 256, rand4k, rand4k, rand4k, rand4k); + testRw!CoupledMemoryBuffers(1024 * 256, 167, 163, rand4k, rand4k); + testRw!CoupledMemoryBuffers(1024 * 16, 1, 1, rand4k, rand4k); + + testBlocking!CoupledMemoryBuffers(); + + // TSocket tests + enum socketMaxOutstanding = 4096; + testRw!CoupledSocketTransports(1024 * 1024, 0, 0, + 0, 0, socketMaxOutstanding); + testRw!CoupledSocketTransports(1024 * 256, rand4k, rand4k, + 0, 0, socketMaxOutstanding); + testRw!CoupledSocketTransports(1024 * 256, 167, 163, + 0, 0, socketMaxOutstanding); + // Doh. Apparently writing to a socket has some additional overhead for + // each send() call. If we have more than ~400 outstanding 1-byte write + // requests, additional send() calls start blocking. + testRw!CoupledSocketTransports(1024 * 16, 1, 1, + 0, 0, 250); + testRw!CoupledSocketTransports(1024 * 256, 0, 0, + rand4k, rand4k, socketMaxOutstanding); + testRw!CoupledSocketTransports(1024 * 256, rand4k, rand4k, + rand4k, rand4k, socketMaxOutstanding); + testRw!CoupledSocketTransports(1024 * 256, 167, 163, + rand4k, rand4k, socketMaxOutstanding); + testRw!CoupledSocketTransports(1024 * 16, 1, 1, + rand4k, rand4k, 250); + + testBlocking!CoupledSocketTransports(); + + // File transport tests. + + // Cannot write more than the frame size at once. + enum maxWriteAtOnce = 1024 * 1024 * 16 - 4; + + testRw!CoupledFileTransports(1024 * 1024, maxWriteAtOnce, 0); + testRw!CoupledFileTransports(1024 * 256, rand4k, rand4k); + testRw!CoupledFileTransports(1024 * 256, 167, 163); + testRw!CoupledFileTransports(1024 * 16, 1, 1); + + testRw!CoupledFileTransports(1024 * 256, 0, 0, rand4k, rand4k); + testRw!CoupledFileTransports(1024 * 256, rand4k, rand4k, rand4k, rand4k); + testRw!CoupledFileTransports(1024 * 256, 167, 163, rand4k, rand4k); + testRw!CoupledFileTransports(1024 * 16, 1, 1, rand4k, rand4k); + + testBlocking!CoupledFileTransports(); +} + + +/* + * Timer handling code for use in tests that check the transport blocking + * semantics. + * + * The implementation has been hacked together in a hurry and wastes a lot of + * threads, but speed should not be the concern here. + */ + +struct Trigger { + this(Duration timeout, TTransport transport, size_t writeLength) { + mutex_ = new Mutex; + cancelCondition_ = new Condition(mutex_); + info_ = new Info(timeout, transport, writeLength); + startThread(); + } + + ~this() { + synchronized (mutex_) { + info_ = null; + cancelCondition_.notifyAll(); + } + if (thread_) thread_.join(); + } + + @disable this(this) { assert(0); } + + void add(Duration timeout, TTransport transport, size_t writeLength) { + synchronized (mutex_) { + auto info = new Info(timeout, transport, writeLength); + if (info_) { + auto prev = info_; + while (prev.next) prev = prev.next; + prev.next = info; + } else { + info_ = info; + startThread(); + } + } + } + + @property short fired() { + return atomicLoad(fired_); + } + +private: + void timerThread() { + // KLUDGE: Make sure the std.concurrency mbox is initialized on the timer + // thread to be able to unblock the file transport. + import std.concurrency; + thisTid; + + synchronized (mutex_) { + while (info_) { + auto cancelled = cancelCondition_.wait(info_.timeout); + if (cancelled) { + info_ = null; + break; + } + + atomicOp!"+="(fired_, 1); + + // Write some data to the transport to unblock it. + auto buf = new ubyte[info_.writeLength]; + buf[] = 'b'; + info_.transport.write(buf); + info_.transport.flush(); + + info_ = info_.next; + } + } + + thread_ = null; + } + + void startThread() { + thread_ = new Thread(&timerThread); + thread_.start(); + } + + struct Info { + this(Duration timeout, TTransport transport, size_t writeLength) { + this.timeout = timeout; + this.transport = transport; + this.writeLength = writeLength; + } + + Duration timeout; + TTransport transport; + size_t writeLength; + Info* next; + } + + Info* info_; + Thread thread_; + shared short fired_; + + import core.sync.mutex; + Mutex mutex_; + import core.sync.condition; + Condition cancelCondition_; +} diff --git a/src/jaegertracing/thrift/lib/dart/.analysis_options b/src/jaegertracing/thrift/lib/dart/.analysis_options new file mode 100644 index 000000000..a10d4c5a0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/.analysis_options @@ -0,0 +1,2 @@ +analyzer: + strong-mode: true diff --git a/src/jaegertracing/thrift/lib/dart/LICENSE b/src/jaegertracing/thrift/lib/dart/LICENSE new file mode 100644 index 000000000..4eacb6431 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/LICENSE @@ -0,0 +1,16 @@ +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. diff --git a/src/jaegertracing/thrift/lib/dart/Makefile.am b/src/jaegertracing/thrift/lib/dart/Makefile.am new file mode 100644 index 000000000..373a883d6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/Makefile.am @@ -0,0 +1,39 @@ +# +# 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. +# + +all-local: + $(DARTPUB) get + +clean-local: + $(RM) -r .pub + find . -type d -name ".dart_tool" | xargs $(RM) -r + find . -type f -name ".packages" | xargs $(RM) + find . -type d -name "packages" | xargs $(RM) -r + +check-local: all + +dist-hook: + $(RM) -r $(distdir)/.pub + find $(distdir) -type d -name ".dart_tool" | xargs $(RM) -r + find $(distdir) -type f -name ".packages" | xargs $(RM) + find $(distdir) -type d -name "packages" | xargs $(RM) -r + +EXTRA_DIST = \ + .analysis_options + diff --git a/src/jaegertracing/thrift/lib/dart/README.md b/src/jaegertracing/thrift/lib/dart/README.md new file mode 100644 index 000000000..4c3029124 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/README.md @@ -0,0 +1,26 @@ +Thrift Dart Library + +License +======= + +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. + +Using Thrift with Dart +==================== + +Dart 1.24.3 or newer is required diff --git a/src/jaegertracing/thrift/lib/dart/coding_standards.md b/src/jaegertracing/thrift/lib/dart/coding_standards.md new file mode 100644 index 000000000..62f600365 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/coding_standards.md @@ -0,0 +1,6 @@ +# Dart Coding Standards + +### Please follow: + * [Thrift General Coding Standards](/doc/coding_standards.md) + * [Use dartfmt](https://www.dartlang.org/tools/dartfmt/) and follow the + [Dart Style Guide](https://www.dartlang.org/articles/style-guide/) diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/browser/t_web_socket.dart b/src/jaegertracing/thrift/lib/dart/lib/src/browser/t_web_socket.dart new file mode 100644 index 000000000..dac9ffdde --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/browser/t_web_socket.dart @@ -0,0 +1,129 @@ +/// 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. + +library thrift.src.browser; + +import 'dart:async'; +import 'package:dart2_constant/convert.dart' show base64; +import 'dart:html' show CloseEvent; +import 'dart:html' show Event; +import 'dart:html' show MessageEvent; +import 'dart:html' show WebSocket; +import 'dart:typed_data' show Uint8List; + +import 'package:thrift/thrift.dart'; + +/// A [TSocket] backed by a [WebSocket] from dart:html +class TWebSocket implements TSocket { + final Uri url; + + final StreamController<TSocketState> _onStateController; + Stream<TSocketState> get onState => _onStateController.stream; + + final StreamController<Object> _onErrorController; + Stream<Object> get onError => _onErrorController.stream; + + final StreamController<Uint8List> _onMessageController; + Stream<Uint8List> get onMessage => _onMessageController.stream; + + final List<Uint8List> _requests = []; + + TWebSocket(this.url) + : _onStateController = new StreamController.broadcast(), + _onErrorController = new StreamController.broadcast(), + _onMessageController = new StreamController.broadcast() { + if (url == null || !url.hasAuthority || !url.hasPort) { + throw new ArgumentError('Invalid url'); + } + } + + WebSocket _socket; + + bool get isOpen => _socket != null && _socket.readyState == WebSocket.OPEN; + + bool get isClosed => + _socket == null || _socket.readyState == WebSocket.CLOSED; + + Future open() { + if (!isClosed) { + throw new TTransportError( + TTransportErrorType.ALREADY_OPEN, 'Socket already connected'); + } + + _socket = new WebSocket(url.toString()); + _socket.onError.listen(_onError); + _socket.onOpen.listen(_onOpen); + _socket.onClose.listen(_onClose); + _socket.onMessage.listen(_onMessage); + + return _socket.onOpen.first; + } + + Future close() { + if (_socket != null) { + _socket.close(); + return _socket.onClose.first; + } else { + return new Future.value(); + } + } + + void send(Uint8List data) { + _requests.add(data); + _sendRequests(); + } + + void _sendRequests() { + while (isOpen && _requests.isNotEmpty) { + Uint8List data = _requests.removeAt(0); + _socket.sendString(base64.encode(data)); + } + } + + void _onOpen(Event event) { + _onStateController.add(TSocketState.OPEN); + _sendRequests(); + } + + void _onClose(CloseEvent event) { + _socket = null; + + if (_requests.isNotEmpty) { + _onErrorController + .add(new StateError('Socket was closed with pending requests')); + } + _requests.clear(); + + _onStateController.add(TSocketState.CLOSED); + } + + void _onMessage(MessageEvent message) { + try { + Uint8List data = new Uint8List.fromList(base64.decode(message.data)); + _onMessageController.add(data); + } on FormatException catch (_) { + var error = new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Expected a Base 64 encoded string."); + _onErrorController.add(error); + } + } + + void _onError(Event event) { + close(); + _onErrorController.add(event.toString()); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/console/t_tcp_socket.dart b/src/jaegertracing/thrift/lib/dart/lib/src/console/t_tcp_socket.dart new file mode 100644 index 000000000..b71480334 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/console/t_tcp_socket.dart @@ -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. + +library thrift.src.console.t_tcp_socket; + +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data' show Uint8List; + +import 'package:thrift/thrift.dart'; + +/// A [TSocket] backed by a [Socket] from dart:io +class TTcpSocket implements TSocket { + final StreamController<TSocketState> _onStateController; + Stream<TSocketState> get onState => _onStateController.stream; + + final StreamController<Object> _onErrorController; + Stream<Object> get onError => _onErrorController.stream; + + final StreamController<Uint8List> _onMessageController; + Stream<Uint8List> get onMessage => _onMessageController.stream; + + TTcpSocket(Socket socket) + : _onStateController = new StreamController.broadcast(), + _onErrorController = new StreamController.broadcast(), + _onMessageController = new StreamController.broadcast() { + if (socket == null) { + throw new ArgumentError.notNull('socket'); + } + + _socket = socket; + _socket.listen(_onMessage, onError: _onError, onDone: close); + } + + Socket _socket; + + bool get isOpen => _socket != null; + + bool get isClosed => _socket == null; + + Future open() async { + _onStateController.add(TSocketState.OPEN); + } + + Future close() async { + if (_socket != null) { + await _socket.close(); + _socket = null; + } + + _onStateController.add(TSocketState.CLOSED); + } + + void send(Uint8List data) { + _socket.add(data); + } + + void _onMessage(List<int> message) { + Uint8List data = new Uint8List.fromList(message); + _onMessageController.add(data); + } + + void _onError(Object error) { + close(); + _onErrorController.add('$error'); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/console/t_web_socket.dart b/src/jaegertracing/thrift/lib/dart/lib/src/console/t_web_socket.dart new file mode 100644 index 000000000..c938a966f --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/console/t_web_socket.dart @@ -0,0 +1,88 @@ +/// 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. + +library thrift.src.console.t_web_socket; + +import 'dart:async'; +import 'package:dart2_constant/convert.dart' show base64; +import 'dart:io'; +import 'dart:typed_data' show Uint8List; + +import 'package:thrift/thrift.dart'; + +/// A [TSocket] backed by a [WebSocket] from dart:io +class TWebSocket implements TSocket { + final StreamController<TSocketState> _onStateController; + Stream<TSocketState> get onState => _onStateController.stream; + + final StreamController<Object> _onErrorController; + Stream<Object> get onError => _onErrorController.stream; + + final StreamController<Uint8List> _onMessageController; + Stream<Uint8List> get onMessage => _onMessageController.stream; + + TWebSocket(WebSocket socket) + : _onStateController = new StreamController.broadcast(), + _onErrorController = new StreamController.broadcast(), + _onMessageController = new StreamController.broadcast() { + if (socket == null) { + throw new ArgumentError.notNull('socket'); + } + + _socket = socket; + _socket.listen(_onMessage, onError: _onError, onDone: close); + } + + WebSocket _socket; + + bool get isOpen => _socket != null; + + bool get isClosed => _socket == null; + + Future open() async { + _onStateController.add(TSocketState.OPEN); + } + + Future close() async { + if (_socket != null) { + await _socket.close(); + _socket = null; + } + + _onStateController.add(TSocketState.CLOSED); + } + + void send(Uint8List data) { + _socket.add(base64.encode(data)); + } + + void _onMessage(String message) { + try { + Uint8List data = new Uint8List.fromList(base64.decode(message)); + _onMessageController.add(data); + } on FormatException catch (_) { + var error = new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Expected a Base 64 encoded string."); + _onErrorController.add(error); + } + } + + void _onError(Object error) { + close(); + _onErrorController.add('$error'); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_binary_protocol.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_binary_protocol.dart new file mode 100644 index 000000000..a785d811c --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_binary_protocol.dart @@ -0,0 +1,281 @@ +/// 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. + +part of thrift; + +class TBinaryProtocolFactory implements TProtocolFactory<TBinaryProtocol> { + TBinaryProtocolFactory({this.strictRead: false, this.strictWrite: true}); + + final bool strictRead; + final bool strictWrite; + + TBinaryProtocol getProtocol(TTransport transport) { + return new TBinaryProtocol(transport, + strictRead: strictRead, strictWrite: strictWrite); + } +} + +/// Binary protocol implementation for Thrift. +/// +/// Adapted from the C# version. +class TBinaryProtocol extends TProtocol { + static const int VERSION_MASK = 0xffff0000; + static const int VERSION_1 = 0x80010000; + + static const Utf8Codec _utf8Codec = const Utf8Codec(); + + final bool strictRead; + final bool strictWrite; + + TBinaryProtocol(TTransport transport, + {this.strictRead: false, this.strictWrite: true}) + : super(transport); + + /// write + void writeMessageBegin(TMessage message) { + if (strictWrite) { + int version = VERSION_1 | message.type; + writeI32(version); + writeString(message.name); + writeI32(message.seqid); + } else { + writeString(message.name); + writeByte(message.type); + writeI32(message.seqid); + } + } + + void writeMessageEnd() {} + + void writeStructBegin(TStruct struct) {} + + void writeStructEnd() {} + + void writeFieldBegin(TField field) { + writeByte(field.type); + writeI16(field.id); + } + + void writeFieldEnd() {} + + void writeFieldStop() { + writeByte(TType.STOP); + } + + void writeMapBegin(TMap map) { + writeByte(map.keyType); + writeByte(map.valueType); + writeI32(map.length); + } + + void writeMapEnd() {} + + void writeListBegin(TList list) { + writeByte(list.elementType); + writeI32(list.length); + } + + void writeListEnd() {} + + void writeSetBegin(TSet set) { + writeByte(set.elementType); + writeI32(set.length); + } + + void writeSetEnd() {} + + void writeBool(bool b) { + if (b == null) b = false; + writeByte(b ? 1 : 0); + } + + final ByteData _byteOut = new ByteData(1); + void writeByte(int byte) { + if (byte == null) byte = 0; + _byteOut.setUint8(0, byte); + transport.write(_byteOut.buffer.asUint8List(), 0, 1); + } + + final ByteData _i16Out = new ByteData(2); + void writeI16(int i16) { + if (i16 == null) i16 = 0; + _i16Out.setInt16(0, i16); + transport.write(_i16Out.buffer.asUint8List(), 0, 2); + } + + final ByteData _i32Out = new ByteData(4); + void writeI32(int i32) { + if (i32 == null) i32 = 0; + _i32Out.setInt32(0, i32); + transport.write(_i32Out.buffer.asUint8List(), 0, 4); + } + + final Uint8List _i64Out = new Uint8List(8); + void writeI64(int i64) { + if (i64 == null) i64 = 0; + var i = new Int64(i64); + var bts = i.toBytes(); + for (var j = 0; j < 8; j++) { + _i64Out[j] = bts[8 - j - 1]; + } + transport.write(_i64Out, 0, 8); + } + + void writeString(String s) { + var bytes = s != null ? _utf8Codec.encode(s) : new Uint8List.fromList([]); + writeI32(bytes.length); + transport.write(bytes, 0, bytes.length); + } + + final ByteData _doubleOut = new ByteData(8); + void writeDouble(double d) { + if (d == null) d = 0.0; + _doubleOut.setFloat64(0, d); + transport.write(_doubleOut.buffer.asUint8List(), 0, 8); + } + + void writeBinary(Uint8List bytes) { + var length = bytes.length; + writeI32(length); + transport.write(bytes, 0, length); + } + + /// read + TMessage readMessageBegin() { + String name; + int type; + int seqid; + + int size = readI32(); + if (size < 0) { + int version = size & VERSION_MASK; + if (version != VERSION_1) { + throw new TProtocolError(TProtocolErrorType.BAD_VERSION, + "Bad version in readMessageBegin: $version"); + } + type = size & 0x000000ff; + name = readString(); + seqid = readI32(); + } else { + if (strictRead) { + throw new TProtocolError(TProtocolErrorType.BAD_VERSION, + "Missing version in readMessageBegin"); + } + name = _readString(size); + type = readByte(); + seqid = readI32(); + } + return new TMessage(name, type, seqid); + } + + void readMessageEnd() {} + + TStruct readStructBegin() { + return new TStruct(); + } + + void readStructEnd() {} + + TField readFieldBegin() { + String name = ""; + int type = readByte(); + int id = type != TType.STOP ? readI16() : 0; + + return new TField(name, type, id); + } + + void readFieldEnd() {} + + TMap readMapBegin() { + int keyType = readByte(); + int valueType = readByte(); + int length = readI32(); + + return new TMap(keyType, valueType, length); + } + + void readMapEnd() {} + + TList readListBegin() { + int elementType = readByte(); + int length = readI32(); + + return new TList(elementType, length); + } + + void readListEnd() {} + + TSet readSetBegin() { + int elementType = readByte(); + int length = readI32(); + + return new TSet(elementType, length); + } + + void readSetEnd() {} + + bool readBool() => readByte() == 1; + + final Uint8List _byteIn = new Uint8List(1); + int readByte() { + transport.readAll(_byteIn, 0, 1); + return _byteIn.buffer.asByteData().getUint8(0); + } + + final Uint8List _i16In = new Uint8List(2); + int readI16() { + transport.readAll(_i16In, 0, 2); + return _i16In.buffer.asByteData().getInt16(0); + } + + final Uint8List _i32In = new Uint8List(4); + int readI32() { + transport.readAll(_i32In, 0, 4); + return _i32In.buffer.asByteData().getInt32(0); + } + + final Uint8List _i64In = new Uint8List(8); + int readI64() { + transport.readAll(_i64In, 0, 8); + var i = new Int64.fromBytesBigEndian(_i64In); + return i.toInt(); + } + + final Uint8List _doubleIn = new Uint8List(8); + double readDouble() { + transport.readAll(_doubleIn, 0, 8); + return _doubleIn.buffer.asByteData().getFloat64(0); + } + + String readString() { + int size = readI32(); + return _readString(size); + } + + String _readString(int size) { + Uint8List stringIn = new Uint8List(size); + transport.readAll(stringIn, 0, size); + return _utf8Codec.decode(stringIn); + } + + Uint8List readBinary() { + int length = readI32(); + Uint8List binaryIn = new Uint8List(length); + transport.readAll(binaryIn, 0, length); + return binaryIn; + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_compact_protocol.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_compact_protocol.dart new file mode 100644 index 000000000..ee8094f8e --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_compact_protocol.dart @@ -0,0 +1,470 @@ +/// 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. + +part of thrift; + +class TCompactProtocolFactory implements TProtocolFactory<TCompactProtocol> { + TCompactProtocolFactory(); + + TCompactProtocol getProtocol(TTransport transport) { + return new TCompactProtocol(transport); + } +} + +/// Compact protocol implementation for Thrift. +/// +/// Use of fixnum library is required due to bugs like +/// https://github.com/dart-lang/sdk/issues/15361 +/// +/// Adapted from the Java version. +class TCompactProtocol extends TProtocol { + static const int PROTOCOL_ID = 0x82; + static const int VERSION = 1; + static const int VERSION_MASK = 0x1f; + static const int TYPE_MASK = 0xE0; + static const int TYPE_BITS = 0x07; + static const int TYPE_SHIFT_AMOUNT = 5; + static final TField TSTOP = new TField("", TType.STOP, 0); + + static const int TYPE_BOOLEAN_TRUE = 0x01; + static const int TYPE_BOOLEAN_FALSE = 0x02; + static const int TYPE_BYTE = 0x03; + static const int TYPE_I16 = 0x04; + static const int TYPE_I32 = 0x05; + static const int TYPE_I64 = 0x06; + static const int TYPE_DOUBLE = 0x07; + static const int TYPE_BINARY = 0x08; + static const int TYPE_LIST = 0x09; + static const int TYPE_SET = 0x0A; + static const int TYPE_MAP = 0x0B; + static const int TYPE_STRUCT = 0x0C; + + static final List<int> _typeMap = new List.unmodifiable(new List(16) + ..[TType.STOP] = TType.STOP + ..[TType.BOOL] = TYPE_BOOLEAN_TRUE + ..[TType.BYTE] = TYPE_BYTE + ..[TType.I16] = TYPE_I16 + ..[TType.I32] = TYPE_I32 + ..[TType.I64] = TYPE_I64 + ..[TType.DOUBLE] = TYPE_DOUBLE + ..[TType.STRING] = TYPE_BINARY + ..[TType.LIST] = TYPE_LIST + ..[TType.SET] = TYPE_SET + ..[TType.MAP] = TYPE_MAP + ..[TType.STRUCT] = TYPE_STRUCT); + + static const Utf8Codec _utf8Codec = const Utf8Codec(); + + // Pretend this is a stack + DoubleLinkedQueue<int> _lastField = new DoubleLinkedQueue<int>(); + int _lastFieldId = 0; + + TField _booleanField = null; + bool _boolValue = null; + + final Uint8List tempList = new Uint8List(10); + final ByteData tempBD = new ByteData(10); + + TCompactProtocol(TTransport transport) : super(transport); + + /// Write + void writeMessageBegin(TMessage message) { + writeByte(PROTOCOL_ID); + writeByte((VERSION & VERSION_MASK) | + ((message.type << TYPE_SHIFT_AMOUNT) & TYPE_MASK)); + _writeVarInt32(new Int32(message.seqid)); + writeString(message.name); + } + + void writeMessageEnd() {} + + void writeStructBegin(TStruct struct) { + _lastField.addLast(_lastFieldId); + _lastFieldId = 0; + } + + void writeStructEnd() { + _lastFieldId = _lastField.removeLast(); + } + + void writeFieldBegin(TField field) { + if (field.type == TType.BOOL) { + _booleanField = field; + } else { + _writeFieldBegin(field, -1); + } + } + + void _writeFieldBegin(TField field, int typeOverride) { + int typeToWrite = + typeOverride == -1 ? _getCompactType(field.type) : typeOverride; + + if (field.id > _lastFieldId && field.id - _lastFieldId <= 15) { + writeByte((field.id - _lastFieldId) << 4 | typeToWrite); + } else { + writeByte(typeToWrite); + writeI16(field.id); + } + + _lastFieldId = field.id; + } + + void writeFieldEnd() {} + + void writeFieldStop() { + writeByte(TType.STOP); + } + + void writeMapBegin(TMap map) { + if (map.length == 0) { + writeByte(0); + } else { + _writeVarInt32(new Int32(map.length)); + writeByte( + _getCompactType(map.keyType) << 4 | _getCompactType(map.valueType)); + } + } + + void writeMapEnd() {} + + void writeListBegin(TList list) { + _writeCollectionBegin(list.elementType, list.length); + } + + void writeListEnd() {} + + void writeSetBegin(TSet set) { + _writeCollectionBegin(set.elementType, set.length); + } + + void writeSetEnd() {} + + void writeBool(bool b) { + if (b == null) b = false; + if (_booleanField != null) { + _writeFieldBegin( + _booleanField, b ? TYPE_BOOLEAN_TRUE : TYPE_BOOLEAN_FALSE); + _booleanField = null; + } else { + writeByte(b ? TYPE_BOOLEAN_TRUE : TYPE_BOOLEAN_FALSE); + } + } + + void writeByte(int b) { + if (b == null) b = 0; + tempList[0] = b; + transport.write(tempList, 0, 1); + } + + void writeI16(int i16) { + if (i16 == null) i16 = 0; + _writeVarInt32(_int32ToZigZag(new Int32(i16))); + } + + void writeI32(int i32) { + if (i32 == null) i32 = 0; + _writeVarInt32(_int32ToZigZag(new Int32(i32))); + } + + void writeI64(int i64) { + if (i64 == null) i64 = 0; + _writeVarInt64(_int64ToZigZag(new Int64(i64))); + } + + void writeDouble(double d) { + if (d == null) d = 0.0; + tempBD.setFloat64(0, d, Endianness.little); + transport.write(tempBD.buffer.asUint8List(), 0, 8); + } + + void writeString(String str) { + Uint8List bytes = + str != null ? _utf8Codec.encode(str) : new Uint8List.fromList([]); + writeBinary(bytes); + } + + void writeBinary(Uint8List bytes) { + _writeVarInt32(new Int32(bytes.length)); + transport.write(bytes, 0, bytes.length); + } + + void _writeVarInt32(Int32 n) { + int idx = 0; + while (true) { + if ((n & ~0x7F) == 0) { + tempList[idx++] = (n & 0xFF).toInt(); + break; + } else { + tempList[idx++] = (((n & 0x7F) | 0x80) & 0xFF).toInt(); + n = n.shiftRightUnsigned(7); + } + } + transport.write(tempList, 0, idx); + } + + void _writeVarInt64(Int64 n) { + int idx = 0; + while (true) { + if ((n & ~0x7F) == 0) { + tempList[idx++] = (n & 0xFF).toInt(); + break; + } else { + tempList[idx++] = (((n & 0x7F) | 0x80) & 0xFF).toInt(); + n = n.shiftRightUnsigned(7); + } + } + transport.write(tempList, 0, idx); + } + + void _writeCollectionBegin(int elemType, int length) { + if (length <= 14) { + writeByte(length << 4 | _getCompactType(elemType)); + } else { + writeByte(0xF0 | _getCompactType(elemType)); + _writeVarInt32(new Int32(length)); + } + } + + Int32 _int32ToZigZag(Int32 n) { + return (n << 1) ^ (n >> 31); + } + + Int64 _int64ToZigZag(Int64 n) { + return (n << 1) ^ (n >> 63); + } + + /// Read + TMessage readMessageBegin() { + int protocolId = readByte(); + if (protocolId != PROTOCOL_ID) { + throw new TProtocolError(TProtocolErrorType.BAD_VERSION, + 'Expected protocol id $PROTOCOL_ID but got $protocolId'); + } + int versionAndType = readByte(); + int version = versionAndType & VERSION_MASK; + if (version != VERSION) { + throw new TProtocolError(TProtocolErrorType.BAD_VERSION, + 'Expected version $VERSION but got $version'); + } + int type = (versionAndType >> TYPE_SHIFT_AMOUNT) & TYPE_BITS; + int seqId = _readVarInt32().toInt(); + String messageName = readString(); + return new TMessage(messageName, type, seqId); + } + + void readMessageEnd() {} + + TStruct readStructBegin() { + _lastField.addLast(_lastFieldId); + _lastFieldId = 0; + // TODO make this a constant? + return new TStruct(); + } + + void readStructEnd() { + _lastFieldId = _lastField.removeLast(); + } + + TField readFieldBegin() { + int type = readByte(); + if (type == TType.STOP) { + return TSTOP; + } + + int fieldId; + int modifier = (type & 0xF0) >> 4; + if (modifier == 0) { + fieldId = readI16(); + } else { + fieldId = _lastFieldId + modifier; + } + + TField field = new TField('', _getTType(type & 0x0F), fieldId); + if (_isBoolType(type)) { + _boolValue = (type & 0x0F) == TYPE_BOOLEAN_TRUE; + } + + _lastFieldId = field.id; + return field; + } + + void readFieldEnd() {} + + TMap readMapBegin() { + int length = _readVarInt32().toInt(); + _checkNegReadLength(length); + + int keyAndValueType = length == 0 ? 0 : readByte(); + int keyType = _getTType(keyAndValueType >> 4); + int valueType = _getTType(keyAndValueType & 0x0F); + return new TMap(keyType, valueType, length); + } + + void readMapEnd() {} + + TList readListBegin() { + int lengthAndType = readByte(); + int length = (lengthAndType >> 4) & 0x0F; + if (length == 15) { + length = _readVarInt32().toInt(); + } + _checkNegReadLength(length); + int type = _getTType(lengthAndType); + return new TList(type, length); + } + + void readListEnd() {} + + TSet readSetBegin() { + TList tlist = readListBegin(); + return new TSet(tlist.elementType, tlist.length); + } + + void readSetEnd() {} + + bool readBool() { + if (_boolValue != null) { + bool result = _boolValue; + _boolValue = null; + return result; + } + return readByte() == TYPE_BOOLEAN_TRUE; + } + + int readByte() { + transport.readAll(tempList, 0, 1); + return tempList.buffer.asByteData().getUint8(0); + } + + int readI16() { + return _zigzagToInt32(_readVarInt32()).toInt(); + } + + int readI32() { + return _zigzagToInt32(_readVarInt32()).toInt(); + } + + int readI64() { + return _zigzagToInt64(_readVarInt64()).toInt(); + } + + double readDouble() { + transport.readAll(tempList, 0, 8); + return tempList.buffer.asByteData().getFloat64(0, Endianness.little); + } + + String readString() { + int length = _readVarInt32().toInt(); + _checkNegReadLength(length); + + // TODO look at using temp for small strings? + Uint8List buff = new Uint8List(length); + transport.readAll(buff, 0, length); + return _utf8Codec.decode(buff); + } + + Uint8List readBinary() { + int length = _readVarInt32().toInt(); + _checkNegReadLength(length); + + Uint8List buff = new Uint8List(length); + transport.readAll(buff, 0, length); + return buff; + } + + Int32 _readVarInt32() { + Int32 result = Int32.ZERO; + int shift = 0; + while (true) { + Int32 b = new Int32(readByte()); + result |= (b & 0x7f) << shift; + if ((b & 0x80) != 0x80) break; + shift += 7; + } + return result; + } + + Int64 _readVarInt64() { + Int64 result = Int64.ZERO; + int shift = 0; + while (true) { + Int64 b = new Int64(readByte()); + result |= (b & 0x7f) << shift; + if ((b & 0x80) != 0x80) break; + shift += 7; + } + return result; + } + + Int32 _zigzagToInt32(Int32 n) { + return (n.shiftRightUnsigned(1)) ^ -(n & 1); + } + + Int64 _zigzagToInt64(Int64 n) { + return (n.shiftRightUnsigned(1)) ^ -(n & 1); + } + + void _checkNegReadLength(int length) { + if (length < 0) { + throw new TProtocolError( + TProtocolErrorType.NEGATIVE_SIZE, 'Negative length: $length'); + } + } + + int _getCompactType(int ttype) { + return _typeMap[ttype]; + } + + int _getTType(int type) { + switch (type & 0x0F) { + case TType.STOP: + return TType.STOP; + case TYPE_BOOLEAN_FALSE: + case TYPE_BOOLEAN_TRUE: + return TType.BOOL; + case TYPE_BYTE: + return TType.BYTE; + case TYPE_I16: + return TType.I16; + case TYPE_I32: + return TType.I32; + case TYPE_I64: + return TType.I64; + case TYPE_DOUBLE: + return TType.DOUBLE; + case TYPE_BINARY: + return TType.STRING; + case TYPE_LIST: + return TType.LIST; + case TYPE_SET: + return TType.SET; + case TYPE_MAP: + return TType.MAP; + case TYPE_STRUCT: + return TType.STRUCT; + default: + throw new TProtocolError( + TProtocolErrorType.INVALID_DATA, "Unknown type: ${type & 0x0F}"); + } + } + + bool _isBoolType(int b) { + int lowerNibble = b & 0x0F; + return lowerNibble == TYPE_BOOLEAN_TRUE || + lowerNibble == TYPE_BOOLEAN_FALSE; + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_field.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_field.dart new file mode 100644 index 000000000..444b4e57a --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_field.dart @@ -0,0 +1,26 @@ +/// 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. + +part of thrift; + +class TField { + final String name; + final int type; + final int id; + + TField(this.name, this.type, this.id); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_json_protocol.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_json_protocol.dart new file mode 100644 index 000000000..180568ddf --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_json_protocol.dart @@ -0,0 +1,784 @@ +/// 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. + +part of thrift; + +class TJsonProtocolFactory implements TProtocolFactory<TJsonProtocol> { + TJsonProtocol getProtocol(TTransport transport) { + return new TJsonProtocol(transport); + } +} + +/// JSON protocol implementation for Thrift. +/// +/// Adapted from the C# version. +class TJsonProtocol extends TProtocol { + static const int VERSION_1 = 1; + + static const Utf8Codec utf8Codec = const Utf8Codec(); + + _BaseContext _context; + _BaseContext _rootContext; + _LookaheadReader _reader; + + final List<_BaseContext> _contextStack = []; + final Uint8List _tempBuffer = new Uint8List(4); + + TJsonProtocol(TTransport transport) : super(transport) { + _rootContext = new _BaseContext(this); + _reader = new _LookaheadReader(this); + _resetContext(); + } + + void _pushContext(_BaseContext c) { + _contextStack.add(c); + _context = c; + } + + void _popContext() { + _contextStack.removeLast(); + _context = _contextStack.isEmpty ? _rootContext : _contextStack.last; + } + + void _resetContext() { + _contextStack.clear(); + _context = _rootContext; + } + + /// Read a byte that must match [char]; otherwise throw a [TProtocolError]. + void _readJsonSyntaxChar(int charByte) { + int byte = _reader.read(); + if (byte != charByte) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Expected character ${new String.fromCharCode(charByte)} but found: ${new String.fromCharCode(byte)}"); + } + } + + int _hexVal(int byte) { + if (byte >= _Constants.HEX_0_BYTES[0] && + byte <= _Constants.HEX_9_BYTES[0]) { + return byte - _Constants.HEX_0_BYTES[0]; + } else if (byte >= _Constants.HEX_A_BYTES[0] && + byte <= _Constants.HEX_F_BYTES[0]) { + byte += 10; + return byte - _Constants.HEX_A_BYTES[0]; + } else { + throw new TProtocolError( + TProtocolErrorType.INVALID_DATA, "Expected hex character"); + } + } + + int _hexChar(int byte) => byte.toRadixString(16).codeUnitAt(0); + + /// write + + /// Write the [bytes] as JSON characters, escaping as needed. + void _writeJsonString(Uint8List bytes) { + _context.write(); + transport.writeAll(_Constants.QUOTE_BYTES); + + int length = bytes.length; + for (int i = 0; i < length; i++) { + int byte = bytes[i]; + if ((byte & 0x00FF) >= 0x30) { + if (byte == _Constants.BACKSLASH_BYTES[0]) { + transport.writeAll(_Constants.BACKSLASH_BYTES); + transport.writeAll(_Constants.BACKSLASH_BYTES); + } else { + transport.write(bytes, i, 1); + } + } else { + _tempBuffer[0] = _Constants.JSON_CHAR_TABLE[byte]; + if (_tempBuffer[0] == 1) { + transport.write(bytes, i, 1); + } else if (_tempBuffer[0] > 1) { + transport.writeAll(_Constants.BACKSLASH_BYTES); + transport.write(_tempBuffer, 0, 1); + } else { + transport.writeAll(_Constants.ESCSEQ_BYTES); + _tempBuffer[0] = _hexChar(byte >> 4); + _tempBuffer[1] = _hexChar(byte); + transport.write(_tempBuffer, 0, 2); + } + } + } + + transport.writeAll(_Constants.QUOTE_BYTES); + } + + void _writeJsonInteger(int i) { + if (i == null) i = 0; + + _context.write(); + String str = i.toString(); + + if (_context.escapeNumbers) { + transport.writeAll(_Constants.QUOTE_BYTES); + } + transport.writeAll(utf8Codec.encode(str)); + if (_context.escapeNumbers) { + transport.writeAll(_Constants.QUOTE_BYTES); + } + } + + void _writeJsonDouble(double d) { + if (d == null) d = 0.0; + + _context.write(); + String str = d.toString(); + bool escapeNumbers = d.isNaN || d.isInfinite || _context.escapeNumbers; + + if (escapeNumbers) { + transport.writeAll(_Constants.QUOTE_BYTES); + } + transport.writeAll(utf8Codec.encode(str)); + if (escapeNumbers) { + transport.writeAll(_Constants.QUOTE_BYTES); + } + } + + void _writeJsonBase64(Uint8List bytes) { + _context.write(); + transport.writeAll(_Constants.QUOTE_BYTES); + + String base64text = base64.encode(bytes); + transport.writeAll(utf8Codec.encode(base64text)); + + transport.writeAll(_Constants.QUOTE_BYTES); + } + + void _writeJsonObjectStart() { + _context.write(); + transport.writeAll(_Constants.LBRACE_BYTES); + _pushContext(new _PairContext(this)); + } + + void _writeJsonObjectEnd() { + _popContext(); + transport.writeAll(_Constants.RBRACE_BYTES); + } + + void _writeJsonArrayStart() { + _context.write(); + transport.writeAll(_Constants.LBRACKET_BYTES); + _pushContext(new _ListContext(this)); + } + + void _writeJsonArrayEnd() { + _popContext(); + transport.writeAll(_Constants.RBRACKET_BYTES); + } + + void writeMessageBegin(TMessage message) { + _resetContext(); + + _writeJsonArrayStart(); + _writeJsonInteger(VERSION_1); + + _writeJsonString(utf8Codec.encode(message.name)); + _writeJsonInteger(message.type); + _writeJsonInteger(message.seqid); + } + + void writeMessageEnd() { + _writeJsonArrayEnd(); + } + + void writeStructBegin(TStruct struct) { + _writeJsonObjectStart(); + } + + void writeStructEnd() { + _writeJsonObjectEnd(); + } + + void writeFieldBegin(TField field) { + _writeJsonInteger(field.id); + _writeJsonObjectStart(); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(field.type)); + } + + void writeFieldEnd() { + _writeJsonObjectEnd(); + } + + void writeFieldStop() {} + + void writeMapBegin(TMap map) { + _writeJsonArrayStart(); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(map.keyType)); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(map.valueType)); + _writeJsonInteger(map.length); + _writeJsonObjectStart(); + } + + void writeMapEnd() { + _writeJsonObjectEnd(); + _writeJsonArrayEnd(); + } + + void writeListBegin(TList list) { + _writeJsonArrayStart(); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(list.elementType)); + _writeJsonInteger(list.length); + } + + void writeListEnd() { + _writeJsonArrayEnd(); + } + + void writeSetBegin(TSet set) { + _writeJsonArrayStart(); + _writeJsonString(_Constants.getTypeNameBytesForTypeId(set.elementType)); + _writeJsonInteger(set.length); + } + + void writeSetEnd() { + _writeJsonArrayEnd(); + } + + void writeBool(bool b) { + if (b == null) b = false; + _writeJsonInteger(b ? 1 : 0); + } + + void writeByte(int b) { + _writeJsonInteger(b); + } + + void writeI16(int i16) { + _writeJsonInteger(i16); + } + + void writeI32(int i32) { + _writeJsonInteger(i32); + } + + void writeI64(int i64) { + _writeJsonInteger(i64); + } + + void writeDouble(double d) { + _writeJsonDouble(d); + } + + void writeString(String s) { + var bytes = s != null ? utf8Codec.encode(s) : new Uint8List.fromList([]); + _writeJsonString(bytes); + } + + void writeBinary(Uint8List bytes) { + _writeJsonBase64(bytes); + } + + bool _isHighSurrogate(int b) => b >= 0xD800 && b <= 0xDBFF; + + bool _isLowSurrogate(int b) => b >= 0xDC00 && b <= 0xDFFF; + + /// read + + Uint8List _readJsonString({bool skipContext: false}) { + List<int> bytes = []; + List<int> codeunits = []; + + if (!skipContext) { + _context.read(); + } + + _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]); + while (true) { + int byte = _reader.read(); + if (byte == _Constants.QUOTE_BYTES[0]) { + break; + } + + // escaped? + if (byte != _Constants.ESCSEQ_BYTES[0]) { + bytes.add(byte); + continue; + } + + byte = _reader.read(); + + // distinguish between \uXXXX and control chars like \n + if (byte != _Constants.ESCSEQ_BYTES[1]) { + String char = new String.fromCharCode(byte); + int offset = _Constants.ESCAPE_CHARS.indexOf(char); + if (offset == -1) { + throw new TProtocolError( + TProtocolErrorType.INVALID_DATA, "Expected control char"); + } + byte = _Constants.ESCAPE_CHAR_VALS.codeUnitAt(offset); + bytes.add(byte); + continue; + } + + // it's \uXXXX + transport.readAll(_tempBuffer, 0, 4); + byte = (_hexVal(_tempBuffer[0]) << 12) + + (_hexVal(_tempBuffer[1]) << 8) + + (_hexVal(_tempBuffer[2]) << 4) + + _hexVal(_tempBuffer[3]); + if (_isHighSurrogate(byte)) { + if (codeunits.isNotEmpty) { + throw new TProtocolError( + TProtocolErrorType.INVALID_DATA, "Expected low surrogate"); + } + codeunits.add(byte); + } + else if (_isLowSurrogate(byte)) { + if (codeunits.isEmpty) { + throw new TProtocolError( + TProtocolErrorType.INVALID_DATA, "Expected high surrogate"); + } + codeunits.add(byte); + bytes.addAll(utf8Codec.encode(new String.fromCharCodes(codeunits))); + codeunits.clear(); + } + else { + bytes.addAll(utf8Codec.encode(new String.fromCharCode(byte))); + } + } + + if (codeunits.isNotEmpty) { + throw new TProtocolError( + TProtocolErrorType.INVALID_DATA, "Expected low surrogate"); + } + + return new Uint8List.fromList(bytes); + } + + String _readJsonNumericChars() { + StringBuffer buffer = new StringBuffer(); + while (true) { + if (!_Constants.isJsonNumeric(_reader.peek())) { + break; + } + buffer.write(new String.fromCharCode(_reader.read())); + } + return buffer.toString(); + } + + int _readJsonInteger() { + _context.read(); + + if (_context.escapeNumbers) { + _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]); + } + String str = _readJsonNumericChars(); + if (_context.escapeNumbers) { + _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]); + } + + try { + return int.parse(str); + } on FormatException catch (_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Bad data encounted in numeric data"); + } + } + + double _readJsonDouble() { + _context.read(); + + if (_reader.peek() == _Constants.QUOTE_BYTES[0]) { + Uint8List bytes = _readJsonString(skipContext: true); + double d = double.parse(utf8Codec.decode(bytes), (_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Bad data encounted in numeric data"); + }); + if (!_context.escapeNumbers && !d.isNaN && !d.isInfinite) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Numeric data unexpectedly quoted"); + } + return d; + } else { + if (_context.escapeNumbers) { + // This will throw - we should have had a quote if escapeNumbers == true + _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]); + } + return double.parse(_readJsonNumericChars(), (_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Bad data encounted in numeric data"); + }); + } + } + + Uint8List _readJsonBase64() { + // convert UTF-8 bytes of a Base 64 encoded string to binary bytes + Uint8List base64Bytes = _readJsonString(); + String base64text = utf8Codec.decode(base64Bytes); + + return new Uint8List.fromList(base64.decode(base64text)); + } + + void _readJsonObjectStart() { + _context.read(); + _readJsonSyntaxChar(_Constants.LBRACE_BYTES[0]); + _pushContext(new _PairContext(this)); + } + + void _readJsonObjectEnd() { + _readJsonSyntaxChar(_Constants.RBRACE_BYTES[0]); + _popContext(); + } + + void _readJsonArrayStart() { + _context.read(); + _readJsonSyntaxChar(_Constants.LBRACKET_BYTES[0]); + _pushContext(new _ListContext(this)); + } + + void _readJsonArrayEnd() { + _readJsonSyntaxChar(_Constants.RBRACKET_BYTES[0]); + _popContext(); + } + + TMessage readMessageBegin() { + _resetContext(); + + _readJsonArrayStart(); + if (_readJsonInteger() != VERSION_1) { + throw new TProtocolError( + TProtocolErrorType.BAD_VERSION, "Message contained bad version."); + } + + Uint8List buffer = _readJsonString(); + String name = utf8Codec.decode(buffer); + int type = _readJsonInteger(); + int seqid = _readJsonInteger(); + + return new TMessage(name, type, seqid); + } + + void readMessageEnd() { + _readJsonArrayEnd(); + } + + TStruct readStructBegin() { + _readJsonObjectStart(); + return new TStruct(); + } + + void readStructEnd() { + _readJsonObjectEnd(); + } + + TField readFieldBegin() { + String name = ""; + int type = TType.STOP; + int id = 0; + + if (_reader.peek() != _Constants.RBRACE_BYTES[0]) { + id = _readJsonInteger(); + _readJsonObjectStart(); + type = _Constants.getTypeIdForTypeName(_readJsonString()); + } + + return new TField(name, type, id); + } + + void readFieldEnd() { + _readJsonObjectEnd(); + } + + TMap readMapBegin() { + _readJsonArrayStart(); + int keyType = _Constants.getTypeIdForTypeName(_readJsonString()); + int valueType = _Constants.getTypeIdForTypeName(_readJsonString()); + int length = _readJsonInteger(); + _readJsonObjectStart(); + + return new TMap(keyType, valueType, length); + } + + void readMapEnd() { + _readJsonObjectEnd(); + _readJsonArrayEnd(); + } + + TList readListBegin() { + _readJsonArrayStart(); + int elementType = _Constants.getTypeIdForTypeName(_readJsonString()); + int length = _readJsonInteger(); + + return new TList(elementType, length); + } + + void readListEnd() { + _readJsonArrayEnd(); + } + + TSet readSetBegin() { + _readJsonArrayStart(); + int elementType = _Constants.getTypeIdForTypeName(_readJsonString()); + int length = _readJsonInteger(); + + return new TSet(elementType, length); + } + + void readSetEnd() { + _readJsonArrayEnd(); + } + + bool readBool() { + return _readJsonInteger() == 0 ? false : true; + } + + int readByte() { + return _readJsonInteger(); + } + + int readI16() { + return _readJsonInteger(); + } + + int readI32() { + return _readJsonInteger(); + } + + int readI64() { + return _readJsonInteger(); + } + + double readDouble() { + return _readJsonDouble(); + } + + String readString() { + return utf8Codec.decode(_readJsonString()); + } + + Uint8List readBinary() { + return new Uint8List.fromList(_readJsonBase64()); + } +} + +class _Constants { + static const utf8codec = const Utf8Codec(); + + static final Uint8List HEX_0_BYTES = new Uint8List.fromList('0'.codeUnits); + static final Uint8List HEX_9_BYTES = new Uint8List.fromList('9'.codeUnits); + static final Uint8List HEX_A_BYTES = new Uint8List.fromList('a'.codeUnits); + static final Uint8List HEX_F_BYTES = new Uint8List.fromList('f'.codeUnits); + static final Uint8List COMMA_BYTES = new Uint8List.fromList(','.codeUnits); + static final Uint8List COLON_BYTES = new Uint8List.fromList(':'.codeUnits); + static final Uint8List LBRACE_BYTES = new Uint8List.fromList('{'.codeUnits); + static final Uint8List RBRACE_BYTES = new Uint8List.fromList('}'.codeUnits); + static final Uint8List LBRACKET_BYTES = new Uint8List.fromList('['.codeUnits); + static final Uint8List RBRACKET_BYTES = new Uint8List.fromList(']'.codeUnits); + static final Uint8List QUOTE_BYTES = new Uint8List.fromList('"'.codeUnits); + static final Uint8List BACKSLASH_BYTES = + new Uint8List.fromList(r'\'.codeUnits); + + static final ESCSEQ_BYTES = new Uint8List.fromList(r'\u00'.codeUnits); + + static final Uint8List JSON_CHAR_TABLE = new Uint8List.fromList([ + 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes + 'b'.codeUnitAt(0), 't'.codeUnitAt(0), 'n'.codeUnitAt(0), 0, // 4 bytes + 'f'.codeUnitAt(0), 'r'.codeUnitAt(0), 0, 0, // 4 bytes + 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes + 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes + 1, 1, '"'.codeUnitAt(0), 1, 1, 1, 1, 1, // 8 bytes + 1, 1, 1, 1, 1, 1, 1, 1 // 8 bytes + ]); + + static const String ESCAPE_CHARS = r'"\/bfnrt'; + static const String ESCAPE_CHAR_VALS = '"\\/\b\f\n\r\t'; + + static const String NAME_BOOL = 'tf'; + static const String NAME_BYTE = 'i8'; + static const String NAME_I16 = 'i16'; + static const String NAME_I32 = 'i32'; + static const String NAME_I64 = 'i64'; + static const String NAME_DOUBLE = 'dbl'; + static const String NAME_STRUCT = 'rec'; + static const String NAME_STRING = 'str'; + static const String NAME_MAP = 'map'; + static const String NAME_LIST = 'lst'; + static const String NAME_SET = 'set'; + + static final Map<int, Uint8List> _TYPE_ID_TO_NAME_BYTES = + new Map.unmodifiable({ + TType.BOOL: new Uint8List.fromList(NAME_BOOL.codeUnits), + TType.BYTE: new Uint8List.fromList(NAME_BYTE.codeUnits), + TType.I16: new Uint8List.fromList(NAME_I16.codeUnits), + TType.I32: new Uint8List.fromList(NAME_I32.codeUnits), + TType.I64: new Uint8List.fromList(NAME_I64.codeUnits), + TType.DOUBLE: new Uint8List.fromList(NAME_DOUBLE.codeUnits), + TType.STRING: new Uint8List.fromList(NAME_STRING.codeUnits), + TType.STRUCT: new Uint8List.fromList(NAME_STRUCT.codeUnits), + TType.MAP: new Uint8List.fromList(NAME_MAP.codeUnits), + TType.SET: new Uint8List.fromList(NAME_SET.codeUnits), + TType.LIST: new Uint8List.fromList(NAME_LIST.codeUnits) + }); + + static Uint8List getTypeNameBytesForTypeId(int typeId) { + if (!_TYPE_ID_TO_NAME_BYTES.containsKey(typeId)) { + throw new TProtocolError( + TProtocolErrorType.NOT_IMPLEMENTED, "Unrecognized type"); + } + + return _TYPE_ID_TO_NAME_BYTES[typeId]; + } + + static final Map<String, int> _NAME_TO_TYPE_ID = new Map.unmodifiable({ + NAME_BOOL: TType.BOOL, + NAME_BYTE: TType.BYTE, + NAME_I16: TType.I16, + NAME_I32: TType.I32, + NAME_I64: TType.I64, + NAME_DOUBLE: TType.DOUBLE, + NAME_STRING: TType.STRING, + NAME_STRUCT: TType.STRUCT, + NAME_MAP: TType.MAP, + NAME_SET: TType.SET, + NAME_LIST: TType.LIST + }); + + static int getTypeIdForTypeName(Uint8List bytes) { + String name = utf8codec.decode(bytes); + if (!_NAME_TO_TYPE_ID.containsKey(name)) { + throw new TProtocolError( + TProtocolErrorType.NOT_IMPLEMENTED, "Unrecognized type"); + } + + return _NAME_TO_TYPE_ID[name]; + } + + static final Set<int> _JSON_NUMERICS = new Set.from([ + '+'.codeUnitAt(0), + '-'.codeUnitAt(0), + '.'.codeUnitAt(0), + '0'.codeUnitAt(0), + '1'.codeUnitAt(0), + '2'.codeUnitAt(0), + '3'.codeUnitAt(0), + '4'.codeUnitAt(0), + '5'.codeUnitAt(0), + '6'.codeUnitAt(0), + '7'.codeUnitAt(0), + '8'.codeUnitAt(0), + '9'.codeUnitAt(0), + 'E'.codeUnitAt(0), + 'e'.codeUnitAt(0) + ]); + + static bool isJsonNumeric(int byte) { + return _JSON_NUMERICS.contains(byte); + } +} + +class _LookaheadReader { + final TJsonProtocol protocol; + + _LookaheadReader(this.protocol); + + bool _hasData = false; + final Uint8List _data = new Uint8List(1); + + int read() { + if (_hasData) { + _hasData = false; + } else { + protocol.transport.readAll(_data, 0, 1); + } + + return _data[0]; + } + + int peek() { + if (!_hasData) { + protocol.transport.readAll(_data, 0, 1); + } + _hasData = true; + + return _data[0]; + } +} + +class _BaseContext { + final TJsonProtocol protocol; + + _BaseContext(this.protocol); + + void write() {} + + void read() {} + + bool get escapeNumbers => false; + + String toString() => 'BaseContext'; +} + +class _ListContext extends _BaseContext { + _ListContext(TJsonProtocol protocol) : super(protocol); + + bool _first = true; + + void write() { + if (_first) { + _first = false; + } else { + protocol.transport.writeAll(_Constants.COMMA_BYTES); + } + } + + void read() { + if (_first) { + _first = false; + } else { + protocol._readJsonSyntaxChar(_Constants.COMMA_BYTES[0]); + } + } + + String toString() => 'ListContext'; +} + +class _PairContext extends _BaseContext { + _PairContext(TJsonProtocol protocol) : super(protocol); + + bool _first = true; + bool _colon = true; + + Uint8List get symbolBytes => + _colon ? _Constants.COLON_BYTES : _Constants.COMMA_BYTES; + + void write() { + if (_first) { + _first = false; + _colon = true; + } else { + protocol.transport.writeAll(symbolBytes); + _colon = !_colon; + } + } + + void read() { + if (_first) { + _first = false; + _colon = true; + } else { + protocol._readJsonSyntaxChar(symbolBytes[0]); + _colon = !_colon; + } + } + + bool get escapeNumbers => _colon; + + String toString() => 'PairContext'; +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_list.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_list.dart new file mode 100644 index 000000000..49f467329 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_list.dart @@ -0,0 +1,25 @@ +/// 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. + +part of thrift; + +class TList { + final int elementType; + final int length; + + TList(this.elementType, this.length); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_map.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_map.dart new file mode 100644 index 000000000..efdf6813a --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_map.dart @@ -0,0 +1,26 @@ +/// 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. + +part of thrift; + +class TMap { + final int keyType; + final int valueType; + final int length; + + TMap(this.keyType, this.valueType, this.length); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_message.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_message.dart new file mode 100644 index 000000000..cc7b88680 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_message.dart @@ -0,0 +1,35 @@ +/// 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. + +part of thrift; + +class TMessageType { + static const int CALL = 1; + static const int REPLY = 2; + static const int EXCEPTION = 3; + static const int ONEWAY = 4; +} + +class TMessage { + final String name; + final int type; + final int seqid; + + TMessage(this.name, this.type, this.seqid); + + String toString() => "<TMessage name: '$name' type: $type seqid: $seqid>"; +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart new file mode 100644 index 000000000..078a6d72e --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart @@ -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. + +part of thrift; + +/// Adapted from the C# version. +class TMultiplexedProtocol extends TProtocolDecorator { + static const SEPARATOR = ':'; + + final String _serviceName; + + TMultiplexedProtocol(TProtocol protocol, String serviceName) + : _serviceName = serviceName, + super(protocol) { + if (serviceName == null) { + throw new ArgumentError.notNull("serviceName"); + } + } + + void writeMessageBegin(TMessage message) { + if (message.type == TMessageType.CALL || + message.type == TMessageType.ONEWAY) { + String name = _serviceName + SEPARATOR + message.name; + message = new TMessage(name, message.type, message.seqid); + } + + super.writeMessageBegin(message); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol.dart new file mode 100644 index 000000000..f49c0321d --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol.dart @@ -0,0 +1,95 @@ +/// 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. + +part of thrift; + +abstract class TProtocol { + final TTransport transport; + + TProtocol(this.transport); + + /// Write + void writeMessageBegin(TMessage message); + void writeMessageEnd(); + + void writeStructBegin(TStruct struct); + void writeStructEnd(); + + void writeFieldBegin(TField field); + void writeFieldEnd(); + void writeFieldStop(); + + void writeMapBegin(TMap map); + void writeMapEnd(); + + void writeListBegin(TList list); + void writeListEnd(); + + void writeSetBegin(TSet set); + void writeSetEnd(); + + void writeBool(bool b); + + void writeByte(int b); + + void writeI16(int i16); + + void writeI32(int i32); + + void writeI64(int i64); + + void writeDouble(double d); + + void writeString(String str); + + void writeBinary(Uint8List bytes); + + /// Read + TMessage readMessageBegin(); + void readMessageEnd(); + + TStruct readStructBegin(); + void readStructEnd(); + + TField readFieldBegin(); + void readFieldEnd(); + + TMap readMapBegin(); + void readMapEnd(); + + TList readListBegin(); + void readListEnd(); + + TSet readSetBegin(); + void readSetEnd(); + + bool readBool(); + + int readByte(); + + int readI16(); + + int readI32(); + + int readI64(); + + double readDouble(); + + String readString(); + + Uint8List readBinary(); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_decorator.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_decorator.dart new file mode 100644 index 000000000..9cd02f645 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_decorator.dart @@ -0,0 +1,150 @@ +/// 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. + +part of thrift; + +/// Forward all operations to the wrapped protocol. Used as a base class. +/// +/// Adapted from the C# version. +class TProtocolDecorator extends TProtocol { + final TProtocol _protocol; + + TProtocolDecorator(TProtocol protocol) + : _protocol = protocol, + super(protocol.transport); + + /// Write + + void writeMessageBegin(TMessage message) { + _protocol.writeMessageBegin(message); + } + + void writeMessageEnd() { + _protocol.writeMessageEnd(); + } + + void writeStructBegin(TStruct struct) { + _protocol.writeStructBegin(struct); + } + + void writeStructEnd() { + _protocol.writeStructEnd(); + } + + void writeFieldBegin(TField field) { + _protocol.writeFieldBegin(field); + } + + void writeFieldEnd() { + _protocol.writeFieldEnd(); + } + + void writeFieldStop() { + _protocol.writeFieldStop(); + } + + void writeMapBegin(TMap map) { + _protocol.writeMapBegin(map); + } + + void writeMapEnd() { + _protocol.writeMapEnd(); + } + + void writeListBegin(TList list) { + _protocol.writeListBegin(list); + } + + void writeListEnd() { + _protocol.writeListEnd(); + } + + void writeSetBegin(TSet set) { + _protocol.writeSetBegin(set); + } + + void writeSetEnd() { + _protocol.writeSetEnd(); + } + + void writeBool(bool b) { + _protocol.writeBool(b); + } + + void writeByte(int b) { + _protocol.writeByte(b); + } + + void writeI16(int i16) { + _protocol.writeI16(i16); + } + + void writeI32(int i32) { + _protocol.writeI32(i32); + } + + void writeI64(int i64) { + _protocol.writeI64(i64); + } + + void writeDouble(double d) { + _protocol.writeDouble(d); + } + + void writeString(String str) { + _protocol.writeString(str); + } + + void writeBinary(Uint8List bytes) { + _protocol.writeBinary(bytes); + } + + /// Read + TMessage readMessageBegin() => _protocol.readMessageBegin(); + void readMessageEnd() => _protocol.readMessageEnd(); + + TStruct readStructBegin() => _protocol.readStructBegin(); + void readStructEnd() => _protocol.readStructEnd(); + + TField readFieldBegin() => _protocol.readFieldBegin(); + void readFieldEnd() => _protocol.readFieldEnd(); + + TMap readMapBegin() => _protocol.readMapBegin(); + void readMapEnd() => _protocol.readMapEnd(); + + TList readListBegin() => _protocol.readListBegin(); + void readListEnd() => _protocol.readListEnd(); + + TSet readSetBegin() => _protocol.readSetBegin(); + void readSetEnd() => _protocol.readSetEnd(); + + bool readBool() => _protocol.readBool(); + + int readByte() => _protocol.readByte(); + + int readI16() => _protocol.readI16(); + + int readI32() => _protocol.readI32(); + + int readI64() => _protocol.readI64(); + + double readDouble() => _protocol.readDouble(); + + String readString() => _protocol.readString(); + + Uint8List readBinary() => _protocol.readBinary(); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_error.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_error.dart new file mode 100644 index 000000000..456baeb79 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_error.dart @@ -0,0 +1,33 @@ +/// 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. + +part of thrift; + +class TProtocolErrorType { + static const int UNKNOWN = 0; + static const int INVALID_DATA = 1; + static const int NEGATIVE_SIZE = 2; + static const int SIZE_LIMIT = 3; + static const int BAD_VERSION = 4; + static const int NOT_IMPLEMENTED = 5; + static const int DEPTH_LIMIT = 6; +} + +class TProtocolError extends TError { + TProtocolError([int type = TProtocolErrorType.UNKNOWN, String message = ""]) + : super(type, message); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_factory.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_factory.dart new file mode 100644 index 000000000..922c6cb69 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_factory.dart @@ -0,0 +1,22 @@ +/// 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. + +part of thrift; + +abstract class TProtocolFactory<T extends TProtocol> { + T getProtocol(TTransport transport); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_util.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_util.dart new file mode 100644 index 000000000..841ea8217 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_protocol_util.dart @@ -0,0 +1,107 @@ +/// 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. + +part of thrift; + +class TProtocolUtil { + // equal to JavaScript Number.MAX_SAFE_INTEGER, 2^53 - 1 + static const int defaultRecursionLimit = 9007199254740991; + + static int maxRecursionLimit = defaultRecursionLimit; + + static skip(TProtocol prot, int type) { + _skip(prot, type, maxRecursionLimit); + } + + static _skip(TProtocol prot, int type, int recursionLimit) { + if (recursionLimit <= 0) { + throw new TProtocolError( + TProtocolErrorType.DEPTH_LIMIT, "Depth limit exceeded"); + } + + switch (type) { + case TType.BOOL: + prot.readBool(); + break; + + case TType.BYTE: + prot.readByte(); + break; + + case TType.I16: + prot.readI16(); + break; + + case TType.I32: + prot.readI32(); + break; + + case TType.I64: + prot.readI64(); + break; + + case TType.DOUBLE: + prot.readDouble(); + break; + + case TType.STRING: + prot.readBinary(); + break; + + case TType.STRUCT: + prot.readStructBegin(); + while (true) { + TField field = prot.readFieldBegin(); + if (field.type == TType.STOP) { + break; + } + _skip(prot, field.type, recursionLimit - 1); + prot.readFieldEnd(); + } + prot.readStructEnd(); + break; + + case TType.MAP: + TMap map = prot.readMapBegin(); + for (int i = 0; i < map.length; i++) { + _skip(prot, map.keyType, recursionLimit - 1); + _skip(prot, map.valueType, recursionLimit - 1); + } + prot.readMapEnd(); + break; + + case TType.SET: + TSet set = prot.readSetBegin(); + for (int i = 0; i < set.length; i++) { + _skip(prot, set.elementType, recursionLimit - 1); + } + prot.readSetEnd(); + break; + + case TType.LIST: + TList list = prot.readListBegin(); + for (int i = 0; i < list.length; i++) { + _skip(prot, list.elementType, recursionLimit - 1); + } + prot.readListEnd(); + break; + + default: + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, "Invalid data"); + } + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_set.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_set.dart new file mode 100644 index 000000000..b342537e3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_set.dart @@ -0,0 +1,25 @@ +/// 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. + +part of thrift; + +class TSet { + final int elementType; + final int length; + + TSet(this.elementType, this.length); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_struct.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_struct.dart new file mode 100644 index 000000000..0303f6395 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_struct.dart @@ -0,0 +1,24 @@ +/// 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. + +part of thrift; + +class TStruct { + final String name; + + TStruct([this.name = ""]); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_type.dart b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_type.dart new file mode 100644 index 000000000..3919d969d --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/protocol/t_type.dart @@ -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. + +part of thrift; + +class TType { + static const int STOP = 0; + static const int VOID = 1; + static const int BOOL = 2; + static const int BYTE = 3; + static const int DOUBLE = 4; + static const int I16 = 6; + static const int I32 = 8; + static const int I64 = 10; + static const int STRING = 11; + static const int STRUCT = 12; + static const int MAP = 13; + static const int SET = 14; + static const int LIST = 15; +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/serializer/t_deserializer.dart b/src/jaegertracing/thrift/lib/dart/lib/src/serializer/t_deserializer.dart new file mode 100644 index 000000000..aefbee25b --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/serializer/t_deserializer.dart @@ -0,0 +1,50 @@ +/// 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. + +part of thrift; + +class TDeserializer { + final message = new TMessage('Deserializer', TMessageType.ONEWAY, 1); + TBufferedTransport transport; + TProtocol protocol; + + TDeserializer({TProtocolFactory protocolFactory}) { + this.transport = new TBufferedTransport(); + + if (protocolFactory == null) { + protocolFactory = new TBinaryProtocolFactory(); + } + + this.protocol = protocolFactory.getProtocol(this.transport); + } + + void read(TBase base, Uint8List data) { + transport.writeAll(data); + + transport.flush(); + + base.read(protocol); + } + + void readString(TBase base, String data) { + + transport.writeAll(base64.decode(data)); + transport.flush(); + + base.read(protocol); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/serializer/t_serializer.dart b/src/jaegertracing/thrift/lib/dart/lib/src/serializer/t_serializer.dart new file mode 100644 index 000000000..feec8226d --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/serializer/t_serializer.dart @@ -0,0 +1,48 @@ +/// 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. + +part of thrift; + +class TSerializer { + final message = new TMessage('Serializer', TMessageType.ONEWAY, 1); + TBufferedTransport transport; + TProtocol protocol; + + TSerializer({TProtocolFactory protocolFactory}) { + this.transport = new TBufferedTransport(); + + if (protocolFactory == null) { + protocolFactory = new TBinaryProtocolFactory(); + } + + this.protocol = protocolFactory.getProtocol(this.transport); + } + + Uint8List write(TBase base) { + base.write(protocol); + + return transport.consumeWriteBuffer(); + } + + String writeString(TBase base) { + base.write(protocol); + + Uint8List bytes = transport.consumeWriteBuffer(); + + return base64.encode(bytes); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/t_application_error.dart b/src/jaegertracing/thrift/lib/dart/lib/src/t_application_error.dart new file mode 100644 index 000000000..6f8abd4bd --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/t_application_error.dart @@ -0,0 +1,104 @@ +/// 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. + +part of thrift; + +class TApplicationErrorType { + static const int UNKNOWN = 0; + static const int UNKNOWN_METHOD = 1; + static const int INVALID_MESSAGE_TYPE = 2; + static const int WRONG_METHOD_NAME = 3; + static const int BAD_SEQUENCE_ID = 4; + static const int MISSING_RESULT = 5; + static const int INTERNAL_ERROR = 6; + static const int PROTOCOL_ERROR = 7; + static const int INVALID_TRANSFORM = 8; + static const int INVALID_PROTOCOL = 9; + static const int UNSUPPORTED_CLIENT_TYPE = 10; +} + +class TApplicationError extends TError { + static final TStruct _struct = new TStruct("TApplicationError"); + static const int MESSAGE = 1; + static final TField _messageField = + new TField("message", TType.STRING, MESSAGE); + static const int TYPE = 2; + static final TField _typeField = new TField("type", TType.I32, TYPE); + + TApplicationError( + [int type = TApplicationErrorType.UNKNOWN, String message = ""]) + : super(type, message); + + static TApplicationError read(TProtocol iprot) { + TField field; + + String message = null; + int type = TApplicationErrorType.UNKNOWN; + + iprot.readStructBegin(); + while (true) { + field = iprot.readFieldBegin(); + + if (field.type == TType.STOP) { + break; + } + + switch (field.id) { + case MESSAGE: + if (field.type == TType.STRING) { + message = iprot.readString(); + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; + + case TYPE: + if (field.type == TType.I32) { + type = iprot.readI32(); + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; + + default: + TProtocolUtil.skip(iprot, field.type); + break; + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + return new TApplicationError(type, message); + } + + write(TProtocol oprot) { + oprot.writeStructBegin(_struct); + + if (message != null && !message.isEmpty) { + oprot.writeFieldBegin(_messageField); + oprot.writeString(message); + oprot.writeFieldEnd(); + } + + oprot.writeFieldBegin(_typeField); + oprot.writeI32(type); + oprot.writeFieldEnd(); + + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/t_base.dart b/src/jaegertracing/thrift/lib/dart/lib/src/t_base.dart new file mode 100644 index 000000000..d5571b6dc --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/t_base.dart @@ -0,0 +1,37 @@ +/// 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. + +part of thrift; + +abstract class TBase { + /// Reads the TObject from the given input protocol. + void read(TProtocol iprot); + + /// Writes the objects out to the [oprot] protocol. + void write(TProtocol oprot); + + /// Check if a field is currently set or unset, using the [fieldId]. + bool isSet(int fieldId); + + /// Get a field's value by [fieldId]. Primitive types will be wrapped in the + /// appropriate "boxed" types. + getFieldValue(int fieldId); + + /// Set a field's value by [fieldId]. Primitive types must be "boxed" in the + /// appropriate object wrapper type. + setFieldValue(int fieldId, Object value); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/t_error.dart b/src/jaegertracing/thrift/lib/dart/lib/src/t_error.dart new file mode 100644 index 000000000..93ab73239 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/t_error.dart @@ -0,0 +1,27 @@ +/// 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. + +part of thrift; + +class TError extends Error { + final String message; + final int type; + + TError(this.type, this.message); + + String toString() => "<TError type: $type message: '$message'>"; +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/t_processor.dart b/src/jaegertracing/thrift/lib/dart/lib/src/t_processor.dart new file mode 100644 index 000000000..dcf20fbc7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/t_processor.dart @@ -0,0 +1,24 @@ +/// 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. + +part of thrift; + +/// A processor is a generic object which operates upon an input stream and +/// writes to some output stream. +abstract class TProcessor { + bool process(TProtocol input, TProtocol output); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_buffered_transport.dart b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_buffered_transport.dart new file mode 100644 index 000000000..b73a30c0e --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_buffered_transport.dart @@ -0,0 +1,98 @@ +/// 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. + +part of thrift; + +/// Buffered implementation of [TTransport]. +class TBufferedTransport extends TTransport { + final List<int> _writeBuffer = []; + Iterator<int> _readIterator; + + Uint8List consumeWriteBuffer() { + Uint8List buffer = new Uint8List.fromList(_writeBuffer); + _writeBuffer.clear(); + return buffer; + } + + void _setReadBuffer(Uint8List readBuffer) { + _readIterator = readBuffer != null ? readBuffer.iterator : null; + } + + void _reset({bool isOpen: false}) { + _isOpen = isOpen; + _writeBuffer.clear(); + _readIterator = null; + } + + bool get hasReadData => _readIterator != null; + + bool _isOpen; + bool get isOpen => _isOpen; + + Future open() async { + _reset(isOpen: true); + } + + Future close() async { + _reset(isOpen: false); + } + + int read(Uint8List buffer, int offset, int length) { + if (buffer == null) { + throw new ArgumentError.notNull("buffer"); + } + + if (offset + length > buffer.length) { + throw new ArgumentError("The range exceeds the buffer length"); + } + + if (_readIterator == null || length <= 0) { + return 0; + } + + int i = 0; + while (i < length && _readIterator.moveNext()) { + buffer[offset + i] = _readIterator.current; + i++; + } + + // cleanup iterator when we've reached the end + if (_readIterator.current == null) { + _readIterator = null; + } + + return i; + } + + void write(Uint8List buffer, int offset, int length) { + if (buffer == null) { + throw new ArgumentError.notNull("buffer"); + } + + if (offset + length > buffer.length) { + throw new ArgumentError("The range exceeds the buffer length"); + } + + _writeBuffer.addAll(buffer.sublist(offset, offset + length)); + } + + Future flush() { + _readIterator = consumeWriteBuffer().iterator; + + return new Future.value(); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_framed_transport.dart b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_framed_transport.dart new file mode 100644 index 000000000..2ef03f7f8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_framed_transport.dart @@ -0,0 +1,169 @@ +/// 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. + +part of thrift; + +/// Framed [TTransport]. +/// +/// Adapted from the Java Framed transport. +class TFramedTransport extends TBufferedTransport { + static const int headerByteCount = 4; + + final TTransport _transport; + + final Uint8List _headerBytes = new Uint8List(headerByteCount); + int _receivedHeaderBytes = 0; + + int _bodySize = 0; + Uint8List _body = null; + int _receivedBodyBytes = 0; + + Completer<Uint8List> _frameCompleter = null; + + TFramedTransport(TTransport transport) : _transport = transport { + if (transport == null) { + throw new ArgumentError.notNull("transport"); + } + } + + bool get isOpen => _transport.isOpen; + + Future open() { + _reset(isOpen: true); + return _transport.open(); + } + + Future close() { + _reset(isOpen: false); + return _transport.close(); + } + + int read(Uint8List buffer, int offset, int length) { + if (hasReadData) { + int got = super.read(buffer, offset, length); + if (got > 0) return got; + } + + // IMPORTANT: by the time you've got here, + // an entire frame is available for reading + + return super.read(buffer, offset, length); + } + + void _readFrame() { + if (_body == null) { + bool gotFullHeader = _readFrameHeader(); + if (!gotFullHeader) { + return; + } + } + + _readFrameBody(); + } + + bool _readFrameHeader() { + var remainingHeaderBytes = headerByteCount - _receivedHeaderBytes; + + int got = _transport.read(_headerBytes, _receivedHeaderBytes, remainingHeaderBytes); + if (got < 0) { + throw new TTransportError( + TTransportErrorType.UNKNOWN, "Socket closed during frame header read"); + } + + _receivedHeaderBytes += got; + + if (_receivedHeaderBytes == headerByteCount) { + int size = _headerBytes.buffer.asByteData().getUint32(0); + + _receivedHeaderBytes = 0; + + if (size < 0) { + throw new TTransportError( + TTransportErrorType.UNKNOWN, "Read a negative frame size: $size"); + } + + _bodySize = size; + _body = new Uint8List(_bodySize); + _receivedBodyBytes = 0; + + return true; + } else { + _registerForReadableBytes(); + return false; + } + } + + void _readFrameBody() { + var remainingBodyBytes = _bodySize - _receivedBodyBytes; + + int got = _transport.read(_body, _receivedBodyBytes, remainingBodyBytes); + if (got < 0) { + throw new TTransportError( + TTransportErrorType.UNKNOWN, "Socket closed during frame body read"); + } + + _receivedBodyBytes += got; + + if (_receivedBodyBytes == _bodySize) { + var body = _body; + + _bodySize = 0; + _body = null; + _receivedBodyBytes = 0; + + _setReadBuffer(body); + + var completer = _frameCompleter; + _frameCompleter = null; + completer.complete(new Uint8List(0)); + } else { + _registerForReadableBytes(); + } + } + + Future flush() { + if (_frameCompleter == null) { + Uint8List buffer = consumeWriteBuffer(); + int length = buffer.length; + + _headerBytes.buffer.asByteData().setUint32(0, length); + _transport.write(_headerBytes, 0, headerByteCount); + _transport.write(buffer, 0, length); + + _frameCompleter = new Completer<Uint8List>(); + _registerForReadableBytes(); + } + + return _frameCompleter.future; + } + + void _registerForReadableBytes() { + _transport.flush().then((_) { + _readFrame(); + }).catchError((e) { + var completer = _frameCompleter; + + _receivedHeaderBytes = 0; + _bodySize = 0; + _body = null; + _receivedBodyBytes = 0; + _frameCompleter = null; + + completer.completeError(e); + }); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_http_transport.dart b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_http_transport.dart new file mode 100644 index 000000000..630213fbf --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_http_transport.dart @@ -0,0 +1,99 @@ +/// 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. + +part of thrift; + +/// HTTP implementation of [TTransport]. +/// +/// For example: +/// +/// var transport = new THttpClientTransport(new BrowserClient(), +/// new THttpConfig(url, {'X-My-Custom-Header': 'my value'})); +/// var protocol = new TJsonProtocol(transport); +/// var client = new MyThriftServiceClient(protocol); +/// var result = client.myMethod(); +/// +/// Adapted from the JS XHR HTTP transport. +class THttpClientTransport extends TBufferedTransport { + final Client httpClient; + final THttpConfig config; + + THttpClientTransport(this.httpClient, this.config) { + if (httpClient == null) { + throw new ArgumentError.notNull("httpClient"); + } + } + + Future close() async { + _reset(isOpen: false); + httpClient.close(); + } + + Future flush() { + var requestBody = base64.encode(consumeWriteBuffer()); + + // Use a sync completer to ensure that the buffer can be read immediately + // after the read buffer is set, and avoid a race condition where another + // response could overwrite the read buffer. + var completer = new Completer.sync(); + + httpClient + .post(config.url, headers: config.headers, body: requestBody) + .then((response) { + Uint8List data; + try { + data = new Uint8List.fromList(base64.decode(response.body)); + } on FormatException catch (_) { + throw new TProtocolError(TProtocolErrorType.INVALID_DATA, + "Expected a Base 64 encoded string."); + } + + _setReadBuffer(data); + completer.complete(); + }); + + return completer.future; + } +} + +class THttpConfig { + final Uri url; + + Map<String, String> _headers; + Map<String, String> get headers => _headers; + + THttpConfig(this.url, Map<String, String> headers) { + if (url == null || !url.hasAuthority) { + throw new ArgumentError("Invalid url"); + } + + _initHeaders(headers); + } + + void _initHeaders(Map<String, String> initial) { + var h = {}; + + if (initial != null) { + h.addAll(initial); + } + + h['Content-Type'] = 'application/x-thrift'; + h['Accept'] = 'application/x-thrift'; + + _headers = new Map.unmodifiable(h); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_message_reader.dart b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_message_reader.dart new file mode 100644 index 000000000..8ca070834 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_message_reader.dart @@ -0,0 +1,99 @@ +/// 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. + +part of thrift; + +/// [TMessageReader] extracts a [TMessage] from bytes. This is used to allow a +/// transport to inspect the message seqid and map responses to requests. +class TMessageReader { + final TProtocolFactory protocolFactory; + + final int byteOffset; + final _TMessageReaderTransport _transport; + + /// Construct a [MessageReader]. The optional [byteOffset] specifies the + /// number of bytes to skip before reading the [TMessage]. + TMessageReader(this.protocolFactory, {int byteOffset: 0}) + : _transport = new _TMessageReaderTransport(), + this.byteOffset = byteOffset; + + TMessage readMessage(Uint8List bytes) { + _transport.reset(bytes, byteOffset); + TProtocol protocol = protocolFactory.getProtocol(_transport); + TMessage message = protocol.readMessageBegin(); + _transport.reset(null); + + return message; + } +} + +/// An internal class used to support [TMessageReader]. +class _TMessageReaderTransport extends TTransport { + _TMessageReaderTransport(); + + Iterator<int> _readIterator; + + void reset(Uint8List bytes, [int offset = 0]) { + if (bytes == null) { + _readIterator = null; + return; + } + + if (offset > bytes.length) { + throw new ArgumentError("The offset exceeds the bytes length"); + } + + _readIterator = bytes.iterator; + + for (var i = 0; i < offset; i++) { + _readIterator.moveNext(); + } + } + + get isOpen => true; + + Future open() => throw new UnsupportedError("Unsupported in MessageReader"); + + Future close() => throw new UnsupportedError("Unsupported in MessageReader"); + + int read(Uint8List buffer, int offset, int length) { + if (buffer == null) { + throw new ArgumentError.notNull("buffer"); + } + + if (offset + length > buffer.length) { + throw new ArgumentError("The range exceeds the buffer length"); + } + + if (_readIterator == null || length <= 0) { + return 0; + } + + int i = 0; + while (i < length && _readIterator.moveNext()) { + buffer[offset + i] = _readIterator.current; + i++; + } + + return i; + } + + void write(Uint8List buffer, int offset, int length) => + throw new UnsupportedError("Unsupported in MessageReader"); + + Future flush() => throw new UnsupportedError("Unsupported in MessageReader"); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_socket.dart b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_socket.dart new file mode 100644 index 000000000..b2eb6b646 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_socket.dart @@ -0,0 +1,38 @@ +/// 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. + +part of thrift; + +enum TSocketState { CLOSED, OPEN } + +abstract class TSocket { + Stream<TSocketState> get onState; + + Stream<Object> get onError; + + Stream<Uint8List> get onMessage; + + bool get isOpen; + + bool get isClosed; + + Future open(); + + Future close(); + + void send(Uint8List data); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_socket_transport.dart b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_socket_transport.dart new file mode 100644 index 000000000..c41374aff --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_socket_transport.dart @@ -0,0 +1,177 @@ +/// 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. + +part of thrift; + +/// Socket implementation of [TTransport]. +/// +/// For example: +/// +/// var transport = new TClientSocketTransport(new TWebSocket(url)); +/// var protocol = new TBinaryProtocol(transport); +/// var client = new MyThriftServiceClient(protocol); +/// var result = client.myMethod(); +/// +/// Adapted from the JS WebSocket transport. +abstract class TSocketTransport extends TBufferedTransport { + final Logger logger = new Logger('thrift.TSocketTransport'); + + final TSocket socket; + + /// A transport using the provided [socket]. + TSocketTransport(this.socket) { + if (socket == null) { + throw new ArgumentError.notNull('socket'); + } + + socket.onError.listen((e) => logger.warning(e)); + socket.onMessage.listen(handleIncomingMessage); + } + + bool get isOpen => socket.isOpen; + + Future open() { + _reset(isOpen: true); + return socket.open(); + } + + Future close() { + _reset(isOpen: false); + return socket.close(); + } + + /// Make an incoming message available to read from the transport. + void handleIncomingMessage(Uint8List messageBytes) { + _setReadBuffer(messageBytes); + } +} + +/// [TClientSocketTransport] is a basic client socket transport. It sends +/// outgoing messages and expects a response. +/// +/// NOTE: This transport expects a single threaded server, as it will process +/// responses in FIFO order. +class TClientSocketTransport extends TSocketTransport { + final List<Completer<Uint8List>> _completers = []; + + TClientSocketTransport(TSocket socket) : super(socket); + + Future flush() { + Uint8List bytes = consumeWriteBuffer(); + + // Use a sync completer to ensure that the buffer can be read immediately + // after the read buffer is set, and avoid a race condition where another + // response could overwrite the read buffer. + var completer = new Completer<Uint8List>.sync(); + _completers.add(completer); + + if (bytes.lengthInBytes > 0) { + socket.send(bytes); + } + + return completer.future; + } + + void handleIncomingMessage(Uint8List messageBytes) { + super.handleIncomingMessage(messageBytes); + + if (_completers.isNotEmpty) { + var completer = _completers.removeAt(0); + completer.complete(); + } + } +} + +/// [TAsyncClientSocketTransport] sends outgoing messages and expects an +/// asynchronous response. +/// +/// NOTE: This transport uses a [MessageReader] to read a [TMessage] when an +/// incoming message arrives to correlate a response to a request, using the +/// seqid. +class TAsyncClientSocketTransport extends TSocketTransport { + static const defaultTimeout = const Duration(seconds: 30); + + final Map<int, Completer<Uint8List>> _completers = {}; + + final TMessageReader messageReader; + + final Duration responseTimeout; + + TAsyncClientSocketTransport(TSocket socket, TMessageReader messageReader, + {Duration responseTimeout: defaultTimeout}) + : this.messageReader = messageReader, + this.responseTimeout = responseTimeout, + super(socket); + + Future flush() { + Uint8List bytes = consumeWriteBuffer(); + TMessage message = messageReader.readMessage(bytes); + int seqid = message.seqid; + + // Use a sync completer to ensure that the buffer can be read immediately + // after the read buffer is set, and avoid a race condition where another + // response could overwrite the read buffer. + var completer = new Completer<Uint8List>.sync(); + _completers[seqid] = completer; + + if (responseTimeout != null) { + new Future.delayed(responseTimeout, () { + var completer = _completers.remove(seqid); + if (completer != null) { + completer.completeError( + new TimeoutException("Response timed out.", responseTimeout)); + } + }); + } + + socket.send(bytes); + + return completer.future; + } + + void handleIncomingMessage(Uint8List messageBytes) { + super.handleIncomingMessage(messageBytes); + + TMessage message = messageReader.readMessage(messageBytes); + var completer = _completers.remove(message.seqid); + if (completer != null) { + completer.complete(); + } + } +} + +/// [TServerSocketTransport] listens for incoming messages. When it sends a +/// response, it does not expect an acknowledgement. +class TServerSocketTransport extends TSocketTransport { + final StreamController _onIncomingMessageController; + Stream get onIncomingMessage => _onIncomingMessageController.stream; + + TServerSocketTransport(TSocket socket) + : _onIncomingMessageController = new StreamController.broadcast(), + super(socket); + + Future flush() async { + Uint8List message = consumeWriteBuffer(); + socket.send(message); + } + + void handleIncomingMessage(Uint8List messageBytes) { + super.handleIncomingMessage(messageBytes); + + _onIncomingMessageController.add(null); + } +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_transport.dart b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_transport.dart new file mode 100644 index 000000000..563d5eb5a --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_transport.dart @@ -0,0 +1,70 @@ +/// 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. + +part of thrift; + +abstract class TTransport { + /// Queries whether the transport is open. + /// Returns [true] if the transport is open. + bool get isOpen; + + /// Opens the transport for reading/writing. + /// Throws [TTransportError] if the transport could not be opened. + Future open(); + + /// Closes the transport. + Future close(); + + /// Reads up to [length] bytes into [buffer], starting at [offset]. + /// Returns the number of bytes actually read. + /// Throws [TTransportError] if there was an error reading data + int read(Uint8List buffer, int offset, int length); + + /// Guarantees that all of [length] bytes are actually read off the transport. + /// Returns the number of bytes actually read, which must be equal to + /// [length]. + /// Throws [TTransportError] if there was an error reading data + int readAll(Uint8List buffer, int offset, int length) { + int got = 0; + int ret = 0; + while (got < length) { + ret = read(buffer, offset + got, length - got); + if (ret <= 0) { + throw new TTransportError( + TTransportErrorType.UNKNOWN, + "Cannot read. Remote side has closed. Tried to read $length " + "bytes, but only got $got bytes."); + } + got += ret; + } + return got; + } + + /// Writes up to [len] bytes from the buffer. + /// Throws [TTransportError] if there was an error writing data + void write(Uint8List buffer, int offset, int length); + + /// Writes the [bytes] to the output. + /// Throws [TTransportError] if there was an error writing data + void writeAll(Uint8List buffer) { + write(buffer, 0, buffer.length); + } + + /// Flush any pending data out of a transport buffer. + /// Throws [TTransportError] if there was an error writing out data. + Future flush(); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_transport_error.dart b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_transport_error.dart new file mode 100644 index 000000000..d3508e052 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_transport_error.dart @@ -0,0 +1,31 @@ +/// 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. + +part of thrift; + +class TTransportErrorType { + static const int UNKNOWN = 0; + static const int NOT_OPEN = 1; + static const int ALREADY_OPEN = 2; + static const int TIMED_OUT = 3; + static const int END_OF_FILE = 4; +} + +class TTransportError extends TError { + TTransportError([int type = TTransportErrorType.UNKNOWN, String message = ""]) + : super(type, message); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_transport_factory.dart b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_transport_factory.dart new file mode 100644 index 000000000..7a10461d2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/src/transport/t_transport_factory.dart @@ -0,0 +1,27 @@ +/// 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. + +part of thrift; + +/// Factory class used to create wrapped instance of a [TTransport]. This is +/// used primarily in servers. +/// +/// Adapted from the Java version. +class TTransportFactory { + Future<TTransport> getTransport(TTransport transport) => + new Future.value(transport); +} diff --git a/src/jaegertracing/thrift/lib/dart/lib/thrift.dart b/src/jaegertracing/thrift/lib/dart/lib/thrift.dart new file mode 100644 index 000000000..c429d773c --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/thrift.dart @@ -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. + +library thrift; + +import 'dart:async'; +import 'dart:collection'; +import 'dart:convert' show Utf8Codec; +import 'dart:typed_data' show ByteData; +import 'dart:typed_data' show Uint8List; + +import 'package:dart2_constant/convert.dart' show base64; +import 'package:dart2_constant/typed_data.dart' show Endianness; +import 'package:fixnum/fixnum.dart'; +import 'package:http/http.dart' show Client; +import 'package:logging/logging.dart'; + +part 'src/t_application_error.dart'; +part 'src/t_base.dart'; +part 'src/t_error.dart'; +part 'src/t_processor.dart'; + +part 'src/protocol/t_binary_protocol.dart'; +part 'src/protocol/t_compact_protocol.dart'; +part 'src/protocol/t_field.dart'; +part 'src/protocol/t_json_protocol.dart'; +part 'src/protocol/t_list.dart'; +part 'src/protocol/t_map.dart'; +part 'src/protocol/t_message.dart'; +part 'src/protocol/t_multiplexed_protocol.dart'; +part 'src/protocol/t_protocol.dart'; +part 'src/protocol/t_protocol_decorator.dart'; +part 'src/protocol/t_protocol_error.dart'; +part 'src/protocol/t_protocol_factory.dart'; +part 'src/protocol/t_protocol_util.dart'; +part 'src/protocol/t_set.dart'; +part 'src/protocol/t_struct.dart'; +part 'src/protocol/t_type.dart'; + +part 'src/serializer/t_deserializer.dart'; +part 'src/serializer/t_serializer.dart'; + +part 'src/transport/t_buffered_transport.dart'; +part 'src/transport/t_framed_transport.dart'; +part 'src/transport/t_http_transport.dart'; +part 'src/transport/t_message_reader.dart'; +part 'src/transport/t_socket.dart'; +part 'src/transport/t_transport.dart'; +part 'src/transport/t_transport_error.dart'; +part 'src/transport/t_transport_factory.dart'; +part 'src/transport/t_socket_transport.dart'; diff --git a/src/jaegertracing/thrift/lib/dart/lib/thrift_browser.dart b/src/jaegertracing/thrift/lib/dart/lib/thrift_browser.dart new file mode 100644 index 000000000..2ebc25758 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/thrift_browser.dart @@ -0,0 +1,22 @@ +/// 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. + +library thrift_browser; + +/// Classes that are only supported in browser applications go here + +export 'src/browser/t_web_socket.dart' show TWebSocket; diff --git a/src/jaegertracing/thrift/lib/dart/lib/thrift_console.dart b/src/jaegertracing/thrift/lib/dart/lib/thrift_console.dart new file mode 100644 index 000000000..48a83d1dc --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/lib/thrift_console.dart @@ -0,0 +1,23 @@ +/// 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. + +library thrift_console; + +/// Classes that are only supported in console applications go here + +export 'src/console/t_tcp_socket.dart' show TTcpSocket; +export 'src/console/t_web_socket.dart' show TWebSocket; diff --git a/src/jaegertracing/thrift/lib/dart/pubspec.yaml b/src/jaegertracing/thrift/lib/dart/pubspec.yaml new file mode 100644 index 000000000..f406b9932 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/pubspec.yaml @@ -0,0 +1,38 @@ +# 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. + +name: thrift +version: 0.13.0 +description: > + A Dart library for Apache Thrift +author: Apache Thrift Developers <dev@thrift.apache.org> +homepage: http://thrift.apache.org +documentation: http://thrift.apache.org + +environment: + sdk: ">=1.24.3 <3.0.0" + +dependencies: + dart2_constant: ^1.0.0 + fixnum: ^0.10.2 + http: ^0.11.3 + logging: ^0.11.0 + +dev_dependencies: + dart_dev: ^2.0.0 + mockito: ">=2.2.2 <4.0.0" + test: ">=0.12.30 <2.0.0" diff --git a/src/jaegertracing/thrift/lib/dart/test/protocol/t_protocol_test.dart b/src/jaegertracing/thrift/lib/dart/test/protocol/t_protocol_test.dart new file mode 100644 index 000000000..dc63dbb71 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/test/protocol/t_protocol_test.dart @@ -0,0 +1,406 @@ +// 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. + +library thrift.test.transport.t_json_protocol_test; + +import 'dart:async'; +import 'dart:typed_data' show Uint8List; + +import 'package:dart2_constant/convert.dart' show utf8; +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + final message = new TMessage('my message', TMessageType.ONEWAY, 123); + + TProtocol protocol; + + Primitive getPrimitive(int tType) { + switch (tType) { + case TType.BOOL: + return new Primitive(protocol.readBool, protocol.writeBool, false); + + case TType.BYTE: + return new Primitive(protocol.readByte, protocol.writeByte, 0); + + case TType.I16: + return new Primitive(protocol.readI16, protocol.writeI16, 0); + + case TType.I32: + return new Primitive(protocol.readI32, protocol.writeI32, 0); + + case TType.I64: + return new Primitive(protocol.readI64, protocol.writeI64, 0); + + case TType.DOUBLE: + return new Primitive(protocol.readDouble, protocol.writeDouble, 0); + + case TType.STRING: + return new Primitive(protocol.readString, protocol.writeString, ''); + + default: + throw new UnsupportedError("Unsupported TType $tType"); + } + } + + Future primitiveTest(Primitive primitive, input) async { + primitive.write(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = primitive.read(); + + expect(output, input); + } + + Future primitiveNullTest(Primitive primitive) async { + primitive.write(null); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = primitive.read(); + + expect(output, primitive.defaultValue); + } + + var sharedTests = () { + test('Test message', () async { + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + var subject = protocol.readMessageBegin(); + + expect(subject.name, message.name); + expect(subject.type, message.type); + expect(subject.seqid, message.seqid); + }); + + test('Test struct', () async { + var input = new TStruct(); + + protocol.writeStructBegin(input); + protocol.writeStructEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readStructBegin(); + + // name is not serialized, see C# version for reference + expect(output, isNotNull); + }); + + test('Test field', () async { + var input = new TField('my field', TType.MAP, 123); + + protocol.writeFieldBegin(input); + protocol.writeFieldEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readFieldBegin(); + + // name is not serialized, see C# version for reference + expect(output.type, input.type); + expect(output.id, input.id); + }); + + test('Test map', () async { + var input = new TMap(TType.STRING, TType.STRUCT, 123); + + protocol.writeMapBegin(input); + protocol.writeMapEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readMapBegin(); + + expect(output.keyType, input.keyType); + expect(output.valueType, input.valueType); + expect(output.length, input.length); + }); + + test('Test list', () async { + var input = new TList(TType.STRING, 123); + + protocol.writeListBegin(input); + protocol.writeListEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readListBegin(); + + expect(output.elementType, input.elementType); + expect(output.length, input.length); + }); + + test('Test set', () async { + var input = new TSet(TType.STRING, 123); + + protocol.writeSetBegin(input); + protocol.writeSetEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readListBegin(); + + expect(output.elementType, input.elementType); + expect(output.length, input.length); + }); + + test('Test bool', () async { + await primitiveTest(getPrimitive(TType.BOOL), true); + }); + + test('Test bool null', () async { + await primitiveNullTest(getPrimitive(TType.BOOL)); + }); + + test('Test byte', () async { + await primitiveTest(getPrimitive(TType.BYTE), 64); + }); + + test('Test byte null', () async { + await primitiveNullTest(getPrimitive(TType.BYTE)); + }); + + test('Test I16', () async { + await primitiveTest(getPrimitive(TType.I16), 32767); + }); + + test('Test I16 null', () async { + await primitiveNullTest(getPrimitive(TType.I16)); + }); + + test('Test I32', () async { + await primitiveTest(getPrimitive(TType.I32), 2147483647); + }); + + test('Test I32 null', () async { + await primitiveNullTest(getPrimitive(TType.I32)); + }); + + test('Test I64', () async { + await primitiveTest(getPrimitive(TType.I64), 9223372036854775807); + }); + + test('Test I64 null', () async { + await primitiveNullTest(getPrimitive(TType.I64)); + }); + + test('Test double', () async { + await primitiveTest(getPrimitive(TType.DOUBLE), 3.1415926); + }); + + test('Test double null', () async { + await primitiveNullTest(getPrimitive(TType.DOUBLE)); + }); + + test('Test string', () async { + var input = 'There are only two hard things in computer science: ' + 'cache invalidation, naming things, and off-by-one errors.'; + await primitiveTest(getPrimitive(TType.STRING), input); + }); + + test('Test string null', () async { + await primitiveNullTest(getPrimitive(TType.STRING)); + }); + + test('Test binary', () async { + var input = new Uint8List.fromList(new List.filled(100, 123)); + + protocol.writeBinary(input); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + var output = protocol.readBinary(); + + expect(output.length, input.length); + expect(output.every((i) => i == 123), isTrue); + }); + + test('Test complex struct', () async { + // {1: {10: 20}, 2: {30: 40}} + protocol.writeStructBegin(new TStruct()); + protocol.writeFieldBegin(new TField('success', TType.MAP, 0)); + protocol.writeMapBegin(new TMap(TType.I32, TType.MAP, 2)); + + protocol.writeI32(1); // key + protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1)); + protocol.writeI32(10); // key + protocol.writeI32(20); // value + protocol.writeMapEnd(); + + protocol.writeI32(2); // key + protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1)); + protocol.writeI32(30); // key + protocol.writeI32(40); // value + protocol.writeMapEnd(); + + protocol.writeMapEnd(); + protocol.writeFieldEnd(); + protocol.writeFieldStop(); + protocol.writeStructEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + protocol.readStructBegin(); + expect(protocol.readFieldBegin().type, TType.MAP); + expect(protocol.readMapBegin().length, 2); + + expect(protocol.readI32(), 1); // key + expect(protocol.readMapBegin().length, 1); + expect(protocol.readI32(), 10); // key + expect(protocol.readI32(), 20); // value + protocol.readMapEnd(); + + expect(protocol.readI32(), 2); // key + expect(protocol.readMapBegin().length, 1); + expect(protocol.readI32(), 30); // key + expect(protocol.readI32(), 40); // value + protocol.readMapEnd(); + + protocol.readMapEnd(); + protocol.readFieldEnd(); + protocol.readStructEnd(); + protocol.readMessageEnd(); + }); + + test('Test nested maps and lists', () async { + // {1: [{10: 20}], 2: [{30: 40}]} + protocol.writeMapBegin(new TMap(TType.I32, TType.LIST, 2)); + + protocol.writeI32(1); // key + protocol.writeListBegin(new TList(TType.MAP, 1)); + protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1)); + protocol.writeI32(10); // key + protocol.writeI32(20); // value + protocol.writeMapEnd(); + protocol.writeListEnd(); + + protocol.writeI32(2); // key + protocol.writeListBegin(new TList(TType.MAP, 1)); + protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1)); + protocol.writeI32(30); // key + protocol.writeI32(40); // value + protocol.writeMapEnd(); + protocol.writeListEnd(); + + protocol.writeMapEnd(); + protocol.writeMessageEnd(); + + await protocol.transport.flush(); + + protocol.readMessageBegin(); + expect(protocol.readMapBegin().length, 2); + + expect(protocol.readI32(), 1); // key + expect(protocol.readListBegin().length, 1); + expect(protocol.readMapBegin().length, 1); + expect(protocol.readI32(), 10); // key + expect(protocol.readI32(), 20); // value + protocol.readMapEnd(); + protocol.readListEnd(); + + expect(protocol.readI32(), 2); // key + expect(protocol.readListBegin().length, 1); + expect(protocol.readMapBegin().length, 1); + expect(protocol.readI32(), 30); // key + expect(protocol.readI32(), 40); // value + protocol.readMapEnd(); + protocol.readListEnd(); + + protocol.readMapEnd(); + protocol.readMessageEnd(); + }); + }; + + group('JSON', () { + setUp(() { + protocol = new TJsonProtocol(new TBufferedTransport()); + protocol.writeMessageBegin(message); + }); + + test('Test escaped unicode', () async { + /* + KOR_KAI + UTF-8: 0xE0 0xB8 0x81 + UTF-16: 0x0E01 + G clef: + UTF-8: 0xF0 0x9D 0x84 0x9E + UTF-16: 0xD834 0xDD1E + */ + var buffer = utf8.encode(r'"\u0001\u0e01 \ud834\udd1e"'); + var transport = new TBufferedTransport(); + transport.writeAll(buffer); + + var protocol = new TJsonProtocol(transport); + + await protocol.transport.flush(); + + var subject = protocol.readString(); + expect(subject, + utf8.decode([0x01, 0xE0, 0xB8, 0x81, 0x20, 0xF0, 0x9D, 0x84, 0x9E])); + }); + + group('shared tests', sharedTests); + }); + + group('binary', () { + setUp(() { + protocol = new TBinaryProtocol(new TBufferedTransport()); + protocol.writeMessageBegin(message); + }); + + group('shared tests', sharedTests); + }); + + group('compact', () { + setUp(() { + protocol = new TCompactProtocol(new TBufferedTransport()); + protocol.writeMessageBegin(message); + }); + + group('shared tests', sharedTests); + }); +} + +class Primitive { + final Function read; + final Function write; + final defaultValue; + + Primitive(this.read, this.write, this.defaultValue); +} diff --git a/src/jaegertracing/thrift/lib/dart/test/serializer/serializer_test.dart b/src/jaegertracing/thrift/lib/dart/test/serializer/serializer_test.dart new file mode 100644 index 000000000..2f76503c4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/test/serializer/serializer_test.dart @@ -0,0 +1,119 @@ +/* + * 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. + */ + +library thrift.test.serializer.serializer_test; + +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; +import 'serializer_test_data.dart'; + +void main() { + var serializer = () { + TDeserializer deserializer; + TSerializer serializer; + TestTObject testTObject; + + setUp(() { + serializer = new TSerializer(); + deserializer = new TDeserializer(); + + testTObject = new TestTObject(); + testTObject.b = true; + testTObject.s = "TEST"; + testTObject.d = 15.25; + testTObject.i = 10; + + var testList = new List<String>(); + testList.add("TEST 1"); + testList.add("TEST 2"); + + testTObject.l = testList; + }); + + assertNewObjectEqualsTObject(TestTObject newObject) { + expect(newObject.l, equals(testTObject.l)); + expect(newObject.b, equals(testTObject.b)); + expect(newObject.i, equals(testTObject.i)); + expect(newObject.d, equals(testTObject.d)); + expect(newObject.s, equals(testTObject.s)); + } + + runWriteStringTest() { + var s = serializer.writeString(testTObject); + + var newObject = new TestTObject(); + deserializer.readString(newObject, s); + + assertNewObjectEqualsTObject(newObject); + }; + + runWriteTest() { + var s = serializer.write(testTObject); + + var newObject = new TestTObject(); + deserializer.read(newObject, s); + + assertNewObjectEqualsTObject(newObject); + }; + + test('JSON Protocol String', () { + serializer.protocol = new TJsonProtocol(serializer.transport); + deserializer.protocol = new TJsonProtocol(deserializer.transport); + + runWriteStringTest(); + }); + + test('JSON Protocol', () { + serializer.protocol = new TJsonProtocol(serializer.transport); + deserializer.protocol = new TJsonProtocol(deserializer.transport); + + runWriteTest(); + }); + + test('Binary Protocol String', () { + serializer.protocol = new TBinaryProtocol(serializer.transport); + deserializer.protocol = new TBinaryProtocol(deserializer.transport); + + runWriteStringTest(); + }); + + test('Binary Protocol', () { + serializer.protocol = new TBinaryProtocol(serializer.transport); + deserializer.protocol = new TBinaryProtocol(deserializer.transport); + + runWriteTest(); + }); + + test('Compact Protocol String', () { + serializer.protocol = new TCompactProtocol(serializer.transport); + deserializer.protocol = new TCompactProtocol(deserializer.transport); + + runWriteStringTest(); + }); + + test('Compact Protocol', () { + serializer.protocol = new TCompactProtocol(serializer.transport); + deserializer.protocol = new TCompactProtocol(deserializer.transport); + + runWriteTest(); + }); + }; + + group('Serializer', serializer); +} diff --git a/src/jaegertracing/thrift/lib/dart/test/serializer/serializer_test_data.dart b/src/jaegertracing/thrift/lib/dart/test/serializer/serializer_test_data.dart new file mode 100644 index 000000000..3586f08fc --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/test/serializer/serializer_test_data.dart @@ -0,0 +1,342 @@ +/* + * 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. + */ + +library thrift.test.serializer.serializer_test; + +import 'package:thrift/thrift.dart'; + +/// TestTObject is a simple test struct +class TestTObject implements TBase { + static final TStruct _STRUCT_DESC = new TStruct("TestTObject"); + static final TField _I_FIELD_DESC = new TField("i", TType.I32, 1); + static final TField _D_FIELD_DESC = new TField("d", TType.DOUBLE, 2); + static final TField _S_FIELD_DESC = new TField("s", TType.STRING, 3); + static final TField _L_FIELD_DESC = new TField("l", TType.LIST, 4); + static final TField _B_FIELD_DESC = new TField("b", TType.BOOL, 5); + + int _i; + static const int I = 1; + double _d; + static const int D = 2; + String _s; + static const int S = 3; + List<String> _l; + static const int L = 4; + bool _b; + static const int B = 5; + + bool __isset_i = false; + bool __isset_d = false; + bool __isset_b = false; + + TestTObject() { + } + + // i + int get i => this._i; + + set i(int i) { + this._i = i; + this.__isset_i = true; + } + + bool isSetI() => this.__isset_i; + + unsetI() { + this.__isset_i = false; + } + + // d + double get d => this._d; + + set d(double d) { + this._d = d; + this.__isset_d = true; + } + + bool isSetD() => this.__isset_d; + + unsetD() { + this.__isset_d = false; + } + + // s + String get s => this._s; + + set s(String s) { + this._s = s; + } + + bool isSetS() => this.s != null; + + unsetS() { + this.s = null; + } + + // l + List<String> get l => this._l; + + set l(List<String> l) { + this._l = l; + } + + bool isSetL() => this.l != null; + + unsetL() { + this.l = null; + } + + // b + bool get b => this._b; + + set b(bool b) { + this._b = b; + this.__isset_b = true; + } + + bool isSetB() => this.__isset_b; + + unsetB() { + this.__isset_b = false; + } + + getFieldValue(int fieldID) { + switch (fieldID) { + case I: + return this.i; + case D: + return this.d; + case S: + return this.s; + case L: + return this.l; + case B: + return this.b; + default: + throw new ArgumentError("Field $fieldID doesn't exist!"); + } + } + + setFieldValue(int fieldID, Object value) { + switch (fieldID) { + case I: + if (value == null) { + unsetI(); + } else { + this.i = value; + } + break; + + case D: + if (value == null) { + unsetD(); + } else { + this.d = value; + } + break; + + case S: + if (value == null) { + unsetS(); + } else { + this.s = value; + } + break; + + case L: + if (value == null) { + unsetL(); + } else { + this.l = value as List<String>; + } + break; + + case B: + if (value == null) { + unsetB(); + } else { + this.b = value; + } + break; + + default: + throw new ArgumentError("Field $fieldID doesn't exist!"); + } + } + + // Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise + bool isSet(int fieldID) { + switch (fieldID) { + case I: + return isSetI(); + case D: + return isSetD(); + case S: + return isSetS(); + case L: + return isSetL(); + case B: + return isSetB(); + default: + throw new ArgumentError("Field $fieldID doesn't exist!"); + } + } + + read(TProtocol iprot) { + TField field; + iprot.readStructBegin(); + while (true) { + field = iprot.readFieldBegin(); + if (field.type == TType.STOP) { + break; + } + switch (field.id) { + case I: + if (field.type == TType.I32) { + this.i = iprot.readI32(); + this.__isset_i = true; + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; + case D: + if (field.type == TType.DOUBLE) { + this.d = iprot.readDouble(); + this.__isset_d = true; + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; + case S: + if (field.type == TType.STRING) { + this.s = iprot.readString(); + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; + case L: + if (field.type == TType.LIST) { + { + TList _list74 = iprot.readListBegin(); + this.l = new List<String>(); + for (int _i75 = 0; _i75 < _list74.length; ++_i75) { + String _elem76; + _elem76 = iprot.readString(); + this.l.add(_elem76); + } + iprot.readListEnd(); + } + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; + case B: + if (field.type == TType.BOOL) { + this.b = iprot.readBool(); + this.__isset_b = true; + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; + default: + TProtocolUtil.skip(iprot, field.type); + break; + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + validate(); + } + + write(TProtocol oprot) { + validate(); + + oprot.writeStructBegin(_STRUCT_DESC); + oprot.writeFieldBegin(_I_FIELD_DESC); + oprot.writeI32(this.i); + oprot.writeFieldEnd(); + oprot.writeFieldBegin(_D_FIELD_DESC); + oprot.writeDouble(this.d); + oprot.writeFieldEnd(); + if (this.s != null) { + oprot.writeFieldBegin(_S_FIELD_DESC); + oprot.writeString(this.s); + oprot.writeFieldEnd(); + } + if (this.l != null) { + oprot.writeFieldBegin(_L_FIELD_DESC); + { + oprot.writeListBegin(new TList(TType.STRING, this.l.length)); + for (var elem77 in this.l) { + oprot.writeString(elem77); + } + oprot.writeListEnd(); + } + oprot.writeFieldEnd(); + } + oprot.writeFieldBegin(_B_FIELD_DESC); + oprot.writeBool(this.b); + oprot.writeFieldEnd(); + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + String toString() { + StringBuffer ret = new StringBuffer("TestTObject("); + + ret.write("i:"); + ret.write(this.i); + + ret.write(", "); + ret.write("d:"); + ret.write(this.d); + + ret.write(", "); + ret.write("s:"); + if (this.s == null) { + ret.write("null"); + } else { + ret.write(this.s); + } + + ret.write(", "); + ret.write("l:"); + if (this.l == null) { + ret.write("null"); + } else { + ret.write(this.l); + } + + ret.write(", "); + ret.write("b:"); + ret.write(this.b); + + ret.write(")"); + + return ret.toString(); + } + + validate() { + // check for required fields + // check that fields of type enum have valid values + } + +} diff --git a/src/jaegertracing/thrift/lib/dart/test/t_application_error_test.dart b/src/jaegertracing/thrift/lib/dart/test/t_application_error_test.dart new file mode 100644 index 000000000..511d8d691 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/test/t_application_error_test.dart @@ -0,0 +1,46 @@ +// 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. + +library thrift.test.t_application_error_test; + +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + TProtocol protocol; + + setUp(() { + protocol = new TBinaryProtocol(new TBufferedTransport()); + }); + + test('Write and read an application error', () { + var expectedType = TApplicationErrorType.INTERNAL_ERROR; + var expectedMessage = 'test error message'; + + TApplicationError error = + new TApplicationError(expectedType, expectedMessage); + error.write(protocol); + + protocol.transport.flush(); + + TApplicationError subject = TApplicationError.read(protocol); + + expect(subject, isNotNull); + expect(subject.type, expectedType); + expect(subject.message, expectedMessage); + }); +} diff --git a/src/jaegertracing/thrift/lib/dart/test/transport/t_framed_transport_test.dart b/src/jaegertracing/thrift/lib/dart/test/transport/t_framed_transport_test.dart new file mode 100644 index 000000000..7ab490539 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/test/transport/t_framed_transport_test.dart @@ -0,0 +1,175 @@ +// 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. + +library thrift.test.transport.t_framed_transport_test; + +import 'dart:async'; +import 'dart:typed_data' show Uint8List; + +import 'package:dart2_constant/convert.dart' show utf8; +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + group('TFramedTransport partial reads', () { + final flushAwaitDuration = new Duration(seconds: 10); + + FakeReadOnlySocket socket; + TSocketTransport socketTransport; + TFramedTransport transport; + var messageAvailable; + + setUp(() { + socket = new FakeReadOnlySocket(); + socketTransport = new TClientSocketTransport(socket); + transport = new TFramedTransport(socketTransport); + messageAvailable = false; + }); + + expectNoReadableBytes() { + var readBuffer = new Uint8List(128); + var readBytes = transport.read(readBuffer, 0, readBuffer.lengthInBytes); + expect(readBytes, 0); + expect(messageAvailable, false); + } + + test('Test transport reads messages where header and body are sent separately', () async { + // buffer into which we'll read + var readBuffer = new Uint8List(10); + var readBytes; + + // registers for readable bytes + var flushFuture = transport.flush().timeout(flushAwaitDuration); + flushFuture.then((_) { + messageAvailable = true; + }); + + // write header bytes + socket.messageController.add(new Uint8List.fromList([0x00, 0x00, 0x00, 0x06])); + + // you shouldn't be able to get any bytes from the read, + // because the header has been consumed internally + expectNoReadableBytes(); + + // write first batch of body + socket.messageController.add(new Uint8List.fromList(utf8.encode("He"))); + + // you shouldn't be able to get any bytes from the read, + // because the frame has been consumed internally + expectNoReadableBytes(); + + // write second batch of body + socket.messageController.add(new Uint8List.fromList(utf8.encode("llo!"))); + + // have to wait for the flush to complete, + // because it's only then that the frame is available for reading + await flushFuture; + expect(messageAvailable, true); + + // at this point the frame is complete, so we expect the read to complete + readBytes = transport.read(readBuffer, 0, readBuffer.lengthInBytes); + expect(readBytes, 6); + expect(readBuffer.sublist(0, 6), utf8.encode("Hello!")); + }); + + test('Test transport reads messages where header is sent in pieces ' + 'and body is also sent in pieces', () async { + // buffer into which we'll read + var readBuffer = new Uint8List(10); + var readBytes; + + // registers for readable bytes + var flushFuture = transport.flush().timeout(flushAwaitDuration); + flushFuture.then((_) { + messageAvailable = true; + }); + + // write first part of header bytes + socket.messageController.add(new Uint8List.fromList([0x00, 0x00])); + + // you shouldn't be able to get any bytes from the read + expectNoReadableBytes(); + + // write second part of header bytes + socket.messageController.add(new Uint8List.fromList([0x00, 0x03])); + + // you shouldn't be able to get any bytes from the read again + // because only the header was read, and there's no frame body + readBytes = expectNoReadableBytes(); + + // write first batch of body + socket.messageController.add(new Uint8List.fromList(utf8.encode("H"))); + + // you shouldn't be able to get any bytes from the read, + // because the frame has been consumed internally + expectNoReadableBytes(); + + // write second batch of body + socket.messageController.add(new Uint8List.fromList(utf8.encode("i!"))); + + // have to wait for the flush to complete, + // because it's only then that the frame is available for reading + await flushFuture; + expect(messageAvailable, true); + + // at this point the frame is complete, so we expect the read to complete + readBytes = transport.read(readBuffer, 0, readBuffer.lengthInBytes); + expect(readBytes, 3); + expect(readBuffer.sublist(0, 3), utf8.encode("Hi!")); + }); + }); +} + + + +class FakeReadOnlySocket extends TSocket { + + StreamController<Uint8List> messageController = new StreamController<Uint8List>(sync: true); + StreamController<Object> errorController = new StreamController<Object>(); + StreamController<TSocketState> stateController = new StreamController<TSocketState>(); + + @override + Future close() { + // noop + } + + @override + bool get isClosed => false; + + @override + bool get isOpen => true; + + @override + Stream<Object> get onError => errorController.stream; + + @override + Stream<Uint8List> get onMessage => messageController.stream; + + @override + Stream<TSocketState> get onState => stateController.stream; + + @override + Future open() { + // noop + } + + @override + void send(Uint8List data) { + // noop + } +} + diff --git a/src/jaegertracing/thrift/lib/dart/test/transport/t_http_transport_test.dart b/src/jaegertracing/thrift/lib/dart/test/transport/t_http_transport_test.dart new file mode 100644 index 000000000..03ccede9a --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/test/transport/t_http_transport_test.dart @@ -0,0 +1,164 @@ +// 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. + +library thrift.test.transport.t_socket_transport_test; + +import 'dart:async'; +import 'dart:convert' show Encoding; +import 'dart:convert' show Utf8Codec; +import 'dart:typed_data' show Uint8List; + +import 'package:dart2_constant/convert.dart' show base64; +import 'package:http/http.dart' show BaseRequest; +import 'package:http/http.dart' show Client; +import 'package:http/http.dart' show Response; +import 'package:http/http.dart' show StreamedResponse; +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + const utf8Codec = const Utf8Codec(); + + group('THttpClientTransport', () { + FakeHttpClient client; + THttpClientTransport transport; + + setUp(() { + client = new FakeHttpClient(sync: false); + var config = new THttpConfig(Uri.parse('http://localhost'), {}); + transport = new THttpClientTransport(client, config); + }); + + test('Test transport sends body', () async { + var expectedText = 'my request'; + transport.writeAll(utf8Codec.encode(expectedText)); + + expect(client.postRequest, isEmpty); + + await transport.flush(); + + expect(client.postRequest, isNotEmpty); + + var requestText = utf8Codec.decode(base64.decode(client.postRequest)); + expect(requestText, expectedText); + }); + + test('Test transport receives response', () async { + var expectedText = 'my response'; + var expectedBytes = utf8Codec.encode(expectedText); + client.postResponse = base64.encode(expectedBytes); + + transport.writeAll(utf8Codec.encode('my request')); + expect(transport.hasReadData, isFalse); + + await transport.flush(); + + expect(transport.hasReadData, isTrue); + + var buffer = new Uint8List(expectedBytes.length); + transport.readAll(buffer, 0, expectedBytes.length); + + var bufferText = utf8Codec.decode(buffer); + expect(bufferText, expectedText); + }); + }); + + group('THttpClientTransport with multiple messages', () { + FakeHttpClient client; + THttpClientTransport transport; + + setUp(() { + client = new FakeHttpClient(sync: true); + var config = new THttpConfig(Uri.parse('http://localhost'), {}); + transport = new THttpClientTransport(client, config); + }); + + test('Test read correct buffer after flush', () async { + String bufferText; + var expectedText = 'response 1'; + var expectedBytes = utf8Codec.encode(expectedText); + + // prepare a response + transport.writeAll(utf8Codec.encode('request 1')); + client.postResponse = base64.encode(expectedBytes); + + Future responseReady = transport.flush().then((_) { + var buffer = new Uint8List(expectedBytes.length); + transport.readAll(buffer, 0, expectedBytes.length); + bufferText = utf8Codec.decode(buffer); + }); + + // prepare a second response + transport.writeAll(utf8Codec.encode('request 2')); + var response2Bytes = utf8Codec.encode('response 2'); + client.postResponse = base64.encode(response2Bytes); + await transport.flush(); + + await responseReady; + expect(bufferText, expectedText); + }); + }); +} + +class FakeHttpClient implements Client { + String postResponse = ''; + String postRequest = ''; + + final bool sync; + + FakeHttpClient({this.sync: false}); + + Future<Response> post(url, + {Map<String, String> headers, body, Encoding encoding}) { + postRequest = body; + var response = new Response(postResponse, 200); + + if (sync) { + return new Future.sync(() => response); + } else { + return new Future.value(response); + } + } + + Future<Response> head(url, {Map<String, String> headers}) => + throw new UnimplementedError(); + + Future<Response> get(url, {Map<String, String> headers}) => + throw new UnimplementedError(); + + Future<Response> put(url, + {Map<String, String> headers, body, Encoding encoding}) => + throw new UnimplementedError(); + + Future<Response> patch(url, + {Map<String, String> headers, body, Encoding encoding}) => + throw new UnimplementedError(); + + Future<Response> delete(url, {Map<String, String> headers}) => + throw new UnimplementedError(); + + Future<String> read(url, {Map<String, String> headers}) => + throw new UnimplementedError(); + + Future<Uint8List> readBytes(url, {Map<String, String> headers}) => + throw new UnimplementedError(); + + Future<StreamedResponse> send(BaseRequest request) => + throw new UnimplementedError(); + + void close() => throw new UnimplementedError(); +} diff --git a/src/jaegertracing/thrift/lib/dart/test/transport/t_socket_transport_test.dart b/src/jaegertracing/thrift/lib/dart/test/transport/t_socket_transport_test.dart new file mode 100644 index 000000000..90bffbe54 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/test/transport/t_socket_transport_test.dart @@ -0,0 +1,311 @@ +// 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. + +library thrift.test.transport.t_socket_transport_test; + +import 'dart:async'; +import 'dart:convert' show Utf8Codec; +import 'dart:typed_data' show Uint8List; + +import 'package:dart2_constant/convert.dart' show base64; +import 'package:dart2_constant/core.dart' as core; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +void main() { + const utf8Codec = const Utf8Codec(); + + final requestText = 'my test request'; + final requestBytes = new Uint8List.fromList(utf8Codec.encode(requestText)); + final requestBase64 = base64.encode(requestBytes); + + final responseText = 'response 1'; + final responseBytes = new Uint8List.fromList(utf8Codec.encode(responseText)); + final responseBase64 = base64.encode(responseBytes); + + final framedResponseBase64 = base64.encode(_getFramedResponse(responseBytes)); + + group('TClientSocketTransport', () { + FakeSocket socket; + TTransport transport; + + setUp(() async { + socket = new FakeSocket(sync: false); + await socket.open(); + transport = new TClientSocketTransport(socket); + await transport.open(); + transport.writeAll(requestBytes); + }); + + test('Test client sending data over transport', () async { + expect(socket.sendPayload, isNull); + + Future responseReady = transport.flush(); + + // allow microtask events to finish + await new Future.value(); + + expect(socket.sendPayload, isNotNull); + expect(socket.sendPayload, requestBytes); + + // simulate a response + socket.receiveFakeMessage(responseBase64); + + await responseReady; + var buffer = new Uint8List(responseBytes.length); + transport.readAll(buffer, 0, responseBytes.length); + var bufferText = utf8Codec.decode(buffer); + + expect(bufferText, responseText); + }); + }, timeout: new Timeout(new Duration(seconds: 1))); + + group('TClientSocketTransport with FramedTransport', () { + FakeSocket socket; + TTransport transport; + + setUp(() async { + socket = new FakeSocket(sync: true); + await socket.open(); + + transport = new TFramedTransport(new TClientSocketTransport(socket)); + await transport.open(); + transport.writeAll(requestBytes); + }); + + test('Test client sending data over framed transport', () async { + String bufferText; + + Future responseReady = transport.flush().then((_) { + var buffer = new Uint8List(responseBytes.length); + transport.readAll(buffer, 0, responseBytes.length); + bufferText = utf8Codec.decode(buffer); + }); + + // simulate a response + socket.receiveFakeMessage(framedResponseBase64); + + await responseReady; + expect(bufferText, responseText); + }); + }, timeout: new Timeout(new Duration(seconds: 1))); + + group('TAsyncClientSocketTransport', () { + FakeSocket socket; + FakeProtocolFactory protocolFactory; + TTransport transport; + + setUp(() async { + socket = new FakeSocket(sync: true); + await socket.open(); + + protocolFactory = new FakeProtocolFactory(); + protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123); + transport = new TAsyncClientSocketTransport( + socket, new TMessageReader(protocolFactory), + responseTimeout: core.Duration.zero); + await transport.open(); + transport.writeAll(requestBytes); + }); + + test('Test response correlates to correct request', () async { + String bufferText; + + Future responseReady = transport.flush().then((_) { + var buffer = new Uint8List(responseBytes.length); + transport.readAll(buffer, 0, responseBytes.length); + bufferText = utf8Codec.decode(buffer); + }); + + // simulate a response + protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123); + socket.receiveFakeMessage(responseBase64); + + // simulate a second response + var response2Text = 'response 2'; + var response2Bytes = + new Uint8List.fromList(utf8Codec.encode(response2Text)); + var response2Base64 = base64.encode(response2Bytes); + protocolFactory.message = new TMessage('foo2', TMessageType.REPLY, 124); + socket.receiveFakeMessage(response2Base64); + + await responseReady; + expect(bufferText, responseText); + }); + + test('Test response timeout', () async { + Future responseReady = transport.flush(); + expect(responseReady, throwsA(new isInstanceOf<TimeoutException>())); + }); + }, timeout: new Timeout(new Duration(seconds: 1))); + + group('TAsyncClientSocketTransport with TFramedTransport', () { + FakeSocket socket; + FakeProtocolFactory protocolFactory; + TTransport transport; + + setUp(() async { + socket = new FakeSocket(sync: true); + await socket.open(); + + protocolFactory = new FakeProtocolFactory(); + protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123); + var messageReader = new TMessageReader(protocolFactory, + byteOffset: TFramedTransport.headerByteCount); + + transport = new TFramedTransport(new TAsyncClientSocketTransport( + socket, messageReader, + responseTimeout: core.Duration.zero)); + await transport.open(); + transport.writeAll(requestBytes); + }); + + test('Test async client sending data over framed transport', () async { + String bufferText; + + Future responseReady = transport.flush().then((_) { + var buffer = new Uint8List(responseBytes.length); + transport.readAll(buffer, 0, responseBytes.length); + bufferText = utf8Codec.decode(buffer); + }); + + // simulate a response + protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123); + socket.receiveFakeMessage(framedResponseBase64); + + await responseReady; + expect(bufferText, responseText); + }); + }, timeout: new Timeout(new Duration(seconds: 1))); + + group('TServerTransport', () { + test('Test server transport listens to socket', () async { + var socket = new FakeSocket(); + await socket.open(); + expect(socket.isOpen, isTrue); + + var transport = new TServerSocketTransport(socket); + expect(transport.hasReadData, isFalse); + + socket.receiveFakeMessage(requestBase64); + + // allow microtask events to finish + await new Future.value(); + + expect(transport.hasReadData, isTrue); + + var buffer = new Uint8List(requestBytes.length); + transport.readAll(buffer, 0, requestBytes.length); + + var bufferText = utf8Codec.decode(buffer); + expect(bufferText, requestText); + }); + + test('Test server sending data over transport', () async { + var socket = new FakeSocket(); + await socket.open(); + + var transport = new TServerSocketTransport(socket); + + transport.writeAll(responseBytes); + expect(socket.sendPayload, isNull); + + transport.flush(); + + // allow microtask events to finish + await new Future.value(); + + expect(socket.sendPayload, isNotNull); + expect(socket.sendPayload, responseBytes); + }); + }, timeout: new Timeout(new Duration(seconds: 1))); +} + +class FakeSocket extends TSocket { + final StreamController<TSocketState> _onStateController; + Stream<TSocketState> get onState => _onStateController.stream; + + final StreamController<Object> _onErrorController; + Stream<Object> get onError => _onErrorController.stream; + + final StreamController<Uint8List> _onMessageController; + Stream<Uint8List> get onMessage => _onMessageController.stream; + + FakeSocket({bool sync: false}) + : _onStateController = new StreamController.broadcast(sync: sync), + _onErrorController = new StreamController.broadcast(sync: sync), + _onMessageController = new StreamController.broadcast(sync: sync); + + bool _isOpen; + + bool get isOpen => _isOpen; + + bool get isClosed => !isOpen; + + Future open() async { + _isOpen = true; + _onStateController.add(TSocketState.OPEN); + } + + Future close() async { + _isOpen = false; + _onStateController.add(TSocketState.CLOSED); + } + + Uint8List _sendPayload; + Uint8List get sendPayload => _sendPayload; + + void send(Uint8List data) { + if (!isOpen) throw new StateError('The socket is not open'); + + _sendPayload = data; + } + + void receiveFakeMessage(String base64text) { + if (!isOpen) throw new StateError('The socket is not open'); + + var message = new Uint8List.fromList(base64.decode(base64text)); + _onMessageController.add(message); + } +} + +class FakeProtocolFactory implements TProtocolFactory { + FakeProtocolFactory(); + + TMessage message; + + getProtocol(TTransport transport) => new FakeProtocol(message); +} + +class FakeProtocol extends Mock implements TProtocol { + FakeProtocol(this._message); + + TMessage _message; + + readMessageBegin() => _message; +} + +Uint8List _getFramedResponse(Uint8List responseBytes) { + var byteOffset = TFramedTransport.headerByteCount; + var response = new Uint8List(byteOffset + responseBytes.length); + + response.buffer.asByteData().setInt32(0, responseBytes.length); + response.setAll(byteOffset, responseBytes); + + return response; +} diff --git a/src/jaegertracing/thrift/lib/dart/test/transport/t_transport_test.dart b/src/jaegertracing/thrift/lib/dart/test/transport/t_transport_test.dart new file mode 100644 index 000000000..0bb381ac8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/test/transport/t_transport_test.dart @@ -0,0 +1,41 @@ +// 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. + +library thrift.test.transport.t_socket_transport_test; + +import 'package:test/test.dart'; +import 'package:thrift/thrift.dart'; + +/// Common transport tests +void main() { + group('TTransportFactory', () { + test('transport is returned from base factory', () async { + TTransport result; + TTransport transport = null; + + var factory = new TTransportFactory(); + + result = await factory.getTransport(transport); + expect(result, isNull); + + transport = new TBufferedTransport(); + result = await factory.getTransport(transport); + + expect(result, transport); + }); + }); +} diff --git a/src/jaegertracing/thrift/lib/dart/tool/dev.dart b/src/jaegertracing/thrift/lib/dart/tool/dev.dart new file mode 100644 index 000000000..27f8b8fcf --- /dev/null +++ b/src/jaegertracing/thrift/lib/dart/tool/dev.dart @@ -0,0 +1,33 @@ +// 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. + +library tool.dev; + +import 'package:dart_dev/dart_dev.dart' show dev, config; + +main(List<String> args) async { + // https://github.com/Workiva/dart_dev + + var directories = ['lib/', 'test/', 'tool/']; + config.analyze.entryPoints = directories; + config.format.directories = directories; + config.copyLicense + ..licensePath = 'LICENSE_HEADER' + ..directories = directories; + + await dev(args); +} diff --git a/src/jaegertracing/thrift/lib/delphi/DelphiThrift.groupproj b/src/jaegertracing/thrift/lib/delphi/DelphiThrift.groupproj new file mode 100644 index 000000000..a172e496c --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/DelphiThrift.groupproj @@ -0,0 +1,156 @@ + <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ProjectGuid>{6BD327A5-7688-4263-B6A8-B15207CF4EC5}</ProjectGuid> + </PropertyGroup> + <ItemGroup> + <Projects Include="test\client.dproj"> + <Dependencies/> + </Projects> + <Projects Include="test\server.dproj"> + <Dependencies/> + </Projects> + <Projects Include="test\multiplexed\Multiplex.Test.Client.dproj"> + <Dependencies/> + </Projects> + <Projects Include="test\multiplexed\Multiplex.Test.Server.dproj"> + <Dependencies/> + </Projects> + <Projects Include="test\serializer\TestSerializer.dproj"> + <Dependencies/> + </Projects> + <Projects Include="test\skip\skiptest_version1.dproj"> + <Dependencies/> + </Projects> + <Projects Include="test\skip\skiptest_version2.dproj"> + <Dependencies/> + </Projects> + <Projects Include="test\typeregistry\TestTypeRegistry.dproj"> + <Dependencies/> + </Projects> + <Projects Include="..\..\tutorial\delphi\DelphiServer\DelphiServer.dproj"> + <Dependencies/> + </Projects> + <Projects Include="..\..\tutorial\delphi\DelphiClient\DelphiClient.dproj"> + <Dependencies/> + </Projects> + <Projects Include="test\keywords\ReservedKeywords.dproj"> + <Dependencies/> + </Projects> + </ItemGroup> + <ProjectExtensions> + <Borland.Personality>Default.Personality.12</Borland.Personality> + <Borland.ProjectType/> + <BorlandProject> + <Default.Personality/> + </BorlandProject> + </ProjectExtensions> + <Target Name="client"> + <MSBuild Projects="test\client.dproj"/> + </Target> + <Target Name="client:Clean"> + <MSBuild Projects="test\client.dproj" Targets="Clean"/> + </Target> + <Target Name="client:Make"> + <MSBuild Projects="test\client.dproj" Targets="Make"/> + </Target> + <Target Name="server"> + <MSBuild Projects="test\server.dproj"/> + </Target> + <Target Name="server:Clean"> + <MSBuild Projects="test\server.dproj" Targets="Clean"/> + </Target> + <Target Name="server:Make"> + <MSBuild Projects="test\server.dproj" Targets="Make"/> + </Target> + <Target Name="Multiplex_Test_Client"> + <MSBuild Projects="test\multiplexed\Multiplex.Test.Client.dproj"/> + </Target> + <Target Name="Multiplex_Test_Client:Clean"> + <MSBuild Projects="test\multiplexed\Multiplex.Test.Client.dproj" Targets="Clean"/> + </Target> + <Target Name="Multiplex_Test_Client:Make"> + <MSBuild Projects="test\multiplexed\Multiplex.Test.Client.dproj" Targets="Make"/> + </Target> + <Target Name="Multiplex_Test_Server"> + <MSBuild Projects="test\multiplexed\Multiplex.Test.Server.dproj"/> + </Target> + <Target Name="Multiplex_Test_Server:Clean"> + <MSBuild Projects="test\multiplexed\Multiplex.Test.Server.dproj" Targets="Clean"/> + </Target> + <Target Name="Multiplex_Test_Server:Make"> + <MSBuild Projects="test\multiplexed\Multiplex.Test.Server.dproj" Targets="Make"/> + </Target> + <Target Name="TestSerializer"> + <MSBuild Projects="test\serializer\TestSerializer.dproj"/> + </Target> + <Target Name="TestSerializer:Clean"> + <MSBuild Projects="test\serializer\TestSerializer.dproj" Targets="Clean"/> + </Target> + <Target Name="TestSerializer:Make"> + <MSBuild Projects="test\serializer\TestSerializer.dproj" Targets="Make"/> + </Target> + <Target Name="skiptest_version1"> + <MSBuild Projects="test\skip\skiptest_version1.dproj"/> + </Target> + <Target Name="skiptest_version1:Clean"> + <MSBuild Projects="test\skip\skiptest_version1.dproj" Targets="Clean"/> + </Target> + <Target Name="skiptest_version1:Make"> + <MSBuild Projects="test\skip\skiptest_version1.dproj" Targets="Make"/> + </Target> + <Target Name="skiptest_version2"> + <MSBuild Projects="test\skip\skiptest_version2.dproj"/> + </Target> + <Target Name="skiptest_version2:Clean"> + <MSBuild Projects="test\skip\skiptest_version2.dproj" Targets="Clean"/> + </Target> + <Target Name="skiptest_version2:Make"> + <MSBuild Projects="test\skip\skiptest_version2.dproj" Targets="Make"/> + </Target> + <Target Name="TestTypeRegistry"> + <MSBuild Projects="test\typeregistry\TestTypeRegistry.dproj"/> + </Target> + <Target Name="TestTypeRegistry:Clean"> + <MSBuild Projects="test\typeregistry\TestTypeRegistry.dproj" Targets="Clean"/> + </Target> + <Target Name="TestTypeRegistry:Make"> + <MSBuild Projects="test\typeregistry\TestTypeRegistry.dproj" Targets="Make"/> + </Target> + <Target Name="DelphiServer"> + <MSBuild Projects="..\..\tutorial\delphi\DelphiServer\DelphiServer.dproj"/> + </Target> + <Target Name="DelphiServer:Clean"> + <MSBuild Projects="..\..\tutorial\delphi\DelphiServer\DelphiServer.dproj" Targets="Clean"/> + </Target> + <Target Name="DelphiServer:Make"> + <MSBuild Projects="..\..\tutorial\delphi\DelphiServer\DelphiServer.dproj" Targets="Make"/> + </Target> + <Target Name="DelphiClient"> + <MSBuild Projects="..\..\tutorial\delphi\DelphiClient\DelphiClient.dproj"/> + </Target> + <Target Name="DelphiClient:Clean"> + <MSBuild Projects="..\..\tutorial\delphi\DelphiClient\DelphiClient.dproj" Targets="Clean"/> + </Target> + <Target Name="DelphiClient:Make"> + <MSBuild Projects="..\..\tutorial\delphi\DelphiClient\DelphiClient.dproj" Targets="Make"/> + </Target> + <Target Name="ReservedKeywords"> + <MSBuild Projects="test\keywords\ReservedKeywords.dproj"/> + </Target> + <Target Name="ReservedKeywords:Clean"> + <MSBuild Projects="test\keywords\ReservedKeywords.dproj" Targets="Clean"/> + </Target> + <Target Name="ReservedKeywords:Make"> + <MSBuild Projects="test\keywords\ReservedKeywords.dproj" Targets="Make"/> + </Target> + <Target Name="Build"> + <CallTarget Targets="client;server;Multiplex_Test_Client;Multiplex_Test_Server;TestSerializer;skiptest_version1;skiptest_version2;TestTypeRegistry;DelphiServer;DelphiClient;ReservedKeywords"/> + </Target> + <Target Name="Clean"> + <CallTarget Targets="client:Clean;server:Clean;Multiplex_Test_Client:Clean;Multiplex_Test_Server:Clean;TestSerializer:Clean;skiptest_version1:Clean;skiptest_version2:Clean;TestTypeRegistry:Clean;DelphiServer:Clean;DelphiClient:Clean;ReservedKeywords:Clean"/> + </Target> + <Target Name="Make"> + <CallTarget Targets="client:Make;server:Make;Multiplex_Test_Client:Make;Multiplex_Test_Server:Make;TestSerializer:Make;skiptest_version1:Make;skiptest_version2:Make;TestTypeRegistry:Make;DelphiServer:Make;DelphiClient:Make;ReservedKeywords:Make"/> + </Target> + <Import Condition="Exists('$(BDS)\Bin\CodeGear.Group.Targets')" Project="$(BDS)\Bin\CodeGear.Group.Targets"/> + </Project> diff --git a/src/jaegertracing/thrift/lib/delphi/README.md b/src/jaegertracing/thrift/lib/delphi/README.md new file mode 100644 index 000000000..91799d04d --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/README.md @@ -0,0 +1,30 @@ +Thrift Delphi Software Library + +License +======= + +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. + +Using Thrift with Delphi +==================== + +The Thrift Delphi Library requires at least Delphi 2010. + +Because the Library heavily relies on generics, using it +with earlier versions (such as Delphi 7) will *not* work. + diff --git a/src/jaegertracing/thrift/lib/delphi/coding_standards.md b/src/jaegertracing/thrift/lib/delphi/coding_standards.md new file mode 100644 index 000000000..fa0390bb5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/coding_standards.md @@ -0,0 +1 @@ +Please follow [General Coding Standards](/doc/coding_standards.md) diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Collections.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Collections.pas new file mode 100644 index 000000000..3b56fe205 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Collections.pas @@ -0,0 +1,692 @@ +(* + * 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. + *) + +unit Thrift.Collections; + +interface + +uses + SysUtils, Generics.Collections, Generics.Defaults, Thrift.Utils; + +type + +{$IF CompilerVersion < 21.0} + TArray<T> = array of T; +{$IFEND} + + IThriftContainer = interface( ISupportsToString) + ['{E05C0F9D-A4F5-491D-AADA-C926B4BDB6E4}'] + end; + + + IThriftDictionary<TKey,TValue> = interface(IThriftContainer) + ['{25EDD506-F9D1-4008-A40F-5940364B7E46}'] + function GetEnumerator: TEnumerator<TPair<TKey,TValue>>; + + function GetKeys: TDictionary<TKey,TValue>.TKeyCollection; + function GetValues: TDictionary<TKey,TValue>.TValueCollection; + function GetItem(const Key: TKey): TValue; + procedure SetItem(const Key: TKey; const Value: TValue); + function GetCount: Integer; + + procedure Add(const Key: TKey; const Value: TValue); + procedure Remove(const Key: TKey); +{$IF CompilerVersion >= 21.0} + function ExtractPair(const Key: TKey): TPair<TKey,TValue>; +{$IFEND} + procedure Clear; + procedure TrimExcess; + function TryGetValue(const Key: TKey; out Value: TValue): Boolean; + procedure AddOrSetValue(const Key: TKey; const Value: TValue); + function ContainsKey(const Key: TKey): Boolean; + function ContainsValue(const Value: TValue): Boolean; + function ToArray: TArray<TPair<TKey,TValue>>; + + property Items[const Key: TKey]: TValue read GetItem write SetItem; default; + property Count: Integer read GetCount; + property Keys: TDictionary<TKey,TValue>.TKeyCollection read GetKeys; + property Values: TDictionary<TKey,TValue>.TValueCollection read GetValues; + end; + + TThriftDictionaryImpl<TKey,TValue> = class( TInterfacedObject, IThriftDictionary<TKey,TValue>, IThriftContainer, ISupportsToString) + private + FDictionaly : TDictionary<TKey,TValue>; + protected + function GetEnumerator: TEnumerator<TPair<TKey,TValue>>; + + function GetKeys: TDictionary<TKey,TValue>.TKeyCollection; + function GetValues: TDictionary<TKey,TValue>.TValueCollection; + function GetItem(const Key: TKey): TValue; + procedure SetItem(const Key: TKey; const Value: TValue); + function GetCount: Integer; + + procedure Add(const Key: TKey; const Value: TValue); + procedure Remove(const Key: TKey); +{$IF CompilerVersion >= 21.0} + function ExtractPair(const Key: TKey): TPair<TKey,TValue>; +{$IFEND} + procedure Clear; + procedure TrimExcess; + function TryGetValue(const Key: TKey; out Value: TValue): Boolean; + procedure AddOrSetValue(const Key: TKey; const Value: TValue); + function ContainsKey(const Key: TKey): Boolean; + function ContainsValue(const Value: TValue): Boolean; + function ToArray: TArray<TPair<TKey,TValue>>; + property Items[const Key: TKey]: TValue read GetItem write SetItem; default; + property Count: Integer read GetCount; + property Keys: TDictionary<TKey,TValue>.TKeyCollection read GetKeys; + property Values: TDictionary<TKey,TValue>.TValueCollection read GetValues; + public + constructor Create(ACapacity: Integer = 0); + destructor Destroy; override; + function ToString : string; override; + end; + + IThriftList<T> = interface(IThriftContainer) + ['{29BEEE31-9CB4-401B-AA04-5148A75F473B}'] + function GetEnumerator: TEnumerator<T>; + function GetCapacity: Integer; + procedure SetCapacity(Value: Integer); + function GetCount: Integer; + procedure SetCount(Value: Integer); + function GetItem(Index: Integer): T; + procedure SetItem(Index: Integer; const Value: T); + function Add(const Value: T): Integer; + procedure AddRange(const Values: array of T); overload; + procedure AddRange(const Collection: IEnumerable<T>); overload; + procedure AddRange(Collection: TEnumerable<T>); overload; + procedure Insert(Index: Integer; const Value: T); + procedure InsertRange(Index: Integer; const Values: array of T); overload; + procedure InsertRange(Index: Integer; const Collection: IEnumerable<T>); overload; + procedure InsertRange(Index: Integer; const Collection: TEnumerable<T>); overload; + function Remove(const Value: T): Integer; + procedure Delete(Index: Integer); + procedure DeleteRange(AIndex, ACount: Integer); + function Extract(const Value: T): T; +{$IF CompilerVersion >= 21.0} + procedure Exchange(Index1, Index2: Integer); + procedure Move(CurIndex, NewIndex: Integer); + function First: T; + function Last: T; +{$IFEND} + procedure Clear; + function Contains(const Value: T): Boolean; + function IndexOf(const Value: T): Integer; + function LastIndexOf(const Value: T): Integer; + procedure Reverse; + procedure Sort; overload; + procedure Sort(const AComparer: IComparer<T>); overload; + function BinarySearch(const Item: T; out Index: Integer): Boolean; overload; + function BinarySearch(const Item: T; out Index: Integer; const AComparer: IComparer<T>): Boolean; overload; + procedure TrimExcess; + function ToArray: TArray<T>; + property Capacity: Integer read GetCapacity write SetCapacity; + property Count: Integer read GetCount write SetCount; + property Items[Index: Integer]: T read GetItem write SetItem; default; + end; + + TThriftListImpl<T> = class( TInterfacedObject, IThriftList<T>, IThriftContainer, ISupportsToString) + private + FList : TList<T>; + protected + function GetEnumerator: TEnumerator<T>; + function GetCapacity: Integer; + procedure SetCapacity(Value: Integer); + function GetCount: Integer; + procedure SetCount(Value: Integer); + function GetItem(Index: Integer): T; + procedure SetItem(Index: Integer; const Value: T); + function Add(const Value: T): Integer; + procedure AddRange(const Values: array of T); overload; + procedure AddRange(const Collection: IEnumerable<T>); overload; + procedure AddRange(Collection: TEnumerable<T>); overload; + procedure Insert(Index: Integer; const Value: T); + procedure InsertRange(Index: Integer; const Values: array of T); overload; + procedure InsertRange(Index: Integer; const Collection: IEnumerable<T>); overload; + procedure InsertRange(Index: Integer; const Collection: TEnumerable<T>); overload; + function Remove(const Value: T): Integer; + procedure Delete(Index: Integer); + procedure DeleteRange(AIndex, ACount: Integer); + function Extract(const Value: T): T; +{$IF CompilerVersion >= 21.0} + procedure Exchange(Index1, Index2: Integer); + procedure Move(CurIndex, NewIndex: Integer); + function First: T; + function Last: T; +{$IFEND} + procedure Clear; + function Contains(const Value: T): Boolean; + function IndexOf(const Value: T): Integer; + function LastIndexOf(const Value: T): Integer; + procedure Reverse; + procedure Sort; overload; + procedure Sort(const AComparer: IComparer<T>); overload; + function BinarySearch(const Item: T; out Index: Integer): Boolean; overload; + function BinarySearch(const Item: T; out Index: Integer; const AComparer: IComparer<T>): Boolean; overload; + procedure TrimExcess; + function ToArray: TArray<T>; + property Capacity: Integer read GetCapacity write SetCapacity; + property Count: Integer read GetCount write SetCount; + property Items[Index: Integer]: T read GetItem write SetItem; default; + public + constructor Create; + destructor Destroy; override; + function ToString : string; override; + end; + + IHashSet<TValue> = interface(IThriftContainer) + ['{0923A3B5-D4D4-48A8-91AD-40238E2EAD66}'] + function GetEnumerator: TEnumerator<TValue>; + function GetIsReadOnly: Boolean; + function GetCount: Integer; + property Count: Integer read GetCount; + property IsReadOnly: Boolean read GetIsReadOnly; + procedure Add( const item: TValue); + procedure Clear; + function Contains( const item: TValue): Boolean; + procedure CopyTo(var A: TArray<TValue>; arrayIndex: Integer); + function Remove( const item: TValue ): Boolean; + end; + + THashSetImpl<TValue> = class( TInterfacedObject, IHashSet<TValue>, IThriftContainer, ISupportsToString) + private + FDictionary : IThriftDictionary<TValue,Integer>; + FIsReadOnly: Boolean; + protected + function GetEnumerator: TEnumerator<TValue>; + function GetIsReadOnly: Boolean; + function GetCount: Integer; + property Count: Integer read GetCount; + property IsReadOnly: Boolean read FIsReadOnly; + procedure Add( const item: TValue); + procedure Clear; + function Contains( const item: TValue): Boolean; + procedure CopyTo(var A: TArray<TValue>; arrayIndex: Integer); + function Remove( const item: TValue ): Boolean; + public + constructor Create; + function ToString : string; override; + end; + +implementation + +{ THashSetImpl<TValue> } + +procedure THashSetImpl<TValue>.Add( const item: TValue); +begin + if not FDictionary.ContainsKey(item) then + begin + FDictionary.Add( item, 0); + end; +end; + +procedure THashSetImpl<TValue>.Clear; +begin + FDictionary.Clear; +end; + +function THashSetImpl<TValue>.Contains( const item: TValue): Boolean; +begin + Result := FDictionary.ContainsKey(item); +end; + +procedure THashSetImpl<TValue>.CopyTo(var A: TArray<TValue>; arrayIndex: Integer); +var + i : Integer; + Enumlator : TEnumerator<TValue>; +begin + Enumlator := GetEnumerator; + while Enumlator.MoveNext do + begin + A[arrayIndex] := Enumlator.Current; + Inc(arrayIndex); + end; +end; + +constructor THashSetImpl<TValue>.Create; +begin + inherited; + FDictionary := TThriftDictionaryImpl<TValue,Integer>.Create; +end; + +function THashSetImpl<TValue>.GetCount: Integer; +begin + Result := FDictionary.Count; +end; + +function THashSetImpl<TValue>.GetEnumerator: TEnumerator<TValue>; +begin + Result := FDictionary.Keys.GetEnumerator; +end; + +function THashSetImpl<TValue>.GetIsReadOnly: Boolean; +begin + Result := FIsReadOnly; +end; + +function THashSetImpl<TValue>.Remove( const item: TValue): Boolean; +begin + Result := False; + if FDictionary.ContainsKey( item ) then + begin + FDictionary.Remove( item ); + Result := not FDictionary.ContainsKey( item ); + end; +end; + +function THashSetImpl<TValue>.ToString : string; +var elm : TValue; + sb : TThriftStringBuilder; + first : Boolean; +begin + sb := TThriftStringBuilder.Create('{'); + try + first := TRUE; + for elm in FDictionary.Keys do begin + if first + then first := FALSE + else sb.Append(', '); + + sb.Append( StringUtils<TValue>.ToString(elm)); + end; + sb.Append('}'); + Result := sb.ToString; + finally + sb.Free; + end; +end; + +{ TThriftDictionaryImpl<TKey, TValue> } + +procedure TThriftDictionaryImpl<TKey, TValue>.Add(const Key: TKey; + const Value: TValue); +begin + FDictionaly.Add( Key, Value); +end; + +procedure TThriftDictionaryImpl<TKey, TValue>.AddOrSetValue(const Key: TKey; + const Value: TValue); +begin + FDictionaly.AddOrSetValue( Key, Value); +end; + +procedure TThriftDictionaryImpl<TKey, TValue>.Clear; +begin + FDictionaly.Clear; +end; + +function TThriftDictionaryImpl<TKey, TValue>.ContainsKey( + const Key: TKey): Boolean; +begin + Result := FDictionaly.ContainsKey( Key ); +end; + +function TThriftDictionaryImpl<TKey, TValue>.ContainsValue( + const Value: TValue): Boolean; +begin + Result := FDictionaly.ContainsValue( Value ); +end; + +constructor TThriftDictionaryImpl<TKey, TValue>.Create(ACapacity: Integer); +begin + inherited Create; + FDictionaly := TDictionary<TKey,TValue>.Create( ACapacity ); +end; + +destructor TThriftDictionaryImpl<TKey, TValue>.Destroy; +begin + FDictionaly.Free; + inherited; +end; + +{$IF CompilerVersion >= 21.0} +function TThriftDictionaryImpl<TKey, TValue>.ExtractPair( const Key: TKey): TPair<TKey, TValue>; +begin + Result := FDictionaly.ExtractPair( Key); +end; +{$IFEND} + +function TThriftDictionaryImpl<TKey, TValue>.GetCount: Integer; +begin + Result := FDictionaly.Count; +end; + +function TThriftDictionaryImpl<TKey, TValue>.GetEnumerator: TEnumerator<TPair<TKey, TValue>>; +begin + Result := FDictionaly.GetEnumerator; +end; + +function TThriftDictionaryImpl<TKey, TValue>.GetItem(const Key: TKey): TValue; +begin + Result := FDictionaly.Items[Key]; +end; + +function TThriftDictionaryImpl<TKey, TValue>.GetKeys: TDictionary<TKey, TValue>.TKeyCollection; +begin + Result := FDictionaly.Keys; +end; + +function TThriftDictionaryImpl<TKey, TValue>.GetValues: TDictionary<TKey, TValue>.TValueCollection; +begin + Result := FDictionaly.Values; +end; + +procedure TThriftDictionaryImpl<TKey, TValue>.Remove(const Key: TKey); +begin + FDictionaly.Remove( Key ); +end; + +procedure TThriftDictionaryImpl<TKey, TValue>.SetItem(const Key: TKey; + const Value: TValue); +begin + FDictionaly.AddOrSetValue( Key, Value); +end; + +function TThriftDictionaryImpl<TKey, TValue>.ToArray: TArray<TPair<TKey, TValue>>; +{$IF CompilerVersion < 22.0} +var + x : TPair<TKey, TValue>; + i : Integer; +{$IFEND} +begin +{$IF CompilerVersion < 22.0} + SetLength(Result, Count); + i := 0; + for x in FDictionaly do + begin + Result[i] := x; + Inc( i ); + end; +{$ELSE} + Result := FDictionaly.ToArray; +{$IFEND} +end; + +function TThriftDictionaryImpl<TKey, TValue>.ToString : string; +var pair : TPair<TKey, TValue>; + sb : TThriftStringBuilder; + first : Boolean; +begin + sb := TThriftStringBuilder.Create('{'); + try + first := TRUE; + for pair in FDictionaly do begin + if first + then first := FALSE + else sb.Append(', '); + + sb.Append( '('); + sb.Append( StringUtils<TKey>.ToString(pair.Key)); + sb.Append(' => '); + sb.Append( StringUtils<TValue>.ToString(pair.Value)); + sb.Append(')'); + end; + sb.Append('}'); + Result := sb.ToString; + finally + sb.Free; + end; +end; + +procedure TThriftDictionaryImpl<TKey, TValue>.TrimExcess; +begin + FDictionaly.TrimExcess; +end; + +function TThriftDictionaryImpl<TKey, TValue>.TryGetValue(const Key: TKey; + out Value: TValue): Boolean; +begin + Result := FDictionaly.TryGetValue( Key, Value); +end; + +{ TThriftListImpl<T> } + +function TThriftListImpl<T>.Add(const Value: T): Integer; +begin + Result := FList.Add( Value ); +end; + +procedure TThriftListImpl<T>.AddRange(Collection: TEnumerable<T>); +begin + FList.AddRange( Collection ); +end; + +procedure TThriftListImpl<T>.AddRange(const Collection: IEnumerable<T>); +begin + FList.AddRange( Collection ); +end; + +procedure TThriftListImpl<T>.AddRange(const Values: array of T); +begin + FList.AddRange( Values ); +end; + +function TThriftListImpl<T>.BinarySearch(const Item: T; + out Index: Integer): Boolean; +begin + Result := FList.BinarySearch( Item, Index); +end; + +function TThriftListImpl<T>.BinarySearch(const Item: T; out Index: Integer; + const AComparer: IComparer<T>): Boolean; +begin + Result := FList.BinarySearch( Item, Index, AComparer); +end; + +procedure TThriftListImpl<T>.Clear; +begin + FList.Clear; +end; + +function TThriftListImpl<T>.Contains(const Value: T): Boolean; +begin + Result := FList.Contains( Value ); +end; + +constructor TThriftListImpl<T>.Create; +begin + inherited; + FList := TList<T>.Create; +end; + +procedure TThriftListImpl<T>.Delete(Index: Integer); +begin + FList.Delete( Index ) +end; + +procedure TThriftListImpl<T>.DeleteRange(AIndex, ACount: Integer); +begin + FList.DeleteRange( AIndex, ACount) +end; + +destructor TThriftListImpl<T>.Destroy; +begin + FList.Free; + inherited; +end; + +{$IF CompilerVersion >= 21.0} +procedure TThriftListImpl<T>.Exchange(Index1, Index2: Integer); +begin + FList.Exchange( Index1, Index2 ) +end; +{$IFEND} + +function TThriftListImpl<T>.Extract(const Value: T): T; +begin + Result := FList.Extract( Value ) +end; + +{$IF CompilerVersion >= 21.0} +function TThriftListImpl<T>.First: T; +begin + Result := FList.First; +end; +{$IFEND} + +function TThriftListImpl<T>.GetCapacity: Integer; +begin + Result := FList.Capacity; +end; + +function TThriftListImpl<T>.GetCount: Integer; +begin + Result := FList.Count; +end; + +function TThriftListImpl<T>.GetEnumerator: TEnumerator<T>; +begin + Result := FList.GetEnumerator; +end; + +function TThriftListImpl<T>.GetItem(Index: Integer): T; +begin + Result := FList[Index]; +end; + +function TThriftListImpl<T>.IndexOf(const Value: T): Integer; +begin + Result := FList.IndexOf( Value ); +end; + +procedure TThriftListImpl<T>.Insert(Index: Integer; const Value: T); +begin + FList.Insert( Index, Value); +end; + +procedure TThriftListImpl<T>.InsertRange(Index: Integer; + const Collection: TEnumerable<T>); +begin + FList.InsertRange( Index, Collection ); +end; + +procedure TThriftListImpl<T>.InsertRange(Index: Integer; + const Values: array of T); +begin + FList.InsertRange( Index, Values); +end; + +procedure TThriftListImpl<T>.InsertRange(Index: Integer; + const Collection: IEnumerable<T>); +begin + FList.InsertRange( Index, Collection ); +end; + +{$IF CompilerVersion >= 21.0} +function TThriftListImpl<T>.Last: T; +begin + Result := FList.Last; +end; +{$IFEND} + +function TThriftListImpl<T>.LastIndexOf(const Value: T): Integer; +begin + Result := FList.LastIndexOf( Value ); +end; + +{$IF CompilerVersion >= 21.0} +procedure TThriftListImpl<T>.Move(CurIndex, NewIndex: Integer); +begin + FList.Move( CurIndex, NewIndex); +end; +{$IFEND} + +function TThriftListImpl<T>.Remove(const Value: T): Integer; +begin + Result := FList.Remove( Value ); +end; + +procedure TThriftListImpl<T>.Reverse; +begin + FList.Reverse; +end; + +procedure TThriftListImpl<T>.SetCapacity(Value: Integer); +begin + FList.Capacity := Value; +end; + +procedure TThriftListImpl<T>.SetCount(Value: Integer); +begin + FList.Count := Value; +end; + +procedure TThriftListImpl<T>.SetItem(Index: Integer; const Value: T); +begin + FList[Index] := Value; +end; + +procedure TThriftListImpl<T>.Sort; +begin + FList.Sort; +end; + +procedure TThriftListImpl<T>.Sort(const AComparer: IComparer<T>); +begin + FList.Sort(AComparer); +end; + +function TThriftListImpl<T>.ToArray: TArray<T>; +{$IF CompilerVersion < 22.0} +var + x : T; + i : Integer; +{$IFEND} +begin +{$IF CompilerVersion < 22.0} + SetLength(Result, Count); + i := 0; + for x in FList do + begin + Result[i] := x; + Inc( i ); + end; +{$ELSE} + Result := FList.ToArray; +{$IFEND} +end; + +function TThriftListImpl<T>.ToString : string; +var elm : T; + sb : TThriftStringBuilder; + first : Boolean; +begin + sb := TThriftStringBuilder.Create('{'); + try + first := TRUE; + for elm in FList do begin + if first + then first := FALSE + else sb.Append(', '); + + sb.Append( StringUtils<T>.ToString(elm)); + end; + sb.Append('}'); + Result := sb.ToString; + finally + sb.Free; + end; +end; + +procedure TThriftListImpl<T>.TrimExcess; +begin + FList.TrimExcess; +end; + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Defines.inc b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Defines.inc new file mode 100644 index 000000000..499ccae12 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Defines.inc @@ -0,0 +1,50 @@ +(* + * 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. + *) + + +// Good lists of Delphi version numbers +// https://github.com/project-jedi/jedi/blob/master/jedi.inc +// http://docwiki.embarcadero.com/RADStudio/Seattle/en/Compiler_Versions + + +// start with most backwards compatible defaults + +{$DEFINE OLD_UNIT_NAMES} +{$DEFINE OLD_SOCKETS} // TODO: add socket support for CompilerVersion >= 28.0 +{$UNDEF HAVE_CLASS_CTOR} + + +// enable features as they are available + +{$IF CompilerVersion >= 21.0} // Delphi 2010 + {$DEFINE HAVE_CLASS_CTOR} +{$IFEND} + +{$IF CompilerVersion >= 23.0} // Delphi XE2 + {$UNDEF OLD_UNIT_NAMES} +{$IFEND} + +{$IF CompilerVersion >= 28.0} // Delphi XE7 + {$UNDEF OLD_SOCKETS} +{$IFEND} + + +// EOF + + diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Exception.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Exception.pas new file mode 100644 index 000000000..5d15c3656 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Exception.pas @@ -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. + *) + +{$SCOPEDENUMS ON} + +unit Thrift.Exception; + +interface + +uses + Classes, SysUtils; + +type + // base class for all Thrift exceptions + TException = class( SysUtils.Exception) + public + function Message : string; // hide inherited property: allow read, but prevent accidental writes + procedure UpdateMessageProperty; // update inherited message property with toString() + end; + + + + +implementation + +{ TException } + +function TException.Message; +// allow read (exception summary), but prevent accidental writes +// read will return the exception summary +begin + result := Self.ToString; +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 +end; + + + + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Processor.Multiplex.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Processor.Multiplex.pas new file mode 100644 index 000000000..8cf23db07 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Processor.Multiplex.pas @@ -0,0 +1,231 @@ +(* + * 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. + *) + +unit Thrift.Processor.Multiplex; + + +interface + +uses + SysUtils, + Generics.Collections, + Thrift, + Thrift.Protocol, + Thrift.Protocol.Multiplex; + +{ TMultiplexedProcessor is a TProcessor allowing a single TServer to provide multiple services. + To do so, you instantiate the processor and then register additional processors with it, + as shown in the following example: + + + TMultiplexedProcessor processor = new TMultiplexedProcessor(); + + processor.registerProcessor( + "Calculator", + new Calculator.Processor(new CalculatorHandler())); + + processor.registerProcessor( + "WeatherReport", + new WeatherReport.Processor(new WeatherReportHandler())); + + TServerTransport t = new TServerSocket(9090); + TSimpleServer server = new TSimpleServer(processor, t); + + server.serve(); +} + + +type + IMultiplexedProcessor = interface( IProcessor) + ['{807F9D19-6CF4-4789-840E-93E87A12EB63}'] + // Register a service with this TMultiplexedProcessor. This allows us + // to broker requests to individual services by using the service name + // to select them at request time. + procedure RegisterProcessor( const serviceName : String; const processor : IProcessor; const asDefault : Boolean = FALSE); + end; + + + TMultiplexedProcessorImpl = class( TInterfacedObject, IMultiplexedProcessor, IProcessor) + private type + // Our goal was to work with any protocol. In order to do that, we needed + // to allow them to call readMessageBegin() and get a TMessage in exactly + // the standard format, without the service name prepended to TMessage.name. + TStoredMessageProtocol = class( TProtocolDecorator) + private + FMessageBegin : TThriftMessage; + public + constructor Create( const protocol : IProtocol; const aMsgBegin : TThriftMessage); + function ReadMessageBegin: TThriftMessage; override; + end; + + private + FServiceProcessorMap : TDictionary<String, IProcessor>; + FDefaultProcessor : IProcessor; + + procedure Error( const oprot : IProtocol; const msg : TThriftMessage; + extype : TApplicationExceptionSpecializedClass; const etxt : string); + + public + constructor Create; + destructor Destroy; override; + + // Register a service with this TMultiplexedProcessorImpl. This allows us + // to broker requests to individual services by using the service name + // to select them at request time. + procedure RegisterProcessor( const serviceName : String; const processor : IProcessor; const asDefault : Boolean = FALSE); + + { This implementation of process performs the following steps: + - Read the beginning of the message. + - Extract the service name from the message. + - Using the service name to locate the appropriate processor. + - Dispatch to the processor, with a decorated instance of TProtocol + that allows readMessageBegin() to return the original TMessage. + + An exception is thrown if the message type is not CALL or ONEWAY + or if the service is unknown (or not properly registered). + } + function Process( const iprot, oprot: IProtocol; const events : IProcessorEvents = nil): Boolean; + end; + + +implementation + +constructor TMultiplexedProcessorImpl.TStoredMessageProtocol.Create( const protocol : IProtocol; const aMsgBegin : TThriftMessage); +begin + inherited Create( protocol); + FMessageBegin := aMsgBegin; +end; + + +function TMultiplexedProcessorImpl.TStoredMessageProtocol.ReadMessageBegin: TThriftMessage; +begin + result := FMessageBegin; +end; + + +constructor TMultiplexedProcessorImpl.Create; +begin + inherited Create; + FServiceProcessorMap := TDictionary<string,IProcessor>.Create; +end; + + +destructor TMultiplexedProcessorImpl.Destroy; +begin + try + FreeAndNil( FServiceProcessorMap); + finally + inherited Destroy; + end; +end; + + +procedure TMultiplexedProcessorImpl.RegisterProcessor( const serviceName : String; const processor : IProcessor; const asDefault : Boolean); +begin + FServiceProcessorMap.Add( serviceName, processor); + + if asDefault then begin + if FDefaultProcessor = nil + then FDefaultProcessor := processor + else raise TApplicationExceptionInternalError.Create('Only one default service allowed'); + end; +end; + + +procedure TMultiplexedProcessorImpl.Error( const oprot : IProtocol; const msg : TThriftMessage; + extype : TApplicationExceptionSpecializedClass; + const etxt : string); +var appex : TApplicationException; + newMsg : TThriftMessage; +begin + appex := extype.Create(etxt); + try + Init( newMsg, msg.Name, TMessageType.Exception, msg.SeqID); + + oprot.WriteMessageBegin(newMsg); + appex.Write(oprot); + oprot.WriteMessageEnd(); + oprot.Transport.Flush(); + + finally + appex.Free; + end; +end; + + +function TMultiplexedProcessorImpl.Process(const iprot, oprot : IProtocol; const events : IProcessorEvents = nil): Boolean; +var msg, newMsg : TThriftMessage; + idx : Integer; + sService : string; + processor : IProcessor; + protocol : IProtocol; +const + ERROR_INVALID_MSGTYPE = 'Message must be "call" or "oneway"'; + ERROR_INCOMPATIBLE_PROT = 'No service name found in "%s". Client is expected to use TMultiplexProtocol.'; + ERROR_UNKNOWN_SERVICE = 'Service "%s" is not registered with MultiplexedProcessor'; +begin + // Use the actual underlying protocol (e.g. TBinaryProtocol) to read the message header. + // This pulls the message "off the wire", which we'll deal with at the end of this method. + msg := iprot.readMessageBegin(); + if not (msg.Type_ in [TMessageType.Call, TMessageType.Oneway]) then begin + Error( oprot, msg, + TApplicationExceptionInvalidMessageType, + ERROR_INVALID_MSGTYPE); + Exit( FALSE); + end; + + // Extract the service name + // use FDefaultProcessor as fallback if there is no separator + idx := Pos( TMultiplexedProtocol.SEPARATOR, msg.Name); + if idx > 0 then begin + + // Create a new TMessage, something that can be consumed by any TProtocol + sService := Copy( msg.Name, 1, idx-1); + if not FServiceProcessorMap.TryGetValue( sService, processor) + then begin + Error( oprot, msg, + TApplicationExceptionInternalError, + Format(ERROR_UNKNOWN_SERVICE,[sService])); + Exit( FALSE); + end; + + // Create a new TMessage, removing the service name + Inc( idx, Length(TMultiplexedProtocol.SEPARATOR)); + Init( newMsg, Copy( msg.Name, idx, MAXINT), msg.Type_, msg.SeqID); + + end + else if FDefaultProcessor <> nil then begin + processor := FDefaultProcessor; + newMsg := msg; // no need to change + + end + else begin + Error( oprot, msg, + TApplicationExceptionInvalidProtocol, + Format(ERROR_INCOMPATIBLE_PROT,[msg.Name])); + Exit( FALSE); + end; + + // Dispatch processing to the stored processor + protocol := TStoredMessageProtocol.Create( iprot, newMsg); + result := processor.process( protocol, oprot, events); +end; + + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.Compact.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.Compact.pas new file mode 100644 index 000000000..07cab9a05 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.Compact.pas @@ -0,0 +1,1118 @@ +(* + * 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. + *) + +{$SCOPEDENUMS ON} + +unit Thrift.Protocol.Compact; + +interface + +uses + Classes, + SysUtils, + Math, + Generics.Collections, + Thrift.Transport, + Thrift.Protocol, + Thrift.Utils; + +type + ICompactProtocol = interface( IProtocol) + ['{C01927EC-021A-45F7-93B1-23D6A5420EDD}'] + end; + + // Compact protocol implementation for thrift. + // Adapted from the C# version. + TCompactProtocolImpl = class( TProtocolImpl, ICompactProtocol) + public + type + TFactory = class( TInterfacedObject, IProtocolFactory) + public + function GetProtocol( const trans: ITransport): IProtocol; + end; + + private const + + { TODO + static TStruct ANONYMOUS_STRUCT = new TStruct(""); + static TField TSTOP = new TField("", TType.Stop, (short)0); + } + + PROTOCOL_ID = Byte( $82); + VERSION = Byte( 1); + VERSION_MASK = Byte( $1F); // 0001 1111 + TYPE_MASK = Byte( $E0); // 1110 0000 + TYPE_BITS = Byte( $07); // 0000 0111 + TYPE_SHIFT_AMOUNT = Byte( 5); + + private type + // All of the on-wire type codes. + Types = ( + STOP = $00, + BOOLEAN_TRUE = $01, + BOOLEAN_FALSE = $02, + BYTE_ = $03, + I16 = $04, + I32 = $05, + I64 = $06, + DOUBLE_ = $07, + BINARY = $08, + LIST = $09, + SET_ = $0A, + MAP = $0B, + STRUCT = $0C + ); + + private const + ttypeToCompactType : array[TType] of Types = ( + Types.STOP, // Stop = 0, + Types(-1), // Void = 1, + Types.BOOLEAN_TRUE, // Bool_ = 2, + Types.BYTE_, // Byte_ = 3, + Types.DOUBLE_, // Double_ = 4, + Types(-5), // unused + Types.I16, // I16 = 6, + Types(-7), // unused + Types.I32, // I32 = 8, + Types(-9), // unused + Types.I64, // I64 = 10, + Types.BINARY, // String_ = 11, + Types.STRUCT, // Struct = 12, + Types.MAP, // Map = 13, + Types.SET_, // Set_ = 14, + Types.LIST // List = 15, + ); + + tcompactTypeToType : array[Types] of TType = ( + TType.Stop, // STOP + TType.Bool_, // BOOLEAN_TRUE + TType.Bool_, // BOOLEAN_FALSE + TType.Byte_, // BYTE_ + TType.I16, // I16 + TType.I32, // I32 + TType.I64, // I64 + TType.Double_, // DOUBLE_ + TType.String_, // BINARY + TType.List, // LIST + TType.Set_, // SET_ + TType.Map, // MAP + TType.Struct // STRUCT + ); + + private + // Used to keep track of the last field for the current and previous structs, + // so we can do the delta stuff. + lastField_ : TStack<Integer>; + lastFieldId_ : Integer; + + // If we encounter a boolean field begin, save the TField here so it can + // have the value incorporated. + private booleanField_ : TThriftField; + + // If we Read a field header, and it's a boolean field, save the boolean + // value here so that ReadBool can use it. + private boolValue_ : ( unused, bool_true, bool_false); + + public + constructor Create(const trans : ITransport); + destructor Destroy; override; + + procedure Reset; + + private + procedure WriteByteDirect( const b : Byte); overload; + + // Writes a byte without any possibility of all that field header nonsense. + procedure WriteByteDirect( const n : Integer); overload; + + // Write an i32 as a varint. Results in 1-5 bytes on the wire. + // TODO: make a permanent buffer like WriteVarint64? + procedure WriteVarint32( n : Cardinal); + + private + // The workhorse of WriteFieldBegin. It has the option of doing a 'type override' + // of the type header. This is used specifically in the boolean field case. + procedure WriteFieldBeginInternal( const field : TThriftField; typeOverride : Byte); + + public + procedure WriteMessageBegin( const msg: TThriftMessage); override; + procedure WriteMessageEnd; override; + procedure WriteStructBegin( const struc: TThriftStruct); override; + procedure WriteStructEnd; override; + procedure WriteFieldBegin( const field: TThriftField); override; + procedure WriteFieldEnd; override; + procedure WriteFieldStop; override; + procedure WriteMapBegin( const map: TThriftMap); override; + procedure WriteMapEnd; override; + procedure WriteListBegin( const list: TThriftList); override; + procedure WriteListEnd(); override; + procedure WriteSetBegin( const set_: TThriftSet ); override; + procedure WriteSetEnd(); override; + procedure WriteBool( b: Boolean); override; + procedure WriteByte( b: ShortInt); override; + procedure WriteI16( i16: SmallInt); override; + procedure WriteI32( i32: Integer); override; + procedure WriteI64( const i64: Int64); override; + procedure WriteDouble( const dub: Double); override; + procedure WriteBinary( const b: TBytes); overload; override; + + private + class function DoubleToInt64Bits( const db : Double) : Int64; + class function Int64BitsToDouble( const i64 : Int64) : Double; + + // Abstract method for writing the start of lists and sets. List and sets on + // the wire differ only by the type indicator. + procedure WriteCollectionBegin( const elemType : TType; size : Integer); + + procedure WriteVarint64( n : UInt64); + + // Convert l into a zigzag long. This allows negative numbers to be + // represented compactly as a varint. + class function longToZigzag( const n : Int64) : UInt64; + + // Convert n into a zigzag int. This allows negative numbers to be + // represented compactly as a varint. + class function intToZigZag( const n : Integer) : Cardinal; + + //Convert a Int64 into little-endian bytes in buf starting at off and going until off+7. + class procedure fixedLongToBytes( const n : Int64; var buf : TBytes); + + public + function ReadMessageBegin: TThriftMessage; override; + procedure ReadMessageEnd(); override; + function ReadStructBegin: TThriftStruct; override; + procedure ReadStructEnd; override; + function ReadFieldBegin: TThriftField; override; + procedure ReadFieldEnd(); override; + function ReadMapBegin: TThriftMap; override; + procedure ReadMapEnd(); override; + function ReadListBegin: TThriftList; override; + procedure ReadListEnd(); override; + function ReadSetBegin: TThriftSet; override; + procedure ReadSetEnd(); override; + function ReadBool: Boolean; override; + function ReadByte: ShortInt; override; + function ReadI16: SmallInt; override; + function ReadI32: Integer; override; + function ReadI64: Int64; override; + function ReadDouble:Double; override; + function ReadBinary: TBytes; overload; override; + + private + // Internal Reading methods + + // Read an i32 from the wire as a varint. The MSB of each byte is set + // if there is another byte to follow. This can Read up to 5 bytes. + function ReadVarint32 : Cardinal; + + // Read an i64 from the wire as a proper varint. The MSB of each byte is set + // if there is another byte to follow. This can Read up to 10 bytes. + function ReadVarint64 : UInt64; + + + // encoding helpers + + // Convert from zigzag Integer to Integer. + class function zigzagToInt( const n : Cardinal ) : Integer; + + // Convert from zigzag Int64 to Int64. + class function zigzagToLong( const n : UInt64) : Int64; + + // Note that it's important that the mask bytes are Int64 literals, + // otherwise they'll default to ints, and when you shift an Integer left 56 bits, + // you just get a messed up Integer. + class function bytesToLong( const bytes : TBytes) : Int64; + + // type testing and converting + class function isBoolType( const b : byte) : Boolean; + + // Given a TCompactProtocol.Types constant, convert it to its corresponding TType value. + class function getTType( const type_ : byte) : TType; + + // Given a TType value, find the appropriate TCompactProtocol.Types constant. + class function getCompactType( const ttype : TType) : Byte; + end; + + +implementation + + + +//--- TCompactProtocolImpl.TFactory ---------------------------------------- + + +function TCompactProtocolImpl.TFactory.GetProtocol( const trans: ITransport): IProtocol; +begin + result := TCompactProtocolImpl.Create( trans); +end; + + +//--- TCompactProtocolImpl ------------------------------------------------- + + +constructor TCompactProtocolImpl.Create(const trans: ITransport); +begin + inherited Create( trans); + + lastFieldId_ := 0; + lastField_ := TStack<Integer>.Create; + + Init( booleanField_, '', TType.Stop, 0); + boolValue_ := unused; +end; + + +destructor TCompactProtocolImpl.Destroy; +begin + try + FreeAndNil( lastField_); + finally + inherited Destroy; + end; +end; + + + +procedure TCompactProtocolImpl.Reset; +begin + lastField_.Clear(); + lastFieldId_ := 0; + Init( booleanField_, '', TType.Stop, 0); + boolValue_ := unused; +end; + + +// Writes a byte without any possibility of all that field header nonsense. +// Used internally by other writing methods that know they need to Write a byte. +procedure TCompactProtocolImpl.WriteByteDirect( const b : Byte); +begin + Transport.Write( @b, SizeOf(b)); +end; + + +// Writes a byte without any possibility of all that field header nonsense. +procedure TCompactProtocolImpl.WriteByteDirect( const n : Integer); +begin + WriteByteDirect( Byte(n)); +end; + + +// Write an i32 as a varint. Results in 1-5 bytes on the wire. +procedure TCompactProtocolImpl.WriteVarint32( n : Cardinal); +var i32buf : TBytes; + idx : Integer; +begin + SetLength( i32buf, 5); + idx := 0; + while TRUE do begin + ASSERT( idx < Length(i32buf)); + + // last part? + if ((n and not $7F) = 0) then begin + i32buf[idx] := Byte(n); + Inc(idx); + Break; + end; + + i32buf[idx] := Byte((n and $7F) or $80); + Inc(idx); + n := n shr 7; + end; + + Transport.Write( i32buf, 0, idx); +end; + + +// Write a message header to the wire. Compact Protocol messages contain the +// protocol version so we can migrate forwards in the future if need be. +procedure TCompactProtocolImpl.WriteMessageBegin( const msg: TThriftMessage); +var versionAndType : Byte; +begin + Reset; + + versionAndType := Byte( VERSION and VERSION_MASK) + or Byte( (Cardinal(msg.Type_) shl TYPE_SHIFT_AMOUNT) and TYPE_MASK); + + WriteByteDirect( PROTOCOL_ID); + WriteByteDirect( versionAndType); + WriteVarint32( Cardinal(msg.SeqID)); + WriteString( msg.Name); +end; + + +// Write a struct begin. This doesn't actually put anything on the wire. We use it as an +// opportunity to put special placeholder markers on the field stack so we can get the +// field id deltas correct. +procedure TCompactProtocolImpl.WriteStructBegin( const struc: TThriftStruct); +begin + lastField_.Push(lastFieldId_); + lastFieldId_ := 0; +end; + + +// Write a struct end. This doesn't actually put anything on the wire. We use this as an +// opportunity to pop the last field from the current struct off of the field stack. +procedure TCompactProtocolImpl.WriteStructEnd; +begin + lastFieldId_ := lastField_.Pop(); +end; + + +// Write a field header containing the field id and field type. If the difference between the +// current field id and the last one is small (< 15), then the field id will be encoded in +// the 4 MSB as a delta. Otherwise, the field id will follow the type header as a zigzag varint. +procedure TCompactProtocolImpl.WriteFieldBegin( const field: TThriftField); +begin + case field.Type_ of + TType.Bool_ : booleanField_ := field; // we want to possibly include the value, so we'll wait. + else + WriteFieldBeginInternal(field, $FF); + end; +end; + + +// The workhorse of WriteFieldBegin. It has the option of doing a 'type override' +// of the type header. This is used specifically in the boolean field case. +procedure TCompactProtocolImpl.WriteFieldBeginInternal( const field : TThriftField; typeOverride : Byte); +var typeToWrite : Byte; +begin + // if there's a type override, use that. + if typeOverride = $FF + then typeToWrite := getCompactType( field.Type_) + else typeToWrite := typeOverride; + + // check if we can use delta encoding for the field id + if (field.ID > lastFieldId_) and ((field.ID - lastFieldId_) <= 15) + then begin + // Write them together + WriteByteDirect( ((field.ID - lastFieldId_) shl 4) or typeToWrite); + end + else begin + // Write them separate + WriteByteDirect( typeToWrite); + WriteI16( field.ID); + end; + + lastFieldId_ := field.ID; +end; + + +// Write the STOP symbol so we know there are no more fields in this struct. +procedure TCompactProtocolImpl.WriteFieldStop; +begin + WriteByteDirect( Byte( Types.STOP)); +end; + + +// Write a map header. If the map is empty, omit the key and value type +// headers, as we don't need any additional information to skip it. +procedure TCompactProtocolImpl.WriteMapBegin( const map: TThriftMap); +var key, val : Byte; +begin + if (map.Count = 0) + then WriteByteDirect( 0) + else begin + WriteVarint32( Cardinal( map.Count)); + key := getCompactType(map.KeyType); + val := getCompactType(map.ValueType); + WriteByteDirect( (key shl 4) or val); + end; +end; + + +// Write a list header. +procedure TCompactProtocolImpl.WriteListBegin( const list: TThriftList); +begin + WriteCollectionBegin( list.ElementType, list.Count); +end; + + +// Write a set header. +procedure TCompactProtocolImpl.WriteSetBegin( const set_: TThriftSet ); +begin + WriteCollectionBegin( set_.ElementType, set_.Count); +end; + + +// Write a boolean value. Potentially, this could be a boolean field, in +// which case the field header info isn't written yet. If so, decide what the +// right type header is for the value and then Write the field header. +// Otherwise, Write a single byte. +procedure TCompactProtocolImpl.WriteBool( b: Boolean); +var bt : Types; +begin + if b + then bt := Types.BOOLEAN_TRUE + else bt := Types.BOOLEAN_FALSE; + + if booleanField_.Type_ = TType.Bool_ then begin + // we haven't written the field header yet + WriteFieldBeginInternal( booleanField_, Byte(bt)); + booleanField_.Type_ := TType.Stop; + end + else begin + // we're not part of a field, so just Write the value. + WriteByteDirect( Byte(bt)); + end; +end; + + +// Write a byte. Nothing to see here! +procedure TCompactProtocolImpl.WriteByte( b: ShortInt); +begin + WriteByteDirect( Byte(b)); +end; + + +// Write an I16 as a zigzag varint. +procedure TCompactProtocolImpl.WriteI16( i16: SmallInt); +begin + WriteVarint32( intToZigZag( i16)); +end; + + +// Write an i32 as a zigzag varint. +procedure TCompactProtocolImpl.WriteI32( i32: Integer); +begin + WriteVarint32( intToZigZag( i32)); +end; + + +// Write an i64 as a zigzag varint. +procedure TCompactProtocolImpl.WriteI64( const i64: Int64); +begin + WriteVarint64( longToZigzag( i64)); +end; + + +class function TCompactProtocolImpl.DoubleToInt64Bits( const db : Double) : Int64; +begin + ASSERT( SizeOf(db) = SizeOf(result)); + Move( db, result, SizeOf(result)); +end; + + +class function TCompactProtocolImpl.Int64BitsToDouble( const i64 : Int64) : Double; +begin + ASSERT( SizeOf(i64) = SizeOf(result)); + Move( i64, result, SizeOf(result)); +end; + + +// Write a double to the wire as 8 bytes. +procedure TCompactProtocolImpl.WriteDouble( const dub: Double); +var data : TBytes; +begin + fixedLongToBytes( DoubleToInt64Bits(dub), data); + Transport.Write( data); +end; + + +// Write a byte array, using a varint for the size. +procedure TCompactProtocolImpl.WriteBinary( const b: TBytes); +begin + WriteVarint32( Cardinal(Length(b))); + Transport.Write( b); +end; + +procedure TCompactProtocolImpl.WriteMessageEnd; +begin + // nothing to do +end; + + +procedure TCompactProtocolImpl.WriteMapEnd; +begin + // nothing to do +end; + + +procedure TCompactProtocolImpl.WriteListEnd; +begin + // nothing to do +end; + + +procedure TCompactProtocolImpl.WriteSetEnd; +begin + // nothing to do +end; + + +procedure TCompactProtocolImpl.WriteFieldEnd; +begin + // nothing to do +end; + + +// Abstract method for writing the start of lists and sets. List and sets on +// the wire differ only by the type indicator. +procedure TCompactProtocolImpl.WriteCollectionBegin( const elemType : TType; size : Integer); +begin + if size <= 14 + then WriteByteDirect( (size shl 4) or getCompactType(elemType)) + else begin + WriteByteDirect( $F0 or getCompactType(elemType)); + WriteVarint32( Cardinal(size)); + end; +end; + + +// Write an i64 as a varint. Results in 1-10 bytes on the wire. +procedure TCompactProtocolImpl.WriteVarint64( n : UInt64); +var varint64out : TBytes; + idx : Integer; +begin + SetLength( varint64out, 10); + idx := 0; + while TRUE do begin + ASSERT( idx < Length(varint64out)); + + // last one? + if (n and not UInt64($7F)) = 0 then begin + varint64out[idx] := Byte(n); + Inc(idx); + Break; + end; + + varint64out[idx] := Byte((n and $7F) or $80); + Inc(idx); + n := n shr 7; + end; + + Transport.Write( varint64out, 0, idx); +end; + + +// Convert l into a zigzag Int64. This allows negative numbers to be +// represented compactly as a varint. +class function TCompactProtocolImpl.longToZigzag( const n : Int64) : UInt64; +begin + // there is no arithmetic right shift in Delphi + if n >= 0 + then result := UInt64(n shl 1) + else result := UInt64(n shl 1) xor $FFFFFFFFFFFFFFFF; +end; + + +// Convert n into a zigzag Integer. This allows negative numbers to be +// represented compactly as a varint. +class function TCompactProtocolImpl.intToZigZag( const n : Integer) : Cardinal; +begin + // there is no arithmetic right shift in Delphi + if n >= 0 + then result := Cardinal(n shl 1) + else result := Cardinal(n shl 1) xor $FFFFFFFF; +end; + + +// Convert a Int64 into 8 little-endian bytes in buf +class procedure TCompactProtocolImpl.fixedLongToBytes( const n : Int64; var buf : TBytes); +begin + SetLength( buf, 8); + buf[0] := Byte( n and $FF); + buf[1] := Byte((n shr 8) and $FF); + buf[2] := Byte((n shr 16) and $FF); + buf[3] := Byte((n shr 24) and $FF); + buf[4] := Byte((n shr 32) and $FF); + buf[5] := Byte((n shr 40) and $FF); + buf[6] := Byte((n shr 48) and $FF); + buf[7] := Byte((n shr 56) and $FF); +end; + + + +// Read a message header. +function TCompactProtocolImpl.ReadMessageBegin : TThriftMessage; +var protocolId, versionAndType, version, type_ : Byte; + seqid : Integer; + msgNm : String; +begin + Reset; + + protocolId := Byte( ReadByte); + if (protocolId <> PROTOCOL_ID) + then raise TProtocolExceptionBadVersion.Create( 'Expected protocol id ' + IntToHex(PROTOCOL_ID,2) + + ' but got ' + IntToHex(protocolId,2)); + + versionAndType := Byte( ReadByte); + version := Byte( versionAndType and VERSION_MASK); + if (version <> VERSION) + then raise TProtocolExceptionBadVersion.Create( 'Expected version ' +IntToStr(VERSION) + + ' but got ' + IntToStr(version)); + + type_ := Byte( (versionAndType shr TYPE_SHIFT_AMOUNT) and TYPE_BITS); + seqid := Integer( ReadVarint32); + msgNm := ReadString; + Init( result, msgNm, TMessageType(type_), seqid); +end; + + +// Read a struct begin. There's nothing on the wire for this, but it is our +// opportunity to push a new struct begin marker onto the field stack. +function TCompactProtocolImpl.ReadStructBegin: TThriftStruct; +begin + lastField_.Push( lastFieldId_); + lastFieldId_ := 0; + Init( result); +end; + + +// Doesn't actually consume any wire data, just removes the last field for +// this struct from the field stack. +procedure TCompactProtocolImpl.ReadStructEnd; +begin + // consume the last field we Read off the wire. + lastFieldId_ := lastField_.Pop(); +end; + + +// Read a field header off the wire. +function TCompactProtocolImpl.ReadFieldBegin: TThriftField; +var type_ : Byte; + modifier : ShortInt; + fieldId : SmallInt; +begin + type_ := Byte( ReadByte); + + // if it's a stop, then we can return immediately, as the struct is over. + if type_ = Byte(Types.STOP) then begin + Init( result, '', TType.Stop, 0); + Exit; + end; + + // mask off the 4 MSB of the type header. it could contain a field id delta. + modifier := ShortInt( (type_ and $F0) shr 4); + if (modifier = 0) + then fieldId := ReadI16 // not a delta. look ahead for the zigzag varint field id. + else fieldId := SmallInt( lastFieldId_ + modifier); // add the delta to the last Read field id. + + Init( result, '', getTType(Byte(type_ and $0F)), fieldId); + + // if this happens to be a boolean field, the value is encoded in the type + // save the boolean value in a special instance variable. + if isBoolType(type_) then begin + if Byte(type_ and $0F) = Byte(Types.BOOLEAN_TRUE) + then boolValue_ := bool_true + else boolValue_ := bool_false; + end; + + // push the new field onto the field stack so we can keep the deltas going. + lastFieldId_ := result.ID; +end; + + +// Read a map header off the wire. If the size is zero, skip Reading the key +// and value type. This means that 0-length maps will yield TMaps without the +// "correct" types. +function TCompactProtocolImpl.ReadMapBegin: TThriftMap; +var size : Integer; + keyAndValueType : Byte; + key, val : TType; +begin + size := Integer( ReadVarint32); + if size = 0 + then keyAndValueType := 0 + else keyAndValueType := Byte( ReadByte); + + key := getTType( Byte( keyAndValueType shr 4)); + val := getTType( Byte( keyAndValueType and $F)); + Init( result, key, val, size); + ASSERT( (result.KeyType = key) and (result.ValueType = val)); +end; + + +// Read a list header off the wire. If the list size is 0-14, the size will +// be packed into the element type header. If it's a longer list, the 4 MSB +// of the element type header will be $F, and a varint will follow with the +// true size. +function TCompactProtocolImpl.ReadListBegin: TThriftList; +var size_and_type : Byte; + size : Integer; + type_ : TType; +begin + size_and_type := Byte( ReadByte); + + size := (size_and_type shr 4) and $0F; + if (size = 15) + then size := Integer( ReadVarint32); + + type_ := getTType( size_and_type); + Init( result, type_, size); +end; + + +// Read a set header off the wire. If the set size is 0-14, the size will +// be packed into the element type header. If it's a longer set, the 4 MSB +// of the element type header will be $F, and a varint will follow with the +// true size. +function TCompactProtocolImpl.ReadSetBegin: TThriftSet; +var size_and_type : Byte; + size : Integer; + type_ : TType; +begin + size_and_type := Byte( ReadByte); + + size := (size_and_type shr 4) and $0F; + if (size = 15) + then size := Integer( ReadVarint32); + + type_ := getTType( size_and_type); + Init( result, type_, size); +end; + + +// Read a boolean off the wire. If this is a boolean field, the value should +// already have been Read during ReadFieldBegin, so we'll just consume the +// pre-stored value. Otherwise, Read a byte. +function TCompactProtocolImpl.ReadBool: Boolean; +begin + if boolValue_ <> unused then begin + result := (boolValue_ = bool_true); + boolValue_ := unused; + Exit; + end; + + result := (Byte(ReadByte) = Byte(Types.BOOLEAN_TRUE)); +end; + + +// Read a single byte off the wire. Nothing interesting here. +function TCompactProtocolImpl.ReadByte: ShortInt; +begin + Transport.ReadAll( @result, SizeOf(result), 0, 1); +end; + + +// Read an i16 from the wire as a zigzag varint. +function TCompactProtocolImpl.ReadI16: SmallInt; +begin + result := SmallInt( zigzagToInt( ReadVarint32)); +end; + + +// Read an i32 from the wire as a zigzag varint. +function TCompactProtocolImpl.ReadI32: Integer; +begin + result := zigzagToInt( ReadVarint32); +end; + + +// Read an i64 from the wire as a zigzag varint. +function TCompactProtocolImpl.ReadI64: Int64; +begin + result := zigzagToLong( ReadVarint64); +end; + + +// No magic here - just Read a double off the wire. +function TCompactProtocolImpl.ReadDouble:Double; +var longBits : TBytes; +begin + SetLength( longBits, 8); + Transport.ReadAll( longBits, 0, 8); + result := Int64BitsToDouble( bytesToLong( longBits)); +end; + + +// Read a byte[] from the wire. +function TCompactProtocolImpl.ReadBinary: TBytes; +var length : Integer; +begin + length := Integer( ReadVarint32); + SetLength( result, length); + if (length > 0) + then Transport.ReadAll( result, 0, length); +end; + + +procedure TCompactProtocolImpl.ReadMessageEnd; +begin + // nothing to do +end; + + +procedure TCompactProtocolImpl.ReadFieldEnd; +begin + // nothing to do +end; + + +procedure TCompactProtocolImpl.ReadMapEnd; +begin + // nothing to do +end; + + +procedure TCompactProtocolImpl.ReadListEnd; +begin + // nothing to do +end; + + +procedure TCompactProtocolImpl.ReadSetEnd; +begin + // nothing to do +end; + + + +// Read an i32 from the wire as a varint. The MSB of each byte is set +// if there is another byte to follow. This can Read up to 5 bytes. +function TCompactProtocolImpl.ReadVarint32 : Cardinal; +var shift : Integer; + b : Byte; +begin + result := 0; + shift := 0; + while TRUE do begin + b := Byte( ReadByte); + result := result or (Cardinal(b and $7F) shl shift); + if ((b and $80) <> $80) + then Break; + Inc( shift, 7); + end; +end; + + +// Read an i64 from the wire as a proper varint. The MSB of each byte is set +// if there is another byte to follow. This can Read up to 10 bytes. +function TCompactProtocolImpl.ReadVarint64 : UInt64; +var shift : Integer; + b : Byte; +begin + result := 0; + shift := 0; + while TRUE do begin + b := Byte( ReadByte); + result := result or (UInt64(b and $7F) shl shift); + if ((b and $80) <> $80) + then Break; + Inc( shift, 7); + end; +end; + + +// Convert from zigzag Integer to Integer. +class function TCompactProtocolImpl.zigzagToInt( const n : Cardinal ) : Integer; +begin + result := Integer(n shr 1) xor (-Integer(n and 1)); +end; + + +// Convert from zigzag Int64 to Int64. +class function TCompactProtocolImpl.zigzagToLong( const n : UInt64) : Int64; +begin + result := Int64(n shr 1) xor (-Int64(n and 1)); +end; + + +// Note that it's important that the mask bytes are Int64 literals, +// otherwise they'll default to ints, and when you shift an Integer left 56 bits, +// you just get a messed up Integer. +class function TCompactProtocolImpl.bytesToLong( const bytes : TBytes) : Int64; +begin + ASSERT( Length(bytes) >= 8); + result := (Int64(bytes[7] and $FF) shl 56) or + (Int64(bytes[6] and $FF) shl 48) or + (Int64(bytes[5] and $FF) shl 40) or + (Int64(bytes[4] and $FF) shl 32) or + (Int64(bytes[3] and $FF) shl 24) or + (Int64(bytes[2] and $FF) shl 16) or + (Int64(bytes[1] and $FF) shl 8) or + (Int64(bytes[0] and $FF)); +end; + + +class function TCompactProtocolImpl.isBoolType( const b : byte) : Boolean; +var lowerNibble : Byte; +begin + lowerNibble := b and $0f; + result := (Types(lowerNibble) in [Types.BOOLEAN_TRUE, Types.BOOLEAN_FALSE]); +end; + + +// Given a TCompactProtocol.Types constant, convert it to its corresponding TType value. +class function TCompactProtocolImpl.getTType( const type_ : byte) : TType; +var tct : Types; +begin + tct := Types( type_ and $0F); + if tct in [Low(Types)..High(Types)] + then result := tcompactTypeToType[tct] + else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(tct))); +end; + + +// Given a TType value, find the appropriate TCompactProtocol.Types constant. +class function TCompactProtocolImpl.getCompactType( const ttype : TType) : Byte; +begin + if ttype in VALID_TTYPES + then result := Byte( ttypeToCompactType[ttype]) + else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(ttype))); +end; + + +//--- unit tests ------------------------------------------- + +{$IFDEF Debug} +procedure TestDoubleToInt64Bits; + + procedure TestPair( const a : Double; const b : Int64); + begin + ASSERT( TCompactProtocolImpl.DoubleToInt64Bits(a) = b); + ASSERT( TCompactProtocolImpl.Int64BitsToDouble(b) = a); + end; + +begin + TestPair( 1.0000000000000000E+000, Int64($3FF0000000000000)); + TestPair( 1.5000000000000000E+001, Int64($402E000000000000)); + TestPair( 2.5500000000000000E+002, Int64($406FE00000000000)); + TestPair( 4.2949672950000000E+009, Int64($41EFFFFFFFE00000)); + TestPair( 3.9062500000000000E-003, Int64($3F70000000000000)); + TestPair( 2.3283064365386963E-010, Int64($3DF0000000000000)); + TestPair( 1.2345678901230000E-300, Int64($01AA74FE1C1E7E45)); + TestPair( 1.2345678901234500E-150, Int64($20D02A36586DB4BB)); + TestPair( 1.2345678901234565E+000, Int64($3FF3C0CA428C59FA)); + TestPair( 1.2345678901234567E+000, Int64($3FF3C0CA428C59FB)); + TestPair( 1.2345678901234569E+000, Int64($3FF3C0CA428C59FC)); + TestPair( 1.2345678901234569E+150, Int64($5F182344CD3CDF9F)); + TestPair( 1.2345678901234569E+300, Int64($7E3D7EE8BCBBD352)); + TestPair( -1.7976931348623157E+308, Int64($FFEFFFFFFFFFFFFF)); + TestPair( 1.7976931348623157E+308, Int64($7FEFFFFFFFFFFFFF)); + TestPair( 4.9406564584124654E-324, Int64($0000000000000001)); + TestPair( 0.0000000000000000E+000, Int64($0000000000000000)); + TestPair( 4.94065645841247E-324, Int64($0000000000000001)); + TestPair( 3.2378592100206092E-319, Int64($000000000000FFFF)); + TestPair( 1.3906711615669959E-309, Int64($0000FFFFFFFFFFFF)); + TestPair( NegInfinity, Int64($FFF0000000000000)); + TestPair( Infinity, Int64($7FF0000000000000)); + + // NaN is special + ASSERT( TCompactProtocolImpl.DoubleToInt64Bits( NaN) = Int64($FFF8000000000000)); + ASSERT( IsNan( TCompactProtocolImpl.Int64BitsToDouble( Int64($FFF8000000000000)))); +end; +{$ENDIF} + + +{$IFDEF Debug} +procedure TestZigZag; + + procedure Test32( const test : Integer); + var zz : Cardinal; + begin + zz := TCompactProtocolImpl.intToZigZag(test); + ASSERT( TCompactProtocolImpl.zigzagToInt(zz) = test, IntToStr(test)); + end; + + procedure Test64( const test : Int64); + var zz : UInt64; + begin + zz := TCompactProtocolImpl.longToZigzag(test); + ASSERT( TCompactProtocolImpl.zigzagToLong(zz) = test, IntToStr(test)); + end; + +var i : Integer; +begin + // protobuf testcases + ASSERT( TCompactProtocolImpl.intToZigZag(0) = 0, 'pb #1 to ZigZag'); + ASSERT( TCompactProtocolImpl.intToZigZag(-1) = 1, 'pb #2 to ZigZag'); + ASSERT( TCompactProtocolImpl.intToZigZag(1) = 2, 'pb #3 to ZigZag'); + ASSERT( TCompactProtocolImpl.intToZigZag(-2) = 3, 'pb #4 to ZigZag'); + ASSERT( TCompactProtocolImpl.intToZigZag(+2147483647) = 4294967294, 'pb #5 to ZigZag'); + ASSERT( TCompactProtocolImpl.intToZigZag(-2147483648) = 4294967295, 'pb #6 to ZigZag'); + + // protobuf testcases + ASSERT( TCompactProtocolImpl.zigzagToInt(0) = 0, 'pb #1 from ZigZag'); + ASSERT( TCompactProtocolImpl.zigzagToInt(1) = -1, 'pb #2 from ZigZag'); + ASSERT( TCompactProtocolImpl.zigzagToInt(2) = 1, 'pb #3 from ZigZag'); + ASSERT( TCompactProtocolImpl.zigzagToInt(3) = -2, 'pb #4 from ZigZag'); + ASSERT( TCompactProtocolImpl.zigzagToInt(4294967294) = +2147483647, 'pb #5 from ZigZag'); + ASSERT( TCompactProtocolImpl.zigzagToInt(4294967295) = -2147483648, 'pb #6 from ZigZag'); + + // back and forth 32 + Test32( 0); + for i := 0 to 30 do begin + Test32( +(Integer(1) shl i)); + Test32( -(Integer(1) shl i)); + end; + Test32( Integer($7FFFFFFF)); + Test32( Integer($80000000)); + + // back and forth 64 + Test64( 0); + for i := 0 to 62 do begin + Test64( +(Int64(1) shl i)); + Test64( -(Int64(1) shl i)); + end; + Test64( Int64($7FFFFFFFFFFFFFFF)); + Test64( Int64($8000000000000000)); +end; +{$ENDIF} + + +{$IFDEF Debug} +procedure TestLongBytes; + + procedure Test( const test : Int64); + var buf : TBytes; + begin + TCompactProtocolImpl.fixedLongToBytes( test, buf); + ASSERT( TCompactProtocolImpl.bytesToLong( buf) = test, IntToStr(test)); + end; + +var i : Integer; +begin + Test( 0); + for i := 0 to 62 do begin + Test( +(Int64(1) shl i)); + Test( -(Int64(1) shl i)); + end; + Test( Int64($7FFFFFFFFFFFFFFF)); + Test( Int64($8000000000000000)); +end; +{$ENDIF} + + +{$IFDEF Debug} +procedure UnitTest; +var w : WORD; +const FPU_CW_DENORMALIZED = $0002; +begin + w := Get8087CW; + try + Set8087CW( w or FPU_CW_DENORMALIZED); + + TestDoubleToInt64Bits; + TestZigZag; + TestLongBytes; + + finally + Set8087CW( w); + end; +end; +{$ENDIF} + + +initialization + {$IFDEF Debug} + UnitTest; + {$ENDIF} + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.JSON.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.JSON.pas new file mode 100644 index 000000000..30600aa80 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.JSON.pas @@ -0,0 +1,1237 @@ +(* + * 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. + *) + +{$SCOPEDENUMS ON} + +unit Thrift.Protocol.JSON; + +interface + +uses + Character, + Classes, + SysUtils, + Math, + Generics.Collections, + Thrift.Transport, + Thrift.Protocol, + Thrift.Utils; + +type + IJSONProtocol = interface( IProtocol) + ['{F0DAFDBD-692A-4B71-9736-F5D485A2178F}'] + // Read a byte that must match b; otherwise an exception is thrown. + procedure ReadJSONSyntaxChar( b : Byte); + end; + + // JSON protocol implementation for thrift. + // This is a full-featured protocol supporting Write and Read. + // Please see the C++ class header for a detailed description of the protocol's wire format. + // Adapted from the C# version. + TJSONProtocolImpl = class( TProtocolImpl, IJSONProtocol) + public + type + TFactory = class( TInterfacedObject, IProtocolFactory) + public + function GetProtocol( const trans: ITransport): IProtocol; + end; + + private + class function GetTypeNameForTypeID(typeID : TType) : string; + class function GetTypeIDForTypeName( const name : string) : TType; + + protected + type + // Base class for tracking JSON contexts that may require + // inserting/Reading additional JSON syntax characters. + // This base context does nothing. + TJSONBaseContext = class + protected + FProto : Pointer; // weak IJSONProtocol; + public + constructor Create( const aProto : IJSONProtocol); + procedure Write; virtual; + procedure Read; virtual; + function EscapeNumbers : Boolean; virtual; + end; + + // Context for JSON lists. + // Will insert/Read commas before each item except for the first one. + TJSONListContext = class( TJSONBaseContext) + private + FFirst : Boolean; + public + constructor Create( const aProto : IJSONProtocol); + procedure Write; override; + procedure Read; override; + end; + + // Context for JSON records. Will insert/Read colons before the value portion of each record + // pair, and commas before each key except the first. In addition, will indicate that numbers + // in the key position need to be escaped in quotes (since JSON keys must be strings). + TJSONPairContext = class( TJSONBaseContext) + private + FFirst, FColon : Boolean; + public + constructor Create( const aProto : IJSONProtocol); + procedure Write; override; + procedure Read; override; + function EscapeNumbers : Boolean; override; + end; + + // Holds up to one byte from the transport + TLookaheadReader = class + protected + FProto : Pointer; // weak IJSONProtocol; + constructor Create( const aProto : IJSONProtocol); + + private + FHasData : Boolean; + FData : Byte; + + public + // Return and consume the next byte to be Read, either taking it from the + // data buffer if present or getting it from the transport otherwise. + function Read : Byte; + + // Return the next byte to be Read without consuming, filling the data + // buffer if it has not been filled alReady. + function Peek : Byte; + end; + + protected + // Stack of nested contexts that we may be in + FContextStack : TStack<TJSONBaseContext>; + + // Current context that we are in + FContext : TJSONBaseContext; + + // Reader that manages a 1-byte buffer + FReader : TLookaheadReader; + + // Push/pop a new JSON context onto/from the stack. + procedure ResetContextStack; + procedure PushContext( const aCtx : TJSONBaseContext); + procedure PopContext; + + public + // TJSONProtocolImpl Constructor + constructor Create( const aTrans : ITransport); + destructor Destroy; override; + + protected + // IJSONProtocol + // Read a byte that must match b; otherwise an exception is thrown. + procedure ReadJSONSyntaxChar( b : Byte); + + private + // Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its corresponding hex value + class function HexVal( ch : Byte) : Byte; + + // Convert a byte containing a hex value to its corresponding hex character + class function HexChar( val : Byte) : Byte; + + // Write the bytes in array buf as a JSON characters, escaping as needed + procedure WriteJSONString( const b : TBytes); overload; + procedure WriteJSONString( const str : string); overload; + + // Write out number as a JSON value. If the context dictates so, it will be + // wrapped in quotes to output as a JSON string. + procedure WriteJSONInteger( const num : Int64); + + // Write out a double as a JSON value. If it is NaN or infinity or if the + // context dictates escaping, Write out as JSON string. + procedure WriteJSONDouble( const num : Double); + + // Write out contents of byte array b as a JSON string with base-64 encoded data + procedure WriteJSONBase64( const b : TBytes); + + procedure WriteJSONObjectStart; + procedure WriteJSONObjectEnd; + procedure WriteJSONArrayStart; + procedure WriteJSONArrayEnd; + + public + // IProtocol + procedure WriteMessageBegin( const aMsg : TThriftMessage); override; + procedure WriteMessageEnd; override; + procedure WriteStructBegin( const struc: TThriftStruct); override; + procedure WriteStructEnd; override; + procedure WriteFieldBegin( const field: TThriftField); override; + procedure WriteFieldEnd; override; + procedure WriteFieldStop; override; + procedure WriteMapBegin( const map: TThriftMap); override; + procedure WriteMapEnd; override; + procedure WriteListBegin( const list: TThriftList); override; + procedure WriteListEnd(); override; + procedure WriteSetBegin( const set_: TThriftSet ); override; + procedure WriteSetEnd(); override; + procedure WriteBool( b: Boolean); override; + procedure WriteByte( b: ShortInt); override; + procedure WriteI16( i16: SmallInt); override; + procedure WriteI32( i32: Integer); override; + procedure WriteI64( const i64: Int64); override; + procedure WriteDouble( const d: Double); override; + procedure WriteString( const s: string ); override; + procedure WriteBinary( const b: TBytes); override; + // + function ReadMessageBegin: TThriftMessage; override; + procedure ReadMessageEnd(); override; + function ReadStructBegin: TThriftStruct; override; + procedure ReadStructEnd; override; + function ReadFieldBegin: TThriftField; override; + procedure ReadFieldEnd(); override; + function ReadMapBegin: TThriftMap; override; + procedure ReadMapEnd(); override; + function ReadListBegin: TThriftList; override; + procedure ReadListEnd(); override; + function ReadSetBegin: TThriftSet; override; + procedure ReadSetEnd(); override; + function ReadBool: Boolean; override; + function ReadByte: ShortInt; override; + function ReadI16: SmallInt; override; + function ReadI32: Integer; override; + function ReadI64: Int64; override; + function ReadDouble:Double; override; + function ReadString : string; override; + function ReadBinary: TBytes; override; + + + private + // Reading methods. + + // Read in a JSON string, unescaping as appropriate. + // Skip Reading from the context if skipContext is true. + function ReadJSONString( skipContext : Boolean) : TBytes; + + // Return true if the given byte could be a valid part of a JSON number. + function IsJSONNumeric( b : Byte) : Boolean; + + // Read in a sequence of characters that are all valid in JSON numbers. Does + // not do a complete regex check to validate that this is actually a number. + function ReadJSONNumericChars : String; + + // Read in a JSON number. If the context dictates, Read in enclosing quotes. + function ReadJSONInteger : Int64; + + // Read in a JSON double value. Throw if the value is not wrapped in quotes + // when expected or if wrapped in quotes when not expected. + function ReadJSONDouble : Double; + + // Read in a JSON string containing base-64 encoded data and decode it. + function ReadJSONBase64 : TBytes; + + procedure ReadJSONObjectStart; + procedure ReadJSONObjectEnd; + procedure ReadJSONArrayStart; + procedure ReadJSONArrayEnd; + end; + + +implementation + +var + COMMA : TBytes; + COLON : TBytes; + LBRACE : TBytes; + RBRACE : TBytes; + LBRACKET : TBytes; + RBRACKET : TBytes; + QUOTE : TBytes; + BACKSLASH : TBytes; + ESCSEQ : TBytes; + +const + VERSION = 1; + JSON_CHAR_TABLE : array[0..$2F] of Byte + = (0,0,0,0, 0,0,0,0, Byte('b'),Byte('t'),Byte('n'),0, Byte('f'),Byte('r'),0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 1,1,Byte('"'),1, 1,1,1,1, 1,1,1,1, 1,1,1,1); + + ESCAPE_CHARS = '"\/btnfr'; + ESCAPE_CHAR_VALS = '"\/'#8#9#10#12#13; + + DEF_STRING_SIZE = 16; + + NAME_BOOL = 'tf'; + NAME_BYTE = 'i8'; + NAME_I16 = 'i16'; + NAME_I32 = 'i32'; + NAME_I64 = 'i64'; + NAME_DOUBLE = 'dbl'; + NAME_STRUCT = 'rec'; + NAME_STRING = 'str'; + NAME_MAP = 'map'; + NAME_LIST = 'lst'; + NAME_SET = 'set'; + + INVARIANT_CULTURE : TFormatSettings + = ( ThousandSeparator: ','; + DecimalSeparator: '.'); + + + +//--- TJSONProtocolImpl ---------------------- + + +function TJSONProtocolImpl.TFactory.GetProtocol( const trans: ITransport): IProtocol; +begin + result := TJSONProtocolImpl.Create(trans); +end; + +class function TJSONProtocolImpl.GetTypeNameForTypeID(typeID : TType) : string; +begin + case typeID of + TType.Bool_: result := NAME_BOOL; + TType.Byte_: result := NAME_BYTE; + TType.I16: result := NAME_I16; + TType.I32: result := NAME_I32; + TType.I64: result := NAME_I64; + TType.Double_: result := NAME_DOUBLE; + TType.String_: result := NAME_STRING; + TType.Struct: result := NAME_STRUCT; + TType.Map: result := NAME_MAP; + TType.Set_: result := NAME_SET; + TType.List: result := NAME_LIST; + else + raise TProtocolExceptionNotImplemented.Create('Unrecognized type ('+IntToStr(Ord(typeID))+')'); + end; +end; + + +class function TJSONProtocolImpl.GetTypeIDForTypeName( const name : string) : TType; +begin + if name = NAME_BOOL then result := TType.Bool_ + else if name = NAME_BYTE then result := TType.Byte_ + else if name = NAME_I16 then result := TType.I16 + else if name = NAME_I32 then result := TType.I32 + else if name = NAME_I64 then result := TType.I64 + else if name = NAME_DOUBLE then result := TType.Double_ + else if name = NAME_STRUCT then result := TType.Struct + else if name = NAME_STRING then result := TType.String_ + else if name = NAME_MAP then result := TType.Map + else if name = NAME_LIST then result := TType.List + else if name = NAME_SET then result := TType.Set_ + else raise TProtocolExceptionNotImplemented.Create('Unrecognized type ('+name+')'); +end; + + +constructor TJSONProtocolImpl.TJSONBaseContext.Create( const aProto : IJSONProtocol); +begin + inherited Create; + FProto := Pointer(aProto); +end; + + +procedure TJSONProtocolImpl.TJSONBaseContext.Write; +begin + // nothing +end; + + +procedure TJSONProtocolImpl.TJSONBaseContext.Read; +begin + // nothing +end; + + +function TJSONProtocolImpl.TJSONBaseContext.EscapeNumbers : Boolean; +begin + result := FALSE; +end; + + +constructor TJSONProtocolImpl.TJSONListContext.Create( const aProto : IJSONProtocol); +begin + inherited Create( aProto); + FFirst := TRUE; +end; + + +procedure TJSONProtocolImpl.TJSONListContext.Write; +begin + if FFirst + then FFirst := FALSE + else IJSONProtocol(FProto).Transport.Write( COMMA); +end; + + +procedure TJSONProtocolImpl.TJSONListContext.Read; +begin + if FFirst + then FFirst := FALSE + else IJSONProtocol(FProto).ReadJSONSyntaxChar( COMMA[0]); +end; + + +constructor TJSONProtocolImpl.TJSONPairContext.Create( const aProto : IJSONProtocol); +begin + inherited Create( aProto); + FFirst := TRUE; + FColon := TRUE; +end; + + +procedure TJSONProtocolImpl.TJSONPairContext.Write; +begin + if FFirst then begin + FFirst := FALSE; + FColon := TRUE; + end + else begin + if FColon + then IJSONProtocol(FProto).Transport.Write( COLON) + else IJSONProtocol(FProto).Transport.Write( COMMA); + FColon := not FColon; + end; +end; + + +procedure TJSONProtocolImpl.TJSONPairContext.Read; +begin + if FFirst then begin + FFirst := FALSE; + FColon := TRUE; + end + else begin + if FColon + then IJSONProtocol(FProto).ReadJSONSyntaxChar( COLON[0]) + else IJSONProtocol(FProto).ReadJSONSyntaxChar( COMMA[0]); + FColon := not FColon; + end; +end; + + +function TJSONProtocolImpl.TJSONPairContext.EscapeNumbers : Boolean; +begin + result := FColon; +end; + + +constructor TJSONProtocolImpl.TLookaheadReader.Create( const aProto : IJSONProtocol); +begin + inherited Create; + FProto := Pointer(aProto); + FHasData := FALSE; +end; + + +function TJSONProtocolImpl.TLookaheadReader.Read : Byte; +begin + if FHasData + then FHasData := FALSE + else begin + IJSONProtocol(FProto).Transport.ReadAll( @FData, SizeOf(FData), 0, 1); + end; + result := FData; +end; + + +function TJSONProtocolImpl.TLookaheadReader.Peek : Byte; +begin + if not FHasData then begin + IJSONProtocol(FProto).Transport.ReadAll( @FData, SizeOf(FData), 0, 1); + FHasData := TRUE; + end; + result := FData; +end; + + +constructor TJSONProtocolImpl.Create( const aTrans : ITransport); +begin + inherited Create( aTrans); + + // Stack of nested contexts that we may be in + FContextStack := TStack<TJSONBaseContext>.Create; + + FContext := TJSONBaseContext.Create( Self); + FReader := TLookaheadReader.Create( Self); +end; + + +destructor TJSONProtocolImpl.Destroy; +begin + try + ResetContextStack; // free any contents + FreeAndNil( FReader); + FreeAndNil( FContext); + FreeAndNil( FContextStack); + finally + inherited Destroy; + end; +end; + + +procedure TJSONProtocolImpl.ResetContextStack; +begin + while FContextStack.Count > 0 + do PopContext; +end; + + +procedure TJSONProtocolImpl.PushContext( const aCtx : TJSONBaseContext); +begin + FContextStack.Push( FContext); + FContext := aCtx; +end; + + +procedure TJSONProtocolImpl.PopContext; +begin + FreeAndNil(FContext); + FContext := FContextStack.Pop; +end; + + +procedure TJSONProtocolImpl.ReadJSONSyntaxChar( b : Byte); +var ch : Byte; +begin + ch := FReader.Read; + if (ch <> b) + then raise TProtocolExceptionInvalidData.Create('Unexpected character ('+Char(ch)+')'); +end; + + +class function TJSONProtocolImpl.HexVal( ch : Byte) : Byte; +var i : Integer; +begin + i := StrToIntDef( '$0'+Char(ch), -1); + if (0 <= i) and (i < $10) + then result := i + else raise TProtocolExceptionInvalidData.Create('Expected hex character ('+Char(ch)+')'); +end; + + +class function TJSONProtocolImpl.HexChar( val : Byte) : Byte; +const HEXCHARS = '0123456789ABCDEF'; +begin + result := Byte( PChar(HEXCHARS)[val and $0F]); + ASSERT( Pos( Char(result), HEXCHARS) > 0); +end; + + +procedure TJSONProtocolImpl.WriteJSONString( const str : string); +begin + WriteJSONString( SysUtils.TEncoding.UTF8.GetBytes( str)); +end; + + +procedure TJSONProtocolImpl.WriteJSONString( const b : TBytes); +var i : Integer; + tmp : TBytes; +begin + FContext.Write; + Transport.Write( QUOTE); + for i := 0 to Length(b)-1 do begin + + if (b[i] and $00FF) >= $30 then begin + + if (b[i] = BACKSLASH[0]) then begin + Transport.Write( BACKSLASH); + Transport.Write( BACKSLASH); + end + else begin + Transport.Write( b, i, 1); + end; + + end + else begin + SetLength( tmp, 2); + tmp[0] := JSON_CHAR_TABLE[b[i]]; + if (tmp[0] = 1) then begin + Transport.Write( b, i, 1) + end + else if (tmp[0] > 1) then begin + Transport.Write( BACKSLASH); + Transport.Write( tmp, 0, 1); + end + else begin + Transport.Write( ESCSEQ); + tmp[0] := HexChar( b[i] div $10); + tmp[1] := HexChar( b[i]); + Transport.Write( tmp, 0, 2); + end; + end; + end; + Transport.Write( QUOTE); +end; + + +procedure TJSONProtocolImpl.WriteJSONInteger( const num : Int64); +var str : String; + escapeNum : Boolean; +begin + FContext.Write; + str := IntToStr(num); + + escapeNum := FContext.EscapeNumbers; + if escapeNum + then Transport.Write( QUOTE); + + Transport.Write( SysUtils.TEncoding.UTF8.GetBytes( str)); + + if escapeNum + then Transport.Write( QUOTE); +end; + + +procedure TJSONProtocolImpl.WriteJSONDouble( const num : Double); +var str : string; + special : Boolean; + escapeNum : Boolean; +begin + FContext.Write; + + str := FloatToStr( num, INVARIANT_CULTURE); + special := FALSE; + + case UpCase(str[1]) of + 'N' : special := TRUE; // NaN + 'I' : special := TRUE; // Infinity + '-' : special := (UpCase(str[2]) = 'I'); // -Infinity + end; + + escapeNum := special or FContext.EscapeNumbers; + + + if escapeNum + then Transport.Write( QUOTE); + + Transport.Write( SysUtils.TEncoding.UTF8.GetBytes( str)); + + if escapeNum + then Transport.Write( QUOTE); +end; + + +procedure TJSONProtocolImpl.WriteJSONBase64( const b : TBytes); +var len, off, cnt : Integer; + tmpBuf : TBytes; +begin + FContext.Write; + Transport.Write( QUOTE); + + len := Length(b); + off := 0; + SetLength( tmpBuf, 4); + + while len >= 3 do begin + // Encode 3 bytes at a time + Base64Utils.Encode( b, off, 3, tmpBuf, 0); + Transport.Write( tmpBuf, 0, 4); + Inc( off, 3); + Dec( len, 3); + end; + + // Encode remainder, if any + if len > 0 then begin + cnt := Base64Utils.Encode( b, off, len, tmpBuf, 0); + Transport.Write( tmpBuf, 0, cnt); + end; + + Transport.Write( QUOTE); +end; + + +procedure TJSONProtocolImpl.WriteJSONObjectStart; +begin + FContext.Write; + Transport.Write( LBRACE); + PushContext( TJSONPairContext.Create( Self)); +end; + + +procedure TJSONProtocolImpl.WriteJSONObjectEnd; +begin + PopContext; + Transport.Write( RBRACE); +end; + + +procedure TJSONProtocolImpl.WriteJSONArrayStart; +begin + FContext.Write; + Transport.Write( LBRACKET); + PushContext( TJSONListContext.Create( Self)); +end; + + +procedure TJSONProtocolImpl.WriteJSONArrayEnd; +begin + PopContext; + Transport.Write( RBRACKET); +end; + + +procedure TJSONProtocolImpl.WriteMessageBegin( const aMsg : TThriftMessage); +begin + ResetContextStack; // THRIFT-1473 + + WriteJSONArrayStart; + WriteJSONInteger(VERSION); + + WriteJSONString( SysUtils.TEncoding.UTF8.GetBytes( aMsg.Name)); + + WriteJSONInteger( LongInt( aMsg.Type_)); + WriteJSONInteger( aMsg.SeqID); +end; + +procedure TJSONProtocolImpl.WriteMessageEnd; +begin + WriteJSONArrayEnd; +end; + + +procedure TJSONProtocolImpl.WriteStructBegin( const struc: TThriftStruct); +begin + WriteJSONObjectStart; +end; + + +procedure TJSONProtocolImpl.WriteStructEnd; +begin + WriteJSONObjectEnd; +end; + + +procedure TJSONProtocolImpl.WriteFieldBegin( const field : TThriftField); +begin + WriteJSONInteger(field.ID); + WriteJSONObjectStart; + WriteJSONString( GetTypeNameForTypeID(field.Type_)); +end; + + +procedure TJSONProtocolImpl.WriteFieldEnd; +begin + WriteJSONObjectEnd; +end; + + +procedure TJSONProtocolImpl.WriteFieldStop; +begin + // nothing to do +end; + +procedure TJSONProtocolImpl.WriteMapBegin( const map: TThriftMap); +begin + WriteJSONArrayStart; + WriteJSONString( GetTypeNameForTypeID( map.KeyType)); + WriteJSONString( GetTypeNameForTypeID( map.ValueType)); + WriteJSONInteger( map.Count); + WriteJSONObjectStart; +end; + + +procedure TJSONProtocolImpl.WriteMapEnd; +begin + WriteJSONObjectEnd; + WriteJSONArrayEnd; +end; + + +procedure TJSONProtocolImpl.WriteListBegin( const list: TThriftList); +begin + WriteJSONArrayStart; + WriteJSONString( GetTypeNameForTypeID( list.ElementType)); + WriteJSONInteger(list.Count); +end; + + +procedure TJSONProtocolImpl.WriteListEnd; +begin + WriteJSONArrayEnd; +end; + + +procedure TJSONProtocolImpl.WriteSetBegin( const set_: TThriftSet); +begin + WriteJSONArrayStart; + WriteJSONString( GetTypeNameForTypeID( set_.ElementType)); + WriteJSONInteger( set_.Count); +end; + + +procedure TJSONProtocolImpl.WriteSetEnd; +begin + WriteJSONArrayEnd; +end; + +procedure TJSONProtocolImpl.WriteBool( b: Boolean); +begin + if b + then WriteJSONInteger( 1) + else WriteJSONInteger( 0); +end; + +procedure TJSONProtocolImpl.WriteByte( b: ShortInt); +begin + WriteJSONInteger( b); +end; + +procedure TJSONProtocolImpl.WriteI16( i16: SmallInt); +begin + WriteJSONInteger( i16); +end; + +procedure TJSONProtocolImpl.WriteI32( i32: Integer); +begin + WriteJSONInteger( i32); +end; + +procedure TJSONProtocolImpl.WriteI64( const i64: Int64); +begin + WriteJSONInteger(i64); +end; + +procedure TJSONProtocolImpl.WriteDouble( const d: Double); +begin + WriteJSONDouble( d); +end; + +procedure TJSONProtocolImpl.WriteString( const s: string ); +begin + WriteJSONString( SysUtils.TEncoding.UTF8.GetBytes( s)); +end; + +procedure TJSONProtocolImpl.WriteBinary( const b: TBytes); +begin + WriteJSONBase64( b); +end; + + +function TJSONProtocolImpl.ReadJSONString( skipContext : Boolean) : TBytes; +var buffer : TMemoryStream; + ch : Byte; + wch : Word; + highSurogate: Char; + surrogatePairs: Array[0..1] of Char; + off : Integer; + tmp : TBytes; +begin + highSurogate := #0; + buffer := TMemoryStream.Create; + try + if not skipContext + then FContext.Read; + + ReadJSONSyntaxChar( QUOTE[0]); + + while TRUE do begin + ch := FReader.Read; + + if (ch = QUOTE[0]) + then Break; + + // check for escapes + if (ch <> ESCSEQ[0]) then begin + buffer.Write( ch, 1); + Continue; + end; + + // distuinguish between \uNNNN and \? + ch := FReader.Read; + if (ch <> ESCSEQ[1]) + then begin + off := Pos( Char(ch), ESCAPE_CHARS); + if off < 1 + then raise TProtocolExceptionInvalidData.Create('Expected control char'); + ch := Byte( ESCAPE_CHAR_VALS[off]); + buffer.Write( ch, 1); + Continue; + end; + + // it is \uXXXX + SetLength( tmp, 4); + Transport.ReadAll( tmp, 0, 4); + wch := (HexVal(tmp[0]) shl 12) + + (HexVal(tmp[1]) shl 8) + + (HexVal(tmp[2]) shl 4) + + HexVal(tmp[3]); + + // we need to make UTF8 bytes from it, to be decoded later + if CharUtils.IsHighSurrogate(char(wch)) then begin + if highSurogate <> #0 + then raise TProtocolExceptionInvalidData.Create('Expected low surrogate char'); + highSurogate := char(wch); + end + else if CharUtils.IsLowSurrogate(char(wch)) then begin + if highSurogate = #0 + then TProtocolExceptionInvalidData.Create('Expected high surrogate char'); + surrogatePairs[0] := highSurogate; + surrogatePairs[1] := char(wch); + tmp := TEncoding.UTF8.GetBytes(surrogatePairs); + buffer.Write( tmp[0], Length(tmp)); + highSurogate := #0; + end + else begin + tmp := SysUtils.TEncoding.UTF8.GetBytes(Char(wch)); + buffer.Write( tmp[0], Length(tmp)); + end; + end; + + if highSurogate <> #0 + then raise TProtocolExceptionInvalidData.Create('Expected low surrogate char'); + + SetLength( result, buffer.Size); + if buffer.Size > 0 then Move( buffer.Memory^, result[0], Length(result)); + + finally + buffer.Free; + end; +end; + + +function TJSONProtocolImpl.IsJSONNumeric( b : Byte) : Boolean; +const NUMCHARS = ['+','-','.','0','1','2','3','4','5','6','7','8','9','E','e']; +begin + result := CharInSet( Char(b), NUMCHARS); +end; + + +function TJSONProtocolImpl.ReadJSONNumericChars : string; +var strbld : TThriftStringBuilder; + ch : Byte; +begin + strbld := TThriftStringBuilder.Create; + try + while TRUE do begin + ch := FReader.Peek; + if IsJSONNumeric(ch) + then strbld.Append( Char(FReader.Read)) + else Break; + end; + result := strbld.ToString; + + finally + strbld.Free; + end; +end; + + +function TJSONProtocolImpl.ReadJSONInteger : Int64; +var str : string; +begin + FContext.Read; + if FContext.EscapeNumbers + then ReadJSONSyntaxChar( QUOTE[0]); + + str := ReadJSONNumericChars; + + if FContext.EscapeNumbers + then ReadJSONSyntaxChar( QUOTE[0]); + + try + result := StrToInt64(str); + except + on e:Exception do begin + raise TProtocolExceptionInvalidData.Create('Bad data encounted in numeric data ('+str+') ('+e.Message+')'); + end; + end; +end; + + +function TJSONProtocolImpl.ReadJSONDouble : Double; +var dub : Double; + str : string; +begin + FContext.Read; + + if FReader.Peek = QUOTE[0] + then begin + str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString( TRUE)); + dub := StrToFloat( str, INVARIANT_CULTURE); + + if not FContext.EscapeNumbers() + and not Math.IsNaN(dub) + and not Math.IsInfinite(dub) + then begin + // Throw exception -- we should not be in a string in Self case + raise TProtocolExceptionInvalidData.Create('Numeric data unexpectedly quoted'); + end; + result := dub; + Exit; + end; + + // will throw - we should have had a quote if escapeNum == true + if FContext.EscapeNumbers + then ReadJSONSyntaxChar( QUOTE[0]); + + try + str := ReadJSONNumericChars; + result := StrToFloat( str, INVARIANT_CULTURE); + except + on e:Exception + do raise TProtocolExceptionInvalidData.Create('Bad data encounted in numeric data ('+str+') ('+e.Message+')'); + end; +end; + + +function TJSONProtocolImpl.ReadJSONBase64 : TBytes; +var b : TBytes; + len, off, size : Integer; +begin + b := ReadJSONString(false); + + len := Length(b); + off := 0; + size := 0; + + // reduce len to ignore fill bytes + Dec(len); + while (len >= 0) and (b[len] = Byte('=')) do Dec(len); + Inc(len); + + // read & decode full byte triplets = 4 source bytes + while (len >= 4) do begin + // Decode 4 bytes at a time + Inc( size, Base64Utils.Decode( b, off, 4, b, size)); // decoded in place + Inc( off, 4); + Dec( len, 4); + end; + + // Don't decode if we hit the end or got a single leftover byte (invalid + // base64 but legal for skip of regular string type) + if len > 1 then begin + // Decode remainder + Inc( size, Base64Utils.Decode( b, off, len, b, size)); // decoded in place + end; + + // resize to final size and return the data + SetLength( b, size); + result := b; +end; + + +procedure TJSONProtocolImpl.ReadJSONObjectStart; +begin + FContext.Read; + ReadJSONSyntaxChar( LBRACE[0]); + PushContext( TJSONPairContext.Create( Self)); +end; + + +procedure TJSONProtocolImpl.ReadJSONObjectEnd; +begin + ReadJSONSyntaxChar( RBRACE[0]); + PopContext; +end; + + +procedure TJSONProtocolImpl.ReadJSONArrayStart; +begin + FContext.Read; + ReadJSONSyntaxChar( LBRACKET[0]); + PushContext( TJSONListContext.Create( Self)); +end; + + +procedure TJSONProtocolImpl.ReadJSONArrayEnd; +begin + ReadJSONSyntaxChar( RBRACKET[0]); + PopContext; +end; + + +function TJSONProtocolImpl.ReadMessageBegin: TThriftMessage; +begin + ResetContextStack; // THRIFT-1473 + + Init( result); + ReadJSONArrayStart; + + if ReadJSONInteger <> VERSION + then raise TProtocolExceptionBadVersion.Create('Message contained bad version.'); + + result.Name := SysUtils.TEncoding.UTF8.GetString( ReadJSONString( FALSE)); + result.Type_ := TMessageType( ReadJSONInteger); + result.SeqID := ReadJSONInteger; +end; + + +procedure TJSONProtocolImpl.ReadMessageEnd; +begin + ReadJSONArrayEnd; +end; + + +function TJSONProtocolImpl.ReadStructBegin : TThriftStruct ; +begin + ReadJSONObjectStart; + Init( result); +end; + + +procedure TJSONProtocolImpl.ReadStructEnd; +begin + ReadJSONObjectEnd; +end; + + +function TJSONProtocolImpl.ReadFieldBegin : TThriftField; +var ch : Byte; + str : string; +begin + Init( result); + ch := FReader.Peek; + if ch = RBRACE[0] + then result.Type_ := TType.Stop + else begin + result.ID := ReadJSONInteger; + ReadJSONObjectStart; + + str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString( FALSE)); + result.Type_ := GetTypeIDForTypeName( str); + end; +end; + + +procedure TJSONProtocolImpl.ReadFieldEnd; +begin + ReadJSONObjectEnd; +end; + + +function TJSONProtocolImpl.ReadMapBegin : TThriftMap; +var str : string; +begin + Init( result); + ReadJSONArrayStart; + + str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString(FALSE)); + result.KeyType := GetTypeIDForTypeName( str); + + str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString(FALSE)); + result.ValueType := GetTypeIDForTypeName( str); + + result.Count := ReadJSONInteger; + ReadJSONObjectStart; +end; + + +procedure TJSONProtocolImpl.ReadMapEnd; +begin + ReadJSONObjectEnd; + ReadJSONArrayEnd; +end; + + +function TJSONProtocolImpl.ReadListBegin : TThriftList; +var str : string; +begin + Init( result); + ReadJSONArrayStart; + + str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString(FALSE)); + result.ElementType := GetTypeIDForTypeName( str); + result.Count := ReadJSONInteger; +end; + + +procedure TJSONProtocolImpl.ReadListEnd; +begin + ReadJSONArrayEnd; +end; + + +function TJSONProtocolImpl.ReadSetBegin : TThriftSet; +var str : string; +begin + Init( result); + ReadJSONArrayStart; + + str := SysUtils.TEncoding.UTF8.GetString( ReadJSONString(FALSE)); + result.ElementType := GetTypeIDForTypeName( str); + result.Count := ReadJSONInteger; +end; + + +procedure TJSONProtocolImpl.ReadSetEnd; +begin + ReadJSONArrayEnd; +end; + + +function TJSONProtocolImpl.ReadBool : Boolean; +begin + result := (ReadJSONInteger <> 0); +end; + + +function TJSONProtocolImpl.ReadByte : ShortInt; +begin + result := ReadJSONInteger; +end; + + +function TJSONProtocolImpl.ReadI16 : SmallInt; +begin + result := ReadJSONInteger; +end; + + +function TJSONProtocolImpl.ReadI32 : LongInt; +begin + result := ReadJSONInteger; +end; + + +function TJSONProtocolImpl.ReadI64 : Int64; +begin + result := ReadJSONInteger; +end; + + +function TJSONProtocolImpl.ReadDouble : Double; +begin + result := ReadJSONDouble; +end; + + +function TJSONProtocolImpl.ReadString : string; +begin + result := SysUtils.TEncoding.UTF8.GetString( ReadJSONString( FALSE)); +end; + + +function TJSONProtocolImpl.ReadBinary : TBytes; +begin + result := ReadJSONBase64; +end; + + +//--- init code --- + +procedure InitBytes( var b : TBytes; aData : array of Byte); +begin + SetLength( b, Length(aData)); + Move( aData, b[0], Length(b)); +end; + +initialization + InitBytes( COMMA, [Byte(',')]); + InitBytes( COLON, [Byte(':')]); + InitBytes( LBRACE, [Byte('{')]); + InitBytes( RBRACE, [Byte('}')]); + InitBytes( LBRACKET, [Byte('[')]); + InitBytes( RBRACKET, [Byte(']')]); + InitBytes( QUOTE, [Byte('"')]); + InitBytes( BACKSLASH, [Byte('\')]); + InitBytes( ESCSEQ, [Byte('\'),Byte('u'),Byte('0'),Byte('0')]); +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.Multiplex.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.Multiplex.pas new file mode 100644 index 000000000..93a38380d --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.Multiplex.pas @@ -0,0 +1,107 @@ +(* + * 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. + *) + +unit Thrift.Protocol.Multiplex; + +interface + +uses Thrift.Protocol; + +{ TMultiplexedProtocol is a protocol-independent concrete decorator + that allows a Thrift client to communicate with a multiplexing Thrift server, + by prepending the service name to the function name during function calls. + + NOTE: THIS IS NOT USED BY SERVERS. + On the server, use TMultiplexedProcessor to handle requests from a multiplexing client. + + This example uses a single socket transport to invoke two services: + + TSocket transport = new TSocket("localhost", 9090); + transport.open(); + + TBinaryProtocol protocol = new TBinaryProtocol(transport); + + TMultiplexedProtocol mp = new TMultiplexedProtocol(protocol, "Calculator"); + Calculator.Client service = new Calculator.Client(mp); + + TMultiplexedProtocol mp2 = new TMultiplexedProtocol(protocol, "WeatherReport"); + WeatherReport.Client service2 = new WeatherReport.Client(mp2); + + System.out.println(service.add(2,2)); + System.out.println(service2.getTemperature()); + +} + +type + TMultiplexedProtocol = class( TProtocolDecorator) + public const + { Used to delimit the service name from the function name } + SEPARATOR = ':'; + + private + FServiceName : String; + + public + { Wrap the specified protocol, allowing it to be used to communicate with a multiplexing server. + The serviceName is required as it is prepended to the message header so that the multiplexing + server can broker the function call to the proper service. + + Args: + protocol ....... Your communication protocol of choice, e.g. TBinaryProtocol. + serviceName .... The service name of the service communicating via this protocol. + } + constructor Create( const aProtocol : IProtocol; const aServiceName : string); + + { Prepends the service name to the function name, separated by SEPARATOR. + Args: The original message. + } + procedure WriteMessageBegin( const msg: TThriftMessage); override; + end; + + +implementation + + +constructor TMultiplexedProtocol.Create(const aProtocol: IProtocol; const aServiceName: string); +begin + ASSERT( aServiceName <> ''); + inherited Create(aProtocol); + FServiceName := aServiceName; +end; + + +procedure TMultiplexedProtocol.WriteMessageBegin( const msg: TThriftMessage); +// Prepends the service name to the function name, separated by TMultiplexedProtocol.SEPARATOR. +var newMsg : TThriftMessage; +begin + case msg.Type_ of + TMessageType.Call, + TMessageType.Oneway : begin + Init( newMsg, FServiceName + SEPARATOR + msg.Name, msg.Type_, msg.SeqID); + inherited WriteMessageBegin( newMsg); + end; + + else + inherited WriteMessageBegin( msg); + end; +end; + + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.pas new file mode 100644 index 000000000..609dfc605 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Protocol.pas @@ -0,0 +1,1370 @@ +(* + * 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. + *) + +{$SCOPEDENUMS ON} + +unit Thrift.Protocol; + +interface + +uses + Classes, + SysUtils, + Contnrs, + Thrift.Exception, + Thrift.Stream, + Thrift.Utils, + Thrift.Collections, + Thrift.Transport; + +type + + TType = ( + Stop = 0, + Void = 1, + Bool_ = 2, + Byte_ = 3, + Double_ = 4, + I16 = 6, + I32 = 8, + I64 = 10, + String_ = 11, + Struct = 12, + Map = 13, + Set_ = 14, + List = 15 + ); + + TMessageType = ( + Call = 1, + Reply = 2, + Exception = 3, + Oneway = 4 + ); + +const + VALID_TTYPES = [ + TType.Stop, TType.Void, + TType.Bool_, TType.Byte_, TType.Double_, TType.I16, TType.I32, TType.I64, TType.String_, + TType.Struct, TType.Map, TType.Set_, TType.List + ]; + + VALID_MESSAGETYPES = [Low(TMessageType)..High(TMessageType)]; + +const + DEFAULT_RECURSION_LIMIT = 64; + +type + IProtocol = interface; + + TThriftMessage = record + Name: string; + Type_: TMessageType; + SeqID: Integer; + end; + + TThriftStruct = record + Name: string; + end; + + TThriftField = record + Name: string; + Type_: TType; + Id: SmallInt; + end; + + TThriftList = record + ElementType: TType; + Count: Integer; + end; + + TThriftMap = record + KeyType: TType; + ValueType: TType; + Count: Integer; + end; + + TThriftSet = record + ElementType: TType; + Count: Integer; + end; + + + + IProtocolFactory = interface + ['{7CD64A10-4E9F-4E99-93BF-708A31F4A67B}'] + function GetProtocol( const trans: ITransport): IProtocol; + end; + + TProtocolException = class( TException) + public + const // TODO(jensg): change into enum + UNKNOWN = 0; + INVALID_DATA = 1; + NEGATIVE_SIZE = 2; + SIZE_LIMIT = 3; + BAD_VERSION = 4; + NOT_IMPLEMENTED = 5; + DEPTH_LIMIT = 6; + protected + constructor HiddenCreate(const Msg: string); + public + // purposefully hide inherited constructor + class function Create(const Msg: string): TProtocolException; overload; deprecated 'Use specialized TProtocolException types (or regenerate from IDL)'; + class function Create: TProtocolException; overload; deprecated 'Use specialized TProtocolException types (or regenerate from IDL)'; + class function Create( type_: Integer): TProtocolException; overload; deprecated 'Use specialized TProtocolException types (or regenerate from IDL)'; + class function Create( type_: Integer; const msg: string): TProtocolException; overload; deprecated 'Use specialized TProtocolException types (or regenerate from IDL)'; + end; + + // Needed to remove deprecation warning + TProtocolExceptionSpecialized = class abstract (TProtocolException) + public + constructor Create(const Msg: string); + end; + + TProtocolExceptionUnknown = class (TProtocolExceptionSpecialized); + TProtocolExceptionInvalidData = class (TProtocolExceptionSpecialized); + TProtocolExceptionNegativeSize = class (TProtocolExceptionSpecialized); + TProtocolExceptionSizeLimit = class (TProtocolExceptionSpecialized); + TProtocolExceptionBadVersion = class (TProtocolExceptionSpecialized); + TProtocolExceptionNotImplemented = class (TProtocolExceptionSpecialized); + TProtocolExceptionDepthLimit = class (TProtocolExceptionSpecialized); + + + TProtocolUtil = class + public + class procedure Skip( prot: IProtocol; type_: TType); + end; + + IProtocolRecursionTracker = interface + ['{29CA033F-BB56-49B1-9EE3-31B1E82FC7A5}'] + // no members yet + end; + + TProtocolRecursionTrackerImpl = class abstract( TInterfacedObject, IProtocolRecursionTracker) + protected + FProtocol : IProtocol; + public + constructor Create( prot : IProtocol); + destructor Destroy; override; + end; + + IProtocol = interface + ['{602A7FFB-0D9E-4CD8-8D7F-E5076660588A}'] + function GetTransport: ITransport; + procedure WriteMessageBegin( const msg: TThriftMessage); + procedure WriteMessageEnd; + procedure WriteStructBegin( const struc: TThriftStruct); + procedure WriteStructEnd; + procedure WriteFieldBegin( const field: TThriftField); + procedure WriteFieldEnd; + procedure WriteFieldStop; + procedure WriteMapBegin( const map: TThriftMap); + procedure WriteMapEnd; + procedure WriteListBegin( const list: TThriftList); + procedure WriteListEnd(); + procedure WriteSetBegin( const set_: TThriftSet ); + procedure WriteSetEnd(); + procedure WriteBool( b: Boolean); + procedure WriteByte( b: ShortInt); + procedure WriteI16( i16: SmallInt); + procedure WriteI32( i32: Integer); + procedure WriteI64( const i64: Int64); + procedure WriteDouble( const d: Double); + procedure WriteString( const s: string ); + procedure WriteAnsiString( const s: AnsiString); + procedure WriteBinary( const b: TBytes); + + function ReadMessageBegin: TThriftMessage; + procedure ReadMessageEnd(); + function ReadStructBegin: TThriftStruct; + procedure ReadStructEnd; + function ReadFieldBegin: TThriftField; + procedure ReadFieldEnd(); + function ReadMapBegin: TThriftMap; + procedure ReadMapEnd(); + function ReadListBegin: TThriftList; + procedure ReadListEnd(); + function ReadSetBegin: TThriftSet; + procedure ReadSetEnd(); + function ReadBool: Boolean; + function ReadByte: ShortInt; + function ReadI16: SmallInt; + function ReadI32: Integer; + function ReadI64: Int64; + function ReadDouble:Double; + function ReadBinary: TBytes; + function ReadString: string; + function ReadAnsiString: AnsiString; + + procedure SetRecursionLimit( value : Integer); + function GetRecursionLimit : Integer; + function NextRecursionLevel : IProtocolRecursionTracker; + procedure IncrementRecursionDepth; + procedure DecrementRecursionDepth; + + property Transport: ITransport read GetTransport; + property RecursionLimit : Integer read GetRecursionLimit write SetRecursionLimit; + end; + + TProtocolImpl = class abstract( TInterfacedObject, IProtocol) + protected + FTrans : ITransport; + FRecursionLimit : Integer; + FRecursionDepth : Integer; + + procedure SetRecursionLimit( value : Integer); + function GetRecursionLimit : Integer; + function NextRecursionLevel : IProtocolRecursionTracker; + procedure IncrementRecursionDepth; + procedure DecrementRecursionDepth; + + function GetTransport: ITransport; + public + procedure WriteMessageBegin( const msg: TThriftMessage); virtual; abstract; + procedure WriteMessageEnd; virtual; abstract; + procedure WriteStructBegin( const struc: TThriftStruct); virtual; abstract; + procedure WriteStructEnd; virtual; abstract; + procedure WriteFieldBegin( const field: TThriftField); virtual; abstract; + procedure WriteFieldEnd; virtual; abstract; + procedure WriteFieldStop; virtual; abstract; + procedure WriteMapBegin( const map: TThriftMap); virtual; abstract; + procedure WriteMapEnd; virtual; abstract; + procedure WriteListBegin( const list: TThriftList); virtual; abstract; + procedure WriteListEnd(); virtual; abstract; + procedure WriteSetBegin( const set_: TThriftSet ); virtual; abstract; + procedure WriteSetEnd(); virtual; abstract; + procedure WriteBool( b: Boolean); virtual; abstract; + procedure WriteByte( b: ShortInt); virtual; abstract; + procedure WriteI16( i16: SmallInt); virtual; abstract; + procedure WriteI32( i32: Integer); virtual; abstract; + procedure WriteI64( const i64: Int64); virtual; abstract; + procedure WriteDouble( const d: Double); virtual; abstract; + procedure WriteString( const s: string ); virtual; + procedure WriteAnsiString( const s: AnsiString); virtual; + procedure WriteBinary( const b: TBytes); virtual; abstract; + + function ReadMessageBegin: TThriftMessage; virtual; abstract; + procedure ReadMessageEnd(); virtual; abstract; + function ReadStructBegin: TThriftStruct; virtual; abstract; + procedure ReadStructEnd; virtual; abstract; + function ReadFieldBegin: TThriftField; virtual; abstract; + procedure ReadFieldEnd(); virtual; abstract; + function ReadMapBegin: TThriftMap; virtual; abstract; + procedure ReadMapEnd(); virtual; abstract; + function ReadListBegin: TThriftList; virtual; abstract; + procedure ReadListEnd(); virtual; abstract; + function ReadSetBegin: TThriftSet; virtual; abstract; + procedure ReadSetEnd(); virtual; abstract; + function ReadBool: Boolean; virtual; abstract; + function ReadByte: ShortInt; virtual; abstract; + function ReadI16: SmallInt; virtual; abstract; + function ReadI32: Integer; virtual; abstract; + function ReadI64: Int64; virtual; abstract; + function ReadDouble:Double; virtual; abstract; + function ReadBinary: TBytes; virtual; abstract; + function ReadString: string; virtual; + function ReadAnsiString: AnsiString; virtual; + + property Transport: ITransport read GetTransport; + + constructor Create( trans: ITransport ); + end; + + IBase = interface( ISupportsToString) + ['{AFF6CECA-5200-4540-950E-9B89E0C1C00C}'] + procedure Read( const iprot: IProtocol); + procedure Write( const iprot: IProtocol); + end; + + + TBinaryProtocolImpl = class( TProtocolImpl ) + protected + const + VERSION_MASK : Cardinal = $ffff0000; + VERSION_1 : Cardinal = $80010000; + protected + FStrictRead : Boolean; + FStrictWrite : Boolean; + + private + function ReadAll( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer ): Integer; inline; + function ReadStringBody( size: Integer): string; + + public + + type + TFactory = class( TInterfacedObject, IProtocolFactory) + protected + FStrictRead : Boolean; + FStrictWrite : Boolean; + public + function GetProtocol( const trans: ITransport): IProtocol; + constructor Create( AStrictRead, AStrictWrite: Boolean ); overload; + constructor Create; overload; + end; + + constructor Create( const trans: ITransport); overload; + constructor Create( const trans: ITransport; strictRead: Boolean; strictWrite: Boolean); overload; + + procedure WriteMessageBegin( const msg: TThriftMessage); override; + procedure WriteMessageEnd; override; + procedure WriteStructBegin( const struc: TThriftStruct); override; + procedure WriteStructEnd; override; + procedure WriteFieldBegin( const field: TThriftField); override; + procedure WriteFieldEnd; override; + procedure WriteFieldStop; override; + procedure WriteMapBegin( const map: TThriftMap); override; + procedure WriteMapEnd; override; + procedure WriteListBegin( const list: TThriftList); override; + procedure WriteListEnd(); override; + procedure WriteSetBegin( const set_: TThriftSet ); override; + procedure WriteSetEnd(); override; + procedure WriteBool( b: Boolean); override; + procedure WriteByte( b: ShortInt); override; + procedure WriteI16( i16: SmallInt); override; + procedure WriteI32( i32: Integer); override; + procedure WriteI64( const i64: Int64); override; + procedure WriteDouble( const d: Double); override; + procedure WriteBinary( const b: TBytes); override; + + function ReadMessageBegin: TThriftMessage; override; + procedure ReadMessageEnd(); override; + function ReadStructBegin: TThriftStruct; override; + procedure ReadStructEnd; override; + function ReadFieldBegin: TThriftField; override; + procedure ReadFieldEnd(); override; + function ReadMapBegin: TThriftMap; override; + procedure ReadMapEnd(); override; + function ReadListBegin: TThriftList; override; + procedure ReadListEnd(); override; + function ReadSetBegin: TThriftSet; override; + procedure ReadSetEnd(); override; + function ReadBool: Boolean; override; + function ReadByte: ShortInt; override; + function ReadI16: SmallInt; override; + function ReadI32: Integer; override; + function ReadI64: Int64; override; + function ReadDouble:Double; override; + function ReadBinary: TBytes; override; + + end; + + + { TProtocolDecorator forwards all requests to an enclosed TProtocol instance, + providing a way to author concise concrete decorator subclasses. The decorator + does not (and should not) modify the behaviour of the enclosed TProtocol + + See p.175 of Design Patterns (by Gamma et al.) + } + TProtocolDecorator = class( TProtocolImpl) + private + FWrappedProtocol : IProtocol; + + public + // Encloses the specified protocol. + // All operations will be forward to the given protocol. Must be non-null. + constructor Create( const aProtocol : IProtocol); + + procedure WriteMessageBegin( const msg: TThriftMessage); override; + procedure WriteMessageEnd; override; + procedure WriteStructBegin( const struc: TThriftStruct); override; + procedure WriteStructEnd; override; + procedure WriteFieldBegin( const field: TThriftField); override; + procedure WriteFieldEnd; override; + procedure WriteFieldStop; override; + procedure WriteMapBegin( const map: TThriftMap); override; + procedure WriteMapEnd; override; + procedure WriteListBegin( const list: TThriftList); override; + procedure WriteListEnd(); override; + procedure WriteSetBegin( const set_: TThriftSet ); override; + procedure WriteSetEnd(); override; + procedure WriteBool( b: Boolean); override; + procedure WriteByte( b: ShortInt); override; + procedure WriteI16( i16: SmallInt); override; + procedure WriteI32( i32: Integer); override; + procedure WriteI64( const i64: Int64); override; + procedure WriteDouble( const d: Double); override; + procedure WriteString( const s: string ); override; + procedure WriteAnsiString( const s: AnsiString); override; + procedure WriteBinary( const b: TBytes); override; + + function ReadMessageBegin: TThriftMessage; override; + procedure ReadMessageEnd(); override; + function ReadStructBegin: TThriftStruct; override; + procedure ReadStructEnd; override; + function ReadFieldBegin: TThriftField; override; + procedure ReadFieldEnd(); override; + function ReadMapBegin: TThriftMap; override; + procedure ReadMapEnd(); override; + function ReadListBegin: TThriftList; override; + procedure ReadListEnd(); override; + function ReadSetBegin: TThriftSet; override; + procedure ReadSetEnd(); override; + function ReadBool: Boolean; override; + function ReadByte: ShortInt; override; + function ReadI16: SmallInt; override; + function ReadI32: Integer; override; + function ReadI64: Int64; override; + function ReadDouble:Double; override; + function ReadBinary: TBytes; override; + function ReadString: string; override; + function ReadAnsiString: AnsiString; override; + end; + + +type + IRequestEvents = interface + ['{F926A26A-5B00-4560-86FA-2CAE3BA73DAF}'] + // Called before reading arguments. + procedure PreRead; + // Called between reading arguments and calling the handler. + procedure PostRead; + // Called between calling the handler and writing the response. + procedure PreWrite; + // Called after writing the response. + procedure PostWrite; + // Called when an oneway (async) function call completes successfully. + procedure OnewayComplete; + // Called if the handler throws an undeclared exception. + procedure UnhandledError( const e : Exception); + // Called when a client has finished request-handling to clean up + procedure CleanupContext; + end; + + + IProcessorEvents = interface + ['{A8661119-657C-447D-93C5-512E36162A45}'] + // Called when a client is about to call the processor. + procedure Processing( const transport : ITransport); + // Called on any service function invocation + function CreateRequestContext( const aFunctionName : string) : IRequestEvents; + // Called when a client has finished request-handling to clean up + procedure CleanupContext; + end; + + + IProcessor = interface + ['{7BAE92A5-46DA-4F13-B6EA-0EABE233EE5F}'] + function Process( const iprot :IProtocol; const oprot: IProtocol; const events : IProcessorEvents = nil): Boolean; + end; + + +procedure Init( var rec : TThriftMessage; const AName: string = ''; const AMessageType: TMessageType = Low(TMessageType); const ASeqID: Integer = 0); overload; inline; +procedure Init( var rec : TThriftStruct; const AName: string = ''); overload; inline; +procedure Init( var rec : TThriftField; const AName: string = ''; const AType: TType = Low(TType); const AID: SmallInt = 0); overload; inline; +procedure Init( var rec : TThriftMap; const AKeyType: TType = Low(TType); const AValueType: TType = Low(TType); const ACount: Integer = 0); overload; inline; +procedure Init( var rec : TThriftSet; const AElementType: TType = Low(TType); const ACount: Integer = 0); overload; inline; +procedure Init( var rec : TThriftList; const AElementType: TType = Low(TType); const ACount: Integer = 0); overload; inline; + + +implementation + +function ConvertInt64ToDouble( const n: Int64): Double; +begin + ASSERT( SizeOf(n) = SizeOf(Result)); + System.Move( n, Result, SizeOf(Result)); +end; + +function ConvertDoubleToInt64( const d: Double): Int64; +begin + ASSERT( SizeOf(d) = SizeOf(Result)); + System.Move( d, Result, SizeOf(Result)); +end; + + + +{ TProtocolRecursionTrackerImpl } + +constructor TProtocolRecursionTrackerImpl.Create( prot : IProtocol); +begin + inherited Create; + + // storing the pointer *after* the (successful) increment is important here + prot.IncrementRecursionDepth; + FProtocol := prot; +end; + +destructor TProtocolRecursionTrackerImpl.Destroy; +begin + try + // we have to release the reference iff the pointer has been stored + if FProtocol <> nil then begin + FProtocol.DecrementRecursionDepth; + FProtocol := nil; + end; + finally + inherited Destroy; + end; +end; + +{ TProtocolImpl } + +constructor TProtocolImpl.Create(trans: ITransport); +begin + inherited Create; + FTrans := trans; + FRecursionLimit := DEFAULT_RECURSION_LIMIT; + FRecursionDepth := 0; +end; + +procedure TProtocolImpl.SetRecursionLimit( value : Integer); +begin + FRecursionLimit := value; +end; + +function TProtocolImpl.GetRecursionLimit : Integer; +begin + result := FRecursionLimit; +end; + +function TProtocolImpl.NextRecursionLevel : IProtocolRecursionTracker; +begin + result := TProtocolRecursionTrackerImpl.Create(Self); +end; + +procedure TProtocolImpl.IncrementRecursionDepth; +begin + if FRecursionDepth < FRecursionLimit + then Inc(FRecursionDepth) + else raise TProtocolExceptionDepthLimit.Create('Depth limit exceeded'); +end; + +procedure TProtocolImpl.DecrementRecursionDepth; +begin + Dec(FRecursionDepth) +end; + +function TProtocolImpl.GetTransport: ITransport; +begin + Result := FTrans; +end; + +function TProtocolImpl.ReadAnsiString: AnsiString; +var + b : TBytes; + len : Integer; +begin + Result := ''; + b := ReadBinary; + len := Length( b ); + if len > 0 then + begin + SetLength( Result, len); + System.Move( b[0], Pointer(Result)^, len ); + end; +end; + +function TProtocolImpl.ReadString: string; +begin + Result := TEncoding.UTF8.GetString( ReadBinary ); +end; + +procedure TProtocolImpl.WriteAnsiString(const s: AnsiString); +var + b : TBytes; + len : Integer; +begin + len := Length(s); + SetLength( b, len); + if len > 0 then + begin + System.Move( Pointer(s)^, b[0], len ); + end; + WriteBinary( b ); +end; + +procedure TProtocolImpl.WriteString(const s: string); +var + b : TBytes; +begin + b := TEncoding.UTF8.GetBytes(s); + WriteBinary( b ); +end; + +{ TProtocolUtil } + +class procedure TProtocolUtil.Skip( prot: IProtocol; type_: TType); +var field : TThriftField; + map : TThriftMap; + set_ : TThriftSet; + list : TThriftList; + i : Integer; + tracker : IProtocolRecursionTracker; +begin + tracker := prot.NextRecursionLevel; + case type_ of + // simple types + TType.Bool_ : prot.ReadBool(); + TType.Byte_ : prot.ReadByte(); + TType.I16 : prot.ReadI16(); + TType.I32 : prot.ReadI32(); + TType.I64 : prot.ReadI64(); + TType.Double_ : prot.ReadDouble(); + TType.String_ : prot.ReadBinary();// Don't try to decode the string, just skip it. + + // structured types + TType.Struct : begin + prot.ReadStructBegin(); + while TRUE do begin + field := prot.ReadFieldBegin(); + if (field.Type_ = TType.Stop) then Break; + Skip(prot, field.Type_); + prot.ReadFieldEnd(); + end; + prot.ReadStructEnd(); + end; + + TType.Map : begin + map := prot.ReadMapBegin(); + for i := 0 to map.Count-1 do begin + Skip(prot, map.KeyType); + Skip(prot, map.ValueType); + end; + prot.ReadMapEnd(); + end; + + TType.Set_ : begin + set_ := prot.ReadSetBegin(); + for i := 0 to set_.Count-1 + do Skip( prot, set_.ElementType); + prot.ReadSetEnd(); + end; + + TType.List : begin + list := prot.ReadListBegin(); + for i := 0 to list.Count-1 + do Skip( prot, list.ElementType); + prot.ReadListEnd(); + end; + + else + raise TProtocolExceptionInvalidData.Create('Unexpected type '+IntToStr(Ord(type_))); + end; +end; + + +{ TBinaryProtocolImpl } + +constructor TBinaryProtocolImpl.Create( const trans: ITransport); +begin + //no inherited + Create( trans, False, True); +end; + +constructor TBinaryProtocolImpl.Create( const trans: ITransport; strictRead, + strictWrite: Boolean); +begin + inherited Create( trans ); + FStrictRead := strictRead; + FStrictWrite := strictWrite; +end; + +function TBinaryProtocolImpl.ReadAll( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer ): Integer; +begin + Result := FTrans.ReadAll( pBuf, buflen, off, len ); +end; + +function TBinaryProtocolImpl.ReadBinary: TBytes; +var + size : Integer; + buf : TBytes; +begin + size := ReadI32; + SetLength( buf, size ); + FTrans.ReadAll( buf, 0, size); + Result := buf; +end; + +function TBinaryProtocolImpl.ReadBool: Boolean; +begin + Result := (ReadByte = 1); +end; + +function TBinaryProtocolImpl.ReadByte: ShortInt; +begin + ReadAll( @result, SizeOf(result), 0, 1); +end; + +function TBinaryProtocolImpl.ReadDouble: Double; +begin + Result := ConvertInt64ToDouble( ReadI64 ) +end; + +function TBinaryProtocolImpl.ReadFieldBegin: TThriftField; +begin + Init( result, '', TType( ReadByte), 0); + if ( result.Type_ <> TType.Stop ) then begin + result.Id := ReadI16; + end; +end; + +procedure TBinaryProtocolImpl.ReadFieldEnd; +begin + +end; + +function TBinaryProtocolImpl.ReadI16: SmallInt; +var i16in : packed array[0..1] of Byte; +begin + ReadAll( @i16in, Sizeof(i16in), 0, 2); + Result := SmallInt(((i16in[0] and $FF) shl 8) or (i16in[1] and $FF)); +end; + +function TBinaryProtocolImpl.ReadI32: Integer; +var i32in : packed array[0..3] of Byte; +begin + ReadAll( @i32in, SizeOf(i32in), 0, 4); + + Result := Integer( + ((i32in[0] and $FF) shl 24) or + ((i32in[1] and $FF) shl 16) or + ((i32in[2] and $FF) shl 8) or + (i32in[3] and $FF)); + +end; + +function TBinaryProtocolImpl.ReadI64: Int64; +var i64in : packed array[0..7] of Byte; +begin + ReadAll( @i64in, SizeOf(i64in), 0, 8); + Result := + (Int64( i64in[0] and $FF) shl 56) or + (Int64( i64in[1] and $FF) shl 48) or + (Int64( i64in[2] and $FF) shl 40) or + (Int64( i64in[3] and $FF) shl 32) or + (Int64( i64in[4] and $FF) shl 24) or + (Int64( i64in[5] and $FF) shl 16) or + (Int64( i64in[6] and $FF) shl 8) or + (Int64( i64in[7] and $FF)); +end; + +function TBinaryProtocolImpl.ReadListBegin: TThriftList; +begin + result.ElementType := TType(ReadByte); + result.Count := ReadI32; +end; + +procedure TBinaryProtocolImpl.ReadListEnd; +begin + +end; + +function TBinaryProtocolImpl.ReadMapBegin: TThriftMap; +begin + result.KeyType := TType(ReadByte); + result.ValueType := TType(ReadByte); + result.Count := ReadI32; +end; + +procedure TBinaryProtocolImpl.ReadMapEnd; +begin + +end; + +function TBinaryProtocolImpl.ReadMessageBegin: TThriftMessage; +var + size : Integer; + version : Integer; +begin + Init( result); + size := ReadI32; + if (size < 0) then begin + version := size and Integer( VERSION_MASK); + if ( version <> Integer( VERSION_1)) then begin + raise TProtocolExceptionBadVersion.Create('Bad version in ReadMessageBegin: ' + IntToStr(version) ); + end; + result.Type_ := TMessageType( size and $000000ff); + result.Name := ReadString; + result.SeqID := ReadI32; + end + else begin + if FStrictRead then begin + raise TProtocolExceptionBadVersion.Create('Missing version in readMessageBegin, old client?' ); + end; + result.Name := ReadStringBody( size ); + result.Type_ := TMessageType( ReadByte ); + result.SeqID := ReadI32; + end; +end; + +procedure TBinaryProtocolImpl.ReadMessageEnd; +begin + inherited; + +end; + +function TBinaryProtocolImpl.ReadSetBegin: TThriftSet; +begin + result.ElementType := TType(ReadByte); + result.Count := ReadI32; +end; + +procedure TBinaryProtocolImpl.ReadSetEnd; +begin + +end; + +function TBinaryProtocolImpl.ReadStringBody( size: Integer): string; +var + buf : TBytes; +begin + SetLength( buf, size ); + FTrans.ReadAll( buf, 0, size ); + Result := TEncoding.UTF8.GetString( buf); +end; + +function TBinaryProtocolImpl.ReadStructBegin: TThriftStruct; +begin + Init( Result); +end; + +procedure TBinaryProtocolImpl.ReadStructEnd; +begin + inherited; + +end; + +procedure TBinaryProtocolImpl.WriteBinary( const b: TBytes); +var iLen : Integer; +begin + iLen := Length(b); + WriteI32( iLen); + if iLen > 0 then FTrans.Write(b, 0, iLen); +end; + +procedure TBinaryProtocolImpl.WriteBool(b: Boolean); +begin + if b then begin + WriteByte( 1 ); + end else begin + WriteByte( 0 ); + end; +end; + +procedure TBinaryProtocolImpl.WriteByte(b: ShortInt); +begin + FTrans.Write( @b, 0, 1); +end; + +procedure TBinaryProtocolImpl.WriteDouble( const d: Double); +begin + WriteI64(ConvertDoubleToInt64(d)); +end; + +procedure TBinaryProtocolImpl.WriteFieldBegin( const field: TThriftField); +begin + WriteByte(ShortInt(field.Type_)); + WriteI16(field.ID); +end; + +procedure TBinaryProtocolImpl.WriteFieldEnd; +begin + +end; + +procedure TBinaryProtocolImpl.WriteFieldStop; +begin + WriteByte(ShortInt(TType.Stop)); +end; + +procedure TBinaryProtocolImpl.WriteI16(i16: SmallInt); +var i16out : packed array[0..1] of Byte; +begin + i16out[0] := Byte($FF and (i16 shr 8)); + i16out[1] := Byte($FF and i16); + FTrans.Write( @i16out, 0, 2); +end; + +procedure TBinaryProtocolImpl.WriteI32(i32: Integer); +var i32out : packed array[0..3] of Byte; +begin + i32out[0] := Byte($FF and (i32 shr 24)); + i32out[1] := Byte($FF and (i32 shr 16)); + i32out[2] := Byte($FF and (i32 shr 8)); + i32out[3] := Byte($FF and i32); + FTrans.Write( @i32out, 0, 4); +end; + +procedure TBinaryProtocolImpl.WriteI64( const i64: Int64); +var i64out : packed array[0..7] of Byte; +begin + i64out[0] := Byte($FF and (i64 shr 56)); + i64out[1] := Byte($FF and (i64 shr 48)); + i64out[2] := Byte($FF and (i64 shr 40)); + i64out[3] := Byte($FF and (i64 shr 32)); + i64out[4] := Byte($FF and (i64 shr 24)); + i64out[5] := Byte($FF and (i64 shr 16)); + i64out[6] := Byte($FF and (i64 shr 8)); + i64out[7] := Byte($FF and i64); + FTrans.Write( @i64out, 0, 8); +end; + +procedure TBinaryProtocolImpl.WriteListBegin( const list: TThriftList); +begin + WriteByte(ShortInt(list.ElementType)); + WriteI32(list.Count); +end; + +procedure TBinaryProtocolImpl.WriteListEnd; +begin + +end; + +procedure TBinaryProtocolImpl.WriteMapBegin( const map: TThriftMap); +begin + WriteByte(ShortInt(map.KeyType)); + WriteByte(ShortInt(map.ValueType)); + WriteI32(map.Count); +end; + +procedure TBinaryProtocolImpl.WriteMapEnd; +begin + +end; + +procedure TBinaryProtocolImpl.WriteMessageBegin( const msg: TThriftMessage); +var + version : Cardinal; +begin + if FStrictWrite then + begin + version := VERSION_1 or Cardinal( msg.Type_); + WriteI32( Integer( version) ); + WriteString( msg.Name); + WriteI32( msg.SeqID); + end else + begin + WriteString( msg.Name); + WriteByte(ShortInt( msg.Type_)); + WriteI32( msg.SeqID); + end; +end; + +procedure TBinaryProtocolImpl.WriteMessageEnd; +begin + +end; + +procedure TBinaryProtocolImpl.WriteSetBegin( const set_: TThriftSet); +begin + WriteByte(ShortInt(set_.ElementType)); + WriteI32(set_.Count); +end; + +procedure TBinaryProtocolImpl.WriteSetEnd; +begin + +end; + +procedure TBinaryProtocolImpl.WriteStructBegin( const struc: TThriftStruct); +begin + +end; + +procedure TBinaryProtocolImpl.WriteStructEnd; +begin + +end; + +{ TProtocolException } + +constructor TProtocolException.HiddenCreate(const Msg: string); +begin + inherited Create(Msg); +end; + +class function TProtocolException.Create(const Msg: string): TProtocolException; +begin + Result := TProtocolExceptionUnknown.Create(Msg); +end; + +class function TProtocolException.Create: TProtocolException; +begin + Result := TProtocolExceptionUnknown.Create(''); +end; + +class function TProtocolException.Create(type_: Integer): TProtocolException; +begin +{$WARN SYMBOL_DEPRECATED OFF} + Result := Create(type_, ''); +{$WARN SYMBOL_DEPRECATED DEFAULT} +end; + +class function TProtocolException.Create(type_: Integer; const msg: string): TProtocolException; +begin + case type_ of + INVALID_DATA: Result := TProtocolExceptionInvalidData.Create(msg); + NEGATIVE_SIZE: Result := TProtocolExceptionNegativeSize.Create(msg); + SIZE_LIMIT: Result := TProtocolExceptionSizeLimit.Create(msg); + BAD_VERSION: Result := TProtocolExceptionBadVersion.Create(msg); + NOT_IMPLEMENTED: Result := TProtocolExceptionNotImplemented.Create(msg); + DEPTH_LIMIT: Result := TProtocolExceptionDepthLimit.Create(msg); + else + Result := TProtocolExceptionUnknown.Create(msg); + end; +end; + +{ TProtocolExceptionSpecialized } + +constructor TProtocolExceptionSpecialized.Create(const Msg: string); +begin + inherited HiddenCreate(Msg); +end; + +{ TBinaryProtocolImpl.TFactory } + +constructor TBinaryProtocolImpl.TFactory.Create(AStrictRead, AStrictWrite: Boolean); +begin + inherited Create; + FStrictRead := AStrictRead; + FStrictWrite := AStrictWrite; +end; + +constructor TBinaryProtocolImpl.TFactory.Create; +begin + //no inherited; + Create( False, True ) +end; + +function TBinaryProtocolImpl.TFactory.GetProtocol( const trans: ITransport): IProtocol; +begin + Result := TBinaryProtocolImpl.Create( trans, FStrictRead, FStrictWrite); +end; + + +{ TProtocolDecorator } + +constructor TProtocolDecorator.Create( const aProtocol : IProtocol); +begin + ASSERT( aProtocol <> nil); + inherited Create( aProtocol.Transport); + FWrappedProtocol := aProtocol; +end; + + +procedure TProtocolDecorator.WriteMessageBegin( const msg: TThriftMessage); +begin + FWrappedProtocol.WriteMessageBegin( msg); +end; + + +procedure TProtocolDecorator.WriteMessageEnd; +begin + FWrappedProtocol.WriteMessageEnd; +end; + + +procedure TProtocolDecorator.WriteStructBegin( const struc: TThriftStruct); +begin + FWrappedProtocol.WriteStructBegin( struc); +end; + + +procedure TProtocolDecorator.WriteStructEnd; +begin + FWrappedProtocol.WriteStructEnd; +end; + + +procedure TProtocolDecorator.WriteFieldBegin( const field: TThriftField); +begin + FWrappedProtocol.WriteFieldBegin( field); +end; + + +procedure TProtocolDecorator.WriteFieldEnd; +begin + FWrappedProtocol.WriteFieldEnd; +end; + + +procedure TProtocolDecorator.WriteFieldStop; +begin + FWrappedProtocol.WriteFieldStop; +end; + + +procedure TProtocolDecorator.WriteMapBegin( const map: TThriftMap); +begin + FWrappedProtocol.WriteMapBegin( map); +end; + + +procedure TProtocolDecorator.WriteMapEnd; +begin + FWrappedProtocol.WriteMapEnd; +end; + + +procedure TProtocolDecorator.WriteListBegin( const list: TThriftList); +begin + FWrappedProtocol.WriteListBegin( list); +end; + + +procedure TProtocolDecorator.WriteListEnd(); +begin + FWrappedProtocol.WriteListEnd(); +end; + + +procedure TProtocolDecorator.WriteSetBegin( const set_: TThriftSet ); +begin + FWrappedProtocol.WriteSetBegin( set_); +end; + + +procedure TProtocolDecorator.WriteSetEnd(); +begin + FWrappedProtocol.WriteSetEnd(); +end; + + +procedure TProtocolDecorator.WriteBool( b: Boolean); +begin + FWrappedProtocol.WriteBool( b); +end; + + +procedure TProtocolDecorator.WriteByte( b: ShortInt); +begin + FWrappedProtocol.WriteByte( b); +end; + + +procedure TProtocolDecorator.WriteI16( i16: SmallInt); +begin + FWrappedProtocol.WriteI16( i16); +end; + + +procedure TProtocolDecorator.WriteI32( i32: Integer); +begin + FWrappedProtocol.WriteI32( i32); +end; + + +procedure TProtocolDecorator.WriteI64( const i64: Int64); +begin + FWrappedProtocol.WriteI64( i64); +end; + + +procedure TProtocolDecorator.WriteDouble( const d: Double); +begin + FWrappedProtocol.WriteDouble( d); +end; + + +procedure TProtocolDecorator.WriteString( const s: string ); +begin + FWrappedProtocol.WriteString( s); +end; + + +procedure TProtocolDecorator.WriteAnsiString( const s: AnsiString); +begin + FWrappedProtocol.WriteAnsiString( s); +end; + + +procedure TProtocolDecorator.WriteBinary( const b: TBytes); +begin + FWrappedProtocol.WriteBinary( b); +end; + + +function TProtocolDecorator.ReadMessageBegin: TThriftMessage; +begin + result := FWrappedProtocol.ReadMessageBegin; +end; + + +procedure TProtocolDecorator.ReadMessageEnd(); +begin + FWrappedProtocol.ReadMessageEnd(); +end; + + +function TProtocolDecorator.ReadStructBegin: TThriftStruct; +begin + result := FWrappedProtocol.ReadStructBegin; +end; + + +procedure TProtocolDecorator.ReadStructEnd; +begin + FWrappedProtocol.ReadStructEnd; +end; + + +function TProtocolDecorator.ReadFieldBegin: TThriftField; +begin + result := FWrappedProtocol.ReadFieldBegin; +end; + + +procedure TProtocolDecorator.ReadFieldEnd(); +begin + FWrappedProtocol.ReadFieldEnd(); +end; + + +function TProtocolDecorator.ReadMapBegin: TThriftMap; +begin + result := FWrappedProtocol.ReadMapBegin; +end; + + +procedure TProtocolDecorator.ReadMapEnd(); +begin + FWrappedProtocol.ReadMapEnd(); +end; + + +function TProtocolDecorator.ReadListBegin: TThriftList; +begin + result := FWrappedProtocol.ReadListBegin; +end; + + +procedure TProtocolDecorator.ReadListEnd(); +begin + FWrappedProtocol.ReadListEnd(); +end; + + +function TProtocolDecorator.ReadSetBegin: TThriftSet; +begin + result := FWrappedProtocol.ReadSetBegin; +end; + + +procedure TProtocolDecorator.ReadSetEnd(); +begin + FWrappedProtocol.ReadSetEnd(); +end; + + +function TProtocolDecorator.ReadBool: Boolean; +begin + result := FWrappedProtocol.ReadBool; +end; + + +function TProtocolDecorator.ReadByte: ShortInt; +begin + result := FWrappedProtocol.ReadByte; +end; + + +function TProtocolDecorator.ReadI16: SmallInt; +begin + result := FWrappedProtocol.ReadI16; +end; + + +function TProtocolDecorator.ReadI32: Integer; +begin + result := FWrappedProtocol.ReadI32; +end; + + +function TProtocolDecorator.ReadI64: Int64; +begin + result := FWrappedProtocol.ReadI64; +end; + + +function TProtocolDecorator.ReadDouble:Double; +begin + result := FWrappedProtocol.ReadDouble; +end; + + +function TProtocolDecorator.ReadBinary: TBytes; +begin + result := FWrappedProtocol.ReadBinary; +end; + + +function TProtocolDecorator.ReadString: string; +begin + result := FWrappedProtocol.ReadString; +end; + + +function TProtocolDecorator.ReadAnsiString: AnsiString; +begin + result := FWrappedProtocol.ReadAnsiString; +end; + + +{ Init helper functions } + +procedure Init( var rec : TThriftMessage; const AName: string; const AMessageType: TMessageType; const ASeqID: Integer); +begin + rec.Name := AName; + rec.Type_ := AMessageType; + rec.SeqID := ASeqID; +end; + + +procedure Init( var rec : TThriftStruct; const AName: string = ''); +begin + rec.Name := AName; +end; + + +procedure Init( var rec : TThriftField; const AName: string; const AType: TType; const AID: SmallInt); +begin + rec.Name := AName; + rec.Type_ := AType; + rec.Id := AId; +end; + + +procedure Init( var rec : TThriftMap; const AKeyType, AValueType: TType; const ACount: Integer); +begin + rec.ValueType := AValueType; + rec.KeyType := AKeyType; + rec.Count := ACount; +end; + + +procedure Init( var rec : TThriftSet; const AElementType: TType; const ACount: Integer); +begin + rec.Count := ACount; + rec.ElementType := AElementType; +end; + + +procedure Init( var rec : TThriftList; const AElementType: TType; const ACount: Integer); +begin + rec.Count := ACount; + rec.ElementType := AElementType; +end; + + + + + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Serializer.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Serializer.pas new file mode 100644 index 000000000..5f2905a97 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Serializer.pas @@ -0,0 +1,230 @@ +(* + * 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. + *) +unit Thrift.Serializer; + +{$I Thrift.Defines.inc} + +interface + +uses + {$IFDEF OLD_UNIT_NAMES} + Classes, Windows, SysUtils, + {$ELSE} + System.Classes, Winapi.Windows, System.SysUtils, + {$ENDIF} + Thrift.Protocol, + Thrift.Transport, + Thrift.Stream; + + +type + // Generic utility for easily serializing objects into a byte array or Stream. + TSerializer = class + private + FStream : TMemoryStream; + FTransport : ITransport; + FProtocol : IProtocol; + + public + // Create a new TSerializer that uses the TBinaryProtocol by default. + constructor Create; overload; + + // Create a new TSerializer. + // It will use the TProtocol specified by the factory that is passed in. + constructor Create( const factory : IProtocolFactory); overload; + + // DTOR + destructor Destroy; override; + + // Serialize the Thrift object. + function Serialize( const input : IBase) : TBytes; overload; + procedure Serialize( const input : IBase; const aStm : TStream); overload; + end; + + + // Generic utility for easily deserializing objects from byte array or Stream. + TDeserializer = class + private + FStream : TMemoryStream; + FTransport : ITransport; + FProtocol : IProtocol; + + public + // Create a new TDeserializer that uses the TBinaryProtocol by default. + constructor Create; overload; + + // Create a new TDeserializer. + // It will use the TProtocol specified by the factory that is passed in. + constructor Create( const factory : IProtocolFactory); overload; + + // DTOR + destructor Destroy; override; + + // Deserialize the Thrift object data. + procedure Deserialize( const input : TBytes; const target : IBase); overload; + procedure Deserialize( const input : TStream; const target : IBase); overload; + end; + + + +implementation + + +{ TSerializer } + + +constructor TSerializer.Create(); +// Create a new TSerializer that uses the TBinaryProtocol by default. +begin + //no inherited; + Create( TBinaryProtocolImpl.TFactory.Create); +end; + + +constructor TSerializer.Create( const factory : IProtocolFactory); +// Create a new TSerializer. +// It will use the TProtocol specified by the factory that is passed in. +var adapter : IThriftStream; +begin + inherited Create; + FStream := TMemoryStream.Create; + adapter := TThriftStreamAdapterDelphi.Create( FStream, FALSE); + FTransport := TStreamTransportImpl.Create( nil, adapter); + FProtocol := factory.GetProtocol( FTransport); +end; + + +destructor TSerializer.Destroy; +begin + try + FProtocol := nil; + FTransport := nil; + FreeAndNil( FStream); + finally + inherited Destroy; + end; +end; + + +function TSerializer.Serialize( const input : IBase) : TBytes; +// Serialize the Thrift object into a byte array. The process is simple, +// just clear the byte array output, write the object into it, and grab the +// raw bytes. +var iBytes : Int64; +begin + try + FStream.Size := 0; + input.Write( FProtocol); + SetLength( result, FStream.Size); + iBytes := Length(result); + if iBytes > 0 + then Move( FStream.Memory^, result[0], iBytes); + finally + FStream.Size := 0; // free any allocated memory + end; +end; + + +procedure TSerializer.Serialize( const input : IBase; const aStm : TStream); +// Serialize the Thrift object into a byte array. The process is simple, +// just clear the byte array output, write the object into it, and grab the +// raw bytes. +const COPY_ENTIRE_STREAM = 0; +begin + try + FStream.Size := 0; + input.Write( FProtocol); + aStm.CopyFrom( FStream, COPY_ENTIRE_STREAM); + finally + FStream.Size := 0; // free any allocated memory + end; +end; + + +{ TDeserializer } + + +constructor TDeserializer.Create(); +// Create a new TDeserializer that uses the TBinaryProtocol by default. +begin + //no inherited; + Create( TBinaryProtocolImpl.TFactory.Create); +end; + + +constructor TDeserializer.Create( const factory : IProtocolFactory); +// Create a new TDeserializer. +// It will use the TProtocol specified by the factory that is passed in. +var adapter : IThriftStream; +begin + inherited Create; + FStream := TMemoryStream.Create; + adapter := TThriftStreamAdapterDelphi.Create( FStream, FALSE); + FTransport := TStreamTransportImpl.Create( adapter, nil); + FProtocol := factory.GetProtocol( FTransport); +end; + + +destructor TDeserializer.Destroy; +begin + try + FProtocol := nil; + FTransport := nil; + FreeAndNil( FStream); + finally + inherited Destroy; + end; +end; + + +procedure TDeserializer.Deserialize( const input : TBytes; const target : IBase); +// Deserialize the Thrift object data from the byte array. +var iBytes : Int64; +begin + try + iBytes := Length(input); + FStream.Size := iBytes; + if iBytes > 0 + then Move( input[0], FStream.Memory^, iBytes); + + target.Read( FProtocol); + finally + FStream.Size := 0; // free any allocated memory + end; +end; + + +procedure TDeserializer.Deserialize( const input : TStream; const target : IBase); +// Deserialize the Thrift object data from the byte array. +const COPY_ENTIRE_STREAM = 0; +var before : Int64; +begin + try + before := FStream.Position; + FStream.CopyFrom( input, COPY_ENTIRE_STREAM); + FStream.Position := before; + target.Read( FProtocol); + finally + FStream.Size := 0; // free any allocated memory + end; +end; + + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Server.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Server.pas new file mode 100644 index 000000000..13c5762cf --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Server.pas @@ -0,0 +1,423 @@ +(* + * 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. + *) + + unit Thrift.Server; + +{$I Thrift.Defines.inc} +{$I-} // prevent annoying errors with default log delegate and no console + +interface + +uses + {$IFDEF OLD_UNIT_NAMES} + Windows, SysUtils, + {$ELSE} + Winapi.Windows, System.SysUtils, + {$ENDIF} + Thrift, + Thrift.Protocol, + Thrift.Transport; + +type + IServerEvents = interface + ['{9E2A99C5-EE85-40B2-9A52-2D1722B18176}'] + // Called before the server begins. + procedure PreServe; + // Called when the server transport is ready to accept requests + procedure PreAccept; + // Called when a new client has connected and the server is about to being processing. + function CreateProcessingContext( const input, output : IProtocol) : IProcessorEvents; + end; + + + IServer = interface + ['{ADC46F2D-8199-4D1C-96D2-87FD54351723}'] + procedure Serve; + procedure Stop; + + function GetServerEvents : IServerEvents; + procedure SetServerEvents( const value : IServerEvents); + + property ServerEvents : IServerEvents read GetServerEvents write SetServerEvents; + end; + + TServerImpl = class abstract( TInterfacedObject, IServer ) + public + type + TLogDelegate = reference to procedure( const str: string); + protected + FProcessor : IProcessor; + FServerTransport : IServerTransport; + FInputTransportFactory : ITransportFactory; + FOutputTransportFactory : ITransportFactory; + FInputProtocolFactory : IProtocolFactory; + FOutputProtocolFactory : IProtocolFactory; + FLogDelegate : TLogDelegate; + FServerEvents : IServerEvents; + + class procedure DefaultLogDelegate( const str: string); + + function GetServerEvents : IServerEvents; + procedure SetServerEvents( const value : IServerEvents); + + procedure Serve; virtual; abstract; + procedure Stop; virtual; abstract; + public + constructor Create( + const AProcessor :IProcessor; + const AServerTransport: IServerTransport; + const AInputTransportFactory : ITransportFactory; + const AOutputTransportFactory : ITransportFactory; + const AInputProtocolFactory : IProtocolFactory; + const AOutputProtocolFactory : IProtocolFactory; + const ALogDelegate : TLogDelegate + ); overload; + + constructor Create( + const AProcessor :IProcessor; + const AServerTransport: IServerTransport + ); overload; + + constructor Create( + const AProcessor :IProcessor; + const AServerTransport: IServerTransport; + const ALogDelegate: TLogDelegate + ); overload; + + constructor Create( + const AProcessor :IProcessor; + const AServerTransport: IServerTransport; + const ATransportFactory : ITransportFactory + ); overload; + + constructor Create( + const AProcessor :IProcessor; + const AServerTransport: IServerTransport; + const ATransportFactory : ITransportFactory; + const AProtocolFactory : IProtocolFactory + ); overload; + end; + + TSimpleServer = class( TServerImpl) + private + FStop : Boolean; + public + constructor Create( const AProcessor: IProcessor; const AServerTransport: IServerTransport); overload; + constructor Create( const AProcessor: IProcessor; const AServerTransport: IServerTransport; + ALogDel: TServerImpl.TLogDelegate); overload; + constructor Create( const AProcessor: IProcessor; const AServerTransport: IServerTransport; + const ATransportFactory: ITransportFactory); overload; + constructor Create( const AProcessor: IProcessor; const AServerTransport: IServerTransport; + const ATransportFactory: ITransportFactory; const AProtocolFactory: IProtocolFactory); overload; + + procedure Serve; override; + procedure Stop; override; + end; + + +implementation + +{ TServerImpl } + +constructor TServerImpl.Create( const AProcessor: IProcessor; + const AServerTransport: IServerTransport; const ALogDelegate: TLogDelegate); +var + InputFactory, OutputFactory : IProtocolFactory; + InputTransFactory, OutputTransFactory : ITransportFactory; + +begin + InputFactory := TBinaryProtocolImpl.TFactory.Create; + OutputFactory := TBinaryProtocolImpl.TFactory.Create; + InputTransFactory := TTransportFactoryImpl.Create; + OutputTransFactory := TTransportFactoryImpl.Create; + + //no inherited; + Create( + AProcessor, + AServerTransport, + InputTransFactory, + OutputTransFactory, + InputFactory, + OutputFactory, + ALogDelegate + ); +end; + +constructor TServerImpl.Create(const AProcessor: IProcessor; + const AServerTransport: IServerTransport); +var + InputFactory, OutputFactory : IProtocolFactory; + InputTransFactory, OutputTransFactory : ITransportFactory; + +begin + InputFactory := TBinaryProtocolImpl.TFactory.Create; + OutputFactory := TBinaryProtocolImpl.TFactory.Create; + InputTransFactory := TTransportFactoryImpl.Create; + OutputTransFactory := TTransportFactoryImpl.Create; + + //no inherited; + Create( + AProcessor, + AServerTransport, + InputTransFactory, + OutputTransFactory, + InputFactory, + OutputFactory, + DefaultLogDelegate + ); +end; + +constructor TServerImpl.Create(const AProcessor: IProcessor; + const AServerTransport: IServerTransport; const ATransportFactory: ITransportFactory); +var + InputProtocolFactory : IProtocolFactory; + OutputProtocolFactory : IProtocolFactory; +begin + InputProtocolFactory := TBinaryProtocolImpl.TFactory.Create; + OutputProtocolFactory := TBinaryProtocolImpl.TFactory.Create; + + //no inherited; + Create( AProcessor, AServerTransport, ATransportFactory, ATransportFactory, + InputProtocolFactory, OutputProtocolFactory, DefaultLogDelegate); +end; + +constructor TServerImpl.Create(const AProcessor: IProcessor; + const AServerTransport: IServerTransport; + const AInputTransportFactory, AOutputTransportFactory: ITransportFactory; + const AInputProtocolFactory, AOutputProtocolFactory: IProtocolFactory; + const ALogDelegate : TLogDelegate); +begin + inherited Create; + FProcessor := AProcessor; + FServerTransport := AServerTransport; + FInputTransportFactory := AInputTransportFactory; + FOutputTransportFactory := AOutputTransportFactory; + FInputProtocolFactory := AInputProtocolFactory; + FOutputProtocolFactory := AOutputProtocolFactory; + FLogDelegate := ALogDelegate; +end; + +class procedure TServerImpl.DefaultLogDelegate( const str: string); +begin + try + Writeln( str); + if IoResult <> 0 then OutputDebugString(PChar(str)); + except + OutputDebugString(PChar(str)); + end; +end; + +constructor TServerImpl.Create( const AProcessor: IProcessor; + const AServerTransport: IServerTransport; const ATransportFactory: ITransportFactory; + const AProtocolFactory: IProtocolFactory); +begin + //no inherited; + Create( AProcessor, AServerTransport, + ATransportFactory, ATransportFactory, + AProtocolFactory, AProtocolFactory, + DefaultLogDelegate); +end; + + +function TServerImpl.GetServerEvents : IServerEvents; +begin + result := FServerEvents; +end; + + +procedure TServerImpl.SetServerEvents( const value : IServerEvents); +begin + // if you need more than one, provide a specialized IServerEvents implementation + FServerEvents := value; +end; + + +{ TSimpleServer } + +constructor TSimpleServer.Create( const AProcessor: IProcessor; + const AServerTransport: IServerTransport); +var + InputProtocolFactory : IProtocolFactory; + OutputProtocolFactory : IProtocolFactory; + InputTransportFactory : ITransportFactory; + OutputTransportFactory : ITransportFactory; +begin + InputProtocolFactory := TBinaryProtocolImpl.TFactory.Create; + OutputProtocolFactory := TBinaryProtocolImpl.TFactory.Create; + InputTransportFactory := TTransportFactoryImpl.Create; + OutputTransportFactory := TTransportFactoryImpl.Create; + + inherited Create( AProcessor, AServerTransport, InputTransportFactory, + OutputTransportFactory, InputProtocolFactory, OutputProtocolFactory, DefaultLogDelegate); +end; + +constructor TSimpleServer.Create( const AProcessor: IProcessor; + const AServerTransport: IServerTransport; ALogDel: TServerImpl.TLogDelegate); +var + InputProtocolFactory : IProtocolFactory; + OutputProtocolFactory : IProtocolFactory; + InputTransportFactory : ITransportFactory; + OutputTransportFactory : ITransportFactory; +begin + InputProtocolFactory := TBinaryProtocolImpl.TFactory.Create; + OutputProtocolFactory := TBinaryProtocolImpl.TFactory.Create; + InputTransportFactory := TTransportFactoryImpl.Create; + OutputTransportFactory := TTransportFactoryImpl.Create; + + inherited Create( AProcessor, AServerTransport, InputTransportFactory, + OutputTransportFactory, InputProtocolFactory, OutputProtocolFactory, ALogDel); +end; + +constructor TSimpleServer.Create( const AProcessor: IProcessor; + const AServerTransport: IServerTransport; const ATransportFactory: ITransportFactory); +begin + inherited Create( AProcessor, AServerTransport, ATransportFactory, + ATransportFactory, TBinaryProtocolImpl.TFactory.Create, TBinaryProtocolImpl.TFactory.Create, DefaultLogDelegate); +end; + +constructor TSimpleServer.Create( const AProcessor: IProcessor; + const AServerTransport: IServerTransport; const ATransportFactory: ITransportFactory; + const AProtocolFactory: IProtocolFactory); +begin + inherited Create( AProcessor, AServerTransport, ATransportFactory, + ATransportFactory, AProtocolFactory, AProtocolFactory, DefaultLogDelegate); +end; + +procedure TSimpleServer.Serve; +var + client : ITransport; + InputTransport : ITransport; + OutputTransport : ITransport; + InputProtocol : IProtocol; + OutputProtocol : IProtocol; + context : IProcessorEvents; +begin + try + FServerTransport.Listen; + except + on E: Exception do + begin + FLogDelegate( E.ToString); + end; + end; + + if FServerEvents <> nil + then FServerEvents.PreServe; + + client := nil; + while (not FStop) do + begin + try + // clean up any old instances before waiting for clients + InputTransport := nil; + OutputTransport := nil; + InputProtocol := nil; + OutputProtocol := nil; + + // close any old connections before before waiting for new clients + if client <> nil then try + try + client.Close; + finally + client := nil; + end; + except + // catch all, we can't do much about it at this point + end; + + client := FServerTransport.Accept( procedure + begin + if FServerEvents <> nil + then FServerEvents.PreAccept; + end); + + if client = nil then begin + if FStop + then Abort // silent exception + else raise TTransportExceptionUnknown.Create('ServerTransport.Accept() may not return NULL'); + end; + + FLogDelegate( 'Client Connected!'); + + InputTransport := FInputTransportFactory.GetTransport( client ); + OutputTransport := FOutputTransportFactory.GetTransport( client ); + InputProtocol := FInputProtocolFactory.GetProtocol( InputTransport ); + OutputProtocol := FOutputProtocolFactory.GetProtocol( OutputTransport ); + + if FServerEvents <> nil + then context := FServerEvents.CreateProcessingContext( InputProtocol, OutputProtocol) + else context := nil; + + while not FStop do begin + if context <> nil + then context.Processing( client); + if not FProcessor.Process( InputProtocol, OutputProtocol, context) + then Break; + end; + + except + on E: TTransportException do + begin + if FStop + then FLogDelegate('TSimpleServer was shutting down, caught ' + E.ToString) + else FLogDelegate( E.ToString); + end; + on E: Exception do + begin + FLogDelegate( E.ToString); + end; + end; + + if context <> nil + then begin + context.CleanupContext; + context := nil; + end; + + if InputTransport <> nil then + begin + InputTransport.Close; + end; + if OutputTransport <> nil then + begin + OutputTransport.Close; + end; + end; + + if FStop then + begin + try + FServerTransport.Close; + except + on E: TTransportException do + begin + FLogDelegate('TServerTranport failed on close: ' + E.Message); + end; + end; + FStop := False; + end; +end; + +procedure TSimpleServer.Stop; +begin + FStop := True; + FServerTransport.Close; +end; + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Socket.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Socket.pas new file mode 100644 index 000000000..f0cab79db --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Socket.pas @@ -0,0 +1,1617 @@ +(* + * 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. + *) + +unit Thrift.Socket; + +{$I Thrift.Defines.inc} +{$I-} // prevent annoying errors with default log delegate and no console + +interface +{$IFNDEF OLD_SOCKETS} // not for OLD_SOCKETS + +uses + Winapi.Windows, Winapi.Winsock2; + +const + AI_PASSIVE = $00000001; // Socket address will be used in bind() call + AI_CANONNAME = $00000002; // Return canonical name in first ai_canonname + AI_NUMERICHOST = $00000004; // Nodename must be a numeric address string + AI_NUMERICSERV = $00000008; // Servicename must be a numeric port number + + AI_ALL = $00000100; // Query both IP6 and IP4 with AI_V4MAPPED + AI_ADDRCONFIG = $00000400; // Resolution only if global address configured + AI_V4MAPPED = $00000800; // On v6 failure, query v4 and convert to V4MAPPED format + + AI_NON_AUTHORITATIVE = $00004000; // LUP_NON_AUTHORITATIVE + AI_SECURE = $00008000; // LUP_SECURE + AI_RETURN_PREFERRED_NAMES = $00010000; // LUP_RETURN_PREFERRED_NAMES + + AI_FQDN = $00020000; // Return the FQDN in ai_canonname + AI_FILESERVER = $00040000; // Resolving fileserver name resolution + +type + PAddrInfoA = ^TAddrInfoA; + TAddrInfoA = record + ai_flags: Integer; + ai_family: Integer; + ai_socktype: Integer; + ai_protocol: Integer; + ai_addrlen: NativeUInt; + ai_canonname: PAnsiChar; + ai_addr: PSockAddr; + ai_next: PAddrInfoA; + end; + + PAddrInfoW = ^TAddrInfoW; + TAddrInfoW = record + ai_flags: Integer; + ai_family: Integer; + ai_socktype: Integer; + ai_protocol: Integer; + ai_addrlen: NativeUInt; + ai_canonname: PChar; + ai_addr: PSockAddr; + ai_next: PAddrInfoW; + end; + + TAddressFamily = USHORT; + + TIn6Addr = record + case Integer of + 0: (_Byte: array[0..15] of UCHAR); + 1: (_Word: array[0..7] of USHORT); + end; + + TScopeId = record + public + Value: ULONG; + private + function GetBitField(Loc: Integer): Integer; inline; + procedure SetBitField(Loc: Integer; const aValue: Integer); inline; + public + property Zone: Integer index $0028 read GetBitField write SetBitField; + property Level: Integer index $2804 read GetBitField write SetBitField; + end; + + TSockAddrIn6 = record + sin6_family: TAddressFamily; + sin6_port: USHORT; + sin6_flowinfo: ULONG; + sin6_addr: TIn6Addr; + case Integer of + 0: (sin6_scope_id: ULONG); + 1: (sin6_scope_struct: TScopeId); + end; + PSockAddrIn6 = ^TSockAddrIn6; + +const + NI_NOFQDN = $01; // Only return nodename portion for local hosts + NI_NUMERICHOST = $02; // Return numeric form of the host's address + NI_NAMEREQD = $04; // Error if the host's name not in DNS + NI_NUMERICSERV = $08; // Return numeric form of the service (port #) + NI_DGRAM = $10; // Service is a datagram service + + NI_MAXHOST = 1025; // Max size of a fully-qualified domain name + NI_MAXSERV = 32; // Max size of a service name + +function getaddrinfo(pNodeName, pServiceName: PAnsiChar; const pHints: TAddrInfoA; var ppResult: PAddrInfoA): Integer; stdcall; +function GetAddrInfoW(pNodeName, pServiceName: PWideChar; const pHints: TAddrInfoW; var ppResult: PAddrInfoW): Integer; stdcall; +procedure freeaddrinfo(pAddrInfo: PAddrInfoA); stdcall; +procedure FreeAddrInfoW(pAddrInfo: PAddrInfoW); stdcall; +function getnameinfo(const pSockaddr: TSockAddr; SockaddrLength: Integer; pNodeBuffer: PAnsiChar; NodeBufferSize: DWORD; pServiceBuffer: PAnsiChar; + ServiceBufferSize: DWORD; Flags: Integer): Integer; stdcall; +function GetNameInfoW(const pSockaddr: TSockAddr; SockaddrLength: Integer; pNodeBuffer: PWideChar; NodeBufferSize: DWORD; pServiceBuffer: PWideChar; + ServiceBufferSize: DWORD; Flags: Integer): Integer; stdcall; + +type + TSmartPointerDestroyer<T> = reference to procedure(Value: T); + + ISmartPointer<T> = reference to function: T; + + TSmartPointer<T> = class(TInterfacedObject, ISmartPointer<T>) + private + FValue: T; + FDestroyer: TSmartPointerDestroyer<T>; + public + constructor Create(AValue: T; ADestroyer: TSmartPointerDestroyer<T>); + destructor Destroy; override; + function Invoke: T; + end; + + TBaseSocket = class abstract + public type + TLogDelegate = reference to procedure( const str: string); + strict private + FPort: Integer; + FSocket: Winapi.Winsock2.TSocket; + FSendTimeout, + FRecvTimeout: Longword; + FKeepAlive: Boolean; + FLogDelegate: TLogDelegate; + class constructor Create; + class destructor Destroy; + class procedure DefaultLogDelegate(const Str: string); + protected type + IGetAddrInfoWrapper = interface + function Init: Integer; + function GetRes: PAddrInfoW; + property Res: PAddrInfoW read GetRes; + end; + TGetAddrInfoWrapper = class(TInterfacedObject, IGetAddrInfoWrapper) + strict private + FNode: string; + FService: string; + FHints, + FRes: PAddrInfoW; + public + constructor Create(ANode, AService: string; AHints: PAddrInfoW); + destructor Destroy; override; + function Init: Integer; + function GetRes: PAddrInfoW; + property Res: PAddrInfoW read GetRes; + end; + strict protected + procedure CommonInit; virtual; + function CreateSocket(AAddress: string; APort: Integer): IGetAddrInfoWrapper; + procedure SetRecvTimeout(ARecvTimeout: Longword); virtual; + procedure SetSendTimeout(ASendTimeout: Longword); virtual; + procedure SetKeepAlive(AKeepAlive: Boolean); virtual; + procedure SetSocket(ASocket: Winapi.Winsock2.TSocket); + property LogDelegate: TLogDelegate read FLogDelegate; + public + // + // Constructs a new socket. Note that this does NOT actually connect the + // socket. + // + constructor Create(ALogDelegate: TLogDelegate = nil); overload; + constructor Create(APort: Integer; ALogDelegate: TLogDelegate = nil); overload; + + // + // Destroys the socket object, closing it if necessary. + // + destructor Destroy; override; + + // + // Shuts down communications on the socket + // + procedure Close; virtual; + + // The port that the socket is connected to + property Port: Integer read FPort write FPort; + + // The receive timeout + property RecvTimeout: Longword read FRecvTimeout write SetRecvTimeout; + + // The send timeout + property SendTimeout: Longword read FSendTimeout write SetSendTimeout; + + // Set SO_KEEPALIVE + property KeepAlive: Boolean read FKeepAlive write SetKeepAlive; + + // The underlying socket descriptor + property Socket: Winapi.Winsock2.TSocket read FSocket write SetSocket; + end; + + TSocket = class(TBaseSocket) + strict private type + TCachedPeerAddr = record + case Integer of + 0: (ipv4: TSockAddrIn); + 1: (ipv6: TSockAddrIn6); + end; + strict private + FHost: string; + FPeerHost: string; + FPeerAddress: string; + FPeerPort: Integer; + FInterruptListener: ISmartPointer<Winapi.Winsock2.TSocket>; + FConnTimeout: Longword; + FLingerOn: Boolean; + FLingerVal: Integer; + FNoDelay: Boolean; + FMaxRecvRetries: Longword; + FCachedPeerAddr: TCachedPeerAddr; + procedure InitPeerInfo; + procedure OpenConnection(Res: TBaseSocket.IGetAddrInfoWrapper); + procedure LocalOpen; + procedure SetGenericTimeout(S: Winapi.Winsock2.TSocket; Timeout: Longword; OptName: Integer); + function GetIsOpen: Boolean; + procedure SetNoDelay(ANoDelay: Boolean); + function GetSocketInfo: string; + function GetPeerHost: string; + function GetPeerAddress: string; + function GetPeerPort: Integer; + function GetOrigin: string; + strict protected + procedure CommonInit; override; + procedure SetRecvTimeout(ARecvTimeout: Longword); override; + procedure SetSendTimeout(ASendTimeout: Longword); override; + procedure SetKeepAlive(AKeepAlive: Boolean); override; + public + // + // Constructs a new socket. Note that this does NOT actually connect the + // socket. + // + constructor Create(ALogDelegate: TBaseSocket.TLogDelegate = nil); overload; + + // + // Constructs a new socket. Note that this does NOT actually connect the + // socket. + // + // @param host An IP address or hostname to connect to + // @param port The port to connect on + // + constructor Create(AHost: string; APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload; + + // + // Constructor to create socket from socket descriptor. + // + constructor Create(ASocket: Winapi.Winsock2.TSocket; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload; + + // + // Constructor to create socket from socket descriptor that + // can be interrupted safely. + // + constructor Create(ASocket: Winapi.Winsock2.TSocket; AInterruptListener: ISmartPointer<Winapi.Winsock2.TSocket>; + ALogDelegate: TBaseSocket.TLogDelegate = nil); overload; + + // + // Creates and opens the socket + // + // @throws ETransportationException If the socket could not connect + // + procedure Open; + + // + // Shuts down communications on the socket + // + procedure Close; override; + + // + // Reads from the underlying socket. + // \returns the number of bytes read or 0 indicates EOF + // \throws TTransportException of types: + // Interrupted means the socket was interrupted + // out of a blocking call + // NotOpen means the socket has been closed + // TimedOut means the receive timeout expired + // Unknown means something unexpected happened + // + function Read(var Buf; Len: Integer): Integer; + + // + // Writes to the underlying socket. Loops until done or fail. + // + procedure Write(const Buf; Len: Integer); + + // + // Writes to the underlying socket. Does single send() and returns result. + // + function WritePartial(const Buf; Len: Integer): Integer; + + // + // Returns a cached copy of the peer address. + // + function GetCachedAddress(out Len: Integer): PSockAddr; + + // + // Set a cache of the peer address (used when trivially available: e.g. + // accept() or connect()). Only caches IPV4 and IPV6; unset for others. + // + procedure SetCachedAddress(const Addr: TSockAddr; Len: Integer); + + // + // Controls whether the linger option is set on the socket. + // + // @param on Whether SO_LINGER is on + // @param linger If linger is active, the number of seconds to linger for + // + procedure SetLinger(LingerOn: Boolean; LingerVal: Integer); + + // + // Calls select() on the socket to see if there is more data available. + // + function Peek: Boolean; + + // Whether the socket is alive + property IsOpen: Boolean read GetIsOpen; + + // The host that the socket is connected to + property Host: string read FHost write FHost; + + // Whether to enable or disable Nagle's algorithm + property NoDelay: Boolean read FNoDelay write SetNoDelay; + + // Connect timeout + property ConnTimeout: Longword read FConnTimeout write FConnTimeout; + + // The max number of recv retries in the case of a WSAEWOULDBLOCK + property MaxRecvRetries: Longword read FMaxRecvRetries write FMaxRecvRetries; + + // Socket information formatted as a string <Host: x Port: x> + property SocketInfo: string read GetSocketInfo; + + // The DNS name of the host to which the socket is connected + property PeerHost: string read GetPeerHost; + + // The address of the host to which the socket is connected + property PeerAddress: string read GetPeerAddress; + + // The port of the host to which the socket is connected + property PeerPort: Integer read GetPeerPort; + + // The origin the socket is connected to + property Origin: string read GetOrigin; + end; + + TServerSocketFunc = reference to procedure(sock: Winapi.Winsock2.TSocket); + + TServerSocket = class(TBaseSocket) + strict private + FAddress: string; + FAcceptBacklog, + FRetryLimit, + FRetryDelay, + FTcpSendBuffer, + FTcpRecvBuffer: Integer; + FAcceptTimeout: Longword; + FListening, + FInterruptableChildren: Boolean; + FInterruptSockWriter, // is notified on Interrupt() + FInterruptSockReader, // is used in select with FSocket for interruptability + FChildInterruptSockWriter: Winapi.Winsock2.TSocket; // is notified on InterruptChildren() + FChildInterruptSockReader: ISmartPointer<Winapi.Winsock2.TSocket>; // if FnterruptableChildren this is shared with child TSockets + FListenCallback, + FAcceptCallback: TServerSocketFunc; + function CreateSocketObj(Client: Winapi.Winsock2.TSocket): TSocket; + procedure Notify(NotifySocket: Winapi.Winsock2.TSocket); + procedure SetInterruptableChildren(AValue: Boolean); + strict protected + procedure CommonInit; override; + public const + DEFAULT_BACKLOG = 1024; + public + // + // Constructor. + // + // @param port Port number to bind to + // + constructor Create(APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload; + + // + // Constructor. + // + // @param port Port number to bind to + // @param sendTimeout Socket send timeout + // @param recvTimeout Socket receive timeout + // + constructor Create(APort: Integer; ASendTimeout, ARecvTimeout: Longword; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload; + + // + // Constructor. + // + // @param address Address to bind to + // @param port Port number to bind to + // + constructor Create(AAddress: string; APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload; + + procedure Listen; + function Accept: TSocket; + procedure Interrupt; + procedure InterruptChildren; + procedure Close; override; + + property AcceptBacklog: Integer read FAcceptBacklog write FAcceptBacklog; + property AcceptTimeout: Longword read FAcceptTimeout write FAcceptTimeout; + property RetryLimit: Integer read FRetryLimit write FRetryLimit; + property RetryDelay: Integer read FRetryDelay write FRetryDelay; + property TcpSendBuffer: Integer read FTcpSendBuffer write FTcpSendBuffer; + property TcpRecvBuffer: Integer read FTcpRecvBuffer write FTcpRecvBuffer; + + // When enabled (the default), new children TSockets will be constructed so + // they can be interrupted by TServerTransport.InterruptChildren(). + // This is more expensive in terms of system calls (poll + recv) however + // ensures a connected client cannot interfere with TServer.Stop(). + // + // When disabled, TSocket children do not incur an additional poll() call. + // Server-side reads are more efficient, however a client can interfere with + // the server's ability to shutdown properly by staying connected. + // + // Must be called before listen(); mode cannot be switched after that. + // \throws EPropertyError if listen() has been called + property InterruptableChildren: Boolean read FInterruptableChildren write SetInterruptableChildren; + + // listenCallback gets called just before listen, and after all Thrift + // setsockopt calls have been made. If you have custom setsockopt + // things that need to happen on the listening socket, this is the place to do it. + property ListenCallback: TServerSocketFunc read FListenCallback write FListenCallback; + + // acceptCallback gets called after each accept call, on the newly created socket. + // It is called after all Thrift setsockopt calls have been made. If you have + // custom setsockopt things that need to happen on the accepted + // socket, this is the place to do it. + property AcceptCallback: TServerSocketFunc read FAcceptCallback write FAcceptCallback; + end; + +{$ENDIF} // not for OLD_SOCKETS +implementation +{$IFNDEF OLD_SOCKETS} // not for OLD_SOCKETS + +uses + System.SysUtils, System.Math, System.DateUtils, Thrift.Transport; + +constructor TBaseSocket.TGetAddrInfoWrapper.Create(ANode, AService: string; AHints: PAddrInfoW); +begin + inherited Create; + FNode := ANode; + FService := AService; + FHints := AHints; + FRes := nil; +end; + +destructor TBaseSocket.TGetAddrInfoWrapper.Destroy; +begin + if Assigned(FRes) then + FreeAddrInfoW(FRes); + inherited Destroy; +end; + +function TBaseSocket.TGetAddrInfoWrapper.Init: Integer; +begin + if FRes = nil then + Exit(GetAddrInfoW(@FNode[1], @FService[1], FHints^, FRes)); + Result := 0; +end; + +function TBaseSocket.TGetAddrInfoWrapper.GetRes: PAddrInfoW; +begin + Result := FRes; +end; + +procedure DestroyerOfFineSockets(ssock: Winapi.Winsock2.TSocket); +begin + closesocket(ssock); +end; + +function TScopeId.GetBitField(Loc: Integer): Integer; +begin + Result := (Value shr (Loc shr 8)) and ((1 shl (Loc and $FF)) - 1); +end; + +procedure TScopeId.SetBitField(Loc: Integer; const aValue: Integer); +begin + Value := (Value and ULONG((not ((1 shl (Loc and $FF)) - 1)))) or ULONG(aValue shl (Loc shr 8)); +end; + +function getaddrinfo; external 'ws2_32.dll' name 'getaddrinfo'; +function GetAddrInfoW; external 'ws2_32.dll' name 'GetAddrInfoW'; +procedure freeaddrinfo; external 'ws2_32.dll' name 'freeaddrinfo'; +procedure FreeAddrInfoW; external 'ws2_32.dll' name 'FreeAddrInfoW'; +function getnameinfo; external 'ws2_32.dll' name 'getnameinfo'; +function GetNameInfoW; external 'ws2_32.dll' name 'GetNameInfoW'; + +constructor TSmartPointer<T>.Create(AValue: T; ADestroyer: TSmartPointerDestroyer<T>); +begin + inherited Create; + FValue := AValue; + FDestroyer := ADestroyer; +end; + +destructor TSmartPointer<T>.Destroy; +begin + if Assigned(FDestroyer) then FDestroyer(FValue); + inherited Destroy; +end; + +function TSmartPointer<T>.Invoke: T; +begin + Result := FValue; +end; + +class constructor TBaseSocket.Create; +var + Version: WORD; + Data: WSAData; + Error: Integer; +begin + Version := $0202; + FillChar(Data, SizeOf(Data), 0); + Error := WSAStartup(Version, Data); + if Error <> 0 then + raise Exception.Create('Failed to initialize Winsock.'); +end; + +class destructor TBaseSocket.Destroy; +begin + WSACleanup; +end; + +class procedure TBaseSocket.DefaultLogDelegate(const Str: string); +var + OutStr: string; +begin + OutStr := Format('Thrift: %s %s', [DateTimeToStr(Now, TFormatSettings.Create), Str]); + try + Writeln(OutStr); + if IoResult <> 0 then OutputDebugString(PChar(OutStr)); + except + OutputDebugString(PChar(OutStr)); + end; +end; + +procedure TBaseSocket.CommonInit; +begin + FSocket := INVALID_SOCKET; + FPort := 0; + FSendTimeout := 0; + FRecvTimeout := 0; + FKeepAlive := False; + FLogDelegate := DefaultLogDelegate; +end; + +function TBaseSocket.CreateSocket(AAddress: string; APort: Integer): IGetAddrInfoWrapper; +var + Hints: TAddrInfoW; + Res: PAddrInfoW; + ThePort: array[0..5] of Char; + Error: Integer; +begin + FillChar(Hints, SizeOf(Hints), 0); + Hints.ai_family := PF_UNSPEC; + Hints.ai_socktype := SOCK_STREAM; + Hints.ai_flags := AI_PASSIVE or AI_ADDRCONFIG; + StrFmt(ThePort, '%d', [FPort]); + + Result := TGetAddrInfoWrapper.Create(AAddress, ThePort, @Hints); + Error := Result.Init; + if Error <> 0 then begin + LogDelegate(Format('GetAddrInfoW %d: %s', [Error, SysErrorMessage(Error)])); + Close; + raise TTransportExceptionNotOpen.Create('Could not resolve host for server socket.'); + end; + + // Pick the ipv6 address first since ipv4 addresses can be mapped + // into ipv6 space. + Res := Result.Res; + while Assigned(Res) do begin + if (Res^.ai_family = AF_INET6) or (not Assigned(Res^.ai_next)) then + Break; + Res := Res^.ai_next; + end; + + FSocket := Winapi.Winsock2.socket(Res^.ai_family, Res^.ai_socktype, Res^.ai_protocol); + if FSocket = INVALID_SOCKET then begin + Error := WSAGetLastError; + LogDelegate(Format('TBaseSocket.CreateSocket() socket() %s', [SysErrorMessage(Error)])); + Close; + raise TTransportExceptionNotOpen.Create(Format('socket(): %s', [SysErrorMessage(Error)])); + end; +end; + +procedure TBaseSocket.SetRecvTimeout(ARecvTimeout: Longword); +begin + FRecvTimeout := ARecvTimeout; +end; + +procedure TBaseSocket.SetSendTimeout(ASendTimeout: Longword); +begin + FSendTimeout := ASendTimeout; +end; + +procedure TBaseSocket.SetKeepAlive(AKeepAlive: Boolean); +begin + FKeepAlive := AKeepAlive; +end; + +procedure TBaseSocket.SetSocket(ASocket: Winapi.Winsock2.TSocket); +begin + if FSocket <> INVALID_SOCKET then + Close; + FSocket := ASocket; +end; + +constructor TBaseSocket.Create(ALogDelegate: TLogDelegate); +begin + inherited Create; + CommonInit; + if Assigned(ALogDelegate) then FLogDelegate := ALogDelegate; +end; + +constructor TBaseSocket.Create(APort: Integer; ALogDelegate: TLogDelegate); +begin + inherited Create; + CommonInit; + FPort := APort; + if Assigned(ALogDelegate) then FLogDelegate := ALogDelegate; +end; + +destructor TBaseSocket.Destroy; +begin + Close; + inherited Destroy; +end; + +procedure TBaseSocket.Close; +begin + if FSocket <> INVALID_SOCKET then begin + shutdown(FSocket, SD_BOTH); + closesocket(FSocket); + end; + FSocket := INVALID_SOCKET; +end; + +procedure TSocket.InitPeerInfo; +begin + FCachedPeerAddr.ipv4.sin_family := AF_UNSPEC; + FPeerHost := ''; + FPeerAddress := ''; + FPeerPort := 0; +end; + +procedure TSocket.CommonInit; +begin + inherited CommonInit; + FHost := ''; + FInterruptListener := nil; + FConnTimeout := 0; + FLingerOn := True; + FLingerVal := 0; + FNoDelay := True; + FMaxRecvRetries := 5; + InitPeerInfo; +end; + +procedure TSocket.OpenConnection(Res: TBaseSocket.IGetAddrInfoWrapper); +label + Done; +var + ErrnoCopy: Integer; + Ret, + Ret2: Integer; + Fds: TFdSet; + TVal: TTimeVal; + PTVal: PTimeVal; + Val, + Lon: Integer; + One, + Zero: Cardinal; +begin + if SendTimeout > 0 then SetSendTimeout(SendTimeout); + if RecvTimeout > 0 then SetRecvTimeout(RecvTimeout); + if KeepAlive then SetKeepAlive(KeepAlive); + SetLinger(FLingerOn, FLingerVal); + SetNoDelay(FNoDelay); + + // Set the socket to be non blocking for connect if a timeout exists + Zero := 0; + if FConnTimeout > 0 then begin + One := 1; + if ioctlsocket(Socket, Integer(FIONBIO), One) = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TSocket.OpenConnection() ioctlsocket() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('ioctlsocket() failed: %s', [SysErrorMessage(ErrnoCopy)])); + end; + end + else begin + if ioctlsocket(Socket, Integer(FIONBIO), Zero) = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TSocket.OpenConnection() ioctlsocket() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('ioctlsocket() failed: %s', [SysErrorMessage(ErrnoCopy)])); + end; + end; + + Ret := connect(Socket, Res.Res^.ai_addr^, Res.Res^.ai_addrlen); + if Ret = 0 then goto Done; + + ErrnoCopy := WSAGetLastError; + if (ErrnoCopy <> WSAEINPROGRESS) and (ErrnoCopy <> WSAEWOULDBLOCK) then begin + LogDelegate(Format('TSocket.OpenConnection() connect() ', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('connect() failed: %s', [SysErrorMessage(ErrnoCopy)])); + end; + + FD_ZERO(Fds); + _FD_SET(Socket, Fds); + if FConnTimeout > 0 then begin + TVal.tv_sec := FConnTimeout div 1000; + TVal.tv_usec := (FConnTimeout mod 1000) * 1000; + PTVal := @TVal; + end + else + PTVal := nil; + Ret := select(1, nil, @Fds, nil, PTVal); + + if Ret > 0 then begin + // Ensure the socket is connected and that there are no errors set + Lon := SizeOf(Val); + Ret2 := getsockopt(Socket, SOL_SOCKET, SO_ERROR, @Val, Lon); + if Ret2 = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TSocket.OpenConnection() getsockopt() ', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('getsockopt(): %s', [SysErrorMessage(ErrnoCopy)])); + end; + // no errors on socket, go to town + if Val = 0 then goto Done; + LogDelegate(Format('TSocket.OpenConnection() error on socket (after select()) ', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('socket OpenConnection() error: %s', [SysErrorMessage(Val)])); + end + else if Ret = 0 then begin + // socket timed out + LogDelegate(Format('TSocket.OpenConnection() timed out ', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create('OpenConnection() timed out'); + end + else begin + // error on select() + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TSocket.OpenConnection() select() ', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('select() failed: %s', [SysErrorMessage(ErrnoCopy)])); + end; + +Done: + // Set socket back to normal mode (blocking) + ioctlsocket(Socket, Integer(FIONBIO), Zero); + SetCachedAddress(Res.Res^.ai_addr^, Res.Res^.ai_addrlen); +end; + +procedure TSocket.LocalOpen; +var + Res: TBaseSocket.IGetAddrInfoWrapper; +begin + if IsOpen then Exit; + + // Validate port number + if (Port < 0) or (Port > $FFFF) then + raise TTransportExceptionBadArgs.Create('Specified port is invalid'); + + Res := CreateSocket(Host, Port); + + OpenConnection(Res); +end; + +procedure TSocket.SetGenericTimeout(S: Winapi.Winsock2.TSocket; Timeout: Longword; OptName: Integer); +var + Time: DWORD; +begin + if S = INVALID_SOCKET then + Exit; + + Time := Timeout; + + if setsockopt(S, SOL_SOCKET, OptName, @Time, SizeOf(Time)) = SOCKET_ERROR then + LogDelegate(Format('SetGenericTimeout() setsockopt() %s', [SysErrorMessage(WSAGetLastError)])); +end; + +function TSocket.GetIsOpen: Boolean; +begin + Result := Socket <> INVALID_SOCKET; +end; + +procedure TSocket.SetNoDelay(ANoDelay: Boolean); +var + V: Integer; +begin + FNoDelay := ANoDelay; + if Socket = INVALID_SOCKET then + Exit; + + V := IfThen(FNoDelay, 1, 0); + if setsockopt(Socket, IPPROTO_TCP, TCP_NODELAY, @V, SizeOf(V)) = SOCKET_ERROR then + LogDelegate(Format('TSocket.SetNoDelay() setsockopt() %s %s', [SocketInfo, SysErrorMessage(WSAGetLastError)])); +end; + +function TSocket.GetSocketInfo: string; +begin + if (FHost = '') or (Port = 0) then + Result := '<Host: ' + GetPeerAddress + ' Port: ' + GetPeerPort.ToString + '>' + else + Result := '<Host: ' + FHost + ' Port: ' + Port.ToString + '>'; +end; + +function TSocket.GetPeerHost: string; +var + Addr: TSockAddrStorage; + AddrPtr: PSockAddr; + AddrLen: Integer; + ClientHost: array[0..NI_MAXHOST-1] of Char; + ClientService: array[0..NI_MAXSERV-1] of Char; +begin + if FPeerHost = '' then begin + if Socket = INVALID_SOCKET then + Exit(FPeerHost); + + AddrPtr := GetCachedAddress(AddrLen); + if AddrPtr = nil then begin + AddrLen := SizeOf(Addr); + if getpeername(Socket, PSockAddr(@Addr)^, AddrLen) <> 0 then + Exit(FPeerHost); + AddrPtr := PSockAddr(@Addr); + SetCachedAddress(AddrPtr^, AddrLen); + end; + + GetNameInfoW(AddrPtr^, AddrLen, ClientHost, NI_MAXHOST, ClientService, NI_MAXSERV, 0); + FPeerHost := ClientHost; + end; + Result := FPeerHost; +end; + +function TSocket.GetPeerAddress: string; +var + Addr: TSockAddrStorage; + AddrPtr: PSockAddr; + AddrLen: Integer; + ClientHost: array[0..NI_MAXHOST-1] of Char; + ClientService: array[0..NI_MAXSERV-1] of Char; +begin + if FPeerAddress = '' then begin + if Socket = INVALID_SOCKET then + Exit(FPeerAddress); + + AddrPtr := GetCachedAddress(AddrLen); + if AddrPtr = nil then begin + AddrLen := SizeOf(Addr); + if getpeername(Socket, PSockAddr(@Addr)^, AddrLen) <> 0 then + Exit(FPeerHost); + AddrPtr := PSockAddr(@Addr); + SetCachedAddress(AddrPtr^, AddrLen); + end; + + GetNameInfoW(AddrPtr^, AddrLen, ClientHost, NI_MAXHOST, ClientService, NI_MAXSERV, NI_NUMERICHOST or NI_NUMERICSERV); + FPeerAddress := ClientHost; + TryStrToInt(ClientService, FPeerPort); + end; + Result := FPeerAddress +end; + +function TSocket.GetPeerPort: Integer; +begin + GetPeerAddress; + Result := FPeerPort; +end; + +function TSocket.GetOrigin: string; +begin + Result := GetPeerHost + ':' + GetPeerPort.ToString; +end; + +procedure TSocket.SetRecvTimeout(ARecvTimeout: Longword); +begin + inherited SetRecvTimeout(ARecvTimeout); + SetGenericTimeout(Socket, ARecvTimeout, SO_RCVTIMEO); +end; + +procedure TSocket.SetSendTimeout(ASendTimeout: Longword); +begin + inherited SetSendTimeout(ASendTimeout); + SetGenericTimeout(Socket, ASendTimeout, SO_SNDTIMEO); +end; + +procedure TSocket.SetKeepAlive(AKeepAlive: Boolean); +var + Value: Integer; +begin + inherited SetKeepAlive(AKeepAlive); + + Value := IfThen(KeepAlive, 1, 0); + if setsockopt(Socket, SOL_SOCKET, SO_KEEPALIVE, @Value, SizeOf(Value)) = SOCKET_ERROR then + LogDelegate(Format('TSocket.SetKeepAlive() setsockopt() %s %s', [SocketInfo, SysErrorMessage(WSAGetLastError)])); +end; + +constructor TSocket.Create(ALogDelegate: TBaseSocket.TLogDelegate = nil); +begin + // Not needed, but just a placeholder + inherited Create(ALogDelegate); +end; + +constructor TSocket.Create(AHost: string; APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate); +begin + inherited Create(APort, ALogDelegate); + FHost := AHost; +end; + +constructor TSocket.Create(ASocket: Winapi.Winsock2.TSocket; ALogDelegate: TBaseSocket.TLogDelegate); +begin + inherited Create(ALogDelegate); + Socket := ASocket; +end; + +constructor TSocket.Create(ASocket: Winapi.Winsock2.TSocket; AInterruptListener: ISmartPointer<Winapi.Winsock2.TSocket>; + ALogDelegate: TBaseSocket.TLogDelegate); +begin + inherited Create(ALogDelegate); + Socket := ASocket; + FInterruptListener := AInterruptListener; +end; + +procedure TSocket.Open; +begin + if IsOpen then Exit; + LocalOpen; +end; + +procedure TSocket.Close; +begin + inherited Close; + InitPeerInfo; +end; + +function TSocket.Read(var Buf; Len: Integer): Integer; +label + TryAgain; +var + Retries: Longword; + EAgainThreshold, + ReadElapsed: UInt64; + Start: TDateTime; + Got: Integer; + Fds: TFdSet; + ErrnoCopy: Integer; + TVal: TTimeVal; + PTVal: PTimeVal; + Ret: Integer; +begin + if Socket = INVALID_SOCKET then + raise TTransportExceptionNotOpen.Create('Called read on non-open socket'); + + Retries := 0; + + // THRIFT_EAGAIN can be signalled both when a timeout has occurred and when + // the system is out of resources (an awesome undocumented feature). + // The following is an approximation of the time interval under which + // THRIFT_EAGAIN is taken to indicate an out of resources error. + EAgainThreshold := 0; + if RecvTimeout <> 0 then + // if a readTimeout is specified along with a max number of recv retries, then + // the threshold will ensure that the read timeout is not exceeded even in the + // case of resource errors + EAgainThreshold := RecvTimeout div IfThen(FMaxRecvRetries > 0, FMaxRecvRetries, 2); + +TryAgain: + // Read from the socket + if RecvTimeout > 0 then + Start := Now + else + // if there is no read timeout we don't need the TOD to determine whether + // an THRIFT_EAGAIN is due to a timeout or an out-of-resource condition. + Start := 0; + + if Assigned(FInterruptListener) then begin + FD_ZERO(Fds); + _FD_SET(Socket, Fds); + _FD_SET(FInterruptListener, Fds); + if RecvTimeout > 0 then begin + TVal.tv_sec := RecvTimeout div 1000; + TVal.tv_usec := (RecvTimeout mod 1000) * 1000; + PTVal := @TVal; + end + else + PTVal := nil; + + Ret := select(2, @Fds, nil, nil, PTVal); + ErrnoCopy := WSAGetLastError; + if Ret < 0 then begin + // error cases + if (ErrnoCopy = WSAEINTR) and (Retries < FMaxRecvRetries) then begin + Inc(Retries); + goto TryAgain; + end; + LogDelegate(Format('TSocket.Read() select() %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionUnknown.Create(Format('Unknown: %s', [SysErrorMessage(ErrnoCopy)])); + end + else if Ret > 0 then begin + // Check the interruptListener + if FD_ISSET(FInterruptListener, Fds) then + raise TTransportExceptionInterrupted.Create('Interrupted'); + end + else // Ret = 0 + raise TTransportExceptionTimedOut.Create('WSAEWOULDBLOCK (timed out)'); + + // falling through means there is something to recv and it cannot block + end; + + Got := recv(Socket, Buf, Len, 0); + ErrnoCopy := WSAGetLastError; + // Check for error on read + if Got < 0 then begin + if ErrnoCopy = WSAEWOULDBLOCK then begin + // if no timeout we can assume that resource exhaustion has occurred. + if RecvTimeout = 0 then + raise TTransportExceptionTimedOut.Create('WSAEWOULDBLOCK (unavailable resources)'); + // check if this is the lack of resources or timeout case + ReadElapsed := MilliSecondsBetween(Now, Start); + if (EAgainThreshold = 0) or (ReadElapsed < EAgainThreshold) then begin + if Retries < FMaxRecvRetries then begin + Inc(Retries); + Sleep(1); + goto TryAgain; + end + else + raise TTransportExceptionTimedOut.Create('WSAEWOULDBLOCK (unavailable resources)'); + end + else + // infer that timeout has been hit + raise TTransportExceptionTimedOut.Create('WSAEWOULDBLOCK (timed out)'); + end; + + // If interrupted, try again + if (ErrnoCopy = WSAEINTR) and (Retries < FMaxRecvRetries) then begin + Inc(Retries); + goto TryAgain; + end; + + if ErrnoCopy = WSAECONNRESET then + Exit(0); + + // This ish isn't open + if ErrnoCopy = WSAENOTCONN then + raise TTransportExceptionNotOpen.Create('WSAENOTCONN'); + + // Timed out! + if ErrnoCopy = WSAETIMEDOUT then + raise TTransportExceptionNotOpen.Create('WSAETIMEDOUT'); + + // Now it's not a try again case, but a real probblez + LogDelegate(Format('TSocket.Read() recv() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + + // Some other error, whatevz + raise TTransportExceptionUnknown.Create(Format('Unknown: %s', [SysErrorMessage(ErrnoCopy)])); + end; + + Result := Got; +end; + +procedure TSocket.Write(const Buf; Len: Integer); +var + Sent, B: Integer; +begin + Sent := 0; + while Sent < Len do begin + B := WritePartial((PByte(@Buf) + Sent)^, Len - Sent); + if B = 0 then + // This should only happen if the timeout set with SO_SNDTIMEO expired. + // Raise an exception. + raise TTransportExceptionTimedOut.Create('send timeout expired'); + Inc(Sent, B); + end; +end; + +function TSocket.WritePartial(const Buf; Len: Integer): Integer; +var + B: Integer; + ErrnoCopy: Integer; +begin + if Socket = INVALID_SOCKET then + raise TTransportExceptionNotOpen.Create('Called write on non-open socket'); + + B := send(Socket, Buf, Len, 0); + + if B < 0 then begin + // Fail on a send error + ErrnoCopy := WSAGetLastError; + if ErrnoCopy = WSAEWOULDBLOCK then + Exit(0); + + LogDelegate(Format('TSocket.WritePartial() send() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + + if (ErrnoCopy = WSAECONNRESET) or (ErrnoCopy = WSAENOTCONN) then begin + Close; + raise TTransportExceptionNotOpen.Create(Format('write() send(): %s', [SysErrorMessage(ErrnoCopy)])); + end; + + raise TTransportExceptionUnknown.Create(Format('write() send(): %s', [SysErrorMessage(ErrnoCopy)])); + end; + + // Fail on blocked send + if B = 0 then + raise TTransportExceptionNotOpen.Create('Socket send returned 0.'); + + Result := B; +end; + +function TSocket.GetCachedAddress(out Len: Integer): PSockAddr; +begin + case FCachedPeerAddr.ipv4.sin_family of + AF_INET: begin + Len := SizeOf(TSockAddrIn); + Result := PSockAddr(@FCachedPeerAddr.ipv4); + end; + AF_INET6: begin + Len := SizeOf(TSockAddrIn6); + Result := PSockAddr(@FCachedPeerAddr.ipv6); + end; + else + Len := 0; + Result := nil; + end; +end; + +procedure TSocket.SetCachedAddress(const Addr: TSockAddr; Len: Integer); +begin + case Addr.sa_family of + AF_INET: if Len = SizeOf(TSockAddrIn) then FCachedPeerAddr.ipv4 := PSockAddrIn(@Addr)^; + AF_INET6: if Len = SizeOf(TSockAddrIn6) then FCachedPeerAddr.ipv6 := PSockAddrIn6(@Addr)^; + end; + FPeerAddress := ''; + FPeerHost := ''; + FPeerPort := 0; +end; + +procedure TSocket.SetLinger(LingerOn: Boolean; LingerVal: Integer); +var + L: TLinger; +begin + FLingerOn := LingerOn; + FLingerVal := LingerVal; + if Socket = INVALID_SOCKET then + Exit; + + L.l_onoff := IfThen(FLingerOn, 1, 0); + L.l_linger := LingerVal; + + if setsockopt(Socket, SOL_SOCKET, SO_LINGER, @L, SizeOf(L)) = SOCKET_ERROR then + LogDelegate(Format('TSocket.SetLinger() setsockopt() %s %s', [SocketInfo, SysErrorMessage(WSAGetLastError)])); +end; + +function TSocket.Peek: Boolean; +var + Retries: Longword; + Fds: TFdSet; + TVal: TTimeVal; + PTVal: PTimeVal; + Ret: Integer; + ErrnoCopy: Integer; + Buf: Byte; +begin + if not IsOpen then Exit(False); + + if Assigned(FInterruptListener) then begin + Retries := 0; + while true do begin + FD_ZERO(Fds); + _FD_SET(Socket, Fds); + _FD_SET(FInterruptListener, Fds); + if RecvTimeout > 0 then begin + TVal.tv_sec := RecvTimeout div 1000; + TVal.tv_usec := (RecvTimeout mod 1000) * 1000; + PTVal := @TVal; + end + else + PTVal := nil; + + Ret := select(2, @Fds, nil, nil, PTVal); + ErrnoCopy := WSAGetLastError; + if Ret < 0 then begin + // error cases + if (ErrnoCopy = WSAEINTR) and (Retries < FMaxRecvRetries) then begin + Inc(Retries); + Continue; + end; + LogDelegate(Format('TSocket.Peek() select() %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionUnknown.Create(Format('Unknown: %s', [SysErrorMessage(ErrnoCopy)])); + end + else if Ret > 0 then begin + // Check the interruptListener + if FD_ISSET(FInterruptListener, Fds) then + Exit(False); + // There must be data or a disconnection, fall through to the PEEK + Break; + end + else + // timeout + Exit(False); + end; + end; + + // Check to see if data is available or if the remote side closed + Ret := recv(Socket, Buf, 1, MSG_PEEK); + if Ret = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + if ErrnoCopy = WSAECONNRESET then begin + Close; + Exit(False); + end; + LogDelegate(Format('TSocket.Peek() recv() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionUnknown.Create(Format('recv(): %s', [SysErrorMessage(ErrnoCopy)])); + end; + Result := Ret > 0; +end; + +function TServerSocket.CreateSocketObj(Client: Winapi.Winsock2.TSocket): TSocket; +begin + if FInterruptableChildren then + Result := TSocket.Create(Client, FChildInterruptSockReader) + else + Result := TSocket.Create(Client); +end; + +procedure TServerSocket.Notify(NotifySocket: Winapi.Winsock2.TSocket); +var + Byt: Byte; +begin + if NotifySocket <> INVALID_SOCKET then begin + Byt := 0; + if send(NotifySocket, Byt, SizeOf(Byt), 0) = SOCKET_ERROR then + LogDelegate(Format('TServerSocket.Notify() send() %s', [SysErrorMessage(WSAGetLastError)])); + end; +end; + +procedure TServerSocket.SetInterruptableChildren(AValue: Boolean); +begin + if FListening then + raise Exception.Create('InterruptableChildren cannot be set after listen()'); + FInterruptableChildren := AValue; +end; + +procedure TServerSocket.CommonInit; +begin + inherited CommonInit; + FInterruptableChildren := True; + FAcceptBacklog := DEFAULT_BACKLOG; + FAcceptTimeout := 0; + FRetryLimit := 0; + FRetryDelay := 0; + FTcpSendBuffer := 0; + FTcpRecvBuffer := 0; + FListening := False; + FInterruptSockWriter := INVALID_SOCKET; + FInterruptSockReader := INVALID_SOCKET; + FChildInterruptSockWriter := INVALID_SOCKET; +end; + +constructor TServerSocket.Create(APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate = nil); +begin + // Unnecessary, but here for documentation purposes + inherited Create(APort, ALogDelegate); +end; + +constructor TServerSocket.Create(APort: Integer; ASendTimeout, ARecvTimeout: Longword; ALogDelegate: TBaseSocket.TLogDelegate); +begin + inherited Create(APort, ALogDelegate); + SendTimeout := ASendTimeout; + RecvTimeout := ARecvTimeout; +end; + +constructor TServerSocket.Create(AAddress: string; APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate); +begin + inherited Create(APort, ALogDelegate); + FAddress := AAddress; +end; + +procedure TServerSocket.Listen; + + function CreateSocketPair(var Reader, Writer: Winapi.Winsock2.TSocket): Integer; + label + Error; + type + TSAUnion = record + case Integer of + 0: (inaddr: TSockAddrIn); + 1: (addr: TSockAddr); + end; + var + a: TSAUnion; + listener: Winapi.Winsock2.TSocket; + e: Integer; + addrlen: Integer; + flags: DWORD; + reuse: Integer; + begin + addrlen := SizeOf(a.inaddr); + flags := 0; + reuse := 1; + + listener := Winapi.Winsock2.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if listener = INVALID_SOCKET then + Exit(SOCKET_ERROR); + + FillChar(a, SizeOf(a), 0); + a.inaddr.sin_family := AF_INET; + a.inaddr.sin_addr.s_addr := htonl(INADDR_LOOPBACK); + a.inaddr.sin_port := 0; + Reader := INVALID_SOCKET; + Writer := INVALID_SOCKET; + + // ignore errors coming out of this setsockopt. This is because + // SO_EXCLUSIVEADDRUSE requires admin privileges on WinXP, but we don't + // want to force socket pairs to be an admin. + setsockopt(listener, SOL_SOCKET, Integer(SO_EXCLUSIVEADDRUSE), @reuse, SizeOf(reuse)); + if bind(listener, a.addr, SizeOf(a.inaddr)) = SOCKET_ERROR then + goto Error; + + if getsockname(listener, a.addr, addrlen) = SOCKET_ERROR then + goto Error; + + if Winapi.Winsock2.listen(listener, 1) = SOCKET_ERROR then + goto Error; + + Reader := WSASocket(AF_INET, SOCK_STREAM, 0, nil, 0, flags); + if Reader = INVALID_SOCKET then + goto Error; + + if connect(Reader, a.addr, SizeOf(a.inaddr)) = SOCKET_ERROR then + goto Error; + + Writer := Winapi.Winsock2.accept(listener, nil, nil); + if Writer = INVALID_SOCKET then + goto Error; + + closesocket(listener); + Exit(0); + + Error: + e := WSAGetLastError; + closesocket(listener); + closesocket(Reader); + closesocket(Writer); + WSASetLastError(e); + Result := SOCKET_ERROR; + end; + +var + TempIntReader, + TempIntWriter: Winapi.Winsock2.TSocket; + One: Cardinal; + ErrnoCopy: Integer; + Ling: TLinger; + Retries: Integer; + AddrInfo: IGetAddrInfoWrapper; + SA: TSockAddrStorage; + Len: Integer; +begin + // Create the socket pair used to interrupt + if CreateSocketPair(TempIntReader, TempIntWriter) = SOCKET_ERROR then begin + LogDelegate(Format('TServerSocket.Listen() CreateSocketPair() Interrupt %s', [SysErrorMessage(WSAGetLastError)])); + FInterruptSockReader := INVALID_SOCKET; + FInterruptSockWriter := INVALID_SOCKET; + end + else begin + FInterruptSockReader := TempIntReader; + FInterruptSockWriter := TempIntWriter; + end; + + // Create the socket pair used to interrupt all clients + if CreateSocketPair(TempIntReader, TempIntWriter) = SOCKET_ERROR then begin + LogDelegate(Format('TServerSocket.Listen() CreateSocketPair() ChildInterrupt %s', [SysErrorMessage(WSAGetLastError)])); + FChildInterruptSockReader := TSmartPointer<Winapi.Winsock2.TSocket>.Create(INVALID_SOCKET, nil); + FChildInterruptSockWriter := INVALID_SOCKET; + end + else begin + FChildInterruptSockReader := TSmartPointer<Winapi.Winsock2.TSocket>.Create(TempIntReader, DestroyerOfFineSockets); + FChildInterruptSockWriter := TempIntWriter; + end; + + if (Port < 0) or (Port > $FFFF) then + raise TTransportExceptionBadArgs.Create('Specified port is invalid'); + + AddrInfo := CreateSocket(FAddress, Port); + + // Set SO_EXCLUSIVEADDRUSE to prevent 2MSL delay on accept + One := 1; + setsockopt(Socket, SOL_SOCKET, Integer(SO_EXCLUSIVEADDRUSE), @one, SizeOf(One)); + // ignore errors coming out of this setsockopt on Windows. This is because + // SO_EXCLUSIVEADDRUSE requires admin privileges on WinXP, but we don't + // want to force servers to be an admin. + + // Set TCP buffer sizes + if FTcpSendBuffer > 0 then begin + if setsockopt(Socket, SOL_SOCKET, SO_SNDBUF, @FTcpSendBuffer, SizeOf(FTcpSendBuffer)) = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TServerSocket.Listen() setsockopt() SO_SNDBUF %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('Could not set SO_SNDBUF: %s', [SysErrorMessage(ErrnoCopy)])); + end; + end; + + if FTcpRecvBuffer > 0 then begin + if setsockopt(Socket, SOL_SOCKET, SO_RCVBUF, @FTcpRecvBuffer, SizeOf(FTcpRecvBuffer)) = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TServerSocket.Listen() setsockopt() SO_RCVBUF %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('Could not set SO_RCVBUF: %s', [SysErrorMessage(ErrnoCopy)])); + end; + end; + + // Turn linger off, don't want to block on calls to close + Ling.l_onoff := 0; + Ling.l_linger := 0; + if setsockopt(Socket, SOL_SOCKET, SO_LINGER, @Ling, SizeOf(Ling)) = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TServerSocket.Listen() setsockopt() SO_LINGER %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('Could not set SO_LINGER: %s', [SysErrorMessage(ErrnoCopy)])); + end; + + // TCP Nodelay, speed over bandwidth + if setsockopt(Socket, IPPROTO_TCP, TCP_NODELAY, @One, SizeOf(One)) = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TServerSocket.Listen() setsockopt() TCP_NODELAY %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('Could not set TCP_NODELAY: %s', [SysErrorMessage(ErrnoCopy)])); + end; + + // Set NONBLOCK on the accept socket + if ioctlsocket(Socket, Integer(FIONBIO), One) = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TServerSocket.Listen() ioctlsocket() FIONBIO %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('ioctlsocket() FIONBIO: %s', [SysErrorMessage(ErrnoCopy)])); + end; + + // prepare the port information + // we may want to try to bind more than once, since THRIFT_NO_SOCKET_CACHING doesn't + // always seem to work. The client can configure the retry variables. + Retries := 0; + while True do begin + if bind(Socket, AddrInfo.Res^.ai_addr^, AddrInfo.Res^.ai_addrlen) = 0 then + Break; + Inc(Retries); + if Retries > FRetryLimit then + Break; + Sleep(FRetryDelay * 1000); + end; + + // retrieve bind info + if (Port = 0) and (Retries < FRetryLimit) then begin + Len := SizeOf(SA); + FillChar(SA, Len, 0); + if getsockname(Socket, PSockAddr(@SA)^, Len) = SOCKET_ERROR then + LogDelegate(Format('TServerSocket.Listen() getsockname() %s', [SysErrorMessage(WSAGetLastError)])) + else begin + if SA.ss_family = AF_INET6 then + Port := ntohs(PSockAddrIn6(@SA)^.sin6_port) + else + Port := ntohs(PSockAddrIn(@SA)^.sin_port); + end; + end; + + // throw an error if we failed to bind properly + if (Retries > FRetryLimit) then begin + LogDelegate(Format('TServerSocket.Listen() BIND %d', [Port])); + Close; + raise TTransportExceptionNotOpen.Create(Format('Could not bind: %s', [SysErrorMessage(WSAGetLastError)])); + end; + + if Assigned(FListenCallback) then + FListenCallback(Socket); + + // Call listen + if Winapi.Winsock2.listen(Socket, FAcceptBacklog) = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TServerSocket.Listen() listen() %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionNotOpen.Create(Format('Could not listen: %s', [SysErrorMessage(ErrnoCopy)])); + end; + + // The socket is now listening! +end; + +function TServerSocket.Accept: TSocket; +var + Fds: TFdSet; + MaxEInters, + NumEInters: Integer; + TVal: TTimeVal; + PTVal: PTimeVal; + ErrnoCopy: Integer; + Buf: Byte; + ClientAddress: TSockAddrStorage; + Size: Integer; + ClientSocket: Winapi.Winsock2.TSocket; + Zero: Cardinal; + Client: TSocket; + Ret: Integer; +begin + MaxEInters := 5; + NumEInters := 0; + + while True do begin + FD_ZERO(Fds); + _FD_SET(Socket, Fds); + _FD_SET(FInterruptSockReader, Fds); + if FAcceptTimeout > 0 then begin + TVal.tv_sec := FAcceptTimeout div 1000; + TVal.tv_usec := (FAcceptTimeout mod 1000) * 1000; + PTVal := @TVal; + end + else + PTVal := nil; + + // TODO: if WSAEINTR is received, we'll restart the timeout. + // To be accurate, we need to fix this in the future. + Ret := select(2, @Fds, nil, nil, PTVal); + + if Ret < 0 then begin + // error cases + if (WSAGetLastError = WSAEINTR) and (NumEInters < MaxEInters) then begin + // THRIFT_EINTR needs to be handled manually and we can tolerate + // a certain number + Inc(NumEInters); + Continue; + end; + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TServerSocket.Accept() select() %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionUnknown.Create(Format('Unknown: %s', [SysErrorMessage(ErrnoCopy)])); + end + else if Ret > 0 then begin + // Check for an interrupt signal + if (FInterruptSockReader <> INVALID_SOCKET) and FD_ISSET(FInterruptSockReader, Fds) then begin + if recv(FInterruptSockReader, Buf, SizeOf(Buf), 0) = SOCKET_ERROR then + LogDelegate(Format('TServerSocket.Accept() recv() interrupt %s', [SysErrorMessage(WSAGetLastError)])); + raise TTransportExceptionInterrupted.Create('interrupted'); + end; + + // Check for the actual server socket being ready + if FD_ISSET(Socket, Fds) then + Break; + end + else begin + LogDelegate('TServerSocket.Accept() select() 0'); + raise TTransportExceptionUnknown.Create('unknown error'); + end; + end; + + Size := SizeOf(ClientAddress); + ClientSocket := Winapi.Winsock2.accept(Socket, @ClientAddress, @Size); + if ClientSocket = INVALID_SOCKET then begin + ErrnoCopy := WSAGetLastError; + LogDelegate(Format('TServerSocket.Accept() accept() %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionUnknown.Create(Format('accept(): %s', [SysErrorMessage(ErrnoCopy)])); + end; + + // Make sure client socket is blocking + Zero := 0; + if ioctlsocket(ClientSocket, Integer(FIONBIO), Zero) = SOCKET_ERROR then begin + ErrnoCopy := WSAGetLastError; + closesocket(ClientSocket); + LogDelegate(Format('TServerSocket.Accept() ioctlsocket() FIONBIO %s', [SysErrorMessage(ErrnoCopy)])); + raise TTransportExceptionUnknown.Create(Format('ioctlsocket(): %s', [SysErrorMessage(ErrnoCopy)])); + end; + + Client := CreateSocketObj(ClientSocket); + if SendTimeout > 0 then + Client.SendTimeout := SendTimeout; + if RecvTimeout > 0 then + Client.RecvTimeout := RecvTimeout; + if KeepAlive then + Client.KeepAlive := KeepAlive; + Client.SetCachedAddress(PSockAddr(@ClientAddress)^, Size); + + if Assigned(FAcceptCallback) then + FAcceptCallback(ClientSocket); + + Result := Client; +end; + +procedure TServerSocket.Interrupt; +begin + Notify(FInterruptSockWriter); +end; + +procedure TServerSocket.InterruptChildren; +begin + Notify(FChildInterruptSockWriter); +end; + +procedure TServerSocket.Close; +begin + inherited Close; + if FInterruptSockWriter <> INVALID_SOCKET then + closesocket(FInterruptSockWriter); + if FInterruptSockReader <> INVALID_SOCKET then + closesocket(FInterruptSockReader); + if FChildInterruptSockWriter <> INVALID_SOCKET then + closesocket(FChildInterruptSockWriter); + FChildInterruptSockReader := TSmartPointer<Winapi.Winsock2.TSocket>.Create(INVALID_SOCKET, nil); + FListening := False; +end; + +{$ENDIF} // not for OLD_SOCKETS +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Stream.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Stream.pas new file mode 100644 index 000000000..3308c53a5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Stream.pas @@ -0,0 +1,319 @@ +(* + * 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. + *) + +unit Thrift.Stream; + +{$I Thrift.Defines.inc} + +interface + +uses + Classes, + SysUtils, + SysConst, + RTLConsts, + {$IFDEF OLD_UNIT_NAMES} + ActiveX, + {$ELSE} + Winapi.ActiveX, + {$ENDIF} + Thrift.Utils; + +type + + IThriftStream = interface + ['{2A77D916-7446-46C1-8545-0AEC0008DBCA}'] + procedure Write( const buffer: TBytes; offset: Integer; count: Integer); overload; + procedure Write( const pBuf : Pointer; offset: Integer; count: Integer); overload; + function Read( var buffer: TBytes; offset: Integer; count: Integer): Integer; overload; + function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; overload; + procedure Open; + procedure Close; + procedure Flush; + function IsOpen: Boolean; + function ToArray: TBytes; + end; + + TThriftStreamImpl = class( TInterfacedObject, IThriftStream) + private + procedure CheckSizeAndOffset( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer); overload; + protected + procedure Write( const buffer: TBytes; offset: Integer; count: Integer); overload; inline; + procedure Write( const pBuf : Pointer; offset: Integer; count: Integer); overload; virtual; + function Read( var buffer: TBytes; offset: Integer; count: Integer): Integer; overload; inline; + function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; overload; virtual; + procedure Open; virtual; abstract; + procedure Close; virtual; abstract; + procedure Flush; virtual; abstract; + function IsOpen: Boolean; virtual; abstract; + function ToArray: TBytes; virtual; abstract; + end; + + TThriftStreamAdapterDelphi = class( TThriftStreamImpl ) + private + FStream : TStream; + FOwnsStream : Boolean; + protected + procedure Write( const pBuf : Pointer; offset: Integer; count: Integer); override; + function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; override; + procedure Open; override; + procedure Close; override; + procedure Flush; override; + function IsOpen: Boolean; override; + function ToArray: TBytes; override; + public + constructor Create( const AStream: TStream; AOwnsStream : Boolean); + destructor Destroy; override; + end; + + TThriftStreamAdapterCOM = class( TThriftStreamImpl) + private + FStream : IStream; + protected + procedure Write( const pBuf : Pointer; offset: Integer; count: Integer); override; + function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; override; + procedure Open; override; + procedure Close; override; + procedure Flush; override; + function IsOpen: Boolean; override; + function ToArray: TBytes; override; + public + constructor Create( const AStream: IStream); + end; + +implementation + +{ TThriftStreamAdapterCOM } + +procedure TThriftStreamAdapterCOM.Close; +begin + FStream := nil; +end; + +constructor TThriftStreamAdapterCOM.Create( const AStream: IStream); +begin + inherited Create; + FStream := AStream; +end; + +procedure TThriftStreamAdapterCOM.Flush; +begin + if IsOpen then begin + if FStream <> nil then begin + FStream.Commit( STGC_DEFAULT ); + end; + end; +end; + +function TThriftStreamAdapterCOM.IsOpen: Boolean; +begin + Result := FStream <> nil; +end; + +procedure TThriftStreamAdapterCOM.Open; +begin + // nothing to do +end; + +function TThriftStreamAdapterCOM.Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; +var pTmp : PByte; +begin + inherited; + + if count >= buflen-offset + then count := buflen-offset; + + Result := 0; + if FStream <> nil then begin + if count > 0 then begin + pTmp := pBuf; + Inc( pTmp, offset); + FStream.Read( pTmp, count, @Result); + end; + end; +end; + +function TThriftStreamAdapterCOM.ToArray: TBytes; +var + statstg: TStatStg; + len : Integer; + NewPos : {$IF CompilerVersion >= 29.0} UInt64 {$ELSE} Int64 {$IFEND}; + cbRead : Integer; +begin + FillChar( statstg, SizeOf( statstg), 0); + len := 0; + if IsOpen then begin + if Succeeded( FStream.Stat( statstg, STATFLAG_NONAME )) then begin + len := statstg.cbSize; + end; + end; + + SetLength( Result, len ); + + if len > 0 then begin + if Succeeded( FStream.Seek( 0, STREAM_SEEK_SET, NewPos) ) then begin + FStream.Read( @Result[0], len, @cbRead); + end; + end; +end; + +procedure TThriftStreamAdapterCOM.Write( const pBuf: Pointer; offset: Integer; count: Integer); +var nWritten : Integer; + pTmp : PByte; +begin + inherited; + if IsOpen then begin + if count > 0 then begin + pTmp := pBuf; + Inc( pTmp, offset); + FStream.Write( pTmp, count, @nWritten); + end; + end; +end; + +{ TThriftStreamImpl } + +procedure TThriftStreamImpl.CheckSizeAndOffset( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer); +begin + if count > 0 then begin + if (offset < 0) or ( offset >= buflen) then begin + raise ERangeError.Create( SBitsIndexError ); + end; + if count > buflen then begin + raise ERangeError.Create( SBitsIndexError ); + end; + end; +end; + +function TThriftStreamImpl.Read(var buffer: TBytes; offset, count: Integer): Integer; +begin + if Length(buffer) > 0 + then Result := Read( @buffer[0], Length(buffer), offset, count) + else Result := 0; +end; + +function TThriftStreamImpl.Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; +begin + Result := 0; + CheckSizeAndOffset( pBuf, buflen, offset, count ); +end; + +procedure TThriftStreamImpl.Write(const buffer: TBytes; offset, count: Integer); +begin + if Length(buffer) > 0 + then Write( @buffer[0], offset, count); +end; + +procedure TThriftStreamImpl.Write( const pBuf : Pointer; offset: Integer; count: Integer); +begin + CheckSizeAndOffset( pBuf, offset+count, offset, count); +end; + +{ TThriftStreamAdapterDelphi } + +procedure TThriftStreamAdapterDelphi.Close; +begin + FStream.Free; + FStream := nil; + FOwnsStream := False; +end; + +constructor TThriftStreamAdapterDelphi.Create( const AStream: TStream; AOwnsStream: Boolean); +begin + inherited Create; + FStream := AStream; + FOwnsStream := AOwnsStream; +end; + +destructor TThriftStreamAdapterDelphi.Destroy; +begin + if FOwnsStream + then Close; + + inherited; +end; + +procedure TThriftStreamAdapterDelphi.Flush; +begin + // nothing to do +end; + +function TThriftStreamAdapterDelphi.IsOpen: Boolean; +begin + Result := FStream <> nil; +end; + +procedure TThriftStreamAdapterDelphi.Open; +begin + // nothing to do +end; + +function TThriftStreamAdapterDelphi.Read(const pBuf : Pointer; const buflen : Integer; offset, count: Integer): Integer; +var pTmp : PByte; +begin + inherited; + + if count >= buflen-offset + then count := buflen-offset; + + if count > 0 then begin + pTmp := pBuf; + Inc( pTmp, offset); + Result := FStream.Read( pTmp^, count) + end + else Result := 0; +end; + +function TThriftStreamAdapterDelphi.ToArray: TBytes; +var + OrgPos : Integer; + len : Integer; +begin + len := 0; + if FStream <> nil then + begin + len := FStream.Size; + end; + + SetLength( Result, len ); + + if len > 0 then + begin + OrgPos := FStream.Position; + try + FStream.Position := 0; + FStream.ReadBuffer( Pointer(@Result[0])^, len ); + finally + FStream.Position := OrgPos; + end; + end +end; + +procedure TThriftStreamAdapterDelphi.Write(const pBuf : Pointer; offset, count: Integer); +var pTmp : PByte; +begin + inherited; + if count > 0 then begin + pTmp := pBuf; + Inc( pTmp, offset); + FStream.Write( pTmp^, count) + end; +end; + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.MsxmlHTTP.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.MsxmlHTTP.pas new file mode 100644 index 000000000..c666e7fed --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.MsxmlHTTP.pas @@ -0,0 +1,268 @@ +(* + * 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. + *) +unit Thrift.Transport.MsxmlHTTP; + +{$I Thrift.Defines.inc} +{$SCOPEDENUMS ON} + +interface + +uses + Classes, + SysUtils, + Math, + Generics.Collections, + {$IFDEF OLD_UNIT_NAMES} + ActiveX, msxml, + {$ELSE} + Winapi.ActiveX, Winapi.msxml, + {$ENDIF} + Thrift.Collections, + Thrift.Transport, + Thrift.Exception, + Thrift.Utils, + Thrift.Stream; + +type + TMsxmlHTTPClientImpl = class( TTransportImpl, IHTTPClient) + private + FUri : string; + FInputStream : IThriftStream; + FOutputStream : IThriftStream; + FDnsResolveTimeout : Integer; + FConnectionTimeout : Integer; + FSendTimeout : Integer; + FReadTimeout : Integer; + FCustomHeaders : IThriftDictionary<string,string>; + + function CreateRequest: IXMLHTTPRequest; + protected + function GetIsOpen: Boolean; override; + procedure Open(); override; + procedure Close(); override; + function Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; override; + procedure Write( const pBuf : Pointer; off, len : Integer); override; + procedure Flush; override; + + procedure SetDnsResolveTimeout(const Value: Integer); + function GetDnsResolveTimeout: Integer; + procedure SetConnectionTimeout(const Value: Integer); + function GetConnectionTimeout: Integer; + procedure SetSendTimeout(const Value: Integer); + function GetSendTimeout: Integer; + procedure SetReadTimeout(const Value: Integer); + function GetReadTimeout: Integer; + function GetSecureProtocols : TSecureProtocols; + procedure SetSecureProtocols( const value : TSecureProtocols); + + function GetCustomHeaders: IThriftDictionary<string,string>; + procedure SendRequest; + + property DnsResolveTimeout: Integer read GetDnsResolveTimeout write SetDnsResolveTimeout; + property ConnectionTimeout: Integer read GetConnectionTimeout write SetConnectionTimeout; + property SendTimeout: Integer read GetSendTimeout write SetSendTimeout; + property ReadTimeout: Integer read GetReadTimeout write SetReadTimeout; + property CustomHeaders: IThriftDictionary<string,string> read GetCustomHeaders; + public + constructor Create( const AUri: string); + destructor Destroy; override; + end; + + +implementation + + +{ TMsxmlHTTPClientImpl } + +constructor TMsxmlHTTPClientImpl.Create(const AUri: string); +begin + inherited Create; + FUri := AUri; + + // defaults according to MSDN + FDnsResolveTimeout := 0; // no timeout + FConnectionTimeout := 60 * 1000; + FSendTimeout := 30 * 1000; + FReadTimeout := 30 * 1000; + + FCustomHeaders := TThriftDictionaryImpl<string,string>.Create; + FOutputStream := TThriftStreamAdapterDelphi.Create( TMemoryStream.Create, True); +end; + +function TMsxmlHTTPClientImpl.CreateRequest: IXMLHTTPRequest; +var + pair : TPair<string,string>; + srvHttp : IServerXMLHTTPRequest; +begin + {$IF CompilerVersion >= 21.0} + Result := CoServerXMLHTTP.Create; + {$ELSE} + Result := CoXMLHTTPRequest.Create; + {$IFEND} + + // setting a timeout value to 0 (zero) means "no timeout" for that setting + if Supports( result, IServerXMLHTTPRequest, srvHttp) + then srvHttp.setTimeouts( DnsResolveTimeout, ConnectionTimeout, SendTimeout, ReadTimeout); + + Result.open('POST', FUri, False, '', ''); + Result.setRequestHeader( 'Content-Type', THRIFT_MIMETYPE); + Result.setRequestHeader( 'Accept', THRIFT_MIMETYPE); + Result.setRequestHeader( 'User-Agent', 'Delphi/IHTTPClient'); + + for pair in FCustomHeaders do begin + Result.setRequestHeader( pair.Key, pair.Value ); + end; +end; + +destructor TMsxmlHTTPClientImpl.Destroy; +begin + Close; + inherited; +end; + +function TMsxmlHTTPClientImpl.GetDnsResolveTimeout: Integer; +begin + Result := FDnsResolveTimeout; +end; + +procedure TMsxmlHTTPClientImpl.SetDnsResolveTimeout(const Value: Integer); +begin + FDnsResolveTimeout := Value; +end; + +function TMsxmlHTTPClientImpl.GetConnectionTimeout: Integer; +begin + Result := FConnectionTimeout; +end; + +procedure TMsxmlHTTPClientImpl.SetConnectionTimeout(const Value: Integer); +begin + FConnectionTimeout := Value; +end; + +function TMsxmlHTTPClientImpl.GetSendTimeout: Integer; +begin + Result := FSendTimeout; +end; + +procedure TMsxmlHTTPClientImpl.SetSendTimeout(const Value: Integer); +begin + FSendTimeout := Value; +end; + +function TMsxmlHTTPClientImpl.GetReadTimeout: Integer; +begin + Result := FReadTimeout; +end; + +procedure TMsxmlHTTPClientImpl.SetReadTimeout(const Value: Integer); +begin + FReadTimeout := Value; +end; + +function TMsxmlHTTPClientImpl.GetSecureProtocols : TSecureProtocols; +begin + Result := []; +end; + +procedure TMsxmlHTTPClientImpl.SetSecureProtocols( const value : TSecureProtocols); +begin + raise TTransportExceptionBadArgs.Create('SetSecureProtocols: Not supported with '+ClassName); +end; + +function TMsxmlHTTPClientImpl.GetCustomHeaders: IThriftDictionary<string,string>; +begin + Result := FCustomHeaders; +end; + +function TMsxmlHTTPClientImpl.GetIsOpen: Boolean; +begin + Result := True; +end; + +procedure TMsxmlHTTPClientImpl.Open; +begin + FOutputStream := TThriftStreamAdapterDelphi.Create( TMemoryStream.Create, True); +end; + +procedure TMsxmlHTTPClientImpl.Close; +begin + FInputStream := nil; + FOutputStream := nil; +end; + +procedure TMsxmlHTTPClientImpl.Flush; +begin + try + SendRequest; + finally + FOutputStream := nil; + FOutputStream := TThriftStreamAdapterDelphi.Create( TMemoryStream.Create, True); + ASSERT( FOutputStream <> nil); + end; +end; + +function TMsxmlHTTPClientImpl.Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; +begin + if FInputStream = nil then begin + raise TTransportExceptionNotOpen.Create('No request has been sent'); + end; + + try + Result := FInputStream.Read( pBuf, buflen, off, len) + except + on E: Exception + do raise TTransportExceptionUnknown.Create(E.Message); + end; +end; + +procedure TMsxmlHTTPClientImpl.SendRequest; +var + xmlhttp : IXMLHTTPRequest; + ms : TMemoryStream; + a : TBytes; + len : Integer; +begin + xmlhttp := CreateRequest; + + ms := TMemoryStream.Create; + try + a := FOutputStream.ToArray; + len := Length(a); + if len > 0 then begin + ms.WriteBuffer( Pointer(@a[0])^, len); + end; + ms.Position := 0; + xmlhttp.send( IUnknown( TStreamAdapter.Create( ms, soReference ))); + FInputStream := nil; + FInputStream := TThriftStreamAdapterCOM.Create( IUnknown( xmlhttp.responseStream) as IStream); + finally + ms.Free; + end; +end; + +procedure TMsxmlHTTPClientImpl.Write( const pBuf : Pointer; off, len : Integer); +begin + FOutputStream.Write( pBuf, off, len); +end; + + + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.Pipes.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.Pipes.pas new file mode 100644 index 000000000..77a343b0c --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.Pipes.pas @@ -0,0 +1,1044 @@ +(* + * 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. + *) +unit Thrift.Transport.Pipes; + +{$WARN SYMBOL_PLATFORM OFF} +{$I Thrift.Defines.inc} + +interface + +uses + {$IFDEF OLD_UNIT_NAMES} + Windows, SysUtils, Math, AccCtrl, AclAPI, SyncObjs, + {$ELSE} + Winapi.Windows, System.SysUtils, System.Math, Winapi.AccCtrl, Winapi.AclAPI, System.SyncObjs, + {$ENDIF} + Thrift.Transport, + Thrift.Utils, + Thrift.Stream; + +const + DEFAULT_THRIFT_PIPE_OPEN_TIMEOUT = 10; // default: fail fast on open + + +type + //--- Pipe Streams --- + + + TPipeStreamBase = class( TThriftStreamImpl) + strict protected + FPipe : THandle; + FTimeout : DWORD; + FOpenTimeOut : DWORD; // separate value to allow for fail-fast-on-open scenarios + FOverlapped : Boolean; + + procedure Write( const pBuf : Pointer; offset, count : Integer); override; + function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; override; + //procedure Open; override; - see derived classes + procedure Close; override; + procedure Flush; override; + + function ReadDirect( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; overload; + function ReadOverlapped( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; overload; + procedure WriteDirect( const pBuf : Pointer; offset: Integer; count: Integer); overload; + procedure WriteOverlapped( const pBuf : Pointer; offset: Integer; count: Integer); overload; + + function IsOpen: Boolean; override; + function ToArray: TBytes; override; + public + constructor Create( aEnableOverlapped : Boolean; + const aTimeOut : DWORD = DEFAULT_THRIFT_TIMEOUT; + const aOpenTimeOut : DWORD = DEFAULT_THRIFT_PIPE_OPEN_TIMEOUT); + destructor Destroy; override; + end; + + + TNamedPipeStreamImpl = class sealed( TPipeStreamBase) + strict private + FPipeName : string; + FShareMode : DWORD; + FSecurityAttribs : PSecurityAttributes; + + strict protected + procedure Open; override; + + public + constructor Create( const aPipeName : string; + const aEnableOverlapped : Boolean; + const aShareMode: DWORD = 0; + const aSecurityAttributes: PSecurityAttributes = nil; + const aTimeOut : DWORD = DEFAULT_THRIFT_TIMEOUT; + const aOpenTimeOut : DWORD = DEFAULT_THRIFT_PIPE_OPEN_TIMEOUT); overload; + end; + + + THandlePipeStreamImpl = class sealed( TPipeStreamBase) + strict private + FSrcHandle : THandle; + + strict protected + procedure Open; override; + + public + constructor Create( const aPipeHandle : THandle; + const aOwnsHandle, aEnableOverlapped : Boolean; + const aTimeOut : DWORD = DEFAULT_THRIFT_TIMEOUT); overload; + destructor Destroy; override; + end; + + + //--- Pipe Transports --- + + + IPipeTransport = interface( IStreamTransport) + ['{5E05CC85-434F-428F-BFB2-856A168B5558}'] + end; + + + TPipeTransportBase = class( TStreamTransportImpl, IPipeTransport) + public + // ITransport + function GetIsOpen: Boolean; override; + procedure Open; override; + procedure Close; override; + end; + + + TNamedPipeTransportClientEndImpl = class( TPipeTransportBase) + public + // Named pipe constructors + constructor Create( aPipe : THandle; aOwnsHandle : Boolean; + const aTimeOut : DWORD); overload; + constructor Create( const aPipeName : string; + const aShareMode: DWORD = 0; + const aSecurityAttributes: PSecurityAttributes = nil; + const aTimeOut : DWORD = DEFAULT_THRIFT_TIMEOUT; + const aOpenTimeOut : DWORD = DEFAULT_THRIFT_PIPE_OPEN_TIMEOUT); overload; + end; + + + TNamedPipeTransportServerEndImpl = class( TNamedPipeTransportClientEndImpl) + strict private + FHandle : THandle; + public + // ITransport + procedure Close; override; + constructor Create( aPipe : THandle; aOwnsHandle : Boolean; + const aTimeOut : DWORD = DEFAULT_THRIFT_TIMEOUT); reintroduce; + end; + + + TAnonymousPipeTransportImpl = class( TPipeTransportBase) + public + // Anonymous pipe constructor + constructor Create(const aPipeRead, aPipeWrite : THandle; + aOwnsHandles : Boolean; + const aTimeOut : DWORD = DEFAULT_THRIFT_TIMEOUT); overload; + end; + + + //--- Server Transports --- + + + IAnonymousPipeServerTransport = interface( IServerTransport) + ['{7AEE6793-47B9-4E49-981A-C39E9108E9AD}'] + // Server side anonymous pipe ends + function ReadHandle : THandle; + function WriteHandle : THandle; + // Client side anonymous pipe ends + function ClientAnonRead : THandle; + function ClientAnonWrite : THandle; + end; + + + INamedPipeServerTransport = interface( IServerTransport) + ['{9DF9EE48-D065-40AF-8F67-D33037D3D960}'] + function Handle : THandle; + end; + + + TPipeServerTransportBase = class( TServerTransportImpl) + strict protected + FStopServer : TEvent; + procedure InternalClose; virtual; abstract; + function QueryStopServer : Boolean; + public + constructor Create; + destructor Destroy; override; + procedure Listen; override; + procedure Close; override; + end; + + + TAnonymousPipeServerTransportImpl = class( TPipeServerTransportBase, IAnonymousPipeServerTransport) + strict private + FBufSize : DWORD; + + // Server side anonymous pipe handles + FReadHandle, + FWriteHandle : THandle; + + //Client side anonymous pipe handles + FClientAnonRead, + FClientAnonWrite : THandle; + + FTimeOut: DWORD; + protected + function Accept(const fnAccepting: TProc): ITransport; override; + + function CreateAnonPipe : Boolean; + + // IAnonymousPipeServerTransport + function ReadHandle : THandle; + function WriteHandle : THandle; + function ClientAnonRead : THandle; + function ClientAnonWrite : THandle; + + procedure InternalClose; override; + + public + constructor Create(aBufsize : Cardinal = 4096; aTimeOut : DWORD = DEFAULT_THRIFT_TIMEOUT); + end; + + + TNamedPipeServerTransportImpl = class( TPipeServerTransportBase, INamedPipeServerTransport) + strict private + FPipeName : string; + FMaxConns : DWORD; + FBufSize : DWORD; + FTimeout : DWORD; + FHandle : THandle; + FConnected : Boolean; + + + strict protected + function Accept(const fnAccepting: TProc): ITransport; override; + function CreateNamedPipe : THandle; + function CreateTransportInstance : ITransport; + + // INamedPipeServerTransport + function Handle : THandle; + procedure InternalClose; override; + + public + constructor Create( aPipename : string; aBufsize : Cardinal = 4096; + aMaxConns : Cardinal = PIPE_UNLIMITED_INSTANCES; + aTimeOut : Cardinal = INFINITE); + end; + + +implementation + + +procedure ClosePipeHandle( var hPipe : THandle); +begin + if hPipe <> INVALID_HANDLE_VALUE + then try + CloseHandle( hPipe); + finally + hPipe := INVALID_HANDLE_VALUE; + end; +end; + + +function DuplicatePipeHandle( const hSource : THandle) : THandle; +begin + if not DuplicateHandle( GetCurrentProcess, hSource, + GetCurrentProcess, @result, + 0, FALSE, DUPLICATE_SAME_ACCESS) + then raise TTransportExceptionNotOpen.Create('DuplicateHandle: '+SysErrorMessage(GetLastError)); +end; + + + +{ TPipeStreamBase } + + +constructor TPipeStreamBase.Create( aEnableOverlapped : Boolean; + const aTimeOut, aOpenTimeOut : DWORD); +begin + inherited Create; + ASSERT( aTimeout > 0); // aOpenTimeout may be 0 + FPipe := INVALID_HANDLE_VALUE; + FTimeout := aTimeOut; + FOpenTimeOut := aOpenTimeOut; + FOverlapped := aEnableOverlapped; +end; + + +destructor TPipeStreamBase.Destroy; +begin + try + Close; + finally + inherited Destroy; + end; +end; + + +procedure TPipeStreamBase.Close; +begin + ClosePipeHandle( FPipe); +end; + + +procedure TPipeStreamBase.Flush; +begin + FlushFileBuffers( FPipe); +end; + + +function TPipeStreamBase.IsOpen: Boolean; +begin + result := (FPipe <> INVALID_HANDLE_VALUE); +end; + + +procedure TPipeStreamBase.Write( const pBuf : Pointer; offset, count : Integer); +begin + if FOverlapped + then WriteOverlapped( pBuf, offset, count) + else WriteDirect( pBuf, offset, count); +end; + + +function TPipeStreamBase.Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; +begin + if FOverlapped + then result := ReadOverlapped( pBuf, buflen, offset, count) + else result := ReadDirect( pBuf, buflen, offset, count); +end; + + +procedure TPipeStreamBase.WriteDirect( const pBuf : Pointer; offset: Integer; count: Integer); +var cbWritten, nBytes : DWORD; + pData : PByte; +begin + if not IsOpen + then raise TTransportExceptionNotOpen.Create('Called write on non-open pipe'); + + // if necessary, send the data in chunks + // there's a system limit around 0x10000 bytes that we hit otherwise + // MSDN: "Pipe write operations across a network are limited to 65,535 bytes per write. For more information regarding pipes, see the Remarks section." + nBytes := Min( 15*4096, count); // 16 would exceed the limit + pData := pBuf; + Inc( pData, offset); + while nBytes > 0 do begin + if not WriteFile( FPipe, pData^, nBytes, cbWritten, nil) + then raise TTransportExceptionNotOpen.Create('Write to pipe failed'); + + Inc( pData, cbWritten); + Dec( count, cbWritten); + nBytes := Min( nBytes, count); + end; +end; + + +procedure TPipeStreamBase.WriteOverlapped( const pBuf : Pointer; offset: Integer; count: Integer); +var cbWritten, dwWait, dwError, nBytes : DWORD; + overlapped : IOverlappedHelper; + pData : PByte; +begin + if not IsOpen + then raise TTransportExceptionNotOpen.Create('Called write on non-open pipe'); + + // if necessary, send the data in chunks + // there's a system limit around 0x10000 bytes that we hit otherwise + // MSDN: "Pipe write operations across a network are limited to 65,535 bytes per write. For more information regarding pipes, see the Remarks section." + nBytes := Min( 15*4096, count); // 16 would exceed the limit + pData := pBuf; + Inc( pData, offset); + while nBytes > 0 do begin + overlapped := TOverlappedHelperImpl.Create; + if not WriteFile( FPipe, pData^, nBytes, cbWritten, overlapped.OverlappedPtr) + then begin + dwError := GetLastError; + case dwError of + ERROR_IO_PENDING : begin + dwWait := overlapped.WaitFor(FTimeout); + + if (dwWait = WAIT_TIMEOUT) then begin + CancelIo( FPipe); // prevents possible AV on invalid overlapped ptr + raise TTransportExceptionTimedOut.Create('Pipe write timed out'); + end; + + if (dwWait <> WAIT_OBJECT_0) + or not GetOverlappedResult( FPipe, overlapped.Overlapped, cbWritten, TRUE) + then raise TTransportExceptionUnknown.Create('Pipe write error'); + end; + + else + raise TTransportExceptionUnknown.Create(SysErrorMessage(dwError)); + end; + end; + + ASSERT( DWORD(nBytes) = cbWritten); + + Inc( pData, cbWritten); + Dec( count, cbWritten); + nBytes := Min( nBytes, count); + end; +end; + + +function TPipeStreamBase.ReadDirect( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; +var cbRead, dwErr, nRemaining : DWORD; + bytes, retries : LongInt; + bOk : Boolean; + pData : PByte; +const INTERVAL = 10; // ms +begin + if not IsOpen + then raise TTransportExceptionNotOpen.Create('Called read on non-open pipe'); + + // MSDN: Handle can be a handle to a named pipe instance, + // or it can be a handle to the read end of an anonymous pipe, + // The handle must have GENERIC_READ access to the pipe. + if FTimeOut <> INFINITE then begin + retries := Max( 1, Round( 1.0 * FTimeOut / INTERVAL)); + while TRUE do begin + if not PeekNamedPipe( FPipe, nil, 0, nil, @bytes, nil) then begin + dwErr := GetLastError; + if (dwErr = ERROR_INVALID_HANDLE) + or (dwErr = ERROR_BROKEN_PIPE) + or (dwErr = ERROR_PIPE_NOT_CONNECTED) + then begin + result := 0; // other side closed the pipe + Exit; + end; + end + else if bytes > 0 then begin + Break; // there are data + end; + + Dec( retries); + if retries > 0 + then Sleep( INTERVAL) + else raise TTransportExceptionTimedOut.Create('Pipe read timed out'); + end; + end; + + result := 0; + nRemaining := count; + pData := pBuf; + Inc( pData, offset); + while nRemaining > 0 do begin + // read the data (or block INFINITE-ly) + bOk := ReadFile( FPipe, pData^, nRemaining, cbRead, nil); + if (not bOk) and (GetLastError() <> ERROR_MORE_DATA) + then Break; // No more data, possibly because client disconnected. + + Dec( nRemaining, cbRead); + Inc( pData, cbRead); + Inc( result, cbRead); + end; +end; + + +function TPipeStreamBase.ReadOverlapped( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; +var cbRead, dwWait, dwError, nRemaining : DWORD; + bOk : Boolean; + overlapped : IOverlappedHelper; + pData : PByte; +begin + if not IsOpen + then raise TTransportExceptionNotOpen.Create('Called read on non-open pipe'); + + result := 0; + nRemaining := count; + pData := pBuf; + Inc( pData, offset); + while nRemaining > 0 do begin + overlapped := TOverlappedHelperImpl.Create; + + // read the data + bOk := ReadFile( FPipe, pData^, nRemaining, cbRead, overlapped.OverlappedPtr); + if not bOk then begin + dwError := GetLastError; + case dwError of + ERROR_IO_PENDING : begin + dwWait := overlapped.WaitFor(FTimeout); + + if (dwWait = WAIT_TIMEOUT) then begin + CancelIo( FPipe); // prevents possible AV on invalid overlapped ptr + raise TTransportExceptionTimedOut.Create('Pipe read timed out'); + end; + + if (dwWait <> WAIT_OBJECT_0) + or not GetOverlappedResult( FPipe, overlapped.Overlapped, cbRead, TRUE) + then raise TTransportExceptionUnknown.Create('Pipe read error'); + end; + + else + raise TTransportExceptionUnknown.Create(SysErrorMessage(dwError)); + end; + end; + + ASSERT( cbRead > 0); // see TTransportImpl.ReadAll() + ASSERT( cbRead <= DWORD(nRemaining)); + Dec( nRemaining, cbRead); + Inc( pData, cbRead); + Inc( result, cbRead); + end; +end; + + +function TPipeStreamBase.ToArray: TBytes; +var bytes : LongInt; +begin + SetLength( result, 0); + bytes := 0; + + if IsOpen + and PeekNamedPipe( FPipe, nil, 0, nil, @bytes, nil) + and (bytes > 0) + then begin + SetLength( result, bytes); + Read( result, 0, bytes); + end; +end; + + +{ TNamedPipeStreamImpl } + + +constructor TNamedPipeStreamImpl.Create( const aPipeName : string; + const aEnableOverlapped : Boolean; + const aShareMode: DWORD; + const aSecurityAttributes: PSecurityAttributes; + const aTimeOut, aOpenTimeOut : DWORD); +begin + inherited Create( aEnableOverlapped, aTimeout, aOpenTimeOut); + + FPipeName := aPipeName; + FShareMode := aShareMode; + FSecurityAttribs := aSecurityAttributes; + + if Copy(FPipeName,1,2) <> '\\' + then FPipeName := '\\.\pipe\' + FPipeName; // assume localhost +end; + + +procedure TNamedPipeStreamImpl.Open; +var hPipe : THandle; + retries, timeout, dwErr : DWORD; +const INTERVAL = 10; // ms +begin + if IsOpen then Exit; + + retries := Max( 1, Round( 1.0 * FOpenTimeOut / INTERVAL)); + timeout := FOpenTimeOut; + + // if the server hasn't gotten to the point where the pipe has been created, at least wait the timeout + // According to MSDN, if no instances of the specified named pipe exist, the WaitNamedPipe function + // returns IMMEDIATELY, regardless of the time-out value. + // Always use INTERVAL, since WaitNamedPipe(0) defaults to some other value + while not WaitNamedPipe( PChar(FPipeName), INTERVAL) do begin + dwErr := GetLastError; + if dwErr <> ERROR_FILE_NOT_FOUND + then raise TTransportExceptionNotOpen.Create('Unable to open pipe, '+SysErrorMessage(dwErr)); + + if timeout <> INFINITE then begin + if (retries > 0) + then Dec(retries) + else raise TTransportExceptionNotOpen.Create('Unable to open pipe, timed out'); + end; + + Sleep(INTERVAL) + end; + + // open that thingy + hPipe := CreateFile( PChar( FPipeName), + GENERIC_READ or GENERIC_WRITE, + FShareMode, // sharing + FSecurityAttribs, // security attributes + OPEN_EXISTING, // opens existing pipe + FILE_FLAG_OVERLAPPED or FILE_FLAG_WRITE_THROUGH, // async+fast, please + 0); // no template file + + if hPipe = INVALID_HANDLE_VALUE + then raise TTransportExceptionNotOpen.Create('Unable to open pipe, '+SysErrorMessage(GetLastError)); + + // everything fine + FPipe := hPipe; +end; + + +{ THandlePipeStreamImpl } + + +constructor THandlePipeStreamImpl.Create( const aPipeHandle : THandle; + const aOwnsHandle, aEnableOverlapped : Boolean; + const aTimeOut : DWORD); +begin + inherited Create( aEnableOverlapped, aTimeOut); + + if aOwnsHandle + then FSrcHandle := aPipeHandle + else FSrcHandle := DuplicatePipeHandle( aPipeHandle); + + Open; +end; + + +destructor THandlePipeStreamImpl.Destroy; +begin + try + ClosePipeHandle( FSrcHandle); + finally + inherited Destroy; + end; +end; + + +procedure THandlePipeStreamImpl.Open; +begin + if not IsOpen + then FPipe := DuplicatePipeHandle( FSrcHandle); +end; + + +{ TPipeTransportBase } + + +function TPipeTransportBase.GetIsOpen: Boolean; +begin + result := (FInputStream <> nil) and (FInputStream.IsOpen) + and (FOutputStream <> nil) and (FOutputStream.IsOpen); +end; + + +procedure TPipeTransportBase.Open; +begin + FInputStream.Open; + FOutputStream.Open; +end; + + +procedure TPipeTransportBase.Close; +begin + FInputStream.Close; + FOutputStream.Close; +end; + + +{ TNamedPipeTransportClientEndImpl } + + +constructor TNamedPipeTransportClientEndImpl.Create( const aPipeName : string; const aShareMode: DWORD; + const aSecurityAttributes: PSecurityAttributes; + const aTimeOut, aOpenTimeOut : DWORD); +// Named pipe constructor +begin + inherited Create( nil, nil); + FInputStream := TNamedPipeStreamImpl.Create( aPipeName, TRUE, aShareMode, aSecurityAttributes, aTimeOut, aOpenTimeOut); + FOutputStream := FInputStream; // true for named pipes +end; + + +constructor TNamedPipeTransportClientEndImpl.Create( aPipe : THandle; aOwnsHandle : Boolean; + const aTimeOut : DWORD); +// Named pipe constructor +begin + inherited Create( nil, nil); + FInputStream := THandlePipeStreamImpl.Create( aPipe, TRUE, aOwnsHandle, aTimeOut); + FOutputStream := FInputStream; // true for named pipes +end; + + +{ TNamedPipeTransportServerEndImpl } + + +constructor TNamedPipeTransportServerEndImpl.Create( aPipe : THandle; aOwnsHandle : Boolean; + const aTimeOut : DWORD); +// Named pipe constructor +begin + FHandle := DuplicatePipeHandle( aPipe); + inherited Create( aPipe, aOwnsHandle, aTimeOut); +end; + + +procedure TNamedPipeTransportServerEndImpl.Close; +begin + FlushFileBuffers( FHandle); + DisconnectNamedPipe( FHandle); // force client off the pipe + ClosePipeHandle( FHandle); + + inherited Close; +end; + + +{ TAnonymousPipeTransportImpl } + + +constructor TAnonymousPipeTransportImpl.Create( const aPipeRead, aPipeWrite : THandle; + aOwnsHandles : Boolean; + const aTimeOut : DWORD = DEFAULT_THRIFT_TIMEOUT); +// Anonymous pipe constructor +begin + inherited Create( nil, nil); + // overlapped is not supported with AnonPipes, see MSDN + FInputStream := THandlePipeStreamImpl.Create( aPipeRead, aOwnsHandles, FALSE, aTimeOut); + FOutputStream := THandlePipeStreamImpl.Create( aPipeWrite, aOwnsHandles, FALSE, aTimeOut); +end; + + +{ TPipeServerTransportBase } + + +constructor TPipeServerTransportBase.Create; +begin + inherited Create; + FStopServer := TEvent.Create(nil,TRUE,FALSE,''); // manual reset +end; + + +destructor TPipeServerTransportBase.Destroy; +begin + try + FreeAndNil( FStopServer); + finally + inherited Destroy; + end; +end; + + +function TPipeServerTransportBase.QueryStopServer : Boolean; +begin + result := (FStopServer = nil) + or (FStopServer.WaitFor(0) <> wrTimeout); +end; + + +procedure TPipeServerTransportBase.Listen; +begin + FStopServer.ResetEvent; +end; + + +procedure TPipeServerTransportBase.Close; +begin + FStopServer.SetEvent; + InternalClose; +end; + + +{ TAnonymousPipeServerTransportImpl } + + +constructor TAnonymousPipeServerTransportImpl.Create(aBufsize : Cardinal; aTimeOut : DWORD); +// Anonymous pipe CTOR +begin + inherited Create; + FBufsize := aBufSize; + FReadHandle := INVALID_HANDLE_VALUE; + FWriteHandle := INVALID_HANDLE_VALUE; + FClientAnonRead := INVALID_HANDLE_VALUE; + FClientAnonWrite := INVALID_HANDLE_VALUE; + FTimeOut := aTimeOut; + + // The anonymous pipe needs to be created first so that the server can + // pass the handles on to the client before the serve (acceptImpl) + // blocking call. + if not CreateAnonPipe + then raise TTransportExceptionNotOpen.Create(ClassName+'.Create() failed'); +end; + + +function TAnonymousPipeServerTransportImpl.Accept(const fnAccepting: TProc): ITransport; +var buf : Byte; + br : DWORD; +begin + if Assigned(fnAccepting) + then fnAccepting(); + + // This 0-byte read serves merely as a blocking call. + if not ReadFile( FReadHandle, buf, 0, br, nil) + and (GetLastError() <> ERROR_MORE_DATA) + then raise TTransportExceptionNotOpen.Create('TServerPipe unable to initiate pipe communication'); + + // create the transport impl + result := TAnonymousPipeTransportImpl.Create( FReadHandle, FWriteHandle, FALSE, FTimeOut); +end; + + +procedure TAnonymousPipeServerTransportImpl.InternalClose; +begin + ClosePipeHandle( FReadHandle); + ClosePipeHandle( FWriteHandle); + ClosePipeHandle( FClientAnonRead); + ClosePipeHandle( FClientAnonWrite); +end; + + +function TAnonymousPipeServerTransportImpl.ReadHandle : THandle; +begin + result := FReadHandle; +end; + + +function TAnonymousPipeServerTransportImpl.WriteHandle : THandle; +begin + result := FWriteHandle; +end; + + +function TAnonymousPipeServerTransportImpl.ClientAnonRead : THandle; +begin + result := FClientAnonRead; +end; + + +function TAnonymousPipeServerTransportImpl.ClientAnonWrite : THandle; +begin + result := FClientAnonWrite; +end; + + +function TAnonymousPipeServerTransportImpl.CreateAnonPipe : Boolean; +var sd : PSECURITY_DESCRIPTOR; + sa : SECURITY_ATTRIBUTES; //TSecurityAttributes; + hCAR, hPipeW, hCAW, hPipe : THandle; +begin + sd := PSECURITY_DESCRIPTOR( LocalAlloc( LPTR,SECURITY_DESCRIPTOR_MIN_LENGTH)); + try + Win32Check( InitializeSecurityDescriptor( sd, SECURITY_DESCRIPTOR_REVISION)); + Win32Check( SetSecurityDescriptorDacl( sd, TRUE, nil, FALSE)); + + sa.nLength := sizeof( sa); + sa.lpSecurityDescriptor := sd; + sa.bInheritHandle := TRUE; //allow passing handle to child + + Result := CreatePipe( hCAR, hPipeW, @sa, FBufSize); //create stdin pipe + if not Result then begin //create stdin pipe + raise TTransportExceptionNotOpen.Create('TServerPipe CreatePipe (anon) failed, '+SysErrorMessage(GetLastError)); + Exit; + end; + + Result := CreatePipe( hPipe, hCAW, @sa, FBufSize); //create stdout pipe + if not Result then begin //create stdout pipe + CloseHandle( hCAR); + CloseHandle( hPipeW); + raise TTransportExceptionNotOpen.Create('TServerPipe CreatePipe (anon) failed, '+SysErrorMessage(GetLastError)); + Exit; + end; + + FClientAnonRead := hCAR; + FClientAnonWrite := hCAW; + FReadHandle := hPipe; + FWriteHandle := hPipeW; + finally + if sd <> nil then LocalFree( Cardinal(sd)); + end; +end; + + +{ TNamedPipeServerTransportImpl } + + +constructor TNamedPipeServerTransportImpl.Create( aPipename : string; aBufsize, aMaxConns, aTimeOut : Cardinal); +// Named Pipe CTOR +begin + inherited Create; + ASSERT( aTimeout > 0); + FPipeName := aPipename; + FBufsize := aBufSize; + FMaxConns := Max( 1, Min( PIPE_UNLIMITED_INSTANCES, aMaxConns)); + FHandle := INVALID_HANDLE_VALUE; + FTimeout := aTimeOut; + FConnected := FALSE; + + if Copy(FPipeName,1,2) <> '\\' + then FPipeName := '\\.\pipe\' + FPipeName; // assume localhost +end; + + +function TNamedPipeServerTransportImpl.Accept(const fnAccepting: TProc): ITransport; +var dwError, dwWait, dwDummy : DWORD; + overlapped : IOverlappedHelper; + handles : array[0..1] of THandle; +begin + overlapped := TOverlappedHelperImpl.Create; + + ASSERT( not FConnected); + CreateNamedPipe; + while not FConnected do begin + + if QueryStopServer then begin + InternalClose; + Abort; + end; + + if Assigned(fnAccepting) + then fnAccepting(); + + // Wait for the client to connect; if it succeeds, the + // function returns a nonzero value. If the function returns + // zero, GetLastError should return ERROR_PIPE_CONNECTED. + if ConnectNamedPipe( Handle, overlapped.OverlappedPtr) then begin + FConnected := TRUE; + Break; + end; + + // ConnectNamedPipe() returns FALSE for OverlappedIO, even if connected. + // We have to check GetLastError() explicitly to find out + dwError := GetLastError; + case dwError of + ERROR_PIPE_CONNECTED : begin + FConnected := not QueryStopServer; // special case: pipe immediately connected + end; + + ERROR_IO_PENDING : begin + handles[0] := overlapped.WaitHandle; + handles[1] := FStopServer.Handle; + dwWait := WaitForMultipleObjects( 2, @handles, FALSE, FTimeout); + FConnected := (dwWait = WAIT_OBJECT_0) + and GetOverlappedResult( Handle, overlapped.Overlapped, dwDummy, TRUE) + and not QueryStopServer; + end; + + else + InternalClose; + raise TTransportExceptionNotOpen.Create('Client connection failed'); + end; + end; + + // create the transport impl + result := CreateTransportInstance; +end; + + +function TNamedPipeServerTransportImpl.CreateTransportInstance : ITransport; +// create the transport impl +var hPipe : THandle; +begin + hPipe := THandle( InterlockedExchangePointer( Pointer(FHandle), Pointer(INVALID_HANDLE_VALUE))); + try + FConnected := FALSE; + result := TNamedPipeTransportServerEndImpl.Create( hPipe, TRUE, FTimeout); + except + ClosePipeHandle(hPipe); + raise; + end; +end; + + +procedure TNamedPipeServerTransportImpl.InternalClose; +var hPipe : THandle; +begin + hPipe := THandle( InterlockedExchangePointer( Pointer(FHandle), Pointer(INVALID_HANDLE_VALUE))); + if hPipe = INVALID_HANDLE_VALUE then Exit; + + try + if FConnected + then FlushFileBuffers( hPipe) + else CancelIo( hPipe); + DisconnectNamedPipe( hPipe); + finally + ClosePipeHandle( hPipe); + FConnected := FALSE; + end; +end; + + +function TNamedPipeServerTransportImpl.Handle : THandle; +begin + {$IFDEF WIN64} + result := THandle( InterlockedExchangeAdd64( Int64(FHandle), 0)); + {$ELSE} + result := THandle( InterlockedExchangeAdd( Integer(FHandle), 0)); + {$ENDIF} +end; + + +function TNamedPipeServerTransportImpl.CreateNamedPipe : THandle; +var SIDAuthWorld : SID_IDENTIFIER_AUTHORITY ; + everyone_sid : PSID; + ea : EXPLICIT_ACCESS; + acl : PACL; + sd : PSECURITY_DESCRIPTOR; + sa : SECURITY_ATTRIBUTES; +const + SECURITY_WORLD_SID_AUTHORITY : TSIDIdentifierAuthority = (Value : (0,0,0,0,0,1)); + SECURITY_WORLD_RID = $00000000; +begin + sd := nil; + everyone_sid := nil; + try + ASSERT( (FHandle = INVALID_HANDLE_VALUE) and not FConnected); + + // Windows - set security to allow non-elevated apps + // to access pipes created by elevated apps. + SIDAuthWorld := SECURITY_WORLD_SID_AUTHORITY; + AllocateAndInitializeSid( SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, everyone_sid); + + ZeroMemory( @ea, SizeOf(ea)); + ea.grfAccessPermissions := GENERIC_ALL; //SPECIFIC_RIGHTS_ALL or STANDARD_RIGHTS_ALL; + ea.grfAccessMode := SET_ACCESS; + ea.grfInheritance := NO_INHERITANCE; + ea.Trustee.TrusteeForm := TRUSTEE_IS_SID; + ea.Trustee.TrusteeType := TRUSTEE_IS_WELL_KNOWN_GROUP; + ea.Trustee.ptstrName := PChar(everyone_sid); + + acl := nil; + SetEntriesInAcl( 1, @ea, nil, acl); + + sd := PSECURITY_DESCRIPTOR( LocalAlloc( LPTR,SECURITY_DESCRIPTOR_MIN_LENGTH)); + Win32Check( InitializeSecurityDescriptor( sd, SECURITY_DESCRIPTOR_REVISION)); + Win32Check( SetSecurityDescriptorDacl( sd, TRUE, acl, FALSE)); + + sa.nLength := SizeOf(sa); + sa.lpSecurityDescriptor := sd; + sa.bInheritHandle := FALSE; + + // Create an instance of the named pipe + {$IFDEF OLD_UNIT_NAMES} + result := Windows.CreateNamedPipe( + {$ELSE} + result := Winapi.Windows.CreateNamedPipe( + {$ENDIF} + PChar( FPipeName), // pipe name + PIPE_ACCESS_DUPLEX or // read/write access + FILE_FLAG_OVERLAPPED, // async mode + PIPE_TYPE_BYTE or // byte type pipe + PIPE_READMODE_BYTE, // byte read mode + FMaxConns, // max. instances + FBufSize, // output buffer size + FBufSize, // input buffer size + FTimeout, // time-out, see MSDN + @sa // default security attribute + ); + + if( result <> INVALID_HANDLE_VALUE) + then InterlockedExchangePointer( Pointer(FHandle), Pointer(result)) + else raise TTransportExceptionNotOpen.Create('CreateNamedPipe() failed ' + IntToStr(GetLastError)); + + finally + if sd <> nil then LocalFree( Cardinal( sd)); + if acl <> nil then LocalFree( Cardinal( acl)); + if everyone_sid <> nil then FreeSid(everyone_sid); + end; +end; + + + +end. + + + diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.WinHTTP.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.WinHTTP.pas new file mode 100644 index 000000000..262e38fb1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.WinHTTP.pas @@ -0,0 +1,408 @@ +(* + * 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. + *) +unit Thrift.Transport.WinHTTP; + +{$I Thrift.Defines.inc} +{$SCOPEDENUMS ON} + +interface + +uses + Classes, + SysUtils, + Math, + Generics.Collections, + Thrift.Collections, + Thrift.Transport, + Thrift.Exception, + Thrift.Utils, + Thrift.WinHTTP, + Thrift.Stream; + +type + TWinHTTPClientImpl = class( TTransportImpl, IHTTPClient) + private + FUri : string; + FInputStream : IThriftStream; + FOutputMemoryStream : TMemoryStream; + FDnsResolveTimeout : Integer; + FConnectionTimeout : Integer; + FSendTimeout : Integer; + FReadTimeout : Integer; + FCustomHeaders : IThriftDictionary<string,string>; + FSecureProtocols : TSecureProtocols; + + function CreateRequest: IWinHTTPRequest; + function SecureProtocolsAsWinHTTPFlags : Cardinal; + + private + type + TErrorInfo = ( SplitUrl, WinHTTPSession, WinHTTPConnection, WinHTTPRequest, RequestSetup, AutoProxy ); + + THTTPResponseStream = class( TThriftStreamImpl) + private + FRequest : IWinHTTPRequest; + protected + procedure Write( const pBuf : Pointer; offset: Integer; count: Integer); override; + function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; override; + procedure Open; override; + procedure Close; override; + procedure Flush; override; + function IsOpen: Boolean; override; + function ToArray: TBytes; override; + public + constructor Create( const aRequest : IWinHTTPRequest); + destructor Destroy; override; + end; + + protected + function GetIsOpen: Boolean; override; + procedure Open(); override; + procedure Close(); override; + function Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; override; + procedure Write( const pBuf : Pointer; off, len : Integer); override; + procedure Flush; override; + + procedure SetDnsResolveTimeout(const Value: Integer); + function GetDnsResolveTimeout: Integer; + procedure SetConnectionTimeout(const Value: Integer); + function GetConnectionTimeout: Integer; + procedure SetSendTimeout(const Value: Integer); + function GetSendTimeout: Integer; + procedure SetReadTimeout(const Value: Integer); + function GetReadTimeout: Integer; + function GetSecureProtocols : TSecureProtocols; + procedure SetSecureProtocols( const value : TSecureProtocols); + + function GetCustomHeaders: IThriftDictionary<string,string>; + procedure SendRequest; + + property DnsResolveTimeout: Integer read GetDnsResolveTimeout write SetDnsResolveTimeout; + property ConnectionTimeout: Integer read GetConnectionTimeout write SetConnectionTimeout; + property SendTimeout: Integer read GetSendTimeout write SetSendTimeout; + property ReadTimeout: Integer read GetReadTimeout write SetReadTimeout; + property CustomHeaders: IThriftDictionary<string,string> read GetCustomHeaders; + public + constructor Create( const AUri: string); + destructor Destroy; override; + end; + +implementation + + +{ TWinHTTPClientImpl } + +constructor TWinHTTPClientImpl.Create(const AUri: string); +begin + inherited Create; + FUri := AUri; + + // defaults according to MSDN + FDnsResolveTimeout := 0; // no timeout + FConnectionTimeout := 60 * 1000; + FSendTimeout := 30 * 1000; + FReadTimeout := 30 * 1000; + + FSecureProtocols := DEFAULT_THRIFT_SECUREPROTOCOLS; + + FCustomHeaders := TThriftDictionaryImpl<string,string>.Create; + FOutputMemoryStream := TMemoryStream.Create; +end; + +destructor TWinHTTPClientImpl.Destroy; +begin + Close; + FreeAndNil( FOutputMemoryStream); + inherited; +end; + +function TWinHTTPClientImpl.CreateRequest: IWinHTTPRequest; +var + pair : TPair<string,string>; + session : IWinHTTPSession; + connect : IWinHTTPConnection; + url : IWinHTTPUrl; + sPath : string; + info : TErrorInfo; +begin + info := TErrorInfo.SplitUrl; + try + url := TWinHTTPUrlImpl.Create( FUri); + + info := TErrorInfo.WinHTTPSession; + session := TWinHTTPSessionImpl.Create('Apache Thrift Delphi WinHTTP'); + session.EnableSecureProtocols( SecureProtocolsAsWinHTTPFlags); + + info := TErrorInfo.WinHTTPConnection; + connect := session.Connect( url.HostName, url.Port); + + info := TErrorInfo.WinHTTPRequest; + sPath := url.UrlPath + url.ExtraInfo; + result := connect.OpenRequest( (url.Scheme = 'https'), 'POST', sPath, THRIFT_MIMETYPE); + + // setting a timeout value to 0 (zero) means "no timeout" for that setting + info := TErrorInfo.RequestSetup; + result.SetTimeouts( DnsResolveTimeout, ConnectionTimeout, SendTimeout, ReadTimeout); + + // headers + result.AddRequestHeader( 'Content-Type: '+THRIFT_MIMETYPE, WINHTTP_ADDREQ_FLAG_ADD); + for pair in FCustomHeaders do begin + Result.AddRequestHeader( pair.Key +': '+ pair.Value, WINHTTP_ADDREQ_FLAG_ADD); + end; + + // enable automatic gzip,deflate decompression + result.EnableAutomaticContentDecompression(TRUE); + + // AutoProxy support + info := TErrorInfo.AutoProxy; + result.TryAutoProxy( FUri); + except + on e:TException do raise; + on e:Exception do raise TTransportExceptionUnknown.Create( e.Message+' (at '+EnumUtils<TErrorInfo>.ToString(Ord(info))+')'); + end; +end; + + +function TWinHTTPClientImpl.SecureProtocolsAsWinHTTPFlags : Cardinal; +const + PROTOCOL_MAPPING : array[TSecureProtocol] of Cardinal = ( + WINHTTP_FLAG_SECURE_PROTOCOL_SSL2, + WINHTTP_FLAG_SECURE_PROTOCOL_SSL3, + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1, + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1, + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 + ); +var + prot : TSecureProtocol; + protos : TSecureProtocols; +begin + result := 0; + protos := GetSecureProtocols; + for prot := Low(TSecureProtocol) to High(TSecureProtocol) do begin + if prot in protos + then result := result or PROTOCOL_MAPPING[prot]; + end; +end; + + +function TWinHTTPClientImpl.GetDnsResolveTimeout: Integer; +begin + Result := FDnsResolveTimeout; +end; + +procedure TWinHTTPClientImpl.SetDnsResolveTimeout(const Value: Integer); +begin + FDnsResolveTimeout := Value; +end; + +function TWinHTTPClientImpl.GetConnectionTimeout: Integer; +begin + Result := FConnectionTimeout; +end; + +procedure TWinHTTPClientImpl.SetConnectionTimeout(const Value: Integer); +begin + FConnectionTimeout := Value; +end; + +function TWinHTTPClientImpl.GetSendTimeout: Integer; +begin + Result := FSendTimeout; +end; + +procedure TWinHTTPClientImpl.SetSendTimeout(const Value: Integer); +begin + FSendTimeout := Value; +end; + +function TWinHTTPClientImpl.GetReadTimeout: Integer; +begin + Result := FReadTimeout; +end; + +procedure TWinHTTPClientImpl.SetReadTimeout(const Value: Integer); +begin + FReadTimeout := Value; +end; + +function TWinHTTPClientImpl.GetSecureProtocols : TSecureProtocols; +begin + Result := FSecureProtocols; +end; + +procedure TWinHTTPClientImpl.SetSecureProtocols( const value : TSecureProtocols); +begin + FSecureProtocols := Value; +end; + +function TWinHTTPClientImpl.GetCustomHeaders: IThriftDictionary<string,string>; +begin + Result := FCustomHeaders; +end; + +function TWinHTTPClientImpl.GetIsOpen: Boolean; +begin + Result := True; +end; + +procedure TWinHTTPClientImpl.Open; +begin + FreeAndNil( FOutputMemoryStream); + FOutputMemoryStream := TMemoryStream.Create; +end; + +procedure TWinHTTPClientImpl.Close; +begin + FInputStream := nil; + FreeAndNil( FOutputMemoryStream); +end; + +procedure TWinHTTPClientImpl.Flush; +begin + try + SendRequest; + finally + FreeAndNil( FOutputMemoryStream); + FOutputMemoryStream := TMemoryStream.Create; + ASSERT( FOutputMemoryStream <> nil); + end; +end; + +function TWinHTTPClientImpl.Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; +begin + if FInputStream = nil then begin + raise TTransportExceptionNotOpen.Create('No request has been sent'); + end; + + try + Result := FInputStream.Read( pBuf, buflen, off, len) + except + on E: Exception + do raise TTransportExceptionUnknown.Create(E.Message); + end; +end; + +procedure TWinHTTPClientImpl.SendRequest; +var + http : IWinHTTPRequest; + pData : PByte; + len : Integer; + error : Cardinal; + sMsg : string; +begin + http := CreateRequest; + + pData := FOutputMemoryStream.Memory; + len := FOutputMemoryStream.Size; + + // send all data immediately, since we have it in memory + if not http.SendRequest( pData, len, 0) then begin + error := Cardinal( GetLastError); + sMsg := 'WinHTTP send error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error); + raise TTransportExceptionUnknown.Create(sMsg); + end; + + // end request and start receiving + if not http.FlushAndReceiveResponse then begin + error := Cardinal( GetLastError); + sMsg := 'WinHTTP recv error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error); + if error = ERROR_WINHTTP_TIMEOUT + then raise TTransportExceptionTimedOut.Create( sMsg) + else raise TTransportExceptionInterrupted.Create( sMsg); + end; + + FInputStream := THTTPResponseStream.Create(http); +end; + +procedure TWinHTTPClientImpl.Write( const pBuf : Pointer; off, len : Integer); +var pTmp : PByte; +begin + pTmp := pBuf; + Inc(pTmp,off); + FOutputMemoryStream.Write( pTmp^, len); +end; + + +{ TWinHTTPClientImpl.THTTPResponseStream } + +constructor TWinHTTPClientImpl.THTTPResponseStream.Create( const aRequest : IWinHTTPRequest); +begin + inherited Create; + FRequest := aRequest; +end; + +destructor TWinHTTPClientImpl.THTTPResponseStream.Destroy; +begin + try + Close; + finally + inherited Destroy; + end; +end; + +procedure TWinHTTPClientImpl.THTTPResponseStream.Close; +begin + FRequest := nil; +end; + +procedure TWinHTTPClientImpl.THTTPResponseStream.Flush; +begin + raise ENotImplemented(ClassName+'.Flush'); +end; + +function TWinHTTPClientImpl.THTTPResponseStream.IsOpen: Boolean; +begin + Result := FRequest <> nil; +end; + +procedure TWinHTTPClientImpl.THTTPResponseStream.Open; +begin + // nothing to do +end; + +procedure TWinHTTPClientImpl.THTTPResponseStream.Write(const pBuf : Pointer; offset, count: Integer); +begin + inherited; // check pointers + raise ENotImplemented(ClassName+'.Write'); +end; + +function TWinHTTPClientImpl.THTTPResponseStream.Read(const pBuf : Pointer; const buflen : Integer; offset, count: Integer): Integer; +var pTmp : PByte; +begin + inherited; // check pointers + + if count >= buflen-offset + then count := buflen-offset; + + if count > 0 then begin + pTmp := pBuf; + Inc( pTmp, offset); + Result := FRequest.ReadData( pTmp, count); + ASSERT( Result >= 0); + end + else Result := 0; +end; + +function TWinHTTPClientImpl.THTTPResponseStream.ToArray: TBytes; +begin + raise ENotImplemented(ClassName+'.ToArray'); +end; + + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.pas new file mode 100644 index 000000000..c2071df89 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Transport.pas @@ -0,0 +1,1523 @@ +(* + * 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. + *) +unit Thrift.Transport; + +{$I Thrift.Defines.inc} +{$SCOPEDENUMS ON} + +interface + +uses + Classes, + SysUtils, + Math, + Generics.Collections, + {$IFDEF OLD_UNIT_NAMES} + WinSock, Sockets, + {$ELSE} + Winapi.WinSock, + {$IFDEF OLD_SOCKETS} + Web.Win.Sockets, + {$ELSE} + Thrift.Socket, + {$ENDIF} + {$ENDIF} + Thrift.Collections, + Thrift.Exception, + Thrift.Utils, + Thrift.WinHTTP, + Thrift.Stream; + +type + ITransport = interface + ['{DB84961E-8BB3-4532-99E1-A8C7AC2300F7}'] + function GetIsOpen: Boolean; + property IsOpen: Boolean read GetIsOpen; + function Peek: Boolean; + procedure Open; + procedure Close; + function Read(var buf: TBytes; off: Integer; len: Integer): Integer; overload; + function Read(const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; overload; + function ReadAll(var buf: TBytes; off: Integer; len: Integer): Integer; overload; + function ReadAll(const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; overload; + procedure Write( const buf: TBytes); overload; + procedure Write( const buf: TBytes; off: Integer; len: Integer); overload; + procedure Write( const pBuf : Pointer; off, len : Integer); overload; + procedure Write( const pBuf : Pointer; len : Integer); overload; + procedure Flush; + end; + + TTransportImpl = class( TInterfacedObject, ITransport) + protected + function GetIsOpen: Boolean; virtual; abstract; + property IsOpen: Boolean read GetIsOpen; + function Peek: Boolean; virtual; + procedure Open(); virtual; abstract; + procedure Close(); virtual; abstract; + function Read(var buf: TBytes; off: Integer; len: Integer): Integer; overload; inline; + function Read(const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; overload; virtual; abstract; + function ReadAll(var buf: TBytes; off: Integer; len: Integer): Integer; overload; inline; + function ReadAll(const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; overload; virtual; + procedure Write( const buf: TBytes); overload; inline; + procedure Write( const buf: TBytes; off: Integer; len: Integer); overload; inline; + procedure Write( const pBuf : Pointer; len : Integer); overload; inline; + procedure Write( const pBuf : Pointer; off, len : Integer); overload; virtual; abstract; + procedure Flush; virtual; + end; + + TTransportException = class( TException) + public + type + TExceptionType = ( + Unknown, + NotOpen, + AlreadyOpen, + TimedOut, + EndOfFile, + BadArgs, + Interrupted + ); + private + function GetType: TExceptionType; + protected + constructor HiddenCreate(const Msg: string); + public + class function Create( AType: TExceptionType): TTransportException; overload; deprecated 'Use specialized TTransportException types (or regenerate from IDL)'; + class function Create( const msg: string): TTransportException; reintroduce; overload; deprecated 'Use specialized TTransportException types (or regenerate from IDL)'; + class function Create( AType: TExceptionType; const msg: string): TTransportException; overload; deprecated 'Use specialized TTransportException types (or regenerate from IDL)'; + property Type_: TExceptionType read GetType; + end; + + // Needed to remove deprecation warning + TTransportExceptionSpecialized = class abstract (TTransportException) + public + constructor Create(const Msg: string); + end; + + TTransportExceptionUnknown = class (TTransportExceptionSpecialized); + TTransportExceptionNotOpen = class (TTransportExceptionSpecialized); + TTransportExceptionAlreadyOpen = class (TTransportExceptionSpecialized); + TTransportExceptionTimedOut = class (TTransportExceptionSpecialized); + TTransportExceptionEndOfFile = class (TTransportExceptionSpecialized); + TTransportExceptionBadArgs = class (TTransportExceptionSpecialized); + TTransportExceptionInterrupted = class (TTransportExceptionSpecialized); + + TSecureProtocol = ( + SSL_2, SSL_3, TLS_1, // outdated, for compatibilty only + TLS_1_1, TLS_1_2 // secure (as of today) + ); + + TSecureProtocols = set of TSecureProtocol; + + IHTTPClient = interface( ITransport ) + ['{7BF615DD-8680-4004-A5B2-88947BA3BA3D}'] + procedure SetDnsResolveTimeout(const Value: Integer); + function GetDnsResolveTimeout: Integer; + procedure SetConnectionTimeout(const Value: Integer); + function GetConnectionTimeout: Integer; + procedure SetSendTimeout(const Value: Integer); + function GetSendTimeout: Integer; + procedure SetReadTimeout(const Value: Integer); + function GetReadTimeout: Integer; + function GetCustomHeaders: IThriftDictionary<string,string>; + procedure SendRequest; + function GetSecureProtocols : TSecureProtocols; + procedure SetSecureProtocols( const value : TSecureProtocols); + + property DnsResolveTimeout: Integer read GetDnsResolveTimeout write SetDnsResolveTimeout; + property ConnectionTimeout: Integer read GetConnectionTimeout write SetConnectionTimeout; + property SendTimeout: Integer read GetSendTimeout write SetSendTimeout; + property ReadTimeout: Integer read GetReadTimeout write SetReadTimeout; + property CustomHeaders: IThriftDictionary<string,string> read GetCustomHeaders; + property SecureProtocols : TSecureProtocols read GetSecureProtocols write SetSecureProtocols; + end; + + IServerTransport = interface + ['{C43B87ED-69EA-47C4-B77C-15E288252900}'] + procedure Listen; + procedure Close; + function Accept( const fnAccepting: TProc): ITransport; + end; + + TServerTransportImpl = class( TInterfacedObject, IServerTransport) + protected + procedure Listen; virtual; abstract; + procedure Close; virtual; abstract; + function Accept( const fnAccepting: TProc): ITransport; virtual; abstract; + end; + + ITransportFactory = interface + ['{DD809446-000F-49E1-9BFF-E0D0DC76A9D7}'] + function GetTransport( const ATrans: ITransport): ITransport; + end; + + TTransportFactoryImpl = class( TInterfacedObject, ITransportFactory) + function GetTransport( const ATrans: ITransport): ITransport; virtual; + end; + + TTcpSocketStreamImpl = class( TThriftStreamImpl ) +{$IFDEF OLD_SOCKETS} + private type + TWaitForData = ( wfd_HaveData, wfd_Timeout, wfd_Error); + private + FTcpClient : TCustomIpClient; + FTimeout : Integer; + function Select( ReadReady, WriteReady, ExceptFlag: PBoolean; + TimeOut: Integer; var wsaError : Integer): Integer; + function WaitForData( TimeOut : Integer; pBuf : Pointer; DesiredBytes: Integer; + var wsaError, bytesReady : Integer): TWaitForData; +{$ELSE} + FTcpClient: TSocket; + protected const + SLEEP_TIME = 200; +{$ENDIF} + protected + procedure Write( const pBuf : Pointer; offset, count: Integer); override; + function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; override; + procedure Open; override; + procedure Close; override; + procedure Flush; override; + + function IsOpen: Boolean; override; + function ToArray: TBytes; override; + public +{$IFDEF OLD_SOCKETS} + constructor Create( const ATcpClient: TCustomIpClient; const aTimeout : Integer = 0); +{$ELSE} + constructor Create( const ATcpClient: TSocket; const aTimeout : Longword = 0); +{$ENDIF} + end; + + IStreamTransport = interface( ITransport ) + ['{A8479B47-2A3E-4421-A9A0-D5A9EDCC634A}'] + function GetInputStream: IThriftStream; + function GetOutputStream: IThriftStream; + property InputStream : IThriftStream read GetInputStream; + property OutputStream : IThriftStream read GetOutputStream; + end; + + TStreamTransportImpl = class( TTransportImpl, IStreamTransport) + protected + FInputStream : IThriftStream; + FOutputStream : IThriftStream; + protected + function GetIsOpen: Boolean; override; + + function GetInputStream: IThriftStream; + function GetOutputStream: IThriftStream; + public + property InputStream : IThriftStream read GetInputStream; + property OutputStream : IThriftStream read GetOutputStream; + + procedure Open; override; + procedure Close; override; + procedure Flush; override; + function Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; override; + procedure Write( const pBuf : Pointer; off, len : Integer); override; + constructor Create( const AInputStream : IThriftStream; const AOutputStream : IThriftStream); + destructor Destroy; override; + end; + + TBufferedStreamImpl = class( TThriftStreamImpl) + private + FStream : IThriftStream; + FBufSize : Integer; + FReadBuffer : TMemoryStream; + FWriteBuffer : TMemoryStream; + protected + procedure Write( const pBuf : Pointer; offset: Integer; count: Integer); override; + function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; override; + procedure Open; override; + procedure Close; override; + procedure Flush; override; + function IsOpen: Boolean; override; + function ToArray: TBytes; override; + public + constructor Create( const AStream: IThriftStream; ABufSize: Integer); + destructor Destroy; override; + end; + + TServerSocketImpl = class( TServerTransportImpl) + private +{$IFDEF OLD_SOCKETS} + FServer : TTcpServer; + FPort : Integer; + FClientTimeout : Integer; +{$ELSE} + FServer: TServerSocket; +{$ENDIF} + FUseBufferedSocket : Boolean; + FOwnsServer : Boolean; + protected + function Accept( const fnAccepting: TProc) : ITransport; override; + public +{$IFDEF OLD_SOCKETS} + constructor Create( const AServer: TTcpServer; AClientTimeout: Integer = 0); overload; + constructor Create( APort: Integer; AClientTimeout: Integer = 0; AUseBufferedSockets: Boolean = FALSE); overload; +{$ELSE} + constructor Create( const AServer: TServerSocket; AClientTimeout: Longword = 0); overload; + constructor Create( APort: Integer; AClientTimeout: Longword = 0; AUseBufferedSockets: Boolean = FALSE); overload; +{$ENDIF} + destructor Destroy; override; + procedure Listen; override; + procedure Close; override; + end; + + TBufferedTransportImpl = class( TTransportImpl ) + private + FInputBuffer : IThriftStream; + FOutputBuffer : IThriftStream; + FTransport : IStreamTransport; + FBufSize : Integer; + + procedure InitBuffers; + function GetUnderlyingTransport: ITransport; + protected + function GetIsOpen: Boolean; override; + procedure Flush; override; + public + procedure Open(); override; + procedure Close(); override; + function Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; override; + procedure Write( const pBuf : Pointer; off, len : Integer); override; + constructor Create( const ATransport : IStreamTransport ); overload; + constructor Create( const ATransport : IStreamTransport; ABufSize: Integer); overload; + property UnderlyingTransport: ITransport read GetUnderlyingTransport; + property IsOpen: Boolean read GetIsOpen; + end; + + TSocketImpl = class(TStreamTransportImpl) + private +{$IFDEF OLD_SOCKETS} + FClient : TCustomIpClient; +{$ELSE} + FClient: TSocket; +{$ENDIF} + FOwnsClient : Boolean; + FHost : string; + FPort : Integer; +{$IFDEF OLD_SOCKETS} + FTimeout : Integer; +{$ELSE} + FTimeout : Longword; +{$ENDIF} + + procedure InitSocket; + protected + function GetIsOpen: Boolean; override; + public + procedure Open; override; +{$IFDEF OLD_SOCKETS} + constructor Create( const AClient : TCustomIpClient; aOwnsClient : Boolean; ATimeout: Integer = 0); overload; + constructor Create( const AHost: string; APort: Integer; ATimeout: Integer = 0); overload; +{$ELSE} + constructor Create(const AClient: TSocket; aOwnsClient: Boolean); overload; + constructor Create( const AHost: string; APort: Integer; ATimeout: Longword = 0); overload; +{$ENDIF} + destructor Destroy; override; + procedure Close; override; +{$IFDEF OLD_SOCKETS} + property TcpClient: TCustomIpClient read FClient; +{$ELSE} + property TcpClient: TSocket read FClient; +{$ENDIF} + property Host : string read FHost; + property Port: Integer read FPort; + end; + + TFramedTransportImpl = class( TTransportImpl) + private const + FHeaderSize : Integer = 4; + private class var + FHeader_Dummy : array of Byte; + protected + FTransport : ITransport; + FWriteBuffer : TMemoryStream; + FReadBuffer : TMemoryStream; + + procedure InitWriteBuffer; + procedure ReadFrame; + public + type + TFactory = class( TTransportFactoryImpl ) + public + function GetTransport( const ATrans: ITransport): ITransport; override; + end; + + {$IFDEF HAVE_CLASS_CTOR} + class constructor Create; + {$ENDIF} + + constructor Create; overload; + constructor Create( const ATrans: ITransport); overload; + destructor Destroy; override; + + procedure Open(); override; + function GetIsOpen: Boolean; override; + + procedure Close(); override; + function Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; override; + procedure Write( const pBuf : Pointer; off, len : Integer); override; + procedure Flush; override; + end; + +{$IFNDEF HAVE_CLASS_CTOR} +procedure TFramedTransportImpl_Initialize; +{$ENDIF} + +const + DEFAULT_THRIFT_TIMEOUT = 5 * 1000; // ms + DEFAULT_THRIFT_SECUREPROTOCOLS = [ TSecureProtocol.TLS_1_1, TSecureProtocol.TLS_1_2]; + + + +implementation + +{ TTransportImpl } + +procedure TTransportImpl.Flush; +begin + // nothing to do +end; + +function TTransportImpl.Peek: Boolean; +begin + Result := IsOpen; +end; + +function TTransportImpl.Read(var buf: TBytes; off: Integer; len: Integer): Integer; +begin + if Length(buf) > 0 + then result := Read( @buf[0], Length(buf), off, len) + else result := 0; +end; + +function TTransportImpl.ReadAll(var buf: TBytes; off: Integer; len: Integer): Integer; +begin + if Length(buf) > 0 + then result := ReadAll( @buf[0], Length(buf), off, len) + else result := 0; +end; + +procedure TTransportImpl.Write( const buf: TBytes); +begin + if Length(buf) > 0 + then Write( @buf[0], 0, Length(buf)); +end; + +procedure TTransportImpl.Write( const buf: TBytes; off: Integer; len: Integer); +begin + if Length(buf) > 0 + then Write( @buf[0], off, len); +end; + +function TTransportImpl.ReadAll(const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; +var ret : Integer; +begin + result := 0; + while result < len do begin + ret := Read( pBuf, buflen, off + result, len - result); + if ret > 0 + then Inc( result, ret) + else raise TTransportExceptionNotOpen.Create( 'Cannot read, Remote side has closed' ); + end; +end; + +procedure TTransportImpl.Write( const pBuf : Pointer; len : Integer); +begin + Self.Write( pBuf, 0, len); +end; + +{ TTransportException } + +function TTransportException.GetType: TExceptionType; +begin + if Self is TTransportExceptionNotOpen then Result := TExceptionType.NotOpen + else if Self is TTransportExceptionAlreadyOpen then Result := TExceptionType.AlreadyOpen + else if Self is TTransportExceptionTimedOut then Result := TExceptionType.TimedOut + else if Self is TTransportExceptionEndOfFile then Result := TExceptionType.EndOfFile + else if Self is TTransportExceptionBadArgs then Result := TExceptionType.BadArgs + else if Self is TTransportExceptionInterrupted then Result := TExceptionType.Interrupted + else Result := TExceptionType.Unknown; +end; + +constructor TTransportException.HiddenCreate(const Msg: string); +begin + inherited Create(Msg); +end; + +class function TTransportException.Create(AType: TExceptionType): TTransportException; +begin + //no inherited; +{$WARN SYMBOL_DEPRECATED OFF} + Result := Create(AType, '') +{$WARN SYMBOL_DEPRECATED DEFAULT} +end; + +class function TTransportException.Create(AType: TExceptionType; + const msg: string): TTransportException; +begin + case AType of + TExceptionType.NotOpen: Result := TTransportExceptionNotOpen.Create(msg); + TExceptionType.AlreadyOpen: Result := TTransportExceptionAlreadyOpen.Create(msg); + TExceptionType.TimedOut: Result := TTransportExceptionTimedOut.Create(msg); + TExceptionType.EndOfFile: Result := TTransportExceptionEndOfFile.Create(msg); + TExceptionType.BadArgs: Result := TTransportExceptionBadArgs.Create(msg); + TExceptionType.Interrupted: Result := TTransportExceptionInterrupted.Create(msg); + else + Result := TTransportExceptionUnknown.Create(msg); + end; +end; + +class function TTransportException.Create(const msg: string): TTransportException; +begin + Result := TTransportExceptionUnknown.Create(Msg); +end; + +{ TTransportExceptionSpecialized } + +constructor TTransportExceptionSpecialized.Create(const Msg: string); +begin + inherited HiddenCreate(Msg); +end; + +{ TTransportFactoryImpl } + +function TTransportFactoryImpl.GetTransport( const ATrans: ITransport): ITransport; +begin + Result := ATrans; +end; + +{ TServerSocket } + +{$IFDEF OLD_SOCKETS} +constructor TServerSocketImpl.Create( const AServer: TTcpServer; AClientTimeout: Integer); +begin + inherited Create; + FServer := AServer; + FClientTimeout := AClientTimeout; +end; +{$ELSE} +constructor TServerSocketImpl.Create( const AServer: TServerSocket; AClientTimeout: Longword); +begin + inherited Create; + FServer := AServer; + FServer.RecvTimeout := AClientTimeout; + FServer.SendTimeout := AClientTimeout; +end; +{$ENDIF} + +{$IFDEF OLD_SOCKETS} +constructor TServerSocketImpl.Create(APort, AClientTimeout: Integer; AUseBufferedSockets: Boolean); +{$ELSE} +constructor TServerSocketImpl.Create(APort: Integer; AClientTimeout: Longword; AUseBufferedSockets: Boolean); +{$ENDIF} +begin + inherited Create; +{$IFDEF OLD_SOCKETS} + FPort := APort; + FClientTimeout := AClientTimeout; + FServer := TTcpServer.Create( nil ); + FServer.BlockMode := bmBlocking; + {$IF CompilerVersion >= 21.0} + FServer.LocalPort := AnsiString( IntToStr( FPort)); + {$ELSE} + FServer.LocalPort := IntToStr( FPort); + {$IFEND} +{$ELSE} + FServer := TServerSocket.Create(APort, AClientTimeout, AClientTimeout); +{$ENDIF} + FUseBufferedSocket := AUseBufferedSockets; + FOwnsServer := True; +end; + +destructor TServerSocketImpl.Destroy; +begin + if FOwnsServer then begin + FServer.Free; + FServer := nil; + end; + inherited; +end; + +function TServerSocketImpl.Accept( const fnAccepting: TProc): ITransport; +var +{$IFDEF OLD_SOCKETS} + client : TCustomIpClient; +{$ELSE} + client: TSocket; +{$ENDIF} + trans : IStreamTransport; +begin + if FServer = nil then begin + raise TTransportExceptionNotOpen.Create('No underlying server socket.'); + end; + +{$IFDEF OLD_SOCKETS} + client := nil; + try + client := TCustomIpClient.Create(nil); + + if Assigned(fnAccepting) + then fnAccepting(); + + if not FServer.Accept( client) then begin + client.Free; + Result := nil; + Exit; + end; + + if client = nil then begin + Result := nil; + Exit; + end; + + trans := TSocketImpl.Create( client, TRUE, FClientTimeout); + client := nil; // trans owns it now + + if FUseBufferedSocket + then result := TBufferedTransportImpl.Create( trans) + else result := trans; + + except + on E: Exception do begin + client.Free; + raise TTransportExceptionUnknown.Create(E.ToString); + end; + end; +{$ELSE} + if Assigned(fnAccepting) then + fnAccepting(); + + client := FServer.Accept; + try + trans := TSocketImpl.Create(client, True); + client := nil; + + if FUseBufferedSocket then + Result := TBufferedTransportImpl.Create(trans) + else + Result := trans; + except + client.Free; + raise; + end; +{$ENDIF} +end; + +procedure TServerSocketImpl.Listen; +begin + if FServer <> nil then + begin +{$IFDEF OLD_SOCKETS} + try + FServer.Active := True; + except + on E: Exception + do raise TTransportExceptionUnknown.Create('Could not accept on listening socket: ' + E.Message); + end; +{$ELSE} + FServer.Listen; +{$ENDIF} + end; +end; + +procedure TServerSocketImpl.Close; +begin + if FServer <> nil then +{$IFDEF OLD_SOCKETS} + try + FServer.Active := False; + except + on E: Exception + do raise TTransportExceptionUnknown.Create('Error on closing socket : ' + E.Message); + end; +{$ELSE} + FServer.Close; +{$ENDIF} +end; + +{ TSocket } + +{$IFDEF OLD_SOCKETS} +constructor TSocketImpl.Create( const AClient : TCustomIpClient; aOwnsClient : Boolean; ATimeout: Integer = 0); +var stream : IThriftStream; +begin + FClient := AClient; + FTimeout := ATimeout; + FOwnsClient := aOwnsClient; + stream := TTcpSocketStreamImpl.Create( FClient, FTimeout); + inherited Create( stream, stream); +end; +{$ELSE} +constructor TSocketImpl.Create(const AClient: TSocket; aOwnsClient: Boolean); +var stream : IThriftStream; +begin + FClient := AClient; + FTimeout := AClient.RecvTimeout; + FOwnsClient := aOwnsClient; + stream := TTcpSocketStreamImpl.Create(FClient, FTimeout); + inherited Create(stream, stream); +end; +{$ENDIF} + +{$IFDEF OLD_SOCKETS} +constructor TSocketImpl.Create(const AHost: string; APort, ATimeout: Integer); +{$ELSE} +constructor TSocketImpl.Create(const AHost: string; APort: Integer; ATimeout: Longword); +{$ENDIF} +begin + inherited Create(nil,nil); + FHost := AHost; + FPort := APort; + FTimeout := ATimeout; + InitSocket; +end; + +destructor TSocketImpl.Destroy; +begin + if FOwnsClient + then FreeAndNil( FClient); + inherited; +end; + +procedure TSocketImpl.Close; +begin + inherited Close; + + FInputStream := nil; + FOutputStream := nil; + + if FOwnsClient + then FreeAndNil( FClient) + else FClient := nil; +end; + +function TSocketImpl.GetIsOpen: Boolean; +begin +{$IFDEF OLD_SOCKETS} + Result := (FClient <> nil) and FClient.Connected; +{$ELSE} + Result := (FClient <> nil) and FClient.IsOpen +{$ENDIF} +end; + +procedure TSocketImpl.InitSocket; +var + stream : IThriftStream; +begin + if FOwnsClient + then FreeAndNil( FClient) + else FClient := nil; + +{$IFDEF OLD_SOCKETS} + FClient := TTcpClient.Create( nil); +{$ELSE} + FClient := TSocket.Create(FHost, FPort); +{$ENDIF} + FOwnsClient := True; + + stream := TTcpSocketStreamImpl.Create( FClient, FTimeout); + FInputStream := stream; + FOutputStream := stream; +end; + +procedure TSocketImpl.Open; +begin + if IsOpen then begin + raise TTransportExceptionAlreadyOpen.Create('Socket already connected'); + end; + + if FHost = '' then begin + raise TTransportExceptionNotOpen.Create('Cannot open null host'); + end; + + if Port <= 0 then begin + raise TTransportExceptionNotOpen.Create('Cannot open without port'); + end; + + if FClient = nil + then InitSocket; + +{$IFDEF OLD_SOCKETS} + FClient.RemoteHost := TSocketHost( Host); + FClient.RemotePort := TSocketPort( IntToStr( Port)); + FClient.Connect; +{$ELSE} + FClient.Open; +{$ENDIF} + + FInputStream := TTcpSocketStreamImpl.Create( FClient, FTimeout); + FOutputStream := FInputStream; +end; + +{ TBufferedStream } + +procedure TBufferedStreamImpl.Close; +begin + Flush; + FStream := nil; + + FReadBuffer.Free; + FReadBuffer := nil; + + FWriteBuffer.Free; + FWriteBuffer := nil; +end; + +constructor TBufferedStreamImpl.Create( const AStream: IThriftStream; ABufSize: Integer); +begin + inherited Create; + FStream := AStream; + FBufSize := ABufSize; + FReadBuffer := TMemoryStream.Create; + FWriteBuffer := TMemoryStream.Create; +end; + +destructor TBufferedStreamImpl.Destroy; +begin + Close; + inherited; +end; + +procedure TBufferedStreamImpl.Flush; +var + buf : TBytes; + len : Integer; +begin + if IsOpen then begin + len := FWriteBuffer.Size; + if len > 0 then begin + SetLength( buf, len ); + FWriteBuffer.Position := 0; + FWriteBuffer.Read( Pointer(@buf[0])^, len ); + FStream.Write( buf, 0, len ); + end; + FWriteBuffer.Clear; + end; +end; + +function TBufferedStreamImpl.IsOpen: Boolean; +begin + Result := (FWriteBuffer <> nil) + and (FReadBuffer <> nil) + and (FStream <> nil) + and FStream.IsOpen; +end; + +procedure TBufferedStreamImpl.Open; +begin + FStream.Open; +end; + +function TBufferedStreamImpl.Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; +var + nRead : Integer; + tempbuf : TBytes; + pTmp : PByte; +begin + inherited; + Result := 0; + + if IsOpen then begin + while count > 0 do begin + + if FReadBuffer.Position >= FReadBuffer.Size then begin + FReadBuffer.Clear; + SetLength( tempbuf, FBufSize); + nRead := FStream.Read( tempbuf, 0, FBufSize ); + if nRead = 0 then Break; // avoid infinite loop + + FReadBuffer.WriteBuffer( Pointer(@tempbuf[0])^, nRead ); + FReadBuffer.Position := 0; + end; + + if FReadBuffer.Position < FReadBuffer.Size then begin + nRead := Min( FReadBuffer.Size - FReadBuffer.Position, count); + pTmp := pBuf; + Inc( pTmp, offset); + Inc( Result, FReadBuffer.Read( pTmp^, nRead)); + Dec( count, nRead); + Inc( offset, nRead); + end; + end; + end; +end; + +function TBufferedStreamImpl.ToArray: TBytes; +var len : Integer; +begin + len := 0; + + if IsOpen then begin + len := FReadBuffer.Size; + end; + + SetLength( Result, len); + + if len > 0 then begin + FReadBuffer.Position := 0; + FReadBuffer.Read( Pointer(@Result[0])^, len ); + end; +end; + +procedure TBufferedStreamImpl.Write( const pBuf : Pointer; offset: Integer; count: Integer); +var pTmp : PByte; +begin + inherited; + if count > 0 then begin + if IsOpen then begin + pTmp := pBuf; + Inc( pTmp, offset); + FWriteBuffer.Write( pTmp^, count ); + if FWriteBuffer.Size > FBufSize then begin + Flush; + end; + end; + end; +end; + +{ TStreamTransportImpl } + +constructor TStreamTransportImpl.Create( const AInputStream : IThriftStream; const AOutputStream : IThriftStream); +begin + inherited Create; + FInputStream := AInputStream; + FOutputStream := AOutputStream; +end; + +destructor TStreamTransportImpl.Destroy; +begin + FInputStream := nil; + FOutputStream := nil; + inherited; +end; + +procedure TStreamTransportImpl.Close; +begin + FInputStream := nil; + FOutputStream := nil; +end; + +procedure TStreamTransportImpl.Flush; +begin + if FOutputStream = nil then begin + raise TTransportExceptionNotOpen.Create('Cannot flush null outputstream' ); + end; + + FOutputStream.Flush; +end; + +function TStreamTransportImpl.GetInputStream: IThriftStream; +begin + Result := FInputStream; +end; + +function TStreamTransportImpl.GetIsOpen: Boolean; +begin + Result := True; +end; + +function TStreamTransportImpl.GetOutputStream: IThriftStream; +begin + Result := FOutputStream; +end; + +procedure TStreamTransportImpl.Open; +begin + +end; + +function TStreamTransportImpl.Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; +begin + if FInputStream = nil then begin + raise TTransportExceptionNotOpen.Create('Cannot read from null inputstream' ); + end; + + Result := FInputStream.Read( pBuf,buflen, off, len ); +end; + +procedure TStreamTransportImpl.Write( const pBuf : Pointer; off, len : Integer); +begin + if FOutputStream = nil then begin + raise TTransportExceptionNotOpen.Create('Cannot write to null outputstream' ); + end; + + FOutputStream.Write( pBuf, off, len ); +end; + +{ TBufferedTransportImpl } + +constructor TBufferedTransportImpl.Create( const ATransport: IStreamTransport); +begin + //no inherited; + Create( ATransport, 1024 ); +end; + +constructor TBufferedTransportImpl.Create( const ATransport: IStreamTransport; ABufSize: Integer); +begin + inherited Create; + FTransport := ATransport; + FBufSize := ABufSize; + InitBuffers; +end; + +procedure TBufferedTransportImpl.Close; +begin + FTransport.Close; + FInputBuffer := nil; + FOutputBuffer := nil; +end; + +procedure TBufferedTransportImpl.Flush; +begin + if FOutputBuffer <> nil then begin + FOutputBuffer.Flush; + end; +end; + +function TBufferedTransportImpl.GetIsOpen: Boolean; +begin + Result := FTransport.IsOpen; +end; + +function TBufferedTransportImpl.GetUnderlyingTransport: ITransport; +begin + Result := FTransport; +end; + +procedure TBufferedTransportImpl.InitBuffers; +begin + if FTransport.InputStream <> nil then begin + FInputBuffer := TBufferedStreamImpl.Create( FTransport.InputStream, FBufSize ); + end; + if FTransport.OutputStream <> nil then begin + FOutputBuffer := TBufferedStreamImpl.Create( FTransport.OutputStream, FBufSize ); + end; +end; + +procedure TBufferedTransportImpl.Open; +begin + FTransport.Open; + InitBuffers; // we need to get the buffers to match FTransport substreams again +end; + +function TBufferedTransportImpl.Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; +begin + Result := 0; + if FInputBuffer <> nil then begin + Result := FInputBuffer.Read( pBuf,buflen, off, len ); + end; +end; + +procedure TBufferedTransportImpl.Write( const pBuf : Pointer; off, len : Integer); +begin + if FOutputBuffer <> nil then begin + FOutputBuffer.Write( pBuf, off, len ); + end; +end; + +{ TFramedTransportImpl } + +{$IFDEF HAVE_CLASS_CTOR} +class constructor TFramedTransportImpl.Create; +begin + SetLength( FHeader_Dummy, FHeaderSize); + FillChar( FHeader_Dummy[0], Length( FHeader_Dummy) * SizeOf( Byte ), 0); +end; +{$ELSE} +procedure TFramedTransportImpl_Initialize; +begin + SetLength( TFramedTransportImpl.FHeader_Dummy, TFramedTransportImpl.FHeaderSize); + FillChar( TFramedTransportImpl.FHeader_Dummy[0], + Length( TFramedTransportImpl.FHeader_Dummy) * SizeOf( Byte ), 0); +end; +{$ENDIF} + +constructor TFramedTransportImpl.Create; +begin + inherited Create; + InitWriteBuffer; +end; + +procedure TFramedTransportImpl.Close; +begin + FTransport.Close; +end; + +constructor TFramedTransportImpl.Create( const ATrans: ITransport); +begin + inherited Create; + InitWriteBuffer; + FTransport := ATrans; +end; + +destructor TFramedTransportImpl.Destroy; +begin + FWriteBuffer.Free; + FReadBuffer.Free; + inherited; +end; + +procedure TFramedTransportImpl.Flush; +var + buf : TBytes; + len : Integer; + data_len : Integer; + +begin + len := FWriteBuffer.Size; + SetLength( buf, len); + if len > 0 then begin + System.Move( FWriteBuffer.Memory^, buf[0], len ); + end; + + data_len := len - FHeaderSize; + if (data_len < 0) then begin + raise TTransportExceptionUnknown.Create('TFramedTransport.Flush: data_len < 0' ); + end; + + InitWriteBuffer; + + buf[0] := Byte($FF and (data_len shr 24)); + buf[1] := Byte($FF and (data_len shr 16)); + buf[2] := Byte($FF and (data_len shr 8)); + buf[3] := Byte($FF and data_len); + + FTransport.Write( buf, 0, len ); + FTransport.Flush; +end; + +function TFramedTransportImpl.GetIsOpen: Boolean; +begin + Result := FTransport.IsOpen; +end; + +type + TAccessMemoryStream = class(TMemoryStream) + end; + +procedure TFramedTransportImpl.InitWriteBuffer; +begin + FWriteBuffer.Free; + FWriteBuffer := TMemoryStream.Create; + TAccessMemoryStream(FWriteBuffer).Capacity := 1024; + FWriteBuffer.Write( Pointer(@FHeader_Dummy[0])^, FHeaderSize); +end; + +procedure TFramedTransportImpl.Open; +begin + FTransport.Open; +end; + +function TFramedTransportImpl.Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; +var pTmp : PByte; +begin + if len > (buflen-off) + then len := buflen-off; + + pTmp := pBuf; + Inc( pTmp, off); + + if (FReadBuffer <> nil) and (len > 0) then begin + result := FReadBuffer.Read( pTmp^, len); + if result > 0 then begin + Exit; + end; + end; + + ReadFrame; + if len > 0 + then Result := FReadBuffer.Read( pTmp^, len) + else Result := 0; +end; + +procedure TFramedTransportImpl.ReadFrame; +var + i32rd : TBytes; + size : Integer; + buff : TBytes; +begin + SetLength( i32rd, FHeaderSize ); + FTransport.ReadAll( i32rd, 0, FHeaderSize); + size := + ((i32rd[0] and $FF) shl 24) or + ((i32rd[1] and $FF) shl 16) or + ((i32rd[2] and $FF) shl 8) or + (i32rd[3] and $FF); + SetLength( buff, size ); + FTransport.ReadAll( buff, 0, size ); + FReadBuffer.Free; + FReadBuffer := TMemoryStream.Create; + if Length(buff) > 0 + then FReadBuffer.Write( Pointer(@buff[0])^, size ); + FReadBuffer.Position := 0; +end; + +procedure TFramedTransportImpl.Write( const pBuf : Pointer; off, len : Integer); +var pTmp : PByte; +begin + if len > 0 then begin + pTmp := pBuf; + Inc( pTmp, off); + + FWriteBuffer.Write( pTmp^, len ); + end; +end; + +{ TFramedTransport.TFactory } + +function TFramedTransportImpl.TFactory.GetTransport( const ATrans: ITransport): ITransport; +begin + Result := TFramedTransportImpl.Create( ATrans ); +end; + +{ TTcpSocketStreamImpl } + +procedure TTcpSocketStreamImpl.Close; +begin + FTcpClient.Close; +end; + +{$IFDEF OLD_SOCKETS} +constructor TTcpSocketStreamImpl.Create( const ATcpClient: TCustomIpClient; const aTimeout : Integer); +begin + inherited Create; + FTcpClient := ATcpClient; + FTimeout := aTimeout; +end; +{$ELSE} +constructor TTcpSocketStreamImpl.Create( const ATcpClient: TSocket; const aTimeout : Longword); +begin + inherited Create; + FTcpClient := ATcpClient; + if aTimeout = 0 then + FTcpClient.RecvTimeout := SLEEP_TIME + else + FTcpClient.RecvTimeout := aTimeout; + FTcpClient.SendTimeout := aTimeout; +end; +{$ENDIF} + +procedure TTcpSocketStreamImpl.Flush; +begin + +end; + +function TTcpSocketStreamImpl.IsOpen: Boolean; +begin +{$IFDEF OLD_SOCKETS} + Result := FTcpClient.Active; +{$ELSE} + Result := FTcpClient.IsOpen; +{$ENDIF} +end; + +procedure TTcpSocketStreamImpl.Open; +begin + FTcpClient.Open; +end; + + +{$IFDEF OLD_SOCKETS} +function TTcpSocketStreamImpl.Select( ReadReady, WriteReady, ExceptFlag: PBoolean; + TimeOut: Integer; var wsaError : Integer): Integer; +var + ReadFds: TFDset; + ReadFdsptr: PFDset; + WriteFds: TFDset; + WriteFdsptr: PFDset; + ExceptFds: TFDset; + ExceptFdsptr: PFDset; + tv: timeval; + Timeptr: PTimeval; + socket : TSocket; +begin + if not FTcpClient.Active then begin + wsaError := WSAEINVAL; + Exit( SOCKET_ERROR); + end; + + socket := FTcpClient.Handle; + + if Assigned(ReadReady) then begin + ReadFdsptr := @ReadFds; + FD_ZERO(ReadFds); + FD_SET(socket, ReadFds); + end + else begin + ReadFdsptr := nil; + end; + + if Assigned(WriteReady) then begin + WriteFdsptr := @WriteFds; + FD_ZERO(WriteFds); + FD_SET(socket, WriteFds); + end + else begin + WriteFdsptr := nil; + end; + + if Assigned(ExceptFlag) then begin + ExceptFdsptr := @ExceptFds; + FD_ZERO(ExceptFds); + FD_SET(socket, ExceptFds); + end + else begin + ExceptFdsptr := nil; + end; + + if TimeOut >= 0 then begin + tv.tv_sec := TimeOut div 1000; + tv.tv_usec := 1000 * (TimeOut mod 1000); + Timeptr := @tv; + end + else begin + Timeptr := nil; // wait forever + end; + + wsaError := 0; + try + {$IFDEF MSWINDOWS} + {$IFDEF OLD_UNIT_NAMES} + result := WinSock.select( socket + 1, ReadFdsptr, WriteFdsptr, ExceptFdsptr, Timeptr); + {$ELSE} + result := Winapi.WinSock.select( socket + 1, ReadFdsptr, WriteFdsptr, ExceptFdsptr, Timeptr); + {$ENDIF} + {$ENDIF} + {$IFDEF LINUX} + result := Libc.select( socket + 1, ReadFdsptr, WriteFdsptr, ExceptFdsptr, Timeptr); + {$ENDIF} + + if result = SOCKET_ERROR + then wsaError := WSAGetLastError; + + except + result := SOCKET_ERROR; + end; + + if Assigned(ReadReady) then + ReadReady^ := FD_ISSET(socket, ReadFds); + + if Assigned(WriteReady) then + WriteReady^ := FD_ISSET(socket, WriteFds); + + if Assigned(ExceptFlag) then + ExceptFlag^ := FD_ISSET(socket, ExceptFds); +end; +{$ENDIF} + +{$IFDEF OLD_SOCKETS} +function TTcpSocketStreamImpl.WaitForData( TimeOut : Integer; pBuf : Pointer; + DesiredBytes : Integer; + var wsaError, bytesReady : Integer): TWaitForData; +var bCanRead, bError : Boolean; + retval : Integer; +const + MSG_PEEK = {$IFDEF OLD_UNIT_NAMES} WinSock.MSG_PEEK {$ELSE} Winapi.WinSock.MSG_PEEK {$ENDIF}; +begin + bytesReady := 0; + + // The select function returns the total number of socket handles that are ready + // and contained in the fd_set structures, zero if the time limit expired, + // or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, + // WSAGetLastError can be used to retrieve a specific error code. + retval := Self.Select( @bCanRead, nil, @bError, TimeOut, wsaError); + if retval = SOCKET_ERROR + then Exit( TWaitForData.wfd_Error); + if (retval = 0) or not bCanRead + then Exit( TWaitForData.wfd_Timeout); + + // recv() returns the number of bytes received, or -1 if an error occurred. + // The return value will be 0 when the peer has performed an orderly shutdown. + + retval := recv( FTcpClient.Handle, pBuf^, DesiredBytes, MSG_PEEK); + if retval <= 0 + then Exit( TWaitForData.wfd_Error); + + // at least we have some data + bytesReady := Min( retval, DesiredBytes); + result := TWaitForData.wfd_HaveData; +end; +{$ENDIF} + +{$IFDEF OLD_SOCKETS} +function TTcpSocketStreamImpl.Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; +// old sockets version +var wfd : TWaitForData; + wsaError, + msecs : Integer; + nBytes : Integer; + pTmp : PByte; +begin + inherited; + + if FTimeout > 0 + then msecs := FTimeout + else msecs := DEFAULT_THRIFT_TIMEOUT; + + result := 0; + pTmp := pBuf; + Inc( pTmp, offset); + while count > 0 do begin + + while TRUE do begin + wfd := WaitForData( msecs, pTmp, count, wsaError, nBytes); + case wfd of + TWaitForData.wfd_Error : Exit; + TWaitForData.wfd_HaveData : Break; + TWaitForData.wfd_Timeout : begin + if (FTimeout = 0) + then Exit + else begin + raise TTransportExceptionTimedOut.Create(SysErrorMessage(Cardinal(wsaError))); + + end; + end; + else + ASSERT( FALSE); + end; + end; + + // reduce the timeout once we got data + if FTimeout > 0 + then msecs := FTimeout div 10 + else msecs := DEFAULT_THRIFT_TIMEOUT div 10; + msecs := Max( msecs, 200); + + ASSERT( nBytes <= count); + nBytes := FTcpClient.ReceiveBuf( pTmp^, nBytes); + Inc( pTmp, nBytes); + Dec( count, nBytes); + Inc( result, nBytes); + end; +end; + +function TTcpSocketStreamImpl.ToArray: TBytes; +// old sockets version +var len : Integer; +begin + len := 0; + if IsOpen then begin + len := FTcpClient.BytesReceived; + end; + + SetLength( Result, len ); + + if len > 0 then begin + FTcpClient.ReceiveBuf( Pointer(@Result[0])^, len); + end; +end; + +procedure TTcpSocketStreamImpl.Write( const pBuf : Pointer; offset, count: Integer); +// old sockets version +var bCanWrite, bError : Boolean; + retval, wsaError : Integer; + pTmp : PByte; +begin + inherited; + + if not FTcpClient.Active + then raise TTransportExceptionNotOpen.Create('not open'); + + // The select function returns the total number of socket handles that are ready + // and contained in the fd_set structures, zero if the time limit expired, + // or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, + // WSAGetLastError can be used to retrieve a specific error code. + retval := Self.Select( nil, @bCanWrite, @bError, FTimeOut, wsaError); + if retval = SOCKET_ERROR + then raise TTransportExceptionUnknown.Create(SysErrorMessage(Cardinal(wsaError))); + + if (retval = 0) + then raise TTransportExceptionTimedOut.Create('timed out'); + + if bError or not bCanWrite + then raise TTransportExceptionUnknown.Create('unknown error'); + + pTmp := pBuf; + Inc( pTmp, offset); + FTcpClient.SendBuf( pTmp^, count); +end; + +{$ELSE} + +function TTcpSocketStreamImpl.Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; +// new sockets version +var nBytes : Integer; + pTmp : PByte; +begin + inherited; + + result := 0; + pTmp := pBuf; + Inc( pTmp, offset); + while count > 0 do begin + nBytes := FTcpClient.Read( pTmp^, count); + if nBytes = 0 then Exit; + Inc( pTmp, nBytes); + Dec( count, nBytes); + Inc( result, nBytes); + end; +end; + +function TTcpSocketStreamImpl.ToArray: TBytes; +// new sockets version +var len : Integer; +begin + len := 0; + try + if FTcpClient.Peek then + repeat + SetLength(Result, Length(Result) + 1024); + len := FTcpClient.Read(Result[Length(Result) - 1024], 1024); + until len < 1024; + except + on TTransportException do begin { don't allow default exceptions } end; + else raise; + end; + if len > 0 then + SetLength(Result, Length(Result) - 1024 + len); +end; + +procedure TTcpSocketStreamImpl.Write( const pBuf : Pointer; offset, count: Integer); +// new sockets version +var pTmp : PByte; +begin + inherited; + + if not FTcpClient.IsOpen + then raise TTransportExceptionNotOpen.Create('not open'); + + pTmp := pBuf; + Inc( pTmp, offset); + FTcpClient.Write( pTmp^, count); +end; + +{$ENDIF} + + +{$IF CompilerVersion < 21.0} +initialization +begin + TFramedTransportImpl_Initialize; +end; +{$IFEND} + + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.TypeRegistry.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.TypeRegistry.pas new file mode 100644 index 000000000..c18e97fe6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.TypeRegistry.pas @@ -0,0 +1,95 @@ +(* + * 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. + *) + +unit Thrift.TypeRegistry; + +interface + +uses + Generics.Collections, TypInfo, + Thrift.Protocol; + +type + TFactoryMethod<T> = function:T; + + TypeRegistry = class + private + class var FTypeInfoToFactoryLookup : TDictionary<Pointer, Pointer>; + public + class constructor Create; + class destructor Destroy; + class procedure RegisterTypeFactory<F>(const aFactoryMethod: TFactoryMethod<F>); + class function Construct<F>: F; + class function ConstructFromTypeInfo(const aTypeInfo: PTypeInfo): IBase; + end; + +implementation + + +{ TypeRegistration } + +class constructor TypeRegistry.Create; +begin + FTypeInfoToFactoryLookup := TDictionary<Pointer, Pointer>.Create; +end; + +class destructor TypeRegistry.Destroy; +begin + FTypeInfoToFactoryLookup.Free; +end; + +class procedure TypeRegistry.RegisterTypeFactory<F>(const aFactoryMethod: TFactoryMethod<F>); +var + TypeInfo : Pointer; +begin + TypeInfo := System.TypeInfo(F); + + if (TypeInfo <> nil) and (PTypeInfo(TypeInfo).Kind = tkInterface) + then FTypeInfoToFactoryLookup.AddOrSetValue(TypeInfo, @aFactoryMethod); +end; + +class function TypeRegistry.Construct<F>: F; +var + TypeInfo : PTypeInfo; + Factory : Pointer; +begin + Result := default(F); + + TypeInfo := System.TypeInfo(F); + + if Assigned(TypeInfo) and (TypeInfo.Kind = tkInterface) + then begin + if FTypeInfoToFactoryLookup.TryGetValue(TypeInfo, Factory) + then Result := TFactoryMethod<F>(Factory)(); + end; +end; + +class function TypeRegistry.ConstructFromTypeInfo(const aTypeInfo: PTypeInfo): IBase; +var + Factory : Pointer; +begin + Result := nil; + if FTypeInfoToFactoryLookup.TryGetValue(aTypeInfo, Factory) + then Result := IBase(TFactoryMethod<IBase>(Factory)()); +end; + + + + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.Utils.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Utils.pas new file mode 100644 index 000000000..ede265646 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.Utils.pas @@ -0,0 +1,336 @@ +(* + * 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. + *) + +unit Thrift.Utils; + +interface + +{$I Thrift.Defines.inc} + +uses + {$IFDEF OLD_UNIT_NAMES} + Classes, Windows, SysUtils, Character, SyncObjs, TypInfo, Rtti; + {$ELSE} + System.Classes, Winapi.Windows, System.SysUtils, System.Character, + System.SyncObjs, System.TypInfo, System.Rtti; + {$ENDIF} + +type + ISupportsToString = interface + ['{AF71C350-E0CD-4E94-B77C-0310DC8227FF}'] + function ToString : string; + end; + + + IOverlappedHelper = interface + ['{A1832EFA-2E02-4884-8F09-F0A0277157FA}'] + function Overlapped : TOverlapped; + function OverlappedPtr : POverlapped; + function WaitHandle : THandle; + function WaitFor(dwTimeout: DWORD) : DWORD; + end; + + TOverlappedHelperImpl = class( TInterfacedObject, IOverlappedHelper) + strict protected + FOverlapped : TOverlapped; + FEvent : TEvent; + + // IOverlappedHelper + function Overlapped : TOverlapped; + function OverlappedPtr : POverlapped; + function WaitHandle : THandle; + function WaitFor(dwTimeout: DWORD) : DWORD; + public + constructor Create; + destructor Destroy; override; + end; + + + TThriftStringBuilder = class( TStringBuilder) + public + function Append(const Value: TBytes): TStringBuilder; overload; + function Append(const Value: ISupportsToString): TStringBuilder; overload; + end; + + + Base64Utils = class sealed + public + class function Encode( const src : TBytes; srcOff, len : Integer; dst : TBytes; dstOff : Integer) : Integer; static; + class function Decode( const src : TBytes; srcOff, len : Integer; dst : TBytes; dstOff : Integer) : Integer; static; + end; + + + CharUtils = class sealed + public + class function IsHighSurrogate( const c : Char) : Boolean; static; inline; + class function IsLowSurrogate( const c : Char) : Boolean; static; inline; + end; + + EnumUtils<T> = class sealed + public + class function ToString(const value : Integer) : string; reintroduce; static; inline; + end; + + StringUtils<T> = class sealed + public + class function ToString(const value : T) : string; reintroduce; static; inline; + end; + + +const + THRIFT_MIMETYPE = 'application/x-thrift'; + +{$IFDEF Win64} +function InterlockedExchangeAdd64( var Addend : Int64; Value : Int64) : Int64; +{$ENDIF} + + +implementation + +{ TOverlappedHelperImpl } + +constructor TOverlappedHelperImpl.Create; +begin + inherited Create; + FillChar( FOverlapped, SizeOf(FOverlapped), 0); + FEvent := TEvent.Create( nil, TRUE, FALSE, ''); // always ManualReset, see MSDN + FOverlapped.hEvent := FEvent.Handle; +end; + + + +destructor TOverlappedHelperImpl.Destroy; +begin + try + FOverlapped.hEvent := 0; + FreeAndNil( FEvent); + + finally + inherited Destroy; + end; + +end; + + +function TOverlappedHelperImpl.Overlapped : TOverlapped; +begin + result := FOverlapped; +end; + + +function TOverlappedHelperImpl.OverlappedPtr : POverlapped; +begin + result := @FOverlapped; +end; + + +function TOverlappedHelperImpl.WaitHandle : THandle; +begin + result := FOverlapped.hEvent; +end; + + +function TOverlappedHelperImpl.WaitFor( dwTimeout : DWORD) : DWORD; +begin + result := WaitForSingleObject( FOverlapped.hEvent, dwTimeout); +end; + + +{ Base64Utils } + +class function Base64Utils.Encode( const src : TBytes; srcOff, len : Integer; dst : TBytes; dstOff : Integer) : Integer; +const ENCODE_TABLE : PAnsiChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +begin + ASSERT( len in [1..3]); + dst[dstOff] := Byte( ENCODE_TABLE[ (src[srcOff] shr 2) and $3F]); + case len of + 3 : begin + Inc(dstOff); + dst[dstOff] := Byte( ENCODE_TABLE[ ((src[srcOff] shl 4) and $30) or ((src[srcOff + 1] shr 4) and $0F)]); + Inc(dstOff); + dst[dstOff] := Byte( ENCODE_TABLE[ ((src[srcOff + 1] shl 2) and $3C) or ((src[srcOff + 2] shr 6) and $03)]); + Inc(dstOff); + dst[dstOff] := Byte( ENCODE_TABLE[ src[srcOff + 2] and $3F]); + result := 4; + end; + + 2 : begin + Inc(dstOff); + dst[dstOff] := Byte( ENCODE_TABLE[ ((src[srcOff] shl 4) and $30) or ((src[srcOff + 1] shr 4) and $0F)]); + Inc(dstOff); + dst[dstOff] := Byte( ENCODE_TABLE[ (src[srcOff + 1] shl 2) and $3C]); + result := 3; + end; + + 1 : begin + Inc(dstOff); + dst[dstOff] := Byte( ENCODE_TABLE[ (src[srcOff] shl 4) and $30]); + result := 2; + end; + + else + ASSERT( FALSE); + result := 0; // because invalid call + end; +end; + + +class function Base64Utils.Decode( const src : TBytes; srcOff, len : Integer; dst : TBytes; dstOff : Integer) : Integer; +const DECODE_TABLE : array[0..$FF] of Integer + = ( -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ); +begin + ASSERT( len in [1..4]); + result := 1; + dst[dstOff] := ((DECODE_TABLE[src[srcOff] and $0FF] shl 2) + or (DECODE_TABLE[src[srcOff + 1] and $0FF] shr 4)); + + if (len > 2) then begin + Inc( result); + Inc( dstOff); + dst[dstOff] := (((DECODE_TABLE[src[srcOff + 1] and $0FF] shl 4) and $F0) + or (DECODE_TABLE[src[srcOff + 2] and $0FF] shr 2)); + + if (len > 3) then begin + Inc( result); + Inc( dstOff); + dst[dstOff] := (((DECODE_TABLE[src[srcOff + 2] and $0FF] shl 6) and $C0) + or DECODE_TABLE[src[srcOff + 3] and $0FF]); + end; + end; +end; + + +class function CharUtils.IsHighSurrogate( const c : Char) : Boolean; +begin + {$IF CompilerVersion < 25.0} + {$IFDEF OLD_UNIT_NAMES} + result := Character.IsHighSurrogate(c); + {$ELSE} + result := System.Character.IsHighSurrogate(c); + {$ENDIF} + {$ELSE} + result := c.IsHighSurrogate(); + {$IFEND} +end; + + +class function CharUtils.IsLowSurrogate( const c : Char) : Boolean; +begin + {$IF CompilerVersion < 25.0} + {$IFDEF OLD_UNIT_NAMES} + result := Character.IsLowSurrogate(c); + {$ELSE} + result := System.Character.IsLowSurrogate(c); + {$ENDIF} + {$ELSE} + result := c.IsLowSurrogate(); + {$IFEND} +end; + + +{$IFDEF Win64} + +function InterlockedCompareExchange64( var Target : Int64; Exchange, Comparand : Int64) : Int64; inline; +begin + {$IFDEF OLD_UNIT_NAMES} + result := Windows.InterlockedCompareExchange64( Target, Exchange, Comparand); + {$ELSE} + result := WinApi.Windows.InterlockedCompareExchange64( Target, Exchange, Comparand); + {$ENDIF} +end; + + +function InterlockedExchangeAdd64( var Addend : Int64; Value : Int64) : Int64; +var old : Int64; +begin + repeat + Old := Addend; + until (InterlockedCompareExchange64( Addend, Old + Value, Old) = Old); + result := Old; +end; + +{$ENDIF} + + +{ EnumUtils<T> } + +class function EnumUtils<T>.ToString(const value : Integer) : string; +var pType : PTypeInfo; +begin + pType := PTypeInfo(TypeInfo(T)); + if Assigned(pType) and (pType^.Kind = tkEnumeration) + then result := GetEnumName(pType,value) + else result := IntToStr(Ord(value)); +end; + + +{ StringUtils<T> } + +class function StringUtils<T>.ToString(const value : T) : string; +type PInterface = ^IInterface; +var pType : PTypeInfo; + stos : ISupportsToString; + pIntf : PInterface; // Workaround: Rio does not allow the direct typecast +begin + pType := PTypeInfo(TypeInfo(T)); + if Assigned(pType) then begin + case pType^.Kind of + tkInterface : begin + pIntf := PInterface(@value); + if Supports( pIntf^, ISupportsToString, stos) then begin + result := stos.toString; + Exit; + end; + end; + end; + end; + + result := TValue.From<T>(value).ToString; +end; + + +{ TThriftStringBuilder } + +function TThriftStringBuilder.Append(const Value: TBytes): TStringBuilder; +begin + Result := Append( string( RawByteString(Value)) ); +end; + +function TThriftStringBuilder.Append( const Value: ISupportsToString): TStringBuilder; +begin + Result := Append( Value.ToString ); +end; + + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.WinHTTP.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.WinHTTP.pas new file mode 100644 index 000000000..854d7c080 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.WinHTTP.pas @@ -0,0 +1,1273 @@ +(* + * 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. + *) +unit Thrift.WinHTTP; + +{$I Thrift.Defines.inc} +{$SCOPEDENUMS ON} + +// packing according to winhttp.h +{$IFDEF Win64} {$ALIGN 8} {$ELSE} {$ALIGN 4} {$ENDIF} + +interface + +uses + Windows, + Classes, + SysUtils, + Math, + Generics.Collections; + + +type + HINTERNET = type Pointer; + INTERNET_PORT = type WORD; + INTERNET_SCHEME = type Integer; + LPLPCWSTR = ^LPCWSTR; + + LPURL_COMPONENTS = ^URL_COMPONENTS; + URL_COMPONENTS = record + dwStructSize : DWORD; // set to SizeOf(URL_COMPONENTS) + lpszScheme : LPWSTR; // scheme name + dwSchemeLength : DWORD; + nScheme : INTERNET_SCHEME; // enumerated scheme type + lpszHostName : LPWSTR; // host name + dwHostNameLength : DWORD; + nPort : INTERNET_PORT; // port number + lpszUserName : LPWSTR; // user name + dwUserNameLength : DWORD; + lpszPassword : LPWSTR; // password + dwPasswordLength : DWORD; + lpszUrlPath : LPWSTR; // URL-path + dwUrlPathLength : DWORD; + lpszExtraInfo : LPWSTR; // extra information + dwExtraInfoLength : DWORD; + end; + + URL_COMPONENTSW = URL_COMPONENTS; + LPURL_COMPONENTSW = LPURL_COMPONENTS; + + + // When retrieving proxy data, an application must free the lpszProxy and + // lpszProxyBypass strings contained in this structure (if they are non-NULL) + // using the GlobalFree function. + LPWINHTTP_PROXY_INFO = ^WINHTTP_PROXY_INFO; + WINHTTP_PROXY_INFO = record + dwAccessType : DWORD; // see WINHTTP_ACCESS_* types below + lpszProxy : LPWSTR; // proxy server list + lpszProxyBypass : LPWSTR; // proxy bypass list + end; + + LPWINHTTP_PROXY_INFOW = ^WINHTTP_PROXY_INFOW; + WINHTTP_PROXY_INFOW = WINHTTP_PROXY_INFO; + + + WINHTTP_AUTOPROXY_OPTIONS = record + dwFlags : DWORD; + dwAutoDetectFlags : DWORD; + lpszAutoConfigUrl : LPCWSTR; + lpvReserved : LPVOID; + dwReserved : DWORD; + fAutoLogonIfChallenged : BOOL; + end; + + + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG = record + fAutoDetect : BOOL; + lpszAutoConfigUrl : LPWSTR; + lpszProxy : LPWSTR; + lpszProxyBypass : LPWSTR; + end; + + + + +function WinHttpCloseHandle( aHandle : HINTERNET) : BOOL; stdcall; + +function WinHttpOpen( const pszAgentW : LPCWSTR; + const dwAccessType : DWORD; + const pszProxyW : LPCWSTR; + const pszProxyBypassW : LPCWSTR; + const dwFlags : DWORD + ) : HINTERNET; stdcall; + +function WinHttpConnect( const hSession : HINTERNET; + const pswzServerName : LPCWSTR; + const nServerPort : INTERNET_PORT; + const dwReserved : DWORD + ) : HINTERNET; stdcall; + +function WinHttpOpenRequest( const hConnect : HINTERNET; + const pwszVerb, pwszObjectName, pwszVersion, pwszReferrer : LPCWSTR; + const ppwszAcceptTypes : LPLPCWSTR; + const dwFlags : DWORD + ) : HINTERNET; stdcall; + +function WinHttpQueryOption( const hInternet : HINTERNET; + const dwOption : DWORD; + const pBuffer : Pointer; + var dwBufferLength : DWORD) : BOOL; stdcall; + +function WinHttpSetOption( const hInternet : HINTERNET; + const dwOption : DWORD; + const pBuffer : Pointer; + const dwBufferLength : DWORD) : BOOL; stdcall; + +function WinHttpSetTimeouts( const hRequestOrSession : HINTERNET; + const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32 + ) : BOOL; stdcall; + +function WinHttpAddRequestHeaders( const hRequest : HINTERNET; + const pwszHeaders : LPCWSTR; + const dwHeadersLengthInChars : DWORD; + const dwModifiers : DWORD + ) : BOOL; stdcall; + +function WinHttpGetProxyForUrl( const hSession : HINTERNET; + const lpcwszUrl : LPCWSTR; + const options : WINHTTP_AUTOPROXY_OPTIONS; + const info : WINHTTP_PROXY_INFO + ) : BOOL; stdcall; + +function WinHttpGetIEProxyConfigForCurrentUser( var config : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + ) : BOOL; stdcall; + + +function WinHttpSendRequest( const hRequest : HINTERNET; + const lpszHeaders : LPCWSTR; + const dwHeadersLength : DWORD; + const lpOptional : Pointer; + const dwOptionalLength : DWORD; + const dwTotalLength : DWORD; + const pContext : Pointer + ) : BOOL; stdcall; + +function WinHttpWriteData( const hRequest : HINTERNET; + const pBuf : Pointer; + const dwBytesToWrite : DWORD; + out dwBytesWritten : DWORD + ) : BOOL; stdcall; + +function WinHttpReceiveResponse( const hRequest : HINTERNET; const lpReserved : Pointer) : BOOL; stdcall; + +function WinHttpQueryHeaders( const hRequest : HINTERNET; + const dwInfoLevel : DWORD; + const pwszName : LPCWSTR; + const lpBuffer : Pointer; + var dwBufferLength : DWORD; + var dwIndex : DWORD + ) : BOOL; stdcall; + +function WinHttpQueryDataAvailable( const hRequest : HINTERNET; + var dwNumberOfBytesAvailable : DWORD + ) : BOOL; stdcall; + +function WinHttpReadData( const hRequest : HINTERNET; + const lpBuffer : Pointer; + const dwBytesToRead : DWORD; + out dwBytesRead : DWORD + ) : BOOL; stdcall; + +function WinHttpCrackUrl( const pwszUrl : LPCWSTR; + const dwUrlLength : DWORD; + const dwFlags : DWORD; + var urlComponents : URL_COMPONENTS + ) : BOOL; stdcall; + +function WinHttpCreateUrl( const UrlComponents : URL_COMPONENTS; + const dwFlags : DWORD; + const pwszUrl : LPCWSTR; + var pdwUrlLength : DWORD + ) : BOOL; stdcall; + + +const + // WinHttpOpen dwAccessType values + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0; + WINHTTP_ACCESS_TYPE_NO_PROXY = 1; + WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3; + + // flags for WinHttpOpen(): + WINHTTP_FLAG_ASYNC = $10000000; // want async session, requires WinHttpSetStatusCallback() usage + + // ports + INTERNET_DEFAULT_PORT = 0; // use the protocol-specific default (80 or 443) + + // flags for WinHttpOpenRequest(): + WINHTTP_FLAG_SECURE = $00800000; // use SSL if applicable (HTTPS) + WINHTTP_FLAG_ESCAPE_PERCENT = $00000004; // if escaping enabled, escape percent as well + WINHTTP_FLAG_NULL_CODEPAGE = $00000008; // assume all symbols are ASCII, use fast convertion + WINHTTP_FLAG_BYPASS_PROXY_CACHE = $00000100; // add "pragma: no-cache" request header + WINHTTP_FLAG_REFRESH = WINHTTP_FLAG_BYPASS_PROXY_CACHE; + WINHTTP_FLAG_ESCAPE_DISABLE = $00000040; // disable escaping + WINHTTP_FLAG_ESCAPE_DISABLE_QUERY = $00000080; // if escaping enabled escape path part, but do not escape query + + // flags for WinHttpSendRequest(): + WINHTTP_NO_ADDITIONAL_HEADERS = nil; + WINHTTP_NO_REQUEST_DATA = nil; + + // WinHttpAddRequestHeaders() dwModifiers + WINHTTP_ADDREQ_INDEX_MASK = $0000FFFF; + WINHTTP_ADDREQ_FLAGS_MASK = $FFFF0000; + + WINHTTP_ADDREQ_FLAG_ADD_IF_NEW = $10000000; + WINHTTP_ADDREQ_FLAG_ADD = $20000000; + WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA = $40000000; + WINHTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON = $01000000; + WINHTTP_ADDREQ_FLAG_COALESCE = WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA; + WINHTTP_ADDREQ_FLAG_REPLACE = $80000000; + + // URL functions + ICU_NO_ENCODE = $20000000; // Don't convert unsafe characters to escape sequence + ICU_DECODE = $10000000; // Convert %XX escape sequences to characters + ICU_NO_META = $08000000; // Don't convert .. etc. meta path sequences + ICU_ENCODE_SPACES_ONLY = $04000000; // Encode spaces only + ICU_BROWSER_MODE = $02000000; // Special encode/decode rules for browser + ICU_ENCODE_PERCENT = $00001000; // Encode any percent (ASCII25) + + ICU_ESCAPE = $80000000; // (un)escape URL characters + ICU_ESCAPE_AUTHORITY = $00002000; // causes InternetCreateUrlA to escape chars in authority components (user, pwd, host) + ICU_REJECT_USERPWD = $00004000; // rejects usrls whick have username/pwd sections + + INTERNET_SCHEME_HTTP = INTERNET_SCHEME(1); + INTERNET_SCHEME_HTTPS = INTERNET_SCHEME(2); + + WINHTTP_NO_CLIENT_CERT_CONTEXT = nil; + + // options manifests for WinHttp{Query|Set}Option + WINHTTP_OPTION_CALLBACK = 1; + WINHTTP_OPTION_RESOLVE_TIMEOUT = 2; + WINHTTP_OPTION_CONNECT_TIMEOUT = 3; + WINHTTP_OPTION_CONNECT_RETRIES = 4; + WINHTTP_OPTION_SEND_TIMEOUT = 5; + WINHTTP_OPTION_RECEIVE_TIMEOUT = 6; + WINHTTP_OPTION_RECEIVE_RESPONSE_TIMEOUT = 7; + WINHTTP_OPTION_HANDLE_TYPE = 9; + WINHTTP_OPTION_READ_BUFFER_SIZE = 12; + WINHTTP_OPTION_WRITE_BUFFER_SIZE = 13; + WINHTTP_OPTION_PARENT_HANDLE = 21; + WINHTTP_OPTION_EXTENDED_ERROR = 24; + WINHTTP_OPTION_SECURITY_FLAGS = 31; + WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT = 32; + WINHTTP_OPTION_URL = 34; + WINHTTP_OPTION_SECURITY_KEY_BITNESS = 36; + WINHTTP_OPTION_PROXY = 38; + WINHTTP_OPTION_USER_AGENT = 41; + WINHTTP_OPTION_CONTEXT_VALUE = 45; + WINHTTP_OPTION_CLIENT_CERT_CONTEXT = 47; + WINHTTP_OPTION_REQUEST_PRIORITY = 58; + WINHTTP_OPTION_HTTP_VERSION = 59; + WINHTTP_OPTION_DISABLE_FEATURE = 63; + WINHTTP_OPTION_CODEPAGE = 68; + WINHTTP_OPTION_MAX_CONNS_PER_SERVER = 73; + WINHTTP_OPTION_MAX_CONNS_PER_1_0_SERVER = 74; + WINHTTP_OPTION_AUTOLOGON_POLICY = 77; + WINHTTP_OPTION_SERVER_CERT_CONTEXT = 78; + WINHTTP_OPTION_ENABLE_FEATURE = 79; + WINHTTP_OPTION_WORKER_THREAD_COUNT = 80; + WINHTTP_OPTION_PASSPORT_COBRANDING_TEXT = 81; + WINHTTP_OPTION_PASSPORT_COBRANDING_URL = 82; + WINHTTP_OPTION_CONFIGURE_PASSPORT_AUTH = 83; + WINHTTP_OPTION_SECURE_PROTOCOLS = 84; + WINHTTP_OPTION_ENABLETRACING = 85; + WINHTTP_OPTION_PASSPORT_SIGN_OUT = 86; + WINHTTP_OPTION_PASSPORT_RETURN_URL = 87; + WINHTTP_OPTION_REDIRECT_POLICY = 88; + WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS = 89; + WINHTTP_OPTION_MAX_HTTP_STATUS_CONTINUE = 90; + WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE = 91; + WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE = 92; + WINHTTP_OPTION_CONNECTION_INFO = 93; + WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST = 94; + WINHTTP_OPTION_SPN = 96; + WINHTTP_OPTION_GLOBAL_PROXY_CREDS = 97; + WINHTTP_OPTION_GLOBAL_SERVER_CREDS = 98; + WINHTTP_OPTION_UNLOAD_NOTIFY_EVENT = 99; + WINHTTP_OPTION_REJECT_USERPWD_IN_URL = 100; + WINHTTP_OPTION_USE_GLOBAL_SERVER_CREDENTIALS = 101; + WINHTTP_OPTION_RECEIVE_PROXY_CONNECT_RESPONSE = 103; + WINHTTP_OPTION_IS_PROXY_CONNECT_RESPONSE = 104; + WINHTTP_OPTION_SERVER_SPN_USED = 106; + WINHTTP_OPTION_PROXY_SPN_USED = 107; + WINHTTP_OPTION_SERVER_CBT = 108; + // options for newer WinHTTP versions + WINHTTP_OPTION_DECOMPRESSION = 118; + // + WINHTTP_FIRST_OPTION = WINHTTP_OPTION_CALLBACK; + //WINHTTP_LAST_OPTION = WINHTTP_OPTION_SERVER_CBT; + + WINHTTP_OPTION_USERNAME = $1000; + WINHTTP_OPTION_PASSWORD = $1001; + WINHTTP_OPTION_PROXY_USERNAME = $1002; + WINHTTP_OPTION_PROXY_PASSWORD = $1003; + + // manifest value for WINHTTP_OPTION_MAX_CONNS_PER_SERVER and WINHTTP_OPTION_MAX_CONNS_PER_1_0_SERVER + WINHTTP_CONNS_PER_SERVER_UNLIMITED = $FFFFFFFF; + + // values for WINHTTP_OPTION_AUTOLOGON_POLICY + WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM = 0; + WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW = 1; + WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH = 2; + + WINHTTP_AUTOLOGON_SECURITY_LEVEL_DEFAULT = WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM; + + // values for WINHTTP_OPTION_REDIRECT_POLICY + WINHTTP_OPTION_REDIRECT_POLICY_NEVER = 0; + WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP = 1; + WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS = 2; + + WINHTTP_OPTION_REDIRECT_POLICY_LAST = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS; + WINHTTP_OPTION_REDIRECT_POLICY_DEFAULT = WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP; + + WINHTTP_DISABLE_PASSPORT_AUTH = $00000000; + WINHTTP_ENABLE_PASSPORT_AUTH = $10000000; + WINHTTP_DISABLE_PASSPORT_KEYRING = $20000000; + WINHTTP_ENABLE_PASSPORT_KEYRING = $40000000; + + // values for WINHTTP_OPTION_DISABLE_FEATURE + WINHTTP_DISABLE_COOKIES = $00000001; + WINHTTP_DISABLE_REDIRECTS = $00000002; + WINHTTP_DISABLE_AUTHENTICATION = $00000004; + WINHTTP_DISABLE_KEEP_ALIVE = $00000008; + + // values for WINHTTP_OPTION_ENABLE_FEATURE + WINHTTP_ENABLE_SSL_REVOCATION = $00000001; + WINHTTP_ENABLE_SSL_REVERT_IMPERSONATION = $00000002; + + // values for WINHTTP_OPTION_SPN + WINHTTP_DISABLE_SPN_SERVER_PORT = $00000000; + WINHTTP_ENABLE_SPN_SERVER_PORT = $00000001; + WINHTTP_OPTION_SPN_MASK = WINHTTP_ENABLE_SPN_SERVER_PORT; + + // winhttp handle types + WINHTTP_HANDLE_TYPE_SESSION = 1; + WINHTTP_HANDLE_TYPE_CONNECT = 2; + WINHTTP_HANDLE_TYPE_REQUEST = 3; + + // values for auth schemes + WINHTTP_AUTH_SCHEME_BASIC = $00000001; + WINHTTP_AUTH_SCHEME_NTLM = $00000002; + WINHTTP_AUTH_SCHEME_PASSPORT = $00000004; + WINHTTP_AUTH_SCHEME_DIGEST = $00000008; + WINHTTP_AUTH_SCHEME_NEGOTIATE = $00000010; + + // WinHttp supported Authentication Targets + WINHTTP_AUTH_TARGET_SERVER = $00000000; + WINHTTP_AUTH_TARGET_PROXY = $00000001; + + // options for WINHTTP_OPTION_DECOMPRESSION + WINHTTP_DECOMPRESSION_FLAG_GZIP = $00000001; + WINHTTP_DECOMPRESSION_FLAG_DEFLATE = $00000002; + WINHTTP_DECOMPRESSION_FLAG_ALL = WINHTTP_DECOMPRESSION_FLAG_GZIP + or WINHTTP_DECOMPRESSION_FLAG_DEFLATE; + + // values for WINHTTP_OPTION_SECURITY_FLAGS + + // query only + SECURITY_FLAG_SECURE = $00000001; // can query only + SECURITY_FLAG_STRENGTH_WEAK = $10000000; + SECURITY_FLAG_STRENGTH_MEDIUM = $40000000; + SECURITY_FLAG_STRENGTH_STRONG = $20000000; + + // Secure connection error status flags + WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED = $00000001; + WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT = $00000002; + WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED = $00000004; + WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA = $00000008; + WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID = $00000010; + WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID = $00000020; + WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE = $00000040; + WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR = $80000000; + + WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 = $00000008; + WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 = $00000020; + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 = $00000080; + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 = $00000200; + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 = $00000800; + + // Note: SECURE_PROTOCOL_ALL does not include TLS1.1 and higher! + WINHTTP_FLAG_SECURE_PROTOCOL_ALL = WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 + or WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 + or WINHTTP_FLAG_SECURE_PROTOCOL_TLS1; + + // AutoProxy + WINHTTP_AUTOPROXY_AUTO_DETECT = $00000001; + WINHTTP_AUTOPROXY_CONFIG_URL = $00000002; + WINHTTP_AUTOPROXY_HOST_KEEPCASE = $00000004; + WINHTTP_AUTOPROXY_HOST_LOWERCASE = $00000008; + WINHTTP_AUTOPROXY_RUN_INPROCESS = $00010000; + WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY = $00020000; + + // Flags for dwAutoDetectFlags + WINHTTP_AUTO_DETECT_TYPE_DHCP = $00000001; + WINHTTP_AUTO_DETECT_TYPE_DNS_A = $00000002; + +const + WINHTTP_ERROR_BASE = 12000; + ERROR_WINHTTP_OUT_OF_HANDLES = WINHTTP_ERROR_BASE + 1; + ERROR_WINHTTP_TIMEOUT = WINHTTP_ERROR_BASE + 2; + ERROR_WINHTTP_INTERNAL_ERROR = WINHTTP_ERROR_BASE + 4; + ERROR_WINHTTP_INVALID_URL = WINHTTP_ERROR_BASE + 5; + ERROR_WINHTTP_UNRECOGNIZED_SCHEME = WINHTTP_ERROR_BASE + 6; + ERROR_WINHTTP_NAME_NOT_RESOLVED = WINHTTP_ERROR_BASE + 7; + ERROR_WINHTTP_INVALID_OPTION = WINHTTP_ERROR_BASE + 9; + ERROR_WINHTTP_OPTION_NOT_SETTABLE = WINHTTP_ERROR_BASE + 11; + ERROR_WINHTTP_SHUTDOWN = WINHTTP_ERROR_BASE + 12; + ERROR_WINHTTP_LOGIN_FAILURE = WINHTTP_ERROR_BASE + 15; + ERROR_WINHTTP_OPERATION_CANCELLED = WINHTTP_ERROR_BASE + 17; + ERROR_WINHTTP_INCORRECT_HANDLE_TYPE = WINHTTP_ERROR_BASE + 18; + ERROR_WINHTTP_INCORRECT_HANDLE_STATE = WINHTTP_ERROR_BASE + 19; + ERROR_WINHTTP_CANNOT_CONNECT = WINHTTP_ERROR_BASE + 29; + ERROR_WINHTTP_CONNECTION_ERROR = WINHTTP_ERROR_BASE + 30; + ERROR_WINHTTP_RESEND_REQUEST = WINHTTP_ERROR_BASE + 32; + ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED = WINHTTP_ERROR_BASE + 44; + ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN = WINHTTP_ERROR_BASE + 100; + ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND = WINHTTP_ERROR_BASE + 101; + ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND = WINHTTP_ERROR_BASE + 102; + ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN = WINHTTP_ERROR_BASE + 103; + ERROR_WINHTTP_HEADER_NOT_FOUND = WINHTTP_ERROR_BASE + 150; + ERROR_WINHTTP_INVALID_SERVER_RESPONSE = WINHTTP_ERROR_BASE + 152; + ERROR_WINHTTP_INVALID_HEADER = WINHTTP_ERROR_BASE + 153; + ERROR_WINHTTP_INVALID_QUERY_REQUEST = WINHTTP_ERROR_BASE + 154; + ERROR_WINHTTP_HEADER_ALREADY_EXISTS = WINHTTP_ERROR_BASE + 155; + ERROR_WINHTTP_REDIRECT_FAILED = WINHTTP_ERROR_BASE + 156; + ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR = WINHTTP_ERROR_BASE + 178; + ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT = WINHTTP_ERROR_BASE + 166; + ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT = WINHTTP_ERROR_BASE + 167; + ERROR_WINHTTP_NOT_INITIALIZED = WINHTTP_ERROR_BASE + 172; + ERROR_WINHTTP_SECURE_FAILURE = WINHTTP_ERROR_BASE + 175; + + // Certificate security errors. Additional information is provided + // via the WINHTTP_CALLBACK_STATUS_SECURE_FAILURE callback notification. + ERROR_WINHTTP_SECURE_CERT_DATE_INVALID = WINHTTP_ERROR_BASE + 37; + ERROR_WINHTTP_SECURE_CERT_CN_INVALID = WINHTTP_ERROR_BASE + 38; + ERROR_WINHTTP_SECURE_INVALID_CA = WINHTTP_ERROR_BASE + 45; + ERROR_WINHTTP_SECURE_CERT_REV_FAILED = WINHTTP_ERROR_BASE + 57; + ERROR_WINHTTP_SECURE_CHANNEL_ERROR = WINHTTP_ERROR_BASE + 157; + ERROR_WINHTTP_SECURE_INVALID_CERT = WINHTTP_ERROR_BASE + 169; + ERROR_WINHTTP_SECURE_CERT_REVOKED = WINHTTP_ERROR_BASE + 170; + ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE = WINHTTP_ERROR_BASE + 179; + + ERROR_WINHTTP_AUTODETECTION_FAILED = WINHTTP_ERROR_BASE + 180; + ERROR_WINHTTP_HEADER_COUNT_EXCEEDED = WINHTTP_ERROR_BASE + 181; + ERROR_WINHTTP_HEADER_SIZE_OVERFLOW = WINHTTP_ERROR_BASE + 182; + ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW = WINHTTP_ERROR_BASE + 183; + ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW = WINHTTP_ERROR_BASE + 184; + ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY = WINHTTP_ERROR_BASE + 185; + ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY = WINHTTP_ERROR_BASE + 186; + + WINHTTP_ERROR_LAST = WINHTTP_ERROR_BASE + 186; + + +const + WINHTTP_THRIFT_DEFAULTS = WINHTTP_FLAG_NULL_CODEPAGE + or WINHTTP_FLAG_BYPASS_PROXY_CACHE + or WINHTTP_FLAG_ESCAPE_DISABLE; + + + +type + IWinHTTPSession = interface; + IWinHTTPConnection = interface; + + IWinHTTPRequest = interface + ['{F65952F2-2F3B-47DC-B524-F1694E6D2AD7}'] + function Handle : HINTERNET; + function Connection : IWinHTTPConnection; + function AddRequestHeader( const aHeader : string; const addflag : DWORD = WINHTTP_ADDREQ_FLAG_ADD) : Boolean; + function SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean; + procedure TryAutoProxy( const aUrl : string); + procedure EnableAutomaticContentDecompression( const aEnable : Boolean); + function SendRequest( const pBuf : Pointer; const dwBytes : DWORD; const dwExtra : DWORD = 0) : Boolean; + function WriteExtraData( const pBuf : Pointer; const dwBytes : DWORD) : DWORD; + function FlushAndReceiveResponse : Boolean; + function ReadData( const dwRead : DWORD) : TBytes; overload; + function ReadData( const pBuf : Pointer; const dwRead : DWORD) : DWORD; overload; + end; + + IWinHTTPConnection = interface + ['{ED5BCA49-84D6-4CFE-BF18-3238B1FF2AFB}'] + function Handle : HINTERNET; + function Session : IWinHTTPSession; + function OpenRequest( const secure : Boolean; const aVerb, aObjName, aAcceptTypes : UnicodeString) : IWinHTTPRequest; + end; + + IWinHTTPSession = interface + ['{261ADCB7-5465-4407-8840-468C17F009F0}'] + function Handle : HINTERNET; + function Connect( const aHostName : UnicodeString; const aPort : INTERNET_PORT = INTERNET_DEFAULT_PORT) : IWinHTTPConnection; + function SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean; + function EnableSecureProtocols( const aFlagSet : DWORD) : Boolean; + end; + + IWinHTTPUrl = interface + ['{78BE977C-4171-4AF5-A250-FD2890205E63}'] + // url parts getter + function GetScheme : UnicodeString; + function GetNumScheme : INTERNET_SCHEME; + function GetHostName : UnicodeString; + function GetPort : INTERNET_PORT; + function GetUserName : UnicodeString; + function GetPassword : UnicodeString; + function GetUrlPath : UnicodeString; + function GetExtraInfo : UnicodeString; + + // url parts setter + procedure SetScheme( const value : UnicodeString); + procedure SetHostName ( const value : UnicodeString); + procedure SetPort( const value : INTERNET_PORT); + procedure SetUserName( const value : UnicodeString); + procedure SetPassword( const value : UnicodeString); + procedure SetUrlPath( const value : UnicodeString); + procedure SetExtraInfo( const value : UnicodeString); + + // url as a whole + function BuildUrl : UnicodeString; + procedure CrackUrl( const value : UnicodeString); + + // url parts + property Scheme : UnicodeString read GetScheme write SetScheme; + property NumScheme : INTERNET_SCHEME read GetNumScheme; // readonly + property HostName : UnicodeString read GetHostName write SetHostName; + property Port : INTERNET_PORT read GetPort write SetPort; + property UserName : UnicodeString read GetUserName write SetUserName; + property Password : UnicodeString read GetPassword write SetPassword; + property UrlPath : UnicodeString read GetUrlPath write SetUrlPath; + property ExtraInfo : UnicodeString read GetExtraInfo write SetExtraInfo; + + // url as a whole + property CompleteURL : UnicodeString read BuildUrl write CrackUrl; + end; + + + + +type + TWinHTTPHandleObjectImpl = class( TInterfacedObject) + strict protected + FHandle : HINTERNET; + function Handle : HINTERNET; + public + constructor Create( const aHandle : HINTERNET); + destructor Destroy; override; + end; + + + TWinHTTPSessionImpl = class( TWinHTTPHandleObjectImpl, IWinHTTPSession) + strict protected + + // IWinHTTPSession + function Connect( const aHostName : UnicodeString; const aPort : INTERNET_PORT = INTERNET_DEFAULT_PORT) : IWinHTTPConnection; + function SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean; + function EnableSecureProtocols( const aFlagSet : DWORD) : Boolean; + public + constructor Create( const aAgent : UnicodeString; + const aAccessType : DWORD = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY; + const aProxy : UnicodeString = ''; + const aProxyBypass : UnicodeString = ''; + const aFlags : DWORD = 0); + destructor Destroy; override; + end; + + + TWinHTTPConnectionImpl = class( TWinHTTPHandleObjectImpl, IWinHTTPConnection) + strict protected + FSession : IWinHTTPSession; + + // IWinHTTPConnection + function OpenRequest( const secure : Boolean; const aVerb, aObjName, aAcceptTypes : UnicodeString) : IWinHTTPRequest; + function Session : IWinHTTPSession; + + public + constructor Create( const aSession : IWinHTTPSession; const aHostName : UnicodeString; const aPort : INTERNET_PORT); + destructor Destroy; override; + end; + + + TAcceptTypesArray = array of string; + + TWinHTTPRequestImpl = class( TWinHTTPHandleObjectImpl, IWinHTTPRequest) + strict protected + FConnection : IWinHTTPConnection; + + // IWinHTTPRequest + function Connection : IWinHTTPConnection; + function AddRequestHeader( const aHeader : string; const addflag : DWORD = WINHTTP_ADDREQ_FLAG_ADD) : Boolean; + function SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean; + procedure TryAutoProxy( const aUrl : string); + procedure EnableAutomaticContentDecompression( const aEnable : Boolean); + function SendRequest( const pBuf : Pointer; const dwBytes : DWORD; const dwExtra : DWORD = 0) : Boolean; + function WriteExtraData( const pBuf : Pointer; const dwBytes : DWORD) : DWORD; + function FlushAndReceiveResponse : Boolean; + function ReadData( const dwRead : DWORD) : TBytes; overload; + function ReadData( const pBuf : Pointer; const dwRead : DWORD) : DWORD; overload; + + public + constructor Create( const aConnection : IWinHTTPConnection; + const aVerb, aObjName : UnicodeString; + const aVersion : UnicodeString = ''; + const aReferrer : UnicodeString = ''; + const aAcceptTypes : UnicodeString = '*/*'; + const aFlags : DWORD = WINHTTP_THRIFT_DEFAULTS + ); + + destructor Destroy; override; + end; + + + TWinHTTPUrlImpl = class( TInterfacedObject, IWinHTTPUrl) + strict private + FScheme : UnicodeString; + FNumScheme : INTERNET_SCHEME; + FHostName : UnicodeString; + FPort : INTERNET_PORT; + FUserName : UnicodeString; + FPassword : UnicodeString; + FUrlPath : UnicodeString; + FExtraInfo : UnicodeString; + + strict protected + // url parts getter + function GetScheme : UnicodeString; + function GetNumScheme : INTERNET_SCHEME; + function GetHostName : UnicodeString; + function GetPort : INTERNET_PORT; + function GetUserName : UnicodeString; + function GetPassword : UnicodeString; + function GetUrlPath : UnicodeString; + function GetExtraInfo : UnicodeString; + + // url parts setter + procedure SetScheme( const value : UnicodeString); + procedure SetHostName ( const value : UnicodeString); + procedure SetPort( const value : INTERNET_PORT); + procedure SetUserName( const value : UnicodeString); + procedure SetPassword( const value : UnicodeString); + procedure SetUrlPath( const value : UnicodeString); + procedure SetExtraInfo( const value : UnicodeString); + + // url as a whole + function BuildUrl : UnicodeString; + procedure CrackUrl( const value : UnicodeString); + + public + constructor Create( const aUri : UnicodeString); + destructor Destroy; override; + end; + + + WINHTTP_PROXY_INFO_Helper = record helper for WINHTTP_PROXY_INFO + procedure Initialize; + procedure FreeAllocatedResources; + end; + + + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG_Helper = record helper for WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + procedure Initialize; + procedure FreeAllocatedResources; + end; + + + EWinHTTPException = class(Exception); + +{ helper functions } + +function WinHttpSysErrorMessage( const error : Cardinal): string; +procedure RaiseLastWinHttpError; + + +implementation + +const WINHTTP_DLL = 'WinHTTP.dll'; + +function WinHttpCloseHandle; stdcall; external WINHTTP_DLL; +function WinHttpOpen; stdcall; external WINHTTP_DLL; +function WinHttpConnect; stdcall; external WINHTTP_DLL; +function WinHttpOpenRequest; stdcall; external WINHTTP_DLL; +function WinHttpSendRequest; stdcall; external WINHTTP_DLL; +function WinHttpSetTimeouts; stdcall; external WINHTTP_DLL; +function WinHttpQueryOption; stdcall; external WINHTTP_DLL; +function WinHttpSetOption; stdcall; external WINHTTP_DLL; +function WinHttpAddRequestHeaders; stdcall; external WINHTTP_DLL; +function WinHttpGetProxyForUrl; stdcall; external WINHTTP_DLL; +function WinHttpGetIEProxyConfigForCurrentUser; stdcall; external WINHTTP_DLL; +function WinHttpWriteData; stdcall; external WINHTTP_DLL; +function WinHttpReceiveResponse; stdcall; external WINHTTP_DLL; +function WinHttpQueryHeaders; stdcall; external WINHTTP_DLL; +function WinHttpQueryDataAvailable; stdcall; external WINHTTP_DLL; +function WinHttpReadData; stdcall; external WINHTTP_DLL; +function WinHttpCrackUrl; stdcall; external WINHTTP_DLL; +function WinHttpCreateUrl; stdcall; external WINHTTP_DLL; + + +{ helper functions } + +function WinHttpSysErrorMessage( const error : Cardinal): string; +const FLAGS = FORMAT_MESSAGE_ALLOCATE_BUFFER + or FORMAT_MESSAGE_IGNORE_INSERTS + or FORMAT_MESSAGE_FROM_SYSTEM + or FORMAT_MESSAGE_FROM_HMODULE; +var pBuffer : PChar; + nChars : Cardinal; +begin + if (error < WINHTTP_ERROR_BASE) + or (error > WINHTTP_ERROR_LAST) + then Exit( SysUtils.SysErrorMessage( error)); + + pBuffer := nil; + try + nChars := FormatMessage( FLAGS, + Pointer( GetModuleHandle( WINHTTP_DLL)), + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language + @pBuffer, 0, + nil); + SetString( result, pBuffer, nChars); + finally + LocalFree( NativeUInt( pBuffer)); + end; +end; + + +procedure RaiseLastWinHttpError; +var error : Cardinal; + sMsg : string; +begin + error := Cardinal( GetLastError); + if error <> NOERROR then begin + sMSg := IntToStr(Integer(error))+' '+WinHttpSysErrorMessage(error); + raise EWinHTTPException.Create( sMsg); + end; +end; + + + +{ misc. record helper } + + +procedure GlobalFreeAndNil( var p : LPWSTR); +begin + if p <> nil then begin + GlobalFree( HGLOBAL( p)); + p := nil; + end; +end; + + +procedure WINHTTP_PROXY_INFO_Helper.Initialize; +begin + FillChar( Self, SizeOf(Self), 0); +end; + + +procedure WINHTTP_PROXY_INFO_Helper.FreeAllocatedResources; +// The caller must free the lpszProxy and lpszProxyBypass strings +// if they are non-NULL. Use GlobalFree to free the strings. +begin + GlobalFreeAndNil( lpszProxy); + GlobalFreeAndNil( lpszProxyBypass); + Initialize; +end; + + +procedure WINHTTP_CURRENT_USER_IE_PROXY_CONFIG_Helper.Initialize; +begin + FillChar( Self, SizeOf(Self), 0); +end; + + +procedure WINHTTP_CURRENT_USER_IE_PROXY_CONFIG_Helper.FreeAllocatedResources; +// The caller must free the lpszProxy, lpszProxyBypass and lpszAutoConfigUrl strings +// if they are non-NULL. Use GlobalFree to free the strings. +begin + GlobalFreeAndNil( lpszProxy); + GlobalFreeAndNil( lpszProxyBypass); + GlobalFreeAndNil( lpszAutoConfigUrl); + Initialize; +end; + + +{ TWinHTTPHandleObjectImpl } + +constructor TWinHTTPHandleObjectImpl.Create( const aHandle : HINTERNET); +begin + inherited Create; + FHandle := aHandle; + + if FHandle = nil + then raise EWinHTTPException.Create('Invalid handle'); +end; + + +destructor TWinHTTPHandleObjectImpl.Destroy; +begin + try + if Assigned(FHandle) then begin + WinHttpCloseHandle(FHandle); + FHandle := nil; + end; + + finally + inherited Destroy; + end; +end; + + +function TWinHTTPHandleObjectImpl.Handle : HINTERNET; +begin + result := FHandle; +end; + + +{ TWinHTTPSessionImpl } + + +constructor TWinHTTPSessionImpl.Create( const aAgent : UnicodeString; const aAccessType : DWORD; + const aProxy, aProxyBypass : UnicodeString; const aFlags : DWORD); +var handle : HINTERNET; +begin + handle := WinHttpOpen( PWideChar(aAgent), aAccessType, + PWideChar(Pointer(aProxy)), // may be nil + PWideChar(Pointer(aProxyBypass)), // may be nil + aFlags); + if handle = nil then RaiseLastWinHttpError; + inherited Create( handle); +end; + + +destructor TWinHTTPSessionImpl.Destroy; +begin + inherited Destroy; + // add code here +end; + + +function TWinHTTPSessionImpl.Connect( const aHostName : UnicodeString; const aPort : INTERNET_PORT) : IWinHTTPConnection; +begin + result := TWinHTTPConnectionImpl.Create( Self, aHostName, aPort); +end; + + +function TWinHTTPSessionImpl.SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean; +begin + result := WinHttpSetTimeouts( FHandle, aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout); +end; + + +function TWinHTTPSessionImpl.EnableSecureProtocols( const aFlagSet : DWORD) : Boolean; +var dwSize : DWORD; +begin + dwSize := SizeOf(aFlagSet); + result := WinHttpSetOption( Handle, WINHTTP_OPTION_SECURE_PROTOCOLS, @aFlagset, dwSize); +end; + + +{ TWinHTTPConnectionImpl } + +constructor TWinHTTPConnectionImpl.Create( const aSession : IWinHTTPSession; const aHostName : UnicodeString; const aPort : INTERNET_PORT); +var handle : HINTERNET; +begin + FSession := aSession; + handle := WinHttpConnect( FSession.Handle, PWideChar(aHostName), aPort, 0); + if handle = nil then RaiseLastWinHttpError; + inherited Create( handle); +end; + + +destructor TWinHTTPConnectionImpl.Destroy; +begin + inherited Destroy; + FSession := nil; +end; + + +function TWinHTTPConnectionImpl.Session : IWinHTTPSession; +begin + result := FSession; +end; + + +function TWinHTTPConnectionImpl.OpenRequest( const secure : Boolean; const aVerb, aObjName, aAcceptTypes : UnicodeString) : IWinHTTPRequest; +var dwFlags : DWORD; +begin + dwFlags := WINHTTP_THRIFT_DEFAULTS; + if secure + then dwFlags := dwFlags or WINHTTP_FLAG_SECURE + else dwFlags := dwFlags and not WINHTTP_FLAG_SECURE; + + result := TWinHTTPRequestImpl.Create( Self, aVerb, aObjName, '', '', aAcceptTypes, dwFlags); +end; + + +{ TWinHTTPRequestImpl } + +constructor TWinHTTPRequestImpl.Create( const aConnection : IWinHTTPConnection; + const aVerb, aObjName, aVersion, aReferrer : UnicodeString; + const aAcceptTypes : UnicodeString; + const aFlags : DWORD + ); +var handle : HINTERNET; + accept : array[0..1] of PWideChar; +begin + FConnection := aConnection; + + accept[0] := PWideChar(aAcceptTypes); + accept[1] := nil; + + handle := WinHttpOpenRequest( FConnection.Handle, + PWideChar(UpperCase(aVerb)), + PWideChar(aObjName), + PWideChar(aVersion), + PWideChar(aReferrer), + @accept, + aFlags); + if handle = nil then RaiseLastWinHttpError; + inherited Create( handle); +end; + + +destructor TWinHTTPRequestImpl.Destroy; +begin + inherited Destroy; + FConnection := nil; +end; + + +function TWinHTTPRequestImpl.Connection : IWinHTTPConnection; +begin + result := FConnection; +end; + + +function TWinHTTPRequestImpl.SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean; +begin + result := WinHttpSetTimeouts( FHandle, aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout); +end; + + +function TWinHTTPRequestImpl.AddRequestHeader( const aHeader : string; const addflag : DWORD) : Boolean; +begin + result := WinHttpAddRequestHeaders( FHandle, PWideChar(aHeader), DWORD(-1), addflag); +end; + + +procedure TWinHTTPRequestImpl.TryAutoProxy( const aUrl : string); +// From MSDN: +// AutoProxy support is not fully integrated into the HTTP stack in WinHTTP. +// Before sending a request, the application must call WinHttpGetProxyForUrl +// to obtain the name of a proxy server and then call WinHttpSetOption using +// WINHTTP_OPTION_PROXY to set the proxy configuration on the WinHTTP request +// handle created by WinHttpOpenRequest. +// See https://docs.microsoft.com/en-us/windows/desktop/winhttp/winhttp-autoproxy-api +var + options : WINHTTP_AUTOPROXY_OPTIONS; + proxy : WINHTTP_PROXY_INFO; + ieProxy : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG; + dwSize : DWORD; +begin + // try AutoProxy via PAC first + proxy.Initialize; + try + FillChar( options, SizeOf(options), 0); + options.dwFlags := WINHTTP_AUTOPROXY_AUTO_DETECT; + options.dwAutoDetectFlags := WINHTTP_AUTO_DETECT_TYPE_DHCP or WINHTTP_AUTO_DETECT_TYPE_DNS_A; + options.fAutoLogonIfChallenged := TRUE; + if WinHttpGetProxyForUrl( FConnection.Session.Handle, PChar(aUrl), options, proxy) then begin + dwSize := SizeOf(proxy); + WinHttpSetOption( Handle, WINHTTP_OPTION_PROXY, @proxy, dwSize); + Exit; + end; + + finally + proxy.FreeAllocatedResources; + end; + + // Use IE settings as a fallback, useful in client (i.e. non-server) environments + ieProxy.Initialize; + try + if WinHttpGetIEProxyConfigForCurrentUser( ieProxy) + then begin + + // lpszAutoConfigUrl = "Use automatic proxy configuration" + if ieProxy.lpszAutoConfigUrl <> nil then begin + options.lpszAutoConfigUrl := ieProxy.lpszAutoConfigUrl; + options.dwFlags := options.dwFlags or WINHTTP_AUTOPROXY_CONFIG_URL; + + proxy.Initialize; + try + if WinHttpGetProxyForUrl( FConnection.Session.Handle, PChar(aUrl), options, proxy) then begin + dwSize := SizeOf(proxy); + WinHttpSetOption( Handle, WINHTTP_OPTION_PROXY, @proxy, dwSize); + Exit; + end; + finally + proxy.FreeAllocatedResources; + end; + end; + + // lpszProxy = "use a proxy server" + if ieProxy.lpszProxy <> nil then begin + proxy.Initialize; + try + proxy.dwAccessType := WINHTTP_ACCESS_TYPE_NAMED_PROXY; + proxy.lpszProxy := ieProxy.lpszProxy; + proxy.lpszProxyBypass := ieProxy.lpszProxyBypass; + dwSize := SizeOf(proxy); + WinHttpSetOption( Handle, WINHTTP_OPTION_PROXY, @proxy, dwSize); + Exit; + finally + proxy.Initialize; // not FreeAllocatedResources, we only hold pointer copies! + end; + end; + + end; + + finally + ieProxy.FreeAllocatedResources; + end; +end; + + +procedure TWinHTTPRequestImpl.EnableAutomaticContentDecompression( const aEnable : Boolean); +// Enable automatic gzip,deflate decompression on systems that support this option +// From the docs: WinHTTP will automatically set an appropriate Accept-Encoding header, +// overriding any value supplied by the caller -> we don't have to do this +// Available on Win 8.1 or higher +var value : DWORD; +begin + if aEnable + then value := WINHTTP_DECOMPRESSION_FLAG_ALL + else value := 0; + + // ignore returned value, the option is not supported with older WinHTTP versions + WinHttpSetOption( Handle, WINHTTP_OPTION_DECOMPRESSION, @value, SizeOf(DWORD)); +end; + + +function TWinHTTPRequestImpl.SendRequest( const pBuf : Pointer; const dwBytes, dwExtra : DWORD) : Boolean; +begin + result := WinHttpSendRequest( FHandle, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + pBuf, dwBytes, // number of bytes in pBuf + dwBytes + dwExtra, // becomes the Content-Length + nil); // context for async operations +end; + + +function TWinHTTPRequestImpl.WriteExtraData( const pBuf : Pointer; const dwBytes : DWORD) : DWORD; +begin + if not WinHttpWriteData( FHandle, pBuf, dwBytes, result) + then result := 0; +end; + + +function TWinHTTPRequestImpl.FlushAndReceiveResponse : Boolean; +begin + result := WinHttpReceiveResponse( FHandle, nil); +end; + + +function TWinHTTPRequestImpl.ReadData( const dwRead : DWORD) : TBytes; +var dwAvailable, dwReceived : DWORD; +begin + if WinHttpQueryDataAvailable( FHandle, dwAvailable) + then dwAvailable := Min( dwRead, dwAvailable) + else dwAvailable := 0; + + SetLength( result, dwAvailable); + if dwAvailable = 0 then Exit; + + if WinHttpReadData( FHandle, @result[0], Length(result), dwReceived) + then SetLength( result, dwReceived) + else SetLength( result, 0); +end; + + +function TWinHTTPRequestImpl.ReadData( const pBuf : Pointer; const dwRead : DWORD) : DWORD; +var dwAvailable : DWORD; +begin + if WinHttpQueryDataAvailable( FHandle, dwAvailable) + then dwAvailable := Min( dwRead, dwAvailable) + else dwAvailable := 0; + + if (dwAvailable = 0) + or not WinHttpReadData( FHandle, pBuf, dwAvailable, result) + then result := 0; +end; + + +{ TWinHTTPUrlImpl } + +constructor TWinHTTPUrlImpl.Create(const aUri: UnicodeString); +begin + inherited Create; + CrackUrl( aUri) +end; + + +destructor TWinHTTPUrlImpl.Destroy; +begin + inherited Destroy; +end; + + +procedure TWinHTTPUrlImpl.CrackURL( const value : UnicodeString); +const FLAGS = 0; // no special operations, leave components as-is +var components : URL_COMPONENTS; +begin + FillChar(components, SizeOf(components), 0); + components.dwStructSize := SizeOf(components); + + if value <> '' then begin + { For the WinHttpCrackUrl function, [...] if the pointer member is NULL but the + length member is not zero, both the pointer and length members are returned. } + components.dwSchemeLength := DWORD(-1); + components.dwHostNameLength := DWORD(-1); + components.dwUserNameLength := DWORD(-1); + components.dwPasswordLength := DWORD(-1); + components.dwUrlPathLength := DWORD(-1); + components.dwExtraInfoLength := DWORD(-1); + + WinHttpCrackUrl( PWideChar(value), Length(value), FLAGS, components); + end; + + FNumScheme := components.nScheme; + FPort := components.nPort; + SetString( FScheme, components.lpszScheme, components.dwSchemeLength); + SetString( FHostName, components.lpszHostName, components.dwHostNameLength); + SetString( FUserName, components.lpszUserName, components.dwUserNameLength); + SetString( FPassword, components.lpszPassword, components.dwPasswordLength); + SetString( FUrlPath, components.lpszUrlPath, components.dwUrlPathLength); + SetString( FExtraInfo, components.lpszExtraInfo, components.dwExtraInfoLength); +end; + + +function TWinHTTPUrlImpl.BuildUrl : UnicodeString; +const FLAGS = 0; // no special operations, leave components as-is +var components : URL_COMPONENTS; + dwChars : DWORD; +begin + FillChar(components, SizeOf(components), 0); + components.dwStructSize := SizeOf(components); + components.lpszScheme := PWideChar(FScheme); + components.dwSchemeLength := Length(FScheme); + components.lpszHostName := PWideChar(FHostName); + components.dwHostNameLength := Length(FHostName); + components.nPort := FPort; + components.lpszUserName := PWideChar(FUserName); + components.dwUserNameLength := Length(FUserName); + components.lpszPassword := PWideChar(FPassword); + components.dwPasswordLength := Length(FPassword); + components.lpszUrlPath := PWideChar(FUrlPath); + components.dwUrlPathLength := Length(FUrlPath); + components.lpszExtraInfo := PWideChar(FExtraInfo); + components.dwExtraInfoLength := Length(FExtraInfo); + + WinHttpCreateUrl( components, FLAGS, nil, dwChars); + if dwChars = 0 + then result := '' + else begin + SetLength( result, dwChars + 1); + WinHttpCreateUrl( components, FLAGS, @result[1], dwChars); + SetLength( result, dwChars); // cut off terminating #0 + end; +end; + + +function TWinHTTPUrlImpl.GetExtraInfo: UnicodeString; +begin + result := FExtraInfo; +end; + +function TWinHTTPUrlImpl.GetHostName: UnicodeString; +begin + result := FHostName; +end; + +function TWinHTTPUrlImpl.GetNumScheme: INTERNET_SCHEME; +begin + result := FNumScheme; +end; + +function TWinHTTPUrlImpl.GetPassword: UnicodeString; +begin + result := FPassword; +end; + +function TWinHTTPUrlImpl.GetPort: INTERNET_PORT; +begin + result := FPort; +end; + +function TWinHTTPUrlImpl.GetScheme: UnicodeString; +begin + result := FScheme; +end; + +function TWinHTTPUrlImpl.GetUrlPath: UnicodeString; +begin + result := FUrlPath; +end; + +function TWinHTTPUrlImpl.GetUserName: UnicodeString; +begin + result := FUserName; +end; + +procedure TWinHTTPUrlImpl.SetExtraInfo(const value: UnicodeString); +begin + FExtraInfo := value; +end; + +procedure TWinHTTPUrlImpl.SetHostName(const value: UnicodeString); +begin + FHostName := value; +end; + +procedure TWinHTTPUrlImpl.SetPassword(const value: UnicodeString); +begin + FPassword := value; +end; + +procedure TWinHTTPUrlImpl.SetPort(const value: INTERNET_PORT); +begin + FPort := value; +end; + +procedure TWinHTTPUrlImpl.SetScheme(const value: UnicodeString); +begin + FScheme := value; +end; + +procedure TWinHTTPUrlImpl.SetUrlPath(const value: UnicodeString); +begin + FUrlPath := value; +end; + +procedure TWinHTTPUrlImpl.SetUserName(const value: UnicodeString); +begin + FUserName := value; +end; + + +initialization + OutputDebugString( PChar( SysErrorMessage( 12002))); + +end. + + diff --git a/src/jaegertracing/thrift/lib/delphi/src/Thrift.pas b/src/jaegertracing/thrift/lib/delphi/src/Thrift.pas new file mode 100644 index 000000000..2ee83441b --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/src/Thrift.pas @@ -0,0 +1,239 @@ +(* + * 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. + *) + +unit Thrift; + +interface + +uses + SysUtils, + Thrift.Exception, + Thrift.Protocol; + +const + Version = '0.13.0'; + +type + TException = Thrift.Exception.TException; // compatibility alias + + TApplicationExceptionSpecializedClass = class of TApplicationExceptionSpecialized; + + TApplicationException = class( TException) + public + type +{$SCOPEDENUMS ON} + TExceptionType = ( + Unknown, + UnknownMethod, + InvalidMessageType, + WrongMethodName, + BadSequenceID, + MissingResult, + InternalError, + ProtocolError, + InvalidTransform, + InvalidProtocol, + UnsupportedClientType + ); +{$SCOPEDENUMS OFF} + private + function GetType: TExceptionType; + protected + constructor HiddenCreate(const Msg: string); + public + // purposefully hide inherited constructor + class function Create(const Msg: string): TApplicationException; overload; deprecated 'Use specialized TApplicationException types (or regenerate from IDL)'; + class function Create: TApplicationException; overload; deprecated 'Use specialized TApplicationException types (or regenerate from IDL)'; + 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)'; + + class function GetSpecializedExceptionType(AType: TExceptionType): TApplicationExceptionSpecializedClass; + + class function Read( const iprot: IProtocol): TApplicationException; + procedure Write( const oprot: IProtocol ); + end; + + // Needed to remove deprecation warning + TApplicationExceptionSpecialized = class abstract (TApplicationException) + public + constructor Create(const Msg: string); + end; + + TApplicationExceptionUnknown = class (TApplicationExceptionSpecialized); + TApplicationExceptionUnknownMethod = class (TApplicationExceptionSpecialized); + TApplicationExceptionInvalidMessageType = class (TApplicationExceptionSpecialized); + TApplicationExceptionWrongMethodName = class (TApplicationExceptionSpecialized); + TApplicationExceptionBadSequenceID = class (TApplicationExceptionSpecialized); + TApplicationExceptionMissingResult = class (TApplicationExceptionSpecialized); + TApplicationExceptionInternalError = class (TApplicationExceptionSpecialized); + TApplicationExceptionProtocolError = class (TApplicationExceptionSpecialized); + TApplicationExceptionInvalidTransform = class (TApplicationExceptionSpecialized); + TApplicationExceptionInvalidProtocol = class (TApplicationExceptionSpecialized); + TApplicationExceptionUnsupportedClientType = class (TApplicationExceptionSpecialized); + + +implementation + +{ TApplicationException } + +function TApplicationException.GetType: TExceptionType; +begin + if Self is TApplicationExceptionUnknownMethod then Result := TExceptionType.UnknownMethod + else if Self is TApplicationExceptionInvalidMessageType then Result := TExceptionType.InvalidMessageType + else if Self is TApplicationExceptionWrongMethodName then Result := TExceptionType.WrongMethodName + else if Self is TApplicationExceptionBadSequenceID then Result := TExceptionType.BadSequenceID + else if Self is TApplicationExceptionMissingResult then Result := TExceptionType.MissingResult + else if Self is TApplicationExceptionInternalError then Result := TExceptionType.InternalError + else if Self is TApplicationExceptionProtocolError then Result := TExceptionType.ProtocolError + else if Self is TApplicationExceptionInvalidTransform then Result := TExceptionType.InvalidTransform + else if Self is TApplicationExceptionInvalidProtocol then Result := TExceptionType.InvalidProtocol + else if Self is TApplicationExceptionUnsupportedClientType then Result := TExceptionType.UnsupportedClientType + else Result := TExceptionType.Unknown; +end; + +constructor TApplicationException.HiddenCreate(const Msg: string); +begin + inherited Create(Msg); +end; + +class function TApplicationException.Create(const Msg: string): TApplicationException; +begin + Result := TApplicationExceptionUnknown.Create(Msg); +end; + +class function TApplicationException.Create: TApplicationException; +begin + Result := TApplicationExceptionUnknown.Create(''); +end; + +class function TApplicationException.Create( AType: TExceptionType): TApplicationException; +begin +{$WARN SYMBOL_DEPRECATED OFF} + Result := Create(AType, ''); +{$WARN SYMBOL_DEPRECATED DEFAULT} +end; + +class function TApplicationException.Create( AType: TExceptionType; const msg: string): TApplicationException; +begin + Result := GetSpecializedExceptionType(AType).Create(msg); +end; + +class function TApplicationException.GetSpecializedExceptionType(AType: TExceptionType): TApplicationExceptionSpecializedClass; +begin + case AType of + TExceptionType.UnknownMethod: Result := TApplicationExceptionUnknownMethod; + TExceptionType.InvalidMessageType: Result := TApplicationExceptionInvalidMessageType; + TExceptionType.WrongMethodName: Result := TApplicationExceptionWrongMethodName; + TExceptionType.BadSequenceID: Result := TApplicationExceptionBadSequenceID; + TExceptionType.MissingResult: Result := TApplicationExceptionMissingResult; + TExceptionType.InternalError: Result := TApplicationExceptionInternalError; + TExceptionType.ProtocolError: Result := TApplicationExceptionProtocolError; + TExceptionType.InvalidTransform: Result := TApplicationExceptionInvalidTransform; + TExceptionType.InvalidProtocol: Result := TApplicationExceptionInvalidProtocol; + TExceptionType.UnsupportedClientType: Result := TApplicationExceptionUnsupportedClientType; + else + Result := TApplicationExceptionUnknown; + end; +end; + +class function TApplicationException.Read( const iprot: IProtocol): TApplicationException; +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 + Break; + end; + + case field.Id of + 1 : begin + if ( field.Type_ = TType.String_) then + begin + msg := 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 + TProtocolUtil.Skip( iprot, field.Type_ ); + end; + end else + begin + TProtocolUtil.Skip( iprot, field.Type_); + end; + end; + iprot.ReadFieldEnd; + end; + iprot.ReadStructEnd; + Result := GetSpecializedExceptionType(typ).Create(msg); +end; + +procedure TApplicationException.Write( const oprot: IProtocol); +var + struc : TThriftStruct; + field : TThriftField; +begin + Init(struc, 'TApplicationException'); + Init(field); + + oprot.WriteStructBegin( struc ); + if Message <> '' then + begin + field.Name := 'message'; + field.Type_ := TType.String_; + field.Id := 1; + oprot.WriteFieldBegin( field ); + oprot.WriteString( Message ); + oprot.WriteFieldEnd; + end; + + field.Name := 'type'; + field.Type_ := TType.I32; + field.Id := 2; + oprot.WriteFieldBegin(field); + oprot.WriteI32(Integer(GetType)); + oprot.WriteFieldEnd(); + oprot.WriteFieldStop(); + oprot.WriteStructEnd(); +end; + +{ TApplicationExceptionSpecialized } + +constructor TApplicationExceptionSpecialized.Create(const Msg: string); +begin + inherited HiddenCreate(Msg); +end; + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/test/ConsoleHelper.pas b/src/jaegertracing/thrift/lib/delphi/test/ConsoleHelper.pas new file mode 100644 index 000000000..0a8ddcf10 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/ConsoleHelper.pas @@ -0,0 +1,132 @@ +(* + * 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. + *) + +unit ConsoleHelper; + +interface + +uses Classes; + +type + TThriftConsole = class + public + procedure Write( const S: string); virtual; + procedure WriteLine( const S: string); virtual; + end; + + TGUIConsole = class( TThriftConsole ) + private + FLineBreak : Boolean; + FMemo : TStrings; + + procedure InternalWrite( const S: string; bWriteLine: Boolean); + public + procedure Write( const S: string); override; + procedure WriteLine( const S: string); override; + constructor Create( AMemo: TStrings); + end; + +function Console: TThriftConsole; +procedure ChangeConsole( AConsole: TThriftConsole ); +procedure RestoreConsoleToDefault; + +implementation + +var + FDefaultConsole : TThriftConsole; + FConsole : TThriftConsole; + +function Console: TThriftConsole; +begin + Result := FConsole; +end; + +{ TThriftConsole } + +procedure TThriftConsole.Write(const S: string); +begin + System.Write( S ); +end; + +procedure TThriftConsole.WriteLine(const S: string); +begin + System.Writeln( S ); +end; + +procedure ChangeConsole( AConsole: TThriftConsole ); +begin + FConsole := AConsole; +end; + +procedure RestoreConsoleToDefault; +begin + FConsole := FDefaultConsole; +end; + +{ TGUIConsole } + +constructor TGUIConsole.Create( AMemo: TStrings); +begin + inherited Create; + FMemo := AMemo; + FLineBreak := True; +end; + +procedure TGUIConsole.InternalWrite(const S: string; bWriteLine: Boolean); +var + idx : Integer; +begin + if FLineBreak then + begin + FMemo.Add( S ); + end else + begin + idx := FMemo.Count - 1; + if idx < 0 then + FMemo.Add( S ) + else + FMemo[idx] := FMemo[idx] + S; + end; + FLineBreak := bWriteLine; +end; + +procedure TGUIConsole.Write(const S: string); +begin + InternalWrite( S, False); +end; + +procedure TGUIConsole.WriteLine(const S: string); +begin + InternalWrite( S, True); +end; + +initialization +begin + FDefaultConsole := TThriftConsole.Create; + FConsole := FDefaultConsole; +end; + +finalization +begin + FDefaultConsole.Free; +end; + +end. + + diff --git a/src/jaegertracing/thrift/lib/delphi/test/Performance/DataFactory.pas b/src/jaegertracing/thrift/lib/delphi/test/Performance/DataFactory.pas new file mode 100644 index 000000000..e131822a3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/Performance/DataFactory.pas @@ -0,0 +1,176 @@ +// 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. +unit DataFactory; + +interface + +uses + SysUtils, + Thrift.Collections, + Thrift.Test; + +type + TestDataFactory = class + strict protected + class function CreateSetField(const count : Integer) : IHashSet< IInsanity>; static; + class function CreateInsanity(const count : Integer) : IInsanity; static; + class function CreateBytesArray(const count : Integer) : TBytes; static; + class function CreateXtructs(const count : Integer) : IThriftList< IXtruct>; static; + class function CreateXtruct(const count : Integer) : IXtruct; static; + class function CreateListField(const count : Integer) : IThriftList< IThriftDictionary< IHashSet< Integer>, IThriftDictionary< Integer, IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>>>>; static; + class function CreateUserMap(const count : Integer) : IThriftDictionary< TNumberz, Int64>; static; + class function CreateListFieldData(const count : Integer) : IThriftDictionary< IHashSet< Integer>, IThriftDictionary< Integer, IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>>>; static; + class function CreateIntHashSet(const count : Integer) : IHashSet< Integer>; static; + class function CreateListFieldDataDict(const count : Integer) : IThriftDictionary< Integer, IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>>; static; + class function CreateListFieldDataDictValue(const count : Integer) : IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>; static; + class function CreateListFieldDataDictValueList(const count : Integer) : IThriftList< IThriftDictionary< IInsanity, string>>; static; + class function CreateListFieldDataDictValueListDict(const count : Integer) : IThriftDictionary< IInsanity, string>; static; + public + class function CreateCrazyNesting(const count : Integer = 10) : ICrazyNesting; static; + end; + +implementation + + +class function TestDataFactory.CreateCrazyNesting(const count : Integer = 10) : ICrazyNesting; +begin + if (count <= 0) + then Exit(nil); + + result := TCrazyNestingImpl.Create; + result.Binary_field := CreateBytesArray(count); + result.List_field := CreateListField(count); + result.Set_field := CreateSetField(count); + result.String_field := Format('data level %d', [count]); +end; + +class function TestDataFactory.CreateSetField(const count : Integer) : IHashSet< IInsanity>; +var i : Integer; +begin + result := THashSetImpl< IInsanity>.Create; + for i := 0 to count-1 do begin + result.Add(CreateInsanity(count)); + end; +end; + +class function TestDataFactory.CreateInsanity(const count : Integer) : IInsanity; +begin + result := TInsanityImpl.Create; + result.UserMap := CreateUserMap(count); + result.Xtructs := CreateXtructs(count); +end; + +class function TestDataFactory.CreateXtructs(const count : Integer) : IThriftList< IXtruct>; +var i : Integer; +begin + result := TThriftListImpl< IXtruct>.Create; + for i := 0 to count-1 do begin + result.Add(CreateXtruct(count)); + end; +end; + +class function TestDataFactory.CreateXtruct(const count : Integer) : IXtruct; +begin + result := TXtructImpl.Create; + result.Byte_thing := SmallInt(count mod 128); + result.I32_thing := count; + result.I64_thing := count; + result.String_thing := Format('data level %d', [count]); +end; + +class function TestDataFactory.CreateUserMap(const count : Integer) : IThriftDictionary< TNumberz, Int64>; +begin + result := TThriftDictionaryImpl< TNumberz, Int64>.Create; + result.Add(TNumberz.ONE, count); + result.Add(TNumberz.TWO, count); + result.Add(TNumberz.THREE, count); + result.Add(TNumberz.FIVE, count); + result.Add(TNumberz.SIX, count); + result.Add(TNumberz.EIGHT, count); +end; + +class function TestDataFactory.CreateListField(const count : Integer) : IThriftList< IThriftDictionary< IHashSet< Integer>, IThriftDictionary< Integer, IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>>>>; +var i : Integer; +begin + result := TThriftListImpl< IThriftDictionary< IHashSet< Integer>, IThriftDictionary< Integer, IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>>>>.Create; + for i := 0 to count-1 do begin + result.Add(CreateListFieldData(count)); + end; +end; + +class function TestDataFactory.CreateListFieldData(const count : Integer) : IThriftDictionary< IHashSet< Integer>, IThriftDictionary< Integer, IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>>>; +var i : Integer; +begin + result := TThriftDictionaryImpl< IHashSet< Integer>, IThriftDictionary< Integer, IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>>>.Create; + for i := 0 to count-1 do begin + result.Add( CreateIntHashSet(count), CreateListFieldDataDict(count)); + end; +end; + +class function TestDataFactory.CreateIntHashSet(const count : Integer) : IHashSet< Integer>; +var i : Integer; +begin + result := THashSetImpl< Integer>.Create; + for i := 0 to count-1 do begin + result.Add(i); + end; +end; + +class function TestDataFactory.CreateListFieldDataDict(const count : Integer) : IThriftDictionary< Integer, IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>>; +var i : Integer; +begin + result := TThriftDictionaryImpl< Integer, IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>>.Create; + for i := 0 to count-1 do begin + result.Add(i, CreateListFieldDataDictValue(count)); + end; +end; + +class function TestDataFactory.CreateListFieldDataDictValue(const count : Integer) : IHashSet< IThriftList< IThriftDictionary< IInsanity, string>>>; +var i : Integer; +begin + result := THashSetImpl< IThriftList< IThriftDictionary< IInsanity, string>>>.Create; + for i := 0 to count-1 do begin + result.Add( CreateListFieldDataDictValueList(count)); + end; +end; + +class function TestDataFactory.CreateListFieldDataDictValueList(const count : Integer) : IThriftList< IThriftDictionary< IInsanity, string>>; +var i : Integer; +begin + result := TThriftListImpl< IThriftDictionary< IInsanity, string>>.Create; + for i := 0 to count-1 do begin + result.Add(CreateListFieldDataDictValueListDict(count)); + end; +end; + +class function TestDataFactory.CreateListFieldDataDictValueListDict(const count : Integer) : IThriftDictionary< IInsanity, string>; +begin + result := TThriftDictionaryImpl< IInsanity, string>.Create; + result.Add(CreateInsanity(count), Format('data level %d', [count])); +end; + +class function TestDataFactory.CreateBytesArray(const count : Integer) : TBytes; +var i : Integer; +begin + SetLength( result, count); + for i := 0 to count-1 do begin + result[i] := i mod $FF; + end; +end; + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/Performance/PerfTests.pas b/src/jaegertracing/thrift/lib/delphi/test/Performance/PerfTests.pas new file mode 100644 index 000000000..2c820b1f3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/Performance/PerfTests.pas @@ -0,0 +1,173 @@ +// 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. +unit PerfTests; + +interface + +uses + Windows, Classes, SysUtils, + Thrift.Collections, + Thrift.Test, + Thrift.Protocol, + Thrift.Protocol.JSON, + Thrift.Protocol.Compact, + Thrift.Transport, + Thrift.Stream, + ConsoleHelper, + TestConstants, + DataFactory; + +type + TPerformanceTests = class + strict private + Testdata : ICrazyNesting; + MemBuffer : TMemoryStream; + Transport : ITransport; + + procedure ProtocolPeformanceTest; + procedure RunTest( const ptyp : TKnownProtocol; const layered : TLayeredTransport); + function GenericProtocolFactory(const ptyp : TKnownProtocol; const layered : TLayeredTransport; const forWrite : Boolean) : IProtocol; + function GetProtocolTransportName(const ptyp : TKnownProtocol; const layered : TLayeredTransport) : string; + public + class function Execute : Integer; + end; + + +implementation + + +// not available in all versions, so make sure we have this one imported +function IsDebuggerPresent: BOOL; stdcall; external KERNEL32 name 'IsDebuggerPresent'; + + +class function TPerformanceTests.Execute : Integer; +var instance : TPerformanceTests; +begin + instance := TPerformanceTests.Create; + instance.ProtocolPeformanceTest; + + // debug only + if IsDebuggerPresent then begin + Console.Write('Hit ENTER ...'); + ReadLn; + end; + + result := 0; +end; + + +procedure TPerformanceTests.ProtocolPeformanceTest; +var layered : TLayeredTransport; +begin + Console.WriteLine('Setting up for ProtocolPeformanceTest ...'); + Testdata := TestDataFactory.CreateCrazyNesting(); + + for layered := Low(TLayeredTransport) to High(TLayeredTransport) do begin + RunTest( TKnownProtocol.prot_Binary, layered); + RunTest( TKnownProtocol.prot_Compact, layered); + RunTest( TKnownProtocol.prot_JSON, layered); + end; +end; + + +procedure TPerformanceTests.RunTest( const ptyp : TKnownProtocol; const layered : TLayeredTransport); +var freq, start, stop : Int64; + proto : IProtocol; + restored : ICrazyNesting; +begin + QueryPerformanceFrequency( freq); + + proto := GenericProtocolFactory( ptyp, layered, TRUE); + QueryPerformanceCounter( start); + Testdata.Write(proto); + Transport.Flush; + QueryPerformanceCounter( stop); + Console.WriteLine( Format('RunTest(%s): write = %d msec', [ + GetProtocolTransportName(ptyp,layered), + Round(1000.0*(stop-start)/freq) + ])); + + restored := TCrazyNestingImpl.Create; + proto := GenericProtocolFactory( ptyp, layered, FALSE); + QueryPerformanceCounter( start); + restored.Read(proto); + QueryPerformanceCounter( stop); + Console.WriteLine( Format('RunTest(%s): read = %d msec', [ + GetProtocolTransportName(ptyp,layered), + Round(1000.0*(stop-start)/freq) + ])); +end; + + +function TPerformanceTests.GenericProtocolFactory(const ptyp : TKnownProtocol; const layered : TLayeredTransport; const forWrite : Boolean) : IProtocol; +var newBuf : TMemoryStream; + stream : IThriftStream; + trans : IStreamTransport; +const COPY_ENTIRE_STREAM = 0; +begin + // read happens after write here, so let's take over the written bytes + newBuf := TMemoryStream.Create; + if not forWrite then newBuf.CopyFrom( MemBuffer, COPY_ENTIRE_STREAM); + MemBuffer := newBuf; + MemBuffer.Position := 0; + + // layered transports anyone? + stream := TThriftStreamAdapterDelphi.Create( newBuf, TRUE); + if forWrite + then trans := TStreamTransportImpl.Create( nil, stream) + else trans := TStreamTransportImpl.Create( stream, nil); + case layered of + trns_Framed : Transport := TFramedTransportImpl.Create( trans); + trns_Buffered : Transport := TBufferedTransportImpl.Create( trans); + else + Transport := trans; + end; + + if not Transport.IsOpen + then Transport.Open; + + case ptyp of + prot_Binary : result := TBinaryProtocolImpl.Create(trans); + prot_Compact : result := TCompactProtocolImpl.Create(trans); + prot_JSON : result := TJSONProtocolImpl.Create(trans); + else + ASSERT(FALSE); + end; +end; + + +function TPerformanceTests.GetProtocolTransportName(const ptyp : TKnownProtocol; const layered : TLayeredTransport) : string; +begin + case layered of + trns_Framed : result := ' + framed'; + trns_Buffered : result := ' + buffered'; + else + result := ''; + end; + + case ptyp of + prot_Binary : result := 'binary' + result; + prot_Compact : result := 'compact' + result; + prot_JSON : result := 'JSON' + result; + else + ASSERT(FALSE); + end; +end; + + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/TestClient.pas b/src/jaegertracing/thrift/lib/delphi/test/TestClient.pas new file mode 100644 index 000000000..e59c32720 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/TestClient.pas @@ -0,0 +1,1506 @@ +(* + * 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. + *) + +unit TestClient; + +{$I ../src/Thrift.Defines.inc} + +{.$DEFINE StressTest} // activate to stress-test the server with frequent connects/disconnects +{.$DEFINE PerfTest} // activate the performance test +{$DEFINE Exceptions} // activate the exceptions test (or disable while debugging) + +{$if CompilerVersion >= 28} +{$DEFINE SupportsAsync} +{$ifend} + +{$WARN SYMBOL_PLATFORM OFF} // Win32Check + +interface + +uses + Windows, SysUtils, Classes, Math, ComObj, ActiveX, + {$IFDEF SupportsAsync} System.Threading, {$ENDIF} + DateUtils, + Generics.Collections, + TestConstants, + ConsoleHelper, + PerfTests, + Thrift, + Thrift.Protocol.Compact, + Thrift.Protocol.JSON, + Thrift.Protocol, + Thrift.Transport.Pipes, + Thrift.Transport.WinHTTP, + Thrift.Transport.MsxmlHTTP, + Thrift.Transport, + Thrift.Stream, + Thrift.Test, + Thrift.WinHTTP, + Thrift.Utils, + Thrift.Collections; + +type + TThreadConsole = class + private + FThread : TThread; + public + procedure Write( const S : string); + procedure WriteLine( const S : string); + constructor Create( AThread: TThread); + end; + + TTestSetup = record + protType : TKnownProtocol; + endpoint : TEndpointTransport; + layered : TLayeredTransports; + useSSL : Boolean; // include where appropriate (TLayeredTransport?) + host : string; + port : Integer; + sPipeName : string; + hAnonRead, hAnonWrite : THandle; + end; + + TClientThread = class( TThread ) + private type + TTestGroup = ( + test_Unknown, + test_BaseTypes, + test_Structs, + test_Containers, + test_Exceptions + // new values here + ); + TTestGroups = set of TTestGroup; + + TTestSize = ( + Empty, // Edge case: the zero-length empty binary + Normal, // Fairly small array of usual size (256 bytes) + ByteArrayTest, // THRIFT-4454 Large writes/reads may cause range check errors in debug mode + PipeWriteLimit, // THRIFT-4372 Pipe write operations across a network are limited to 65,535 bytes per write. + TwentyMB // that's quite a bit of data + ); + + private + FSetup : TTestSetup; + FTransport : ITransport; + FProtocol : IProtocol; + FNumIteration : Integer; + FConsole : TThreadConsole; + + // test reporting, will be refactored out into separate class later + FTestGroup : string; + FCurrentTest : TTestGroup; + FSuccesses : Integer; + FErrors : TStringList; + FFailed : TTestGroups; + FExecuted : TTestGroups; + procedure StartTestGroup( const aGroup : string; const aTest : TTestGroup); + procedure Expect( aTestResult : Boolean; const aTestInfo : string); + procedure ReportResults; + function CalculateExitCode : Byte; + + procedure ClientTest; + {$IFDEF SupportsAsync} + procedure ClientAsyncTest; + {$ENDIF} + + procedure InitializeProtocolTransportStack; + procedure ShutdownProtocolTransportStack; + function InitializeHttpTransport( const aTimeoutSetting : Integer) : IHTTPClient; + + procedure JSONProtocolReadWriteTest; + function PrepareBinaryData( aRandomDist : Boolean; aSize : TTestSize) : TBytes; + {$IFDEF StressTest} + procedure StressTest(const client : TThriftTest.Iface); + {$ENDIF} + {$IFDEF Win64} + procedure UseInterlockedExchangeAdd64; + {$ENDIF} + protected + procedure Execute; override; + public + constructor Create( const aSetup : TTestSetup; const aNumIteration: Integer); + destructor Destroy; override; + end; + + TTestClient = class + private + class var + FNumIteration : Integer; + FNumThread : Integer; + + class procedure PrintCmdLineHelp; + class procedure InvalidArgs; + public + class function Execute( const args: array of string) : Byte; + end; + + +implementation + +const + EXITCODE_SUCCESS = $00; // no errors bits set + // + EXITCODE_FAILBIT_BASETYPES = $01; + EXITCODE_FAILBIT_STRUCTS = $02; + EXITCODE_FAILBIT_CONTAINERS = $04; + EXITCODE_FAILBIT_EXCEPTIONS = $08; + + MAP_FAILURES_TO_EXITCODE_BITS : array[TClientThread.TTestGroup] of Byte = ( + EXITCODE_SUCCESS, // no bits here + EXITCODE_FAILBIT_BASETYPES, + EXITCODE_FAILBIT_STRUCTS, + EXITCODE_FAILBIT_CONTAINERS, + EXITCODE_FAILBIT_EXCEPTIONS + ); + + + +function BoolToString( b : Boolean) : string; +// overrides global BoolToString() +begin + if b + then result := 'true' + else result := 'false'; +end; + +// not available in all versions, so make sure we have this one imported +function IsDebuggerPresent: BOOL; stdcall; external KERNEL32 name 'IsDebuggerPresent'; + +{ TTestClient } + +class procedure TTestClient.PrintCmdLineHelp; +const HELPTEXT = ' [options]'#10 + + #10 + + 'Allowed options:'#10 + + ' -h [ --help ] produce help message'#10 + + ' --host arg (=localhost) Host to connect'#10 + + ' --port arg (=9090) Port number to connect'#10 + + ' --domain-socket arg Domain Socket (e.g. /tmp/ThriftTest.thrift),'#10 + + ' instead of host and port'#10 + + ' --named-pipe arg Windows Named Pipe (e.g. MyThriftPipe)'#10 + + ' --anon-pipes hRead hWrite Windows Anonymous Pipes pair (handles)'#10 + + ' --transport arg (=sockets) Transport: buffered, framed, http, winhttp'#10 + + ' --protocol arg (=binary) Protocol: binary, compact, json'#10 + + ' --ssl Encrypted Transport using SSL'#10 + + ' -n [ --testloops ] arg (=1) Number of Tests'#10 + + ' -t [ --threads ] arg (=1) Number of Test threads'#10 + + ' --performance Run the built-in performance test (no other arguments)'#10 + ; +begin + Writeln( ChangeFileExt(ExtractFileName(ParamStr(0)),'') + HELPTEXT); +end; + +class procedure TTestClient.InvalidArgs; +begin + Console.WriteLine( 'Invalid args.'); + Console.WriteLine( ChangeFileExt(ExtractFileName(ParamStr(0)),'') + ' -h for more information'); + Abort; +end; + +class function TTestClient.Execute(const args: array of string) : Byte; +var + i : Integer; + threadExitCode : Byte; + s : string; + threads : array of TThread; + dtStart : TDateTime; + test : Integer; + thread : TThread; + setup : TTestSetup; +begin + // init record + with setup do begin + protType := prot_Binary; + endpoint := trns_Sockets; + layered := []; + useSSL := FALSE; + host := 'localhost'; + port := 9090; + sPipeName := ''; + hAnonRead := INVALID_HANDLE_VALUE; + hAnonWrite := INVALID_HANDLE_VALUE; + end; + + try + i := 0; + while ( i < Length(args) ) do begin + s := args[i]; + Inc( i); + + if (s = '-h') or (s = '--help') then begin + // -h [ --help ] produce help message + PrintCmdLineHelp; + result := $FF; // all tests failed + Exit; + end + else if s = '--host' then begin + // --host arg (=localhost) Host to connect + setup.host := args[i]; + Inc( i); + end + else if s = '--port' then begin + // --port arg (=9090) Port number to connect + s := args[i]; + Inc( i); + setup.port := StrToIntDef(s,0); + if setup.port <= 0 then InvalidArgs; + end + else if s = '--domain-socket' then begin + // --domain-socket arg Domain Socket (e.g. /tmp/ThriftTest.thrift), instead of host and port + raise Exception.Create('domain-socket not supported'); + end + else if s = '--named-pipe' then begin + // --named-pipe arg Windows Named Pipe (e.g. MyThriftPipe) + setup.endpoint := trns_NamedPipes; + setup.sPipeName := args[i]; + Inc( i); + Console.WriteLine('Using named pipe ('+setup.sPipeName+')'); + end + else if s = '--anon-pipes' then begin + // --anon-pipes hRead hWrite Windows Anonymous Pipes pair (handles) + setup.endpoint := trns_AnonPipes; + setup.hAnonRead := THandle( StrToIntDef( args[i], Integer(INVALID_HANDLE_VALUE))); + Inc( i); + setup.hAnonWrite := THandle( StrToIntDef( args[i], Integer(INVALID_HANDLE_VALUE))); + Inc( i); + Console.WriteLine('Using anonymous pipes ('+IntToStr(Integer(setup.hAnonRead))+' and '+IntToStr(Integer(setup.hAnonWrite))+')'); + end + else if s = '--transport' then begin + // --transport arg (=sockets) Transport: buffered, framed, http, winhttp, evhttp + s := args[i]; + Inc( i); + + if s = 'buffered' then Include( setup.layered, trns_Buffered) + else if s = 'framed' then Include( setup.layered, trns_Framed) + else if s = 'http' then setup.endpoint := trns_MsXmlHttp + else if s = 'winhttp' then setup.endpoint := trns_WinHttp + else if s = 'evhttp' then setup.endpoint := trns_EvHttp // recognized, but not supported + else InvalidArgs; + end + else if s = '--protocol' then begin + // --protocol arg (=binary) Protocol: binary, compact, json + s := args[i]; + Inc( i); + + if s = 'binary' then setup.protType := prot_Binary + else if s = 'compact' then setup.protType := prot_Compact + else if s = 'json' then setup.protType := prot_JSON + else InvalidArgs; + end + else if s = '--ssl' then begin + // --ssl Encrypted Transport using SSL + setup.useSSL := TRUE; + + end + else if (s = '-n') or (s = '--testloops') then begin + // -n [ --testloops ] arg (=1) Number of Tests + FNumIteration := StrToIntDef( args[i], 0); + Inc( i); + if FNumIteration <= 0 + then InvalidArgs; + + end + else if (s = '-t') or (s = '--threads') then begin + // -t [ --threads ] arg (=1) Number of Test threads + FNumThread := StrToIntDef( args[i], 0); + Inc( i); + if FNumThread <= 0 + then InvalidArgs; + end + else if (s = '--performance') then begin + result := TPerformanceTests.Execute; + Exit; + end + else begin + InvalidArgs; + end; + end; + + + // In the anonymous pipes mode the client is launched by the test server + // -> behave nicely and allow for attaching a debugger to this process + if (setup.endpoint = trns_AnonPipes) and not IsDebuggerPresent + then MessageBox( 0, 'Attach Debugger and/or click OK to continue.', + 'Thrift TestClient (Delphi)', + MB_OK or MB_ICONEXCLAMATION); + + SetLength( threads, FNumThread); + dtStart := Now; + + // layered transports are not really meant to be stacked upon each other + if (trns_Framed in setup.layered) then begin + Console.WriteLine('Using framed transport'); + end + else if (trns_Buffered in setup.layered) then begin + Console.WriteLine('Using buffered transport'); + end; + + Console.WriteLine(THRIFT_PROTOCOLS[setup.protType]+' protocol'); + + for test := 0 to FNumThread - 1 do begin + thread := TClientThread.Create( setup, FNumIteration); + threads[test] := thread; + thread.Start; + end; + + result := 0; + for test := 0 to FNumThread - 1 do begin + threadExitCode := threads[test].WaitFor; + result := result or threadExitCode; + threads[test].Free; + threads[test] := nil; + end; + + Console.Write('Total time: ' + IntToStr( MilliSecondsBetween(Now, dtStart))); + + except + on E: EAbort do raise; + on E: Exception do begin + Console.WriteLine( E.Message + #10 + E.StackTrace); + raise; + end; + end; + + Console.WriteLine(''); + Console.WriteLine('done!'); +end; + +{ TClientThread } + +procedure TClientThread.ClientTest; +var + client : TThriftTest.Iface; + s : string; + i8 : ShortInt; + i32 : Integer; + i64 : Int64; + binOut,binIn : TBytes; + dub : Double; + o : IXtruct; + o2 : IXtruct2; + i : IXtruct; + i2 : IXtruct2; + mapout : IThriftDictionary<Integer,Integer>; + mapin : IThriftDictionary<Integer,Integer>; + strmapout : IThriftDictionary<string,string>; + strmapin : IThriftDictionary<string,string>; + j : Integer; + first : Boolean; + key : Integer; + strkey : string; + listout : IThriftList<Integer>; + listin : IThriftList<Integer>; + setout : IHashSet<Integer>; + setin : IHashSet<Integer>; + ret : TNumberz; + uid : Int64; + mm : IThriftDictionary<Integer, IThriftDictionary<Integer, Integer>>; + pos : IThriftDictionary<Integer, Integer>; + neg : IThriftDictionary<Integer, Integer>; + m2 : IThriftDictionary<Integer, Integer>; + k2 : Integer; + insane : IInsanity; + truck : IXtruct; + whoa : IThriftDictionary<Int64, IThriftDictionary<TNumberz, IInsanity>>; + key64 : Int64; + val : IThriftDictionary<TNumberz, IInsanity>; + k2_2 : TNumberz; + k3 : TNumberz; + v2 : IInsanity; + userMap : IThriftDictionary<TNumberz, Int64>; + xtructs : IThriftList<IXtruct>; + x : IXtruct; + arg0 : ShortInt; + arg1 : Integer; + arg2 : Int64; + arg3 : IThriftDictionary<SmallInt, string>; + arg4 : TNumberz; + arg5 : Int64; + {$IFDEF PerfTest} + StartTick : Cardinal; + k : Integer; + {$ENDIF} + hello, goodbye : IXtruct; + crazy : IInsanity; + looney : IInsanity; + first_map : IThriftDictionary<TNumberz, IInsanity>; + second_map : IThriftDictionary<TNumberz, IInsanity>; + pair : TPair<TNumberz, TUserId>; + testsize : TTestSize; +begin + client := TThriftTest.TClient.Create( FProtocol); + FTransport.Open; + + {$IFDEF StressTest} + StressTest( client); + {$ENDIF StressTest} + + {$IFDEF Exceptions} + // in-depth exception test + // (1) do we get an exception at all? + // (2) do we get the right exception? + // (3) does the exception contain the expected data? + StartTestGroup( 'testException', test_Exceptions); + // case 1: exception type declared in IDL at the function call + try + client.testException('Xception'); + Expect( FALSE, 'testException(''Xception''): must trow an exception'); + except + on e:TXception do begin + Expect( e.ErrorCode = 1001, 'error code'); + Expect( e.Message_ = 'Xception', 'error message'); + Console.WriteLine( ' = ' + IntToStr(e.ErrorCode) + ', ' + e.Message_ ); + end; + on e:TTransportException do Expect( FALSE, 'Unexpected : "'+e.ToString+'"'); + on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'": '+e.Message); + end; + + // case 2: exception type NOT declared in IDL at the function call + // this will close the connection + try + client.testException('TException'); + Expect( FALSE, 'testException(''TException''): must trow an exception'); + except + on e:TTransportException do begin + Console.WriteLine( e.ClassName+' = '+e.Message); // this is what we get + end; + on e:TApplicationException do begin + Console.WriteLine( e.ClassName+' = '+e.Message); // this is what we get + end; + on e:TException do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'": '+e.Message); + on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'": '+e.Message); + end; + + + if FTransport.IsOpen then FTransport.Close; + FTransport.Open; // re-open connection, server has already closed + + + // case 3: no exception + try + client.testException('something'); + Expect( TRUE, 'testException(''something''): must not trow an exception'); + except + on e:TTransportException do Expect( FALSE, 'Unexpected : "'+e.ToString+'"'); + on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'": '+e.Message); + end; + {$ENDIF Exceptions} + + + // simple things + StartTestGroup( 'simple Thrift calls', test_BaseTypes); + client.testVoid(); + Expect( TRUE, 'testVoid()'); // success := no exception + + s := BoolToString( client.testBool(TRUE)); + Expect( s = BoolToString(TRUE), 'testBool(TRUE) = '+s); + s := BoolToString( client.testBool(FALSE)); + Expect( s = BoolToString(FALSE), 'testBool(FALSE) = '+s); + + s := client.testString('Test'); + Expect( s = 'Test', 'testString(''Test'') = "'+s+'"'); + + s := client.testString(''); // empty string + Expect( s = '', 'testString('''') = "'+s+'"'); + + s := client.testString(HUGE_TEST_STRING); + Expect( length(s) = length(HUGE_TEST_STRING), + 'testString( length(HUGE_TEST_STRING) = '+IntToStr(Length(HUGE_TEST_STRING))+') ' + +'=> length(result) = '+IntToStr(Length(s))); + + i8 := client.testByte(1); + Expect( i8 = 1, 'testByte(1) = ' + IntToStr( i8 )); + + i32 := client.testI32(-1); + Expect( i32 = -1, 'testI32(-1) = ' + IntToStr(i32)); + + Console.WriteLine('testI64(-34359738368)'); + i64 := client.testI64(-34359738368); + Expect( i64 = -34359738368, 'testI64(-34359738368) = ' + IntToStr( i64)); + + // random binary small + for testsize := Low(TTestSize) to High(TTestSize) do begin + binOut := PrepareBinaryData( TRUE, testsize); + Console.WriteLine('testBinary('+IntToStr(Length(binOut))+' bytes)'); + try + binIn := client.testBinary(binOut); + Expect( Length(binOut) = Length(binIn), 'testBinary('+IntToStr(Length(binOut))+' bytes): '+IntToStr(Length(binIn))+' bytes received'); + i32 := Min( Length(binOut), Length(binIn)); + Expect( CompareMem( binOut, binIn, i32), 'testBinary('+IntToStr(Length(binOut))+' bytes): validating received data'); + except + on e:TApplicationException do Console.WriteLine('testBinary(): '+e.Message); + on e:Exception do Expect( FALSE, 'testBinary(): Unexpected exception "'+e.ClassName+'": '+e.Message); + end; + end; + + Console.WriteLine('testDouble(5.325098235)'); + dub := client.testDouble(5.325098235); + Expect( abs(dub-5.325098235) < 1e-14, 'testDouble(5.325098235) = ' + FloatToStr( dub)); + + // structs + StartTestGroup( 'testStruct', test_Structs); + Console.WriteLine('testStruct({''Zero'', 1, -3, -5})'); + o := TXtructImpl.Create; + o.String_thing := 'Zero'; + o.Byte_thing := 1; + o.I32_thing := -3; + o.I64_thing := -5; + i := client.testStruct(o); + Expect( i.String_thing = 'Zero', 'i.String_thing = "'+i.String_thing+'"'); + Expect( i.Byte_thing = 1, 'i.Byte_thing = '+IntToStr(i.Byte_thing)); + Expect( i.I32_thing = -3, 'i.I32_thing = '+IntToStr(i.I32_thing)); + Expect( i.I64_thing = -5, 'i.I64_thing = '+IntToStr(i.I64_thing)); + Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing)); + Expect( i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing)); + Expect( i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing)); + Expect( i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing)); + + // nested structs + StartTestGroup( 'testNest', test_Structs); + Console.WriteLine('testNest({1, {''Zero'', 1, -3, -5}, 5})'); + o2 := TXtruct2Impl.Create; + o2.Byte_thing := 1; + o2.Struct_thing := o; + o2.I32_thing := 5; + i2 := client.testNest(o2); + i := i2.Struct_thing; + Expect( i.String_thing = 'Zero', 'i.String_thing = "'+i.String_thing+'"'); + Expect( i.Byte_thing = 1, 'i.Byte_thing = '+IntToStr(i.Byte_thing)); + Expect( i.I32_thing = -3, 'i.I32_thing = '+IntToStr(i.I32_thing)); + Expect( i.I64_thing = -5, 'i.I64_thing = '+IntToStr(i.I64_thing)); + Expect( i2.Byte_thing = 1, 'i2.Byte_thing = '+IntToStr(i2.Byte_thing)); + Expect( i2.I32_thing = 5, 'i2.I32_thing = '+IntToStr(i2.I32_thing)); + Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing)); + Expect( i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing)); + Expect( i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing)); + Expect( i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing)); + Expect( i2.__isset_Byte_thing, 'i2.__isset_Byte_thing'); + Expect( i2.__isset_I32_thing, 'i2.__isset_I32_thing'); + + // map<type1,type2>: A map of strictly unique keys to values. + // Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc. + StartTestGroup( 'testMap', test_Containers); + mapout := TThriftDictionaryImpl<Integer,Integer>.Create; + for j := 0 to 4 do + begin + mapout.AddOrSetValue( j, j - 10); + end; + Console.Write('testMap({'); + first := True; + for key in mapout.Keys do + begin + if first + then first := False + else Console.Write( ', ' ); + Console.Write( IntToStr( key) + ' => ' + IntToStr( mapout[key])); + end; + Console.WriteLine('})'); + + mapin := client.testMap( mapout ); + Expect( mapin.Count = mapout.Count, 'testMap: mapin.Count = mapout.Count'); + for j := 0 to 4 do + begin + Expect( mapout.ContainsKey(j), 'testMap: mapout.ContainsKey('+IntToStr(j)+') = '+BoolToString(mapout.ContainsKey(j))); + end; + for key in mapin.Keys do + begin + Expect( mapin[key] = mapout[key], 'testMap: '+IntToStr(key) + ' => ' + IntToStr( mapin[key])); + Expect( mapin[key] = key - 10, 'testMap: mapin['+IntToStr(key)+'] = '+IntToStr( mapin[key])); + end; + + + // map<type1,type2>: A map of strictly unique keys to values. + // Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc. + StartTestGroup( 'testStringMap', test_Containers); + strmapout := TThriftDictionaryImpl<string,string>.Create; + for j := 0 to 4 do + begin + strmapout.AddOrSetValue( IntToStr(j), IntToStr(j - 10)); + end; + Console.Write('testStringMap({'); + first := True; + for strkey in strmapout.Keys do + begin + if first + then first := False + else Console.Write( ', ' ); + Console.Write( strkey + ' => ' + strmapout[strkey]); + end; + Console.WriteLine('})'); + + strmapin := client.testStringMap( strmapout ); + Expect( strmapin.Count = strmapout.Count, 'testStringMap: strmapin.Count = strmapout.Count'); + for j := 0 to 4 do + begin + Expect( strmapout.ContainsKey(IntToStr(j)), + 'testStringMap: strmapout.ContainsKey('+IntToStr(j)+') = ' + + BoolToString(strmapout.ContainsKey(IntToStr(j)))); + end; + for strkey in strmapin.Keys do + begin + Expect( strmapin[strkey] = strmapout[strkey], 'testStringMap: '+strkey + ' => ' + strmapin[strkey]); + Expect( strmapin[strkey] = IntToStr( StrToInt(strkey) - 10), 'testStringMap: strmapin['+strkey+'] = '+strmapin[strkey]); + end; + + + // set<type>: An unordered set of unique elements. + // Translates to an STL set, Java HashSet, set in Python, etc. + // Note: PHP does not support sets, so it is treated similar to a List + StartTestGroup( 'testSet', test_Containers); + setout := THashSetImpl<Integer>.Create; + for j := -2 to 2 do + begin + setout.Add( j ); + end; + Console.Write('testSet({'); + first := True; + for j in setout do + begin + if first + then first := False + else Console.Write(', '); + Console.Write(IntToStr( j)); + end; + Console.WriteLine('})'); + + setin := client.testSet(setout); + Expect( setin.Count = setout.Count, 'testSet: setin.Count = setout.Count'); + Expect( setin.Count = 5, 'testSet: setin.Count = '+IntToStr(setin.Count)); + for j := -2 to 2 do // unordered, we can't rely on the order => test for known elements only + begin + Expect( setin.Contains(j), 'testSet: setin.Contains('+IntToStr(j)+') => '+BoolToString(setin.Contains(j))); + end; + + // list<type>: An ordered list of elements. + // Translates to an STL vector, Java ArrayList, native arrays in scripting languages, etc. + StartTestGroup( 'testList', test_Containers); + listout := TThriftListImpl<Integer>.Create; + listout.Add( +1); + listout.Add( -2); + listout.Add( +3); + listout.Add( -4); + listout.Add( 0); + Console.Write('testList({'); + first := True; + for j in listout do + begin + if first + then first := False + else Console.Write(', '); + Console.Write(IntToStr( j)); + end; + Console.WriteLine('})'); + + listin := client.testList(listout); + Expect( listin.Count = listout.Count, 'testList: listin.Count = listout.Count'); + Expect( listin.Count = 5, 'testList: listin.Count = '+IntToStr(listin.Count)); + Expect( listin[0] = +1, 'listin[0] = '+IntToStr( listin[0])); + Expect( listin[1] = -2, 'listin[1] = '+IntToStr( listin[1])); + Expect( listin[2] = +3, 'listin[2] = '+IntToStr( listin[2])); + Expect( listin[3] = -4, 'listin[3] = '+IntToStr( listin[3])); + Expect( listin[4] = 0, 'listin[4] = '+IntToStr( listin[4])); + + // enums + ret := client.testEnum(TNumberz.ONE); + Expect( ret = TNumberz.ONE, 'testEnum(ONE) = '+IntToStr(Ord(ret))); + + ret := client.testEnum(TNumberz.TWO); + Expect( ret = TNumberz.TWO, 'testEnum(TWO) = '+IntToStr(Ord(ret))); + + ret := client.testEnum(TNumberz.THREE); + Expect( ret = TNumberz.THREE, 'testEnum(THREE) = '+IntToStr(Ord(ret))); + + ret := client.testEnum(TNumberz.FIVE); + Expect( ret = TNumberz.FIVE, 'testEnum(FIVE) = '+IntToStr(Ord(ret))); + + ret := client.testEnum(TNumberz.EIGHT); + Expect( ret = TNumberz.EIGHT, 'testEnum(EIGHT) = '+IntToStr(Ord(ret))); + + + // typedef + uid := client.testTypedef(309858235082523); + Expect( uid = 309858235082523, 'testTypedef(309858235082523) = '+IntToStr(uid)); + + + // maps of maps + StartTestGroup( 'testMapMap(1)', test_Containers); + mm := client.testMapMap(1); + Console.Write(' = {'); + for key in mm.Keys do + begin + Console.Write( IntToStr( key) + ' => {'); + m2 := mm[key]; + for k2 in m2.Keys do + begin + Console.Write( IntToStr( k2) + ' => ' + IntToStr( m2[k2]) + ', '); + end; + Console.Write('}, '); + end; + Console.WriteLine('}'); + + // verify result data + Expect( mm.Count = 2, 'mm.Count = '+IntToStr(mm.Count)); + pos := mm[4]; + neg := mm[-4]; + for j := 1 to 4 do + begin + Expect( pos[j] = j, 'pos[j] = '+IntToStr(pos[j])); + Expect( neg[-j] = -j, 'neg[-j] = '+IntToStr(neg[-j])); + end; + + + + // insanity + StartTestGroup( 'testInsanity', test_Structs); + insane := TInsanityImpl.Create; + insane.UserMap := TThriftDictionaryImpl<TNumberz, Int64>.Create; + insane.UserMap.AddOrSetValue( TNumberz.FIVE, 5000); + truck := TXtructImpl.Create; + truck.String_thing := 'Truck'; + truck.Byte_thing := -8; // byte is signed + truck.I32_thing := 32; + truck.I64_thing := 64; + insane.Xtructs := TThriftListImpl<IXtruct>.Create; + insane.Xtructs.Add( truck ); + whoa := client.testInsanity( insane ); + Console.Write(' = {'); + for key64 in whoa.Keys do + begin + val := whoa[key64]; + Console.Write( IntToStr( key64) + ' => {'); + for k2_2 in val.Keys do + begin + v2 := val[k2_2]; + Console.Write( IntToStr( Integer( k2_2)) + ' => {'); + userMap := v2.UserMap; + Console.Write('{'); + if userMap <> nil then + begin + for k3 in userMap.Keys do + begin + Console.Write( IntToStr( Integer( k3)) + ' => ' + IntToStr( userMap[k3]) + ', '); + end; + end else + begin + Console.Write('null'); + end; + Console.Write('}, '); + xtructs := v2.Xtructs; + Console.Write('{'); + + if xtructs <> nil then + begin + for x in xtructs do + begin + Console.Write('{"' + x.String_thing + '", ' + + IntToStr( x.Byte_thing) + ', ' + + IntToStr( x.I32_thing) + ', ' + + IntToStr( x.I32_thing) + '}, '); + end; + end else + begin + Console.Write('null'); + end; + Console.Write('}'); + Console.Write('}, '); + end; + Console.Write('}, '); + end; + Console.WriteLine('}'); + + (** + * So you think you've got this all worked, out eh? + * + * Creates a the returned map with these values and prints it out: + * { 1 => { 2 => argument, + * 3 => argument, + * }, + * 2 => { 6 => <empty Insanity struct>, }, + * } + * @return map<UserId, map<Numberz,Insanity>> - a map with the above values + *) + + // verify result data + Expect( whoa.Count = 2, 'whoa.Count = '+IntToStr(whoa.Count)); + // + first_map := whoa[1]; + second_map := whoa[2]; + Expect( first_map.Count = 2, 'first_map.Count = '+IntToStr(first_map.Count)); + Expect( second_map.Count = 1, 'second_map.Count = '+IntToStr(second_map.Count)); + // + looney := second_map[TNumberz.SIX]; + Expect( Assigned(looney), 'Assigned(looney) = '+BoolToString(Assigned(looney))); + Expect( not looney.__isset_UserMap, 'looney.__isset_UserMap = '+BoolToString(looney.__isset_UserMap)); + Expect( not looney.__isset_Xtructs, 'looney.__isset_Xtructs = '+BoolToString(looney.__isset_Xtructs)); + // + for ret in [TNumberz.TWO, TNumberz.THREE] do begin + crazy := first_map[ret]; + Console.WriteLine('first_map['+intToStr(Ord(ret))+']'); + + Expect( crazy.__isset_UserMap, 'crazy.__isset_UserMap = '+BoolToString(crazy.__isset_UserMap)); + Expect( crazy.__isset_Xtructs, 'crazy.__isset_Xtructs = '+BoolToString(crazy.__isset_Xtructs)); + + Expect( crazy.UserMap.Count = insane.UserMap.Count, 'crazy.UserMap.Count = '+IntToStr(crazy.UserMap.Count)); + for pair in insane.UserMap do begin + Expect( crazy.UserMap[pair.Key] = pair.Value, 'crazy.UserMap['+IntToStr(Ord(pair.key))+'] = '+IntToStr(crazy.UserMap[pair.Key])); + end; + + Expect( crazy.Xtructs.Count = insane.Xtructs.Count, 'crazy.Xtructs.Count = '+IntToStr(crazy.Xtructs.Count)); + for arg0 := 0 to insane.Xtructs.Count-1 do begin + hello := insane.Xtructs[arg0]; + goodbye := crazy.Xtructs[arg0]; + Expect( goodbye.String_thing = hello.String_thing, 'goodbye.String_thing = '+goodbye.String_thing); + Expect( goodbye.Byte_thing = hello.Byte_thing, 'goodbye.Byte_thing = '+IntToStr(goodbye.Byte_thing)); + Expect( goodbye.I32_thing = hello.I32_thing, 'goodbye.I32_thing = '+IntToStr(goodbye.I32_thing)); + Expect( goodbye.I64_thing = hello.I64_thing, 'goodbye.I64_thing = '+IntToStr(goodbye.I64_thing)); + end; + end; + + + // multi args + StartTestGroup( 'testMulti', test_BaseTypes); + arg0 := 1; + arg1 := 2; + arg2 := High(Int64); + arg3 := TThriftDictionaryImpl<SmallInt, string>.Create; + arg3.AddOrSetValue( 1, 'one'); + arg4 := TNumberz.FIVE; + arg5 := 5000000; + Console.WriteLine('Test Multi(' + IntToStr( arg0) + ',' + + IntToStr( arg1) + ',' + IntToStr( arg2) + ',' + + arg3.ToString + ',' + IntToStr( Integer( arg4)) + ',' + + IntToStr( arg5) + ')'); + + i := client.testMulti( arg0, arg1, arg2, arg3, arg4, arg5); + Expect( i.String_thing = 'Hello2', 'testMulti: i.String_thing = "'+i.String_thing+'"'); + Expect( i.Byte_thing = arg0, 'testMulti: i.Byte_thing = '+IntToStr(i.Byte_thing)); + Expect( i.I32_thing = arg1, 'testMulti: i.I32_thing = '+IntToStr(i.I32_thing)); + Expect( i.I64_thing = arg2, 'testMulti: i.I64_thing = '+IntToStr(i.I64_thing)); + Expect( i.__isset_String_thing, 'testMulti: i.__isset_String_thing = '+BoolToString(i.__isset_String_thing)); + Expect( i.__isset_Byte_thing, 'testMulti: i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing)); + Expect( i.__isset_I32_thing, 'testMulti: i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing)); + Expect( i.__isset_I64_thing, 'testMulti: i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing)); + + // multi exception + StartTestGroup( 'testMultiException(1)', test_Exceptions); + try + i := client.testMultiException( 'need more pizza', 'run out of beer'); + Expect( i.String_thing = 'run out of beer', 'i.String_thing = "' +i.String_thing+ '"'); + Expect( i.__isset_String_thing, 'i.__isset_String_thing = '+BoolToString(i.__isset_String_thing)); + { this is not necessarily true, these fields are default-serialized + Expect( not i.__isset_Byte_thing, 'i.__isset_Byte_thing = '+BoolToString(i.__isset_Byte_thing)); + Expect( not i.__isset_I32_thing, 'i.__isset_I32_thing = '+BoolToString(i.__isset_I32_thing)); + Expect( not i.__isset_I64_thing, 'i.__isset_I64_thing = '+BoolToString(i.__isset_I64_thing)); + } + except + on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'": '+e.Message); + end; + + StartTestGroup( 'testMultiException(Xception)', test_Exceptions); + try + i := client.testMultiException( 'Xception', 'second test'); + Expect( FALSE, 'testMultiException(''Xception''): must trow an exception'); + except + on x:TXception do begin + Expect( x.__isset_ErrorCode, 'x.__isset_ErrorCode = '+BoolToString(x.__isset_ErrorCode)); + Expect( x.__isset_Message_, 'x.__isset_Message_ = '+BoolToString(x.__isset_Message_)); + Expect( x.ErrorCode = 1001, 'x.ErrorCode = '+IntToStr(x.ErrorCode)); + Expect( x.Message_ = 'This is an Xception', 'x.Message = "'+x.Message_+'"'); + end; + on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'": '+e.Message); + end; + + StartTestGroup( 'testMultiException(Xception2)', test_Exceptions); + try + i := client.testMultiException( 'Xception2', 'third test'); + Expect( FALSE, 'testMultiException(''Xception2''): must trow an exception'); + except + on x:TXception2 do begin + Expect( x.__isset_ErrorCode, 'x.__isset_ErrorCode = '+BoolToString(x.__isset_ErrorCode)); + Expect( x.__isset_Struct_thing, 'x.__isset_Struct_thing = '+BoolToString(x.__isset_Struct_thing)); + Expect( x.ErrorCode = 2002, 'x.ErrorCode = '+IntToStr(x.ErrorCode)); + Expect( x.Struct_thing.String_thing = 'This is an Xception2', 'x.Struct_thing.String_thing = "'+x.Struct_thing.String_thing+'"'); + Expect( x.Struct_thing.__isset_String_thing, 'x.Struct_thing.__isset_String_thing = '+BoolToString(x.Struct_thing.__isset_String_thing)); + { this is not necessarily true, these fields are default-serialized + Expect( not x.Struct_thing.__isset_Byte_thing, 'x.Struct_thing.__isset_Byte_thing = '+BoolToString(x.Struct_thing.__isset_Byte_thing)); + Expect( not x.Struct_thing.__isset_I32_thing, 'x.Struct_thing.__isset_I32_thing = '+BoolToString(x.Struct_thing.__isset_I32_thing)); + Expect( not x.Struct_thing.__isset_I64_thing, 'x.Struct_thing.__isset_I64_thing = '+BoolToString(x.Struct_thing.__isset_I64_thing)); + } + end; + on e:Exception do Expect( FALSE, 'Unexpected exception "'+e.ClassName+'": '+e.Message); + end; + + + // oneway functions + StartTestGroup( 'Test Oneway(1)', test_Unknown); + client.testOneway(1); + Expect( TRUE, 'Test Oneway(1)'); // success := no exception + + // call time + {$IFDEF PerfTest} + StartTestGroup( 'Test Calltime()'); + StartTick := GetTickCount; + for k := 0 to 1000 - 1 do + begin + client.testVoid(); + end; + Console.WriteLine(' = ' + FloatToStr( (GetTickCount - StartTick) / 1000 ) + ' ms a testVoid() call' ); + {$ENDIF PerfTest} + + // no more tests here + StartTestGroup( '', test_Unknown); +end; + + +{$IFDEF SupportsAsync} +procedure TClientThread.ClientAsyncTest; +var + client : TThriftTest.IAsync; + s : string; + i8 : ShortInt; +begin + StartTestGroup( 'Async Tests', test_Unknown); + client := TThriftTest.TClient.Create( FProtocol); + FTransport.Open; + + // oneway void functions + client.testOnewayAsync(1).Wait; + Expect( TRUE, 'Test Oneway(1)'); // success := no exception + + // normal functions + s := client.testStringAsync(HUGE_TEST_STRING).Value; + Expect( length(s) = length(HUGE_TEST_STRING), + 'testString( length(HUGE_TEST_STRING) = '+IntToStr(Length(HUGE_TEST_STRING))+') ' + +'=> length(result) = '+IntToStr(Length(s))); + + i8 := client.testByte(1).Value; + Expect( i8 = 1, 'testByte(1) = ' + IntToStr( i8 )); +end; +{$ENDIF} + + +{$IFDEF StressTest} +procedure TClientThread.StressTest(const client : TThriftTest.Iface); +begin + while TRUE do begin + try + if not FTransport.IsOpen then FTransport.Open; // re-open connection, server has already closed + try + client.testString('Test'); + Write('.'); + finally + if FTransport.IsOpen then FTransport.Close; + end; + except + on e:Exception do Writeln(#10+e.message); + end; + end; +end; +{$ENDIF} + + +function TClientThread.PrepareBinaryData( aRandomDist : Boolean; aSize : TTestSize) : TBytes; +var i : Integer; +begin + case aSize of + Empty : SetLength( result, 0); + Normal : SetLength( result, $100); + ByteArrayTest : SetLength( result, SizeOf(TByteArray) + 128); + PipeWriteLimit : SetLength( result, 65535 + 128); + TwentyMB : SetLength( result, 20 * 1024 * 1024); + else + raise EArgumentException.Create('aSize'); + end; + + ASSERT( Low(result) = 0); + if Length(result) = 0 then Exit; + + // linear distribution, unless random is requested + if not aRandomDist then begin + for i := Low(result) to High(result) do begin + result[i] := i mod $100; + end; + Exit; + end; + + // random distribution of all 256 values + FillChar( result[0], Length(result) * SizeOf(result[0]), $0); + for i := Low(result) to High(result) do begin + result[i] := Byte( Random($100)); + end; +end; + + +{$IFDEF Win64} +procedure TClientThread.UseInterlockedExchangeAdd64; +var a,b : Int64; +begin + a := 1; + b := 2; + Thrift.Utils.InterlockedExchangeAdd64( a,b); + Expect( a = 3, 'InterlockedExchangeAdd64'); +end; +{$ENDIF} + + +procedure TClientThread.JSONProtocolReadWriteTest; +// Tests only then read/write procedures of the JSON protocol +// All tests succeed, if we can read what we wrote before +// Note that passing this test does not imply, that our JSON is really compatible to what +// other clients or servers expect as the real JSON. This is beyond the scope of this test. +var prot : IProtocol; + stm : TStringStream; + list : TThriftList; + binary, binRead, emptyBinary : TBytes; + i,iErr : Integer; +const + TEST_SHORT = ShortInt( $FE); + TEST_SMALL = SmallInt( $FEDC); + TEST_LONG = LongInt( $FEDCBA98); + TEST_I64 = Int64( $FEDCBA9876543210); + TEST_DOUBLE = -1.234e-56; + DELTA_DOUBLE = TEST_DOUBLE * 1e-14; + TEST_STRING = 'abc-'#$00E4#$00f6#$00fc; // german umlauts (en-us: "funny chars") + // Test THRIFT-2336 and THRIFT-3404 with U+1D11E (G Clef symbol) and 'РуÑÑкое Ðазвание'; + G_CLEF_AND_CYRILLIC_TEXT = #$1d11e' '#$0420#$0443#$0441#$0441#$043a#$043e#$0435' '#$041d#$0430#$0437#$0432#$0430#$043d#$0438#$0435; + G_CLEF_AND_CYRILLIC_JSON = '"\ud834\udd1e \u0420\u0443\u0441\u0441\u043a\u043e\u0435 \u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435"'; + // test both possible solidus encodings + SOLIDUS_JSON_DATA = '"one/two\/three"'; + SOLIDUS_EXCPECTED = 'one/two/three'; +begin + stm := TStringStream.Create; + try + StartTestGroup( 'JsonProtocolTest', test_Unknown); + + // prepare binary data + binary := PrepareBinaryData( FALSE, Normal); + SetLength( emptyBinary, 0); // empty binary data block + + // output setup + prot := TJSONProtocolImpl.Create( + TStreamTransportImpl.Create( + nil, TThriftStreamAdapterDelphi.Create( stm, FALSE))); + + // write + Init( list, TType.String_, 9); + prot.WriteListBegin( list); + prot.WriteBool( TRUE); + prot.WriteBool( FALSE); + prot.WriteByte( TEST_SHORT); + prot.WriteI16( TEST_SMALL); + prot.WriteI32( TEST_LONG); + prot.WriteI64( TEST_I64); + prot.WriteDouble( TEST_DOUBLE); + prot.WriteString( TEST_STRING); + prot.WriteBinary( binary); + prot.WriteString( ''); // empty string + prot.WriteBinary( emptyBinary); // empty binary data block + prot.WriteListEnd; + + // input setup + Expect( stm.Position = stm.Size, 'Stream position/length after write'); + stm.Position := 0; + prot := TJSONProtocolImpl.Create( + TStreamTransportImpl.Create( + TThriftStreamAdapterDelphi.Create( stm, FALSE), nil)); + + // read and compare + list := prot.ReadListBegin; + Expect( list.ElementType = TType.String_, 'list element type'); + Expect( list.Count = 9, 'list element count'); + Expect( prot.ReadBool, 'WriteBool/ReadBool: TRUE'); + Expect( not prot.ReadBool, 'WriteBool/ReadBool: FALSE'); + Expect( prot.ReadByte = TEST_SHORT, 'WriteByte/ReadByte'); + Expect( prot.ReadI16 = TEST_SMALL, 'WriteI16/ReadI16'); + Expect( prot.ReadI32 = TEST_LONG, 'WriteI32/ReadI32'); + Expect( prot.ReadI64 = TEST_I64, 'WriteI64/ReadI64'); + Expect( abs(prot.ReadDouble-TEST_DOUBLE) < abs(DELTA_DOUBLE), 'WriteDouble/ReadDouble'); + Expect( prot.ReadString = TEST_STRING, 'WriteString/ReadString'); + binRead := prot.ReadBinary; + Expect( Length(prot.ReadString) = 0, 'WriteString/ReadString (empty string)'); + Expect( Length(prot.ReadBinary) = 0, 'empty WriteBinary/ReadBinary (empty data block)'); + prot.ReadListEnd; + + // test binary data + Expect( Length(binary) = Length(binRead), 'Binary data length check'); + iErr := -1; + for i := Low(binary) to High(binary) do begin + if binary[i] <> binRead[i] then begin + iErr := i; + Break; + end; + end; + if iErr < 0 + then Expect( TRUE, 'Binary data check ('+IntToStr(Length(binary))+' Bytes)') + else Expect( FALSE, 'Binary data check at offset '+IntToStr(iErr)); + + Expect( stm.Position = stm.Size, 'Stream position after read'); + + + // Solidus can be encoded in two ways. Make sure we can read both + stm.Position := 0; + stm.Size := 0; + stm.WriteString(SOLIDUS_JSON_DATA); + stm.Position := 0; + prot := TJSONProtocolImpl.Create( + TStreamTransportImpl.Create( + TThriftStreamAdapterDelphi.Create( stm, FALSE), nil)); + Expect( prot.ReadString = SOLIDUS_EXCPECTED, 'Solidus encoding'); + + + // Widechars should work too. Do they? + // After writing, we ensure that we are able to read it back + // We can't assume hex-encoding, since (nearly) any Unicode char is valid JSON + stm.Position := 0; + stm.Size := 0; + prot := TJSONProtocolImpl.Create( + TStreamTransportImpl.Create( + nil, TThriftStreamAdapterDelphi.Create( stm, FALSE))); + prot.WriteString( G_CLEF_AND_CYRILLIC_TEXT); + stm.Position := 0; + prot := TJSONProtocolImpl.Create( + TStreamTransportImpl.Create( + TThriftStreamAdapterDelphi.Create( stm, FALSE), nil)); + Expect( prot.ReadString = G_CLEF_AND_CYRILLIC_TEXT, 'Writing JSON with chars > 8 bit'); + + // Widechars should work with hex-encoding too. Do they? + stm.Position := 0; + stm.Size := 0; + stm.WriteString( G_CLEF_AND_CYRILLIC_JSON); + stm.Position := 0; + prot := TJSONProtocolImpl.Create( + TStreamTransportImpl.Create( + TThriftStreamAdapterDelphi.Create( stm, FALSE), nil)); + Expect( prot.ReadString = G_CLEF_AND_CYRILLIC_TEXT, 'Reading JSON with chars > 8 bit'); + + + finally + stm.Free; + prot := nil; //-> Release + StartTestGroup( '', test_Unknown); // no more tests here + end; +end; + + +procedure TClientThread.StartTestGroup( const aGroup : string; const aTest : TTestGroup); +begin + FTestGroup := aGroup; + FCurrentTest := aTest; + + Include( FExecuted, aTest); + + if FTestGroup <> '' then begin + Console.WriteLine(''); + Console.WriteLine( aGroup+' tests'); + Console.WriteLine( StringOfChar('-',60)); + end; +end; + + +procedure TClientThread.Expect( aTestResult : Boolean; const aTestInfo : string); +begin + if aTestResult then begin + Inc(FSuccesses); + Console.WriteLine( aTestInfo+': passed'); + end + else begin + FErrors.Add( FTestGroup+': '+aTestInfo); + Include( FFailed, FCurrentTest); + Console.WriteLine( aTestInfo+': *** FAILED ***'); + + // We have a failed test! + // -> issue DebugBreak ONLY if a debugger is attached, + // -> unhandled DebugBreaks would cause Windows to terminate the app otherwise + if IsDebuggerPresent + then {$IFDEF CPUX64} DebugBreak {$ELSE} asm int 3 end {$ENDIF}; + end; +end; + + +procedure TClientThread.ReportResults; +var nTotal : Integer; + sLine : string; +begin + // prevent us from stupid DIV/0 errors + nTotal := FSuccesses + FErrors.Count; + if nTotal = 0 then begin + Console.WriteLine('No results logged'); + Exit; + end; + + Console.WriteLine(''); + Console.WriteLine( StringOfChar('=',60)); + Console.WriteLine( IntToStr(nTotal)+' tests performed'); + Console.WriteLine( IntToStr(FSuccesses)+' tests succeeded ('+IntToStr(round(100*FSuccesses/nTotal))+'%)'); + Console.WriteLine( IntToStr(FErrors.Count)+' tests failed ('+IntToStr(round(100*FErrors.Count/nTotal))+'%)'); + Console.WriteLine( StringOfChar('=',60)); + if FErrors.Count > 0 then begin + Console.WriteLine('FAILED TESTS:'); + for sLine in FErrors do Console.WriteLine('- '+sLine); + Console.WriteLine( StringOfChar('=',60)); + InterlockedIncrement( ExitCode); // return <> 0 on errors + end; + Console.WriteLine(''); +end; + + +function TClientThread.CalculateExitCode : Byte; +var test : TTestGroup; +begin + result := EXITCODE_SUCCESS; + for test := Low(TTestGroup) to High(TTestGroup) do begin + if (test in FFailed) or not (test in FExecuted) + then result := result or MAP_FAILURES_TO_EXITCODE_BITS[test]; + end; +end; + + +constructor TClientThread.Create( const aSetup : TTestSetup; const aNumIteration: Integer); +begin + FSetup := aSetup; + FNumIteration := ANumIteration; + + FConsole := TThreadConsole.Create( Self ); + FCurrentTest := test_Unknown; + + // error list: keep correct order, allow for duplicates + FErrors := TStringList.Create; + FErrors.Sorted := FALSE; + FErrors.Duplicates := dupAccept; + + inherited Create( TRUE); +end; + +destructor TClientThread.Destroy; +begin + FreeAndNil( FConsole); + FreeAndNil( FErrors); + inherited; +end; + +procedure TClientThread.Execute; +var + i : Integer; +begin + // perform all tests + try + {$IFDEF Win64} + UseInterlockedExchangeAdd64; + {$ENDIF} + JSONProtocolReadWriteTest; + + // must be run in the context of the thread + InitializeProtocolTransportStack; + try + for i := 0 to FNumIteration - 1 do begin + ClientTest; + {$IFDEF SupportsAsync} + ClientAsyncTest; + {$ENDIF} + end; + + // report the outcome + ReportResults; + SetReturnValue( CalculateExitCode); + + finally + ShutdownProtocolTransportStack; + end; + + except + on e:Exception do Expect( FALSE, 'unexpected exception: "'+e.message+'"'); + end; +end; + + +function TClientThread.InitializeHttpTransport( const aTimeoutSetting : Integer) : IHTTPClient; +var sUrl : string; + comps : URL_COMPONENTS; + dwChars : DWORD; +begin + ASSERT( FSetup.endpoint in [trns_MsxmlHttp, trns_WinHttp]); + + if FSetup.useSSL + then sUrl := 'https://' + else sUrl := 'http://'; + + sUrl := sUrl + FSetup.host; + + // add the port number if necessary and at the right place + FillChar( comps, SizeOf(comps), 0); + comps.dwStructSize := SizeOf(comps); + comps.dwSchemeLength := MAXINT; + comps.dwHostNameLength := MAXINT; + comps.dwUserNameLength := MAXINT; + comps.dwPasswordLength := MAXINT; + comps.dwUrlPathLength := MAXINT; + comps.dwExtraInfoLength := MAXINT; + Win32Check( WinHttpCrackUrl( PChar(sUrl), Length(sUrl), 0, comps)); + case FSetup.port of + 80 : if FSetup.useSSL then comps.nPort := FSetup.port; + 443 : if not FSetup.useSSL then comps.nPort := FSetup.port; + else + if FSetup.port > 0 then comps.nPort := FSetup.port; + end; + dwChars := Length(sUrl) + 64; + SetLength( sUrl, dwChars); + Win32Check( WinHttpCreateUrl( comps, 0, @sUrl[1], dwChars)); + SetLength( sUrl, dwChars); + + + Console.WriteLine('Target URL: '+sUrl); + case FSetup.endpoint of + trns_MsxmlHttp : result := TMsxmlHTTPClientImpl.Create( sUrl); + trns_WinHttp : result := TWinHTTPClientImpl.Create( sUrl); + else + raise Exception.Create(ENDPOINT_TRANSPORTS[FSetup.endpoint]+' unhandled case'); + end; + + result.DnsResolveTimeout := aTimeoutSetting; + result.ConnectionTimeout := aTimeoutSetting; + result.SendTimeout := aTimeoutSetting; + result.ReadTimeout := aTimeoutSetting; +end; + + +procedure TClientThread.InitializeProtocolTransportStack; +var streamtrans : IStreamTransport; + canSSL : Boolean; +const + DEBUG_TIMEOUT = 30 * 1000; + RELEASE_TIMEOUT = DEFAULT_THRIFT_TIMEOUT; + PIPE_TIMEOUT = RELEASE_TIMEOUT; + HTTP_TIMEOUTS = 10 * 1000; +begin + // needed for HTTP clients as they utilize the MSXML COM components + OleCheck( CoInitialize( nil)); + + canSSL := FALSE; + case FSetup.endpoint of + trns_Sockets: begin + Console.WriteLine('Using sockets ('+FSetup.host+' port '+IntToStr(FSetup.port)+')'); + streamtrans := TSocketImpl.Create( FSetup.host, FSetup.port ); + FTransport := streamtrans; + end; + + trns_MsxmlHttp, + trns_WinHttp: begin + Console.WriteLine('Using HTTPClient'); + FTransport := InitializeHttpTransport( HTTP_TIMEOUTS); + canSSL := TRUE; + end; + + trns_EvHttp: begin + raise Exception.Create(ENDPOINT_TRANSPORTS[FSetup.endpoint]+' transport not implemented'); + end; + + trns_NamedPipes: begin + streamtrans := TNamedPipeTransportClientEndImpl.Create( FSetup.sPipeName, 0, nil, PIPE_TIMEOUT, PIPE_TIMEOUT); + FTransport := streamtrans; + end; + + trns_AnonPipes: begin + streamtrans := TAnonymousPipeTransportImpl.Create( FSetup.hAnonRead, FSetup.hAnonWrite, FALSE); + FTransport := streamtrans; + end; + + else + raise Exception.Create('Unhandled endpoint transport'); + end; + ASSERT( FTransport <> nil); + + // layered transports are not really meant to be stacked upon each other + if (trns_Framed in FSetup.layered) then begin + FTransport := TFramedTransportImpl.Create( FTransport); + end + else if (trns_Buffered in FSetup.layered) and (streamtrans <> nil) then begin + FTransport := TBufferedTransportImpl.Create( streamtrans, 32); // small buffer to test read() + end; + + if FSetup.useSSL and not canSSL then begin + raise Exception.Create('SSL/TLS not implemented'); + end; + + // create protocol instance, default to BinaryProtocol + case FSetup.protType of + prot_Binary : FProtocol := TBinaryProtocolImpl.Create( FTransport, BINARY_STRICT_READ, BINARY_STRICT_WRITE); + prot_JSON : FProtocol := TJSONProtocolImpl.Create( FTransport); + prot_Compact : FProtocol := TCompactProtocolImpl.Create( FTransport); + else + raise Exception.Create('Unhandled protocol'); + end; + + ASSERT( (FTransport <> nil) and (FProtocol <> nil)); +end; + + +procedure TClientThread.ShutdownProtocolTransportStack; +begin + try + FProtocol := nil; + + if FTransport <> nil then begin + FTransport.Close; + FTransport := nil; + end; + + finally + CoUninitialize; + end; +end; + + +{ TThreadConsole } + +constructor TThreadConsole.Create(AThread: TThread); +begin + inherited Create; + FThread := AThread; +end; + +procedure TThreadConsole.Write(const S: string); +var + proc : TThreadProcedure; +begin + proc := procedure + begin + Console.Write( S ); + end; + TThread.Synchronize( FThread, proc); +end; + +procedure TThreadConsole.WriteLine(const S: string); +var + proc : TThreadProcedure; +begin + proc := procedure + begin + Console.WriteLine( S ); + end; + TThread.Synchronize( FThread, proc); +end; + +initialization +begin + TTestClient.FNumIteration := 1; + TTestClient.FNumThread := 1; +end; + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/test/TestConstants.pas b/src/jaegertracing/thrift/lib/delphi/test/TestConstants.pas new file mode 100644 index 000000000..ae3b3e8a3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/TestConstants.pas @@ -0,0 +1,164 @@ +(* + * 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. + *) + +unit TestConstants; + +interface + +uses SysUtils; + +type + TKnownProtocol = ( + prot_Binary, // default binary protocol + prot_JSON, // JSON protocol + prot_Compact + ); + + TServerType = ( + srv_Simple, + srv_Nonblocking, + srv_Threadpool, + srv_Threaded + ); + + TEndpointTransport = ( + trns_Sockets, + trns_MsxmlHttp, + trns_WinHttp, + trns_NamedPipes, + trns_AnonPipes, + trns_EvHttp // as listed on http://thrift.apache.org/test + ); + + TLayeredTransport = ( + trns_None, + trns_Buffered, + trns_Framed + ); + + TLayeredTransports = set of TLayeredTransport; + +const + SERVER_TYPES : array[TServerType] of string + = ('Simple', 'Nonblocking', 'Threadpool', 'Threaded'); + + THRIFT_PROTOCOLS : array[TKnownProtocol] of string + = ('Binary', 'JSON', 'Compact'); + + LAYERED_TRANSPORTS : array[TLayeredTransport] of string + = ('None', 'Buffered', 'Framed'); + + ENDPOINT_TRANSPORTS : array[TEndpointTransport] of string + = ('Sockets', 'Http', 'WinHttp', 'Named Pipes','Anon Pipes', 'EvHttp'); + + // defaults are: read=false, write=true + BINARY_STRICT_READ = FALSE; + BINARY_STRICT_WRITE = FALSE; + + HUGE_TEST_STRING = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ' + + 'eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam ' + + 'voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit ' + + 'amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ' + + 'nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed ' + + 'diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ' + + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ' + + 'eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam ' + + 'voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit ' + + 'amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ' + + 'nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed ' + + 'diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ' + + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ' + + 'eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam ' + + 'voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit ' + + 'amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ' + + 'nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed ' + + 'diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ' + + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ' + + 'eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam ' + + 'voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit ' + + 'amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ' + + 'nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed ' + + 'diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ' + + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ' + + 'eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam ' + + 'voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit ' + + 'amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ' + + 'nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed ' + + 'diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ' + + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ' + + 'eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam ' + + 'voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit ' + + 'amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ' + + 'nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed ' + + 'diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ' + + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ' + + 'eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam ' + + 'voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit ' + + 'amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ' + + 'nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed ' + + 'diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ' + + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ' + + 'eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam ' + + 'voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit ' + + 'amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ' + + 'nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed ' + + 'diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ' + + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ' + + 'eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam ' + + 'voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit ' + + 'amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ' + + 'nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed ' + + 'diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet ' + + 'clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. '; + + +function BytesToHex( const bytes : TBytes) : string; + + +implementation + + +function BytesToHex( const bytes : TBytes) : string; +var i : Integer; +begin + result := ''; + for i := Low(bytes) to High(bytes) do begin + result := result + IntToHex(bytes[i],2); + end; +end; + + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/test/TestServer.pas b/src/jaegertracing/thrift/lib/delphi/test/TestServer.pas new file mode 100644 index 000000000..2a80d52a7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/TestServer.pas @@ -0,0 +1,684 @@ +(* + * 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. + *) + +unit TestServer; + +{$I ../src/Thrift.Defines.inc} +{$WARN SYMBOL_PLATFORM OFF} + +{.$DEFINE RunEndless} // activate to interactively stress-test the server stop routines via Ctrl+C + +interface + +uses + Windows, SysUtils, + Generics.Collections, + Thrift.Server, + Thrift.Transport, + Thrift.Transport.Pipes, + Thrift.Protocol, + Thrift.Protocol.JSON, + Thrift.Protocol.Compact, + Thrift.Collections, + Thrift.Utils, + Thrift.Test, + Thrift, + TestConstants, + TestServerEvents, + ConsoleHelper, + Contnrs; + +type + TTestServer = class + public + type + + ITestHandler = interface( TThriftTest.Iface ) + procedure SetServer( const AServer : IServer ); + procedure TestStop; + end; + + TTestHandlerImpl = class( TInterfacedObject, ITestHandler ) + private + FServer : IServer; + protected + procedure testVoid(); + function testBool(thing: Boolean): Boolean; + function testString(const thing: string): string; + function testByte(thing: ShortInt): ShortInt; + function testI32(thing: Integer): Integer; + function testI64(const thing: Int64): Int64; + function testDouble(const thing: Double): Double; + function testBinary(const thing: TBytes): TBytes; + function testStruct(const thing: IXtruct): IXtruct; + function testNest(const thing: IXtruct2): IXtruct2; + function testMap(const thing: IThriftDictionary<Integer, Integer>): IThriftDictionary<Integer, Integer>; + function testStringMap(const thing: IThriftDictionary<string, string>): IThriftDictionary<string, string>; + function testSet(const thing: IHashSet<Integer>): IHashSet<Integer>; + function testList(const thing: IThriftList<Integer>): IThriftList<Integer>; + function testEnum(thing: TNumberz): TNumberz; + function testTypedef(const thing: Int64): Int64; + function testMapMap(hello: Integer): IThriftDictionary<Integer, IThriftDictionary<Integer, Integer>>; + function testInsanity(const argument: IInsanity): IThriftDictionary<Int64, IThriftDictionary<TNumberz, IInsanity>>; + function testMulti(arg0: ShortInt; arg1: Integer; const arg2: Int64; const arg3: IThriftDictionary<SmallInt, string>; arg4: TNumberz; const arg5: Int64): IXtruct; + procedure testException(const arg: string); + function testMultiException(const arg0: string; const arg1: string): IXtruct; + procedure testOneway(secondsToSleep: Integer); + + procedure TestStop; + procedure SetServer( const AServer : IServer ); + end; + + class procedure PrintCmdLineHelp; + class procedure InvalidArgs; + + class procedure LaunchAnonPipeChild( const app : string; const transport : IAnonymousPipeServerTransport); + class procedure Execute( const args: array of string); + end; + +implementation + + +var g_Handler : TTestServer.ITestHandler = nil; + + +function MyConsoleEventHandler( dwCtrlType : DWORD) : BOOL; stdcall; +// Note that this Handler procedure is called from another thread +var handler : TTestServer.ITestHandler; +begin + result := TRUE; + try + case dwCtrlType of + CTRL_C_EVENT : Console.WriteLine( 'Ctrl+C pressed'); + CTRL_BREAK_EVENT : Console.WriteLine( 'Ctrl+Break pressed'); + CTRL_CLOSE_EVENT : Console.WriteLine( 'Received CloseTask signal'); + CTRL_LOGOFF_EVENT : Console.WriteLine( 'Received LogOff signal'); + CTRL_SHUTDOWN_EVENT : Console.WriteLine( 'Received Shutdown signal'); + else + Console.WriteLine( 'Received console event #'+IntToStr(Integer(dwCtrlType))); + end; + + handler := g_Handler; + if handler <> nil then handler.TestStop; + + except + // catch all + end; +end; + + +{ TTestServer.TTestHandlerImpl } + +procedure TTestServer.TTestHandlerImpl.SetServer( const AServer: IServer); +begin + FServer := AServer; +end; + +function TTestServer.TTestHandlerImpl.testByte(thing: ShortInt): ShortInt; +begin + Console.WriteLine('testByte("' + IntToStr( thing) + '")'); + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.testDouble( const thing: Double): Double; +begin + Console.WriteLine('testDouble("' + FloatToStr( thing ) + '")'); + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.testBinary(const thing: TBytes): TBytes; +begin + Console.WriteLine('testBinary('+IntToStr(Length(thing)) + ' bytes)'); + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.testEnum(thing: TNumberz): TNumberz; +begin + Console.WriteLine('testEnum(' + EnumUtils<TNumberz>.ToString(Ord(thing)) + ')'); + Result := thing; +end; + +procedure TTestServer.TTestHandlerImpl.testException(const arg: string); +begin + Console.WriteLine('testException(' + arg + ')'); + if ( arg = 'Xception') then + begin + raise TXception.Create( 1001, arg); + end; + + if (arg = 'TException') then + begin + raise TException.Create('TException'); + end; + + // else do not throw anything +end; + +function TTestServer.TTestHandlerImpl.testI32(thing: Integer): Integer; +begin + Console.WriteLine('testI32("' + IntToStr( thing) + '")'); + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.testI64( const thing: Int64): Int64; +begin + Console.WriteLine('testI64("' + IntToStr( thing) + '")'); + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.testInsanity( + const argument: IInsanity): IThriftDictionary<Int64, IThriftDictionary<TNumberz, IInsanity>>; +var + looney : IInsanity; + first_map : IThriftDictionary<TNumberz, IInsanity>; + second_map : IThriftDictionary<TNumberz, IInsanity>; + insane : IThriftDictionary<Int64, IThriftDictionary<TNumberz, IInsanity>>; + +begin + Console.Write('testInsanity('); + if argument <> nil then Console.Write(argument.ToString); + Console.WriteLine(')'); + + + (** + * So you think you've got this all worked, out eh? + * + * Creates a the returned map with these values and prints it out: + * { 1 => { 2 => argument, + * 3 => argument, + * }, + * 2 => { 6 => <empty Insanity struct>, }, + * } + * @return map<UserId, map<Numberz,Insanity>> - a map with the above values + *) + + first_map := TThriftDictionaryImpl<TNumberz, IInsanity>.Create; + second_map := TThriftDictionaryImpl<TNumberz, IInsanity>.Create; + + first_map.AddOrSetValue( TNumberz.TWO, argument); + first_map.AddOrSetValue( TNumberz.THREE, argument); + + looney := TInsanityImpl.Create; + second_map.AddOrSetValue( TNumberz.SIX, looney); + + insane := TThriftDictionaryImpl<Int64, IThriftDictionary<TNumberz, IInsanity>>.Create; + + insane.AddOrSetValue( 1, first_map); + insane.AddOrSetValue( 2, second_map); + + Result := insane; +end; + +function TTestServer.TTestHandlerImpl.testList( const thing: IThriftList<Integer>): IThriftList<Integer>; +begin + Console.Write('testList('); + if thing <> nil then Console.Write(thing.ToString); + Console.WriteLine(')'); + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.testMap( + const thing: IThriftDictionary<Integer, Integer>): IThriftDictionary<Integer, Integer>; +begin + Console.Write('testMap('); + if thing <> nil then Console.Write(thing.ToString); + Console.WriteLine(')'); + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.TestMapMap( + hello: Integer): IThriftDictionary<Integer, IThriftDictionary<Integer, Integer>>; +var + mapmap : IThriftDictionary<Integer, IThriftDictionary<Integer, Integer>>; + pos : IThriftDictionary<Integer, Integer>; + neg : IThriftDictionary<Integer, Integer>; + i : Integer; +begin + Console.WriteLine('testMapMap(' + IntToStr( hello) + ')'); + mapmap := TThriftDictionaryImpl<Integer, IThriftDictionary<Integer, Integer>>.Create; + pos := TThriftDictionaryImpl<Integer, Integer>.Create; + neg := TThriftDictionaryImpl<Integer, Integer>.Create; + + for i := 1 to 4 do + begin + pos.AddOrSetValue( i, i); + neg.AddOrSetValue( -i, -i); + end; + + mapmap.AddOrSetValue(4, pos); + mapmap.AddOrSetValue( -4, neg); + + Result := mapmap; +end; + +function TTestServer.TTestHandlerImpl.testMulti(arg0: ShortInt; arg1: Integer; + const arg2: Int64; const arg3: IThriftDictionary<SmallInt, string>; + arg4: TNumberz; const arg5: Int64): IXtruct; +var + hello : IXtruct; +begin + Console.WriteLine('testMulti()'); + hello := TXtructImpl.Create; + hello.String_thing := 'Hello2'; + hello.Byte_thing := arg0; + hello.I32_thing := arg1; + hello.I64_thing := arg2; + Result := hello; +end; + +function TTestServer.TTestHandlerImpl.testMultiException( const arg0, arg1: string): IXtruct; +var + x2 : TXception2; +begin + Console.WriteLine('testMultiException(' + arg0 + ', ' + arg1 + ')'); + if ( arg0 = 'Xception') then begin + raise TXception.Create( 1001, 'This is an Xception'); // test the new rich CTOR + end; + + if ( arg0 = 'Xception2') then begin + x2 := TXception2.Create; // the old way still works too? + x2.ErrorCode := 2002; + x2.Struct_thing := TXtructImpl.Create; + x2.Struct_thing.String_thing := 'This is an Xception2'; + x2.UpdateMessageProperty; + raise x2; + end; + + Result := TXtructImpl.Create; + Result.String_thing := arg1; +end; + +function TTestServer.TTestHandlerImpl.testNest( const thing: IXtruct2): IXtruct2; +begin + Console.Write('testNest('); + if thing <> nil then Console.Write(thing.ToString); + Console.WriteLine(')'); + + Result := thing; +end; + +procedure TTestServer.TTestHandlerImpl.testOneway(secondsToSleep: Integer); +begin + Console.WriteLine('testOneway(' + IntToStr( secondsToSleep )+ '), sleeping...'); + Sleep(secondsToSleep * 1000); + Console.WriteLine('testOneway finished'); +end; + +function TTestServer.TTestHandlerImpl.testSet( const thing: IHashSet<Integer>):IHashSet<Integer>; +begin + Console.Write('testSet('); + if thing <> nil then Console.Write(thing.ToString); + Console.WriteLine(')');; + + Result := thing; +end; + +procedure TTestServer.TTestHandlerImpl.testStop; +begin + if FServer <> nil then begin + FServer.Stop; + end; +end; + +function TTestServer.TTestHandlerImpl.testBool(thing: Boolean): Boolean; +begin + Console.WriteLine('testBool(' + BoolToStr(thing,true) + ')'); + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.testString( const thing: string): string; +begin + Console.WriteLine('teststring("' + thing + '")'); + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.testStringMap( + const thing: IThriftDictionary<string, string>): IThriftDictionary<string, string>; +begin + Console.Write('testStringMap('); + if thing <> nil then Console.Write(thing.ToString); + Console.WriteLine(')'); + + Result := thing; +end; + +function TTestServer.TTestHandlerImpl.testTypedef( const thing: Int64): Int64; +begin + Console.WriteLine('testTypedef(' + IntToStr( thing) + ')'); + Result := thing; +end; + +procedure TTestServer.TTestHandlerImpl.TestVoid; +begin + Console.WriteLine('testVoid()'); +end; + +function TTestServer.TTestHandlerImpl.testStruct( const thing: IXtruct): IXtruct; +begin + Console.Write('testStruct('); + if thing <> nil then Console.Write(thing.ToString); + Console.WriteLine(')'); + + Result := thing; +end; + + +{ TTestServer } + + +class procedure TTestServer.PrintCmdLineHelp; +const HELPTEXT = ' [options]'#10 + + #10 + + 'Allowed options:'#10 + + ' -h [ --help ] produce help message'#10 + + ' --port arg (=9090) Port number to listen'#10 + + ' --domain-socket arg Unix Domain Socket (e.g. /tmp/ThriftTest.thrift)'#10 + + ' --named-pipe arg Windows Named Pipe (e.g. MyThriftPipe)'#10 + + ' --server-type arg (=simple) type of server, "simple", "thread-pool",'#10 + + ' "threaded", or "nonblocking"'#10 + + ' --transport arg (=socket) transport: buffered, framed, http, anonpipe'#10 + + ' --protocol arg (=binary) protocol: binary, compact, json'#10 + + ' --ssl Encrypted Transport using SSL'#10 + + ' --processor-events processor-events'#10 + + ' -n [ --workers ] arg (=4) Number of thread pools workers. Only valid for'#10 + + ' thread-pool server type'#10 + ; +begin + Console.WriteLine( ChangeFileExt(ExtractFileName(ParamStr(0)),'') + HELPTEXT); +end; + +class procedure TTestServer.InvalidArgs; +begin + Console.WriteLine( 'Invalid args.'); + Console.WriteLine( ChangeFileExt(ExtractFileName(ParamStr(0)),'') + ' -h for more information'); + Abort; +end; + +class procedure TTestServer.LaunchAnonPipeChild( const app : string; const transport : IAnonymousPipeServerTransport); +//Launch child process and pass R/W anonymous pipe handles on cmd line. +//This is a simple example and does not include elevation or other +//advanced features. +var pi : PROCESS_INFORMATION; + si : STARTUPINFO; + sArg, sHandles, sCmdLine : string; + i : Integer; +begin + GetStartupInfo( si); //set startupinfo for the spawned process + + // preformat handles args + sHandles := Format( '%d %d', + [ Integer(transport.ClientAnonRead), + Integer(transport.ClientAnonWrite)]); + + // pass all settings to client + sCmdLine := app; + for i := 1 to ParamCount do begin + sArg := ParamStr(i); + + // add anonymous handles and quote strings where appropriate + if sArg = '-anon' + then sArg := sArg +' '+ sHandles + else begin + if Pos(' ',sArg) > 0 + then sArg := '"'+sArg+'"'; + end;; + + sCmdLine := sCmdLine +' '+ sArg; + end; + + // spawn the child process + Console.WriteLine('Starting client '+sCmdLine); + Win32Check( CreateProcess( nil, PChar(sCmdLine), nil,nil,TRUE,0,nil,nil,si,pi)); + + CloseHandle( pi.hThread); + CloseHandle( pi.hProcess); +end; + + +class procedure TTestServer.Execute( const args: array of string); +var + Port : Integer; + ServerEvents : Boolean; + sPipeName : string; + testHandler : ITestHandler; + testProcessor : IProcessor; + ServerTrans : IServerTransport; + ServerEngine : IServer; + anonymouspipe : IAnonymousPipeServerTransport; + namedpipe : INamedPipeServerTransport; + TransportFactory : ITransportFactory; + ProtocolFactory : IProtocolFactory; + i, numWorker : Integer; + s : string; + protType : TKnownProtocol; + servertype : TServerType; + endpoint : TEndpointTransport; + layered : TLayeredTransports; + UseSSL : Boolean; // include where appropriate (TLayeredTransport?) +begin + try + ServerEvents := FALSE; + protType := prot_Binary; + servertype := srv_Simple; + endpoint := trns_Sockets; + layered := []; + UseSSL := FALSE; + Port := 9090; + sPipeName := ''; + numWorker := 4; + + i := 0; + while ( i < Length(args) ) do begin + s := args[i]; + Inc(i); + + // Allowed options: + if (s = '-h') or (s = '--help') then begin + // -h [ --help ] produce help message + PrintCmdLineHelp; + Exit; + end + else if (s = '--port') then begin + // --port arg (=9090) Port number to listen + s := args[i]; + Inc(i); + Port := StrToIntDef( s, Port); + end + else if (s = '--domain-socket') then begin + // --domain-socket arg Unix Domain Socket (e.g. /tmp/ThriftTest.thrift) + raise Exception.Create('domain-socket not supported'); + end + else if (s = '--named-pipe') then begin + // --named-pipe arg Windows Named Pipe (e.g. MyThriftPipe) + endpoint := trns_NamedPipes; + sPipeName := args[i]; // -pipe <name> + Inc( i ); + end + else if (s = '--server-type') then begin + // --server-type arg (=simple) type of server, + // arg = "simple", "thread-pool", "threaded", or "nonblocking" + s := args[i]; + Inc(i); + + if s = 'simple' then servertype := srv_Simple + else if s = 'thread-pool' then servertype := srv_Threadpool + else if s = 'threaded' then servertype := srv_Threaded + else if s = 'nonblocking' then servertype := srv_Nonblocking + else InvalidArgs; + end + else if (s = '--transport') then begin + // --transport arg (=buffered) transport: buffered, framed, http + s := args[i]; + Inc(i); + + if s = 'buffered' then Include( layered, trns_Buffered) + else if s = 'framed' then Include( layered, trns_Framed) + else if s = 'http' then endpoint := trns_MsxmlHttp + else if s = 'winhttp' then endpoint := trns_WinHttp + else if s = 'anonpipe' then endpoint := trns_AnonPipes + else InvalidArgs; + end + else if (s = '--protocol') then begin + // --protocol arg (=binary) protocol: binary, compact, json + s := args[i]; + Inc(i); + + if s = 'binary' then protType := prot_Binary + else if s = 'compact' then protType := prot_Compact + else if s = 'json' then protType := prot_JSON + else InvalidArgs; + end + else if (s = '--ssl') then begin + // --ssl Encrypted Transport using SSL + UseSSL := TRUE; + end + else if (s = '--processor-events') then begin + // --processor-events processor-events + ServerEvents := TRUE; + end + else if (s = '-n') or (s = '--workers') then begin + // -n [ --workers ] arg (=4) Number of thread pools workers. + // Only valid for thread-pool server type + s := args[i]; + numWorker := StrToIntDef(s,0); + if numWorker > 0 + then Inc(i) + else numWorker := 4; + end + else begin + InvalidArgs; + end; + end; + + + Console.WriteLine('Server configuration: '); + + // create protocol factory, default to BinaryProtocol + case protType of + prot_Binary : ProtocolFactory := TBinaryProtocolImpl.TFactory.Create( BINARY_STRICT_READ, BINARY_STRICT_WRITE); + prot_JSON : ProtocolFactory := TJSONProtocolImpl.TFactory.Create; + prot_Compact : ProtocolFactory := TCompactProtocolImpl.TFactory.Create; + else + raise Exception.Create('Unhandled protocol'); + end; + ASSERT( ProtocolFactory <> nil); + Console.WriteLine('- '+THRIFT_PROTOCOLS[protType]+' protocol'); + + case endpoint of + + trns_Sockets : begin + Console.WriteLine('- sockets (port '+IntToStr(port)+')'); + if (trns_Buffered in layered) then Console.WriteLine('- buffered'); + servertrans := TServerSocketImpl.Create( Port, 0, (trns_Buffered in layered)); + end; + + trns_MsxmlHttp, + trns_WinHttp : begin + raise Exception.Create('HTTP server transport not implemented'); + end; + + trns_NamedPipes : begin + Console.WriteLine('- named pipe ('+sPipeName+')'); + namedpipe := TNamedPipeServerTransportImpl.Create( sPipeName, 4096, PIPE_UNLIMITED_INSTANCES); + servertrans := namedpipe; + end; + + trns_AnonPipes : begin + Console.WriteLine('- anonymous pipes'); + anonymouspipe := TAnonymousPipeServerTransportImpl.Create; + servertrans := anonymouspipe; + end + + else + raise Exception.Create('Unhandled endpoint transport'); + end; + ASSERT( servertrans <> nil); + + if UseSSL then begin + raise Exception.Create('SSL not implemented'); + end; + + if (trns_Framed in layered) then begin + Console.WriteLine('- framed transport'); + TransportFactory := TFramedTransportImpl.TFactory.Create + end + else begin + TransportFactory := TTransportFactoryImpl.Create; + end; + ASSERT( TransportFactory <> nil); + + testHandler := TTestHandlerImpl.Create; + testProcessor := TThriftTest.TProcessorImpl.Create( testHandler ); + + case servertype of + srv_Simple : begin + ServerEngine := TSimpleServer.Create( testProcessor, ServerTrans, TransportFactory, ProtocolFactory); + end; + + srv_Nonblocking : begin + raise Exception.Create(SERVER_TYPES[servertype]+' server not implemented'); + end; + + srv_Threadpool, + srv_Threaded: begin + if numWorker > 1 then {use here}; + raise Exception.Create(SERVER_TYPES[servertype]+' server not implemented'); + end; + + else + raise Exception.Create('Unhandled server type'); + end; + ASSERT( ServerEngine <> nil); + + testHandler.SetServer( ServerEngine); + + // test events? + if ServerEvents then begin + Console.WriteLine('- server events test enabled'); + ServerEngine.ServerEvents := TServerEventsImpl.Create; + end; + + // start the client now when we have the anon handles, but before the server starts + if endpoint = trns_AnonPipes + then LaunchAnonPipeChild( ExtractFilePath(ParamStr(0))+'client.exe', anonymouspipe); + + // install Ctrl+C handler before the server starts + g_Handler := testHandler; + SetConsoleCtrlHandler( @MyConsoleEventHandler, TRUE); + + Console.WriteLine(''); + repeat + Console.WriteLine('Starting the server ...'); + serverEngine.Serve; + until {$IFDEF RunEndless} FALSE {$ELSE} TRUE {$ENDIF}; + + testHandler.SetServer( nil); + g_Handler := nil; + + except + on E: EAbort do raise; + on E: Exception do begin + Console.WriteLine( E.Message + #10 + E.StackTrace ); + end; + end; + Console.WriteLine( 'done.'); +end; + + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/test/TestServerEvents.pas b/src/jaegertracing/thrift/lib/delphi/test/TestServerEvents.pas new file mode 100644 index 000000000..2208cd4ba --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/TestServerEvents.pas @@ -0,0 +1,174 @@ +(* + * 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. + *) + +unit TestServerEvents; + +interface + +uses + SysUtils, + Thrift, + Thrift.Protocol, + Thrift.Transport, + Thrift.Server, + ConsoleHelper; + +type + TRequestEventsImpl = class( TInterfacedObject, IRequestEvents) + protected + FStart : TDateTime; + // IRequestProcessingEvents + procedure PreRead; + procedure PostRead; + procedure PreWrite; + procedure PostWrite; + procedure OnewayComplete; + procedure UnhandledError( const e : Exception); + procedure CleanupContext; + public + constructor Create; + end; + + + TProcessorEventsImpl = class( TInterfacedObject, IProcessorEvents) + protected + FReqs : Integer; + // IProcessorEvents + procedure Processing( const transport : ITransport); + function CreateRequestContext( const aFunctionName : string) : IRequestEvents; + procedure CleanupContext; + public + constructor Create; + end; + + + TServerEventsImpl = class( TInterfacedObject, IServerEvents) + protected + // IServerEvents + procedure PreServe; + procedure PreAccept; + function CreateProcessingContext( const input, output : IProtocol) : IProcessorEvents; + end; + + +implementation + +{ TServerEventsImpl } + +procedure TServerEventsImpl.PreServe; +begin + Console.WriteLine('ServerEvents: Server starting to serve requests'); +end; + + +procedure TServerEventsImpl.PreAccept; +begin + Console.WriteLine('ServerEvents: Server transport is ready to accept incoming calls'); +end; + + +function TServerEventsImpl.CreateProcessingContext(const input, output: IProtocol): IProcessorEvents; +begin + result := TProcessorEventsImpl.Create; +end; + + +{ TProcessorEventsImpl } + +constructor TProcessorEventsImpl.Create; +begin + inherited Create; + FReqs := 0; + Console.WriteLine('ProcessorEvents: Client connected, processing begins'); +end; + +procedure TProcessorEventsImpl.Processing(const transport: ITransport); +begin + Console.WriteLine('ProcessorEvents: Processing of incoming request begins'); +end; + + +function TProcessorEventsImpl.CreateRequestContext( const aFunctionName: string): IRequestEvents; +begin + result := TRequestEventsImpl.Create; + Inc( FReqs); +end; + + +procedure TProcessorEventsImpl.CleanupContext; +begin + Console.WriteLine( 'ProcessorEvents: completed after handling '+IntToStr(FReqs)+' requests.'); +end; + + +{ TRequestEventsImpl } + + +constructor TRequestEventsImpl.Create; +begin + inherited Create; + FStart := Now; + Console.WriteLine('RequestEvents: New request'); +end; + + +procedure TRequestEventsImpl.PreRead; +begin + Console.WriteLine('RequestEvents: Reading request message ...'); +end; + + +procedure TRequestEventsImpl.PostRead; +begin + Console.WriteLine('RequestEvents: Reading request message completed'); +end; + +procedure TRequestEventsImpl.PreWrite; +begin + Console.WriteLine('RequestEvents: Writing response message ...'); +end; + + +procedure TRequestEventsImpl.PostWrite; +begin + Console.WriteLine('RequestEvents: Writing response message completed'); +end; + + +procedure TRequestEventsImpl.OnewayComplete; +begin + Console.WriteLine('RequestEvents: Oneway message processed'); +end; + + +procedure TRequestEventsImpl.UnhandledError(const e: Exception); +begin + Console.WriteLine('RequestEvents: Unhandled exception of type '+e.classname); +end; + + +procedure TRequestEventsImpl.CleanupContext; +var millis : Double; +begin + millis := (Now - FStart) * (24*60*60*1000); + Console.WriteLine( 'Request processing completed in '+IntToStr(Round(millis))+' ms'); +end; + + +end. diff --git a/src/jaegertracing/thrift/lib/delphi/test/client.dpr b/src/jaegertracing/thrift/lib/delphi/test/client.dpr new file mode 100644 index 000000000..83727f619 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/client.dpr @@ -0,0 +1,77 @@ +(* + * 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. + *) + + +program client; + +{$APPTYPE CONSOLE} + +uses + SysUtils, + DataFactory in 'Performance\DataFactory.pas', + PerfTests in 'Performance\PerfTests.pas', + TestClient in 'TestClient.pas', + Thrift.Test, // in 'gen-delphi\Thrift.Test.pas', + Thrift in '..\src\Thrift.pas', + Thrift.Transport in '..\src\Thrift.Transport.pas', + Thrift.Socket in '..\src\Thrift.Socket.pas', + Thrift.Exception in '..\src\Thrift.Exception.pas', + Thrift.Transport.Pipes in '..\src\Thrift.Transport.Pipes.pas', + Thrift.Transport.WinHTTP in '..\src\Thrift.Transport.WinHTTP.pas', + Thrift.Transport.MsxmlHTTP in '..\src\Thrift.Transport.MsxmlHTTP.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.Protocol.Multiplex in '..\src\Thrift.Protocol.Multiplex.pas', + Thrift.Collections in '..\src\Thrift.Collections.pas', + Thrift.Server in '..\src\Thrift.Server.pas', + Thrift.Stream in '..\src\Thrift.Stream.pas', + Thrift.TypeRegistry in '..\src\Thrift.TypeRegistry.pas', + Thrift.WinHTTP in '..\src\Thrift.WinHTTP.pas', + Thrift.Utils in '..\src\Thrift.Utils.pas'; + +var + nParamCount : Integer; + args : array of string; + i : Integer; + arg : string; + +begin + try + Writeln( 'Delphi TestClient '+Thrift.Version); + nParamCount := ParamCount; + SetLength( args, nParamCount); + for i := 1 to nParamCount do begin + arg := ParamStr( i ); + args[i-1] := arg; + end; + + ExitCode := TTestClient.Execute( args); + + except + on E: EAbort do begin + ExitCode := $FF; + end; + on E: Exception do begin + Writeln(E.ClassName, ': ', E.Message); + ExitCode := $FF; + end; + end; +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/codegen/README.md b/src/jaegertracing/thrift/lib/delphi/test/codegen/README.md new file mode 100644 index 000000000..a0145890f --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/codegen/README.md @@ -0,0 +1,28 @@ +How to use the test case: +---------------------------------------------- +- copy and the template batch file +- open the batch file and adjust configuration as necessary +- run the batch + + +Configuration: +---------------------------------------------- +SVNWORKDIR +should point to the Thrift working copy root + +MY_THRIFT_FILES +can be set to point to a folder with more thrift IDL files. +If you don't have any such files, just leave the setting blank. + +BIN +Local MSYS binary folder. Your THRIFT.EXE is installed here. + +MINGW_BIN +Local MinGW bin folder. Contains DLL files required by THRIFT.EXE + +DCC +Identifies the Delphi Command Line compiler (dcc32.exe) +To be configuired only, if the default is not suitable. + +---------------------------------------------- +*EOF*
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/delphi/test/codegen/run-Pascal-Codegen-Tests.bat.tmpl b/src/jaegertracing/thrift/lib/delphi/test/codegen/run-Pascal-Codegen-Tests.bat.tmpl new file mode 100644 index 000000000..dbab0ae7c --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/codegen/run-Pascal-Codegen-Tests.bat.tmpl @@ -0,0 +1,173 @@ +REM /* +REM * Licensed to the Apache Software Foundation (ASF) under one +REM * or more contributor license agreements. See the NOTICE file +REM * distributed with this work for additional information +REM * regarding copyright ownership. The ASF licenses this file +REM * to you under the Apache License, Version 2.0 (the +REM * "License"); you may not use this file except in compliance +REM * with the License. You may obtain a copy of the License at +REM * +REM * http://www.apache.org/licenses/LICENSE-2.0 +REM * +REM * Unless required by applicable law or agreed to in writing, +REM * software distributed under the License is distributed on an +REM * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +REM * KIND, either express or implied. See the License for the +REM * specific language governing permissions and limitations +REM * under the License. +REM */ +@echo off +if ""=="%1" goto CONFIG +goto HANDLEDIR + +REM ----------------------------------------------------- +:CONFIG +REM ----------------------------------------------------- + +rem * CONFIGURATION BEGIN +rem * configuration settings, adjust as necessary to meet your system setup +set SVNWORKDIR= +set MY_THRIFT_FILES= +set BIN=C:\MSys10\local\bin +set MINGW_BIN=C:\MinGW\bin +set DCC= +set SUBDIR=gen-delphi +rem * CONFIGURATION END + + +REM ----------------------------------------------------- +:START +REM ----------------------------------------------------- + +rem * configured? +if "%SVNWORKDIR%"=="" goto CONFIG_ERROR + +rem * try to find dcc32.exe +echo Looking for dcc32.exe ... +if not exist "%DCC%" set DCC=%ProgramFiles%\Embarcadero\RAD Studio\8.0\bin\dcc32.exe +if not exist "%DCC%" set DCC=%ProgramFiles(x86)%\Embarcadero\RAD Studio\8.0\bin\dcc32.exe +if not exist "%DCC%" goto CONFIG_ERROR +echo Found %DCC% +echo. + +rem * some helpers +set PATH=%BIN%;%MINGW_BIN%;%PATH% +set TARGET=%SVNWORKDIR%\..\thrift-testing +set SOURCE=%SVNWORKDIR% +set TESTAPP=TestProject +set UNITSEARCH=%SOURCE%\lib\pas\src;%SOURCE%\lib\delphi\src +set OUTDCU="%TARGET%\dcu" +set LOGFILE=%TARGET%\%SUBDIR%\codegen.log + +rem * create and/or empty target dirs +if not exist "%TARGET%" md "%TARGET%" +if not exist "%TARGET%\%SUBDIR%" md "%TARGET%\%SUBDIR%" +if not exist "%OUTDCU%" md "%OUTDCU%" +if exist "%TARGET%\*.thrift" del "%TARGET%\*.thrift" /Q +if exist "%TARGET%\%SUBDIR%\*.*" del "%TARGET%\%SUBDIR%\*.*" /Q +if exist "%OUTDCU%\*.*" del "%OUTDCU%\*.*" /Q + +rem * recurse through thrift WC and "my thrift files" folder +rem * copies all .thrift files into thrift-testing +call %0 %SOURCE% +if not "%MY_THRIFT_FILES%"=="" call %0 %MY_THRIFT_FILES% + +rem * compile all thrift files, generate PAS and C++ code +echo. +echo Generating code, please wait ... +cd "%TARGET%" +for %%a in (*.thrift) do "%BIN%\thrift.exe" -v --gen delphi:register_types,constprefix,events,xmldoc "%%a" 2>> "%LOGFILE%" +REM * for %%a in (*.thrift) do "%BIN%\thrift.exe" -v --gen cpp "%%a" >> NUL: +cmd /c start notepad "%LOGFILE%" +cd .. + +rem * check for special Delphi testcases being processed +if not exist "%TARGET%\%SUBDIR%\ReservedKeywords.pas" goto TESTCASE_MISSING + + +rem * generate a minimal DPR file that uses all generated pascal units +cd "%TARGET%\%SUBDIR%\" +if exist inherited.* ren inherited.* _inherited.* +echo program %TESTAPP%; > %TESTAPP%.dpr +echo {$APPTYPE CONSOLE} >> %TESTAPP%.dpr +echo. >> %TESTAPP%.dpr +echo uses >> %TESTAPP%.dpr +for %%a in (*.pas) do echo %%~na, >> %TESTAPP%.dpr +echo Windows, Classes, SysUtils; >> %TESTAPP%.dpr +echo. >> %TESTAPP%.dpr +echo begin >> %TESTAPP%.dpr +echo Writeln('Successfully compiled!'); >> %TESTAPP%.dpr +echo Writeln('List of units:'); >> %TESTAPP%.dpr +for %%a in (*.pas) do echo Write('%%~na':30,'':10); >> %TESTAPP%.dpr +echo Writeln; >> %TESTAPP%.dpr +echo end. >> %TESTAPP%.dpr +echo. >> %TESTAPP%.dpr +cd ..\.. + +rem * try to compile the DPR +rem * this should not throw any errors, warnings or hints +"%DCC%" -B "%TARGET%\%SUBDIR%\%TESTAPP%" -U"%UNITSEARCH%" -I"%UNITSEARCH%" -N"%OUTDCU%" -E"%TARGET%\%SUBDIR%" +dir "%TARGET%\%SUBDIR%\%TESTAPP%.exe" +if not exist "%TARGET%\%SUBDIR%\%TESTAPP%.exe" goto CODEGEN_FAILED +echo. +echo ----------------------------------------------------------------- +echo The compiled program is now executed. If it hangs or crashes, we +echo have a serious problem with the generated code. Expected output +echo is "Successfully compiled:" followed by a list of generated units. +echo ----------------------------------------------------------------- +"%TARGET%\%SUBDIR%\%TESTAPP%.exe" +echo ----------------------------------------------------------------- +echo. +pause +GOTO EOF + +REM ----------------------------------------------------- +:DXE_NOT_FOUND +REM ----------------------------------------------------- +echo Delphi Compiler (dcc32.exe) not found. +echo Please check the "DCC" setting in this batch. +echo. +cmd /c start notepad README.MD +cmd /c start notepad %0 +pause +GOTO EOF + + +REM ----------------------------------------------------- +:CONFIG_ERROR +REM ----------------------------------------------------- +echo Missing, incomplete or wrong configuration settings! +cmd /c start notepad README.MD +cmd /c start notepad %0 +pause +GOTO EOF + + +REM ----------------------------------------------------- +:TESTCASE_MISSING +REM ----------------------------------------------------- +echo Missing an expected Delphi testcase! +pause +GOTO EOF + + +REM ----------------------------------------------------- +:CODEGEN_FAILED +REM ----------------------------------------------------- +echo Code generation FAILED! +pause +GOTO EOF + + +REM ----------------------------------------------------- +:HANDLEDIR +REM ----------------------------------------------------- +REM echo %1 +for /D %%a in (%1\*) do call %0 %%a +if exist "%1\*.thrift" copy /b "%1\*.thrift" "%TARGET%\*.*" +GOTO EOF + + +REM ----------------------------------------------------- +:EOF +REM ----------------------------------------------------- diff --git a/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedIncluded.thrift b/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedIncluded.thrift new file mode 100644 index 000000000..8b47a50bc --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedIncluded.thrift @@ -0,0 +1,25 @@ +/* + * 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. + */ + +// make sure generated code does not produce name collisions with predefined keywords +namespace delphi SysUtils + +const i32 integer = 42 + +// EOF diff --git a/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedKeywords.dpr b/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedKeywords.dpr new file mode 100644 index 000000000..1fbc8c1d7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedKeywords.dpr @@ -0,0 +1,15 @@ +program ReservedKeywords; + +{$APPTYPE CONSOLE} + +uses + SysUtils, System_; + +begin + try + { TODO -oUser -cConsole Main : Code hier einfügen } + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end; +end. diff --git a/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedKeywords.dproj b/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedKeywords.dproj new file mode 100644 index 000000000..6bd9544bc --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedKeywords.dproj @@ -0,0 +1,112 @@ + <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ProjectGuid>{F2E9B6FC-A931-4271-8E30-5A4E402481B4}</ProjectGuid> + <MainSource>ReservedKeywords.dpr</MainSource> + <ProjectVersion>12.3</ProjectVersion> + <Basis>True</Basis> + <Config Condition="'$(Config)'==''">Debug</Config> + <Platform>Win32</Platform> + <AppType>Console</AppType> + <FrameworkType>None</FrameworkType> + <DCC_DCCCompiler>DCC32</DCC_DCCCompiler> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Basis' or '$(Base)'!=''"> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_1)'!=''"> + <Cfg_1>true</Cfg_1> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_2)'!=''"> + <Cfg_2>true</Cfg_2> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Base)'!=''"> + <DCC_ImageBase>00400000</DCC_ImageBase> + <DCC_DcuOutput>.\$(Config)\$(Platform)</DCC_DcuOutput> + <DCC_UnitSearchPath>gen-delphi;..\..\src;$(DCC_UnitSearchPath)</DCC_UnitSearchPath> + <DCC_UnitAlias>WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;$(DCC_UnitAlias)</DCC_UnitAlias> + <DCC_ExeOutput>.\$(Config)\$(Platform)</DCC_ExeOutput> + <DCC_N>false</DCC_N> + <DCC_S>false</DCC_S> + <DCC_K>false</DCC_K> + <DCC_E>false</DCC_E> + <DCC_F>false</DCC_F> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1)'!=''"> + <DCC_Define>DEBUG;$(DCC_Define)</DCC_Define> + <DCC_Optimize>false</DCC_Optimize> + <DCC_GenerateStackFrames>true</DCC_GenerateStackFrames> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2)'!=''"> + <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols> + <DCC_Define>RELEASE;$(DCC_Define)</DCC_Define> + <DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo> + <DCC_DebugInformation>false</DCC_DebugInformation> + </PropertyGroup> + <ItemGroup> + <DelphiCompile Include="ReservedKeywords.dpr"> + <MainSource>MainSource</MainSource> + </DelphiCompile> + <BuildConfiguration Include="Release"> + <Key>Cfg_2</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + <BuildConfiguration Include="Basis"> + <Key>Base</Key> + </BuildConfiguration> + <BuildConfiguration Include="Debug"> + <Key>Cfg_1</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + </ItemGroup> + <Import Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')" Project="$(BDS)\Bin\CodeGear.Delphi.Targets"/> + <Import Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')" Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj"/> + <PropertyGroup> + <PreBuildEvent><![CDATA[thrift -r -gen delphi ReservedKeywords.thrift]]></PreBuildEvent> + </PropertyGroup> + <ProjectExtensions> + <Borland.Personality>Delphi.Personality.12</Borland.Personality> + <Borland.ProjectType/> + <BorlandProject> + <Delphi.Personality> + <VersionInfo> + <VersionInfo Name="IncludeVerInfo">False</VersionInfo> + <VersionInfo Name="AutoIncBuild">False</VersionInfo> + <VersionInfo Name="MajorVer">1</VersionInfo> + <VersionInfo Name="MinorVer">0</VersionInfo> + <VersionInfo Name="Release">0</VersionInfo> + <VersionInfo Name="Build">0</VersionInfo> + <VersionInfo Name="Debug">False</VersionInfo> + <VersionInfo Name="PreRelease">False</VersionInfo> + <VersionInfo Name="Special">False</VersionInfo> + <VersionInfo Name="Private">False</VersionInfo> + <VersionInfo Name="DLL">False</VersionInfo> + <VersionInfo Name="Locale">1031</VersionInfo> + <VersionInfo Name="CodePage">1252</VersionInfo> + </VersionInfo> + <VersionInfoKeys> + <VersionInfoKeys Name="CompanyName"/> + <VersionInfoKeys Name="FileDescription"/> + <VersionInfoKeys Name="FileVersion">1.0.0.0</VersionInfoKeys> + <VersionInfoKeys Name="InternalName"/> + <VersionInfoKeys Name="LegalCopyright"/> + <VersionInfoKeys Name="LegalTrademarks"/> + <VersionInfoKeys Name="OriginalFilename"/> + <VersionInfoKeys Name="ProductName"/> + <VersionInfoKeys Name="ProductVersion">1.0.0.0</VersionInfoKeys> + <VersionInfoKeys Name="Comments"/> + </VersionInfoKeys> + <Source> + <Source Name="MainSource">ReservedKeywords.dpr</Source> + </Source> + </Delphi.Personality> + <Platforms> + <Platform value="Win32">True</Platform> + </Platforms> + </BorlandProject> + <ProjectFileVersion>12</ProjectFileVersion> + </ProjectExtensions> + </Project> diff --git a/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedKeywords.thrift b/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedKeywords.thrift new file mode 100644 index 000000000..2f49d742c --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/keywords/ReservedKeywords.thrift @@ -0,0 +1,138 @@ +/* + * 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. + */ + +// make sure generated code does not produce name collisions with predefined keywords +namespace delphi System + +include "ReservedIncluded.thrift" + + +typedef i32 Cardinal +typedef string message +typedef list< map< Cardinal, message>> program + +struct unit { + 1: Cardinal downto; + 2: program procedure; +} + +typedef set< unit> units + +exception exception1 { + 1: program message; + 2: unit array; +} + +service constructor { + unit Create(1: Cardinal asm; 2: message inherited) throws (1: exception1 label); + units Destroy(); +} + +const Cardinal downto = +1 +const Cardinal published = -1 + +enum keywords { + record = 1, + repeat = 2, + deprecated = 3 +} + + +struct Struct_lists { + 1: list<Struct_simple> init; + 2: list<Struct_simple> struc; + 3: list<Struct_simple> field; + 4: list<Struct_simple> field_; + 5: list<Struct_simple> tracker; + 6: list<Struct_simple> Self; +} + +struct Struct_structs { + 1: Struct_simple init; + 2: Struct_simple struc; + 3: Struct_simple field; + 4: Struct_simple field_; + 5: Struct_simple tracker; + 6: Struct_simple Self; +} + +struct Struct_simple { + 1: bool init; + 2: bool struc; + 3: bool field; + 4: bool field_; + 5: bool tracker; + 6: bool Self; +} + +struct Struct_strings { + 1: string init; + 2: string struc; + 3: string field; + 4: string field_; + 5: string tracker; + 6: string Self; +} + +struct Struct_binary { + 1: binary init; + 2: binary struc; + 3: binary field; + 4: binary field_; + 5: binary tracker; + 6: binary Self; +} + + +typedef i32 IProtocol +typedef i32 ITransport +typedef i32 IFace +typedef i32 IAsync +typedef i32 System +typedef i32 SysUtils +typedef i32 Generics +typedef i32 Thrift + +struct Struct_Thrift_Names { + 1: IProtocol IProtocol + 2: ITransport ITransport + 3: IFace IFace + 4: IAsync IAsync + 5: System System + 6: SysUtils SysUtils + 7: Generics Generics + 8: Thrift Thrift +} + + +enum Thrift4554_Enum { + Foo = 0, + Bar = 1, + Baz = 2, +} + +struct Thrift4554_Struct { + 1 : optional double MinValue + 2 : optional double MaxValue + 3 : optional bool Integer // causes issue + 4 : optional Thrift4554_Enum Foo +} + + +// EOF diff --git a/src/jaegertracing/thrift/lib/delphi/test/maketest.sh b/src/jaegertracing/thrift/lib/delphi/test/maketest.sh new file mode 100755 index 000000000..8f0639c05 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/maketest.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# +# 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. +# + +../../../compiler/cpp/thrift --gen delphi -o . ../../../test/ThriftTest.thrift + diff --git a/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Client.Main.pas b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Client.Main.pas new file mode 100644 index 000000000..35fdf6f5b --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Client.Main.pas @@ -0,0 +1,131 @@ +(* + * 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. + *) + +unit Multiplex.Client.Main; + +{.$DEFINE StressTest} // activate to stress-test the server with frequent connects/disconnects +{.$DEFINE PerfTest} // activate to activate the performance test + +interface + +uses + Windows, SysUtils, Classes, + DateUtils, + Generics.Collections, + Thrift, + Thrift.Protocol, + Thrift.Protocol.Multiplex, + Thrift.Transport.Pipes, + Thrift.Transport, + Thrift.Stream, + Thrift.Collections, + Benchmark, // in gen-delphi folder + Aggr, // in gen-delphi folder + Multiplex.Test.Common; + +type + TTestClient = class + protected + FProtocol : IProtocol; + + procedure ParseArgs( const args: array of string); + procedure Setup; + procedure Run; + public + constructor Create( const args: array of string); + class procedure Execute( const args: array of string); + end; + +implementation + + +type + IServiceClient = interface + ['{7745C1C2-AB20-43BA-B6F0-08BF92DE0BAC}'] + procedure Test; + end; + +//--- TTestClient ------------------------------------- + + +class procedure TTestClient.Execute( const args: array of string); +var client : TTestClient; +begin + client := TTestClient.Create(args); + try + client.Run; + finally + client.Free; + end; +end; + + +constructor TTestClient.Create( const args: array of string); +begin + inherited Create; + ParseArgs(args); + Setup; +end; + + +procedure TTestClient.ParseArgs( const args: array of string); +begin + if Length(args) <> 0 + then raise Exception.Create('No args accepted so far'); +end; + + +procedure TTestClient.Setup; +var trans : ITransport; +begin + trans := TSocketImpl.Create( 'localhost', 9090); + trans := TFramedTransportImpl.Create( trans); + trans.Open; + FProtocol := TBinaryProtocolImpl.Create( trans, TRUE, TRUE); +end; + + +procedure TTestClient.Run; +var bench : TBenchmarkService.Iface; + aggr : TAggr.Iface; + multiplex : IProtocol; + i : Integer; +begin + try + multiplex := TMultiplexedProtocol.Create( FProtocol, NAME_BENCHMARKSERVICE); + bench := TBenchmarkService.TClient.Create( multiplex); + + multiplex := TMultiplexedProtocol.Create( FProtocol, NAME_AGGR); + aggr := TAggr.TClient.Create( multiplex); + + for i := 1 to 10 + do aggr.addValue( bench.fibonacci(i)); + + for i in aggr.getValues + do Write(IntToStr(i)+' '); + WriteLn; + except + on e:Exception do Writeln(#10+e.Message); + end; +end; + + +end. + + diff --git a/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Server.Main.pas b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Server.Main.pas new file mode 100644 index 000000000..3860f5ace --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Server.Main.pas @@ -0,0 +1,201 @@ +(* + * 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. + *) + +unit Multiplex.Server.Main; + +{$WARN SYMBOL_PLATFORM OFF} + +{.$DEFINE RunEndless} // activate to interactively stress-test the server stop routines via Ctrl+C + +interface + +uses + Windows, SysUtils, + Generics.Collections, + Thrift.Server, + Thrift.Transport, + Thrift.Transport.Pipes, + Thrift.Protocol, + Thrift.Protocol.Multiplex, + Thrift.Processor.Multiplex, + Thrift.Collections, + Thrift.Utils, + Thrift, + Benchmark, // in gen-delphi folder + Aggr, // in gen-delphi folder + Multiplex.Test.Common, + ConsoleHelper, + Contnrs; + +type + TTestServer = class + public type + ITestHandler = interface + ['{CAE09AAB-80FB-48E9-B3A8-7F9B96F5419A}'] + procedure SetServer( const AServer : IServer ); + end; + + protected type + TTestHandlerImpl = class( TInterfacedObject, ITestHandler) + private + FServer : IServer; + protected + // ITestHandler + procedure SetServer( const AServer : IServer ); + + property Server : IServer read FServer write SetServer; + end; + + TBenchmarkServiceImpl = class( TTestHandlerImpl, TBenchmarkService.Iface) + protected + // TBenchmarkService.Iface + function fibonacci(n: ShortInt): Integer; + end; + + + TAggrImpl = class( TTestHandlerImpl, TAggr.Iface) + protected + FList : IThriftList<Integer>; + + // TAggr.Iface + procedure addValue(value: Integer); + function getValues(): IThriftList<Integer>; + public + constructor Create; + destructor Destroy; override; + end; + + public + class procedure Execute( const args: array of string); + end; + + +implementation + + +{ TTestServer.TTestHandlerImpl } + +procedure TTestServer.TTestHandlerImpl.SetServer( const AServer: IServer); +begin + FServer := AServer; +end; + + +{ TTestServer.TBenchmarkServiceImpl } + +function TTestServer.TBenchmarkServiceImpl.fibonacci(n: ShortInt): Integer; +var prev, next : Integer; +begin + prev := 0; + result := 1; + while n > 0 do begin + next := result + prev; + prev := result; + result := next; + Dec(n); + end; +end; + +{ TTestServer.TAggrImpl } + +constructor TTestServer.TAggrImpl.Create; +begin + inherited Create; + FList := TThriftListImpl<Integer>.Create; +end; + + +destructor TTestServer.TAggrImpl.Destroy; +begin + try + FreeAndNil( FList); + finally + inherited Destroy; + end; +end; + + +procedure TTestServer.TAggrImpl.addValue(value: Integer); +begin + FList.Add( value); +end; + + +function TTestServer.TAggrImpl.getValues(): IThriftList<Integer>; +begin + result := FList; +end; + + +{ TTestServer } + +class procedure TTestServer.Execute( const args: array of string); +var + TransportFactory : ITransportFactory; + ProtocolFactory : IProtocolFactory; + ServerTrans : IServerTransport; + benchHandler : TBenchmarkService.Iface; + aggrHandler : TAggr.Iface; + benchProcessor : IProcessor; + aggrProcessor : IProcessor; + multiplex : IMultiplexedProcessor; + ServerEngine : IServer; +begin + try + // create protocol factory, default to BinaryProtocol + ProtocolFactory := TBinaryProtocolImpl.TFactory.Create( TRUE, TRUE); + servertrans := TServerSocketImpl.Create( 9090, 0, FALSE); + TransportFactory := TFramedTransportImpl.TFactory.Create; + + benchHandler := TBenchmarkServiceImpl.Create; + benchProcessor := TBenchmarkService.TProcessorImpl.Create( benchHandler); + + aggrHandler := TAggrImpl.Create; + aggrProcessor := TAggr.TProcessorImpl.Create( aggrHandler); + + multiplex := TMultiplexedProcessorImpl.Create; + multiplex.RegisterProcessor( NAME_BENCHMARKSERVICE, benchProcessor); + multiplex.RegisterProcessor( NAME_AGGR, aggrProcessor); + + ServerEngine := TSimpleServer.Create( multiplex, + ServerTrans, + TransportFactory, + ProtocolFactory); + + (benchHandler as ITestHandler).SetServer( ServerEngine); + (aggrHandler as ITestHandler).SetServer( ServerEngine); + + Console.WriteLine('Starting the server ...'); + ServerEngine.serve(); + + (benchHandler as ITestHandler).SetServer( nil); + (aggrHandler as ITestHandler).SetServer( nil); + + except + on E: Exception do + begin + Console.Write( E.Message); + end; + end; + Console.WriteLine( 'done.'); +end; + + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Test.Client.dpr b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Test.Client.dpr new file mode 100644 index 000000000..a57e93a2e --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Test.Client.dpr @@ -0,0 +1,68 @@ +(* + * 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. + *) + + +program Multiplex.Test.Client; + +{$APPTYPE CONSOLE} + +uses + SysUtils, + Multiplex.Client.Main in 'Multiplex.Client.Main.pas', + Thrift in '..\..\src\Thrift.pas', + Thrift.Socket in '..\..\src\Thrift.Socket.pas', + Thrift.Exception in '..\..\src\Thrift.Exception.pas', + Thrift.Transport in '..\..\src\Thrift.Transport.pas', + Thrift.Transport.Pipes in '..\..\src\Thrift.Transport.Pipes.pas', + Thrift.Protocol in '..\..\src\Thrift.Protocol.pas', + Thrift.Protocol.Multiplex in '..\..\src\Thrift.Protocol.Multiplex.pas', + Thrift.Collections in '..\..\src\Thrift.Collections.pas', + Thrift.Server in '..\..\src\Thrift.Server.pas', + Thrift.Stream in '..\..\src\Thrift.Stream.pas', + Thrift.TypeRegistry in '..\..\src\Thrift.TypeRegistry.pas', + Thrift.WinHTTP in '..\..\src\Thrift.WinHTTP.pas', + Thrift.Utils in '..\..\src\Thrift.Utils.pas'; + +var + nParamCount : Integer; + args : array of string; + i : Integer; + arg : string; + s : string; + +begin + try + Writeln( 'Multiplex TestClient '+Thrift.Version); + nParamCount := ParamCount; + SetLength( args, nParamCount); + for i := 1 to nParamCount do + begin + arg := ParamStr( i ); + args[i-1] := arg; + end; + TTestClient.Execute( args ); + Readln; + except + on E: Exception do begin + Writeln(E.ClassName, ': ', E.Message); + ExitCode := $FFFF; + end; + end; +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Test.Common.pas b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Test.Common.pas new file mode 100644 index 000000000..2caf08108 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Test.Common.pas @@ -0,0 +1,35 @@ +(* + * 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. + *) + +unit Multiplex.Test.Common; + +interface + +const + NAME_BENCHMARKSERVICE = 'BenchmarkService'; + NAME_AGGR = 'Aggr'; + + +implementation + +// nix + +end. + + diff --git a/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Test.Server.dpr b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Test.Server.dpr new file mode 100644 index 000000000..81ed3ddc4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/multiplexed/Multiplex.Test.Server.dpr @@ -0,0 +1,69 @@ +(* + * 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. + *) + +program Multiplex.Test.Server; + +{$APPTYPE CONSOLE} + +uses + SysUtils, + Multiplex.Server.Main in 'Multiplex.Server.Main.pas', + ConsoleHelper in '..\ConsoleHelper.pas', + Thrift in '..\..\src\Thrift.pas', + Thrift.Exception in '..\..\src\Thrift.Exception.pas', + Thrift.Socket in '..\..\src\Thrift.Socket.pas', + Thrift.Transport in '..\..\src\Thrift.Transport.pas', + Thrift.Transport.Pipes in '..\..\src\Thrift.Transport.Pipes.pas', + Thrift.Protocol in '..\..\src\Thrift.Protocol.pas', + Thrift.Protocol.Multiplex in '..\..\src\Thrift.Protocol.Multiplex.pas', + Thrift.Processor.Multiplex in '..\..\src\Thrift.Processor.Multiplex.pas', + Thrift.Collections in '..\..\src\Thrift.Collections.pas', + Thrift.Server in '..\..\src\Thrift.Server.pas', + Thrift.Utils in '..\..\src\Thrift.Utils.pas', + Thrift.WinHTTP in '..\..\src\Thrift.WinHTTP.pas', + Thrift.TypeRegistry in '..\..\src\Thrift.TypeRegistry.pas', + Thrift.Stream in '..\..\src\Thrift.Stream.pas'; + +var + nParamCount : Integer; + args : array of string; + i : Integer; + arg : string; + s : string; + +begin + try + Writeln( 'Multiplex TestServer '+Thrift.Version); + nParamCount := ParamCount; + SetLength( args, nParamCount); + for i := 1 to nParamCount do + begin + arg := ParamStr( i ); + args[i-1] := arg; + end; + TTestServer.Execute( args ); + Writeln('Press ENTER to close ... '); Readln; + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end; +end. + + + diff --git a/src/jaegertracing/thrift/lib/delphi/test/serializer/TestSerializer.Data.pas b/src/jaegertracing/thrift/lib/delphi/test/serializer/TestSerializer.Data.pas new file mode 100644 index 000000000..2420e9a2f --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/serializer/TestSerializer.Data.pas @@ -0,0 +1,354 @@ +(* + * 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. + *) + +unit TestSerializer.Data; + +interface + +uses + SysUtils, + Thrift.Collections, + DebugProtoTest; + + +type + Fixtures = class + public + class function CreateOneOfEach : IOneOfEach; + class function CreateNesting : INesting; + class function CreateHolyMoley : IHolyMoley; + class function CreateCompactProtoTestStruct : ICompactProtoTestStruct; + + // These byte arrays are serialized versions of the above structs. + // They were serialized in binary protocol using thrift 0.6.x and are used to + // test backwards compatibility with respect to the standard scheme. + (* + all data copied from JAVA version, + to be used later + + public static final byte[] persistentBytesOneOfEach = new byte[] { + $02, $00, $01, $01, $02, $00, $02, $00, $03, $00, + $03, $D6, $06, $00, $04, $69, $78, $08, $00, $05, + $01, $00, $00, $00, $0A, $00, $06, $00, $00, $00, + $01, $65, $A0, $BC, $00, $04, $00, $07, $40, $09, + $21, $FB, $54, $44, $2D, $18, $0B, $00, $08, $00, + $00, $00, $0D, $4A, $53, $4F, $4E, $20, $54, $48, + $49, $53, $21, $20, $22, $01, $0B, $00, $09, $00, + $00, $00, $2E, $D3, $80, $E2, $85, $AE, $CE, $9D, + $20, $D0, $9D, $CE, $BF, $E2, $85, $BF, $D0, $BE, + $C9, $A1, $D0, $B3, $D0, $B0, $CF, $81, $E2, $84, + $8E, $20, $CE, $91, $74, $74, $CE, $B1, $E2, $85, + $BD, $CE, $BA, $EF, $BF, $BD, $E2, $80, $BC, $02, + $00, $0A, $00, $0B, $00, $0B, $00, $00, $00, $06, + $62, $61, $73, $65, $36, $34, $0F, $00, $0C, $03, + $00, $00, $00, $03, $01, $02, $03, $0F, $00, $0D, + $06, $00, $00, $00, $03, $00, $01, $00, $02, $00, + $03, $0F, $00, $0E, $0A, $00, $00, $00, $03, $00, + $00, $00, $00, $00, $00, $00, $01, $00, $00, $00, + $00, $00, $00, $00, $02, $00, $00, $00, $00, $00, + $00, $00, $03, $00 }; + + + public static final byte[] persistentBytesNesting = new byte[] { + $0C, $00, $01, $08, $00, $01, $00, $00, $7A, $69, + $0B, $00, $02, $00, $00, $00, $13, $49, $20, $61, + $6D, $20, $61, $20, $62, $6F, $6E, $6B, $2E, $2E, + $2E, $20, $78, $6F, $72, $21, $00, $0C, $00, $02, + $02, $00, $01, $01, $02, $00, $02, $00, $03, $00, + $03, $D6, $06, $00, $04, $69, $78, $08, $00, $05, + $01, $00, $00, $00, $0A, $00, $06, $00, $00, $00, + $01, $65, $A0, $BC, $00, $04, $00, $07, $40, $09, + $21, $FB, $54, $44, $2D, $18, $0B, $00, $08, $00, + $00, $00, $0D, $4A, $53, $4F, $4E, $20, $54, $48, + $49, $53, $21, $20, $22, $01, $0B, $00, $09, $00, + $00, $00, $2E, $D3, $80, $E2, $85, $AE, $CE, $9D, + $20, $D0, $9D, $CE, $BF, $E2, $85, $BF, $D0, $BE, + $C9, $A1, $D0, $B3, $D0, $B0, $CF, $81, $E2, $84, + $8E, $20, $CE, $91, $74, $74, $CE, $B1, $E2, $85, + $BD, $CE, $BA, $EF, $BF, $BD, $E2, $80, $BC, $02, + $00, $0A, $00, $0B, $00, $0B, $00, $00, $00, $06, + $62, $61, $73, $65, $36, $34, $0F, $00, $0C, $03, + $00, $00, $00, $03, $01, $02, $03, $0F, $00, $0D, + $06, $00, $00, $00, $03, $00, $01, $00, $02, $00, + $03, $0F, $00, $0E, $0A, $00, $00, $00, $03, $00, + $00, $00, $00, $00, $00, $00, $01, $00, $00, $00, + $00, $00, $00, $00, $02, $00, $00, $00, $00, $00, + $00, $00, $03, $00, $00 }; + + public static final byte[] persistentBytesHolyMoley = new byte[] { + $0F, $00, $01, $0C, $00, $00, $00, $02, $02, $00, + $01, $01, $02, $00, $02, $00, $03, $00, $03, $23, + $06, $00, $04, $69, $78, $08, $00, $05, $01, $00, + $00, $00, $0A, $00, $06, $00, $00, $00, $01, $65, + $A0, $BC, $00, $04, $00, $07, $40, $09, $21, $FB, + $54, $44, $2D, $18, $0B, $00, $08, $00, $00, $00, + $0D, $4A, $53, $4F, $4E, $20, $54, $48, $49, $53, + $21, $20, $22, $01, $0B, $00, $09, $00, $00, $00, + $2E, $D3, $80, $E2, $85, $AE, $CE, $9D, $20, $D0, + $9D, $CE, $BF, $E2, $85, $BF, $D0, $BE, $C9, $A1, + $D0, $B3, $D0, $B0, $CF, $81, $E2, $84, $8E, $20, + $CE, $91, $74, $74, $CE, $B1, $E2, $85, $BD, $CE, + $BA, $EF, $BF, $BD, $E2, $80, $BC, $02, $00, $0A, + $00, $0B, $00, $0B, $00, $00, $00, $06, $62, $61, + $73, $65, $36, $34, $0F, $00, $0C, $03, $00, $00, + $00, $03, $01, $02, $03, $0F, $00, $0D, $06, $00, + $00, $00, $03, $00, $01, $00, $02, $00, $03, $0F, + $00, $0E, $0A, $00, $00, $00, $03, $00, $00, $00, + $00, $00, $00, $00, $01, $00, $00, $00, $00, $00, + $00, $00, $02, $00, $00, $00, $00, $00, $00, $00, + $03, $00, $02, $00, $01, $01, $02, $00, $02, $00, + $03, $00, $03, $D6, $06, $00, $04, $69, $78, $08, + $00, $05, $01, $00, $00, $00, $0A, $00, $06, $00, + $00, $00, $01, $65, $A0, $BC, $00, $04, $00, $07, + $40, $09, $21, $FB, $54, $44, $2D, $18, $0B, $00, + $08, $00, $00, $00, $0D, $4A, $53, $4F, $4E, $20, + $54, $48, $49, $53, $21, $20, $22, $01, $0B, $00, + $09, $00, $00, $00, $2E, $D3, $80, $E2, $85, $AE, + $CE, $9D, $20, $D0, $9D, $CE, $BF, $E2, $85, $BF, + $D0, $BE, $C9, $A1, $D0, $B3, $D0, $B0, $CF, $81, + $E2, $84, $8E, $20, $CE, $91, $74, $74, $CE, $B1, + $E2, $85, $BD, $CE, $BA, $EF, $BF, $BD, $E2, $80, + $BC, $02, $00, $0A, $00, $0B, $00, $0B, $00, $00, + $00, $06, $62, $61, $73, $65, $36, $34, $0F, $00, + $0C, $03, $00, $00, $00, $03, $01, $02, $03, $0F, + $00, $0D, $06, $00, $00, $00, $03, $00, $01, $00, + $02, $00, $03, $0F, $00, $0E, $0A, $00, $00, $00, + $03, $00, $00, $00, $00, $00, $00, $00, $01, $00, + $00, $00, $00, $00, $00, $00, $02, $00, $00, $00, + $00, $00, $00, $00, $03, $00, $0E, $00, $02, $0F, + $00, $00, $00, $03, $0B, $00, $00, $00, $00, $0B, + $00, $00, $00, $03, $00, $00, $00, $0F, $74, $68, + $65, $6E, $20, $61, $20, $6F, $6E, $65, $2C, $20, + $74, $77, $6F, $00, $00, $00, $06, $74, $68, $72, + $65, $65, $21, $00, $00, $00, $06, $46, $4F, $55, + $52, $21, $21, $0B, $00, $00, $00, $02, $00, $00, + $00, $09, $61, $6E, $64, $20, $61, $20, $6F, $6E, + $65, $00, $00, $00, $09, $61, $6E, $64, $20, $61, + $20, $74, $77, $6F, $0D, $00, $03, $0B, $0F, $00, + $00, $00, $03, $00, $00, $00, $03, $74, $77, $6F, + $0C, $00, $00, $00, $02, $08, $00, $01, $00, $00, + $00, $01, $0B, $00, $02, $00, $00, $00, $05, $57, + $61, $69, $74, $2E, $00, $08, $00, $01, $00, $00, + $00, $02, $0B, $00, $02, $00, $00, $00, $05, $57, + $68, $61, $74, $3F, $00, $00, $00, $00, $05, $74, + $68, $72, $65, $65, $0C, $00, $00, $00, $00, $00, + $00, $00, $04, $7A, $65, $72, $6F, $0C, $00, $00, + $00, $00, $00 }; + + +*) + + private + const + kUnicodeBytes : packed array[0..43] of Byte + = ( $d3, $80, $e2, $85, $ae, $ce, $9d, $20, $d0, $9d, + $ce, $bf, $e2, $85, $bf, $d0, $be, $c9, $a1, $d0, + $b3, $d0, $b0, $cf, $81, $e2, $84, $8e, $20, $ce, + $91, $74, $74, $ce, $b1, $e2, $85, $bd, $ce, $ba, + $83, $e2, $80, $bc); + + end; + + +implementation + + +class function Fixtures.CreateOneOfEach : IOneOfEach; +var db : Double; + us : Utf8String; +begin + result := TOneOfEachImpl.Create; + result.setIm_true( TRUE); + result.setIm_false( FALSE); + result.setA_bite( ShortInt($D6)); + result.setInteger16( 27000); + result.setInteger32( 1 shl 24); + result.setInteger64( Int64(6000) * Int64(1000) * Int64(1000)); + db := Pi; + result.setDouble_precision( db); + result.setSome_characters( 'JSON THIS! \"\1'); + + // ?? + SetLength( us, Length(kUnicodeBytes)); + Move( kUnicodeBytes[0], us[1], Length(kUnicodeBytes)); + // ?? + SetString( us, PChar(@kUnicodeBytes[0]), Length(kUnicodeBytes)); + // !! + result.setZomg_unicode( UnicodeString( us)); + + {$IF cDebugProtoTest_Option_AnsiStr_Binary} + result.SetBase64('base64'); + {$ELSE} + result.SetBase64( TEncoding.UTF8.GetBytes('base64')); + {$IFEND} + + // byte, i16, and i64 lists are populated by default constructor +end; + + +class function Fixtures.CreateNesting : INesting; +var bonk : IBonk; +begin + bonk := TBonkImpl.Create; + bonk.Type_ := 31337; + bonk.Message := 'I am a bonk... xor!'; + + result := TNestingImpl.Create; + result.My_bonk := bonk; + result.My_ooe := CreateOneOfEach; +end; + + +class function Fixtures.CreateHolyMoley : IHolyMoley; +var big : IThriftList<IOneOfEach>; + stage1 : IThriftList<String>; + stage2 : IThriftList<IBonk>; + b : IBonk; +begin + result := THolyMoleyImpl.Create; + + big := TThriftListImpl<IOneOfEach>.Create; + big.add( CreateOneOfEach); + big.add( CreateNesting.my_ooe); + result.Big := big; + result.Big[0].setA_bite( $22); + result.Big[0].setA_bite( $23); + + result.Contain := THashSetImpl< IThriftList<string>>.Create; + stage1 := TThriftListImpl<String>.Create; + stage1.add( 'and a one'); + stage1.add( 'and a two'); + result.Contain.add( stage1); + + stage1 := TThriftListImpl<String>.Create; + stage1.add( 'then a one, two'); + stage1.add( 'three!'); + stage1.add( 'FOUR!!'); + result.Contain.add( stage1); + + stage1 := TThriftListImpl<String>.Create; + result.Contain.add( stage1); + + stage2 := TThriftListImpl<IBonk>.Create; + result.Bonks := TThriftDictionaryImpl< String, IThriftList< IBonk>>.Create; + // one empty + result.Bonks.Add( 'zero', stage2); + + // one with two + stage2 := TThriftListImpl<IBonk>.Create; + b := TBonkImpl.Create; + b.type_ := 1; + b.message := 'Wait.'; + stage2.Add( b); + b := TBonkImpl.Create; + b.type_ := 2; + b.message := 'What?'; + stage2.Add( b); + result.Bonks.Add( 'two', stage2); + + // one with three + stage2 := TThriftListImpl<IBonk>.Create; + b := TBonkImpl.Create; + b.type_ := 3; + b.message := 'quoth'; + stage2.Add( b); + b := TBonkImpl.Create; + b.type_ := 4; + b.message := 'the raven'; + stage2.Add( b); + b := TBonkImpl.Create; + b.type_ := 5; + b.message := 'nevermore'; + stage2.Add( b); + result.bonks.Add( 'three', stage2); +end; + + +class function Fixtures.CreateCompactProtoTestStruct : ICompactProtoTestStruct; +// superhuge compact proto test struct +begin + result := TCompactProtoTestStructImpl.Create; + result.A_byte := TDebugProtoTestConstants.COMPACT_TEST.A_byte; + result.A_i16 := TDebugProtoTestConstants.COMPACT_TEST.A_i16; + result.A_i32 := TDebugProtoTestConstants.COMPACT_TEST.A_i32; + result.A_i64 := TDebugProtoTestConstants.COMPACT_TEST.A_i64; + result.A_double := TDebugProtoTestConstants.COMPACT_TEST.A_double; + result.A_string := TDebugProtoTestConstants.COMPACT_TEST.A_string; + result.A_binary := TDebugProtoTestConstants.COMPACT_TEST.A_binary; + result.True_field := TDebugProtoTestConstants.COMPACT_TEST.True_field; + result.False_field := TDebugProtoTestConstants.COMPACT_TEST.False_field; + result.Empty_struct_field := TDebugProtoTestConstants.COMPACT_TEST.Empty_struct_field; + result.Byte_list := TDebugProtoTestConstants.COMPACT_TEST.Byte_list; + result.I16_list := TDebugProtoTestConstants.COMPACT_TEST.I16_list; + result.I32_list := TDebugProtoTestConstants.COMPACT_TEST.I32_list; + result.I64_list := TDebugProtoTestConstants.COMPACT_TEST.I64_list; + result.Double_list := TDebugProtoTestConstants.COMPACT_TEST.Double_list; + result.String_list := TDebugProtoTestConstants.COMPACT_TEST.String_list; + result.Binary_list := TDebugProtoTestConstants.COMPACT_TEST.Binary_list; + result.Boolean_list := TDebugProtoTestConstants.COMPACT_TEST.Boolean_list; + result.Struct_list := TDebugProtoTestConstants.COMPACT_TEST.Struct_list; + result.Byte_set := TDebugProtoTestConstants.COMPACT_TEST.Byte_set; + result.I16_set := TDebugProtoTestConstants.COMPACT_TEST.I16_set; + result.I32_set := TDebugProtoTestConstants.COMPACT_TEST.I32_set; + result.I64_set := TDebugProtoTestConstants.COMPACT_TEST.I64_set; + result.Double_set := TDebugProtoTestConstants.COMPACT_TEST.Double_set; + result.String_set := TDebugProtoTestConstants.COMPACT_TEST.String_set; + result.String_set := TDebugProtoTestConstants.COMPACT_TEST.String_set; + result.String_set := TDebugProtoTestConstants.COMPACT_TEST.String_set; + result.Binary_set := TDebugProtoTestConstants.COMPACT_TEST.Binary_set; + result.Boolean_set := TDebugProtoTestConstants.COMPACT_TEST.Boolean_set; + result.Struct_set := TDebugProtoTestConstants.COMPACT_TEST.Struct_set; + result.Byte_byte_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_byte_map; + result.I16_byte_map := TDebugProtoTestConstants.COMPACT_TEST.I16_byte_map; + result.I32_byte_map := TDebugProtoTestConstants.COMPACT_TEST.I32_byte_map; + result.I64_byte_map := TDebugProtoTestConstants.COMPACT_TEST.I64_byte_map; + result.Double_byte_map := TDebugProtoTestConstants.COMPACT_TEST.Double_byte_map; + result.String_byte_map := TDebugProtoTestConstants.COMPACT_TEST.String_byte_map; + result.Binary_byte_map := TDebugProtoTestConstants.COMPACT_TEST.Binary_byte_map; + result.Boolean_byte_map := TDebugProtoTestConstants.COMPACT_TEST.Boolean_byte_map; + result.Byte_i16_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_i16_map; + result.Byte_i32_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_i32_map; + result.Byte_i64_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_i64_map; + result.Byte_double_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_double_map; + result.Byte_string_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_string_map; + result.Byte_binary_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_binary_map; + result.Byte_boolean_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_boolean_map; + result.List_byte_map := TDebugProtoTestConstants.COMPACT_TEST.List_byte_map; + result.Set_byte_map := TDebugProtoTestConstants.COMPACT_TEST.Set_byte_map; + result.Map_byte_map := TDebugProtoTestConstants.COMPACT_TEST.Map_byte_map; + result.Byte_map_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_map_map; + result.Byte_set_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_set_map; + result.Byte_list_map := TDebugProtoTestConstants.COMPACT_TEST.Byte_list_map; + + result.Field500 := 500; + result.Field5000 := 5000; + result.Field20000 := 20000; + + {$IF cDebugProtoTest_Option_AnsiStr_Binary} + result.A_binary := AnsiString( #0#1#2#3#4#5#6#7#8); + {$ELSE} + result.A_binary := TEncoding.UTF8.GetBytes( #0#1#2#3#4#5#6#7#8); + {$IFEND} +end; + + + + +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/serializer/TestSerializer.dpr b/src/jaegertracing/thrift/lib/delphi/test/serializer/TestSerializer.dpr new file mode 100644 index 000000000..56d0d15d4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/serializer/TestSerializer.dpr @@ -0,0 +1,283 @@ +(* + * 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. + *) + +program TestSerializer; + +{$APPTYPE CONSOLE} + +uses + Classes, Windows, SysUtils, Generics.Collections, + Thrift in '..\..\src\Thrift.pas', + Thrift.Exception in '..\..\src\Thrift.Exception.pas', + Thrift.Socket in '..\..\src\Thrift.Socket.pas', + 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.Server in '..\..\src\Thrift.Server.pas', + Thrift.Utils in '..\..\src\Thrift.Utils.pas', + Thrift.Serializer in '..\..\src\Thrift.Serializer.pas', + Thrift.Stream in '..\..\src\Thrift.Stream.pas', + Thrift.WinHTTP in '..\..\src\Thrift.WinHTTP.pas', + Thrift.TypeRegistry in '..\..\src\Thrift.TypeRegistry.pas', + System_, + DebugProtoTest, + TestSerializer.Data; + + + +type + TTestSerializer = class //extends TestCase { + private type + TMethod = ( + mt_Bytes, + mt_Stream + ); + + private + FProtocols : TList< IProtocolFactory>; + + class function Serialize(const input : IBase; const factory : IProtocolFactory) : TBytes; overload; + class procedure Serialize(const input : IBase; const factory : IProtocolFactory; const aStream : TStream); overload; + class procedure Deserialize( const input : TBytes; const target : IBase; const factory : IProtocolFactory); overload; + class procedure Deserialize( const input : TStream; const target : IBase; const factory : IProtocolFactory); overload; + + procedure Test_Serializer_Deserializer; + procedure Test_OneOfEach( const method : TMethod; const factory : IProtocolFactory; const stream : TFileStream); + procedure Test_CompactStruct( const method : TMethod; const factory : IProtocolFactory; const stream : TFileStream); + + public + constructor Create; + destructor Destroy; override; + + procedure RunTests; + end; + + + +{ TTestSerializer } + +constructor TTestSerializer.Create; +begin + inherited Create; + FProtocols := TList< IProtocolFactory>.Create; + FProtocols.Add( TBinaryProtocolImpl.TFactory.Create); + FProtocols.Add( TCompactProtocolImpl.TFactory.Create); + FProtocols.Add( TJSONProtocolImpl.TFactory.Create); +end; + + +destructor TTestSerializer.Destroy; +begin + try + FreeAndNil( FProtocols); + finally + inherited Destroy; + end; +end; + + +procedure TTestSerializer.Test_OneOfEach( const method : TMethod; const factory : IProtocolFactory; const stream : TFileStream); +var tested, correct : IOneOfEach; + bytes : TBytes; + i : Integer; +begin + // write + tested := Fixtures.CreateOneOfEach; + case method of + mt_Bytes: bytes := Serialize( tested, factory); + mt_Stream: begin + stream.Size := 0; + Serialize( tested, factory, stream); + end + else + ASSERT( FALSE); + end; + + // init + read + tested := TOneOfEachImpl.Create; + case method of + mt_Bytes: Deserialize( bytes, tested, factory); + mt_Stream: begin + stream.Position := 0; + Deserialize( stream, tested, factory); + end + else + ASSERT( FALSE); + end; + + // check + correct := Fixtures.CreateOneOfEach; + ASSERT( tested.Im_true = correct.Im_true); + ASSERT( tested.Im_false = correct.Im_false); + ASSERT( tested.A_bite = correct.A_bite); + ASSERT( tested.Integer16 = correct.Integer16); + ASSERT( tested.Integer32 = correct.Integer32); + ASSERT( tested.Integer64 = correct.Integer64); + ASSERT( Abs( tested.Double_precision - correct.Double_precision) < 1E-12); + ASSERT( tested.Some_characters = correct.Some_characters); + ASSERT( tested.Zomg_unicode = correct.Zomg_unicode); + ASSERT( tested.What_who = correct.What_who); + + ASSERT( Length(tested.Base64) = Length(correct.Base64)); + ASSERT( CompareMem( @tested.Base64[0], @correct.Base64[0], Length(correct.Base64))); + + ASSERT( tested.Byte_list.Count = correct.Byte_list.Count); + for i := 0 to tested.Byte_list.Count-1 + do ASSERT( tested.Byte_list[i] = correct.Byte_list[i]); + + ASSERT( tested.I16_list.Count = correct.I16_list.Count); + for i := 0 to tested.I16_list.Count-1 + do ASSERT( tested.I16_list[i] = correct.I16_list[i]); + + ASSERT( tested.I64_list.Count = correct.I64_list.Count); + for i := 0 to tested.I64_list.Count-1 + do ASSERT( tested.I64_list[i] = correct.I64_list[i]); +end; + + +procedure TTestSerializer.Test_CompactStruct( const method : TMethod; const factory : IProtocolFactory; const stream : TFileStream); +var tested, correct : ICompactProtoTestStruct; + bytes : TBytes; +begin + // write + tested := Fixtures.CreateCompactProtoTestStruct; + case method of + mt_Bytes: bytes := Serialize( tested, factory); + mt_Stream: begin + stream.Size := 0; + Serialize( tested, factory, stream); + end + else + ASSERT( FALSE); + end; + + // init + read + correct := TCompactProtoTestStructImpl.Create; + case method of + mt_Bytes: Deserialize( bytes, tested, factory); + mt_Stream: begin + stream.Position := 0; + Deserialize( stream, tested, factory); + end + else + ASSERT( FALSE); + end; + + // check + correct := Fixtures.CreateCompactProtoTestStruct; + ASSERT( correct.Field500 = tested.Field500); + ASSERT( correct.Field5000 = tested.Field5000); + ASSERT( correct.Field20000 = tested.Field20000); +end; + + +procedure TTestSerializer.Test_Serializer_Deserializer; +var factory : IProtocolFactory; + stream : TFileStream; + method : TMethod; +begin + stream := TFileStream.Create( 'TestSerializer.dat', fmCreate); + try + + for method in [Low(TMethod)..High(TMethod)] do begin + for factory in FProtocols do begin + + Test_OneOfEach( method, factory, stream); + Test_CompactStruct( method, factory, stream); + end; + end; + + finally + stream.Free; + end; +end; + + +procedure TTestSerializer.RunTests; +begin + try + Test_Serializer_Deserializer; + except + on e:Exception do begin + Writeln( e.Message); + Write('Hit ENTER to close ... '); Readln; + end; + end; +end; + + +class function TTestSerializer.Serialize(const input : IBase; const factory : IProtocolFactory) : TBytes; +var serial : TSerializer; +begin + serial := TSerializer.Create( factory); + try + result := serial.Serialize( input); + finally + serial.Free; + end; +end; + + +class procedure TTestSerializer.Serialize(const input : IBase; const factory : IProtocolFactory; const aStream : TStream); +var serial : TSerializer; +begin + serial := TSerializer.Create( factory); + try + serial.Serialize( input, aStream); + finally + serial.Free; + end; +end; + + +class procedure TTestSerializer.Deserialize( const input : TBytes; const target : IBase; const factory : IProtocolFactory); +var serial : TDeserializer; +begin + serial := TDeserializer.Create( factory); + try + serial.Deserialize( input, target); + finally + serial.Free; + end; +end; + +class procedure TTestSerializer.Deserialize( const input : TStream; const target : IBase; const factory : IProtocolFactory); +var serial : TDeserializer; +begin + serial := TDeserializer.Create( factory); + try + serial.Deserialize( input, target); + finally + serial.Free; + end; +end; + + +var test : TTestSerializer; +begin + test := TTestSerializer.Create; + try + test.RunTests; + finally + test.Free; + end; +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/server.dpr b/src/jaegertracing/thrift/lib/delphi/test/server.dpr new file mode 100644 index 000000000..9731dd4fa --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/server.dpr @@ -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. + *) + +program server; + +{$APPTYPE CONSOLE} + +uses + SysUtils, + TestServer in 'TestServer.pas', + TestServerEvents in 'TestServerEvents.pas', + Thrift.Test, // in gen-delphi folder + Thrift in '..\src\Thrift.pas', + Thrift.Exception in '..\src\Thrift.Exception.pas', + Thrift.Transport in '..\src\Thrift.Transport.pas', + Thrift.Socket in '..\src\Thrift.Socket.pas', + Thrift.Transport.Pipes in '..\src\Thrift.Transport.Pipes.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.Protocol.Multiplex in '..\src\Thrift.Protocol.Multiplex.pas', + Thrift.Processor.Multiplex in '..\src\Thrift.Processor.Multiplex.pas', + Thrift.Collections in '..\src\Thrift.Collections.pas', + Thrift.Server in '..\src\Thrift.Server.pas', + Thrift.TypeRegistry in '..\src\Thrift.TypeRegistry.pas', + Thrift.Utils in '..\src\Thrift.Utils.pas', + Thrift.WinHTTP in '..\src\Thrift.WinHTTP.pas', + Thrift.Stream in '..\src\Thrift.Stream.pas'; + +var + nParamCount : Integer; + args : array of string; + i : Integer; + arg : string; + +begin + try + Writeln( 'Delphi TestServer '+Thrift.Version); + nParamCount := ParamCount; + SetLength( args, nParamCount); + for i := 1 to nParamCount do begin + arg := ParamStr( i ); + args[i-1] := arg; + end; + + TTestServer.Execute( args ); + + except + on E: EAbort do begin + ExitCode := $FF; + end; + on E: Exception do begin + Writeln(E.ClassName, ': ', E.Message); + ExitCode := $FF; + end; + end; +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/skip/README.md b/src/jaegertracing/thrift/lib/delphi/test/skip/README.md new file mode 100644 index 000000000..f34936834 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/skip/README.md @@ -0,0 +1,11 @@ +These two projects belong together. Both programs +simulate server and client for different versions +of the same protocol. + +The intention of this test is to ensure fully +working compatibility features of the Delphi Thrift +implementation. + +The expected test result is, that no errors occur +with both programs, regardless in which order they +might be started. diff --git a/src/jaegertracing/thrift/lib/delphi/test/skip/idl/skiptest_version_1.thrift b/src/jaegertracing/thrift/lib/delphi/test/skip/idl/skiptest_version_1.thrift new file mode 100644 index 000000000..8353c5e12 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/skip/idl/skiptest_version_1.thrift @@ -0,0 +1,45 @@ +/* + * 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. + */ + + +// version 1 of the interface + +namespace * Skiptest.One + +const i32 SKIPTESTSERVICE_VERSION = 1 + +struct Pong { + 1 : optional i32 version1 +} + +struct Ping { + 1 : optional i32 version1 +} + +exception PongFailed { + 222 : optional i32 pongErrorCode +} + + +service SkipTestService { + void PingPong( 1: Ping pong) throws (444: PongFailed pof); +} + + +// EOF
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/delphi/test/skip/idl/skiptest_version_2.thrift b/src/jaegertracing/thrift/lib/delphi/test/skip/idl/skiptest_version_2.thrift new file mode 100644 index 000000000..f3352d327 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/skip/idl/skiptest_version_2.thrift @@ -0,0 +1,69 @@ +/* + * 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. + */ + + +// version 2 of the interface + +namespace * Skiptest.Two + +const i32 SKIPTESTSERVICE_VERSION = 2 + +struct Pong { + 1 : optional i32 version1 + 2 : optional i16 version2 +} + +struct Ping { + 1 : optional i32 version1 + 10 : optional bool boolVal + 11 : optional byte byteVal + 12 : optional double dbVal + 13 : optional i16 i16Val + 14 : optional i32 i32Val + 15 : optional i64 i64Val + 16 : optional string strVal + 17 : optional Pong structVal + 18 : optional map< list< Pong>, set< string>> mapVal +} + +exception PingFailed { + 1 : optional i32 pingErrorCode +} + +exception PongFailed { + 222 : optional i32 pongErrorCode + 10 : optional bool boolVal + 11 : optional byte byteVal + 12 : optional double dbVal + 13 : optional i16 i16Val + 14 : optional i32 i32Val + 15 : optional i64 i64Val + 16 : optional string strVal + 17 : optional Pong structVal + 18 : optional map< list< Pong>, set< string>> mapVal +} + + +service SkipTestService { + Ping PingPong( 1: Ping ping, 3: Pong pong) throws (1: PingFailed pif, 444: PongFailed pof); +} + + +// EOF + diff --git a/src/jaegertracing/thrift/lib/delphi/test/skip/skiptest_version1.dpr b/src/jaegertracing/thrift/lib/delphi/test/skip/skiptest_version1.dpr new file mode 100644 index 000000000..0bfe96fef --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/skip/skiptest_version1.dpr @@ -0,0 +1,202 @@ +(* + * 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. + *) + +program skiptest_version1; + +{$APPTYPE CONSOLE} + +uses + Classes, Windows, SysUtils, + Skiptest.One, + Thrift in '..\..\src\Thrift.pas', + Thrift.Exception in '..\..\src\Thrift.Exception.pas', + Thrift.Socket in '..\..\src\Thrift.Socket.pas', + Thrift.Transport in '..\..\src\Thrift.Transport.pas', + Thrift.Protocol in '..\..\src\Thrift.Protocol.pas', + Thrift.Protocol.JSON in '..\..\src\Thrift.Protocol.JSON.pas', + Thrift.Collections in '..\..\src\Thrift.Collections.pas', + Thrift.Server in '..\..\src\Thrift.Server.pas', + Thrift.Utils in '..\..\src\Thrift.Utils.pas', + Thrift.WinHTTP in '..\..\src\Thrift.WinHTTP.pas', + Thrift.TypeRegistry in '..\..\src\Thrift.TypeRegistry.pas', + Thrift.Stream in '..\..\src\Thrift.Stream.pas'; + +const + REQUEST_EXT = '.request'; + RESPONSE_EXT = '.response'; + + +function CreatePing : IPing; +begin + result := TPingImpl.Create; + result.Version1 := Tskiptest_version_1Constants.SKIPTESTSERVICE_VERSION; +end; + + +type + TDummyServer = class( TInterfacedObject, TSkipTestService.Iface) + protected + // TSkipTestService.Iface + procedure PingPong(const ping: IPing); + end; + + +procedure TDummyServer.PingPong(const ping: IPing); +// TSkipTestService.Iface +begin + Writeln('- performing request from version '+IntToStr(ping.Version1)+' client'); +end; + + +function CreateProtocol( protfact : IProtocolFactory; stm : TStream; aForInput : Boolean) : IProtocol; +var adapt : IThriftStream; + trans : ITransport; +begin + adapt := TThriftStreamAdapterDelphi.Create( stm, FALSE); + if aForInput + then trans := TStreamTransportImpl.Create( adapt, nil) + else trans := TStreamTransportImpl.Create( nil, adapt); + result := protfact.GetProtocol( trans); +end; + + +procedure CreateRequest( 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 +begin + Writeln('- creating new request'); + stm := TFileStream.Create( fname+REQUEST_EXT+'.tmp', fmCreate); + try + ping := CreatePing; + + // save request data + proto := CreateProtocol( protfact, stm, FALSE); + client := TSkipTestService.TClient.Create( nil, proto); + cliRef := client as IUnknown; + client.send_PingPong( ping); + + finally + client := nil; // not Free! + cliRef := nil; + stm.Free; + if client = nil then {warning suppressed}; + end; + + DeleteFile( fname+REQUEST_EXT); + RenameFile( fname+REQUEST_EXT+'.tmp', fname+REQUEST_EXT); +end; + + +procedure ReadResponse( protfact : IProtocolFactory; fname : string); +var stm : TFileStream; + proto : IProtocol; + client : TSkipTestService.TClient; // we need access to send/recv_pingpong() + cliRef : IUnknown; // holds the refcount +begin + Writeln('- reading response'); + stm := TFileStream.Create( fname+RESPONSE_EXT, fmOpenRead); + try + // save request data + proto := CreateProtocol( protfact, stm, TRUE); + client := TSkipTestService.TClient.Create( proto, nil); + cliRef := client as IUnknown; + client.recv_PingPong; + + finally + client := nil; // not Free! + cliRef := nil; + stm.Free; + if client = nil then {warning suppressed}; + end; +end; + + +procedure ProcessFile( protfact : IProtocolFactory; fname : string); +var stmIn, stmOut : TFileStream; + protIn, protOut : IProtocol; + server : IProcessor; +begin + Writeln('- processing request'); + stmOut := nil; + stmIn := TFileStream.Create( fname+REQUEST_EXT, fmOpenRead); + try + stmOut := TFileStream.Create( fname+RESPONSE_EXT+'.tmp', fmCreate); + + // process request and write response data + protIn := CreateProtocol( protfact, stmIn, TRUE); + protOut := CreateProtocol( protfact, stmOut, FALSE); + + server := TSkipTestService.TProcessorImpl.Create( TDummyServer.Create); + server.Process( protIn, protOut); + + finally + server := nil; // not Free! + stmIn.Free; + stmOut.Free; + if server = nil then {warning suppressed}; + end; + + DeleteFile( fname+RESPONSE_EXT); + RenameFile( fname+RESPONSE_EXT+'.tmp', fname+RESPONSE_EXT); +end; + + +procedure Test( protfact : IProtocolFactory; fname : string); +begin + // try to read an existing request + if FileExists( fname + REQUEST_EXT) then begin + ProcessFile( protfact, fname); + ReadResponse( protfact, fname); + end; + + // create a new request and try to process + CreateRequest( protfact, fname); + ProcessFile( protfact, fname); + ReadResponse( protfact, fname); +end; + + +const + FILE_BINARY = 'pingpong.bin'; + FILE_JSON = 'pingpong.json'; +begin + try + Writeln( 'Delphi SkipTest '+IntToStr(Tskiptest_version_1Constants.SKIPTESTSERVICE_VERSION)+' using '+Thrift.Version); + + Writeln; + Writeln('Binary protocol'); + Test( TBinaryProtocolImpl.TFactory.Create, FILE_BINARY); + + Writeln; + Writeln('JSON protocol'); + Test( TJSONProtocolImpl.TFactory.Create, FILE_JSON); + + Writeln; + Writeln('Test completed without errors.'); + Writeln; + Write('Press ENTER to close ...'); Readln; + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end; +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/skip/skiptest_version2.dpr b/src/jaegertracing/thrift/lib/delphi/test/skip/skiptest_version2.dpr new file mode 100644 index 000000000..7893748a0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/skip/skiptest_version2.dpr @@ -0,0 +1,229 @@ +(* + * 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. + *) + +program skiptest_version2; + +{$APPTYPE CONSOLE} + +uses + Classes, Windows, SysUtils, + Skiptest.Two, + Thrift in '..\..\src\Thrift.pas', + Thrift.Exception in '..\..\src\Thrift.Exception.pas', + Thrift.Socket in '..\..\src\Thrift.Socket.pas', + Thrift.Transport in '..\..\src\Thrift.Transport.pas', + Thrift.Protocol in '..\..\src\Thrift.Protocol.pas', + Thrift.Protocol.JSON in '..\..\src\Thrift.Protocol.JSON.pas', + Thrift.Collections in '..\..\src\Thrift.Collections.pas', + Thrift.Server in '..\..\src\Thrift.Server.pas', + Thrift.Utils in '..\..\src\Thrift.Utils.pas', + Thrift.WinHTTP in '..\..\src\Thrift.WinHTTP.pas', + Thrift.TypeRegistry in '..\..\src\Thrift.TypeRegistry.pas', + Thrift.Stream in '..\..\src\Thrift.Stream.pas'; + +const + 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.BoolVal := TRUE; + result.ByteVal := 2; + result.DbVal := 3; + result.I16Val := 4; + result.I32Val := 5; + result.I64Val := 6; + result.StrVal := 'seven'; + + result.StructVal := TPongImpl.Create; + result.StructVal.Version1 := -1; + result.StructVal.Version2 := -2; + + list := TThriftListImpl<IPong>.Create; + list.Add( result.StructVal); + list.Add( result.StructVal); + + set_ := THashSetImpl<string>.Create; + set_.Add( 'one'); + set_.Add( 'uno'); + set_.Add( 'eins'); + set_.Add( 'een'); + + result.MapVal := TThriftDictionaryImpl< IThriftList<IPong>, IHashSet<string>>.Create; + result.MapVal.Add( list, set_); +end; + + +type + TDummyServer = class( TInterfacedObject, TSkipTestService.Iface) + protected + // TSkipTestService.Iface + function PingPong(const ping: IPing; const pong: IPong): IPing; + end; + + +function TDummyServer.PingPong(const ping: IPing; const pong: IPong): IPing; +// TSkipTestService.Iface +begin + Writeln('- performing request from version '+IntToStr(ping.Version1)+' client'); + result := CreatePing; +end; + + +function CreateProtocol( protfact : IProtocolFactory; stm : TStream; aForInput : Boolean) : IProtocol; +var adapt : IThriftStream; + trans : ITransport; +begin + adapt := TThriftStreamAdapterDelphi.Create( stm, FALSE); + if aForInput + then trans := TStreamTransportImpl.Create( adapt, nil) + else trans := TStreamTransportImpl.Create( nil, adapt); + result := protfact.GetProtocol( trans); +end; + + +procedure CreateRequest( 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 +begin + Writeln('- creating new request'); + stm := TFileStream.Create( fname+REQUEST_EXT+'.tmp', fmCreate); + try + ping := CreatePing; + + // save request data + proto := CreateProtocol( protfact, stm, FALSE); + client := TSkipTestService.TClient.Create( nil, proto); + cliRef := client as IUnknown; + client.send_PingPong( ping, ping.StructVal); + + finally + client := nil; // not Free! + cliRef := nil; + stm.Free; + if client = nil then {warning suppressed}; + end; + + DeleteFile( fname+REQUEST_EXT); + RenameFile( fname+REQUEST_EXT+'.tmp', fname+REQUEST_EXT); +end; + + +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 +begin + Writeln('- reading response'); + stm := TFileStream.Create( fname+RESPONSE_EXT, fmOpenRead); + try + // save request data + proto := CreateProtocol( protfact, stm, TRUE); + client := TSkipTestService.TClient.Create( proto, nil); + cliRef := client as IUnknown; + ping := client.recv_PingPong; + + finally + client := nil; // not Free! + cliRef := nil; + stm.Free; + if client = nil then {warning suppressed}; + end; +end; + + +procedure ProcessFile( protfact : IProtocolFactory; fname : string); +var stmIn, stmOut : TFileStream; + protIn, protOut : IProtocol; + server : IProcessor; +begin + Writeln('- processing request'); + stmOut := nil; + stmIn := TFileStream.Create( fname+REQUEST_EXT, fmOpenRead); + try + stmOut := TFileStream.Create( fname+RESPONSE_EXT+'.tmp', fmCreate); + + // process request and write response data + protIn := CreateProtocol( protfact, stmIn, TRUE); + protOut := CreateProtocol( protfact, stmOut, FALSE); + + server := TSkipTestService.TProcessorImpl.Create( TDummyServer.Create); + server.Process( protIn, protOut); + + finally + server := nil; // not Free! + stmIn.Free; + stmOut.Free; + if server = nil then {warning suppressed}; + end; + + DeleteFile( fname+RESPONSE_EXT); + RenameFile( fname+RESPONSE_EXT+'.tmp', fname+RESPONSE_EXT); +end; + + +procedure Test( protfact : IProtocolFactory; fname : string); +begin + // try to read an existing request + if FileExists( fname + REQUEST_EXT) then begin + ProcessFile( protfact, fname); + ReadResponse( protfact, fname); + end; + + // create a new request and try to process + CreateRequest( protfact, fname); + ProcessFile( protfact, fname); + ReadResponse( protfact, fname); +end; + + +const + FILE_BINARY = 'pingpong.bin'; + FILE_JSON = 'pingpong.json'; +begin + try + Writeln( 'Delphi SkipTest '+IntToStr(Tskiptest_version_2Constants.SKIPTESTSERVICE_VERSION)+' using '+Thrift.Version); + + Writeln; + Writeln('Binary protocol'); + Test( TBinaryProtocolImpl.TFactory.Create, FILE_BINARY); + + Writeln; + Writeln('JSON protocol'); + Test( TJSONProtocolImpl.TFactory.Create, FILE_JSON); + + Writeln; + Writeln('Test completed without errors.'); + Writeln; + Write('Press ENTER to close ...'); Readln; + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end; +end. + diff --git a/src/jaegertracing/thrift/lib/delphi/test/typeregistry/TestTypeRegistry.dpr b/src/jaegertracing/thrift/lib/delphi/test/typeregistry/TestTypeRegistry.dpr new file mode 100644 index 000000000..fd5e3dd4e --- /dev/null +++ b/src/jaegertracing/thrift/lib/delphi/test/typeregistry/TestTypeRegistry.dpr @@ -0,0 +1,91 @@ +(* + * 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. + *) + +program TestTypeRegistry; + +{$APPTYPE CONSOLE} + +uses + Classes, Windows, SysUtils, Generics.Collections, TypInfo, + Thrift in '..\..\src\Thrift.pas', + Thrift.Transport in '..\..\src\Thrift.Transport.pas', + Thrift.Exception in '..\..\src\Thrift.Exception.pas', + Thrift.Socket in '..\..\src\Thrift.Socket.pas', + Thrift.Protocol in '..\..\src\Thrift.Protocol.pas', + Thrift.Protocol.JSON in '..\..\src\Thrift.Protocol.JSON.pas', + Thrift.Collections in '..\..\src\Thrift.Collections.pas', + Thrift.Server in '..\..\src\Thrift.Server.pas', + Thrift.Utils in '..\..\src\Thrift.Utils.pas', + Thrift.Serializer in '..\..\src\Thrift.Serializer.pas', + Thrift.Stream in '..\..\src\Thrift.Stream.pas', + Thrift.WinHTTP in '..\..\src\Thrift.WinHTTP.pas', + Thrift.TypeRegistry in '..\..\src\Thrift.TypeRegistry.pas', + DebugProtoTest; + +type + Tester<T : IInterface> = class + public + class procedure Test; + end; + +class procedure Tester<T>.Test; +var instance : T; + name : string; +begin + instance := TypeRegistry.Construct<T>; + name := GetTypeName(TypeInfo(T)); + if instance <> nil + then Writeln( name, ' = ok') + else begin + Writeln( name, ' = failed'); + raise Exception.Create( 'Test with '+name+' failed!'); + end; +end; + +begin + Writeln('Testing ...'); + Tester<IDoubles>.Test; + Tester<IOneOfEach>.Test; + Tester<IBonk>.Test; + Tester<INesting>.Test; + Tester<IHolyMoley>.Test; + Tester<IBackwards>.Test; + Tester<IEmpty>.Test; + Tester<IWrapper>.Test; + Tester<IRandomStuff>.Test; + Tester<IBase64>.Test; + Tester<ICompactProtoTestStruct>.Test; + Tester<ISingleMapTestStruct>.Test; + Tester<IBlowUp>.Test; + Tester<IReverseOrderStruct>.Test; + Tester<IStructWithSomeEnum>.Test; + Tester<ITestUnion>.Test; + Tester<ITestUnionMinusStringField>.Test; + Tester<IComparableUnion>.Test; + Tester<IStructWithAUnion>.Test; + Tester<IPrimitiveThenStruct>.Test; + Tester<IStructWithASomemap>.Test; + Tester<IBigFieldIdStruct>.Test; + Tester<IBreaksRubyCompactProtocol>.Test; + Tester<ITupleProtocolTestStruct>.Test; + Writeln('Completed.'); + + +end. + |