mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Merge branch 'dev' into feature/CTLE-434-oms-update-business-logic
# Conflicts: # qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryBackendModuleTest.java # qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java
This commit is contained in:
@ -64,6 +64,7 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.module.api.exceptions.OAuthCredentialsException;
|
||||
import com.kingsrook.qqq.backend.module.api.exceptions.OAuthExpiredTokenException;
|
||||
import com.kingsrook.qqq.backend.module.api.exceptions.RateLimitException;
|
||||
import com.kingsrook.qqq.backend.module.api.exceptions.RetryableServerErrorException;
|
||||
import com.kingsrook.qqq.backend.module.api.model.AuthorizationType;
|
||||
import com.kingsrook.qqq.backend.module.api.model.OutboundAPILog;
|
||||
import com.kingsrook.qqq.backend.module.api.model.metadata.APIBackendMetaData;
|
||||
@ -525,7 +526,7 @@ public class BaseAPIActionUtil
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if(statusCode == HttpStatus.SC_BAD_GATEWAY)
|
||||
else if(statusCode == HttpStatus.SC_BAD_GATEWAY || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT)
|
||||
{
|
||||
LOG.info("HTTP " + request.getMethod() + " failed", logPair("table", table.getName()), logPair("statusCode", statusCode), logPair("responseContent", StringUtils.safeTruncate(resultString, 1024, "...")));
|
||||
didLog = true;
|
||||
@ -890,8 +891,10 @@ public class BaseAPIActionUtil
|
||||
*******************************************************************************/
|
||||
public QHttpResponse makeRequest(QTableMetaData table, HttpRequestBase request) throws QException
|
||||
{
|
||||
int sleepMillis = getInitialRateLimitBackoffMillis();
|
||||
int rateLimitSleepMillis = getInitialRateLimitBackoffMillis();
|
||||
int serverErrorsSleepMillis = getInitialServerErrorBackoffMillis();
|
||||
int rateLimitsCaught = 0;
|
||||
int serverErrorsCaught = 0;
|
||||
boolean caughtAnOAuthExpiredToken = false;
|
||||
|
||||
while(true)
|
||||
@ -925,7 +928,11 @@ public class BaseAPIActionUtil
|
||||
{
|
||||
throw (new RateLimitException(qResponse.getContent()));
|
||||
}
|
||||
if(statusCode >= 400)
|
||||
else if(shouldBeRetryableServerErrorException(qResponse))
|
||||
{
|
||||
throw (new RetryableServerErrorException(statusCode, qResponse.getContent()));
|
||||
}
|
||||
else if(statusCode >= 400)
|
||||
{
|
||||
handleResponseError(table, request, qResponse);
|
||||
}
|
||||
@ -962,9 +969,22 @@ public class BaseAPIActionUtil
|
||||
throw (new QException(rle));
|
||||
}
|
||||
|
||||
LOG.info("Caught RateLimitException", logPair("rateLimitsCaught", rateLimitsCaught), logPair("uri", request.getURI()), logPair("table", table.getName()), logPair("sleeping", sleepMillis));
|
||||
SleepUtils.sleep(sleepMillis, TimeUnit.MILLISECONDS);
|
||||
sleepMillis *= 2;
|
||||
LOG.info("Caught RateLimitException", logPair("rateLimitsCaught", rateLimitsCaught), logPair("uri", request.getURI()), logPair("table", table.getName()), logPair("sleeping", rateLimitSleepMillis));
|
||||
SleepUtils.sleep(rateLimitSleepMillis, TimeUnit.MILLISECONDS);
|
||||
rateLimitSleepMillis *= 2;
|
||||
}
|
||||
catch(RetryableServerErrorException see)
|
||||
{
|
||||
serverErrorsCaught++;
|
||||
if(serverErrorsCaught > getMaxAllowedServerErrors())
|
||||
{
|
||||
LOG.error("Giving up " + request.getMethod() + " to [" + table.getName() + "] after too many server-side errors (" + getMaxAllowedServerErrors() + ")");
|
||||
throw (new QException(see));
|
||||
}
|
||||
|
||||
LOG.info("Caught Server-side error during API request", logPair("serverErrorsCaught", serverErrorsCaught), logPair("uri", request.getURI()), logPair("code", see.getCode()), logPair("table", table.getName()), logPair("sleeping", serverErrorsSleepMillis));
|
||||
SleepUtils.sleep(serverErrorsSleepMillis, TimeUnit.MILLISECONDS);
|
||||
serverErrorsSleepMillis *= 2;
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
@ -984,6 +1004,16 @@ public class BaseAPIActionUtil
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected boolean shouldBeRetryableServerErrorException(QHttpResponse qResponse)
|
||||
{
|
||||
return (qResponse.getStatusCode() != null && qResponse.getStatusCode() >= 500);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** one-line method, factored out so mock/tests can override
|
||||
*******************************************************************************/
|
||||
@ -1153,6 +1183,16 @@ public class BaseAPIActionUtil
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected int getInitialServerErrorBackoffMillis()
|
||||
{
|
||||
return (500);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -1163,6 +1203,16 @@ public class BaseAPIActionUtil
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected int getMaxAllowedServerErrors()
|
||||
{
|
||||
return (3);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.api.exceptions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class RetryableServerErrorException extends QException
|
||||
{
|
||||
private Integer code;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RetryableServerErrorException(Integer code, String message)
|
||||
{
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for code
|
||||
*******************************************************************************/
|
||||
public Integer getCode()
|
||||
{
|
||||
return (this.code);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for code
|
||||
*******************************************************************************/
|
||||
public void setCode(Integer code)
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for code
|
||||
*******************************************************************************/
|
||||
public RetryableServerErrorException withCode(Integer code)
|
||||
{
|
||||
this.code = code;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -124,9 +124,16 @@ class BaseAPIActionUtilTest extends BaseTest
|
||||
// avoid the fully mocked makeRequest //
|
||||
////////////////////////////////////////
|
||||
mockApiUtilsHelper.setUseMock(false);
|
||||
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||
{"error": "Server error"}
|
||||
"""));
|
||||
|
||||
//////////////////////////
|
||||
// set to retry 3 times //
|
||||
//////////////////////////
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||
{"error": "Server error"}
|
||||
"""));
|
||||
}
|
||||
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||
@ -290,9 +297,16 @@ class BaseAPIActionUtilTest extends BaseTest
|
||||
// avoid the fully mocked makeRequest //
|
||||
////////////////////////////////////////
|
||||
mockApiUtilsHelper.setUseMock(false);
|
||||
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||
{"error": "Server error"}
|
||||
"""));
|
||||
|
||||
//////////////////////////
|
||||
// set to retry 3 times //
|
||||
//////////////////////////
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||
{"error": "Server error"}
|
||||
"""));
|
||||
}
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||
@ -344,9 +358,16 @@ class BaseAPIActionUtilTest extends BaseTest
|
||||
// avoid the fully mocked makeRequest //
|
||||
////////////////////////////////////////
|
||||
mockApiUtilsHelper.setUseMock(false);
|
||||
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||
{"error": "Server error"}
|
||||
"""));
|
||||
|
||||
//////////////////////////
|
||||
// set to retry 3 times //
|
||||
//////////////////////////
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||
{"error": "Server error"}
|
||||
"""));
|
||||
}
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setRecords(List.of(new QRecord().withValue("name", "Milhouse")));
|
||||
@ -411,9 +432,13 @@ class BaseAPIActionUtilTest extends BaseTest
|
||||
// avoid the fully mocked makeRequest //
|
||||
////////////////////////////////////////
|
||||
mockApiUtilsHelper.setUseMock(false);
|
||||
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||
{"error": "Server error"}
|
||||
"""));
|
||||
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||
{"error": "Server error"}
|
||||
"""));
|
||||
}
|
||||
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("name", "Milhouse")));
|
||||
@ -682,4 +707,4 @@ class BaseAPIActionUtilTest extends BaseTest
|
||||
return (new GetAction().execute(getInput));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user