Skip to content

Commit a1454eb

Browse files
author
DrMimik
committed
Idempotent Requests Implementation
1 parent 2bf4351 commit a1454eb

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

parse/src/main/java/com/parse/ParseException.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ public class ParseException extends Exception {
102102
public static final int FILE_DELETE_ERROR = 153;
103103
/** Error code indicating that the application has exceeded its request limit. */
104104
public static final int REQUEST_LIMIT_EXCEEDED = 155;
105+
/** Error code indicating that the request was a duplicate and has been discarded due to idempotency rules. */
106+
public static final int DUPLICATE_REQUEST = 159;
105107
/** Error code indicating that the provided event name is invalid. */
106108
public static final int INVALID_EVENT_NAME = 160;
107109
/** Error code indicating that the username is missing or empty. */

parse/src/main/java/com/parse/ParseRESTCommand.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ class ParseRESTCommand extends ParseRequest<JSONObject> {
3535
/* package */ static final String HEADER_OS_VERSION = "X-Parse-OS-Version";
3636

3737
/* package */ static final String HEADER_INSTALLATION_ID = "X-Parse-Installation-Id";
38+
/* package */ static final String HEADER_REQUEST_ID = "X-Parse-Request-Id";
3839
/* package */ static final String USER_AGENT = "User-Agent";
39-
private static final String HEADER_SESSION_TOKEN = "X-Parse-Session-Token";
40-
private static final String HEADER_MASTER_KEY = "X-Parse-Master-Key";
40+
/* package */ static final String HEADER_SESSION_TOKEN = "X-Parse-Session-Token";
41+
/* package */ static final String HEADER_MASTER_KEY = "X-Parse-Master-Key";
4142
private static final String PARAMETER_METHOD_OVERRIDE = "_method";
4243

4344
// Set via Parse.initialize(Configuration)
@@ -215,6 +216,20 @@ protected void addAdditionalHeaders(ParseHttpRequest.Builder requestBuilder) {
215216
if (masterKey != null) {
216217
requestBuilder.addHeader(HEADER_MASTER_KEY, masterKey);
217218
}
219+
try {
220+
JSONObject jsonObject = jsonParameters != null ? new JSONObject(jsonParameters.toString()) : new JSONObject();
221+
// using header names so we don't override a parameter with the same key name,
222+
// using all headers to insure the requestId generated doesn't conflict with the rest of the users
223+
if (installationId != null)
224+
jsonObject.put(HEADER_INSTALLATION_ID, installationId);
225+
if (sessionToken != null)
226+
jsonObject.put(HEADER_SESSION_TOKEN, sessionToken);
227+
if (masterKey != null)
228+
jsonObject.put(HEADER_MASTER_KEY, masterKey);
229+
requestBuilder.addHeader(HEADER_REQUEST_ID, ParseDigestUtils.md5(toDeterministicString(jsonObject)));
230+
} catch (JSONException e) {
231+
throw new RuntimeException(e.getMessage());
232+
}
218233
}
219234

220235
@Override

parse/src/test/java/com/parse/ParseRESTUserCommandTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
import java.net.URL;
2020
import java.util.HashMap;
2121
import java.util.Map;
22+
import java.util.Random;
23+
24+
import org.json.JSONArray;
2225
import org.json.JSONObject;
2326
import org.junit.After;
2427
import org.junit.Before;
2528
import org.junit.Test;
2629
import org.skyscreamer.jsonassert.JSONCompareMode;
2730

2831
public class ParseRESTUserCommandTest {
32+
private static final String ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnm";
2933

3034
@Before
3135
public void setUp() throws MalformedURLException {
@@ -163,5 +167,44 @@ public void testOnResponseAsync() {
163167
assertEquals(200, command.getStatusCode());
164168
}
165169

170+
@Test
171+
public void testRequestIdHeader() throws Exception {
172+
JSONArray nestedJSONArray = new JSONArray().put(true).put(1).put("test");
173+
JSONObject nestedJSON =
174+
new JSONObject().put("bool", false).put("int", 2).put("string", "test");
175+
String sessionToken = generateRandomString(32);
176+
String installationId = generateRandomString(32);
177+
String masterKey = generateRandomString(32);
178+
JSONObject json =
179+
new JSONObject()
180+
.put("json", nestedJSON)
181+
.put("jsonArray", nestedJSONArray)
182+
.put("bool", true)
183+
.put("int", 3)
184+
.put("string", "test");
185+
186+
String jsonString = ParseRESTCommand.toDeterministicString(json);
187+
188+
JSONObject jsonAgain = new JSONObject(jsonString);
189+
jsonAgain.put(ParseRESTCommand.HEADER_INSTALLATION_ID, installationId);
190+
jsonAgain.put(ParseRESTCommand.HEADER_SESSION_TOKEN, sessionToken);
191+
jsonAgain.put(ParseRESTCommand.HEADER_MASTER_KEY, masterKey);
192+
ParseRESTCommand restCommand = new ParseRESTCommand.Builder().jsonParameters(json)
193+
.installationId(installationId).sessionToken(sessionToken).masterKey(masterKey)
194+
.build();
195+
196+
ParseHttpRequest.Builder builder = new ParseHttpRequest.Builder();
197+
restCommand.addAdditionalHeaders(builder);
198+
assertEquals(ParseDigestUtils.md5(ParseRESTCommand.toDeterministicString(jsonAgain)), builder.build().getHeader(ParseRESTCommand.HEADER_REQUEST_ID));
199+
}
200+
201+
private static String generateRandomString(final int sizeOfRandomString) {
202+
final Random random = new Random();
203+
final StringBuilder sb = new StringBuilder(sizeOfRandomString);
204+
for (int i = 0; i < sizeOfRandomString; ++i)
205+
sb.append(ALLOWED_CHARACTERS.charAt(random.nextInt(ALLOWED_CHARACTERS.length())));
206+
return sb.toString();
207+
}
208+
166209
// endregion
167210
}

0 commit comments

Comments
 (0)