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
On a closely related note, for those unit testing their concrete ResourceAssemblerSupport
implementations, and experiencing the same stack trace, here's how to mock it out:
@Before
public void setup() {
HttpServletRequest mockRequest = new MockHttpServletRequest();
ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(mockRequest);
RequestContextHolder.setRequestAttributes(servletRequestAttributes);
}
@After
public void teardown() {
RequestContextHolder.resetRequestAttributes();
}
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.
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)
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
.
In CreateCallable
, call createAndSaveIfValidExtended
instead of createAndSaveIfValid
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With