This page is part of the MapGuide Future section, where ideas are proposed and refined before being turned into RFCs (or discarded). Visit the Future page to view more!
The ideas presented here have been realised in the mapguide-rest project
Overview
This page is a living proposal for defining a RESTful Web Service interface to MapGuide. Most of the discussion here will center around the concepts of Resources, Addressability, and Representations (and probably some side debate about state). If you have no idea what REST is, the O'Reilly book RESTful Web Services by Leonard Richardson and Sam Ruby is a great place to start. RESTful web services are all about using HTTP the way it was meant to be used, and as such most operations are performed using standard HTTP GET, PUT, and DELETE methods. The HTTP HEAD, OPTIONS, and POST methods also play a role, but POST in particular needs to be used carefully in order to be considered RESTful.
In all cases, this proposal should attempt to make the best use of the HTTP 1.1 protocol as defined in RFC 2616.
Conventions
All of the calls here assume a HOST header similar to "HOST: example.org". As well, all requests would be located under /mapguide/rest but in this document we will assume that they start from the document root.
Unless otherwise stated, you can assume that a GET returns the content of the resource, a POST creates a new resource from the HTTP body contents, a PUT replaces the contents of an existing resource with the contents of the HTTP body, and a DELETE removes the resource. The HTTP contents returned for these requests is variable depending on what is requested, but proper HTTP response codes (200, 404, and many other relevant codes) must be issued to ensure correct behaviour of the clients.
HTTP header handling
There are certain components of the HTTP mechanism that should be supported, but some will also require pragmatic fallbacks to ensure that the MapGuide service works with existing applications.
Cache-based headers
Where possible, the rest services should take full advantage of the mechansims of the web to improve performance and reduce load. In particular, this means things like setting headers such as ETag and Last-Modified, and responding to HTTP HEAD requests like If-Modified-Since and If-None-Match with appropriate headers and data. See sections 13 and 14 of the HTTP spec.
Accept / Content-Type
It would be desirable for our rest services to support content negotiation. For instance, if a client sends
GET /library/MyStuff/Parcels.FeatureSource/content Accept: application/json
Then the services would respond with a JSON-encoded version of that resource. Unfortunately, the majority of current client applications make poor use of this mechanism, and in some cases there may be no official MIME type associated with the data we need to return, so we will also support alternate mechanisms for requesting different representations of the same resource. For instance, the above request could also be made like this:
GET /library/MyStuff/Parcels.FeatureSource/content?f=json
or
GET /library/MyStuff/Parcels.FeatureSource/content.json
For each of the examples in this discussion, the supported types will be indicated but only the first form (without explicit type) will be shown.
Method Overrides
Again, we need to be pragmatic. Although the preferred method of updating and deleting features is through the PUT and DELETE methods, these web services will also support an alternate mechanism for overloading the POST method to allow clients to work around firewalls that prohibit these methods. This strategy is the same used by Google with GData (http://code.google.com/apis/gdata/basics.html)
In cases where PUT is not supported, the client can use:
X-HTTP-Method-Override: PUT
In cases where DELETE is not supported, the client can use:
X-HTTP-Method-Override: DELETE
Hyperlinks
The MapGuide repository encodes resource references internally with a pseudo-URI that looks something like:
Library://PathTo/Resource.FeatureSource
In this implementation, these "internal" hyperlinks will be transcoded to true hyperlinks to resources via the rest service. For instance, the above would appear as:
http://example.org/library/PathTo/Resource.FeatureSource
Or, in an actual installation, as:
http://example.org/mapguide/rest/library/PathTo/Resource.FeatureSource
It is important to note that these will always be provided without hints to content type.
Resources and Representations
MapGuide at its core is a storage container of Resources (or more correctly the Resource Database is the container and Resource Service is the API for accessing it). The other MapGuide Services are a means of providing alternate representations of the resources stored in the resource database. In fact it is fairly easy to think of each Resource Type as having a number of representations, e.g. a Feature Source Definition has it's content (the resource itself), a schema, and features. If you follow and buy into that line of thinking, then the RESTful interface falls out fairly naturally. Let's start by looking at Resources themselves and the operations of Resource Service in a RESTful kind of way. Then we'll look at the different representations of each resource type and how that maps to our RESTful way of thinking.
Resources and the Resource Service
Most of the calls in this section (apart from places where arbitrary resource data is stored) support both XML and JSON encoded results. These can be requested using the pattern shown in the introduction of this document.
(jb) QUESTION: Can we provide an HTML representation of the repository as well? This would keep us honest with hyperlinking, and make an easy tool for authenticated users to navigate the repository. I think that a simple transcoding of the XML to HTML entities and wrapping it in an HTML body would be sufficient?
Resource Service supports the manipulation of resource content, headers, and data. A RESTful interface to get a resource might be:
GET /library/MyStuff/Parcels.FeatureSource/content
(jb) QUESTION: Is there any way that we could have GET /library/MyStuff/Parcels.FeatureSource/ return a set of links to the header, content, and resource data (and more, such as features, covered below)? Currently there is a gap in hyperlinking between the enumeration of MyStuff and the three components of the FeatureSource. We don't want to break the "discoverability" of these resources.
Likewise updating a header with permissions or meta data for an existing feature source would look like:
PUT /library/MyStuff/Parcels.FeatureSource/header
And similarly, you could create resource data as (ensuring that the media type is set in the HTTP headers):
POST /library/MyStuff/Parcels.FeatureSource/data/<dataname>.<datatype>
Where <dataname> is the name of the data element and <datatype> is one of file, string, or stream.
Another typical operation in Resource Service is enumerating resources and resource data. At first that seems simple, just use an HTTP GET operation on a library folder. However that could easily flood the client with way too much information. The EnumerateResources operation in Resource Service takes two optional parameters, depth and resource type, to limit the enumeration, and these can be specified as parameters:
GET /library/MyStuff?depth=<depth>&type=<resourcetype>
Enumerating resource data is easier and would be represented as follows:
GET /library/MyStuff/Parcels.FeatureSource/data
So what about those session-based resources that MapGuide is so dependent on for complex long-term user interactions? You simply apply the same logic as above to use URIs with a session prefix. For example, to get layer definition content from a session the following could be used:
GET /session/<uglysessionid>/Parcels.LayerDefinition
Apart from simple CRUD operations on resources, there are other operations that should be implemented as service endpoints. These include things like Copy, Move, ApplyPackage, etc, that require operations on multiple resources. For isntance, Copy must copy the resource content, header and data all in one atomic call. These will be implemeted under their own URIs.
For instance, copying a resource could be accomplished something like this:
POST /services/CopyResource Source=http://example.com/mapguide/rest/library/MyStuff/Parcels.FeatureSource Destination=http://example.com/mapguide/rest/library/MyStuff/ParcelsCopy.FeatureSource
Alternate Representations for Feature Sources
Now this is where things start to get interesting. What alternate representations do we expose for Feature Sources to support the Feature Services API?
(jb) QUESTION: Again, I am somewhat concerned that we are introducing elements (schema, spatialcontexts, features) that the client has to just know about. Is there any way that we can return an enumeration of these endpoints when the MyData.FeatureSource is requested?.
We'll start with basic schema access and drill down into feature selection, update, etc. To describe the schema of a Feature Source do something like:
GET /library/MyStuff/MyData.FeatureSource/schema
Want just the representation of a particular schema or even a single class? How about the following (with a type of XML or JSON):
GET /library/MyStuff/MyData.FeatureSource/schema/<schemaname>
GET /library/MyStuff/MyData.FeatureSource/schema/<schemaname>/<classname>
To get a list of the Spatial Contexts you would call:
GET /library/MyStuff/MyData.FeatureSource/spatialcontexts
Retrieving all features of a given class might look something like:
GET /library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname> Optional Parameters: properties=<p1,p2,...,pn> maxFeatures=n filter=<fdo filter> spatialFilter=<fdo spatial filter> orderBy=<p1,p2,...,pn> orderOption=<ascending | descending>
For feature classes that have a single identity property, the following syntax may be used to retrieve a single feature:
GET /library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>/<id>
Where <id> is the unique id of the feature. Deleting features can be performed with the HTTP DELETE method as follows:
DELETE /library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>/<id>
or
DELETE /library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname> Required Parameter: filter=<fdo filter>
(jb) QUESTION: Does this make sense? Shouldn't a DELETE on the class delete the class itself?
To support Insert and Update operations we'll need to rely on the HTTP POST (insert) and PUT (update) methods. These operations can be defined as follows:
POST /library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>
and
PUT /library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname> Required Parameter: filter="<fdo filter>"
In both cases the HTTP request envelope must contain a property value collection that defines either the properties of the newly created feature or the properties to be updated and their new values. The collection could be in either JSON or XML format.
(rb) QUESTION: whether the URI should specify the expected type?
(jb) QUESTION: Say what?
TBD - Still need to think about how to support other Feature Service operations
Alternate Representations for Maps, Map Definitions, and Layer Definitions
The representations for Maps, Map Definitions, and Layer Definitions are all dealt with together as they relate to creating and interacting with maps. The first thing that needs to be understood is the relationship between a Map Definition (resource type MapDefinition) and a Map (resource type Map and object representation MgMap). These are not the same things, however they are closely related. Looking at it from a programming perspective, a Map Definition would be a class and a Map would be an instance of a Map Definition. In more human terms, a Map Definition is a template or set of rules and a map is a per client/user object built from a Map Definition template. The Map object is stored in session state and knows the current list of layers and their visibility, current scale, and current extents. Having this information server-side allows MapGuide to render and plot maps with minimal client-server interaction.
The first thing we need is the ability to create a Map resource / MgMap object.
POST /session/<uglysesssionid>/MyMap.Map Required Parameters: MapDefinition=http://example.org/mapguide/rest/library/MyStuff/MyMap.MapDefinition
POST /session/<uglysesssionid>/MyMap.Map Required Parameters: srs=<srswkt> envelope=x1,y1,x2,y2
And for server-assigned resource naming (e.g. need a unique resource to ensure no collisions):
POST /services/createmap Required Parameters: session=<uglysesssionid> name=<mapname/ srs=<srswkt> envelope=x1,y1,x2,y2
All of these, of course, would return a "201 Created" response code, and the would reply with the elements specified in section 10 of the HTTP 1.1 spec. This includes a pointer to the created resource, which is particularly important in the latter case.
Now that we have a map, we need some way to render the map (with and without selection), the dynamic overlay (with and without selection), and base map tiles. These rendering calls support media types of JPG, PNG, and GIF, and we need some way of specifying PNG24 or PNG8.
(jb) How to do this with no separate MIME type? Do we need to use a format override? Are there browsers that need to have an extension for the image? And again, we run into not knowing what resource are available under the meta "MyMap.Map" resource; we need a way to enumerate the options.
GET /session/<uglysesssionid>/MyMap.Map/image Optional Parameters: <Big list of them from RenderingService::RenderMap>
POST /session/<uglysesssionid>/MyMap.Map/image Optional Parameters: <Big list of them from RenderingService::RenderMap>
The first one would render a single image of the map (all visible layers including base map) using the current state of the map from the session. The second provides a way to update the map state stored in the session and return a new image in a single shot. In the HTTP POST method case the post data is expected to contain a list of commands that would alter the map state, e.g. turn on these layers, turn off those layers, set the new center to this, etc.
So what about support for tiled maps with dynamic overlays.
GET /session/<uglysesssionid>/MyMap.Map/overlayimage Optional Parameters: <List of them from RenderingService::RenderDynamicOverlay>
POST /session/<uglysesssionid>/MyMap.Map/overlayimage Optional Parameters: <List of them from RenderingService::RenderDynamicOverlay>
GET /library/MyStuff/MyMap.MapDefinition/basetileimage/<baselayergroupname>/<scaleindex>/<tilecolumn>,<tilerow>
As with the normal map image delivery, the HTTP POST form of /overlayimage can include a set of commands for altering the state of the map object prior to rendering and returning the image. Note that base tile image access works based on a the Map Definition, not the runtime Map. That is because MapGuide caches base map tiles based on the groups within a Map Definition, not on a per user basis.
(jb) COMMENT: For the /basetileimage calls, it is critical that we support all standard HTTP caching headers. This means checking state of the underlying tile; does this require new APIs for the tile service to return properties of the existing tile when not generating fresh?
To build a cool client application, we need information about the map, the layers, the layer groups, etc. The next set of operations is designed to return an informational representation of map that can be used to create a compelling user interface. These could be in JSON or XML.
GET /session/<uglysesssionid>/MyMap.Map GET /session/<uglysesssionid>/MyMap.Map/layers GET /session/<uglysesssionid>/MyMap.Map/layers/<layername> GET /session/<uglysesssionid>/MyMap.Map/layergroups GET /session/<uglysesssionid>/MyMap.Map/layergroups/<layergroupname>
The output of these needs some thought, but basically would encapsulate the properties found in the MgMap, MgLayer, and MgLayerGroup objects in the Web API. To complete this we need to include some way to generate a icons for the legend (these would return PNG8, PNG24, GIF, or JPG images)
GET /library/MyStuff/MyLayer.LayerDefinition/legendicon/<mapscale>/<geomtype>/<themecat> Optional Parameters: width=<desiredimagewidth> height=<desiredimageheight>
Now of course no mapping client is complete without some way to interact with and query the features displayed on the map. The MapGuide Web API includes the !MgRenderingService::QueryFeatures APIs which could be exposed RESTfully as:
GET /session/<uglysesssionid>/MyMap.Map/features Optional (but highly recommended) Parameters: layerNames=<l1, l2, ..., ln> filter=<fdo filter> spatialFilter=<fdo spatial filter> maxFeatures=<maxdesiredfeatures>
More parameters are probably needed here, but you get the idea.
(jb) COMMENT: Some services have suggested storing the spatial filter feature as its own resource, and just refering to it in the GET. This would prevent problems with overflowing the GET max size
TBD - Still need more stuff for adding layers dynamically, plotting to DWF, etc.
(rb) Note: Within all of this the handling of selection is bugging me. We need to think about that some more and figure out what is the right way of handling selection RESTfully.
Other Necessary Stuff
We need some way for an application to create a session. We can do that with a POST to /session as follows.
POST /session
There is no required HTTP request envelope and the return envelope would simply contain the newly created session URI, e.g. http://somemgsite.org/mapguide/rest/session/<newuglysessionid>/ along with a 201 header.
Security, Authentication, and Access Control
This is all handled by native HTTP authentication. If generic service endpoints are required to create users and associate users with roles/groups, then these can be created as required. Access control is handled through modification of the resource headers.