Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 createAndSaveIfValidwhich 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

like image 331
Adrian Reyes Avatar asked Jan 06 '15 16:01

Adrian Reyes


2 Answers

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();
}
like image 105
Robert Bain Avatar answered Oct 26 '22 20:10

Robert Bain


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

like image 22
Francisco J. Lopez-Pellicer Avatar answered Oct 26 '22 21:10

Francisco J. Lopez-Pellicer