| @ -0,0 +1,2 @@ | |||||
| *.py[co] | |||||
| *.egg-info | |||||
| @ -0,0 +1,22 @@ | |||||
| The MIT License (MIT) | |||||
| Copyright (c) 2014 Brett Langdon | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in all | |||||
| copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| SOFTWARE. | |||||
| @ -0,0 +1,4 @@ | |||||
| include README.* LICENSE setup.py setup.cfg | |||||
| recursive-include greenrpc *.py | |||||
| global-exclude *.pyc | |||||
| global-exclude *.pyo | |||||
| @ -0,0 +1,25 @@ | |||||
| #!/usr/bin/env python | |||||
| import argparse | |||||
| from greenrpc import TCP_SERVER_DEFAULT_PORT | |||||
| from greenrpc.client import TCPClient | |||||
| if __name__ == "__main__": | |||||
| parser = argparse.ArgumentParser(description="Start a new GreenRPC TCP Server") | |||||
| parser.add_argument("method", metavar="<method>", type=str, | |||||
| help="The remote method to call") | |||||
| parser.add_argument("args", metavar="<arg>", nargs="*", type=str, | |||||
| help="Arguments to send for the remote method call") | |||||
| default_connect = "127.0.0.1:%s" % (TCP_SERVER_DEFAULT_PORT, ) | |||||
| parser.add_argument("--connect", dest="connect", type=str, default=default_connect, | |||||
| help="<address>:<port> of the server to connect to(default: %s)" % (default_connect, )) | |||||
| parser.add_argument("--debug", dest="debug", action="store_true", default=False, | |||||
| help="Whether or not to show the full result") | |||||
| args = parser.parse_args() | |||||
| address, _, port = args.connect.partition(":") | |||||
| client = TCPClient(connect=(address, int(port))) | |||||
| result = client.call(args.method, args.args, debug=args.debug) | |||||
| print result | |||||
| @ -0,0 +1,24 @@ | |||||
| #!/usr/bin/env python | |||||
| import argparse | |||||
| from greenrpc import TCP_SERVER_DEFAULT_PORT | |||||
| from greenrpc.server import TCPServer | |||||
| if __name__ == "__main__": | |||||
| parser = argparse.ArgumentParser(description="Start a new GreenRPC TCP Server") | |||||
| parser.add_argument("module", metavar="<module>", type=str, | |||||
| help="Python module to expose for the RPC Server") | |||||
| default_bind = "127.0.0.1:%s" % (TCP_SERVER_DEFAULT_PORT, ) | |||||
| parser.add_argument("--bind", dest="bind", type=str, default=default_bind, | |||||
| help="<address>:<port> to bind the server to (default: %s)" % (default_bind, )) | |||||
| parser.add_argument("--spawn", dest="spawn", type=int, default=4, | |||||
| help="number of greenlets to spawn (default: 4)") | |||||
| args = parser.parse_args() | |||||
| address, _, port = args.bind.partition(":") | |||||
| server = TCPServer(args.module, bind=(address, int(port)), spawn=args.spawn) | |||||
| try: | |||||
| server.serve_forever() | |||||
| except KeyboardInterrupt: | |||||
| print "Stopping GreenRPC Server" | |||||
| server.stop() | |||||
| @ -0,0 +1,4 @@ | |||||
| __version__ = "0.1.0" | |||||
| TCP_SERVER_DEFAULT_PORT = 3434 | |||||
| HTTP_SERVER_DEFAULT_PORT = 3435 | |||||
| @ -0,0 +1,83 @@ | |||||
| import time | |||||
| import types | |||||
| import msgpack | |||||
| class BaseServer(object): | |||||
| SOCKET_BUFFER_SIZE = 1024 | |||||
| def __init__(self, services): | |||||
| if isinstance(services, (dict, types.ModuleType)): | |||||
| self.services = services | |||||
| elif isinstance(services, basestring): | |||||
| self.services = __import__(services) | |||||
| else: | |||||
| raise TypeError("First argument to BaseServer.__init__ must be a dict or a string") | |||||
| self.packer = msgpack.Packer() | |||||
| def unpack_requests(self, sock): | |||||
| unpacker = msgpack.Unpacker() | |||||
| while True: | |||||
| data = sock.recv(self.SOCKET_BUFFER_SIZE) | |||||
| if not data: | |||||
| break | |||||
| unpacker.feed(data) | |||||
| for request in unpacker: | |||||
| yield request | |||||
| def pack_result(self, result): | |||||
| return self.packer.pack(result) | |||||
| def handle_request(self, request): | |||||
| start_time = time.time() | |||||
| req_method = request.get("method") | |||||
| req_args = request.get("args", []) | |||||
| result = { | |||||
| "id": request.get("id"), | |||||
| "results": None, | |||||
| } | |||||
| if not req_method: | |||||
| result["error"] = "No request method was provided" | |||||
| elif not hasattr(self.services, req_method): | |||||
| result["error"] = "Unknown request method '%s'" % (req_method, ) | |||||
| else: | |||||
| try: | |||||
| result["results"] = getattr(self.services, req_method)(*req_args) | |||||
| except Exception, e: | |||||
| result["error"] = e.message | |||||
| result["run_time"] = (time.time() - start_time) * 1000.0 | |||||
| return result | |||||
| class BaseClient(object): | |||||
| SOCKET_BUFFER_SIZE = 1024 | |||||
| def __init__(self): | |||||
| self.unpacker = msgpack.Unpacker() | |||||
| self.packer = msgpack.Packer() | |||||
| self.id = 0 | |||||
| def pack_request(self, id, method, args=[]): | |||||
| return self.packer.pack({ | |||||
| "id": id, | |||||
| "method": method, | |||||
| "args": args, | |||||
| }) | |||||
| def unpack_results(self, sock): | |||||
| while True: | |||||
| data = sock.recv(self.SOCKET_BUFFER_SIZE) | |||||
| if not data: | |||||
| break | |||||
| self.unpacker.feed(data) | |||||
| return self.unpacker.next() | |||||
| def __getattr__(self, method): | |||||
| def wrapper(*args): | |||||
| return self.call(method, args) | |||||
| return wrapper | |||||
| @ -0,0 +1,29 @@ | |||||
| from gevent.socket import socket | |||||
| from greenrpc import TCP_SERVER_DEFAULT_PORT | |||||
| from greenrpc.base import BaseClient | |||||
| from greenrpc.error import RPCException | |||||
| class TCPClient(BaseClient): | |||||
| def __init__(self, connect=("127.0.0.1", TCP_SERVER_DEFAULT_PORT)): | |||||
| super(TCPClient, self).__init__() | |||||
| self.connection = socket() | |||||
| self.connection.connect(connect) | |||||
| self.fp = self.connection.makefile() | |||||
| def call(self, method, args=[], debug=False): | |||||
| self.id += 1 | |||||
| request = self.pack_request(self.id, method, args) | |||||
| self.fp.write(request) | |||||
| self.fp.flush() | |||||
| results = self.unpack_results(self.connection) | |||||
| if debug: | |||||
| return results | |||||
| if results.get("error"): | |||||
| raise RPCException(results["error"]) | |||||
| return results.get("results") | |||||
| class HTTPClient(BaseClient): | |||||
| pass | |||||
| @ -0,0 +1,2 @@ | |||||
| class RPCException(Exception): | |||||
| pass | |||||
| @ -0,0 +1,31 @@ | |||||
| import contextlib | |||||
| from gevent.pywsgi import WSGIServer | |||||
| from gevent.server import StreamServer | |||||
| from greenrpc import TCP_SERVER_DEFAULT_PORT, HTTP_SERVER_DEFAULT_PORT | |||||
| from greenrpc.base import BaseServer | |||||
| class TCPServer(StreamServer, BaseServer): | |||||
| def __init__(self, services, bind=("127.0.0.1", TCP_SERVER_DEFAULT_PORT), spawn=1): | |||||
| StreamServer.__init__(self, bind, spawn=spawn) | |||||
| BaseServer.__init__(self, services) | |||||
| def handle(self, socket, address): | |||||
| with contextlib.closing(socket) as sock: | |||||
| with contextlib.closing(sock.makefile()) as fp: | |||||
| for request in self.unpack_requests(sock): | |||||
| result = self.handle_request(request) | |||||
| fp.write(self.pack_result(result)) | |||||
| fp.flush() | |||||
| class WSGIServer(WSGIServer, BaseServer): | |||||
| def __init__(self, services, bind=("127.0.0.1", HTTP_SERVER_DEFAULT_PORT)): | |||||
| WSGIServer.__init__(self, bind) | |||||
| BaseServer.__init__(self, services) | |||||
| def application(self, environ, start_response): | |||||
| start_response("200 OK", [("Content-Type", "text/plain")]) | |||||
| return ["Hello, Worlds"] | |||||
| @ -0,0 +1,2 @@ | |||||
| gevent==1.0.1 | |||||
| msgpack-python==0.4.2 | |||||
| @ -0,0 +1,2 @@ | |||||
| [metadata] | |||||
| description-file = README.md | |||||
| @ -0,0 +1,30 @@ | |||||
| #!/usr/bin/env python | |||||
| from setuptools import setup, find_packages | |||||
| from greenrpc import __version__ | |||||
| with open("./requirements.txt") as fp: | |||||
| requirements = fp.read() | |||||
| requirements = requirements.split("\n") | |||||
| setup( | |||||
| name="greenrpc", | |||||
| version=__version__, | |||||
| description="msgpack TCP & HTTP RPC Servers written with gevent", | |||||
| author="Brett Langdon", | |||||
| author_email="brett@blangdon.com", | |||||
| url="https://github.com/brettlangdon/greenrpc", | |||||
| packages=find_packages(), | |||||
| license="MIT", | |||||
| scripts=["bin/greenrpc-server", "bin/greenrpc-client"], | |||||
| install_requires=requirements, | |||||
| classifiers=[ | |||||
| "Intended Audience :: Developers", | |||||
| "Programming Language :: Python", | |||||
| "Programming Language :: Python :: 2.6", | |||||
| "Programming Language :: Python :: 2.7", | |||||
| "License :: OSI Approved :: MIT License", | |||||
| "Topic :: Utilities", | |||||
| ] | |||||
| ) | |||||