Библиотека JSTL fmt выдает ошибку 500, сеансы включены? - PullRequest
3 голосов
/ 13 августа 2010

Я разрабатываю простой сервлет / JSP, управляемый данными веб-сайт в Google App Engine. Я начал использовать библиотеку JSTL fmt в некоторых моих формах ввода данных и получаю следующую ошибку, связанную с сеансом, при использовании тегов <fmt:dateFormat> and <fmt:numberFormat>:

[java] java.lang.RuntimeException: поддержка сеанса не включена в appengine-web.xml. Чтобы включить сеансы, укажите в этом файле значение true. Без этого getSession () разрешен, но манипулирование сессионными атрибутами запрещено.

Код в моем 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" />

Когда я закомментирую эти строки, при повторном включении страницы отображается ошибка.

У меня нет включенных сессий, потому что они мне не нужны для этого веб-приложения, и я прочел, что лучше этого не делать, если вам не нужно. Я подтвердил, что никакие атрибуты не относятся к области сеанса, единственное возникновение «сеанса» в моем проекте NetBeans - в файле 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)

Ответы [ 5 ]

2 голосов
/ 30 декабря 2011

Недавно я столкнулся с той же проблемой, когда код JSTL настаивает на установке атрибута сеанса для отслеживания кодировки символов.Поскольку я хочу, чтобы мое приложение масштабировалось до большого числа одновременно работающих пользователей, я полностью избегаю создания сеанса сервлета.

Первое, что я сделал, - установил прослушиватель для регистрации / уничтожения сеанса и добавления / удаления атрибута сеанса.,После просмотра всех страниц моего сайта я убедился, что доступ к сеансу ограничен атрибутом 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 голосов
/ 13 августа 2010

Это очень неудачно. Похоже, вы не можете обойти это. Лучше всего тогда создавать пользовательские функции 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, если так, то вам нужно переопределить его как taglib JSP 2.0):

<?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 голос
/ 22 сентября 2013

Я решил это, используя эту директиву страницы:

<%@ page session="false" %> 
1 голос
/ 18 августа 2010

Код, вызывающий проблему в 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
}
}

вслепую помещает кодировку в сеанс.Вы не можете сделать много ...

1 голос
/ 16 августа 2010

Во время ожидания, чтобы увидеть, что показывает полная трассировка стека, вот еще один вариант. Этот фильтр будет искать параметры конфигурации JSTL в web.xml и устанавливать их в качестве атрибутов запроса в надежде, что это приведет к короткому замыканию при необходимости доступа 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);
    }
}

Вариации, конечно, возможны, но, надеюсь, вы поняли идею ...

...