פייתון מגיע עם שרת XML-RPC כחלק מהספריה הסטנדרטית שלו.
לאלו מכם שלא מכירים את XML-RPC – זהו פרוטוקול רשת , מבוסס XML שמאפשר להריץ פרוצדורות מרוחקות.
במילים אחרות, XML-RPC מאפשר לנו להריץ קוד על שרת מרוחק ולקבל חזרה את התוצאה.
זהו פרוטוקול יחסית פשוט לממשקי רשת, שממנו בסופו של דבר צמח הסטנדרט של SOAP.
בפייתון, גם צד הלקוח של הפרוטוקול, וגם שרת (מאד -מאד- בסיסי) כלולים בספריה הסטנדרטית,
ואני מוצא את שניהם קלים מאד להבנה ולשימוש.
לצורך ההסבר, בניתי שירות "ענן" (במרכאות כפולות ומכופלות) שמאפשר למשתמשים לקבל מסד נתונים על השרת, ולדבר עימו באמצעות XML-RPC.
כמובן, לא הייתי משתמש בקוד הזה בשביל שום דבר שאינו דוגמא מהסיבות הבאות:
1. אבטחה (לא קיימת כלל) – כל המידע עובר ברשת כטקסט ואינו מוצפן. אין שום מנגנון אימות משתמשים.
2. ביצועים (השרת הבסיסי משרת רק לקוח אחד בכל רגע נתון).
3. הקוד בקושי נבדק וסביר להניח שהרבה דברים לא יעבדו כמצופה.
אבל – בשביל ללמוד ולהבין כיצד להשתמש ב XML-RPC – הקוד הזה מושלם
אז נתחיל בצד השרת:
#!/usr/bin/env python
from SimpleXMLRPCServer import SimpleXMLRPCServer
import sqlite3
class SqliteServer(object):
"""
A wrapper class form SQLite.
used for registering XML-RPC methods.
"""
def __init__(self, db_file):
"""
start a connection.
A DB file is mandatory (will be created if doesn't alread exist)
"""
self.con = sqlite3.connect(db_file)
self._c = self.con.cursor()
def execute(self, query, params=None):
"""
run an SQL statement with optional parameters.
returns a pickleable result set.
"""
results = []
if params:
self._c.execute(query, params)
else:
self._c.execute(query)
for r in self._c:
results.append(r)
return results
def commit(self):
self.con.commit()
def list_tables(self):
"""
return a list of tables in the database, as an array
"""
query = """SELECT name FROM sqlite_master
WHERE type='table'
ORDER BY name"""
res = self.execute(query)
tables = []
for i in res:
if len(i) > 0:
tables.append(i[0])
return tables
if __name__ == '__main__':
# Init an SQLite wrapper pointing to a 'test.db' file
backend = SqliteServer('test.db')
# Start an XML-RPC server
server = SimpleXMLRPCServer(('localhost',8080), allow_none=True)
# Register the methods from the wrapper class
server.register_instance(backend)
# Serve.
server.serve_forever()
אז מה יש לנו כאן?
קודם כל, אנחנו מייבאים (import) מחלקה שנקראת SimpleXMLRPCServer, מתוך מודול בעל שם זהה.
זהו שרת ה XML-RPC הבסיסי שמגיע עם הפצת הפייתון. בנוסף אנחנו מייבאים את הספריה שמאפשרת לנו להשתמש ב SQLite, מסד הנתונים שלנו.
לאחר מכן, הגדרנו מחלקה משלנו, שמכילה מתודות שאחר כך השרת שלנו יחשוף לאויר העולם.
בגדול, יש לנו מתודות שמאפשרות לנו להריץ קוד SQL על מסד הנתונים (ולקבל חזרה מערך של תוצאות),
ומתודה שמחזירה לנו רשימה של טבלאות הקיימות במסד הנתונים.
לא אתעכב על כל מתודה, כי אנחנו כאן בשביל לדון ב XML-RPC, לא במסדי נתונים וב SQL.
לאחר הגדרת המחלקה, אנחנו מגיעים לריצה עצמה של התכנית.
דבר ראשון אנחנו יוצרים אובייקט מהמחלקה שכתבנו, ומכוונים אותו לקובץ שישמש כאחסון עבור מסד הנתונים שלנו.
לאחר מכן, אנחנו מגדירים אובייקט שרת XML-RPC, שיאזין על הכתובת המקומית, בפורט מספר 8080.
רצוי לא להריץ את הקוד הזה על שום כתובת אחרת מלבד המקומית, מהסיבות הפורטו לעיל.
בשלב הזה השרת עדיין איננו באויר, ואינו מקבל בקשות.
קודם אנחנו צריכים להגדיר לו אילו פרוצדורות לחשוף. את זה אנחנו עושים על ידי רישום האובייקט שלנו בשרת.
השרת כבר יודע לבחון בעצמו את האובייקט, ולחשוף את הפונקציות שבתוכו.
ניתן לרשום גם פונקציות בודדות, אחת אחת בשרת – אבל אני מוצא את השיטה הזאת נוחה יותר.
עכשיו שהשרת יודע אילו פונקציות לחשוף, אפשר להגיד לו להאזין על הכתובת, ולהתחיל לקבל בקשות.
וזהו. מפה הלאה התכנית שלנו תמשיך לרוץ עד ש-
- נהרוג אותה ידנית
- נגרום איכשהו לשגיאה פאטאלית שתגרום לשרת ליפול.
וזהו. זה צד השרת.
צד הלקוח:
#!/usr/bin/env python
import xmlrpclib
if __name__ == '__main__':
# Start an XML-RPC client
server = xmlrpclib.ServerProxy('http://localhost:8080')
# Check if a table called 'students' exists
if not 'students' in server.list_tables():
# If not, create it.
server.execute('create table students (fname text, lname text)')
server.commit()
# Insert a new student...
server.execute('insert into students values (?, ?)', ('billy','bob'))
server.commit()
# Get all students
resp = server.execute('select * from students')
for i in resp:
print(i)
כמו שאתם רואים, לא הרבה קוד.
התחלנו מלייבא את הספריה שמטפלת בXML-RPC.
לאחר מכן, הגדנו אובייקט proxy שמתחבר לשרת שלנו, ודרכו נוכל להריץ את הקוד המרוחק
לאחר מכן, נריץ את המתודה שמחזירה לנו את שמות הטבלאות הקיימות. אם טבלת הסטודנטים לא קיימת,
נריץ מתודה נוספת, שמקבלת קוד SQL, ולמעשה יוצרת את הטבלה
עכשיו, כשאנחנו יודעים בוודאות שהטבלה קיימת, נאכלס אותה בנתונים, ונשלוף את כל תכנה.
שימו לב, שהקריאה למתודות שרשמנו בשרת היא קריאה ישירה, כאילו היתה זו קריאה למתודה על אובייקט הפרוקסי.
כך למעשה כל מה שמתרחש ברקע (תקשורת בין השרת ללקוח, יצירה ועיבוד של ה XML, המרה בין סוגי הנתונים של XML-RPC לבין אילו של פייתון)
למעשה שקוף מבחינתנו.
עוד על המודולים שתוארו כאן אפשר למצוא בתיעוד הרשמי הנהדר של פייתון.