2017-03-20 2 views
-1

Ich arbeite an einer unordentlichen Struts 1-Anwendung, die eine benutzerdefinierte Kontextklasse verwendet, um Werte in der gesamten Anwendung zu speichern. Im Grunde wird es nur zum Speichern von Sitzungsbereichsvariablen verwendet. Ich vermute, der Grund, dass diese benutzerdefinierte Klasse verwendet wird, ist, dass andere Klassen, die keinen Zugriff auf die HTTP-Sitzung haben, die Sitzungsvariablen abrufen und festlegen können.ThreadLocale-Wert vermischt sich in Servlet-Filter

Wie auch immer, das funktioniert in den meisten Fällen gut. Der benutzerdefinierte Kontext wird in allen Aktionen und Serviceklassen verwendet, um Variablen problemlos zu teilen. Wie auch immer, ich habe gerade herausgefunden, dass es mit diesem benutzerdefinierten Kontext in einem HTTP-Filter nicht so gut funktioniert! Es scheint, dass es zufällig den Wert von einer anderen Sitzung zieht. Und nach der Sitzung meine ich eigentlich thread, da dieser benutzerdefinierte Kontext ThreadLocale verwendet, um seine schmutzige Arbeit zu erledigen.

Werfen Sie einen Blick

package com.zero.alpha.common; 

import java.io.Serializable; 
import java.util.Hashtable; 
import java.util.Locale; 
import java.util.Map; 

public final class CustomContext implements Serializable { 
    private static final long serialVersionUID = 400312938676062620L; 
    private static ThreadLocal<CustomContext> local = new ThreadLocal() { 
     protected CustomContext initialValue() { 
      return new CustomContext("0", "0", Locale.getDefault()); 
     } 
    }; 
    private String dscId; 
    private String sessionId; 
    private Locale locale; 
    private Map<String, Serializable> generalArea; 

    public CustomContext(String dscId, String sessionId, Locale locale) { 
     this.dscId = dscId; 
     this.sessionId = sessionId; 

     if (locale != null) { 
      this.locale = locale; 
     } else { 
      this.locale = Locale.getDefault(); 
     } 
     this.generalArea = new Hashtable(); 
    } 

    public static CustomContext get() { 
     return ((CustomContext) local.get()); 
    } 

    public static void set(CustomContext context) { 
     local.set(context); 
    } 

    public String getDscId() { 
     return this.dscId; 
    } 

    public String getSessionId() { 
     return this.sessionId; 
    } 

    public Locale getLocale() { 
     return this.locale; 
    } 

    public Serializable getGeneralArea(String key) { 
     return ((Serializable) this.generalArea.get(key)); 
    } 

    public Serializable putGeneralArea(String key, Serializable value) { 
     return ((Serializable) this.generalArea.put(key, value)); 
    } 

    public void clearGeneralArea() { 
     this.generalArea.clear(); 
    } 

    public Serializable removeGeneralArea(String key) { 
     return ((Serializable) this.generalArea.remove(key)); 
    } 
} 

Wieder scheint dies nur schön und gut innerhalb jeder anderen Klasse anders als ein Filter zu arbeiten. Lass mich dir den Filter zeigen, wo er versagt.

package com.zero.alpha.myapp.common.filter; 

import java.io.IOException; 

import javax.servlet.Filter; 
import javax.servlet.FilterChain; 
import javax.servlet.FilterConfig; 
import javax.servlet.ServletException; 
import javax.servlet.ServletRequest; 
import javax.servlet.ServletResponse; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import javax.servlet.http.HttpSession; 

import com.zero.alpha.common.CustomContext; 
import com.zero.alpha.myapp.utility.CommonConstants; 
import com.zero.alpha.myapp.utility.CommonHelpers; 
import com.zero.alpha.myapp.UserDomain; 


public class LoginFilter implements Filter { 

    public LoginFilter() { 
    } 


    public void init(FilterConfig config) throws ServletException {} 


    public void destroy() {} 

    public void doFilter(ServletRequest request, ServletResponse response, 
      FilterChain chain) throws IOException, ServletException { 

     HttpServletRequest req = (HttpServletRequest) request; 

     // Don't use the login filter during a login or logout request 
     if (req.getServletPath().equals("/login.do") 
      || req.getServletPath().equals("/login-submit.do") 
      || req.getServletPath().equals("/logout.do")) { 
      chain.doFilter(request, response); 

     } else { 
      doFilter(req, (HttpServletResponse) response, chain); 
     } 
    } 

    protected void doFilter(HttpServletRequest request, HttpServletResponse response, 
      FilterChain chain) throws IOException, ServletException { 

     HttpSession session = request.getSession(false); 

     // This is the problem right here. Sometimes this will grab the value of a different user currently logged in 
     UserDomain user = (UserDomain) CustomContext.get() 
      .getGeneralArea(CommonConstants.ContextKey.USER_SESSION); 

     if (session == null || user == null) { 
      // Unauthorized 
      response.sendRedirect(loginPage); 
     } else { 
      // Authorized 
      session.setAttribute("userInfo", CommonHelpers.getUserDisplay(user)); 
      chain.doFilter(request, response); 
     } 
    } 
} 

Wenn der benutzerdefinierte Kontext den Benutzer in dem doFilter Verfahren zu greifen verwendet wird, es wird das Benutzerobjekt von einem anderen angemeldeten Benutzern zufällig greifen. Offensichtlich keine gute Situation!

Die einzige Zeit, die dies passiert, ist nach einigen Aktivitäten von einem anderen angemeldeten Benutzer. Ich könnte den ganzen Tag dort sitzen und die Sitzung von Benutzer A auffrischen und es würde kein Problem geben. Nachdem Sie jedoch eine Aktion als Benutzer B ausgeführt und dann die Sitzung von Benutzer A erneut aktualisiert haben, wird sie normalerweise ausgetauscht. Aber wenn ich die Sitzung von Benutzer A erneut aktualisiere, sind die Dinge wieder normal.

Ich habe bemerkt, dass dies extrem häufiger passiert, wenn die Anwendung tatsächlich auf einem Remote-Entwicklungs-Tomcat-Server bereitgestellt wird. Es passiert immer noch lokal, aber nicht annähernd so oft wie bei der Remote-Bereitstellung. Es passiert fast 100% der Zeit aus der Ferne.

Ich habe die Sitzungsvariable innerhalb des Filters untersucht, und es scheint kein Problem mit der Sitzung selbst zu sein. Ich habe bestätigt, dass die Sitzungs-ID auch dann noch korrekt ist, wenn der falsche Benutzer aus der ThreadLocale-Variable gezogen wird.

Kann mir jemand helfen? Vielen Dank.

+1

Sind Sie sicher, dass Aktionen von Benutzer b nicht auf die benutzerdefinierte Kontextinstanz zugreifen? – efekctive

+0

@efekctive Die Aktionen von Benutzer B verwenden den gleichen benutzerdefinierten Kontext wie Benutzer A auf genau dieselbe Weise. – zero01alpha

Antwort

2

Ihre Strategie ist irreparabel fehlerhaft. Sofern Ihre Servlet-Engine nicht single-threaded ist, können Sie sich nicht darauf verlassen, dass der gleiche Thread jede Anfrage in einer bestimmten Sitzung behandelt. Darüber hinaus und auch für das angegebene Problem relevanter, ist es nicht sicher anzunehmen, dass ein Thread, der eine Anfrage in einer bestimmten Sitzung behandelt, auch die nächste Anfrage in dieser Sitzung behandelt, nicht sicher ist, dass eine Anfrage nicht bearbeitet wird eine andere Sitzung dazwischen. Es gibt keine Garantie für Thread/Session-Bindung.

Wie auch immer, Sie scheinen das Offensichtliche zu vermissen. Wenn Sie Variablen für den Sitzungsbereich speichern möchten, speichern Sie sie in der Sitzung . Dafür gibt es Sitzungsattribute. Siehe HttpSession.setAttribute() und HttpSession.getAttribute() - Sie können diese verwenden, um Ihr Kontextobjekt festzulegen und abzurufen.

+0

Danke. Haben Sie als Nachfolger eine Ahnung, warum dies nur innerhalb der Filtermethoden geschieht? – zero01alpha

+1

@ zero01alpha, ich neige dazu zu vermuten, dass du es nur in deinen Filtermethoden * siehst *.Vielleicht liegt das daran, dass Ihre Filter, die bei jeder Anfrage zuerst gecrackt werden, irgendeine Art von Aktion ausführen, die das Problem von anderen Klassen verbirgt. Obwohl keine Thread-/Sitzungsbindung besteht, gibt es * Anfrage/Sitzungsbindung für die Dauer der Behandlung jeder Anfrage. –

+1

@ zero01alpha, könnten Sie auch überlegen, ob Sie einen Teil der Arbeit, die Sie jetzt in einem Filter erledigen, in einem [SessionListener] (http://docs.oracle.com/javaee/6/api/javax/servlet/) http/HttpSessionListener.html) statt. Dies könnte zum Beispiel ein praktischer Ort sein, um zunächst Ihr Kontextobjekt zu erstellen und es an eine neue Sitzung zu binden, sodass Ihre Filter sich nicht darum kümmern müssen, ob sie damit umgehen müssen. –

Verwandte Themen