Skip to content

Commit

Permalink
Idempotent Requests Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
DrMimik committed Mar 16, 2023
1 parent 2bf4351 commit a1454eb
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 2 deletions.
2 changes: 2 additions & 0 deletions parse/src/main/java/com/parse/ParseException.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ public class ParseException extends Exception {
public static final int FILE_DELETE_ERROR = 153;
/** Error code indicating that the application has exceeded its request limit. */
public static final int REQUEST_LIMIT_EXCEEDED = 155;
/** Error code indicating that the request was a duplicate and has been discarded due to idempotency rules. */
public static final int DUPLICATE_REQUEST = 159;
/** Error code indicating that the provided event name is invalid. */
public static final int INVALID_EVENT_NAME = 160;
/** Error code indicating that the username is missing or empty. */
Expand Down
19 changes: 17 additions & 2 deletions parse/src/main/java/com/parse/ParseRESTCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ class ParseRESTCommand extends ParseRequest<JSONObject> {
/* package */ static final String HEADER_OS_VERSION = "X-Parse-OS-Version";

/* package */ static final String HEADER_INSTALLATION_ID = "X-Parse-Installation-Id";
/* package */ static final String HEADER_REQUEST_ID = "X-Parse-Request-Id";
/* package */ static final String USER_AGENT = "User-Agent";
private static final String HEADER_SESSION_TOKEN = "X-Parse-Session-Token";
private static final String HEADER_MASTER_KEY = "X-Parse-Master-Key";
/* package */ static final String HEADER_SESSION_TOKEN = "X-Parse-Session-Token";
/* package */ static final String HEADER_MASTER_KEY = "X-Parse-Master-Key";
private static final String PARAMETER_METHOD_OVERRIDE = "_method";

// Set via Parse.initialize(Configuration)
Expand Down Expand Up @@ -215,6 +216,20 @@ protected void addAdditionalHeaders(ParseHttpRequest.Builder requestBuilder) {
if (masterKey != null) {
requestBuilder.addHeader(HEADER_MASTER_KEY, masterKey);
}
try {
JSONObject jsonObject = jsonParameters != null ? new JSONObject(jsonParameters.toString()) : new JSONObject();
// using header names so we don't override a parameter with the same key name,
// using all headers to insure the requestId generated doesn't conflict with the rest of the users
if (installationId != null)
jsonObject.put(HEADER_INSTALLATION_ID, installationId);
if (sessionToken != null)
jsonObject.put(HEADER_SESSION_TOKEN, sessionToken);
if (masterKey != null)
jsonObject.put(HEADER_MASTER_KEY, masterKey);
requestBuilder.addHeader(HEADER_REQUEST_ID, ParseDigestUtils.md5(toDeterministicString(jsonObject)));
} catch (JSONException e) {
throw new RuntimeException(e.getMessage());
}
}

@Override
Expand Down
43 changes: 43 additions & 0 deletions parse/src/test/java/com/parse/ParseRESTUserCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.skyscreamer.jsonassert.JSONCompareMode;

public class ParseRESTUserCommandTest {
private static final String ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnm";

@Before
public void setUp() throws MalformedURLException {
Expand Down Expand Up @@ -163,5 +167,44 @@ public void testOnResponseAsync() {
assertEquals(200, command.getStatusCode());
}

@Test
public void testRequestIdHeader() throws Exception {
JSONArray nestedJSONArray = new JSONArray().put(true).put(1).put("test");
JSONObject nestedJSON =
new JSONObject().put("bool", false).put("int", 2).put("string", "test");
String sessionToken = generateRandomString(32);
String installationId = generateRandomString(32);
String masterKey = generateRandomString(32);
JSONObject json =
new JSONObject()
.put("json", nestedJSON)
.put("jsonArray", nestedJSONArray)
.put("bool", true)
.put("int", 3)
.put("string", "test");

String jsonString = ParseRESTCommand.toDeterministicString(json);

JSONObject jsonAgain = new JSONObject(jsonString);
jsonAgain.put(ParseRESTCommand.HEADER_INSTALLATION_ID, installationId);
jsonAgain.put(ParseRESTCommand.HEADER_SESSION_TOKEN, sessionToken);
jsonAgain.put(ParseRESTCommand.HEADER_MASTER_KEY, masterKey);
ParseRESTCommand restCommand = new ParseRESTCommand.Builder().jsonParameters(json)
.installationId(installationId).sessionToken(sessionToken).masterKey(masterKey)
.build();

ParseHttpRequest.Builder builder = new ParseHttpRequest.Builder();
restCommand.addAdditionalHeaders(builder);
assertEquals(ParseDigestUtils.md5(ParseRESTCommand.toDeterministicString(jsonAgain)), builder.build().getHeader(ParseRESTCommand.HEADER_REQUEST_ID));
}

private static String generateRandomString(final int sizeOfRandomString) {
final Random random = new Random();
final StringBuilder sb = new StringBuilder(sizeOfRandomString);
for (int i = 0; i < sizeOfRandomString; ++i)
sb.append(ALLOWED_CHARACTERS.charAt(random.nextInt(ALLOWED_CHARACTERS.length())));
return sb.toString();
}

// endregion
}

0 comments on commit a1454eb

Please sign in to comment.