2017-10-03 4 views
0

Bonjour, tout.Suivi de session de servlet - sécurité de thread de l'attribut de session

J'ai une servlet, dans laquelle j'ai des variables locales déclarées, initiées et assignées aux attributs de session. J'ai besoin que le servlet soit thread-safe, de sorte que plusieurs fenêtres de navigateur sur le même ordinateur puissent faire fonctionner la servlet et que vous puissiez effectuer des calculs dans chaque fenêtre, mais de telle sorte que les opérations dans les différents threads ne s'influencent pas mutuellement , c'est-à-dire que la variable de solde de compte n'a pas été partagée entre les threads, provoquant des états incohérents pour n'importe quel thread à tout moment.

Le dessous est mon code:

// Import servlet and HTTP functionality packages. 
import javax.servlet.*; 
import javax.servlet.http.*; 

// Import packages to: handle user inputs and outputs, enable usage of decimal formatting of numbers. 
import java.io.*; 
import java.util.*; 
import java.text.DecimalFormat; 

public class SessionBank extends HttpServlet // Define concrete class and extend HTTP functionality. 
{ 
    public void init() throws ServletException // Initialise variables at start of the servlet. Possible exception. 
    { 

    } 

    // The method to output the initial HTML form to the screen, addressing also possible exceptions. 
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    { 
     // Declare and initiate local variables. 
     double balance = 0;      // The current balance variable, un-formatted number, initiate to zero. 
     String formattedBal = "";    // The current balance variable, formatted to show as currency amount, initially blank. 

     // Set balance and formatted balance as session attributes. 
     request.getSession().setAttribute("balance", balance); 
     request.getSession().setAttribute("formattedBal", formattedBal); 

     showBalance(request, response); // Call custom-defined method to display initial page. 
    } 

    // Method to respond to user's button inputs - output relevant HTML back to the screen. Possible exceptions. 
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    { 
     HttpSession session = request.getSession(true);  // Establish session object. 

     response.setContentType("text/html");  // Set response type to text/HTML. 
     response.setHeader("Expires", "Tues, 01 Jan 1980 00:00:00 GMT"); // Set past date to forbid cache. 

     // If user clicks "Deposit" button: 
     if(request.getParameter("depButton") != null && request.getParameter("depButton").equals("Deposit")) 
     { 
      if(verifyAmount(request)) // If entered amount passes verification. 
      { 
       addToBalance(request, session);   // Add the amount to current balance. 
       redisplayCurrentPage(response);  // Redisplay initial page. 
      } 
      else  // If entered amount does not pass verification. 
      { 
       showErrorPage(response); // Display error page. 
      } 
     } 

     // If user clicks "Withdraw" button: 
     if(request.getParameter("withdrButton") != null && request.getParameter("withdrButton").equals("Withdraw")) 
     { 
      if(verifyAmount(request)) // If entered amount passes verification. 
      { 
       subtractFromBalance(request, session); // Subtract the amount from current balance. 
       redisplayCurrentPage(response);  // Redisplay initial page. 
      } 
      else  // If entered amount does not pass verification. 
      { 
       showErrorPage(response); // Display error page. 
      } 
     } 

     // If user clicks "Balance" button: 
     if(request.getParameter("balButton") != null && request.getParameter("balButton").equals("Balance")) 
     { 
      showBalance(request, response);  // Display current formatted balance on page. 
     } 
    } 

    private boolean verifyAmount(HttpServletRequest request) // Method to verify entered amount, based on textbook criteria. 
    { 
     boolean amountValid = false; // Declare and initiate a validity variable. 

     // If entered amount is not blank and is greater than zero, return validity as true. Else, return false. 
     if(request.getParameter("amount") != "" && Double.parseDouble(request.getParameter("amount")) > 0) 
      amountValid = true; 
     else 
      amountValid = false; 

     return amountValid;  // Return validity variable. 
    } 

    // Method to add amount to balance, addressing possible exception. 
    private void addToBalance(HttpServletRequest request, HttpSession session) throws IOException 
    { 
     double userAmount = Double.parseDouble(request.getParameter("amount")); // Declare and assign entered amount variable. 

     // Down-cast session attribute object to String, then parse into double type variable. 
     double balOld = Double.parseDouble(String.valueOf(session.getAttribute("balance"))); 

     double balNew = balOld + userAmount;  // Add the amount to current balance and save the value. 

     session.setAttribute("balance", balNew); // Assign new balance to the session attribute. 
    } 

    // Method to subtract from balance. Possible exception. 
    private void subtractFromBalance(HttpServletRequest request, HttpSession session) throws IOException 
    { 
     double userAmount = Double.parseDouble(request.getParameter("amount")); // Declare and assign entered amount value. 

     // Down-cast session attribute object to String, then parse into a double type variable. 
     double balOld = Double.parseDouble(String.valueOf(session.getAttribute("balance"))); 

     double balNew = balOld - userAmount;  // Subtract the amount from the balance and save the value. 

     session.setAttribute("balance", balNew); // Assign new balance value to the session attribute. 
    } 

    private void showBalance(HttpServletRequest request, HttpServletResponse response) throws IOException // Method to output balance HTML page. Possible exception. 
    { 
     PrintWriter out = response.getWriter();  // Establish HTML writer object. 

     formatBalance(request); // Format current balance for displaying. 

     out.println("<html>"); 
      out.println("<hr>");  // Horizontal line. 
      out.println("<title>Online Bank ATM Simulator</title>");  // Title to show on browser title bar. 
      out.println("<h1 align = \"center\">Bank ATM Simulation</h1>"); // Page heading, centered on page. 
      out.println("<body onLoad = \"amount.focus()\">");    // Set focus to the text-field. 
       out.println("<form method = \"POST\" action = \"../servlet/SessionBank\">"); // Form method and submission address. 
        out.println("<center>");  // Tag to center the following output on page. 
        out.println("Amount: "); 
        out.println("<input type = \"text\" name = \"amount\" id = \"amount\" size = \"20\"><br><br>"); // Amount text field. 
        out.println("Balance: "); 
        out.println(request.getSession().getAttribute("formattedBal") + "<br><br>"); // Current formatted balance shown. 
        out.println("<button name = \"balButton\" value = \"Balance\">Balance</button>"); // "Balance" button. 
        out.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"); // Spacers. 
        out.println("<button name = \"depButton\" value = \"Deposit\">Deposit</button>"); // "Deposit" button. 
        out.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"); // Spacers. 
        out.println("<button name = \"withdrButton\" value = \"Withdraw\">Withdraw</button>"); // "Withdraw" button. 
        out.println("</center>");  // Tag to end centering of output on page. 
       out.println("</form>");  // End of form. 
      out.println("</body>"); 
      out.println("<br>"); 
      out.println("<hr>");  // Horizontal line. 
     out.println("</html>"); 
    } 

    // Method to redisplay form after deposit/withdrawal. 
    private void redisplayCurrentPage(HttpServletResponse response) throws IOException 
    { 
     PrintWriter out = response.getWriter();  // Establish HTML writer object. 

     out.println("<html>"); 
      out.println("<hr>");  // Horizontal line. 
      out.println("<title>Online Bank ATM Simulator</title>");  // Title to show on browser title bar. 
      out.println("<h1 align = \"center\">Bank ATM Simulation</h1>"); // Page heading, centered on page. 
      out.println("<body onLoad = \"amount.focus()\">");    // Set focus to the text-field. 
       out.println("<form method = \"POST\" action = \"../servlet/SessionBank\">"); // Form method and submission address. 
        out.println("<center>");  // Tag to center the following output on page. 
        out.println("Amount: "); 
        out.println("<input type = \"text\" name = \"amount\" id = \"amount\" size = \"20\"><br><br>"); // Amount text field. 
        out.println("Balance: "); 
        out.println("<br><br>"); // No formatted balance value shown. 
        out.println("<button name = \"balButton\" value = \"Balance\">Balance</button>"); // "Balance" button. 
        out.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"); // Spacers. 
        out.println("<button name = \"depButton\" value = \"Deposit\">Deposit</button>"); // "Deposit" button. 
        out.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"); // Spacers. 
        out.println("<button name = \"withdrButton\" value = \"Withdraw\">Withdraw</button>"); // "Withdraw" button. 
        out.println("</center>");  // Tag to end centering of output on page. 
       out.println("</form>");  // End of form. 
      out.println("</body>"); 
      out.println("<br>"); 
      out.println("<hr>");  // Horizontal line. 
     out.println("</html>"); 
    } 

    private void formatBalance(HttpServletRequest request) // Method to format the current balance number to a currency amount. 
    { 
     DecimalFormat dollars = new DecimalFormat("$###,###.###"); // Construct new decimal format. 

     // Down-cast session attribute to String, parse to double, then format using the above decimal format and save value. 
     String formattedBal = dollars.format(Double.parseDouble(String.valueOf(request.getSession().getAttribute("balance")))); 

     request.getSession().setAttribute("formattedBal", formattedBal); // Assign new formatted balance to session attribute. 
    } 

    // Method to output error HTML page, if entered amount does not pass verification. Possible exception. 
    private void showErrorPage(HttpServletResponse response) throws IOException 
    { 
     PrintWriter out = response.getWriter();  // Establish HTML writer object. 

     out.println("<html>"); 
      out.println("<head>"); 
       out.println("<title>Amount Input Error</title>"); // Title to show in browser title bar. 
      out.println("</head>"); 

      out.println("<body>"); 
       out.println("<h1>Error processing the input.</h1><br>"); // Heading text. 
       out.println("Please ensure your input:<br><br>"); 
       out.println("- Is not blank.<br>"); 
       out.println("- Is strictly a number.<br>"); 
       out.println("- Is a positive, non-zero amount."); 
      out.println("</body>"); 
     out.println("</html>"); 
    } 

    public void destroy() // Method to terminate the servlet. 
    { 

    } 
} 

Maintenant, ce qui se passe au lieu est, lors de l'utilisation de plusieurs threads (fenêtres de navigateur exécutant le servlet), le solde qu'un fil remplace, devient l'équilibre qu'un autre thread lit . Ce qui est bien sûr incorrect. J'ai besoin de chaque thread simultané pour avoir sa propre copie de cette variable pour des résultats cohérents. Pour autant que je sache, contrairement au stockage de la balance en tant que variable de classe (instance), le stocker en tant qu'attribut local et en l'assignant à un attribut de session est thread-safe. Mais si c'est le cas, pourquoi les threads mettent-ils à jour la variable de l'autre? Qu'est-ce qui est codé incorrectement?

Merci beaucoup!

Répondre

0

Les navigateurs peuvent être un peu incompatibles avec cela, mais finalement ce que vous voyez est qu'un seul navigateur interagit avec votre serveur avec un seul cookie de session (généralement appelé JSESSIONID). Du point de vue du serveur et de votre code, il ne sait pas qu'il y a plusieurs onglets ou navigateurs à cause du cookie unique.

Dans ce cas, un thread accède à la valeur de la session, la met à jour et la stocke. Un autre thread - traitant un onglet de navigateur différent - fait la même chose. Mais il n'y a qu'une seule session et une seule variable dans la session afin qu'elle soit finalement partagée entre les onglets du navigateur.

Résoudre ceci peut être un peu un défi d'une manière multiplateforme. Une façon est d'avoir du code JavaScript dans le navigateur pour générer un numéro ou un code unique au démarrage. Ainsi, à titre d'exemple, vous créez une méthode onload dans votre code HTML et utilisez un simple appel JavaScript Math.random(). Ensuite, pour chaque demande, vous passez ce numéro à l'extrémité arrière. À l'arrière, vous créez une carte qui mappe le numéro généré par JavaScript sur une autre carte de paramètres et l'enregistre dans la session.

Votre code rend l'implémentation un peu plus difficile car vous disposez à la fois de l'affichage et de la logique dorsale (généralement un JSP pour l'affichage et le servlet pour le backend), mais cela pourrait être fait .

+0

stdunbar, merci beaucoup pour la réponse rapide et vous avez raison, le partage de session dans le même navigateur était le problème. Je l'ai testé en ouvrant le servlet dans Chrome et dans Internet Explorer, ce qui faisait que chacun fonctionnait indépendamment et avait sa propre variable «balance» non affectée. Je vais d'abord voir s'il existe un moyen de définir les paramètres de Chrome pour ne pas partager le cookie. Je ne suis pas sûr si je vais passer par la modification du code pour générer un ID descripteur aléatoire comme vous l'avez suggéré, puisqu'il s'agit d'une question d'attribution et le livre jusqu'à présent n'a jamais traité avec des extras comme celui-ci. –