| @ -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", | |||
| ] | |||
| ) | |||