Introduction - grey-kristy/nimble GitHub Wiki
= Nimble Service Library v0.4 =
= Functionality and goals =
Goal: fast and agile python-based wsgi backend server deployment and creation
Current state:
nimble.server package:
-
decorator-based shared over the net methods creation
-
exception catching and restoring over the net
-
secret-based scheme for authorizing trusted clients
-
some common authorization helpers
-
FastCGI input-output processing based on Flup
-
Http proxy input-output processing based on GEvent
-
daemonizing and server process management
-
simultanious multiprotocol work based on PATH_INFO header
-
multifrontend support: flup and gevent are currently supported
nimble.protocols package:
- two protocols out-of-the-box: specific and fast 'simple', general-purpose 'json'
nimble.client package:
- simple RPC functionality (service module required for method binding)
nimble javascript library:
- helper functions to operate nimble requests/responses in javascript with both protocols support
= Requirements =
-
python 2.5/2.6 (2.7 untested)
-
simplejson package for json protocol
-
[http://trac.saddi.com/flup Flup] - fast-cgi python daemon library
= Nimble server =
from nimble.server.base import Server
from nimble.server.tools import shared
class MyServer(Server):
@shared
def return_args(self, connection, arg1, *listArg):
return connection.OK([arg1, listArg])
if __name__=="__main__":
MyServer.application #standart wsgi application
MyServer.run #start with no daemon support over nimble-defined frontend (flup, etc)
MyServer.run_daemon #as run but in daemon mode, uses console arguments
= Nimble protocols =
==Simple==
request:
command<space>command_params
command is: text with no spaces
command_params are:
param1<space>param2<space>...
paramN is: text with no spaces
so, currently if you're not encoding-decoding your data structures (using standart 'pickle' module for example) your shared server method signature will be looking like that:
class MyServer(Server):
@shared
def method(self, connection, [arg1...argN], [*listArg]):
pass
where arg1...argN have primitive type (strings, numbers and so on) and listArg can be a list as a varargs.
response:
result_code<space>results
result_code is: OK or ERROR
results are:
result11<space>result12...\nresult21<space>result22...
resultN is: text with no spaces and no '\n'
'simple' protocol client is designed for fast processing, required safe (quoted) string data items
==Json==
request:
{command:<shared method name or alias>, args:<arguments for the method>}
response:
{is_error:<boolean error status>, results:<any simplejson deserializable data structure>}
= Examples =
Example source (geoipservice.py):
#!/usr/bin/env python2.6
from nimble.server.base import Server
from nimble.server.tools import shared, shared_as
from nimble.client.base import ServerClient
import GeoIP
class GeoIPServer(Server):
def __init__(self, *args, **kwargs):
Server.__init__(self, *args, **kwargs)
self.db = GeoIP.open("./GeoLiteCity.dat", GeoIP.GEOIP_STANDARD)
@shared_as('LC')
def get_location_coordinates(self, connection, ip):
try:
rec = self.db.record_by_addr(ip)
return connection.OK([rec['latitude'], rec['longitude']])
except:
return connection.ERROR('SOME_ERROR')
@shared
def get_multiple_location_coordinates(self, connection, *ips):
recs = [self.db.record_by_addr(ip) for ip in ips]
return connection.OK([(rec['latitude'], rec['longitude']) for rec in recs])
class GeoIPServerClient(ServerClient):
SERVER_CLASS=GeoIPServer
if __name__=='__main__':
GeoIPServer.run_daemon(port=1111)
Example cmdline:
$ ./geoipservice.py start/restart/stop/debug [secret=value] [port=value] [optionX=value]
Traffic example (over 'simple' protocol):
In:
LC 12.23.12.12
Out:
OK 42.2345 21.2322
Example usage in http client (web browser, javascript by the prototype library or like that) (over 'simple' protocol):
new Ajax.Request('http://192.168.1.100/geoip/',
{ method: 'post',
postBody: 'LC 12.23.12.12',
onComplete: function(r) {alert(r.responseText);}
}
);
Example usage of nimble.js + jquery
conn = new NimbleConnection('/geoip/');
var response_func = function(text) {
try {
server_result = conn.parse_response(text);
} catch(error) {
alert(error);
}
};
$.ajax({type: "POST",
url: conn.get_url(),
data: conn.make_request('LC',['12.23.12.12']),
dataType: 'text'
}).done(response_func);
or
var conn = new NimbleConnection('/geoip/');
var onsuccess = function(result) {
alert(result);
};
var onerror = function(error) {
alert(error);
};
conn.request_by_jquery('LC',['12.23.12.12'], onsuccess, onerror);
Example python client usage:
from geoipservice import GeoIPServerClient as Client
import nimble.protocols.json as json
client = Client('http://192.168.1.100/geoip/') #over default nimble protocol
lat, long = client.get_location_coordinates('1.1.1.1') # convert to float from string manually
client = Client('http://192.168.1.100/geoip/', default_protocol=json) #over your protocol
lat, long = client.get_location_coordinates('1.1.1.1')
Example nginx+flup setup:
server {
listen 80;
server_name 192.168.1.100;
location /geoip {
fastcgi_pass 127.0.0.1:1111;
fastcgi_param SCRIPT_FILENAME /home/projects/geoip/$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REMOTE_ADDR $remote_addr;
}
}
Example nginx+gevent setup:
server {
listen 80;
server_name 192.168.1.100;
location /geoip {
proxy_pass http://127.0.0.1:1111;
proxy_set_header x-real-ip $remote_addr;
}
}