Support faster cross-domain XHR calls

CORS preflight for POST, PUT, DELETE makes every mutation operation
require 2 round trips with the server.  This can increase latency for
any application running on a different origin.

There is a workaround available in modern browsers: use POST with
Content-Type: text/plain.  This does not require CORS preflight, as
servers should already be using XSRF protection strategies.

Unfortunately this is incompatible with the current REST API, as many
operations require PUT or DELETE methods, and a Content-Type of
application/json.  Support the requester to select a different method
using query parameter '$m' and Content-Type with '$ct' in the URL,
mocking the request with those.

Using this style of request still requires the user session to be
valid for access.  Accept identity through the query parameters as
'access_token'.

The XSRF token isn't necessary in this type of request as only
permitted websites would be allowed to read cookie content to obtain
the GerritAccount cookie value and include it in the URL.

Change-Id: Ic7bc5ad2e57eef27b0d2e13523be78e8a2d0a65c
This commit is contained in:
Shawn Pearce
2017-06-10 11:57:30 -07:00
parent 53ebad44d4
commit d79dcef4a6
10 changed files with 456 additions and 79 deletions

View File

@@ -19,11 +19,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.gerrit.extensions.restapi.Url;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@@ -60,6 +60,7 @@ public class FakeHttpServletRequest implements HttpServletRequest {
private final ListMultimap<String, String> headers;
private ListMultimap<String, String> parameters;
private String queryString;
private String hostName;
private int port;
private String contextPath;
@@ -158,6 +159,7 @@ public class FakeHttpServletRequest implements HttpServletRequest {
}
public void setQueryString(String qs) {
this.queryString = qs;
ListMultimap<String, String> params = LinkedListMultimap.create();
for (String entry : Splitter.on('&').split(qs)) {
List<String> kv = Splitter.on('=').limit(2).splitToList(entry);
@@ -306,7 +308,7 @@ public class FakeHttpServletRequest implements HttpServletRequest {
@Override
public String getQueryString() {
return paramsToString(parameters);
return queryString;
}
@Override
@@ -317,8 +319,8 @@ public class FakeHttpServletRequest implements HttpServletRequest {
@Override
public String getRequestURI() {
String uri = contextPath + servletPath + path;
if (!parameters.isEmpty()) {
uri += "?" + paramsToString(parameters);
if (!Strings.isNullOrEmpty(queryString)) {
uri += '?' + queryString;
}
return uri;
}
@@ -379,23 +381,6 @@ public class FakeHttpServletRequest implements HttpServletRequest {
throw new UnsupportedOperationException();
}
private static String paramsToString(ListMultimap<String, String> params) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> e : params.entries()) {
if (!first) {
sb.append('&');
} else {
first = false;
}
sb.append(Url.encode(e.getKey()));
if (!"".equals(e.getValue())) {
sb.append('=').append(Url.encode(e.getValue()));
}
}
return sb.toString();
}
@Override
public AsyncContext getAsyncContext() {
throw new UnsupportedOperationException();