Obtaining list of redirect urls with Spring RestTemplate for HEAD/GET request - spring

As far as I'm aware I initialize the RestTemplate in the usual way:
val factory = HttpComponentsClientHttpRequestFactory()
factory.httpClient = HttpClientBuilder
.create()
.setRedirectStrategy(LaxRedirectStrategy()) // follow redirects
.build()
val restTemplate = RestTemplate(factory)
val url = "some url..."
val response = restTemplate.headForHeaders(url)
The automatic redirection works correctly but I want to obtain a list of all redirects that occurred along the way.
I know that all redirects are being recorded inside the HttpContext object for each call but as far as I can tell there is no way to obtain the context itself from the RestTemplate.
What's the best and correct way to to obtain all redirect urls from Springs RestTemplate?

Related

Best Way to encode Fragment in URL - SpringBoot

I have a spring boot application where an endpoint responds with a url for the client to redirect to. This correct url looks something like:
https://checkout.stripe.com/c/pay/stuff#morestuff which is being properly logged below
Here is my spring boot code:
#PostMapping(path = "/create-checkout-session", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> createSubscription(#RequestParam String priceId) throws StripeException {
...
Session session = Session.create(params);
log.info("Redirecting with session {}", session.getUrl());
return ResponseEntity.status(HttpStatus.FOUND).location(URI.create(session.getUrl())).build();
}
However, when my client receives a response from my endpoint, the URL is truncated up to the # to something like:https://checkout.stripe.com/c/pay/stuff (removing the #morestuff).
I found this post about needing to encode the # and I was wondering what the best way to do this is?

REST API call from spring boot not working

I am trying to fetch live data from NSE options trading. Below code is not working and the request made is stuck without any response.
Any workaround on this?
public void getLiveBankNiftyData() {
String RESOURCE_PATH = "https://www.nseindia.com/api/option-chain-indices?symbol=BANKNIFTY";
ResponseEntity<Object[]> responseEntity = restTemplate.getForEntity(RESOURCE_PATH, Object[].class);
Object[] objects = responseEntity.getBody();
}
i tried this
// request url
String url = "https://www.nseindia.com/api/option-chain-indices?symbol=BANKNIFTY";
// create an instance of RestTemplate
RestTemplate restTemplate = new RestTemplate();
// make an HTTP GET request
String json = restTemplate.getForObject(url, String.class);
// print json
System.out.println(json);
I found a way out. Instead of using RestTemplate I used WebClient and this solved the issue.

How do I unit test a Spring RestTemplate that takes a ResponseExtractor and RequestCallback?

I am developing in Groovy and I am trying to write a Spock unit test for the following use of Spring's RestTemplate...
Included are my request callback and response extractors, and my initialization class of the RestTemplate bean. I am using the ResponseExtractor to stream the response from GET myurl/ and copy it to a file. The RequestCallback is simply setting some headers on the request.
class RestTemplateConfig() {
#Bean(name = 'myRestTemplate')
RestTemplate getMyRestTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder().build()
return restTemplate
}
}
class MyClass() {
#Autowired
#Qualifier('myRestTemplate')
RestTemplate restTemplate
File getFile() {
ResponseExtractor<Void> responseExtractor = { ClientHttpResponse response ->
// do something with the response
// in this case, the response is an input stream so we copy the input stream to a file
myFile = response.getBody() // roughly, in a psuedocode-ish way
return null
}
RequestCallback requestCallback = { ClientHttpRequest request ->
request.getHeaders().setAccept([MediaType.APPLICATION_JSON])
}
File myFile
// get my file data
restTemplate.execute('myurl/', HttpMethod.GET, requestCallback, responseExtractor)
return myFile
}
}
Spring framework docs for that particular execute(...) method: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html#execute-java.net.URI-org.springframework.http.HttpMethod-org.springframework.web.client.RequestCallback-org.springframework.web.client.ResponseExtractor-
How do I mock out what's happening in these closures? Specifically, I'm interested in mocking out my response extractor because my current test always returns myFile as null.
when:
// do stuff
then:
1 * restTemplate.execute('myurl/, HttpMethod.GET, _, _) // how can I mock out the expected response here?
0 * _
myFile != null // this fails because myFile is null
After you updated your sample code as I requested, I can see more clearly now. You are suffering from a typical (non-)testability problem: Your method getFile does more than just getting a file. It instantiates two dependencies as local variables, making them unmockable and consequently the whole method mostly untestable.
So you want to refactor for better testability so as to be able to use one testing method I mentioned in my first comment:
If the requestCallback and responseExtractor can be injected via constructor or setter, you can inject mocks.
If they are created by some kind of factory class, you can stub that class.
In case of a factory method inside the class under test itself you can use a spy on the class and stub the factory method.
For a more general discussion of testability and how tests drive application design, see my other answer here, sections "General comments" and "Update".
If any of this is unclear, feel free to ask related(!) follow-up questions.

How to test a POSTing method by using an embedded Webserver in Springboot?

I am searching for a way to test a method, which sends a POST request to an external service. The application will NOT be itself a consumable webservice, that is why I didn't implement the shown class below as #RestController, #Controller, #Service, whatever types there may be.
But I don't know how to call the method postNumberPlate() to send a request to an embedded webserver (started in/by/at the unit test) to make some assertions on it. I want to avoid, to install an external webserver.
In other words: Can I start an embedded webserver inside a unit-test and 'tell' it to accept my POST request to inspect and assert the contents?
I already did:
a massive Webresearch (2-3 days?)
read Howto's
check the springboot docs
use an embedded Jetty Server (somehow blocking loop)
declare the Application as Webapplication and setting random port to jetty
experiment with Mockito, MockMVC
read "How to unittest a class using RestTemplate offline?" and compared it to my case, but found,
that it's very old (8y),
I don't know how to implement the parent interface, which is pretty huge
that the question and answers are too generic to deduce a solution for my case
it's not answering the embedded testing webserver problem I included.
The Class to be tested:
public class RestfulClient {
private RestTemplate restTemplate = new RestTemplate();
private HttpHeaders headers = new HttpHeaders();
#Value("${kafkaeskadapter.uri}")
private String destinationURL;
public RestfulClient() {}
public ResponseEntity<String> postNumberPlate(String key, CamImage camImage) {
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("numplate", camImage.getIdentifier());
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<LinkedMultiValueMap<String,
Object>>(map, headers);
ByteArrayResource resource = new ByteArrayResource(camImage.getData()) {
/**
* IMPORTANT!!! Otherwise I receive a BAD REQUEST
* #return
*/
#Override
public String getFilename() {
return camImage.getIdentifier() + ".png";
}
};
map.add("image", resource);
ResponseEntity<String> result = restTemplate.exchange(destinationURL, HttpMethod.POST,
requestEntity, String.class);
return result;
}
}
I hope I could clarify my question a bit.
A solution is to write a simple light-weight Webservice Endpoint and include it into your Run Configuration of your IDE. I made a separate mini project and would add further methods if needed, e.g. to accept different media.
Prior to run the actual unit tests, it is possible to configure the start of the Endpoint and return a meaningful ResponseEntity. The result can be inspected et voilĂ , assertions are possible.
A word about StackOverflow user arrogance: #Raedwald, after reading and trying, the answers in the linked question are not really helpful, but involve a lot of knowlegde about the stuff, and I have no one around of my colleagues, which could ever assist at programming. So it wasn't helpful to flag my question for deletion.

Redirect after a POST vs redirect after a GET

I'm working on a Spring project. Here's my basic controller:
#Controller
public class Editor {
private static final String EDITOR_URL = "/editor";
#RequestMapping(value = EDITOR_URL, method = {POST, GET})
public ModelAndView edit(HttpServletResponse response,
HttpServletRequest request,
RedirectAttributes redirectAttributes,
#RequestParam Map<String, String> allRequestParams) {
// The code is trimmed to keep it short
// It doesn't really matter where it gets the URL, it works fine
String redirectURL = getRedirectUrl();
// redirectURL is going to be /editor/pad.html
return new ModelAndView("redirect:" + redirectUrl);
}
From web.xml:
<servlet-mapping>
<servlet-name>edm</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
I have jetty embedded and I'm trying an integration test:
#Test
public void redirectToEditPadSuccess() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(END_POINT + "/edm/editor")
.queryParam("param1", "val1")
.queryParam("param2", "val2");
HttpEntity<?> entity = new HttpEntity<>(requestHeaders);
HttpEntity<String> response = restTemplate.exchange(
builder.build().encode().toUri(),
HttpMethod.POST,
entity,
String.class);
HttpHeaders httpResponseHeaders = response.getHeaders();
List<String> httpReponseLocationHeader = httpResponseHeaders.get("Location");
assertTrue(httpReponseLocationHeader.size() == 1);
String redirectLocation = httpReponseLocationHeader.get(0);
URL redirectURL = new URL(redirectLocation);
assertEquals("/edm/editor/pad.html", redirectURL.getPath());
}
So when I execute the above it works fine and I get a green OK sign.
Now, the controller accepts both POST and GET methods. If I execute the test using GET method (replacing HttpMethod.POST with HttpMethod.GET), the result is going to be a 404.
The logs reveal:
WARN org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/edm/editor/pad.html] in DispatcherServlet with name 'edm'
I tried to debug the application up to the DispatcherServlet and weird thing is that with GET, after the 302/redirect response the Dispatcher is being called again and turns this to a 200 - no idea how and why.
I'm going to try and explain what is going on, and then provide a solution.
First let's forget that you're running a rest case, and assume that the request is coming from a browser.
Scenario 1 : Browser issues a GET request, and the server responds with a redirect.
In this case, the browser reads the response status code as 302 and makes another request using the Location response header. The user sees a quick reload but doesn't notice anything wrong.
Scenario 2 : Browser issues a POST request, and the server responds with a redirect.
In this case, the browser does follow the response code and does issue a redirect, but, the second request is a GET request, and the original request body is lost in the second request. This is because strictly by HTTP standards, the browser cannot "re-post" data to the server, without an explicit request by the user. (Some browsers will prompt the user and ask them if they want to re-post)
Now in your code, RestTemplate is using what I presume to be a default HttpClientFactory, most likely this one: https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java.
This is how RestTemplate is handling the above two scenarios:
Scenario 1 : Rest Template issues a GET request, and the server responds with a redirect.
Here the Rest Template instance will work exactly as a browser would. That's the reason why two requests are being made, and the second one is looking for /edm/editor/pad.html
Scenario 2 : Rest Template issues a POST request, and the server responds with a redirect.
In this case, Rest Template will stop after the first call, because it cannot automatically override your request method and change it to GET, and it cannot prompt you for permission, like a browser would.
Solution: When creating an instance of RestTemplate, pass it an overridden version of the client factory, something like
new RestTemplate(new SimpleClientHttpRequestFactory() {
protected void prepareConnection(HttpURLConnection conn, String httpMethod) throws IOException {
super.prepareConnection(conn, httpMethod);
conn.setInstanceFollowRedirects(false);
}
});
This will instruct rest template to stop after the first request.
Sorry for the lengthy answer, but I hope this clarifies things.

Resources