【问题标题】:Spring Boot, java.lang.IllegalStateException when calling ControllerLinkBuilder.linkTo from a websocketSpring Boot,从 websocket 调用 ControllerLinkBuilder.linkTo 时出现 java.lang.IllegalStateException
【发布时间】:2015-03-04 08:43:36
【问题描述】:

从 websocket 调用 ControllerLinkBuilder.linkTo 时出现以下错误。

 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)

整个项目都是关于缩短 url,作为我使用 spring websockets 的第一种方法,我试图通过从任何 url 回复缩短的 url 来使其工作。

我的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()));
    }
}

这里是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;
    }
}

使用 RequestContextAwareCallable 在实现与 REST 服务相同的功能时解决了相同的问题,无论如何,即使使用简单的 Callable,我仍然会遇到相同的错误。

这里是只包装主函数的 CreateCallable

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);
    }
}

最后是createAndSaveIfValid,它调用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;
    }
}

完整的项目可以在here on github找到

【问题讨论】:

    标签: java spring websocket spring-boot


    【解决方案1】:

    linkTo 依赖于当前的 HTTP 请求,但 HTTP 当前请求不存在,因为调用是由 WebSocket 事件发起的。因此,您需要一种不同的方法。

    1. 创建一个名为例如的方法createAndSaveIfValidExtended 基于 createAndSaveIfValid。代码相同,但linkTo(methodOn(UrlShortenerController.class).redirectTo(id, null)).toUri()createLink(id)方法替换

    2. 创建一个方法String createLink(String id)。此方法将使用application.properties 中定义的属性(请参阅此处how)来构建 URL,该属性的值将被注入一个表示应用程序部署位置的字段中,该字段与/lìd 的值连接.

    3. CreateCallable 中,调用createAndSaveIfValidExtended 而不是createAndSaveIfValid

    【讨论】:

      【解决方案2】:

      在一个密切相关的注释中,对于那些对其具体的ResourceAssemblerSupport 实现进行单元测试并体验相同堆栈跟踪的人,以下是模拟它的方法:

      @Before
      public void setup() {
          HttpServletRequest mockRequest = new MockHttpServletRequest();
          ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(mockRequest);
          RequestContextHolder.setRequestAttributes(servletRequestAttributes);
      }
      
      @After
      public void teardown() {
          RequestContextHolder.resetRequestAttributes();
      }
      

      【讨论】:

      • 这可以简化为使用 MockHttpServletRequest,然后你就不必做所有的 Mockito 魔术了。 HttpServletRequest mockRequest = new MockHttpServletRequest(); ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(mockRequest); RequestContextHolder.setRequestAttributes(servletRequestAttributes);
      • 在测试完成后也会重置RequestContextHolder.resetRequestAttributes(); @AfterClass
      【解决方案3】:

      这是我们在 Spock 测试中最终使用的 Robert Bain 代码的更简单版本...

          HttpServletRequest httpServletRequestMock = new MockHttpServletRequest()
          ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(httpServletRequestMock)
          RequestContextHolder.setRequestAttributes(servletRequestAttributes)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-07-05
        • 2021-05-04
        • 1970-01-01
        • 1970-01-01
        • 2021-11-02
        • 2016-05-06
        • 2020-06-17
        • 2015-07-24
        相关资源
        最近更新 更多