Changes between Version 2 and Version 3 of Future/RESTfulWebServices
- Timestamp:
- 03/19/08 21:27:03 (17 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
Future/RESTfulWebServices
v2 v3 4 4 5 5 = 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 7 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. 8 9 In 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 13 For 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 15 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. 16 17 == HTTP header handling == 18 19 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. 20 21 === Cache-based headers === 22 23 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. 24 25 === Accept / Content-Type === 26 27 It would be desirable for our rest services to support content negotiation. For instance, if a client sends 28 29 {{{ 30 GET /library/MyStuff/Parcels.FeatureSource/content 31 Accept: application/json 32 }}} 33 34 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: 35 36 {{{ 37 GET /library/MyStuff/Parcels.FeatureSource/content?f=json 38 }}} 39 40 or 41 42 {{{ 43 GET /library/MyStuff/Parcels.FeatureSource/content.json 44 }}} 45 46 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. 47 48 === Method Overrides === 49 50 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) 51 52 In cases where PUT is not supported, the client can use: 53 54 {{{ 55 X-HTTP-Method-Override: PUT 56 }}} 57 58 In cases where DELETE is not supported, the client can use: 59 60 {{{ 61 X-HTTP-Method-Override: DELETE 62 }}} 63 64 == Hyperlinks == 65 66 The MapGuide repository encodes resource references internally with a pseudo-URI that looks something like: 67 68 {{{ 69 Library://PathTo/Resource.FeatureSource 70 }}} 71 72 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: 73 74 {{{ 75 http://example.org/mapguide/rest/library/PathTo/Resource.FeatureSource 76 }}} 77 78 It is important to note that these will always be provided without hints to content type. 9 79 10 80 = 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. 12 83 13 84 == Resources and the Resource Service == 14 85 86 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. 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 15 90 Resource 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 {{{ 93 GET 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 100 Likewise updating a header with permissions or meta data for an existing feature source would look like: 101 26 102 {{{ 27 103 PUT 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 107 And similarly, you could create resource data as (ensuring that the media type is set in the HTTP headers): 108 109 {{{ 110 POST 41 111 .../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 114 Where <dataname> is the name of the data element and <datatype> is one of file, string, or stream. 115 116 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: 117 118 {{{ 119 GET 120 .../library/MyStuff?depth=<depth>&type=<resourcetype> 121 }}} 122 123 Enumerating resource data is easier and would be represented as follows: 124 125 {{{ 126 GET 127 .../library/MyStuff/Parcels.FeatureSource/data 128 }}} 129 130 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: 131 132 {{{ 133 GET 134 .../session/<uglysessionid>/Parcels.LayerDefinition 135 }}} 136 137 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. 138 139 For instance, copying a resource could be accomplished something like this: 140 141 {{{ 142 POST 143 .../services/CopyResource 144 Source=http://example.com/mapguide/rest/library/MyStuff/Parcels.FeatureSource 145 Destination=http://example.com/mapguide/rest/library/MyStuff/ParcelsCopy.FeatureSource 146 }}} 70 147 71 148 == 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 150 Now 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 155 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: 156 157 {{{ 158 GET 159 .../library/MyStuff/MyData.FeatureSource/schema 160 }}} 161 162 Want 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 {{{ 165 GET 166 .../library/MyStuff/MyData.FeatureSource/schema/<schemaname> 167 }}} 168 169 {{{ 170 GET 171 .../library/MyStuff/MyData.FeatureSource/schema/<schemaname>/<classname> 172 }}} 173 84 174 To 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 {{{ 177 GET 178 .../library/MyStuff/MyData.FeatureSource/spatialcontexts 179 }}} 180 89 181 Retrieving 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 {{{ 184 GET 185 .../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname> 93 186 94 187 Optional Parameters: … … 100 193 orderOption=<ascending | descending> 101 194 }}} 195 102 196 For 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 {{{ 199 GET 200 .../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>/<id> 201 }}} 202 107 203 Where <id> is the unique id of the feature. Deleting features can be performed with the HTTP DELETE method as follows: 204 108 205 {{{ 109 206 DELETE 110 207 .../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname>/<id> 208 }}} 111 209 112 210 or 113 211 212 {{{ 114 213 DELETE 115 214 .../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname> … … 118 217 filter=<fdo filter> 119 218 }}} 219 120 220 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: 221 121 222 {{{ 122 223 POST 123 224 .../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname> 225 }}} 124 226 125 227 and 126 228 229 {{{ 127 230 PUT 128 231 .../library/MyStuff/MyData.FeatureSource/features/<schemaname>/<classname> … … 131 234 filter="<fdo filter>" 132 235 }}} 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 237 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 whether the URI should specify the expected type? 238 134 239 [[BR]] 135 240 [[BR]] … … 138 243 [[BR]] 139 244 [[BR]] 245 140 246 == 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 248 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. 249 143 250 The first thing we need is the ability to create a Map resource / !MgMap object. 251 144 252 {{{ 145 253 POST … … 148 256 Optional Parameters: 149 257 name=<mapname> 150 258 }}} 259 260 {{{ 151 261 POST 152 262 .../session/<uglysesssionid>/createmap … … 157 267 envelope=x1,y1,x2,y2 158 268 }}} 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 {{{ 273 POST 274 .../session/<uglysesssionid>/MyMap.Map 275 276 Required Parameters: 277 MapDefinition=http://example.org/mapguide/rest/library/MyStuff/MyMap.MapDefinition 278 }}} 279 280 and 281 282 {{{ 283 POST 284 .../session/<uglysesssionid>/MyMap.Map 285 286 Required Parameters: 287 srs=<srswkt> 288 envelope=x1,y1,x2,y2 289 }}} 290 291 ''' (jb) ALTERNATIVE ENDS''' 292 159 293 The 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 160 295 {{{ 161 296 GET