ETag (or entity tag) values and/or Last modified time of the resource are typically used for this purpose. I'm only discussing ETags here, interchanging this with Last-modified time is trivial, so skipping.
In this post I'm concentrating on deep ETags, where application developer can generate and compare ETags based on the underlying domain objects, database tables, etc.
Other kind of ETags, the shallow ones, can be supported at the framework level. They rely on hash of the representations. A web framework can generate ETag value, and compare them with the representation from the response. Shallow ETags are useful with respect to saving bandwidth but does not eliminate the computation on the server side. (Expect a post on the shallow ETags soon).
Conditional GET
Conditional GET is a great way to conserve bandwidth. An intermediary cache may check with the origin server whether the resource has changed since it last received a representation. The server responds either with the new representation if the resource state changed or send back only the headers with 304 Not Modified response.
Let's start with defining a Product class which is using Lift's Mapper (as ORM). Also, note the use of CreatedUpdated trait, this will automatically add two timestamp fields -- createdAt and updatedAt for insert and update operations respectively.
There are various strategies to generate ETags, I'm using the one that uses the updatedAt field (and use its Long value). Let's first see this in action and get back to implementation details in a bit. Using cURL to test.
Request and Response for a Product of known ID

For subsequent requests the client sends the value of ETag provided by the server. See If-None-Match header in the request below. Adding this header makes the request a conditional one. If the resource doesn't change the server sends back only the headers with 304 header (see below).

As far as implementation is concerned, relevant portion of the code is provided below:
Value of If-None-Match header from the request is compared with the resource ETag value and then either respond with 304 (resource not modified) response or 200 (ok) response. Note that the value of If-None-Match can be an array of ETag values separated by commas, which is accounted for in the code above. NotModifiedResponse used above can very well be a standard sub class of LiftResponse in the framework. Regardless, you could create one as follows, which is actually a wrapper around Lift's InMemoryResponse
Conditional PUT
Conditional PUT is a great approach to enforce that the client is updating the most recent version of the resource state. Client does a GET first and gets the ETag value and uses that in the If-Match header (see below). The usage of If-Match makes it a conditional request for updates. Server can enforce this by rejecting any updates without If-Match header in the request.
If the ETags match the resource state is updated. The server responds back with 204 (No Content) and with the new ETag value.

Suppose some other client that doesn't have the updated ETag value tries to send a request to update. The server responds with 412 (Precondition Failed) with the new ETag header value (shown below)

Implementation-wise, the code below compares the ETags and responds with either 204 or 412 indicating success or failure of conditional update (It also checks the request's content type and the existence of the resource and respond appropriately).
Just like in the case of GET, added NoContentResponse and PreConditionFailedResponse, both are wrappers around InMemoryResponse.
Complete source of the service is here, just in case.
Follow on Twitter