我正在使用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开始使用。有没有办法实现这个目标,或者如果没有,我应该采用什么方法?
答案 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
方法中。可以访问会话变量。