TestRestTemplate.postForEntity returns 400 with No Descriptive Message - spring-boot

I'm having some trouble using TestRestTemplate.postForEntity(). My code is:
#Test
public void testAdd() {
Map<String,String> mParams=new HashMap<>();
mParams.put("joke","This is my joke");
mParams.put("description","Totally Tasteless Joke");
mParams.put("date",Long.toString(System.currentTimeMillis()));
ResponseEntity<Joke> reJoke = restTemplate.postForEntity(getURLBase()+"/add",
null,
Joke.class,
mParams
);
Joke j = reJoke.getBody();
System.out.println("Status="+reJoke.getStatusCode()+" j.getJoke()="+j.getJoke()+" id="+j.getId());
}
The returned Status value is 400. Nothing is printed on the console.
I have a testGet() that does work:
#Test
public void testGet() {
System.out.println("testGet()");
initData();
ResponseEntity<Joke> reJoke=restTemplate.getForEntity(getURLBase()+"/1",Joke.class,new HashMap<String,String>());
Joke j=reJoke.getBody();
System.out.println("Status=" + reJoke.getStatusCode()+" j =" + j + (j == null ? "" : j.getJoke()));
}
I noticed in the Javadoc for TestRestTemplate it says:
If Apache Http Client 4.3.2 or better is available (recommended) it will be used as the client, and by default configured to ignore cookies and redirects.
I've added
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
<scope>test</scope>
</dependency>
to pom.xml, but it doesn't seem to make any difference.
Can anyone tell me how to either solve the issue, or get more information than "400"? It's really appreciated.

After a little searching, I found that the mParams was actually URL parameters. I needed to send the form encoded parameters as the request object. The working code is:
#Test
public void testAdd() {
MultiValueMap<String, String> mParams= new LinkedMultiValueMap<String, String>();
mParams.add("joke", "This is my joke");
mParams.add("description", "Totally Tasteless Joke");
mParams.add("date", Long.toString(System.currentTimeMillis()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(mParams, headers);
ResponseEntity<Joke> reJoke = restTemplate.postForEntity(getURLBase()+"/add",
request,
Joke.class
);
Joke j = reJoke.getBody();
System.out.println("Status="+reJoke.getStatusCode()+" j.getJoke()="+j.getJoke()+" id="+j.getId());
}

Related

Create mock server to test on result of RestTemplate

I am not sure if it is possible to write a Test case that can mock the "http://localhost:8888/setup" site, so the above code can hit it and I want to check if the "http://localhost:8888/setup" received the inputStream correctly.
InputStream inputStream = //got the inputStream;
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
InputStreamResource inputStreamResource = new InputStreamResource(inputStream){
#Override
public String getFilename(){
return filename;
}
#Override
public long contentLength(){
return -1;
}
}
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>():
body.add("file", inputStreamResource);
HttpHeader headers = new HttpHeader();
headers.setContentType(MediaType.MULTIPART_FORM_DATA)LinkedMultiValueMap
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
String url = "http://localhost:8888/setup";
restTemplate.postForObject(url, requestEntity, String.class);
Try using Wiremock!
Many ways of using it, back then when I used it, I used to run a JAR (wiremock jar) and it spawns up a program on your localhost with your port specified. Henceforth, you can test by hitting that localhost on the port it's up!
For reference check this out :
https://www.softwaretestinghelp.com/wiremock-tutorial/
https://www.baeldung.com/introduction-to-wiremock
https://github.com/wiremock/wiremock

RestTemplate execute() method cannot send JSON Payload

In my application, I need to take data from another request and chain into a new one
I must use the exchange() method of RestTemplate because I have issue with jacksons lib and I cannot add/change the libs.
this is my code:
final RequestCallback requestCallback = new RequestCallback() {
#Override
public void doWithRequest(final ClientHttpRequest request) throws IOException {
request.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
// Add basic auth header
String auth = username + ":" + password;
byte[] encodedAuth = Base64Utils.encode(auth.getBytes(StandardCharsets.US_ASCII));
String authHeader = "Basic " + new String(encodedAuth);
request.getHeaders().add("Authorization", authHeader);
// Add Headers Request
Enumeration headerNamesReq = servletRequest.getHeaderNames();
while (headerNamesReq.hasMoreElements()) {
String headerName = (String) headerNamesReq.nextElement();
if (whiteListedHeaders.contains(headerName.toLowerCase())) {
String headerValue = servletRequest.getHeader(headerName);
request.getHeaders().add(headerName, headerValue);
}
}
request.getHeaders().forEach((name, value) -> {
log.info("RestExecutorMiddleware", "HEADERS ---\t" + name + ":" + value);
});
IOUtils.copy(new ByteArrayInputStream(payload.getBytes()), request.getBody());
}
};
// Factory for restTemplate
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
ClientHttpResponse responsePost = restTemplate.execute(url, method, requestCallback, new ResponseFromHeadersExtractor());
But at the end, the endpoint cannot receive my JSON (receive data, but not JSON.)
Where I wrong?
Thanks
Very inaccuracy code. Make all steps one-to-one and it will work, you make optimization later ...
Basic Auth. Don't do unnecessary actions
var headers = new HttpHeaders();
headers.setBasicAuth(username, password);
That's all, Spring will take care of everything else - to apply Base64, add Basic: and set properly a header.
Set all required headers including headers.setContentType(MediaType.APPLICATION_JSON)
Get an entity/object which you need to send (set as a body) with a request.
Serialize your object. The most popular, proper and simple way is using fasterxml json framework, you can make serialization with mapper.writeBalueAsString(<your object>). If you really cannot use external libraries, HttpEntity should make it: var request = new HttpEntity<>(<object>, headers);
Make restTemplate request. In almost all cases more convenient methods are restTemplate.postForObject(), restTemplate.getForObject(), restTemplate.postForEntity(), etc.: restTemplate.postForObject(uri, request, ResponseObject.class)

Uploading jar to nexus with Spring RestTemplate

I would like to use Spring's RestTemplate to upload a jar to nexus. I found instructions on how to do it using curl here, which works just fine. However I have utterly failed in converting this to a RestTemplate call. Here is my code:
String auth = "admin:admin123";
byte[] encodedAuth = Base64.encodeBase64( auth.getBytes(Charset.forName("US-ASCII")) );
String authHeader = "Basic " + new String( encodedAuth );
RestTemplate restTemplate = new RestTemplate();
LinkedMultiValueMap<String, Object> files = new LinkedMultiValueMap<>();
files.add("r", "releases");
files.add("hasPom", "false");
files.add("e", "jar");
files.add("g", "com.test.proj");
files.add("a", "my-artifact");
files.add("v", "1.0.0");
files.add("p", "jar");
files.add("file", new ByteArrayResource(jarBytes));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.set("Authorization", authHeader);
HttpEntity<LinkedMultiValueMap<String, Object>> entity = new HttpEntity<>(files, headers);
String response = restTemplate.postForObject("http://localhost:8081/nexus/service/local/artifact/maven/content", entity, String.class);
This fails with a 400 Bad Request. After looking at the source, it looks like my file is failing this key check:
for (FileItem fi : files) {
if (fi.isFormField()) {
// parameters are first in "nibble"
processFormField(request, uploadContext, fi);
}
else {
// a file, this means NO parameters will income anymore
// we either received all the GAVs as params, or we have a POM to work with (file1)
...
FileItem.isFormField is from Apache Commons FileUpload. Does anyone know how I could get this to succeed with my "file" that I am passing in?
In another question (FileUpload isFormField() returning true when submitting file) the answer suggests that I need a name field, or perhaps in my case, the "type" doesn't come through. If this is the case, is it possible to specify these while making a post request?
I'm thinking that this currently isn't possible with RestTemplate. I opted to use Apache's HttpComponents, which has worked wonderfully. Here is the resulting code:
public void uploadToNexus(String version, File myJar, String artifactId)
{
try(CloseableHttpClient httpClient = HttpClients.createDefault())
{
HttpPost httpPost = new HttpPost("http://admin:admin123#mydomain:8081/nexus/service/local/artifact/maven/content");
FileBody jarFileBody = new FileBody(myJar);
HttpEntity requestEntity = MultipartEntityBuilder.create()
.addPart("r", new StringBody("releases", ContentType.TEXT_PLAIN))
.addPart("hasPom", new StringBody("false", ContentType.TEXT_PLAIN))
.addPart("e", new StringBody("jar", ContentType.TEXT_PLAIN))
.addPart("g", new StringBody("com.domain.my", ContentType.TEXT_PLAIN))
.addPart("a", new StringBody("my-artifactId", ContentType.TEXT_PLAIN))
.addPart("v", new StringBody(version, ContentType.TEXT_PLAIN))
.addPart("p", new StringBody("jar", ContentType.TEXT_PLAIN))
.addPart("file", jarFileBody)
.build();
httpPost.setEntity(requestEntity);
try(CloseableHttpResponse response = httpClient.execute(httpPost))
{
logger.info("response from nexus: {}", response.toString());
}
}
catch (IOException e)
{
throw new RuntimeException("Unable to close the httpClient", e);
}
}
I needed the following maven dependencies:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.4.1</version>
</dependency>

Spring Rest Template to send JsonArray

I am using spring rest template to send json array as request. Source code to send request is as follow:
JSONArray jsonArray = new JSONArray();
for (Iterator iterator = itemlist.iterator(); iterator.hasNext();) {
Item item = (Item)iterator.next();
JSONObject formDetailsJson = new JSONObject();
formDetailsJson.put("id", item.getItemConfId());
formDetailsJson.put("name", item.getItems().getItemName());
formDetailsJson.put("price", item.getPrice());
formDetailsJson.put("Cost",item.getCost());
jsonArray.put(formDetailsJson);
}
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
// Prepare header
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
// Pass the new person and header
HttpEntity<JSONArray> entity = new HttpEntity<JSONArray>(jsonArray, headers);
System.out.println("Json Object : "+entity);
// Send the request as POST
try {
ResponseEntity<String> result = restTemplate.exchange("my url", HttpMethod.POST, entity, String.class);
} catch (Exception e) {
logger.error(e);
return "Connection not avilable please try again";
}
And to accept request:
#RequestMapping(value = "/testStock", method = RequestMethod.POST,headers="Accept=application/xml, application/json")
public #ResponseBody int testStock(#RequestBody List<ItemList> jsonArray) {
logger.debug("Received request to connect ms access : "+jsonArray.size());
//int returnSizecount = stockList.getStocklst().size();
return 1;
}
The problem is that it giving me following error:
Could not write request: no suitable HttpMessageConverter found for request type [org.json.JSONArray].Any suggestion is greatly acceptable.
There are no MessageConverter for JSONArray, so I suggest do the following.
HttpEntity<JSONArray> entity = new HttpEntity<JSONArray>(jsonArray, headers);
Convert Class JSONArray to String, and add that to HttpEntity, you know use toString
java.lang.String toString()
Make a JSON text of this JSONArray.
HttpEntity entity = new HttpEntity(jsonArray.toString(), headers);
Or change to Jackson implementation Spring have support to that. XD
If you dont want to do the above, consider create your own implementation of messageConverter, that will work but is harder
update
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
headers.setContentType(MediaType.APPLICATION_JSON);
update 2 Change endpoint to.
#RequestMapping(value = "/testStock", method = RequestMethod.POST)
public #ResponseBody int testStock(#RequestBody String jsonArray) {
you need to have httpmessageconverter configured for your resttemplate, please read my post for configuring http message conveter for you webservice
http://stackoverflow.com/questions/19963127/new-to-spring-and-jackson-2-what-does-this-bean-declaration-allow-for-in-a-spri/19973636#19973636.
and for you problem to convert your http request to json you might add this entry in your restemplate configuration
<bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
The error is quite straightforward. You do not have a converter for the JSONArray. Converting the array to a String (using toString) did help you here, but there is a better way:
Just add a converter for the json.org objects:
Add this to your pom.xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
</dependency>
And then on your ObjectMapper add the JsonOrgModule:
mapper.registerModule(new JsonOrgModule());

Can't get Spring SOAP Client to work: content type 'text/xml; charset=utf-8' was not the expected type 'application/soap+xml; charset=utf-8'

Hi, I am trying to make a simple soap client work using Spring-ws. The googling I've done on this error says I'm using Soap 1.1 and need to specify Soap 1.2. I've tried to do that. Am I doing it correctly below? If this is not the problem does anybody see what the problem is?
Here's a chunk of the stack trace:
org.springframework.ws.client.WebServiceTransportException: Cannot process the message because the content type 'text/xml; charset=utf-8' was not the expected type 'application/soap+xml; charset=utf-8'. [415]
at org.springframework.ws.client.core.WebServiceTemplate.handleError(WebServiceTemplate.java:663)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:587)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:537)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:492)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:436)
at com.jda.fileserver.FujiAuthenticationTest.testLogin(FujiAuthenticationTest.java:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
Here's my code, thanks for trying to help:
public class AuthTest {
#Test
public void testLogin() throws Exception {
StringBuffer loginXml = new StringBuffer();
loginXml.append("<soapenv:Envelope xmlns:soapenv=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:ns=\"http://abc.com/xyz/2010/08\">");
loginXml.append(" <soapenv:Header>");
loginXml.append(" <ns:loginOperationDetails>");
loginXml.append(" </ns:loginOperationDetails>");
loginXml.append(" </soapenv:Header>");
loginXml.append(" <soapenv:Body>");
loginXml.append(" <ns:LogIn>");
loginXml.append(" <ns:logInInfo>");
loginXml.append(" <ns:CustomerAccountId>customer1</ns:CustomerAccountId>");
loginXml.append(" <ns:Username>jsmith</ns:Username>");
loginXml.append(" <ns:Password>abc123</ns:Password>");
loginXml.append(" </ns:logInInfo>");
loginXml.append(" </ns:LogIn>");
loginXml.append(" </soapenv:Body>");
loginXml.append("</soapenv:Envelope>");
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
SaajSoapMessageFactory defaultMessageFactory = (SaajSoapMessageFactory) webServiceTemplate.getMessageFactory();
defaultMessageFactory.setSoapVersion(SoapVersion.SOAP_12);
webServiceTemplate.setMessageFactory(defaultMessageFactory); // probably not needed
StreamSource source = new StreamSource(new StringReader(loginXml.toString()));
StreamResult result = new StreamResult(System.out);
String uri = "http://xyz.abcstage.com/xyz_1.0/membership.svc/ws";
SoapActionCallback requestCallback = new SoapActionCallback("http://abc.com/xyz/2010/08/MembershipService/LogIn");
try {
webServiceTemplate.sendSourceAndReceiveToResult(uri, source, requestCallback, result);
}
catch (SoapFaultException sfe) {
throw new Exception("SoapFaultException", sfe);
}
catch (WebServiceTransportException wste) {
throw new Exception("WebServiceTransportException", wste);
}
}
}
Ok, I fixed the above problem, which gets me to another problem. First here's how I fixed the above problem. Now I don't set the SOAP version on the SaajSoapMessageFactory, I set it on the wrapped MessageFactory. Now the Content-Type going out in my request is application/soap+xml.
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
MessageFactory msgFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
SaajSoapMessageFactory newSoapMessageFactory = new SaajSoapMessageFactory(msgFactory);
webServiceTemplate.setMessageFactory(newSoapMessageFactory);
Next problem, now I'm getting this:
org.springframework.ws.soap.client.SoapFaultClientException: Unexpected fault in the service.
at org.springframework.ws.soap.client.core.SoapFaultMessageResolver.resolveFault(SoapFaultMessageResolver.java:37)
at org.springframework.ws.client.core.WebServiceTemplate.handleFault(WebServiceTemplate.java:774)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:600)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:537)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:492)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:436)
and here's the info that was returned in the response:
500 Internal Server Error
The SOAP action specified on the message, '', does not match the HTTP SOAP Action, 'http://abc.com/xyz/2010/08/MembershipService/LogIn'.
I'll try to solve this, but wanted to update anybody reading this so they can stop looking into the previous error. I need to figure out how to correctly set the soap action.
Please use the below code to change the header content type to text/xml;charset=utf-8 in Spring webservice template marshallSendAndReceive method.
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.marshalSendAndReceive(url, request, new WebServiceMessageCallback() {
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
MimeHeaders headers = soapMessage.getSaajMessage().getMimeHeaders();
headers.addHeader(TransportConstants.HEADER_CONTENT_TYPE, "text/xml;charset=utf-8");
For adding soapheader with action and to tags, below code is working fine for me.
public void doWithMessage(WebServiceMessage message) throws IOException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
SoapEnvelope soapEnvelope = soapMessage.getEnvelope();
SoapHeader soapHeader = soapEnvelope.getHeader();
//Initialize QName for Action and To
QName action = new QName("{uri}", "Action", "{actionname}");
QName to = new QName("{uri}", "To", "{actionname}");
soapHeader.addNamespaceDeclaration("{actionname}", "{uri}");
SoapHeaderElement soapHeaderElementAction = soapHeader.addHeaderElement(action);
SoapHeaderElement soapHeaderElementTo = soapHeader.addHeaderElement(to);
soapHeaderElementAction.setText("{text inside the tags}");
soapHeaderElementTo.setText("{text inside the tags}");
soapMessage.setSoapAction("{add soap action uri}");
soapMessage.writeTo(out);
}
Use setHeader since you want probably want to replace the contents of an existing header or add it if it doesn't exist. Though Httpheaders are supposed to be case insensitive using addHeader with do just that. Debugging through the source code when addHeader() is called even though it ignores case you'll see that it inserts the new header after the other header.
So , at least in Java, will end up with
content-type:
Content-Type:
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
MimeHeaders headers = soapMessage.getSaajMessage().getMimeHeaders();
headers.setHeader(TransportConstants.HEADER_CONTENT_TYPE, "text/xml;charset=utf-8");

Resources