How to customize HTTP response for WebSocket upgrade request in Spring? - java

I have a WebSocket endpoint configured like this in my WebSocketConfigurer implementation
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ExceptionWebSocketHandlerDecorator(myWebSocketHandler), "/ws/")
.setAllowedOrigins("*")
.addInterceptors(myHandshakeInterceptor);
}
There are some checks in myWebSocketHandler#beforeHandshake and I would like to send an error message to the user in case the server decides to refuse to upgrade the connection. I've tried this
response.setStatusCode(HttpStatus.BAD_GATEWAY);
try {
response.getBody().write("Error".getBytes());
response.getBody().flush();
} catch (IOException e) {
log.error("Error writing response", e);
}
return false;
The status code works, but the body is empty. How can I send it?
EDIT: turns out the problem was that I used Firefox console to check for the response and it didn't show me anything. If I use cURL to make the same request then everything works fine and I see the message I write to the response body!

To update response body you need to write your custom RequestUpateStrategy.
As from spring official documentation from AbstractHandshakeHandler it says that:
The actual upgrade is delegated to a server-specific RequestUpgradeStrategy, which will update the response as necessary and initialize the WebSocket.
If you are using Tomcat as servlet container you can extend TomcatRequestUpgradeStrategyclass and provider your own logic how to handle
websocket upgrade.
For each servlet container there is implementation of RequestUpgradeStrategy
documentation

Related

Java webapp responding the source code of my login page

I have a java web application which publish a service that returns an object in JSON format, and I have another java web app just to consume that service through a JSONP call and show the response. In my local machine it's working fine, but now that I want to test it in a web environment (Layershift in my case), I can't get the JSON object. I don't see any errors on Chrome developer tools, but when I look into the Response tab (in Network option) I see the source code of the login page of my application. Let me show you the my code
Controller with the service:
#RestController
public class MyController {
#RequestMapping(value="/myservice/get/{somevar}")
public MappingJacksonValue getMyObject (#RequestParam String callback, #PathVariable String somevar, HttpServletRequest request, HttpServletResponse response) {
MyObject obj = new MyObject();
//some logic
MappingJacksonValue value = new MappingJacksonValue(obj);
value.setJsonpFunction(callback);
return value;
}
}
javascript code for call the service:
$.fn.callWithJsonP = function(somevar) {
var url = "/myservice/get/" + somevar + "?callback=myCallback";
$.getJSON(url, function(data) {
if (data.value.status == "OK") {
//shows the data contained
}
});
}
Apache configuration (added to avoid CORS error), present in both applications
Header add Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"
Header always unset X-Frame-Options
This is working perfectly on muy local machine (except for the Apache, I don't use it locally), but as I said, on a web environment I receive the source code of my login page. In the Headers tab I can see the headers added in Apache, and the status code of the response is OK, any clues about what's goin on?
Kind regards
UPDATEI've removed the Apache web server, and even tested the web service with Postman (meaning, no second application), and still the same result. I've tried changing #RestController for #Controller and returning an instance of MyObject (instead of MappingJacksonValue), with no results, please help me
I am probably way off here, but is it possible that you have a servlet filter or other part of your web app config that is routing your get request to your login page before your REST framework is able to map it to your endpoint? I use Jersey for this usually, so I am unfamiliar with Spring's RestController, but I assume it is doing similar thing - routes URLs that match to the java code. If you are seeing login page in response it sounds like something is interfering and trying to force user to login before Spring directs to your endpoint method.
It seems like you have a fallback set in your server, maybe configured for a single page application. This is normally used for routing with HTML5 mode using routers like angular's.
you are getting the login page code as response because the login is failed and you are redirected to the same page.. so before calling the service first you need to do the authentication and then by using the authentication token call the service..

I get a status 200 when connecting to the websocket, but it is an error?

My error shows up in the console of my browser:
"WebSocket connection to 'ws://localhost:32768/DspClusterWebServices/myHandler' failed: Unexpected response code: 200"
I am using Spring Websockets 4.1.5 and Tomcat 8.0.18. My WebSocketConfigurer implementation class looks like:
#Configuration
#Controller
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer
{
class MyHandler implements WebSocketHandler
{
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception
{
System.out.println("afterConntectionEstablished called");
}
...implements rest of functions with a System.out.println and false for supportsPartialMessages()
}
}
#Override registerWebSocketHandlers(WebSocketHandlerRegistry registry)
{
registry.addHandler(myHandler(), "myHandler").withSockJS();
}
#Bean
public WebSocketHandler myHandler()
{
return new MyHandler();
}
}
My testWebsocketClient.js tries to connect with this code, but has a error code of 200:
websocket = new WebSocket("ws://localhost:8080/myApp/myHandler");
I cannot figure out what to try next. I thought that this would cause the afterConnectionEstablished(WebSocketSession session) method to fire? Isn't code 200 good?
Please check http://procbits.com/connecting-to-a-sockjs-server-from-native-html5-websocket!
After you append /websocket (to your URL), it will give you the error
Failed to parse Origin header value [null]
;)
, which then will in turn lead you to that link.
You'll have to add .setAllowedOrigins("*") to your addHandler() method, and then it could finally work!
As my another answer:[https://stackoverflow.com/a/53272666/2930417][1]
I use springboot 2 +STOMP。
remove .withSockJS(),then everything is ok.
I don't know the reason,but works for me.
Have a look at the specification . The server should respond with 101 to signal protocol change from http to ws.
Don't know if this is too late but a solution that I stumbled upon is simply appending the string /websocket after the websocket endpoint that you declared in the spring boot server. This will help keep both the forwarding logic and connect and establish a websocket connection.
For those guys like me who use angular + springboot and got this error. please check if you have enabled the redirect or forward all non api endpoint request back to index.html. like:
#RequestMapping(value = "/**/{[path:[^\\.]*}")
public String redirect() {
// Forward to home page so that route is preserved.
return "forward:/index.html";
}
If you do, disable it and you will get 101
Please check that if 'ws://localhost:32768/DspClusterWebServices/myHandler' is correct.

Request right after Response?

I am a bit lost with the following scenario:
My webservice consumes POST requests with form data. I have to reply with 200 OK or otherwise the senders thinks the request failed.
Immediately after answering with 200 I would like to proceed to call another webservice on a remote host with some of the data I have just received.
My webservice consumes the POST request with the #GET annotation. That works I can read all the form data. To call the other webservice I used the Jersey Client API. That works fine too.
I just can't figure out how to switch from switching from one call to another. Everything is programmed with Jersey 2 and deployed in Tomcat, so no real Application Server. There is no full Java EE stack available.
Am I missing some middleware? Do I need to implement a custom event-loop or some message broker?
Not sure if there's any "standard" way to handle this, but there's a CompletionCallback we can register with an AyncResponse.
CompletionCallback:
A request processing callback that receives request processing completion events.
A completion callback is invoked when the whole request processing is over, i.e. once a response for the request has been processed and sent back to the client or in when an unmapped exception or error is being propagated to the container.
The AsyncResponse is meant to handle requests asynchronously , but we can immediately call resume to treat it like it was synchronous. A simple example would be something like
#Path("/callback")
public class AsyncCallback {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public void postWithAsync(#Suspended AsyncResponse asyncResponse,
SomeObject object) {
asyncResponse.register(new CompletionCallback() {
#Override
public void onComplete(Throwable error) {
if (error == null) {
System.out.println("Processing new Request");
} else {
System.out.println("Exception in Request handling");
}
}
});
Response response = Response.ok("Success").build();
// Carry on like nothing happened
asyncResponse.resume(response);
}
}
You can see more explanation at Asynchronous Server-side Callbacks

RestEasy resets all headers when resource throws an exception

I've uncovered something strange while using resteasy-jaxrs in a Cors enabled jBoss server. Here's the setup:
Our server has thetransactioncompany.com's CorsFilter (v1.3.2) enabled as a servlet filter to enable CORS and add the appropriate CORS headers to the HttpServletResponse object.
The server itself is using resteasy-jaxrs (v2.3.2.Final) to serve JSON endpoints to power our app, obviously running on a separate domain.
The issue is that if one of my endpoint methods generates any type of exception (NPE, UnauthorizedException, InternalServerErrorException), as a part of preparing the response, RestEasy makes the call
(HttpServletResponse)response.reset()
which clears out my CORS headers. This causes Chrome to rightly act as if the request should be canceled. This is really inconvenient for my front end dev who needs those error codes.
Two questions:
Why would RestEasy want to clear those headers?
Has anyone else run across this and have any workarounds?
I am actually surprised to see that you have encountered this behavior since I've never seen it, and I certainly have generated plenty of exceptions in my day.
Regardless, consider writing a custom ExceptionMapper as described here. You can populate the response with your CORS headers and return it from toResponse along with the appropriate error information.
I ran into this issue too, and I had a workaround by using a response wrapper
class CorsHeaderResponseWrapper extends HttpServletResponseWrapper{
public CorsHeaderResponseWrapper(HttpServletResponse resp) {
super(resp);
setCorsHeader();
}
void setCorsHeader(){
HttpServletResponse resp = (HttpServletResponse)getResponse();
//set cors header here
}
public void reset(){
super.reset();
//set again if anyone reset it
setCorsHeader();
}
}
when calling doFilter
chain.doFilter(req, new CorsHeaderResponseWrapper(resp));

Java Spring Web Service Client Fault Handling

I have written a web service client (using Java Spring and JAXB Marshaller) that works with the UPS web service. When I send a valid request everything works well. When I send an invalid request (weight > 150 lbs) then the UPS web service responds with a SOAP Fault. The client application just fails with a
org.springframework.oxm.UnmarshallingFailureException: JAXB unmarshalling
exception; nested exception is javax.xml.bind.UnmarshalException:
unexpected element (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Fault").
Obviously my program isn't able to decipher the SOAP fault returned by the web service. I wrote a custom FaultMessageResolver, but it doesn't get invoked. Here's the code:
public class UpsRateClient extends WebServiceGatewaySupport {
public UpsRateClient(WebServiceMessageFactory messageFactory) {
super(messageFactory);
getWebServiceTemplate().setFaultMessageResolver(new UpsFaultMessageResolver());
}
public RateResponse getRate(RateRequest rateRequest) {
return (RateResponse) getWebServiceTemplate().marshalSendAndReceive(rateRequest, new UpsRequestWSMC());
}
private class UpsFaultMessageResolver implements FaultMessageResolver {
public void resolveFault(WebServiceMessage message) throws IOException{
System.out.println("Inside UpsFaultMessageResolver");
}
}
}
Thanks for your time!
I had the same problem (SOAP error with HTTP 200OK) and I solved it setting the CheckConnectionForFault property to false. See.
Take a wireshark trace.My thought is that perhaps the web service sends the SOAP fault using (erroneously) a HTTP 200OK instead of a 500 Internal Server error and your client tries to handle it as a valid response.
If this is the case this is then the problem lies in the web service which does not addere to SOAP standard(my emphasis):
From SOAP RFC
In case of a SOAP error while processing the request, the SOAP HTTP
server MUST issue an HTTP 500 "Internal Server Error" response and
include a SOAP message in the response containing a SOAP Fault element
(see section 4.4) indicating the SOAP processing error.
If you do not own the web service, they should fix this.
To be honest I am not sure what the work arround would be in Spring-WS.
If I really needed a work arround in Jax-Ws I would replace the stub call with a Dispatcher to handle the raw xml myself and avoid the automatic marshal/demarhal.
Look into Spring-Ws if you can do the same, but this is a bug of the web service, not your client

Categories