diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/README.md b/README.md index b86c6fb..2f41c6b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,26 @@ # service_demo -Simple examples for gRPC, Thrift and RPyC +[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/hardikp/service_demo/blob/master/LICENSE) + +Python3 server and client examples for [gRPC](https://grpc.io/), [Thrift](https://thrift.apache.org/docs/) and [RPyC](https://rpyc.readthedocs.io/en/latest/). + +## Service Example + +* `TimeService` implents `get_time` RPC call. +* `get_time` returns the current server time in string format. + +## How to use + +1. Create an virtualenv and install requirements. + ```bash + cd grpc + pip3 install -r requirements.txt + ``` + +1. Run Server in one terminal window: + ```bash + python3 server.py + ``` + +1. Run Client in another terminal: + ```bash + python3 server.py diff --git a/grpc/client.py b/grpc/client.py new file mode 100644 index 0000000..b722562 --- /dev/null +++ b/grpc/client.py @@ -0,0 +1,15 @@ +import grpc + +import time_pb2 +import time_pb2_grpc + + +def run(): + channel = grpc.insecure_channel('localhost:50051') + stub = time_pb2_grpc.TimeStub(channel) + response = stub.GetTime(time_pb2.TimeRequest()) + print('Client received: {}'.format(response.message)) + + +if __name__ == '__main__': + run() diff --git a/grpc/requirements.txt b/grpc/requirements.txt new file mode 100644 index 0000000..7717e37 --- /dev/null +++ b/grpc/requirements.txt @@ -0,0 +1,3 @@ +grpcio +grpcio-tools +googleapis-common-protos diff --git a/grpc/server.py b/grpc/server.py new file mode 100644 index 0000000..6347f8f --- /dev/null +++ b/grpc/server.py @@ -0,0 +1,30 @@ +import time +from concurrent import futures + +import grpc + +import time_pb2 +import time_pb2_grpc + +_ONE_DAY_IN_SECONDS = 60 * 60 * 24 + + +class Timer(time_pb2_grpc.TimeServicer): + def GetTime(self, request, context): + return time_pb2.TimeReply(message=time.ctime()) + + +def serve(): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + time_pb2_grpc.add_TimeServicer_to_server(Timer(), server) + server.add_insecure_port('[::]:50051') + server.start() + try: + while True: + time.sleep(_ONE_DAY_IN_SECONDS) + except KeyboardInterrupt: + server.stop(0) + + +if __name__ == '__main__': + serve() diff --git a/grpc/time.proto b/grpc/time.proto new file mode 100644 index 0000000..5281d41 --- /dev/null +++ b/grpc/time.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package time; + +service Time { + rpc GetTime (TimeRequest) returns (TimeReply) {} +} + +// Empty Request Message +message TimeRequest { +} + +// The response message containing the time +message TimeReply { + string message = 1; +} diff --git a/grpc/time_pb2.py b/grpc/time_pb2.py new file mode 100644 index 0000000..dec91df --- /dev/null +++ b/grpc/time_pb2.py @@ -0,0 +1,125 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: time.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='time.proto', + package='time', + syntax='proto3', + serialized_pb=_b('\n\ntime.proto\x12\x04time\"\r\n\x0bTimeRequest\"\x1c\n\tTimeReply\x12\x0f\n\x07message\x18\x01 \x01(\t27\n\x04Time\x12/\n\x07GetTime\x12\x11.time.TimeRequest\x1a\x0f.time.TimeReply\"\x00\x62\x06proto3') +) + + + + +_TIMEREQUEST = _descriptor.Descriptor( + name='TimeRequest', + full_name='time.TimeRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=20, + serialized_end=33, +) + + +_TIMEREPLY = _descriptor.Descriptor( + name='TimeReply', + full_name='time.TimeReply', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='message', full_name='time.TimeReply.message', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=35, + serialized_end=63, +) + +DESCRIPTOR.message_types_by_name['TimeRequest'] = _TIMEREQUEST +DESCRIPTOR.message_types_by_name['TimeReply'] = _TIMEREPLY +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +TimeRequest = _reflection.GeneratedProtocolMessageType('TimeRequest', (_message.Message,), dict( + DESCRIPTOR = _TIMEREQUEST, + __module__ = 'time_pb2' + # @@protoc_insertion_point(class_scope:time.TimeRequest) + )) +_sym_db.RegisterMessage(TimeRequest) + +TimeReply = _reflection.GeneratedProtocolMessageType('TimeReply', (_message.Message,), dict( + DESCRIPTOR = _TIMEREPLY, + __module__ = 'time_pb2' + # @@protoc_insertion_point(class_scope:time.TimeReply) + )) +_sym_db.RegisterMessage(TimeReply) + + + +_TIME = _descriptor.ServiceDescriptor( + name='Time', + full_name='time.Time', + file=DESCRIPTOR, + index=0, + options=None, + serialized_start=65, + serialized_end=120, + methods=[ + _descriptor.MethodDescriptor( + name='GetTime', + full_name='time.Time.GetTime', + index=0, + containing_service=None, + input_type=_TIMEREQUEST, + output_type=_TIMEREPLY, + options=None, + ), +]) +_sym_db.RegisterServiceDescriptor(_TIME) + +DESCRIPTOR.services_by_name['Time'] = _TIME + +# @@protoc_insertion_point(module_scope) diff --git a/grpc/time_pb2_grpc.py b/grpc/time_pb2_grpc.py new file mode 100644 index 0000000..c703e4e --- /dev/null +++ b/grpc/time_pb2_grpc.py @@ -0,0 +1,46 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +import grpc + +import time_pb2 as time__pb2 + + +class TimeStub(object): + # missing associated documentation comment in .proto file + pass + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetTime = channel.unary_unary( + '/time.Time/GetTime', + request_serializer=time__pb2.TimeRequest.SerializeToString, + response_deserializer=time__pb2.TimeReply.FromString, + ) + + +class TimeServicer(object): + # missing associated documentation comment in .proto file + pass + + def GetTime(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TimeServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetTime': grpc.unary_unary_rpc_method_handler( + servicer.GetTime, + request_deserializer=time__pb2.TimeRequest.FromString, + response_serializer=time__pb2.TimeReply.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'time.Time', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) diff --git a/rpyc/client.py b/rpyc/client.py new file mode 100644 index 0000000..7fde0b3 --- /dev/null +++ b/rpyc/client.py @@ -0,0 +1,4 @@ +import rpyc + +conn = rpyc.connect('localhost', 18871) +print('Time is {}'.format(conn.root.get_time())) diff --git a/rpyc/requirements.txt b/rpyc/requirements.txt new file mode 100644 index 0000000..b0dbf07 --- /dev/null +++ b/rpyc/requirements.txt @@ -0,0 +1 @@ +rpyc diff --git a/rpyc/server.py b/rpyc/server.py new file mode 100644 index 0000000..de366a5 --- /dev/null +++ b/rpyc/server.py @@ -0,0 +1,14 @@ +import time + +from rpyc import Service +from rpyc.utils.server import ThreadedServer + + +class TimeService(Service): + def exposed_get_time(self): + return time.ctime() + + +if __name__ == '__main__': + s = ThreadedServer(TimeService, port=18871) + s.start() diff --git a/thrift/client.py b/thrift/client.py new file mode 100755 index 0000000..3b21807 --- /dev/null +++ b/thrift/client.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +import sys + +from thrift import Thrift +from thrift.protocol import TBinaryProtocol +from thrift.transport import TSocket, TTransport +sys.path.append('gen-py') +from time_service import TimeService + + +def main(): + # Make socket + transport = TSocket.TSocket('localhost', 9090) + + # Buffering is critical. Raw sockets are very slow + transport = TTransport.TBufferedTransport(transport) + + # Wrap in a protocol + protocol = TBinaryProtocol.TBinaryProtocol(transport) + + # Create a client to use the protocol encoder + client = TimeService.Client(protocol) + + # Connect! + transport.open() + + ts = client.get_time() + print('Client Received {}'.format(ts)) + + # Close! + transport.close() + + +if __name__ == '__main__': + try: + main() + except Thrift.TException as tx: + print('%s' % tx.message) diff --git a/thrift/gen-py/__init__.py b/thrift/gen-py/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/thrift/gen-py/time_service/TimeService-remote b/thrift/gen-py/time_service/TimeService-remote new file mode 100755 index 0000000..685c03d --- /dev/null +++ b/thrift/gen-py/time_service/TimeService-remote @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# +# Autogenerated by Thrift Compiler (0.11.0) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# options string: py +# + +import sys +import pprint +if sys.version_info[0] > 2: + from urllib.parse import urlparse +else: + from urlparse import urlparse +from thrift.transport import TTransport, TSocket, TSSLSocket, THttpClient +from thrift.protocol.TBinaryProtocol import TBinaryProtocol + +from time_service import TimeService +from time_service.ttypes import * + +if len(sys.argv) <= 1 or sys.argv[1] == '--help': + print('') + print('Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] [-s[sl]] [-novalidate] [-ca_certs certs] [-keyfile keyfile] [-certfile certfile] function [arg1 [arg2...]]') + print('') + print('Functions:') + print(' string get_time()') + print('') + sys.exit(0) + +pp = pprint.PrettyPrinter(indent=2) +host = 'localhost' +port = 9090 +uri = '' +framed = False +ssl = False +validate = True +ca_certs = None +keyfile = None +certfile = None +http = False +argi = 1 + +if sys.argv[argi] == '-h': + parts = sys.argv[argi + 1].split(':') + host = parts[0] + if len(parts) > 1: + port = int(parts[1]) + argi += 2 + +if sys.argv[argi] == '-u': + url = urlparse(sys.argv[argi + 1]) + parts = url[1].split(':') + host = parts[0] + if len(parts) > 1: + port = int(parts[1]) + else: + port = 80 + uri = url[2] + if url[4]: + uri += '?%s' % url[4] + http = True + argi += 2 + +if sys.argv[argi] == '-f' or sys.argv[argi] == '-framed': + framed = True + argi += 1 + +if sys.argv[argi] == '-s' or sys.argv[argi] == '-ssl': + ssl = True + argi += 1 + +if sys.argv[argi] == '-novalidate': + validate = False + argi += 1 + +if sys.argv[argi] == '-ca_certs': + ca_certs = sys.argv[argi+1] + argi += 2 + +if sys.argv[argi] == '-keyfile': + keyfile = sys.argv[argi+1] + argi += 2 + +if sys.argv[argi] == '-certfile': + certfile = sys.argv[argi+1] + argi += 2 + +cmd = sys.argv[argi] +args = sys.argv[argi + 1:] + +if http: + transport = THttpClient.THttpClient(host, port, uri) +else: + if ssl: + socket = TSSLSocket.TSSLSocket(host, port, validate=validate, ca_certs=ca_certs, keyfile=keyfile, certfile=certfile) + else: + socket = TSocket.TSocket(host, port) + if framed: + transport = TTransport.TFramedTransport(socket) + else: + transport = TTransport.TBufferedTransport(socket) +protocol = TBinaryProtocol(transport) +client = TimeService.Client(protocol) +transport.open() + +if cmd == 'get_time': + if len(args) != 0: + print('get_time requires 0 args') + sys.exit(1) + pp.pprint(client.get_time()) + +else: + print('Unrecognized method %s' % cmd) + sys.exit(1) + +transport.close() diff --git a/thrift/gen-py/time_service/TimeService.py b/thrift/gen-py/time_service/TimeService.py new file mode 100644 index 0000000..4205a3d --- /dev/null +++ b/thrift/gen-py/time_service/TimeService.py @@ -0,0 +1,210 @@ +# +# Autogenerated by Thrift Compiler (0.11.0) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# options string: py +# + +from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException +from thrift.protocol.TProtocol import TProtocolException +from thrift.TRecursive import fix_spec + +import sys +import logging +from .ttypes import * +from thrift.Thrift import TProcessor +from thrift.transport import TTransport +all_structs = [] + + +class Iface(object): + def get_time(self): + pass + + +class Client(Iface): + def __init__(self, iprot, oprot=None): + self._iprot = self._oprot = iprot + if oprot is not None: + self._oprot = oprot + self._seqid = 0 + + def get_time(self): + self.send_get_time() + return self.recv_get_time() + + def send_get_time(self): + self._oprot.writeMessageBegin('get_time', TMessageType.CALL, self._seqid) + args = get_time_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + def recv_get_time(self): + iprot = self._iprot + (fname, mtype, rseqid) = iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(iprot) + iprot.readMessageEnd() + raise x + result = get_time_result() + result.read(iprot) + iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "get_time failed: unknown result") + + +class Processor(Iface, TProcessor): + def __init__(self, handler): + self._handler = handler + self._processMap = {} + self._processMap["get_time"] = Processor.process_get_time + + def process(self, iprot, oprot): + (name, type, seqid) = iprot.readMessageBegin() + if name not in self._processMap: + iprot.skip(TType.STRUCT) + iprot.readMessageEnd() + x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name)) + oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid) + x.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + return + else: + self._processMap[name](self, seqid, iprot, oprot) + return True + + def process_get_time(self, seqid, iprot, oprot): + args = get_time_args() + args.read(iprot) + iprot.readMessageEnd() + result = get_time_result() + try: + result.success = self._handler.get_time() + msg_type = TMessageType.REPLY + except TTransport.TTransportException: + raise + except TApplicationException as ex: + logging.exception('TApplication exception in handler') + msg_type = TMessageType.EXCEPTION + result = ex + except Exception: + logging.exception('Unexpected exception in handler') + msg_type = TMessageType.EXCEPTION + result = TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error') + oprot.writeMessageBegin("get_time", msg_type, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + +# HELPER FUNCTIONS AND STRUCTURES + + +class get_time_args(object): + + + def read(self, iprot): + if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None: + iprot._fast_decode(self, iprot, [self.__class__, self.thrift_spec]) + return + iprot.readStructBegin() + while True: + (fname, ftype, fid) = iprot.readFieldBegin() + if ftype == TType.STOP: + break + else: + iprot.skip(ftype) + iprot.readFieldEnd() + iprot.readStructEnd() + + def write(self, oprot): + if oprot._fast_encode is not None and self.thrift_spec is not None: + oprot.trans.write(oprot._fast_encode(self, [self.__class__, self.thrift_spec])) + return + oprot.writeStructBegin('get_time_args') + oprot.writeFieldStop() + oprot.writeStructEnd() + + def validate(self): + return + + def __repr__(self): + L = ['%s=%r' % (key, value) + for key, value in self.__dict__.items()] + return '%s(%s)' % (self.__class__.__name__, ', '.join(L)) + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not (self == other) +all_structs.append(get_time_args) +get_time_args.thrift_spec = ( +) + + +class get_time_result(object): + """ + Attributes: + - success + """ + + + def __init__(self, success=None,): + self.success = success + + def read(self, iprot): + if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None: + iprot._fast_decode(self, iprot, [self.__class__, self.thrift_spec]) + return + iprot.readStructBegin() + while True: + (fname, ftype, fid) = iprot.readFieldBegin() + if ftype == TType.STOP: + break + if fid == 0: + if ftype == TType.STRING: + self.success = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString() + else: + iprot.skip(ftype) + else: + iprot.skip(ftype) + iprot.readFieldEnd() + iprot.readStructEnd() + + def write(self, oprot): + if oprot._fast_encode is not None and self.thrift_spec is not None: + oprot.trans.write(oprot._fast_encode(self, [self.__class__, self.thrift_spec])) + return + oprot.writeStructBegin('get_time_result') + if self.success is not None: + oprot.writeFieldBegin('success', TType.STRING, 0) + oprot.writeString(self.success.encode('utf-8') if sys.version_info[0] == 2 else self.success) + oprot.writeFieldEnd() + oprot.writeFieldStop() + oprot.writeStructEnd() + + def validate(self): + return + + def __repr__(self): + L = ['%s=%r' % (key, value) + for key, value in self.__dict__.items()] + return '%s(%s)' % (self.__class__.__name__, ', '.join(L)) + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not (self == other) +all_structs.append(get_time_result) +get_time_result.thrift_spec = ( + (0, TType.STRING, 'success', 'UTF8', None, ), # 0 +) +fix_spec(all_structs) +del all_structs + diff --git a/thrift/gen-py/time_service/__init__.py b/thrift/gen-py/time_service/__init__.py new file mode 100644 index 0000000..6bec563 --- /dev/null +++ b/thrift/gen-py/time_service/__init__.py @@ -0,0 +1 @@ +__all__ = ['ttypes', 'constants', 'TimeService'] diff --git a/thrift/gen-py/time_service/constants.py b/thrift/gen-py/time_service/constants.py new file mode 100644 index 0000000..0c217ce --- /dev/null +++ b/thrift/gen-py/time_service/constants.py @@ -0,0 +1,14 @@ +# +# Autogenerated by Thrift Compiler (0.11.0) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# options string: py +# + +from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException +from thrift.protocol.TProtocol import TProtocolException +from thrift.TRecursive import fix_spec + +import sys +from .ttypes import * diff --git a/thrift/gen-py/time_service/ttypes.py b/thrift/gen-py/time_service/ttypes.py new file mode 100644 index 0000000..9fb0f57 --- /dev/null +++ b/thrift/gen-py/time_service/ttypes.py @@ -0,0 +1,18 @@ +# +# Autogenerated by Thrift Compiler (0.11.0) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# options string: py +# + +from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException +from thrift.protocol.TProtocol import TProtocolException +from thrift.TRecursive import fix_spec + +import sys + +from thrift.transport import TTransport +all_structs = [] +fix_spec(all_structs) +del all_structs diff --git a/thrift/server.py b/thrift/server.py new file mode 100755 index 0000000..f1e4617 --- /dev/null +++ b/thrift/server.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +import sys +import time + +from thrift.protocol import TBinaryProtocol +from thrift.server import TServer +from thrift.transport import TSocket, TTransport +sys.path.append('gen-py') +from time_service import TimeService + + +class TimeHandler: + def __init__(self): + self.log = {} + + def get_time(self): + return time.ctime() + + +if __name__ == '__main__': + handler = TimeHandler() + processor = TimeService.Processor(handler) + transport = TSocket.TServerSocket(host='127.0.0.1', port=9090) + tfactory = TTransport.TBufferedTransportFactory() + pfactory = TBinaryProtocol.TBinaryProtocolFactory() + + server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) + + print('Starting the server...') + server.serve() + print('done.') diff --git a/thrift/time_service.thrift b/thrift/time_service.thrift new file mode 100644 index 0000000..463f7d8 --- /dev/null +++ b/thrift/time_service.thrift @@ -0,0 +1,3 @@ +service TimeService { + string get_time() +} \ No newline at end of file diff --git a/thriftpy/client.py b/thriftpy/client.py new file mode 100644 index 0000000..41efaf5 --- /dev/null +++ b/thriftpy/client.py @@ -0,0 +1,6 @@ +import thriftpy +from thriftpy.rpc import make_client + +time_thrift = thriftpy.load('time_service.thrift', module_name='time_thrift') +client = make_client(time_thrift.TimeService, '127.0.0.1', 6000) +print(client.get_time()) diff --git a/thriftpy/requirements.txt b/thriftpy/requirements.txt new file mode 100644 index 0000000..656538d --- /dev/null +++ b/thriftpy/requirements.txt @@ -0,0 +1 @@ +thriftpy diff --git a/thriftpy/server.py b/thriftpy/server.py new file mode 100644 index 0000000..f7c95a8 --- /dev/null +++ b/thriftpy/server.py @@ -0,0 +1,14 @@ +import time + +import thriftpy +from thriftpy.rpc import make_server + + +class Dispatcher(object): + def get_time(self): + return time.ctime() + + +time_thrift = thriftpy.load('time_service.thrift', module_name='time_thrift') +server = make_server(time_thrift.TimeService, Dispatcher(), '127.0.0.1', 6000) +server.serve() diff --git a/thriftpy/time_service.thrift b/thriftpy/time_service.thrift new file mode 100644 index 0000000..463f7d8 --- /dev/null +++ b/thriftpy/time_service.thrift @@ -0,0 +1,3 @@ +service TimeService { + string get_time() +} \ No newline at end of file