访问ServletContextListener中的会话变量

时间:2018-02-28 13:17:06

标签: java session grails websocket

我正在使用Grails实现上传功能,基本上用户可以上传文本文件,然后系统会将该文本文件的每一行保留为数据库记录。虽然上传工作正常,但较大的文件需要时间来处理,因此他们要求有一个进度条,以便用户可以确定他们的上传是否仍在处理或是否发生了实际错误。

为此,我所做的是创建两个网址:

  • /upload这是接收上传文本文件的实际网址。
  • /upload/status?uploadToken=根据uploadToken
  • 返回特定上传的状态

我所做的是在处理每一行之后,服务将更新会话级计数器变量:

// import ...

class UploadService {
    Map upload(CommonsMultipartFile record, GrailsParameterMap params) {
        Map response = [success: true]

        try {
            File file = new File(record.getOriginalFilename())
            FileUtils.writeByteArrayToFile(file, record.getBytes())
            HttpSession session = WebUtils.retrieveGrailsWebRequest().session
            List<String> lines = FileUtils.readLines(file, "UTF-8"), errors = []
            String uploadToken = params.uploadToken

            session.status.put(uploadToken,
                [message: "Checking content of the file of errors.",
                    size: lines.size(),
                    done: 0])

            lines.eachWithIndex { l, li ->
                // ... regex checking per line and appending any error to the errors List

                session.status.get(uploadToken).done++
            }

            if(errors.size() == 0) {
                session.status.put(uploadToken,
                    [message: "Persisting record to the database.",
                        size: lines.size(),
                        done: 0])

                lines.eachWithIndex { l, li ->
                    // ... Performs GORM manipulation here

                    session.status.get(uploadToken).done++
                }           
            }
            else {
                response.success = false
            }
        }
        catch(Exception e) {
            response.success = false
        }

        response << [errors: errors]

        return response
    }
}

然后创建一个连接到/upload/status?uploadToken=网址的simple WebSocket implementation。问题是我无法访问POGO上的会话变量。我甚至将POGO改为Grails服务,因为我认为这是问题的原因,但我仍然无法访问会话变量。

// import ...

@ServerEndpoint("/upload/status")
@WebListener
class UploadEndpointService implements ServletContextListener {    
    @OnOpen
    public void onOpen(Session userSession) { /* ... */ }

    @OnClose
    public void onClose(Session userSession, CloseReason closeReason) { /* ... */ }

    @OnError
    public void onError(Throwable t) { /* ... */ }

    @OnMessage
    public void onMessage(String token, Session userSession) {
        // Both of these cause IllegalStateException
        def session = WebUtils.retrieveGrailsWebRequest().session
        def session = RequestContextHolder.currentRequestAttributes().getSession()

        // This returns the session id but I don't know what to do with that information.
        String sessionId = userSession.getHttpSessionId() 

        // Sends the upload status through this line    
        sendMessage((session.get(token) as JSON).toString(), userSession)
    }

    private void sendMessage(String message, Session userSession = null) {
        Iterator<Session> iterator = users.iterator()
        while(iterator.hasNext()) {
            iterator.next().basicRemote.sendText(message)
        }
    }
}

相反,给我一个错误:

  

由IllegalStateException引起:未找到线程绑定请求:您是在引用实际Web请求之外的请求属性,还是在最初接收的线程之外处理请求?如果您实际在Web请求中运行并仍然收到此消息,则您的代码可能在DispatcherServlet / DispatcherPortlet之外运行:在这种情况下,    使用RequestContextListener或RequestContextFilter来公开当前请求。

我已经通过发送静态String内容来验证Web套件是否正常工作。但我想要的是能够获得该计数器并将其设置为发送消息。我使用Grails 2.4.4和Grails Spring Websocket插件虽然看起来很有希望,但只能从Grails 3开始使用。有没有办法实现这个目标,或者如果没有,我应该采用什么方法?

1 个答案:

答案 0 :(得分:0)

非常感谢the answer to this question帮助我解决了我的问题。

我刚刚将UploadEndpointService修改为与该答案上的POGO相同,而不是将其作为服务类,我将其还原为@Serverendpoint。我还配置了configurator注释并添加了onOpen()值。我还在import grails.converters.JSON import grails.util.Environment import javax.servlet.annotation.WebListener import javax.servlet.http.HttpSession import javax.servlet.ServletContext import javax.servlet.ServletContextEvent import javax.servlet.ServletContextListener import javax.websocket.CloseReason import javax.websocket.EndpointConfig import javax.websocket.OnClose import javax.websocket.OnError import javax.websocket.OnMessage import javax.websocket.OnOpen import javax.websocket.server.ServerContainer import javax.websocket.server.ServerEndpoint import javax.websocket.Session import org.apache.log4j.Logger import org.codehaus.groovy.grails.commons.GrailsApplication import org.codehaus.groovy.grails.web.json.JSONObject import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes import org.springframework.context.ApplicationContext @ServerEndpoint(value="/ep/maintenance/attendance-monitoring/upload/status", configurator=GetHttpSessionConfigurator.class) @WebListener class UploadEndpoint implements ServletContextListener { private static final Logger log = Logger.getLogger(UploadEndpoint.class) private Session wsSession private HttpSession httpSession @Override void contextInitialized(ServletContextEvent servletContextEvent) { ServletContext servletContext = servletContextEvent.servletContext ServerContainer serverContainer = servletContext.getAttribute("javax.websocket.server.ServerContainer") try { if (Environment.current == Environment.DEVELOPMENT) { serverContainer.addEndpoint(UploadEndpoint) } ApplicationContext ctx = (ApplicationContext) servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT) GrailsApplication grailsApplication = ctx.grailsApplication serverContainer.defaultMaxSessionIdleTimeout = grailsApplication.config.servlet.defaultMaxSessionIdleTimeout ?: 0 } catch (IOException e) { log.error(e.message, e) } } @Override void contextDestroyed(ServletContextEvent servletContextEvent) { } @OnOpen public void onOpen(Session userSession, EndpointConfig config) { this.wsSession = userSession this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName()) } @OnMessage public void onMessage(String message, Session userSession) { try { Map params = new JSONObject(message) if(httpSession.status == null) { params = [message: "Initializing file upload.", size: 0, token: 0] sendMessage((params as JSON).toString()) } else { sendMessage((httpSession.status.get(params.token) as JSON).toString()) } } catch(IllegalStateException e) { } } @OnClose public void onClose(Session userSession, CloseReason closeReason) { try { userSession.close() } catch(IllegalStateException e) { } } @OnError public void onError(Throwable t) { log.error(t.message, t) } private void sendMessage(String message, Session userSession=null) { wsSession.basicRemote.sendText(message) } } 方法中添加了第二个参数。这是编辑过的课程:

onOpen()

真正的魔法发生在toString方法中。可以访问会话变量。