Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: typos

Introduction

As shown in 109. Concurrency Control on Multiple Nodes [editor] it is possible for a session to become de-synchronized when a slow Runtime node overwrites changes made by another Runtime node, and how this scenario is mitigated using keyspace notifications and interruptible sessions.

...

The real problem, as before, is that the session is de-synchronized. The user sees the session as being on Page 1, while on the back-end the session is actually on Page 2. The user needs to refresh the page in order to re-synchronize with the back-end, but there is no feedback from the back-end informing the user that he/she should refresh the page.

De-Synchronization Protection Using Request-Wards

This scenario can be detected by attaching a unique identifier to each request, a randomly generated GUID we call a request-ward. For a given session, the Runtime always knows what is the next expected request-ward and can detect a de-synchronized request, as in the following example:

...

  1. An invalid CSRF token indicates a security incident. For safety purposes the session is terminated. An invalid request ward indicates a synchronization issue. Instead of terminating the session, a helpful error message is sent back to the user.
  2. CSRF tokens change only when the page is recomposed. Request Wards change on every request which may modify the session.

Technical Details

The request wards are sent from the client to the server and from the server to the client as a header named X-Request-Ward. By default, the server expects a request ward in the following cases:

...

Code Block
@Controller
public class ExampleController {
 
  @GetMapping("/index.html")
  public String index() {
    // omitted
  }
  
  @GetMapping("/{sessionId}/view-profile")
  @ResponseBody
  public ProfileModel viewProfile(String sessionId) {
    // omitted
  }
 
  @GetMapping("/{sessionId}/view-instance/{instanceId}")
  @ResponseBody
  public InstanceModel viewInstance(@AquimaSessionId String sessionId, String instanceId) {
    // omitted
  }
 
  @PostMapping("/{sessionId}/create-instance/{entityName}")
  @ResponseBody
  public InstanceModel createInstance(String sessionId, String entityName) {
    // omitted
  }
 
  @PostMapping("/{sessionId}/update-instance/{instanceId}")
  @ResponseBody
  public InstanceModel updateInstance(@AquimaSessionId String sessionId, String instanceId, @RequestBody InstanceModel instance) {
    // omitted
  }
 
  @PostMappig@PostMapping("/{sessionId}/operation1")
  @ResponseBody
  @RequestWard(validate = false, renew = false)
  public void customOoperationcustomOperation(@AquimaSessionId String sessionId) {
    // omitted
  }
}

...

The default MVC v2 front-end implementation just displays this message to the user. Custom front-end implementations may use a combination of the response status code and type field to detect this exception and automatically reload the page. However, notifying the user is recommended, so the user has a chance to take note of the data entered on the page, which will be lost once the page is reloaded.

Multipart Requests

For multipart requests the request ward is also accepted as a request parameter named X-Request-Ward. This exception is made in order to accomodate older browsers which do not support true AJAX multipart requests / file uploads and require the use of hidden forms and iframes to simulate AJAX file uploads. The request ward is accepted as a parameter only for multipart requests. If both the header and parameter are present, the header takes precedence. All other rules from regular requests apply as well.

Configuration

Request wards can be enabled or disabled globally with the following property in application.properties:

...

Code Block
request-ward.invalid.title=Invalid request
request-ward.invalid.message=Please refresh the page
Panel
Section
Column
width50%

 Previous: 9. Concurrency Control on Multiple Nodes

Column

Next: 11. Performance Tuning