Adrian Reyes Adrian Reyes - 7 months ago 45
Java Question

Spring Boot, java.lang.IllegalStateException when calling ControllerLinkBuilder.linkTo from a websocket

I'm getting the following error when calling

ControllerLinkBuilder.linkTo
from a websocket.

java.lang.IllegalStateException: Could not find current request via RequestContextHolder
at org.springframework.util.Assert.state(Assert.java:385)
at org.springframework.hateoas.mvc.ControllerLinkBuilder.getCurrentRequest(ControllerLinkBuilder.java:234)
at org.springframework.hateoas.mvc.ControllerLinkBuilder.getBuilder(ControllerLinkBuilder.java:186)
at org.springframework.hateoas.mvc.ControllerLinkBuilderFactory.linkTo(ControllerLinkBuilderFactory.java:117)
at org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo(ControllerLinkBuilder.java:135)
at urlshortener2014.common.web.UrlShortenerController.createAndSaveIfValid(UrlShortenerController.java:94)
at urlshortener2014.richcarmine.web.UrlShortenerControllerWithLogs.access$200(UrlShortenerControllerWithLogs.java:45)
at urlshortener2014.richcarmine.web.UrlShortenerControllerWithLogs$CreateCallable.call(UrlShortenerControllerWithLogs.java:226)
at urlshortener2014.richcarmine.massiveShortenerNaiveWS.ShortURLWSGenerator.onCall(ShortURLWSGenerator.java:41)
at urlshortener2014.richcarmine.massiveShortenerNaiveWS.ShortURLWSGenerator.onCall(ShortURLWSGenerator.java:15)
at urlshortener2014.richcarmine.massiveShortenerREST.RequestContextAwareCallable.call(RequestContextAwareCallable.java:26)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)


The whole project is about shortening urls, as my first approach with spring websockets I'm trying to get it work just by answering back the shortened url from any url.

My
TextWebSocketHandler


public class MyHandler extends TextWebSocketHandler {

AtomicLong messageOrder = new AtomicLong(0);
ExecutorService threadPool = Executors.newCachedThreadPool();
CompletionService<CSVContent> pool = new ExecutorCompletionService<>(threadPool);

/* controller reference */
@Autowired
UrlShortenerControllerWithLogs controller;

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
logger.info("WS: " + message.getPayload());
long order = messageOrder.getAndIncrement();
pool.submit(new ShortURLWSGenerator(order,message.getPayload(),"","","",session.getRemoteAddress(),controller));
CSVContent content = pool.take().get();
session.sendMessage(new TextMessage("Echo Test: " + content.getShortURL().getUri()));
}
}


Here is
ShortURLWSGenerator


public class ShortURLWSGenerator extends RequestContextAwareCallable<CSVContent>{
...
@Override
public CSVContent onCall() {

ShortURL shortURL = null;
try {
shortURL = controller. new CreateCallable(url,sponsor,brand,owner,address.toString()).call();
} catch (Exception e) {
e.printStackTrace();
}

CSVContent content = new CSVContent();
content.setOrder(order);
content.setShortURL(shortURL);

return content;
}
}


Used RequestContextAwareCallable which solved the same problem when implementing the same functionlity as a REST service, in any case I'm still getting the same error even with a simple Callable.

Here is CreateCallable that just wraps the main function

public class CreateCallable implements Callable<ShortURL>{
...
@Override
public ShortURL call() throws Exception {
/* explodes while creating the new short url */
return createAndSaveIfValid(url,sponsor,brand,owner,ip);
}
}


Finally here is
createAndSaveIfValid
which calls
ControllerLinkBuilder.linkTo


protected ShortURL createAndSaveIfValid(String url, String sponsor,
String brand, String owner, String ip) {
UrlValidator urlValidator = new UrlValidator(new String[] { "http",
"https" });
if (urlValidator.isValid(url)) {
String id = Hashing.murmur3_32()
.hashString(url, StandardCharsets.UTF_8).toString();
ShortURL su = new ShortURL(id, url,
linkTo(
methodOn(UrlShortenerController.class).redirectTo(
id, null)).toUri(), sponsor, new Date(
System.currentTimeMillis()), owner,
HttpStatus.TEMPORARY_REDIRECT.value(), true, ip, null);
return shortURLRepository.save(su);
} else {
return null;
}
}


The complete project can be found here on github

Answer

linkTo depends on the current HTTP request but the HTTP current request does not exist because the call was originated by a WebSocket event. Therefore you need a different approach.

  1. Create a method named e.g. createAndSaveIfValidExtended based on createAndSaveIfValid. The code is the same but linkTo(methodOn(UrlShortenerController.class).redirectTo(id, null)).toUri() is replaced by the method createLink(id)

  2. Create a method String createLink(String id). This method will build the URL by using a property defined in application.properties (see here how) whose value will be injected in a field that represents where the app will be deployed concatenated with /l and the value of ìd.

  3. In CreateCallable, call createAndSaveIfValidExtended instead of createAndSaveIfValid

Comments