I am trying to call an API using POST method. There is no body in it and all parameters in the URL only.
This is how I am building request to call via okHttpClient version :
Request requestToken = new Request.Builder()
.url(TOKEN_GEN_API_URL + ConfigConstants.TOKEN_CLIENT_ID_PARAM
+ TOKEN_CLIENT_ID_PARAM_VALUE + ConfigConstants.TOKEN_CLINET_SECRET_PARAM
+ TOKEN_CLIENT_SECRET_PARAM_VALUE + ConfigConstants.TOKEN_GRANT_TYPE_PARAM
+ TOKEN_GRANT_TYPE_PARAM_VALUE)
.post(requestBody)
.addHeader("Content-Type", "application/json")
.addHeader("cache-control", "no-cache")
.build();
ConfigConstants Class :
public class ConfigConstants {
public static final String TOKEN_CLIENT_ID_PARAM = "?client_id=";
public static final String TOKEN_CLINET_SECRET_PARAM = "&client_secret=";
public static final String TOKEN_GRANT_TYPE_PARAM = "&grant_type=";
}
This would create URL something like this :
<endpoint>?client_id=...&client_secret=..&grant_type=..
Now, I am trying call this via RestTemplate, I think it would less complicated in that. But, I am not sure how to build this request without any body.
#RestController
public class TokenRestAPI {
#Autowired
RestTemplate restTemplate;
private static final Logger logger = LogManager.getLogger(TokenRestAPI.class);
#Value("${TOKEN_CLIENT_SEC_PARAM_VALUE}")
private String TOKEN_CLIENT_SECRET_PARAM_VALUE;
#Value("${TOKEN_CLIENT_ID_PARAM_VALUE}")
private String TOKEN_CLIENT_ID_PARAM_VALUE;
#Value("${TOKEN_GRANT_TYPE_PARAM_VALUE}")
private String TOKEN_GRANT_TYPE_PARAM_VALUE;
#Value("${TOKEN_GEN_API_URL}")
private String TOKEN_GEN_API_URL;
#PostMapping("/")
public void getAuthToken() {
ResponseEntity<TokenPOJO> response = restTemplate.postForEntity(TOKEN_GEN_API_URL, request, TokenPOJO.class);
}
}
If you don't want to send a body, just pass null as the request object:
ResponseEntity<TokenPOJO> response = restTemplate.postForEntity(TOKEN_GEN_API_URL, null, TokenPOJO.class);
you will still have to either build up the URL or use the parameters. The last you can do by putting variables in your url string like:
String url = "http://example.com?foo={bar}"
and pass a map with the variable name as key and the parameter value as the value.
Related
I am trying to call SOAP API in Java Spring Boot using WebServiceGatewaySupport by Spring WebServiceTemplate
Config java class
public WebServiceTemplate createWebServiceTemplate(Jaxb2Marshaller marshaller, ClientInterceptor clientInterceptor) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
//SOAP URL
webServiceTemplate.setDefaultUri("http://host/Services.asmx");
//Auth ---It seems issue is here only????? need to check
webServiceTemplate.setMessageSender(new Authentication());
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
webServiceTemplate.afterPropertiesSet();
webServiceTemplate.setCheckConnectionForFault(true);
webServiceTemplate.setInterceptors((ClientInterceptor[]) Arrays.asList(createLoggingInterceptor()).toArray());
return webServiceTemplate;
}
SOAP Client Call
public class TicketClient extends WebServiceGatewaySupport {
public String getTicket(Ticket req) {
System.out.println("test inside webservice support1");
response = (AcquireTicketResponse) getWebServiceTemplate().marshalSendAndReceive(req);
Authentication Class
public class Authentication extends HttpUrlConnectionMessageSender {
#Override protected void prepareConnection(HttpURLConnection connection) {
String userpassword = username+":"+password+":"+domain;
String encoded =
Base64.getEncoder().withoutPadding().encodeToString(userpassword.getBytes(StandardCharsets.UTF_8));
connection.setRequestProperty("Authorization", "Basic "+encoded); connection.setRequestProperty("Content-Type", "application/xml"); super.prepareConnection(connection);
}
Not using Authetication class and add the above into
ClientInterceptor
public class SoapLoggingInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String username="test";
String password="test";
String domain = "#test";
String userpassword = username+":"+password+domain;
String encoded = Base64.getEncoder().withoutPadding().encodeToString(userpassword.getBytes(StandardCharsets.UTF_8));
messageContext.setProperty("Authorization", "Basic "+encoded);
messageContext.setProperty("Content-type", "XML");
Case -1 --->When I passed (user, pwd, domain and content-type) through messagesender, content type is taking but throwed "BAD REQUEST ERROR 400"....When i comment contenttype property, then it throwed "INTERNAL SERVER ERROR 500".
Case-2...when I passed (user, pwd, domain and content-type) through ClientInterceptor , always it throwed "INTERNAL SERVER ERROR 500"......It seems Authentication properties for the service are not going to API call.............................Please suggest some options
Both the cases, Authentication is not passing to service, if i comment,Authentication code (userid/pwd/domain) in both cases also...no efforts in output
After setting the user ID/pwd
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String username="test";
String password="test";
String domain = "#test";
String userpassword = username+":"+password+domain;
byte[] userpassword = (username+":"+password).getBytes(StandardCharsets.UTF_8);
String encoded = Base64.getEncoder().encodeToString(userpassword);
ByteArrayTransportOutputStream os = new
ByteArrayTransportOutputStream();
try {
TransportContext context = TransportContextHolder.getTransportContext();
WebServiceConnection conn = context.getConnection();
((HeadersAwareSenderWebServiceConnection) conn).addRequestHeader("Authorization", "Basic " + encoded);
} catch (IOException e) {
throw new WebServiceIOException(e.getMessage(), e);
}
First of all don't set the content type Spring WebServices will do that for you, messing around with that will only make things worse.
You should get the WebServiceConnection and cast that to a HeadersAwareSenderWebServiceConnection to add a header.
public class BasicAuthenticationInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String username="test#test";
String password="test";
byte[] userpassword = (username+":"+password).getBytes(UTF_8);
String encoded = Base64.getEncoder().encodeToString(userpassword);
WebServiceConnection conn = TransportContext.getConnection();
((HeadersAwareSenderWebServiceConnection) conn).addHeader("Authorization", "Basic " + encoded);
}
}
You also need to configure it. Assuming it is a bean don't call afterPropertiesSet (and ofcourse you are now using the ClientInterceptor remove the new Authentication() for your customized message sender.
The List<ClientInterceptor> will automatically create a list with all the interceptors so you can easily inject them.
#Bean
public WebServiceTemplate createWebServiceTemplate(Jaxb2Marshaller marshaller, List<ClientInterceptor> clientInterceptors) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate(marshaller);
//SOAP URL
webServiceTemplate.setDefaultUri("http://host/Services.asmx");
webServiceTemplate.setCheckConnectionForFault(true);
webServiceTemplate.setInterceptors(clientInterceptors);
return webServiceTemplate;
}
If this doesn't work there is something else you are doing wrong and you will need to get in touch with the server developers and get more information on the error.
Update:
Apparently you also need to provide a SOAP Action in your request, which you currently don't. For this you can specify the SoapActionCallback in the marshalSendAndReceive method. Which action to specify you can find in the WSDL you are using.
SoapActionCallback soapAction = new SoapActionCallback("SoapActionToUse");
response = (AcquireTicketResponse) getWebServiceTemplate().marshalSendAndReceive(req, soapAction);
I have an endpoint in RestController. When request would be processed , there must performed redirecting to another URL and There need to pass one parameters in redirect URL.
#RestController
#RequiredArgsConstructor
#Slf4j
public class RestControllerImpl {
#Value("${uri-root}")
private String uriRoot;
private final Service service;
#PostMapping("/api")
public RedirectView performRequest(#RequestBody Transaction dto) {
service.perform(dto);
String referenceToken = "sldflh#2lhf*shdjkla"
String urlRedirect = uriRoot + "?token=" + referenceToken;
return new RedirectView(urlRedirect);
}
}
The code above doesn't work for me.
I was looking for information on stackoverflow, but it suggests either using ModelAndView or RedirectAttributes attributes. But I need endpoint to accept a Post request that would bring data that will be processed in the service layer.
It is not possible to complete this task. Could someone explain how this can work and how such a task can be accomplished ?
It worked.
#RestController
#RequiredArgsConstructor
#Slf4j
public class RestControllerImpl {
#Value("${uri-root}")
private String uriRoot;
private final Service service;
#PostMapping("/api")
public ResponseEntity createClient(#RequestBody Transaction dto) throws URISyntaxException {
service.perform(dto);
String referenceToken = "sldflh#2lhf*shdjkla"
String urlRedirect = uriRoot + "?token=" + referenceToken;
return ResponseEntity.created(new URI(urlRedirect)).build();
}
}
I have a spring boot application that uses Retrofit to make requests to a secured server.
My endpoints:
public interface ServiceAPI {
#GET("/v1/isrcResource/{isrc}/summary")
Call<ResourceSummary> getResourceSummaryByIsrc(#Path("isrc") String isrc);
}
public interface TokenServiceAPI {
#FormUrlEncoded
#POST("/bbcb6b2f-8c7c-4e24-86e4-6c36fed00b78/oauth2/v2.0/token")
Call<Token> obtainToken(#Field("client_id") String clientId,
#Field("scope") String scope,
#Field("client_secret") String clientSecret,
#Field("grant_type") String grantType);
}
Configuration class:
#Bean
Retrofit tokenAPIFactory(#Value("${some.token.url}") String tokenUrl) {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(tokenUrl)
.addConverterFactory(JacksonConverterFactory.create());
return builder.build();
}
#Bean
Retrofit serviceAPIFactory(#Value("${some.service.url}") String serviceUrl, TokenServiceAPI tokenAPI) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new ServiceInterceptor(clientId, scope, clientSecret, grantType, apiKey, tokenAPI))
.build();
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(repertoireUrl)
.client(okHttpClient)
.addConverterFactory(JacksonConverterFactory.create());
return builder.build();
}
Interceptor to add the Authorization header to every request
public class ServiceInterceptor implements Interceptor {
public ServiceInterceptor(String clientId,
String scope,
String clientSecret,
String grantType,
String apiKey,
TokenServiceAPI tokenAPI) {
this.clientId = clientId;
this.scope = scope;
this.clientSecret = clientSecret;
this.grantType = grantType;
this.apiKey = apiKey;
this.tokenAPI = tokenAPI;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader(AUTHORIZATION_HEADER, getToken())
.addHeader(API_KEY_HEADER, this.apiKey)
.build();
return chain.proceed(newRequest);
}
private String getToken() throws IOException {
retrofit2.Response<Token> tokenResponse = repertoireTokenAPI.obtainToken(clientId, scope, clientSecret, grantType).execute();
String accessToken = "Bearer " + tokenAPI.body().getAccessToken();
return accessToken;
}
}
This is working as expected, the problem is that the token is being requested for every request rather than using the existing valid one. How can one store the token somewhere and re-use it? I was wondering if Retrofit had a built-in solution.
a possible option with caching:
add caffeiene
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
add #Cacheable("your-token-cache-name") on the method returning the token, looks like getToken above
add max cache size and expiration configuration in application.yml
e.g. 500 entries and 10 minutes for configuration below
spring.cache.cache-names=your-token-cache-name
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
example from: https://www.javadevjournal.com/spring-boot/spring-boot-with-caffeine-cache/
I have a controller that should allow downloading files with arbitrary content type:
#GetMapping(value="/download/{directory}/{name}",
consumes=MediaType.ALL_VALUE)
#Timed
public ResponseEntity<byte[]> downloadFile(#PathVariable String directory,
#PathVariable String name) {
log.debug("REST request to download File : {}/{}", directory, name);
byte[] content = "it works".getBytes();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, "text/plain");
return new ResponseEntity<>(content, headers, HttpStatus.OK);
}
I want to test that in a unit test like this:
...
private MockMvc restFileMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
final FileResource fileResource = new FileResource(fileService);
this.restFileMockMvc = MockMvcBuilders.standaloneSetup(fileResource)
.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter)
.setValidator(validator).build();
}
#Test
#Transactional
public void downloadFile() throws Exception {
String url = "/api/download/it/works.txt";
restFileMockMvc.perform(get(url).header(HttpHeaders.ACCEPT, "*/*"))
.andDo(MockMvcResultHandlers.print()) // Debugging only!
.andExpect(status().isOk());
}
But obviously, there is a problem with the content type, resp. the accept header. MockMvcResultHandlers.print() produces the following:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/download/DIRDIR/NAMENAME
Parameters = {}
Headers = {Accept=[*/*]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.example.storage.web.rest.FileResource
Method = public org.springframework.http.ResponseEntity<byte[]> com.example.storage.web.rest.FileResource.downloadFile(java.lang.String,java.lang.String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotAcceptableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 406
Error message = null
Headers = {Content-Type=[application/problem+json]}
Content type = application/problem+json
Body = {"type":"https://www.jhipster.tech/problem/problem-with-message","title":"Not Acceptable","status":406,"detail":"Could not find acceptable representation","path":"/api/download/DIRDIR/NAMENAME","message":"error.http.406"}
Forwarded URL = null
Redirected URL = null
Cookies = []
It looks like the request is sent with Accept: */*. What does Spring complain about then?
It could be an issue with your message converter, used in your test case. I too faced similar issue and resolved it by passing additional parameter in messageConverter for my mockMvc
this.restMockMvc = MockMvcBuilders.standaloneSetup(testResource)
.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setMessageConverters(jacksonMessageConverter,new
ByteArrayHttpMessageConverter()).build();
You need to overload message converter property for MockMVC. for more info , relevant question
I was already using #SpringJUnitWebConfig(...) and included the #EnableWebMvc annotation to my imported Config. This seemed to add all the necessary converters. E.g.
#SpringJUnitWebConfig(MyTestConfig.class)
class MyTest {
#Inject
private WebApplicationContext wac;
private MockMvc mockMvc;
...
}
#EnableWebMvc
class MyTestConfig {
#Bean
...
}
I have controller:
#PostMapping(value = "/value/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String updateSettings(final Dto dto) {
System.out.println(">>> " + dto);
return "template";
}
Controller works if I send request across chrome window. But when I write test for this method I get problem. Not converted object, value not inserted.
Test:
#Test
#WithMockUser(username = FAKE_VALID_USER, password = FAKE_VALID_PASSWORD)
public void test_B_CreateDtoWithValidForm() throws Exception {
final Dto dto = new Dto();
dto.setId("value");
dto.setEnabled("true");
this.mockMvc.perform(post(URL_SET_PROVIDER_SETTINGS)
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content(dto.toString()))
.andDo(print());
}
Output is >>> Dto{id=null, enabled=false}
How test Post request with custom object in content type application/x-www-form-urlencoded?
In this case you don't need to use content, but instead you need to use param in this way:
this.mockMvc.perform(post(URL_SET_PROVIDER_SETTINGS)
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.param("id", "value")
.param("enabled", "true"))
.andDo(print());