Avoid encoding the data while url creation - java

I am trying to make get call like this:
#GET(AppConstants.BASE_URL + "{category_type}/")
Call<JsonObject> callCustomFilterApI(#Path("category_type") String type,
#QueryMap(encoded = true) Map<String,String> fields ,
#Query("page") String pageNo);
But #QueryMap can have "&" in the data, so retrofit encoding it to %26.
Is there anyway "&" do not change to "%26".
Solution I tried:
Solution mentioned here
setting encoded=true/false
And also this one.
#DebDeep Asked:
I am passing data in QueryMap as:
private void callCustomFilterSearchPagesApi(String type, ArrayList<FilterListWithHeaderTitle> customFiltersList, int pageNumber, final ApiInteractor listener) {
Map<String, String> queryMap = new HashMap<>();
for (FilterListWithHeaderTitle item: customFiltersList) {
String pairValue;
if (queryMap.containsKey(item.getHeaderTitle())){
// Add the duplicate key and new value onto the previous value
// so (key, value) will now look like (key, value&key=value2)
// which is a hack to work with Retrofit's QueryMap
String oldValue=queryMap.get(item.getHeaderTitle());
String newValue="filters[" + item.getHeaderTitle() + "][]"
+oldValue+ "&"+"filters[" + item.getHeaderTitle() + "][]"+item.getFilterItem();
pairValue=newValue;
}else {
// adding first time
pairValue= item.getFilterItem();
}
try {
//pairValue= URLEncoder.encode(pairValue, "utf-8");
// LoggerUtils.logE(TAG,pairValue);
//queryMap.put(item.getHeaderTitle(), Html.fromHtml(pairValue).toString());
queryMap.put(item.getHeaderTitle(), pairValue);
}catch (Exception u){
LoggerUtils.crashlyticsLog(TAG,u.getMessage());
}
}
Call<JsonObject> call = TagTasteApplicationInitializer.mRetroClient.callCustomFilterApI(type, queryMap, "1");
requestCall(call, listener);
}

Use Interceptor and convert %26 to &:
class RequestInterceptor implements Interceptor {
#Override
Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
String stringurl = request.url().toString();
stringurl = stringurl.replace("%26", "&");
Request newRequest = new Request.Builder()
.url(stringurl)
.build();
return chain.proceed(newRequest);
}
}
Set this to your OkHttp builder:
OkHttpClient client = new OkHttpClient.Builder();
client.addInterceptor(new RequestInterceptor());

Related

How to inject dynamic id for body of pact contract written in Java?

We have a put api that will update an assignment based on its id. As we should be cleaning up the data after a test, our assignment id would change after the original one is deleted, so we're trying to dynamically inject that into the body for the request from the provider side. However, we seem to be perhaps missing something here as it's not updating correctly and the request is still being triggered with the id set as the example.
This is the provider class:
#Slf4j
#Provider("Assignments API")
#Consumer("LTI-AGS-Tool")
//#PactBroker(url = BROKER_PACT_URL, authentication = #PactBrokerAuth(token = "${pactbroker.auth.token}"))
#VerificationReports(value = {"console", "markdown"}, reportDir = "target/pacts")
class PactProviderLTIAGSIT {
private HashMap<String, String> headers = new HashMap<>();
private String updateAssignmentId;
private final String SERVICE_TOKEN = "myToken";
#BeforeEach
void createTeacherAssignment() {
String assignmentBody = createBodyStringForStudentAssignmentSetup();
assignmentBody = assignmentBody.replace("CPWAG", "OTHER_TEXT_RESOURCE");
headers.put("Content-Type", "application/json");
headers.put("Authorization", "myToken");
RequestSpecification rq = Util.getRequestSpecification().baseUri(baseAssignmentUrl).headers(headers);
Response response = rq.body(assignmentBody).post();
assertEquals(201, response.getStatusCode());
updateAssignmentId = response.jsonPath().get("assignments[0].refId");
log.info("assignment id is " + updateAssignmentId);
}
#TestTemplate
#ExtendWith(PactVerificationInvocationContextProvider.class)
void pactTestTemplate(PactVerificationContext context, HttpRequest request) {
request.addHeader("Authorization", SERVICE_TOKEN);
logCurlFromPact(context, request);
context.verifyInteraction();
}
#BeforeEach
void before(PactVerificationContext context) {
context.setTarget(new HttpsTestTarget(BASE_PACT_TEACHER_ASSIGNMENTS_URL, 443, ""));
}
#State("Scoring info is passed between ags-tool and assignmentapi")
Map<String, Object> getScoringInfo() {
Map<String, Object> map = new HashMap<>();
map.put("assignmentId", updateAssignmentId);
return map;
}
}
And here the consumer contract:
#ExtendWith(PactConsumerTestExt.class)
class PactConsumerSendScoreIT {
private final Map<String, String> headers = new HashMap<>();
private final String path = "/v5/assignmentStatus/update";
#Pact(provider = PACT_PROVIDER, consumer = PACT_CONSUMER)
public RequestResponsePact scoreConsumerPact(PactDslWithProvider builder) {
headers.put("Content-Type", "application/json");
//Body given and returned
DslPart body = new PactDslJsonBody()
.valueFromProviderState("assignmentId", "assignmentId", "c1ef3bbf-55a2-4638-8f93-22b2916fe085")
.stringType("timestamp", DateTime.now().plusHours(3).toString())
.decimalType("scoreGiven", 75.00)
.decimalType("scoreMaximum", 100.00)
.stringType("comment", "Good work!")
.stringType("status", "IN_PROGRESS")
.stringType("userId", "c2ef3bbf-55a2-4638-8f93-22b2916fe085")
.close();
return builder
.given("Scoring info is passed between ags-tool and assignmentapi")
.uponReceiving("Scoring info is passed between ags-tool and assignmentapi")
.path(path)
.method("POST")
.body(body)
.headers(headers)
.willRespondWith()
.status(201)
.body(body)
.toPact();
}
#Test
#PactTestFor(pactMethod = "scoreConsumerPact", providerName = PACT_PROVIDER, port = "8080", pactVersion = PactSpecVersion.V3)
void runTest(MockServer mockServer) {
String updateAssignmentId = "c2ef3bbf-55a2-4638-8f93-22b2916fe085";
HashMap<String, Object> map = new HashMap<>();
map.put("timestamp", DateTime.now().plusHours(3).toString());
map.put("scoreGiven", 75.00);
map.put("scoreMaximum", 100.00);
map.put("comment", "Good work!");
map.put("status", "IN_PROGRESS");
map.put("userId", "c2ef3bbf-55a2-4638-8f93-22b2916fe085");
map.put("assignmentId", updateAssignmentId);
//Mock url
RequestSpecification rq = Util.getRequestSpecification().baseUri(mockServer.getUrl()).headers(headers);
Response response = rq.body(map)
.post(path);
assertEquals(201, response.getStatusCode());
}
}
Thank you.
We figured out that the expression needed is to include the ${} (at least if it's a string).
Once we updated it to valueFromProviderState("assignmentId", "${assignmentId}", "c1ef3bbf-55a2-4638-8f93-22b2916fe085") it seemed to work.

Is It possible to change the content of the Body of a responseEntity

I am trying to return the content of a Json file. But I want to modify before sending it to the front end. I want to add "[" and "]" at the beginning and end of the file. I am doing that because the json file has multiple json root elements.
Like for example extract the result as illustrated in
result = restTemplate.executeRequest(HttpMethod.GET, String.class);
//change Body and put it back in result
Question
Is it possible to change the body of the response and put it back in ResponseEntity?
Source Code
public ResponseEntity<String> getScalityObject(String chainCode, String dataCenter, String path, String byteRange) {
Map<String, Object> queryParams = new HashMap<>();
if (dataCenter != null && !dataCenter.isEmpty()) {
queryParams.put("dataCenter", dataCenter);
}
if (byteRange != null && !byteRange.isEmpty()) {
queryParams.put("byteRange", byteRange);
}
String decodedStr = URLDecoder.decode(path);
queryParams.put("path", decodedStr);
reservationService.setContext(
RESA_INTERNAL_SERVICE_NAME,
queryParams,
"/chains/{chainCode}/objects/file",
chainCode);
restTemplate.setServiceDefinition(reservationService);
ResponseEntity<String> result;
try {
result = restTemplate.executeRequest(HttpMethod.GET, String.class);
//Change responseBody here
return result;
} catch (IOException e) {
e.printStackTrace();
result = new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return result;
}
public <T> ResponseEntity<T> executeRequest(HttpMethod method, Class<T> responseType) throws IOException {
if (this.serviceDefinition == null) {
throw new IllegalArgumentException("You haven't provided any service definition for this call. " +
"Are you sure you called the right method before using this Amadeus Rest Template?");
}
// Resolve the URI
URI url = this.serviceDefinition.getUriComponents().toUri();
// Add the extra headers if necessary
HttpHeaders headers = new HttpHeaders();
if (this.serviceDefinition.getHeaders() != null) {
for(Map.Entry<String,String> headerSet : this.serviceDefinition.getHeaders().entrySet()) {
headers.put(headerSet.getKey(), Arrays.asList(headerSet.getValue()));
}
}
HttpEntity entity = new HttpEntity(headers);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
RequestCallback requestCallback = httpEntityCallback(entity, responseType);
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
throw ex;
}
finally {
if (response != null) {
response.close();
}
}
}
One of the way which I can think of is :
ResponseEntity<String> result = restTemplate.executeRequest(HttpMethod.GET, String.class);
StringBuilder builder = new StringBuilder(result.getBody());
... //do your transformation to stringbuilder reference.
result = ResponseEntity.status(result.getStatusCode()).body(builder.toString());
Another way if you want to avoid this is to return String response from your executeRequest & modify that response before creating ResponseEntity.
Try this:
Create your own HttpMessageConverter, implementing:
public interface HttpMessageConverter<T> {
// Indicates whether the given class can be read by this converter.
boolean canRead(Class<?> clazz, MediaType mediaType);
// Indicates whether the given class can be written by this converter.
boolean canWrite(Class<?> clazz, MediaType mediaType);
// Return the list of {#link MediaType} objects supported by this converter.
List<MediaType> getSupportedMediaTypes();
// Read an object of the given type form the given input message, and returns it.
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// Write an given object to the given output message.
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
Register the custom converter into your restTemplate object:
String url = "url";
// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();
// Add the String message converter
restTemplate.getMessageConverters().add(new YourConverter());
// Make the HTTP GET request, marshaling the response to a String
String result = restTemplate.getForObject(url, String.class);

OkHttp returns 403 as response

I'm trying to make a request to the Third-party API, but I'm running into some issues using OkHTTP.
I'm using AWS4Signer to sign the request. I'm able to generate the credentials for the same.
Request<Void> requestAws = new DefaultRequest<Void>("sts");
requestAws.setHttpMethod(HttpMethodName.POST);
requestAws.setEndpoint(URI.create("third pary api call which uses https"));
requestAws.addHeader("x-amz-security-token", sessionCredentials.getSessionToken());
requestAws.addHeader("Content-Type", "application/json");
//sign the request
AWS4Signer signer = new AWS4Signer();
signer.setServiceName(Constant.SERVICE_NAME);
signer.setRegionName(Constant.AWS_REGION);
signer.sign(requestAws, new AWSCredentials() {
#Override
public String getAWSSecretKey() {
return sessionCredentials.getAccessKeyId();
}
#Override
public String getAWSAccessKeyId() {
return sessionCredentials.getSecretAccessKey();
}
});
Map<String, String> headers = requestAws.getHeaders();
String x_date = null;
String x_token = null;
String authorization = null;
String x_content = null;
//get and assign values
for (Map.Entry<String, String> entry : headers.entrySet()) {
if (entry.getKey().equals("x-amz-security-token")) {
x_token = entry.getValue();
}
if (entry.getKey().equals("X-Amz-Date")) {
x_date = entry.getValue();
}
if (entry.getKey().equals("Authorization")) {
authorization = entry.getValue();
}
}
logger.info("Headers body response: " + JsonUtils.jsonize(headers));
String json = objectMapper.writeValueAsString(emailRequestBody);
postHandler.post(x_date, x_token, authorization, json);
Below is the request code of okHTTP
String post(String x_date, String x_token, String authorization, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url("https url is here")
.addHeader("Content-Type", "application/json")
.addHeader("X-Amz-Date", x_date)
.addHeader("x-amz-security-token", x_token)
.addHeader("Authorization", authorization)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
Below is how the request looks like with headers:
Request{method=POST, url=https://cbc.com/api/send/email, headers=[Content-Type:application/json, X-Amz-Date:20220125T111056Z, x-amz-security-token:FwoGZXIvYXdzEHUaDF6/kQ0g7Mog7W1f7CK0ATG5xhFIXP34wRjziAkJKhw9vE5cbADBOpji7uqtLp5GLGLay+e9O2deFRB4eSpUMOOThDCEQg1tum43iX4a+8Kikuc3fv5gDjbMrdLJYAK3piYVbOAET8BAXdDdkPZVG+nNu31cEWZe9HC60svIj0m95YZ9Xx5rBIDm0AVWtj4JRCmonNm1ymCNRB4GTjhEzgnxlkqEYfdUivFdlORq/IlIssUzzV04fkr0kiqDiE9GrmU51ijAtb+PBjIt9MWbM8+x4z+y+IV4JFjuK4zrVW3Iaw4xUG/C+mpcCrZrunh+8fWgVTR6In1r, Authorization:AWS4-HMAC-SHA256 Credential=medS2y7xvISbOf7ke3IWthyCMV5koeTDD5r3gkxJ/20220125/us-west-2/execute-api/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token, Signature=d862c9ed8175770244e17fd3cb216c2a92138183ad427ed67fc5f284a1a75266]}
Below is the response:
Response{protocol=h2, code=403, message=, url=https://cbc.com/api/send/email}
Why the response is returning 403? Can someone help me what I missed? Thank you for your time.

How can I pass headers using RestTemplate?

In my method I initially used RestTemplate postForObject method to post request to an endpoint. Now I have to add default OAuth token and pass it as Post request. Is there any way I can pass both request as well as Default Header as part of POST request by using postForObject?
Initiall I used below postForObject
String result = restTemplate.postForObject(url, request, String.class);
I am looking for something like below
restTemplate.exchange(url,HttpMethod.POST,getEntity(),String.class );
Here is my code
private final String url;
private final MarkBuild header;
public DataImpl(#Qualifier(OAuth) MarkBuild header,RestTemplate restTemplate) {
this.restTemplate= restTemplate;
this.header = header;
}
public void postJson(Set<String> results){
try {
Map<String, String> requestBody = new HashMap<>();
requestBody.put("news", "data");
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
String result = restTemplate.postForObject(url, request, String.class);
}
}
Below is getHttpEntity which I want to pass with Post request
private HttpEntity getHttpEntity(Set <String>results) {
return new HttpEntity<>( null, getHttpHeaders() );
}
private HttpHeaders getHttpHeaders() {
return header.build();
}
}
Is there any way I can pass both request as well as Default Header as
part of POST request by using postForObject?
Yes, there is a way to do that, I can give a basic example:
HttpHeaders lHttpHeaders = new HttpHeaders();
lHttpHeaders.setContentType( MediaType.APPLICATION_JSON );//or whatever it's in your case
String payload="<PAYLOAD HERE>"
try
{
String lResponseJson = mRestTemplate.postForObject( url, new HttpEntity<Object>( payload, lHttpHeaders ), String.class);
return lResponseJson;
}
catch( Exception lExcp )
{
logger.error( lExcp.getMessage(), lExcp );
}
Let me know if this doesn't work!!

How to resolve URI encoding problem in spring-boot?

I am using spring-boot to host a http request service.
#RequestMapping("/extract")
#SuppressWarnings("unchecked")
#ResponseBody
public ExtractionResponse extract(#RequestParam(value = "extractionInput") String input) {
// LOGGER.info("input: " + input);
JSONObject inputObject = JSON.parseObject(input);
InputInfo inputInfo = new InputInfo();
//Object object = inputObject.get(InputInfo.INPUT_INFO);
JSONObject object = (JSONObject) inputObject.get(InputInfo.INPUT_INFO);
String inputText = object.getString(InputInfo.INPUT_TEXT);
inputInfo.setInputText(inputText);
return jnService.getExtraction(inputInfo);
}
When there is a % sign, as follows, it got an errror:
http://localhost:8090/extract?extractionInput={"inputInfo":{"inputText":"5.00%"}}
The error message is below:
2018-10-09 at 19:12:53.340 [http-nio-8090-exec-1] INFO org.apache.juli.logging.DirectJDKLog [180] [log] - Character decoding failed. Parameter [extractionInput] with value [{"inputInfo":{"inputText":"5.0022:%225.00%%22}}] has been ignored. Note that the name and value quoted here may be corrupted due to the failed decoding. Use debug level logging to see the original, non-corrupted values.
Note: further occurrences of Parameter errors will be logged at DEBUG level.
2018-10-09 at 19:12:53.343 [http-nio-8090-exec-1] WARN org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver [140] [resolveException] - Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'extractionInput' is not present]
How to configure the URI encoding to fix this issue in my spring-boot configurations?
EDIT: Possible Java client code to make the request:
public String process(String question) {
QueryInfo queryInfo = getQueryInfo(question);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String jsonResult = null;
try {
String jsonStr = mapper.writeValueAsString(queryInfo);
String urlStr = Parameters.getQeWebserviceUrl() + URLEncoder.encode(jsonStr, "UTF-8");
URL url = new URL(urlStr);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
jsonResult = in.readLine();
in.close();
} catch (Exception jpe) {
jpe.printStackTrace();
}
return jsonResult
}
Without encoding from your client side - you could still achieve this if you follow any of the following strategies by encoding before the request is processed in the servlet:
use Spring preprocessor bean to preprocess the controller endpoint request
use Spring AspectJ to preprocess the controller endpoint request
use Spring servlet filter to preprocess the controller endpoint request
With any of the above cross-cutting strategies, you could encode the request URL and pass back to the endpoint.
For example below is one implmentation using Filter. You could possibly do some caching there if you need better performance.
#Component
public class SomeFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(SomeFilter.class);
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletRequest modifiedRequest = new SomeHttpServletRequest(request);
filterChain.doFilter(modifiedRequest, servletResponse);
}
#Override
public void destroy() {
}
class SomeHttpServletRequest extends HttpServletRequestWrapper {
HttpServletRequest request;
SomeHttpServletRequest(final HttpServletRequest request) {
super(request);
this.request = request;
}
#Override
public String getQueryString() {
String queryString = request.getQueryString();
LOGGER.info("Original query string: " + queryString);
try {
// You need to escape all your non encoded special characters here
String specialChar = URLEncoder.encode("%", "UTF-8");
queryString = queryString.replaceAll("\\%\\%", specialChar + "%");
String decoded = URLDecoder.decode(queryString, "UTF-8");
LOGGER.info("Modified query string: " + decoded);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return queryString;
}
#Override
public String getParameter(final String name) {
String[] params = getParameterMap().get(name);
return params.length > 0 ? params[0] : null;
}
#Override
public Map<String, String[]> getParameterMap() {
String queryString = getQueryString();
return getParamsFromQueryString(queryString);
}
#Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(getParameterMap().keySet());
}
#Override
public String[] getParameterValues(final String name) {
return getParameterMap().get(name);
}
private Map<String, String[]> getParamsFromQueryString(final String queryString) {
String decoded = "";
try {
decoded = URLDecoder.decode(queryString, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String[] params = decoded.split("&");
Map<String, List<String>> collect = Stream.of(params)
.map(x -> x.split("="))
.collect(Collectors.groupingBy(
x -> x[0],
Collectors.mapping(
x -> x.length > 1 ? x[1] : null,
Collectors.toList())));
Map<String, String[]> result = collect.entrySet().stream()
.collect(Collectors.toMap(
x -> x.getKey(),
x -> x.getValue()
.stream()
.toArray(String[]::new)));
return result;
}
}
}
You probably need to URLEncode the query parameter, e.g.
http://localhost:8090/extract?extractionInput=%7B%22inputInfo%22%3A%7B%22inputText%22%3A%225.00%25%22%7D%7D
The generally easier way to pass a parameter like this is to use an HTTP POST instead of a GET, and pass your JSON object in the body.
This is not a best practice for a REST API.
Try to normalize your URLs in object oriented way to capture path variables.
if your object likes:
param1:{
param2:{
param3: ""
}
}
use url pattern to capture attribute as:
class/param1/param2/{param3}
otherwise you will get more problems when altering front-end technologies while keeping back-end REST API same.

Categories