Though ChessGames' web service at "/perl/nph-chesspgn.pgn" uses CGI as a client-to-server messaging protocol and PGN as a data interchange format, you don't have to. If your client and web service already have PGN generators & parsers, there isn't as much reason to switch to another interchange format (such as XML or JSON), but a heavier messaging protocol such as
SOAP or XML-RPC can make coding web services & clients easier because you don't have to parse messages passed between client and server. Even if you want to stick with CGI for messaging, parsing JSON (using
JSON on Java client side) will be easier than writing your own PGN parser.
PHP has support for SOAP and XML-RPC, but it's not compiled into the PHP version on the free hosts. Ruby on Rails is great for rapid web service development; if only it were available on the free host. The same goes for
Django. Perl and Python have SOAP and XML-RPC packages you could add fairly easily. Actually, Python has the insanely easy to use (if not type) CGIXMLRPCRequestHandler:
games.py (service):
Code:
#!/usr/bin/env python
from SimpleXMLRPCServer import CGIXMLRPCRequestHandler
from localDBConn import user, passwd # localDBConn.py is mode 0600 & hides user credentials from prying eyes
import chess # chess package
handler=CGIXMLRPCRequestHandler()
handler.register_instance(chess.GameService(user, passwd))
handler.handle_request()
client:
Code:
#!/usr/bin/env python
import xmlrpclib
games=xmlrpclib.ServerProxy('http://chess.example.com/games')
game = games.get(1282672) # 1282672 is game id
In just a few lines, an HTTP POST turns into a function or method call. Damn,
Python is sweet.
The
chess.GameService referenced in games.py performs the DB query and is a little more involved. The only problem with it is I'm not certain if there's a way of installing
MySQLdb on the free hosts, though you could try uploading a local build.
chess/GameService.py:
Code:
import MySQLdb
from chess import Game
import sql # sql package needs to be defined
MySQLdb.paramstyle='numeric' # use (eg) ":1" for query parameters
class GameService:
def __init__(self, user, passwd, host="localhost", db="chess"):
self.conn = MySQLdb.connect (host = host,
user = user,
passwd = passwd,
db = db)
self.cursor = conn.cursor()
self.tables {'moves': 'moves', 'tags': 'san'}
def __del__(self):
self.cursor.close() #Cleanliness is next to godliness
self.conn.close()
def getTags(self, gid):
self.cursor.execute("SELECT * FROM %s WHERE id=:1" % self.tables['tags'], (gid,))
# Pair up column names and field values
return zip([col[0] for col in self.cursor.description], self.cursor.fetchone())
def getMoves(self, gid):
self.cursor.execute("SELECT move FROM %s WHERE id=:1 ORDER BY number" %self.tables['moves'], (gid,))
return [move[0] for move in self.cursor.fetchall()]
def get(gid):
tags=getTags(cursor, gid)
moves=getMoves(cursor, gid)
return Game(tags, moves)
def addGame(tags, moves):
ids = lookupID(tags)
if not gameExists(tags):
self.cursor.execute(sql.insert(self.tables['tags'], tags.keys()), tags.values())
ids = lookupID(tags)
if len(ids) == 1:
id=ids[0]
moves = [(id, n, m) for n,m in zip(range(1, len(moves)+2),moves)]
self.cursor.execute(sql.insertMany(self.tables['tags'], ['id', 'number', 'move'], len(moves)),
moves)
return id
else: # there are no or more than one rows with tags 'tags'; how did that happen?
# should probably return an error
pass
return ids
# Given game tags, find all rows with those tags
def lookupID(tags)
#...
def gameExists(tags):
ids=lookupID(tags)
return len(ids)
The MySQL access code hasn't been tested, but it shows you the gist. As you see from
addGame, to add new functionality to your web service, add new methods to the GameService class.
addGame is messy because the GameService isn't sufficiently abstract. If you want to go all out, take an entity model approach, with the Moves entity having a many-to-one relationship to Tags.
In the above, the "moves" table will have many, many short rows. With the primary index on moves.(gid, number), performance should be decent, but you could also denormalize the moves into a single field and combine "tags" and "moves" into a "games" table. This would reduce the load on the DB by making the client's task more complex.
sql.insert and
sql.insertMany take Python collections and create parameterized SQL statements to insert them. They aren't predefined classes; you'd need to write them.
To fetch a web resource with client side Java, you create a
URL object, open a connection with
URL.openConnection and read from the connections input stream. Corey Gold
demonstrates the basics when comparing Java to Python. If you want to go the XML-RPC route, grab
Apache XML-RPC.
GameClient.java:
Code:
package chess;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.XmlRpcException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Map;
import java.util.List;
import java.util.Vector;
import chess.Game;
public class GameClient {
protected final String host;
protected final String games_path;
public GameClient(host, path) {
this.host = host;
games_path = path;
}
public GameClient() {
this("http://chess.example.com", "/games");
}
public Game get(Integer gid) {
try {
XmlRpcClient rpcClient;
XmlRpcClientConfigImpl config;
config = new XmlRpcClientConfigImpl();
config.setServerURL(new URL(host + games_path));
rpcClient = new XmlRpcClient();
rpcClient.setConfig(config);
Vector getParams = new Vector(1);
getParams.add(gid);
Map<String, Object> gameData = (Map<String, Object>) rpcClient.execute("get", getParams);
Map<String, String> tags = (Map<String, String>)gameData.get("tags");
Object[] moves = (Object[]) gameData.get("moves");
return new Game(tags, Array.asList(moves));
} catch (XmlRpcException e) {
// ...
} catch (MalformedURLException e) {
// ...
} catch (IOException e) {
// ...
}
}
}
For comparison, here's the Python equivalent to the above:
chess/GameClient.py:
Code:
import xmlrpclib
class GameClient:
def __init__(self, host='http://chess.example.com', path='/games'):
self.host=host
self.path=path
self.games=xmlrpclib.ServerProxy(host+path)
def get(id)
gameData=self.games.get(id)
return Game(gameData[tags], gameData['moves'])
client:
Code:
#!/usr/bin/env python
import chess
game=chess.GameClient().get(1282672)