我正在Google App Engine上开发一个简单的servlet / JSP,数据驱动的网站。我已经开始在我的一些数据输入表单上使用JSTL fmt库,并在使用标记<fmt:dateFormat> and <fmt:numberFormat>:
时获得以下与会话相关的错误
[java] java.lang.RuntimeException:appengine-web.xml中未启用会话支持。要启用会话,请在该文件中输入true。没有它,getSession()是允许的,但是sessionattributes的操作不是。
我的JSP中的代码例如是:
<c:forEach var="item" items="${dayList}" >
<option><fmt:formatNumber value="${item}" pattern="00"/></option>
</c:forEach>
或
<jsp:useBean id="now" scope="page" class="java.util.Date" />
Now: ${now}<br/>
Year: <fmt:formatDate value="${now}" pattern="yyyy" />
当我对这些行进行注释时,页面呈现,当重新打开时,会出现错误结果。
我没有启用会话,因为我不需要它们用于此Web应用程序,并且读取最好不要,如果您不需要。我已经确认没有属性是会话范围的,我的NetBeans项目中唯一出现的“session”是在web.xml中:
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
一旦我在appengine-web.xml上启用了会话,一切正常。
我的研究指出了您可以在web.xml中设置的一些与语言环境相关的上下文参数,即:
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.fallbackLocale</param-name>
<param-value>en-US</param-value>
</context-param>
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.locale</param-name>
<param-value>en-US</param-value>
</context-param>
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.timeZone</param-name>
<param-value>EDT</param-value>
</context-param>
我已经使用这些参数设置重新部署(并关闭会话)并获得相同的错误。
JSTL fmt库似乎非常方便用于日期和数字等网页表单,尽管在一天结束时我可能需要启用会话并继续前进 - 但是我在这里遗漏了一些关于导致会话被默认引用的语言环境设置的内容?这可能是App Engine特有的吗?
由于
P.S。 这是禁用会话时获得的堆栈跟踪:
[java] Aug 17, 2010 2:41:26 AM com.google.apphosting.utils.jetty.JettyLogger warn
[java] WARNING: /manage/events/new
[java] java.lang.RuntimeException: Session support is not enabled in appengine-web.xml. To enable sessions, put <sessions-enabled>true</sessions-enabled> in that file. Without it, getSession() is allowed, but manipulation of sessionattributes is not.
[java] at com.google.apphosting.utils.jetty.StubSessionManager$StubSession.throwException(StubSessionManager.java:67)
[java] at com.google.apphosting.utils.jetty.StubSessionManager$StubSession.setAttribute(StubSessionManager.java:63)
[java] at org.apache.jasper.runtime.PageContextImpl.doSetAttribute(PageContextImpl.java:340)
[java] at org.apache.jasper.runtime.PageContextImpl.access$300(PageContextImpl.java:64)
[java] at org.apache.jasper.runtime.PageContextImpl$4.run(PageContextImpl.java:314)
[java] at java.security.AccessController.doPrivileged(Native Method)
[java] at org.apache.jasper.runtime.PageContextImpl.setAttribute(PageContextImpl.java:312)
[java] at org.apache.taglibs.standard.tag.common.fmt.SetLocaleSupport.setResponseLocale(SetLocaleSupport.java:209)
[java] at org.apache.taglibs.standard.tag.common.fmt.SetLocaleSupport.doEndTag(SetLocaleSupport.java:108)
[java] at org.apache.jsp.WEB_002dINF.update_005fevent_jsp._jspx_meth_fmt_setLocale_0(update_005fevent_jsp.java:362)
[java] at org.apache.jsp.WEB_002dINF.update_005fevent_jsp._jspService(update_005fevent_jsp.java:117)
[java] at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:94)
[java] at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
[java] at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:324)
[java] at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:292)
[java] at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:236)
[java] at com.google.appengine.tools.development.PrivilegedJspServlet.access$101(PrivilegedJspServlet.java:23)
[java] at com.google.appengine.tools.development.PrivilegedJspServlet$2.run(PrivilegedJspServlet.java:59)
[java] at java.security.AccessController.doPrivileged(Native Method)
[java] at com.google.appengine.tools.development.PrivilegedJspServlet.service(PrivilegedJspServlet.java:57)
[java] at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
[java] at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
[java] at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
[java] at com.cj.trim.trimFilter.doFilter(Unknown Source)
[java] at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
[java] at com.queerartfilm.web.JSTLConfigFilter.doFilter(JSTLConfigFilter.java:114)
[java] at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
[java] at com.google.appengine.api.blobstore.dev.ServeBlobFilter.doFilter(ServeBlobFilter.java:51)
[java] at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
[java] at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
[java] at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
[java] at com.google.appengine.tools.development.StaticFileFilter.doFilter(StaticFileFilter.java:122)
[java] at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
[java] at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
[java] at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
[java] at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
[java] at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
[java] at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
[java] at com.google.apphosting.utils.jetty.DevAppEngineWebAppContext.handle(DevAppEngineWebAppContext.java:70)
[java] at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
[java] at com.google.appengine.tools.development.JettyContainerService$ApiProxyHandler.handle(JettyContainerService.java:349)
[java] at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
[java] at org.mortbay.jetty.Server.handle(Server.java:326)
[java] at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
[java] at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923)
[java] at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:547)
[java] at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
[java] at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
[java] at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
[java] at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
答案 0 :(得分:2)
这非常不幸。看起来你不能绕过这个。那么你最好的办法就是创建自定义EL功能。以下是如何替换fmt:formatDate
的示例。
首先创建一个EL函数类(只是一个简单的静态类):
package com.example;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class Functions {
private Functions() {
//
}
public static String formatDate(Date date, String pattern) {
return new SimpleDateFormat(pattern).format(date);
}
}
然后创建一个/WEB-INF/functions.tld
(注意:JSP 2.1有针对性,不确定你的GAE支持什么,它可能是Servlet 2.4,如果是这样,那么你需要将它重新声明为JSP 2.0 taglib):
<?xml version="1.0" encoding="UTF-8" ?>
<taglib
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<display-name>Custom Functions</display-name>
<tlib-version>1.0</tlib-version>
<uri>http://example.com/functions</uri>
<function>
<name>formatDate</name>
<function-class>com.example.Functions</function-class>
<function-signature>java.lang.String formatDate(java.util.Date, java.lang.String)</function-signature>
</function>
</taglib>
然后您可以在JSP中使用它:
<%@taglib uri="http://example.com/functions" prefix="f" %>
<jsp:useBean id="date" class="java.util.Date" />
...
<p>Current year: ${f:formatDate(date, 'yyyy')}</p>
答案 1 :(得分:2)
我最近遇到了同样的问题,其中JSTL代码坚持设置会话范围属性以跟踪字符编码。因为我希望我的应用程序扩展到大量并发用户,所以我完全避免了servlet会话的创建。
我做的第一件事是安装一个监听器来记录会话创建/销毁和会话属性添加/删除。浏览了我网站上的所有页面后,我确认会话访问仅限于JSTL javax.servlet.jsp.jstl.fmt.request.charset
属性。
知道我只需要处理一个非常具体的案例,我编写了一个包装HttpServletRequest的过滤器,并覆盖了getSession()方法以返回我自己的HttpSession接口实现。会话实现是过滤器的实例变量,并使用来自过滤器配置的init-params来“支持”一组固定的名称/值对,这些名称/值对将被允许作为伪会话范围属性。除了在过滤器中配置的会话属性之外,任何设置会话属性的尝试都将导致异常。
我希望这对任何面临抑制会话创建和使用JSTL标记的竞争目标的人都有帮助。
这是StatelessRequestFilter类:
package com.your.company;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import org.apache.log4j.Logger;
public class StatelessRequestFilter implements Filter
{
private FilterConfig config;
public void init( FilterConfig config )
throws ServletException
{
if( logger().isDebugEnabled() )
{
logger().debug( "init() called" );
}
this.config = config;
if( logger().isInfoEnabled() )
{
// Look for any configuration name/value pairs. These will be considered valid
// pseudo session attributes. If a set operation tries to set one of these
// properties, it will be ignored -- as opposed to throwing an exception
Enumeration<String> initParamNames = config.getInitParameterNames();
while( initParamNames.hasMoreElements() )
{
logger().info( "init(): attributes defined in the filter configuration..." );
String paramName = initParamNames.nextElement();
String paramValue = config.getInitParameter( paramName );
logger().info( "init(): " + paramName + " = " + paramValue );
}
}
}
/**
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain )
throws IOException, ServletException
{
if( logger().isInfoEnabled() )
{
logger().info( "doFilter(): wrapping request with StatelessHttpRequestWrapper" );
}
ServletRequest weDontNeedNoStinkinSessionsRequest = new StatelessHttpRequestWrapper(
(HttpServletRequest) request );
chain.doFilter( weDontNeedNoStinkinSessionsRequest, response );
}
public void destroy()
{
if( logger().isDebugEnabled() )
{
logger().debug( "destroy() called" );
}
}
public class StatelessHttpRequestWrapper extends HttpServletRequestWrapper
{
public StatelessHttpRequestWrapper( HttpServletRequest request )
{
super( request );
}
/**
* @see javax.servlet.http.HttpServletRequestWrapper#getSession()
*
* @return
*/
@Override
public HttpSession getSession()
{
return statelessSession;
}
/**
* @see javax.servlet.http.HttpServletRequestWrapper#getSession(boolean)
*
* @param create
* @return
*/
@Override
public HttpSession getSession( boolean create )
{
if( create == true )
{
logger()
.warn(
"getSession( boolean ): Calling this method with 'true' will NOT create a new HttpSession!" );
}
return statelessSession;
}
}
// This object will be used by all threads going through this filter
private final PseudoHttpSession statelessSession = new PseudoHttpSession();
public class PseudoHttpSession implements HttpSession
{
public Object getAttribute( String arg0 )
{
if( logger().isDebugEnabled() )
{
logger().debug( "getAttribute(): '" + arg0 + "'" );
}
return config.getInitParameter( arg0 );
}
public Enumeration<String> getAttributeNames()
{
if( logger().isDebugEnabled() )
{
logger().debug( "getAttributeNames()" );
}
return config.getInitParameterNames();
}
public long getCreationTime()
{
if( logger().isDebugEnabled() )
{
logger().debug( "getCreationTime()" );
}
return 0;
}
public String getId()
{
if( logger().isDebugEnabled() )
{
logger().debug( "getId()" );
}
return null;
}
public long getLastAccessedTime()
{
if( logger().isDebugEnabled() )
{
logger().debug( "getLastAccessedTime()" );
}
return 0;
}
public int getMaxInactiveInterval()
{
if( logger().isDebugEnabled() )
{
logger().debug( "getMaxInactiveInterval()" );
}
return 0;
}
public ServletContext getServletContext()
{
if( logger().isDebugEnabled() )
{
logger().debug( "getServletContext()" );
}
return null;
}
public HttpSessionContext getSessionContext()
{
if( logger().isDebugEnabled() )
{
logger().debug( "getSessionContext()" );
}
return null;
}
public Object getValue( String arg0 )
{
if( logger().isDebugEnabled() )
{
logger().debug( "getValue(): '" + arg0 + "'" );
}
return null;
}
public String[] getValueNames()
{
if( logger().isDebugEnabled() )
{
logger().debug( "getValueNames()" );
}
return null;
}
public void invalidate()
{
logger().warn( "invalidate(): ignoring request to invalidate session!" );
}
public boolean isNew()
{
if( logger().isDebugEnabled() )
{
logger().debug( "isNew()" );
}
return false;
}
public void putValue( String arg0, Object arg1 )
{
if( logger().isDebugEnabled() )
{
logger().debug( "putValue(): '" + arg0 + "'" + ": " + arg1 );
}
String configAttribute = config.getInitParameter( arg0 );
if( configAttribute == null )
{
logger().error( "putValue(): unconfigured attribute: " + arg0 );
throw new UnsupportedOperationException(
"PseudoHttpSession cannot be used to store state, hence the 'pseudo' part of the name" );
}
}
public void setAttribute( String arg0, Object arg1 )
{
if( logger().isDebugEnabled() )
{
logger().debug( "setAttribute(): '" + arg0 + "'" + ": " + arg1 );
}
String configAttribute = config.getInitParameter( arg0 );
if( configAttribute == null )
{
logger().error( "setAttribute(): unconfigured attribute: " + arg0 );
throw new UnsupportedOperationException(
"PseudoHttpSession cannot be used to store state, hence the 'pseudo' part of the name" );
}
}
public void removeAttribute( String arg0 )
{
logger().warn( "removeAttribute(): '" + arg0 + "' ignored!" );
}
public void removeValue( String arg0 )
{
logger().warn( "removeValue(): '" + arg0 + "' ignored!" );
}
public void setMaxInactiveInterval( int arg0 )
{
if( logger().isDebugEnabled() )
{
logger().debug( "setMaxInactiveInterval(): '" + arg0 + "'" );
}
logger().warn( "setMaxInactiveInterval(): call ignored!" );
}
}
private Logger logger()
{
return Logger.getLogger( this.getClass() );
}
}
以下是在web.xml中配置的方式:
<!-- The 'WeDontNeedNoStinkinSessions' filter will wrap the incoming HTTP request to
override the getSession() methods to return a 'PseudoHttpSession'. This object
prevents creation of servlet sessions. The request is only wrapped if the
HttpServletRequest.getUserPrincipal() method returns 'null'. -->
<filter>
<filter-name>WeDontNeedNoStinkinSessionsFilter</filter-name>
<filter-class>com.sherwin.sd.web.filter.StatelessRequestFilter</filter-class>
<!--
PseudoHttpSession with treat any 'init-param' name/value pairs as legal session-scope
attributes. If a session attempts to set an attribute whose name matches an init-param
below, the setAttribute() call is ignored, as opposed to raising an exception. When
getAttribute() is called, the param-value is returned.
For example, when in contribution mode, something attempts to save a character
encoding to the session. Since this value is always the same (and to the best
of our knowledge never used!), it is reasonable to treat this as a static, configurable
property.
-->
<init-param>
<param-name>javax.servlet.jsp.jstl.fmt.request.charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<!-- The 'WeDontNeedNoStinkinSessions' is applied to all FORWARDs. -->
<filter-mapping>
<filter-name>WeDontNeedNoStinkinSessionsFilter</filter-name>
<url-pattern>/whatever/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
答案 2 :(得分:1)
在等待查看完整堆栈跟踪显示的内容时,这是另一个选项。这个filter将在web.xml中查找JSTL配置参数并将它们设置为请求属性,希望这可以缩短GAE访问会话的任何需求:
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.jsp.jstl.core.Config;
public class JSTLConfigFilter implements Filter {
private static final String[] JSTL_CONFIG_PARAMS = { Config.FMT_FALLBACK_LOCALE,
Config.FMT_LOCALE,
Config.FMT_LOCALIZATION_CONTEXT,
Config.FMT_TIME_ZONE,
Config.SQL_DATA_SOURCE,
Config.SQL_MAX_ROWS };
private final Map<String, String> jstlConfig = new ConcurrentHashMap<String, String>();
public void init(FilterConfig config) throws ServletException {
ServletContext ctx = config.getServletContext();
for (String param : JSTL_CONFIG_PARAMS) {
String value = ctx.getInitParameter(param);
if (value != null) {
this.jstlConfig.put(param, value);
}
}
}
public void destroy() {
this.jstlConfig.clear();
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
for (Map.Entry<String, String> entry : this.jstlConfig.entrySet()) {
Config.set(request, entry.getKey(), entry.getValue());
}
chain.doFilter(request, response);
}
}
当然,变化是可能的,但希望你能得到这个想法......
答案 3 :(得分:1)
代码导致问题出在fmt lib中,SetLocaleSupport类是
static void setResponseLocale(PageContext pc, Locale locale) {
.../...
// get response character encoding and store it in session attribute
if (pc.getSession() != null) {
try {
pc.setAttribute(RequestEncodingSupport.REQUEST_CHAR_SET,
response.getCharacterEncoding(),
PageContext.SESSION_SCOPE);
} catch (IllegalStateException ex) {} // invalidated session ignored
}
}
它无情地把charset放在了会议中。你做不了多少......
答案 4 :(得分:1)
我使用此页面指令解决了它:
<%@ page session="false" %>