Changes between Version 2 and Version 3 of Future/RESTfulWebServices


Ignore:
Timestamp:
03/19/08 21:27:03 (17 years ago)
Author:
jbirch
Comment:

Gotta save more frequently...

Legend:

Unmodified
Added
Removed
Modified
  • Future/RESTfulWebServices

    v2 v3  
    44
    55= Overview =
    6 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 I am talking about 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.
    7 
    8 For now lets assume the URI: http://somemgsite.org/mapguide/rest is the root of the !MapGuide RESTful web services. All subsequent URIs below will leave this part off and focus on resources and their representations.
     6
     7This 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.
     8
     9In all cases, this proposal should attempt to make the best use of the HTTP 1.1 protocol as defined in [http://www.w3.org/Protocols/rfc2616/rfc2616.html RFC 2616].
     10
     11= Conventions =
     12
     13For now lets assume the URI: !http://example.org/mapguide/rest is the root of the !MapGuide RESTful web services. All subsequent URIs below will leave this part off and focus on resources and their representations.
     14
     15Unless 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.
     16
     17== HTTP header handling ==
     18
     19There 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.
     20
     21=== Cache-based headers ===
     22
     23Where 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.
     24
     25=== Accept / Content-Type ===
     26
     27It would be desirable for our rest services to support content negotiation.  For instance, if a client sends
     28
     29{{{
     30GET /library/MyStuff/Parcels.FeatureSource/content
     31Accept: application/json
     32}}}
     33
     34Then 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:
     35
     36{{{
     37GET /library/MyStuff/Parcels.FeatureSource/content?f=json
     38}}}
     39
     40or
     41
     42{{{
     43GET /library/MyStuff/Parcels.FeatureSource/content.json
     44}}}
     45
     46For each of the examples in this discussion, the supported types will be indicated but only the first form (without explicit type) will be shown.
     47
     48=== Method Overrides ===
     49
     50Again, 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)
     51
     52In cases where PUT is not supported, the client can use:
     53
     54{{{
     55X-HTTP-Method-Override: PUT
     56}}}
     57
     58In cases where DELETE is not supported, the client can use:
     59
     60{{{
     61X-HTTP-Method-Override: DELETE
     62}}}
     63
     64== Hyperlinks ==
     65
     66The MapGuide repository encodes resource references internally with a pseudo-URI that looks something like:
     67
     68{{{
     69  Library://PathTo/Resource.FeatureSource
     70}}}
     71
     72In this implementation, these "internal" hyperlinks will be transcoded to true hyperlinks to resources via the rest service.  For instance, the above would appear as:
     73
     74{{{
     75  http://example.org/mapguide/rest/library/PathTo/Resource.FeatureSource
     76}}}
     77
     78It is important to note that these will always be provided without hints to content type.
    979
    1080= Resources and Representations =
    11 In my twisted little mind !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.
     81
     82!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.
    1283
    1384== Resources and the Resource Service ==
    1485
     86Most 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.
     87
     88''' (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?'''
     89
    1590Resource Service supports the manipulation of resource content, headers, and data. A RESTful interface to get a resource might be:
    16 {{{
    17 GET
    18 .../library/MyStuff/Parcels.FeatureSource/content.xml
    19 }}}
    20 In this case the XML definition of the resource comes back in the HTTP response envelope. To get the JSON representation one could use:
    21 {{{
    22 GET
    23 .../library/MyStuff/Parcels.FeatureSource/content.json
    24 }}}
    25 To change or create a resource the PUT operation would be used instead with the resource content in the HTTP request envelope. For example:
     91
     92{{{
     93GET
     94.../library/MyStuff/Parcels.FeatureSource/content
     95}}}
     96
     97''' (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.'''
     98
     99
     100Likewise updating a header with permissions or meta data for an existing feature source would look like:
     101
    26102{{{
    27103PUT
    28 .../library/MyStuff/Parcels.FeatureSource/content.xml
    29 }}}
    30 Likewise to get or set a resource header to update permissions or meta data would be as follows:
    31 {{{
    32 GET
    33 .../library/MyStuff/Parcels.FeatureSource/header.xml
    34 
    35 PUT
    36 .../library/MyStuff/Parcels.FeatureSource/header.xml
    37 }}}
    38 And similarly to modify resource data the URIs would be:
    39 {{{
    40 GET
     104.../library/MyStuff/Parcels.FeatureSource/header
     105}}}
     106
     107And similarly, you could create resource data as (ensuring that the media type is set in the HTTP headers):
     108
     109{{{
     110POST
    41111.../library/MyStuff/Parcels.FeatureSource/data/<dataname>.<datatype>
    42 
    43 PUT
    44 .../library/MyStuff/Parcels.FeatureSource/data/<dataname>.<datatype>
    45 }}}
    46 Where <dataname> is the name of the data element and <datatype> is one of file, string, or stream. The actual type of the data element being stored should be specified in the media-type in the HTTP header.
    47 [[br]][[br]]
    48 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 parameters, depth and resource type, to limit the enumeration. We could support these through perverse URI encoding, but I think this is a better place to introduce parameters. So I propose the following:
    49 {{{
    50 GET
    51 .../library/MyStuff.xml
    52 
    53 Optional Parameters:
    54    depth=<depth>
    55    type=<resourcetype>
    56 }}}
    57 Like the other operations above we could also support /json. The depth and type parameters are optional and match those of the existing !EnumerateResources operation. Enumerating resource data is easier and would be represented as follows:
    58 {{{
    59 GET
    60 .../library/MyStuff/Parcels.FeatureSource/data.xml
    61 }}}
    62 So what about those session-based resources that !MapGuide is so dependent on. Well, from my perspective that is easy. You simply change the URI to address the session. For example to get layer definition content from a session the following could be used:
    63 {{{
    64 GET
    65 .../session/<uglysessionid>/Parcels.LayerDefinition/content.xml
    66 }}}
    67 '''TBD''' - Still need to think about how to support other Resource Service operations like Move, Copy, ApplyPackage, etc.
    68 [[BR]]
    69 [[BR]]
     112}}}
     113
     114Where <dataname> is the name of the data element and <datatype> is one of file, string, or stream.
     115
     116Another 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:
     117
     118{{{
     119GET
     120.../library/MyStuff?depth=<depth>&type=<resourcetype>
     121}}}
     122
     123Enumerating resource data is easier and would be represented as follows:
     124
     125{{{
     126GET
     127.../library/MyStuff/Parcels.FeatureSource/data
     128}}}
     129
     130So 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:
     131
     132{{{
     133GET
     134.../session/<uglysessionid>/Parcels.LayerDefinition
     135}}}
     136
     137Apart 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.
     138
     139For instance, copying a resource could be accomplished something like this:
     140
     141{{{
     142POST
     143.../services/CopyResource
     144Source=http://example.com/mapguide/rest/library/MyStuff/Parcels.FeatureSource
     145Destination=http://example.com/mapguide/rest/library/MyStuff/ParcelsCopy.FeatureSource
     146}}}
    70147
    71148== Alternate Representations for Feature Sources ==
    72 Now this is where things start to get interesting. What alternate representations do we expose for Feature Sources to support the Feature Services API. We'll start with basic schema access and drill down into feature selection, update, etc. So to describe the schema of a Feature Source I would propose something like:
    73 {{{
    74 GET
    75 .../library/MyStuff/MyData.FeatureSource/schema.xml [or .json]
    76 }}}
    77 Want just the schema of a particular schema or even a single class? How about:
    78 {{{
    79 GET
    80 .../library/MyStuff/MyData.FeatureSource/schema/<schemaname>.xml [or .json]
    81 GET
    82 .../library/MyStuff/MyData.FeatureSource/schema/<schemaname>/<classname>.xml [or .json]
    83 }}}
     149
     150Now this is where things start to get interesting. What alternate representations do we expose for Feature Sources to support the Feature Services API?
     151
     152
     153''' (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?.'''
     154
     155We'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:
     156
     157{{{
     158GET
     159.../library/MyStuff/MyData.FeatureSource/schema
     160}}}
     161
     162Want just the representation of a particular schema or even a single class? How about the following (with a type of XML or JSON):
     163
     164{{{
     165GET
     166.../library/MyStuff/MyData.FeatureSource/schema/<schemaname>
     167}}}
     168
     169{{{
     170GET
     171.../library/MyStuff/MyData.FeatureSource/schema/<schemaname>/<classname>
     172}}}
     173
    84174To get a list of the Spatial Contexts the following might work:
    85 {{{
    86 GET
    87 .../library/MyStuff/MyData.FeatureSource/spatialcontexts.xml [or .json]
    88 }}}
     175
     176{{{
     177GET
     178.../library/MyStuff/MyData.FeatureSource/spatialcontexts
     179}}}
     180
    89181Retrieving all features of a given class might look something like:
    90 {{{
    91 GET
    92 .../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>.xml [or .json]
     182
     183{{{
     184GET
     185.../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>
    93186
    94187Optional Parameters:
     
    100193   orderOption=<ascending | descending>
    101194}}}
     195
    102196For feature classes that have a single identity property, the following syntax may be used to retrieve a single feature:
    103 {{{
    104 GET
    105 .../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>/<id>.xml [or .json]
    106 }}}
     197
     198{{{
     199GET
     200.../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>/<id>
     201}}}
     202
    107203Where <id> is the unique id of the feature. Deleting features can be performed with the HTTP DELETE method as follows:
     204
    108205{{{
    109206DELETE
    110207.../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>/<id>
     208}}}
    111209
    112210or
    113211
     212{{{
    114213DELETE
    115214.../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>
     
    118217   filter=<fdo filter>
    119218}}}
     219
    120220To 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:
     221
    121222{{{
    122223POST
    123224.../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>
     225}}}
    124226
    125227and
    126228
     229{{{
    127230PUT
    128231.../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>
     
    131234   filter="<fdo filter>"
    132235}}}
    133 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 and an open question I have is whether the URI should specify the expected type?
     236
     237In 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 and an open question whether the URI should specify the expected type?
     238
    134239[[BR]]
    135240[[BR]]
     
    138243[[BR]]
    139244[[BR]]
     245
    140246== Alternate Representations for Maps, Map Definitions, and Layer Definitions ==
    141 I decided to lump the representations for Maps, Map Definitions, and Layer Definitions together as they all relate to creating and interacting with maps. The first thing that needs to be understood here 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.
    142 [[BR]]
     247
     248The 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.
     249
    143250The first thing we need is the ability to create a Map resource / !MgMap object.
     251
    144252{{{
    145253POST
     
    148256Optional Parameters:
    149257   name=<mapname>
    150 
     258}}}
     259
     260{{{
    151261POST
    152262.../session/<uglysesssionid>/createmap
     
    157267   envelope=x1,y1,x2,y2
    158268}}}
     269
     270''' (jb) ALTERNATIVE:  Unless you are going to be storing the map in those locations, I would prefer to see something like the following'''
     271
     272{{{
     273POST
     274.../session/<uglysesssionid>/MyMap.Map
     275
     276Required Parameters:
     277  MapDefinition=http://example.org/mapguide/rest/library/MyStuff/MyMap.MapDefinition
     278}}}
     279
     280and
     281
     282{{{
     283POST
     284.../session/<uglysesssionid>/MyMap.Map
     285
     286Required Parameters:
     287  srs=<srswkt>
     288  envelope=x1,y1,x2,y2
     289}}}
     290
     291''' (jb) ALTERNATIVE ENDS'''
     292
    159293The first creates a new map from the Map Definition resource identified in the URI. The later creates an empty map with the specified name, coordinate system, and extent. 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.
     294
    160295{{{
    161296GET