Created
October 21, 2009 09:23
-
-
Save rgaudin/214985 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# vim: ai ts=4 sts=4 et sw=4 encoding=utf-8 | |
import re | |
import urllib | |
from datetime import datetime | |
from select import select | |
from SocketServer import ThreadingMixIn | |
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer | |
import rapidsms | |
def _uni(str): | |
''' Make inbound a unicode str Decoding from utf-8 if needed ''' | |
try: | |
return unicode(str) | |
except: | |
return unicode(str,'utf-8') | |
def _str(uni): | |
''' Make inbound a string Encoding to utf-8 if needed ''' | |
try: | |
return str(uni) | |
except: | |
return uni.encode('utf-8') | |
class HttpServer(HTTPServer, ThreadingMixIn): | |
def handle_request (self, timeout=1.0): | |
# don't block on handle_request | |
reads, writes, errors = (self,), (), () | |
reads, writes, errors = select(reads, writes, errors, timeout) | |
if reads: | |
HTTPServer.handle_request(self) | |
class Backend(rapidsms.backends.Backend): | |
''' Kannel backend for rapidSMS | |
Should work with most Kannel configuration | |
Although only tested with SMPP to SMS Aggregator. | |
Check if Kannel is UP on configuration by testing sendsms HTTP server''' | |
kannel_host = 'localhost' | |
kannel_port = 13013 | |
kannel_username = 'kannel' | |
kannel_password = 'kannel' | |
def configure(self, host="localhost", port=8080, kannel_host='localhost', kannel_port=13013, kannel_username='kannel', kannel_password="kannel"): | |
self.kannel_host = kannel_host | |
self.kannel_port = int(kannel_port) | |
self.kannel_username = kannel_username | |
self.kannel_password = kannel_password | |
KannelHTTPHandler.kannel_host = self.kannel_host | |
KannelHTTPHandler.kannel_port = self.kannel_port | |
KannelHTTPHandler.kannel_username = self.kannel_username | |
KannelHTTPHandler.kannel_password = self.kannel_password | |
self.server = HttpServer((host, int(port)), KannelHTTPHandler) | |
self.type = "KANNEL" | |
# set this backend in the server instance so it | |
# can callback when a message is received | |
self.server.backend = self | |
KannelHTTPHandler.backend = self | |
self._slug = "kannel" | |
try: | |
# connect to Kannel sendsms server to test if Kannel is up. | |
url = "http://%s:%d" % (self.kannel_host, self.kannel_port) | |
res = urllib.urlopen(url) | |
except Exception, err: | |
raise Exception("Unable to connect to Kannel: %s" % err) | |
if not res.code in (200, 202, 400, 404): | |
raise Exception("Unable to connect to Kannel: %s" % res.code) | |
def run (self): | |
while self.running: | |
if self.message_waiting: | |
msg = self.next_message() | |
KannelHTTPHandler.outgoing(msg) | |
self.server.handle_request() | |
class KannelHTTPHandler(BaseHTTPRequestHandler): | |
kannel_host = 'localhost' | |
kannel_port = 13013 | |
kannel_username = 'kannel' | |
kannel_password = 'kannel' | |
def respond(self, code, msg): | |
self.send_response(code) | |
self.send_header("Content-type", "text/plain") | |
self.end_headers() | |
self.wfile.write(_str(msg)) | |
def log_message(self, format, *args): | |
self.__class__.debug(format, *args) | |
def log_debug(self, format, *args): | |
self.__class__.debug(format, *args) | |
def log_error(self, format, *args): | |
self.__class__.error(format, *args) | |
def do_GET(self): | |
''' called on SMS arrival by Kannel | |
sender and message as parameter | |
http://server:port/+number/mymessage''' | |
def _plus(str): | |
return str.replace('%2B', '+') | |
# answer to / for testing purpose | |
if self.path == "/": | |
self.respond(200, "working") | |
return | |
# accepts /+digits/message | |
request_regex = re.compile(r"^/([\%B0-9]+)/(.*)") | |
match = request_regex.match(self.path) | |
if match: | |
# build the message | |
sender_id = _plus(match.group(1)) | |
text = _str(match.group(2)) | |
text = _plus(text.replace('+', ' ')) | |
# get time | |
received = datetime.utcnow() | |
msg = self.server.backend.message( | |
sender_id, | |
urllib.unquote(text), | |
date=received | |
) | |
# send the message to router | |
self.server.backend.route(msg) | |
# send HTTP response | |
self.respond(200, '') | |
return | |
return | |
@classmethod | |
def debug(cls, format, *args): | |
cls.backend.debug(format, *args) | |
@classmethod | |
def error(cls, format, *args): | |
cls.backend.error(format, *args) | |
@classmethod | |
def outgoing(cls, msg): | |
'''Used to send outgoing messages through this interface.''' | |
cls.debug("sending message: %s to %s. %s chars" % (msg.text, msg.connection.identity, msg.text.__len__())) | |
# remove non digit from number | |
target = re.compile('\D').sub("", msg.connection.identity) | |
# urlencode for HTTP get | |
message = _str(msg.text) | |
msg_enc = urllib.quote(message) | |
# send HTTP GET request to Kannel | |
try: | |
url = "http://%s:%d/cgi-bin/sendsms?username=%s&password=%s&to=%s&from=&text=%s"\ | |
% (cls.kannel_host, cls.kannel_port, cls.kannel_username, cls.kannel_password, target, msg_enc) | |
res = urllib.urlopen(url) | |
ans = res.read() | |
except Exception, err: | |
cls.error("Error sending message: %s" % err) | |
return False | |
if res.code == 202: | |
if ans.startswith('0: Accepted'): | |
kw = 'sent' | |
elif ans.startswith('3: Queued'): | |
kw = 'queued' | |
else: | |
kw = 'sent' | |
cls.debug("message %s: %s" % (kw, message)) | |
elif res.code == 503: | |
cls.error("message failed to send (temporary error): %s" % ans) | |
else: | |
cls.error("message failed to send: %s" % ans) |
@asuraphel I wouldn't remember since this was 13 years ago. I bet it was for simplicity and the fact that there is no additional dependency. Maybe it was a proof-of-concept that proved to be good-enough for my use case…
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @rgaudin any specific reasons for implementing this instead of using a web framework like Flask?