Recipe Sharing Protocol Specification

Version 1.0 Draft (November 2007)
Copyright 2007 Daniel G. Taylor

Table of Contents

  1. Overview
  2. Requirements
  3. Constants
  4. Structures
  5. Methods
  6. Examples

Overview

The Recipe Sharing Protocol (RSP) is a standard for sharing and publishing recipe information over a network. It provides a means for clients to connect to each other to share recipes as well as a means for clients to connect to a central server, upload recipes, and download other users' recipes.

RSP is built on XML-RPC, an IPC method that uses XML over HTTP to make remote method calls over a network. Recipes are stored in the Recipe Sharing Protocol Markup Language (RSPML), a simple XML format.

A client will generally connect to a server and retrieve a list of recipes along with some server information, and then periodically request hashes to check for updated information.

This document describes the features, values, structures, and methods required to implement the Recipe Sharing Protocol. It also provides some examples to show how to use the various methods and structures to retrieve and publish recipes on the network.

Requirements

The following lists requirements which clients and servers must support, as well as optional features that may be implemented.

Hashing

Clients and servers must support hasing of recipes in RSPML format by using the SHA-1 hasing algorithm. Hashes must be transfered as hexadecimal strings.

Compression

Before recipes are sent over the network they must be compressed using gzip compression. Any method that returns a recipe below is returning the gzip-compressed RSPML representation of the recipe.

XML-RPC

Clients must support sending XML-RPC requests. Servers must support receiving and replying to XML-RPC requests. A server must have all methods defined which are listed below in the common methods and the methods for that server type. Servers must support XML-RPC introspection methods.

Requests may be made via HTTPS, but this is not required. Servers may support XML-RPC Multicall.

DNS Service Discovery

Servers may advertise on the network using DNS Service Discovery (aka Zeroconf, Bonjour). Servers must use the service type of _recipe._tcp when advertising on the network.

Clients may browse for servers advertising the _recipe._tcp service on the network using DNS Service Discovery.

A TXT field called count may be set which must contain the number of shared recipes.

Constants

The following constants are defined for use by clients and servers with the methods below.

Response Codes

OK = 0
AUTH_ERROR = 1
SERVER_ERROR = 2
NAME_TAKEN = 3

Server Types

TYPE_SINGLE = 0
TYPE_MULTIPLE = 1

Search Flags

Field Binary Hex
SEARCH_NAME 00000001 0x01
SEARCH_DESCRIPTION 00000010 0x02
SEARCH_TAGS 00000100 0x04
SEARCH_INGREDIENTS 00001000 0x08
SEARCH_DIRECTIONS 00010000 0x10
SEARCH_USERS 00100000 0x20

Structures

Structures are used to send data back and forth in RSP. Every response from the server will have associated with it a return code and some data. The data depends on the method that was called.

Response

The response structure is the base of every response from the server. It contains a code representing the type of response (or if an error occured) and a data field.

If an error occurs the data field will generally have a string set with information on the specific error.

Response { code: RESPONSE_CODE data: ... }

ServerInfo

The server info structure contains information about the server, such as the name and version. The type variable determines whether the server is another client (and thus supports client to client methods) or a multi-user server (and thus supports client to server methods). The update interval is the minimum amount of time (in seconds) a client must wait before trying to get updated recipe hashes from the server.

Response { code: OK data: ServerInfo { name: string version: string type: SERVER_TYPE update_interval: int } }

Token

A token represents a unique value that is sent to a user when she logs in and is used to call other methods without having to resend password information. Tokens can be used by servers to keep information about a current session with a particular user.

On the server side, a token should last at least as long as the server's minimum update interval, so that clients don't have to reauthenticate when getting updates.

Response { code: OK data: string }

IDList

An ID list is an array of recipe IDs. They should be used to fetch individual recipes from the server using get_recipe or get_user_recipe. IDs are stored as integers.

Response { code: OK data: IDList [ id, id, id, id, ... ] }

HashList

A hash list is an array of lists containing recipe IDs and their hashes. This should be used to test against recipes that have changed and thus need to be refetched from the server. IDs are stored as integers, while hashes are stored as hexadecimal strings.

Response { code: OK data: HashList [ [id, hash], [id, hash], [id, hash], ... ] }

Recipe

A gzip-compressed RSPML representation of a recipe encoded to base64.

Response { code: OK data: base64 }

UserList

A user list is a list of usernames that are on a server, any of which can be used to retreive recipes, search, etc. User names are stored as strings.

Response { code: OK data: UserList [ name, name, name, ... ] }

QueryList

A query list stores lists of user names and recipe IDs that match criteria passed to the search method. User names are stored as strings. Recipe IDs are stored as integers.

Response { code: OK data: QueryList [ [name, id], [name, id], [name, id], ... ] }

Methods

Common Methods

Common methods must be present on all servers, and are used to get information about the server and authenticate with the server.

login(user, pass) => Token
get_info(token) => ServerInfo
logout(token) => Response

login(user, pass) => Token

Log into the server and get a session token. This must be called by all clients before calling functions that require a session token.

Anonymous authentication must be done using the username and password of anonymous. When a server requires authentication it must reply with an AUTH_ERROR. If, after sending a real username and password the return code is AUTH_ERROR again, then the username and/or password are invalid.

get_info(token) => ServerInfo

Get information about a server, such as the name, version, and type.

logout(token) => Response

Logout a user by invalidating the session token.

Client to Client Methods

These methods are to be used when communicating with another client.

get_recipe_ids(token) => IDList
get_recipe(token, id) => Recipe
get_recipe_hashes(token) => HashList

get_recipe_ids(token) => IDList

Get a list of recipe IDs from the server.

get_recipe_hashes(token) => HashList

Get a list of IDs and hashes from the server. Clients should periodically call this method and compare hashes to find which recipes have changed, been added or removed, and then call get_recipe to updated those.

get_recipe(token, id) => Recipe

Get a single recipe from the server given its ID.

Client to Server Methods

These methods are to be used when communicating with a recipe server containing multiple user accounts with their own published recipes.

register(user, pass) => Token update_pass(token, pass) => Response unregister(token) => Response publish_recipe(token, recipe) => Response update_recipe(token, recipe) => Response unpublish_recipe(token, id) => Response search(token, query, name, flags) => QueryList get_users(token) => UserList get_user_recipe_ids(token, name) => IDList get_user_recipe(token, name, id) => Recipe get_user_recipe_hashes(token, name) => HashList

register(user, pass) => Token

Register a new user with the server. Returns a token after logging in the newly registered user. When the name is already in use by another registered user an NAME_TAKEN will be returned.

update_pass(token, pass) => Response

Update the currently logged in user's password.

unregister(token) => Response

Delete the logged in user from the server. This will invalidate the user's current token. The server may delete all information (including recipes) from the deleted user.

publish_recipe(token, recipe) => Response

Publish a recipe under the currently logged in user's account.

update_recipe(token, recipe) => Response

Update a published recipe on the server.

unpublish_recipe(token, id) => Response

Remove a recipe that has been published from the server.

search(token, query, name, flags) => QueryList

Perform a search on the recipe server. The query string can be any space-separated list of strings or regular expressions to search for. The name argument specifies a specific users recipe's to search. The flags argument is a combination of SEARCH_FLAGS.

get_users(token) => UserList

Get a list of users registered on the server.

get_user_recipe_ids(token, name) => IDList

Get a list of published recipe IDs from a particular user.

get_user_recipe(token, name, id) => Recipe

Get a single published recipe from a user on the server given its ID.

get_user_recipe_hashes(token, name) => Recipe

Get a list of IDs and hashes from a user on the server. Clients should periodically call this method and compare hashes to find which recipes have changed, been added or removed, and then call get_user_recipe to updated those.

Examples

The examples below try to show typical usage of the Recipe Sharing Protocol. All examples are written in Python.

Fetching Recipes from a Simple Server

#!/usr/bin/env python

import sys
from gzip import decompress
from xmlrpclib import Server

OK = 0
TYPE_SINGLE = 0
recipelist = []

server = Server("http://localhost:8080")

print "Logging in..."
response = server.login("anonymous", "anonymous")
if response["code"] is OK:
        
token = response["data"]
elif response["code"] is NEED_AUTH:
        
print "Server requires a password!"
        
user = raw_input("User name: ")
        
password = raw_input("Password: ")
        
response = server.login(user, password)
        
if response["code"] is OK:
                
token = response["data"]
        
else:
                
print "Error logging in!"
                
sys.exit(1)

response = server.get_info(token)
if response["code"] is OK:
        
info = response["data"]
        
if info["type"] is not TYPE_SINGLE:
                
print "Not a simple server!"
                
sys.exit(1)

print "Fetching recipe IDs..."
response = server.get_recipe_ids(token)
if response["code"] is OK:
        
ids = response["data"]

for id in ids:
        
print "Fetching recipe " + str(id)
        
response = server.get_recipe(token, id)
        
if response["code"] is OK:
                
rspml = decompress(response["data"].decode())
                
recipelist.append(rspml)

server.logout(token)

print "Fetched all recipes."

This example illustrates how to write a simple client that logs into another client (simple server) and fetches all the recipes into a list.

Notice that even in the simplest case you must call login, get_recipe_ids, and then get_recipe in order to get any recipe data.

The call to get_info is useful in determining the type of the server and hence the methods you will use to communicate with it.

Normally you would want to do more error checking for when the code isn't returned as OK.