Designing RESTful Services
Designing RESTful Services
In this section, we’ll start from a traditional RPC-based service and redesign it to become a RESTful service. To accomplish this, first we’ll extract the resources that make up the existing service. Then we’ll design a URI scheme for identifying the resources and decide which HTTP methods they’ll support. And finally, we’ll design the resource representations that will be supported by each method.
A Traditional RPC-based Service
Let’s suppose your company provides an online bookmarking service similar to what’s provided by Windows Live Favorites, Google Bookmarks, or delicious. We’ll assume it was originally implemented using SOAP with an RPC-based design. The service supports the list of operations described in Figure 3.
Figure 3: An RPC-based bookmarking service
| Operation | Description |
|---|---|
createUserAccount | Creates a new user account |
getUserAccount | Retrieves user account details for the authenticated user |
updateUserAccount | Updates user account details for the authenticated user |
deleteUserAccount | Deletes the authenticated user’s account |
getUserProfile | Retrieves a specific user’s public profile information |
createBookmark | Creates a new bookmark for the authenticated user |
updateBookmark | Updates an existing bookmark for the authenticated user |
deleteBookmark | Deletes one of the authenticated user’s bookmarks |
getBookmark | Retrieves a specific bookmark (anyone can retrieve a public bookmark; only authenticated users can retrieve a private bookmark) |
getUserBookmarks | Retrieves the user’s private bookmarks, allows filtering by tags |
getUserPublicBookmarks | Retrieves the user’s public bookmarks, allows filtering by tags |
getPublicBookmarks | Retrieves all public bookmarks, allows filtering by tags |
Several of these operations are publicly accessible including createUserAccount, getUserProfile, getUserPublicBookmarks, getPublicBookmarks, and getBookmark (assuming the bookmark is public). Anyone can use these operations without authentication. The remaining operations, however, require the user to provide valid credentials and can be used only by valid, authenticated users. For example, only an authenticated user can modify or delete his account or create, update, and delete bookmarks.
Also, bookmarks can be marked as “public” or “private” and they can be labeled with arbitrary textual “tags.” Anyone can retrieve public bookmarks, but only authenticated users can access private bookmarks. Plus, all of the operations that return bookmark collections can be filtered by “tags.”
Moving from Verbs to Nouns
The first step in designing a RESTful service is to identify the resources the service will expose. From inspecting the operations in Figure 3, it looks like there are just a few resources in play:
- Users
- Bookmarks
However, we need to get a little more specific than this since we’ll need the ability to operate on individual bookmarks as well as different collections of bookmarks. After analyzing the current functionality, it’s apparent we’ll need the ability to address the following types of resources:
- An individual user account
- A specific user’s public profile
- An individual bookmark
- A user’s collection of private bookmarks
- A user’s collection of public bookmarks
- The collection of all public bookmarks
You can think of these things as the “nouns” that make up the service. The original service design outlined in Figure 3 focused on “verbs” and not the underlying nouns. This is where RESTful design takes a radical turn. With REST, you focus first on the nouns first (e.g., the resources) because you’ll rely on a standard set of verbs (the uniform interface) to operate on them within the service.
Designing the URI Templates
Now that we’ve identified the fundamental resources that make up our service, our next task is to define identifiers for them. Since we plan to host this service on the “Web,” we’ll rely on the Web’s URI syntax for identifying these resources.
To keep things simple for consumers, we’ll use the service’s base address to identify the list of all public bookmarks. So if our service were hosted at http://contoso.com/bookmarkservice, we’d browse to that address to retrieve the list of all public bookmarks. Since the list of all public bookmarks can get quite large, we should probably also provide a way to filter the collection of bookmarks somehow. We can accomplish this by building additional scoping information into the URI design. For example, we can use the following query string to identify all public bookmarks marked with a particular tag:
?tag={tag}
In this case, the “tag” is providing additional scoping information that consumers can use to reduce the identified collection’s size. The syntax I’m using here is referred to as URI template syntax.
Anything within curly braces represents a variable, like tag in this case. Everything else in the URI (not enclosed within curly braces) is considered a static part of the URI. Later, when we implement the service, you’ll see how to map these URI variables to method parameters in our WCF code.
The URI template in this case is relative to the service’s base URI. So you can identify all public bookmarks marked with the “rest” tag using http://contoso.com/bookmarkservice?tag=rest.
We can further filter the list of public bookmarks by username. In this case, we’ll use the username as part of the path to filter the collection by user before applying the tag scoping information:
{username}?tag={tag}
For example, you can identify all of skonnard’s bookmarks marked with “wcf” using http://contoso.com/bookmarkservice/skonnard?tag=wcf. And you can access all of onion’s bookmarks marked with “silverlight” using http://contoso.com/bookmarkservice/onion?tag=silverlight.
Next, let’s think about how to identify a particular user. Since we’ve already used a variable in the first path segment (for identifying a user’s public bookmarks), we’ll need to specify a literal string in that first segment to change the meaning of what comes next. For example, we can say that all URIs starting with “users” will identify a specific user. We’ll use the following templates to identify the user resources:
/users/{username}
/users/{username}/profile
And we can identify a user’s complete list of bookmarks by adding “bookmarks” instead:
/users/{username}/bookmarks
Plus, like before, we can filter bookmarks by using “tag” scoping information:
/users/{username}/bookmarks?tag={tag}
When it comes to identifying individual bookmarks, we have to make a decision about how to do that. If we assign each bookmark a unique Id, we could potentially use a simpler URI template for identifying individual bookmarks based on the Id. However, since bookmarks really belong to a specific user, it might make sense to make individual bookmark identifiers relative to a particular user as shown here:
/users/{username}/bookmarks/{id}
Figure 4 summarizes the URI design for our RESTful bookmark service. Now that we know what resources we’re dealing with and how to identify them, we can turn our attention to thinking about which methods on the uniform interface we’ll support for each of these resources.
Figure 4: The BookmarkService URI design
| Operation | Description |
|---|---|
A collection of all public bookmarks | ?tag={tag} |
A user’s collection of public bookmarks | {username}?tag={tag} |
An individual user account | users/{username} |
A specific user’s public profile | users/{username}/profile |
A user’s collection of bookmarks | users/{username}/bookmarks?tag={tag} |
An individual bookmark | users/{username}/bookmarks/{id} |
Applying the Uniform HTTP Interface
For the publicly accessible bookmark collections and the public user profile resource, we’ll only support GET requests. You can think of these as read-only resources. We’ll return a 200 (“OK”) when the requests are successful. If the URI doesn’t identify a known user, we’ll return 404 (“Not Found”).
The remaining resources will allow creating and modifying resources so you can think of them as read/write resources. For example, we’ll support GET, PUT, and DELETE on user account resources to replace the equivalent operations in the RPC version. We’ll use PUT to create new user accounts, GET to retrieve them, PUT (again) to update them, and DELETE to delete them.
We can use PUT to create new user accounts because the client is the one picking the username that forms the new resource’s URI. If the client is successful, the service will return a 201 (“Created”) response. If the client attempts to use an existing username, the service will return a 401 (“Unauthorized”) response. When issuing the PUT request to create a user account, the client will provide a user account representation in the HTTP request body containing the user’s information.
Once created, a user can retrieve her account resource by issuing a GET request to her account URI. She can issue a PUT request to update her account resource by supplying an updated user account representation. She can also issue a DELETE request (with no representation) to delete her account. When these operations are successful, the service returns a 200 (“OK”) response. If the client attempts to update or delete a non-existent account, the service will return a 404 (“Not Found”) response.
For individual bookmark resources, we’ll support GET, POST, PUT, and DELETE requests. If a particular bookmark is marked as private, only the owner can retrieve it, but if it’s public, anyone can retrieve it. When a user creates a new bookmark, the service is responsible for assigning it a unique bookmark Id. Hence, the client won’t know the Id to use in the URI ahead of time. So, instead of using a PUT request, we’ll have users POST bookmarks to the user’s bookmark collection resource. The handler for the POST request will create the new bookmark, assign it an Id, and return a 201 (“Created”) to the client, specifying the URI of the new bookmark resource in the Location header.
This explains when to use POST or PUT for creating new resources. The answer ultimately lies in who is responsible for determining the new resource’s URI. If the client is in charge, the client can use PUT to the new URI (like we did for user accounts) and the service can return a response code of 201 (“Created”). However, if the service is in charge of generating the new URI, the client should POST the new resource to a factory URI like we’ve done for bookmarks. Then, the service can return a response code of 201 (“Created”) along with the URI of the new resource in the response “Location” header.
Once clients know the bookmark ID’s, they can issue PUT and DELETE requests to individual bookmark URIs to update and delete bookmarks. If successful, the service will return a 200 (“OK”). If the client uses a URI that doesn’t exist, the service will return a 404 (“Not Found”) response.
Figure 5 summarizes the final design of our RESTful interface for bookmark resources, showing which HTTP methods we’ll support with each resource. We’ve been able to completely replace the functionality found in the original RPC service through HTTP’s uniform interface.
Figure 5: RESTful interface for user accounts
| Method | URI Template | Equivalent RPC Operation |
|---|---|---|
PUT | users/{username} | createUserAccount |
GET | users/{username} | getUserAccount |
PUT | users/{username} | updateUserAccount |
DELETE | users/{username} | deleteUserAccount |
GET | users/{username}/profile | getUserProfile |
POST | users/{username}/bookmarks | createBookmark |
PUT | users/{username}/bookmarks/{id} | updateBookmark |
DELETE | users/{username}/bookmarks/{id} | deleteBookmark |
GET | users/{username}/bookmarks/{id} | getBookmark |
GET | users/{username}/bookmarks?tag={tag} | getUserBookmarks |
GET | {username}?tag={tag} | getUserPublicBookmarks |
GET | ?tag={tag} | getPublicBookmarks |
Security Considerations
Our service requires the ability to authenticate users so we can authorize the resources and methods they’re allowed to access. For example, only authenticated users can access their own user account resources and operate on them. And only authenticated users can create new bookmarks and operate on them. If an unauthenticated user attempts to do so – or a user attempts to operate on another user’s resources – the service needs to return a 401 (“Unauthorized”) response and deny access.
So we need to figure out how we’ll identify users in order to authenticate them. HTTP comes with some built-in authentication mechanisms, the most popular of which is basic access authentication. This is one of the most popular authentication schemes used on the Web today because it’s so easy and widely supported, but it’s also one of the most unsecure, because passwords are sent across the wire in a simple-to-decode plain text format. One way around this is to require SSL (HTTPS) for all HTTP traffic that will be using basic authentication, thereby encrypting the pipe carrying the passwords.
Another approach is to use digest authentication, another authentication scheme built into HTTP. Digest authentication prevents eavesdropping by never sending the password across the wire. Instead, the authentication algorithm relies on sending hash values computed from the password and other values known only by the client and server. This makes it possible for the server to recompute the hash found in an incoming message to validate that client has possession of the password.
Here’s how it works. When a client attempts to access a protected resource, the server returns a 401 (“Unauthorized”) response to the client along with a “WWW-Authenticate” header indicating that it requires digest authentication along with some supporting data. Once the client receives this, it can generate an “Authorization” header containing the computed hash value and send an identical request back to the server including the new header. Assuming the client generates a valid “Authorization” header, the server will allow access to the resource. Digest authentication is better than basic but it’s still subject to offline dictionary and brute force attacks (unless you enforce a really strong password policy), and it’s not as widely supported by Web browsers and servers.
Another approach is to avoid both basic and digest authentication and implement a custom authentication scheme around the “Authorization” header. Many of these schemes use a custom Hash Message Authentication Code (HMAC) approach, where the server provides the client with a user id and a secret key through some out-of-band technique (e.g., the service sends the client an e-mail containing the user id and secret key). The client will use the supplied secret key to sign all requests.
For this approach to work, the service must define an algorithm for the client to follow when signing the requests. For example, it must outline how to canonicalize the message and which parts should be included in the HMAC signature along with the secret key. This is important because the client and service must follow the same algorithm for this to work. Once the client has generated the HMAC hash, it can include it in the “Authorization” header along with the user id:
Authorization: skonnard:uCMfSzkjue+HSDygYB5aEg==
When the service receives this request, it will read the “Authorization” header and split out the user id and hash value. It can find the secret for the supplied user id and perform the same HMAC algorithm on the message. If the computed hash matches the one in the message, we know the client has possession of the shared secret and is a valid user. We also know that no one has tampered with whatever parts of the message were used to compute the HMAC hash (and that could be the entire message). In order to mitigate replay attacks, we can include a timestamp in the message and include it in the hash algorithm. Then the service can reject out-of-date messages or recently seen timestamp values.
The HMAC approach is superior to both basic and digest authentication, especially if the generated secrets are sufficiently long and random, because it doesn’t subject the password to dictionary or brute force attacks. As a result, this technique is quote common in today’s public facing RESTful services.
For the service we’re designing, we could pick any of these techniques to authenticate users. We’ll assume an HMAC approach for our service, and that each user will be assigned a secret key through an out-of-band e-mail after creating a new user account. And for all of the non-public resources, we’ll look for a valid HMAC hash in the “Authorization” header and return a 401 (“Unauthorized”) when necessary.
Now that we have a way for users to prove who they are, we’ll need logic to authorize their requests, in other words, to decide what they are allowed to do. For example, any authenticated or anonymous user may retrieve any public bookmark, while private bookmarks may only be retrieved by the authenticated user who owns them. We’ll see how to implementation this authorization logic later in the paper.
Designing the Resource Representations
Now we need to decide how we’re going to represent the resources exposed by our service. There are many different data formats commonly used to represent resources on the Web including plain text, form-encoding, HTML, XML, and JSON, not to mention the variety of different media formats used to represent images, videos, and the like. XML is probably the most popular choice for RESTful services, although JSON has been growing in popularity thanks to the Web 2.0/Ajax movement.
XML is easier to consume in most programming languages (e.g., .NET) so it’s often the default format. However, for browser-based scenarios (which abound), the JavaScript Object Notation (JSON) is actually easier to consume because it’s a JavaScript native format. For the service we’re designing, we’ll support both XML and JSON to accommodate both client scenarios equally well.
An important thing to think about while designing resource representations is how to define the relationships between different resources. Doing so will allow consumers to navigate the “Web” of resources exposed by your service, and even discover how to use navigate service by actually using it.
Let’s begin by designing the XML representation for a user account. When creating a new user account, we need the user to supply only a name and e-mail address (remember, the username is represented in the URI). The following is the XML format we’ll use for creating new user accounts:
<User>
<Email>aaron@pluralsight.com</Email>
<Name>Aaron Skonnard</Name>
</User>
However, when a user retrieves his user account resource, the service will supply a different representation containing a little more information, in this case an Id and a link. We’ll provide the Id that links back to this particular user resource and a link to this user’s list of public bookmarks:
<User>
<Bookmarks>http://contoso.com/bookmarkservice/skonnard</Bookmarks>
<Email>aaron@pluralsight.com</Email>
<Id>http://contoso.com/bookmarkservice/skonnard</Id>
<Name>Aaron Skonnard</Name>
</User>
There may be other pieces of information that make sense only in either the request or response representations. A valid user can update this representation with a new e-mail address or a different name and PUT it back to the same URI to perform an update.
A user’s public profile will provide yet another representation because we probably don’t want to share one user’s e-mail address with another. Here’s what we’ll use for the user profile resource:
<UserProfile>
<Bookmarks>http://contoso.com/bookmarkservice/skonnard</Bookmarks>
<Id>http://contoso.com/bookmarkservice/skonnard</Id>
<Name>Aaron Skonnard</Name>
</UserProfile>
Now let’s turn our attention to bookmark resources. For our example, a bookmark is a pretty simple data set. When a user creates a bookmark, it must provide a title, a URL, some optional tags, and a public/private flag. We’ll support the following representation for creating new bookmarks:
<Bookmark>
<Public>true</Public>
<Tags>REST,WCF</Tags>
<Title>Aaron’s Blog</Title>
<Url>http://pluralsight.com/aaron</Url>
</Bookmark>
And we’ll use a slightly enhanced representation when returning bookmark resources that includes an Id, additional information about the user who created it, and a last-modified time:
<Bookmark>
<Id>http://contoso.com/bookmarkservice/users/skonnard/bookmarks/13</Id>
<LastModified>2008-03-12T00:00:00</LastModified>
<Public>true</Public>
<Tags>REST,WCF</Tags>
<Title>Aaron's Blog</Title>
<Url>http://pluralsight.com/aaron</Url>
<User>skonnard</User>
<UserProfile>http://contoso.com/bookmarkservice/users/skonnard/profile
</UserProfile>
</Bookmark>
A list of bookmarks will simply be represented by a <Bookmarks> element, containing a list of child <Bookmark> elements as shown here:
<Bookmarks>
<Bookmark>
<Id>http://contoso.com/bookmarkservice/users/skonnard/bookmarks/13</Id>
<LastModified>2008-03-12T00:00:00</LastModified>
<Public>true</Public>
<Tags>REST,WCF</Tags>
<Title>Aaron's Blog</Title>
<Url>http://pluralsight.com/aaron</Url>
<User>skonnard</User>
<UserProfile>http://contoso.com/bookmarkservice/users/skonnard/profile
</UserProfile>
</Bookmark>
<Bookmark>...</Bookmark>
<Bookmark>...</Bookmark>
</Bookmarks>
These representations make is possible to navigate between different types of resources, and they are simple to consume in any programming framework that includes a simple XML API.
Supporting Alternate Representations
We can expose our resources in several representations to accommodate different client scenarios. For example, some clients (like Web browsers) will have an easier time dealing with JSON representations than XML. But if we decide to support multiple formats, we’ll need the client to specify which format it wants somehow. There are a few ways to handle this, either by using HTTP’s content negotiation headers (e.g., Accept) or by encoding the desired resource format into the URI.
Both approaches are completely RESTful, but I prefer the latter because it allows the URI to contain all of the necessary information. We’ll assume XML is the default representation for our URIs, and we’ll extend them to support JSON by adding “?format=json” to the end of each URI template:
?tag={tag}&format=json
{username}?tag={tag}&format=json
users/{username}?format=json
users/{username}/profile?format=json
...
This is an example of what the new user account resource would look like in JSON:
{User:{Email:'aaron@pluralsight.com', Name:'Aaron Skonnard'}}
This is just another representation of the same resource. Again, the reason for supporting multiple formats is to make things easier for certain types of clients. It wouldn’t be hard to also support form-encoding to simplify things for Web browser forms or other text-based formats (e.g., CSV) in order to accommodate even more client application scenarios (e.g., Microsoft Excel).
Standard Representations: XHTML & Atom
The problem with using a custom XML vocabulary is you’ll have to provide metadata (like an XML Schema definition) and documentation for clients consuming your resources. If you can use a standard format, on the other hand, you will immediately have an audience that knows how to consume your service. There are two standard formats that are quite popular today: XHTML and Atom/AtomPub .
One of the benefits of using XHTML as the representation format is that it can be rendered in a Web browser for human viewing during development. With XHTML, you can represent lists of items and you can use forms to encode additional metadata that describes how to interact with other linked resources.
AtomPub is another popular choice because it was specifically designed to represent and manipulate collections of resources. There are many feed-aware clients (including modern Web browsers) that know how to render Atom feeds, providing a human view that can prove helpful during development.
The main downside to using either of these formats is that they are somewhat constrained in terms of the data set the XML vocabulary was designed to model (e.g., there’s not a natural way to map a purchase order into an Atom entry). Both of these formats do provide extensibility elements for injecting custom XML fragments, also commonly referred to as micro-formats. However, introducing micro-formats begins to counteract the benefit of using a standard representation format.
For our bookmark service, in addition to the XML format we’ve defined, we’ll also expose the public bookmarks as an Atom feed. If we’re going to support Atom feeds, we should probably also support RSS feeds since they’re very similar formats and it might open the door for more feed readers. Hence, we’ll support both by adding “feed” to the URI along with a “format” parameter to indicate “atom” or “rss”:
feed?tag={tag}&format={format}
Now let’s look at how we can represent bookmarks using Atom. Atom defines a standard format for representing feeds, which are essentially just lists of time-stamped entries that can really represent anything. For example, we can represent a list of bookmarks using this type of Atom feed:
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Public Bookmarks</title>
<updated>2008-09-13T18:30:02Z</updated>
<id>http://contoso.com/bookmarkservice/feed</id>
<entry>
<author>
<name>Aaron Skonnard</name>
</author>
<title>Aaron’s Blog</title>
<link href="http://pluralsight.com/aaron"/>
<id>http://contoso.com/bookmarkservice/users/skonnard/bookmarks/13</id>
<updated>2008-09-13T18:30:02Z</updated>
<category term="REST,WCF"/>
</entry>
<entry>...</entry>
<entry>...</entry>
</feed>
We’ve simply defined a mapping between our bookmark fields and the elements that make up an Atom <entry>. Once you have your data in Atom/RSS format, it can be easily consumed by any Atom/RSS compatible client. Figure 6 shows an Atom bookmark feed rendered in Internet Explorer and notice how we’re able to search, sort and filter this feed within the browser using the right-hand control pane.
Figure 6: An Atom feed rendered in Internet Explorer

AtomPub defines a standard way to represent a service document, which is a high-level description of the collections supported by a service. AtomPub also defines a standard API for manipulating entries using the standard Atom feed format along with GET, POST, PUT, and DELETE requests. The following shows an example AtomPub service document describing our service’s bookmarks collections:
<service xmlns="http://www.w3.org/2007/app"
xmlns:atom="http://www.w3.org/2005/Atom">
<workspace>
<atom:title>Contoso Bookmark Service</atom:title>
<collection href="http://contoso.com/bookmarkservice/" >
<atom:title>Public Bookmarks</atom:title>
</collection>
<collection href="http://contoso.com/bookmarkservice/skonnard" >
<atom:title>Aaron Skonnard's Public Bookmarks</atom:title>
</collection>
<collection>...</collection>
</workspace>
</service>
Figuring out the right representation to use for your RESTful service is primarily about figuring out what types of clients you want to accommodate and what scenarios you want to facilitate.
Providing Resource Metadata
The things you have to discover when using a RESTful service include the URI templates, the HTTP methods supported by each resource, and the representations supported by each resource. Today, most developers discover these things through either human-readable documentation or by actually interacting with the service. For example, once you know the URI templates for the service resources, you can browse to the various retrievable resources to inspect their representations, and you can use HEAD and OPTION requests to figure out what methods and headers a resource supports.
A HEAD request works just like a GET request but it returns only the response headers and no entity body, allowing the client to determine what the service is capable of returning. An OPTION request allows you to query a resource to figure out what HTTP methods it supports. The service can return the comma-separated list of supported HTTP methods to the client in the “Allow” header. The following example shows how to issue an OPTIONS request for a user’s bookmark collection:
OPTIONS http://contoso.com/bookmarkservice/skonnard/bookmarks HTTP/1.1
Assuming the client is authenticated as “skonnard”, the service will return the following response indicating that the resource supports GET and POST requests:
HTTP/1.1 200 OK
Allow: GET, POST
However, if someone other than “skonnard” issues the same OPTIONS request, the service will return the following response indicating that only GET requests are supported:
HTTP/1.1 200 OK
Allow: GET
HTTP also comes with a sophisticated built-in content negotiation mechanism. Clients can provide the “User-Agent” and the various “Accept” headers to indicate what media types (or representations) are acceptable for the response. The server can then pick a representation best suited for that particular client. When multiple acceptable representations might exist, the server can return a 300 (“Multiple Choices”) response including the URIs for each supported resource representation. The combination of HEAD, OPTIONS, and the content negotiation headers provides a foundation for runtime discovery.
If you want to make it possible for clients to discover the exact representation formats, you can provide clients with schema definitions that can be used to generate the client-side processing logic. Or you can choose to use a standard format like XHTML or Atom that removes the need for this altogether.
In addition to all of this, there are a few service description languages that can be used to fully describe RESTful services. WSDL 2.0 is one such language and the Web Application Description Language (WADL) is another, but not many toolkits provide support for either today. Although having WSDL-based code-generation would be a huge win for some consumers, not having it hasn’t been a huge show-stopper thus far. After all, there are many large-scale RESTful services around getting by just fine without it. Nevertheless, my hope is that we’ll see additional innovation in this area in the years ahead.
Avoiding RPC Tendencies
During URI design, beware of letting RPC tendencies slip into your URI templates. It’s often tempting to include a verb in the URI (e.g., /users?method=createUserAccount or even /users/create). Although this may seem obvious at first, there are several popular services on the Web today that break this rule. A service designed like this isn’t fully RESTful – it’s more of a hybrid REST/RPC service .
This type of design misuses HTTP’s uniform interface and violates the semantics for GET, which can cause big problems down the road when dealing with retries and caching. Like we learned earlier, GET is intended to be a safe operation (no side effects) but these operations do cause side effects. This can lead to problems since other components will make incorrect assumptions about these resources.
The primary reason some services do things this way is because many of today’s Web browsers and firewalls only allow GET and POST requests. Due to this limitation, many sites overload GET or POST to fake PUT and DELETE requests. This can be accomplished by specifying the real HTTP method in a custom HTTP header. A common HTTP header used for this purpose is the X-HTTP-Method-Override header, which you can use to overload POST with a DELETE operation as shown in this example:
POST /bookmarkservice/skonnard/bookmarks/123 HTTP/1.1
X-HTTP-Method-Override: DELETE
Using this technique is widely considered an acceptable practice for working around the limitations of today’s Web infrastructure because it allows you to keep your URI design free of RPCisms.
浙公网安备 33010602011771号