Using SoapUI I am able to send a request with a custom SOAP header like this:
<soap:Header>
<To xmlns="http://www.w3.org/2005/08/addressing">ws://xxx.com/PP/QM/GPMService/Vx</To>
<Action xmlns="http://www.w3.org/2005/08/addressing">http://xmldefs.xxx.com/PP/QM/GPMService/Vx/AbcService/GetServiceInfoRequest</Action>
<MessageID xmlns="http://www.w3.org/2005/08/addressing">ITEST-2018-04-16-0001</MessageID>
<Stage xmlns="http://xmldefs.xxx.com/Technical/Addressing/V1">ProdX</Stage>
</soap:Header>
and get a reasonable response.
I can't achieve this in my SpringBoot application.
I have a service extending WebServiceGatewaySupport:
#Service
public class AbcService extends WebServiceGatewaySupport{
private AbcConfiguration abcConfiguration;
#Autowired
public void setAbcConfiguration(final AbcConfiguration abcConfiguration) {
this.abcConfiguration = abcConfiguration;
}
public GetServiceInfoResponse GetServiceInfo() {
final String actionStr = "GetServiceInfo";
final ObjectFactory factory = new ObjectFactory();
GetServiceInfo getServiceInfo = factory.createGetServiceInfo();
JAXBElement<GetServiceInfo> gsiRequest = factory.createGetServiceInfo(getServiceInfo);
WebServiceTemplate wst = this.getWebServiceTemplate();
#SuppressWarnings("unchecked")
JAXBElement<GetServiceInfoResponse> gsiResponse = (JAXBElement<GetServiceInfoResponse>)wst
.marshalSendAndReceive("https://ws-gateway-cert.xxx.com/services/", gsiRequest, new WebServiceMessageCallback() {
#Override
public void doWithMessage(WebServiceMessage message) {
try {
SoapHeader soapHeader = ((SoapMessage) message).getSoapHeader();
SoapHeaderElement toElem = soapHeader.addHeaderElement(new QName("http://www.w3.org/2005/08/addressing", "To"));
toElem.setText("ws://xxx.com/PP/QM/GPMService/Vx");
...
} catch (Exception e) {
logger.error("Error during marshalling of the SOAP headers", e);
}
}
});
return gsiResponse.getValue();
}
}
What am I doing wrong? Can anybody tell me how I can do this?
Okay. I got it working so far and the SOAP XML looks as demanded and running the request (being generated form my SpringBoot app) in SoapUI I get the demanded result.
public GetServiceInfoResponse GetServiceInfo() {
final String actionStr = "GetServiceInfo";
final ObjectFactory factory = new ObjectFactory();
GetServiceInfo getServiceInfo = factory.createGetServiceInfo();
JAXBElement<GetServiceInfo> gsiRequest = factory.createGetServiceInfo(getServiceInfo);
WebServiceTemplate wst = this.getWebServiceTemplate();
#SuppressWarnings("unchecked")
JAXBElement<GetServiceInfoResponse> gsiResponse = (JAXBElement<GetServiceInfoResponse>)wst
.marshalSendAndReceive(kpmConfiguration.getEndpoint(), gsiRequest, new WebServiceMessageCallback() {
#Override
public void doWithMessage(WebServiceMessage message) {
System.out.println(message.toString());
try {
// get the header from the SOAP message
final SoapHeader soapHeader = ((SoapMessage) message).getSoapHeader();
final SaajSoapMessage ssMessage = (SaajSoapMessage)message;
final SOAPEnvelope envelope = ssMessage.getSaajMessage().getSOAPPart().getEnvelope();
System.out.println("envelope.getPrefix(): " + envelope.getPrefix());
envelope.removeNamespaceDeclaration("SOAP-ENV");
envelope.setPrefix(NAMESPACE_PREFIX_SOAP);
System.out.println("envelope.getPrefix(): " + envelope.getPrefix());
envelope.getBody().setPrefix(NAMESPACE_PREFIX_SOAP);
envelope.getHeader().setPrefix(NAMESPACE_PREFIX_SOAP);
envelope.addNamespaceDeclaration(NAMESPACE_PREFIX_SOAP, NAMESPACE_PREFIX_SOAP_DEF);
envelope.addNamespaceDeclaration(NAMESPACE_PREFIX_V2, NAMESPACE_PREFIX_V2_DEF);
envelope.addNamespaceDeclaration(NAMESPACE_PREFIX_WSSE, NAMESPACE_PREFIX_WSSE_DEF);
final SoapHeaderElement toElem = soapHeader.addHeaderElement(new QName(NAMESPACE_PREFIX_ADDRESSING, "To"));
toElem.setText(TO_VALUE);
final SoapHeaderElement actionElem = soapHeader.addHeaderElement(new QName(NAMESPACE_PREFIX_ADDRESSING, "Action"));
actionElem.setText(NAMESPACE_PREFIX_V2_DEF + "/AbcService/" + actionStr + "Request");
final SoapHeaderElement messageIdElem = soapHeader.addHeaderElement(new QName(NAMESPACE_PREFIX_ADDRESSING, "MessageID"));
messageIdElem.setText(MESSAGE_ID_VALUE + UUID.randomUUID());
final SoapHeaderElement stageElem = soapHeader.addHeaderElement(new QName(NAMESPACE_PREFIX_VWA, "Stage"));
stageElem.setText("Production");
final NodeList nl = ssMessage.getSaajMessage().getSOAPPart().getEnvelope().getBody().getChildNodes();
ssMessage.getSaajMessage().getSOAPPart().getEnvelope().getBody().removeChild(nl.item(0));
final SOAPElement se = ssMessage.getSaajMessage().getSOAPPart().getEnvelope().getBody().addBodyElement(new QName(actionStr));
se.setPrefix(NAMESPACE_PREFIX_V2);
final SOAPElement userAuthElem = se.addChildElement(new QName("UserAuthentification"));
final SOAPElement userIdElem = userAuthElem.addChildElement("UserId");
userIdElem.setTextContent(kpmConfiguration.getCredentials().getUsername());
System.out.println(userIdElem.getTextContent());
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(ssMessage.getPayloadSource(), soapHeader.getResult());
} catch (Exception e) {
logger.error("Error during marshalling of the SOAP headers", e);
}
}
});
return gsiResponse.getValue();
}
However, when I submit the request from my SpringBoot app I always get an exception:
java.net.SocketException: Unexpected end of file from server
Am I missing something in the code?
See the answer to the original question above in the edited question.
Concerning the java.net.SocketException: Unexpected end of file from server it seemed to come from redirecting the request through Eclipse's TCP/IP Monitor. When sending the request directly to the server I get a meaningful response with:
INFO_001
Method compelted successfully
:-)
Related
I have been looking around for sample code how to call a Restful service written in Spring boot (deployed in different server/ip) from an EJB client.
I couldn't find a simple example or reference to guide me on how to implement an EJB client that can call a restful service(deployed in different server/ip). Could you please point me to a document or example that shows or describe how the two can interface/talk to each other.
I need to call the endpoint by passing two header parameters for authentication, if authentication is success then only retrieve the details from Rest and send back the response to EJB client.
I use something like this, try
`public void calExternal() throws ProtocolException,
MalformedURLException,
IOException,
NoSuchAlgorithmException,
InvalidKeyException {
URL myurl = new URL("API END POINT URL");
ObjectMapper mapper = new ObjectMapper();
HttpURLConnection conn = (HttpURLConnection) myurl.openConnection();
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
String payLoad = mapper.writeValueAsString("your payload here");
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("AUTHORIZATION-TYPE", "HMAC");
try {
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(payLoad);
wr.flush();
InputStream in = null;
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
in = conn.getInputStream();
} else {
in = conn.getErrorStream();
}
String encoding = conn.getContentEncoding() == null ? "UTF-8" : conn.getContentEncoding();
String response = IOUtils.toString(in, encoding);
} catch (Exception e) {
e.printStackTrace();
}
}
First of all, I am new with Spring Boot.
I am not sure if it is possible, but I would like to return the xml response from the external url.
I have this code:
#GetMapping("/myPage")
public void myPage() {
restConfiguration().host("localhost").port(8080);
from("timer://runOnce?repeatCount=1&delay=0")
.to("rest:get:/external-page")
.to("stream:out");
}
myPage() is returning a XML (that's OK). So, now I would like to return the same XML when I do:
curl http://localhost/myPage
I am not sure if I have to use .to("stream:out"), but the curl is returning an empty result.
Can someone help me?
Thanks in advance.
I found the solution, this is how to get the response.
CamelContext context = new DefaultCamelContext();
context.addRoutes(new RouteBuilder() {
public void configure() {
restConfiguration().host(sHost).port(iPort);
from("direct:start")
.setHeader(Exchange.HTTP_METHOD,simple("GET"))
.to("rest:get:/external-page");
}
});
context.start();
ProducerTemplate template = context.createProducerTemplate();
String headerValue = "application/xml";
Map<String, Object> headers = new HashMap<String,Object>();
headers.put("Content-Type", headerValue);
Object result = template.requestBodyAndHeaders("direct:start", null, headers, String.class);
Exchange exchange = new DefaultExchange(context);
String response = ExchangeHelper.convertToType(exchange, String.class, result);
context.stop();
return response;
I am using RestTemplate to make Http connection to get data from external APIs. For this I have implemented a custom error handler and set it on the restTemplate object. Below is my custom error handler
public class CustomResponseErrorHandler implements ResponseErrorHandler {
public boolean hasError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
if (rawStatusCode / 200 != 1) {
LOG.debug("HTTPS hasError - " + rawStatusCode + "; " + response.getStatusText() + "; " + response.getStatusCode());
return true;
}
return false;
}
public void handleError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
LOG.debug("HTTPS handleError - " + rawStatusCode + "; " + response.getStatusText() + "; " + response.getStatusCode());
}
}
and my RestTemplateUtils class looks like below
public class RestTemplateUtils {
RestTemplate restTemplate;
public ResponseEntity<String> restGet(String url) {
restTemplate.setErrorHandler(new CustomResponseErrorHandler());
ResponseEntity<String> response= restTemplate.getForEntity(url, String.class);
return response;
}
}
I expect that any error that gets thrown during the restTemplate.getForEntity() call should be caught and logged by the CustomResponseErrorHandler but that is not the case. When I pass in a non-existent url ResponseEntity<String> response= restTemplate.getForEntity(url, String.class); throws ResourceAccessException. What should I do if I want my custom error handler to catch a 404 in such a case? Am I missing something here or misunderstanding how custom error handler should work here?
If you completely give a non existing url then I don't think the code is going to the point where error handler is executed;
Looking at RestTemplate#doExecute
doExecute(URI url, #Nullable HttpMethod method, #Nullable RequestCallback requestCallback,
#Nullable ResponseExtractor<T> responseExtractor)
code
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
handleResponse is where the error handler is looked for but I think yours is erroring out at request.execute();
Provide some non existing url on the server api path, then you would recieve a 404 from the server and your custom error handler gets executed.
I'm trying to figure out how to log exceptions from the webclient, whatever the error status code that is returned from the api that gets called.
I've seen the following implementation:
.onStatus(status -> status.value() != HttpStatus.OK.value(),
rs -> rs.bodyToMono(String.class).map(body -> new IOException(String.format(
"Response HTTP code is different from 200: %s, body: '%s'", rs.statusCode(), body))))
Another example I've seen uses a filter. I guess this filter could be used to log errors as well, aside from requests like in this example:
public MyClient(WebClient.Builder webClientBuilder) {
webClient = webClientBuilder // you can also just use WebClient.builder()
.baseUrl("https://httpbin.org")
.filter(logRequest()) // here is the magic
.build();
}
But are we serious that there is no dedicated exception handler to this thing?
Found it.
bodyToMono throws a WebClientException if the status code is 4xx (client error) or 5xx (Server error).
Full implementation of the service:
#Service
public class FacebookService {
private static final Logger LOG = LoggerFactory.getLogger(FacebookService.class);
private static final String URL_DEBUG = "https://graph.facebook.com/debug_token";
private WebClient webClient;
public FacebookService() {
webClient = WebClient.builder()
.filter(logRequest())
.build();
}
public Mono<DebugTokenResponse> verifyFbAccessToken(String fbAccessToken, String fbAppToken) {
LOG.info("verifyFacebookToken for " + String.format("fbAccessToken: %s and fbAppToken: %s", fbAccessToken, fbAppToken));
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(URL_DEBUG)
.queryParam("input_token", fbAccessToken)
.queryParam("access_token", fbAppToken);
return this.webClient.get()
.uri(builder.toUriString())
.retrieve()
.bodyToMono(DebugTokenResponse.class);
}
private static ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
LOG.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> LOG.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
#ExceptionHandler(WebClientResponseException.class)
public ResponseEntity<String> handleWebClientResponseException(WebClientResponseException ex) {
LOG.error("Error from WebClient - Status {}, Body {}", ex.getRawStatusCode(), ex.getResponseBodyAsString(), ex);
return ResponseEntity.status(ex.getRawStatusCode()).body(ex.getResponseBodyAsString());
}
}
I am using Google's Volley library for my application project , that targets minimum api level 14.So, Volley library uses HttpURLConnection as the NetworkClient.
Therefore , there should not be any issue related to Removal of Apache HTTPClient. However, I have done the configuration required for 6.0 Sdk i.e compileSdkVersion 23, build-tool-version 23.0.1 and build:gradle:1.3.1' and even tried adding useLibrary 'org.apache.http.legacy'. Have updated the same for Volley library project in my application.
Recently ,I tried to run my app on Android 6.0 (MarshMallow), my project compiles and runs. But those requests that require authentication headers are failing on MarshMallow with:
BasicNetwork.performRequest: Unexpected response code 401 com.android.volley.AuthFailureError
However the same is running on all Api level below 23.
I have checked the headers many times.Strangely, those requests that do not require authentication are giving response with 200 OK.
Right now I am totally clueless what is breaking the requests, does anybody have any idea what has changed in new Version that HttpURLConnection request fails for only Api level 23? Is anybody else using Volley and facing similar issue?
Here is my CustomRequest Class
public class CustomRequest extends Request<Void> {
int id, cmd;
Map<String, String> params;
BaseModel model;
public CustomRequest(int method, int cmd, String url, Map<String, String> params, int id, BaseModel model) {
super(method, url, null);
this.id = id;
this.cmd = cmd;
this.params = params;
this.model = model;
if (method == Method.GET) {
setUrl(buildUrlForGetRequest(url));
}
Log.v("Volley", "Making request to: " + getUrl());
}
private String buildUrlForGetRequest(String url) {
if (params == null || params.size() == 0) return url;
StringBuilder newUrl = new StringBuilder(url);
Set<Entry<String, String>> paramPairs = params.entrySet();
Iterator<Entry<String, String>> iter = paramPairs.iterator();
newUrl.append("?");
while (iter.hasNext()) {
Entry<String, String> param = iter.next();
newUrl
.append(param.getKey())
.append("=")
.append(param.getValue());
if (iter.hasNext()) newUrl.append("&");
}
return newUrl.toString();
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<String, String>();
headers.put("X-Api-Version", Contract.API_VERSION);
headers.put("X-Client", "android");
String accessToken = APP.getInstance().getToken();
if (!accessToken.equals("")) {
headers.put("Authorization", "Bearer " + accessToken);
}
return headers;
}
#Override
protected Response<Void> parseNetworkResponse(NetworkResponse response) {
Exception ex;
try {
String jsonString = new String(
response.data, HttpHeaderParser.parseCharset(response.headers));
JsonNode json = new ObjectMapper().readTree(jsonString);
if (model != null) model.parse(id, json);
EventBus.getDefault().post(new EventResponse(cmd, true, null));
return Response.success(null, HttpHeaderParser.parseCacheHeaders(response)); //Doesn't return anything. BaseModel.parse() does all the storage work.
} catch (NoMoreDataException e) {
ex = e;
EventBus.getDefault().post(new NoMoreDataModel(cmd, e));
EventBus.getDefault().post(new EventResponse(cmd, false, null));
return Response.success(null, HttpHeaderParser.parseCacheHeaders(response));
} catch (Exception e) {
ex = e;
Log.e("CustomRequest", Log.getStackTraceString(e));
String message = APP.getInstance().getString(R.string.failedRequest);
if (!TextUtils.isEmpty(e.getMessage()))
message = e.getMessage();
EventBus.getDefault().post(new ErrorEventModel(cmd, message, e));
return Response.error(new ParseError(ex));
}
}
#Override
protected Map<String, String> getParams() throws AuthFailureError {
return params;
}
#Override
protected void deliverResponse(Void response) {
Log.v("Volley", "Delivering result: " + getUrl());
}
#Override
public void deliverError(VolleyError error) {
Log.e("CustomRequest", "Delivering error: Request=" + getUrl() + " | Error=" + error.toString());
String message = APP.getInstance().getString(R.string.failedRequest);
EventBus.getDefault().post(new ErrorEventModel(cmd, message, error));
}
}
Only difference I found between Api 23 and others is the HostNameVerifier.
For Api level 23 : com.android.okhttp.internal.tls.OkHostnameVerifier
For Api level <23 : javax.net.ssl.DefaultHostnameVerifier.
After checking the Server side of my application, found the reason behind the issue.
For Android 6.0(MarshMallow) the headers were becoming empty and this was not the case with other versions.
So the fix that worked for me:
Created and Added a new custom header X-Proxy-No-Redirect => 1 and passed along with other headers.
Theory behind it:
There is a API server to which we send request, then the API server redirects the request to corresponding site based on the oAuth token
While redirecting
For the redirection to happen, there are two ways to do that
1 - We just send a response back to the caller stating to redirect to a certain page. Then the caller(Networking library) takes care of redirection
2 - API server will itself makes the redirect request and get the response and then pass to caller
Earlier we were using the Method 1.
On Android 6.0 - The networking lib(Volley) doesn't seem to set all the headers while making the redirection request.
Once this new header is set, Method 2 will come into effective.
P.S This fix was applicable for my application project , maybe not for others.Just providing what was wrong and what helped me.