화면 구성
1) 메인 화면
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>로그인</h3> <!--html 구성 화면 - 제목 -->
<!-- input 태그들의 값을 서버로 전송하기 위한 정보를 담고 있다. -->
<!-- action의 값으로는 요청하는 컴포넌트 이름 -->
<form name="formLogin" method = "post" action="/pro10/login100402">
<!-- 사용자가 입력한 아이디 값을 userId 매개 변수 이름으로 서버로 전송 -->
<input type="text" name = "userId" id = "userId" placeholder="아이디를 입력하세요.">
<!-- 사용자가 입력한 암호 값을 userPw 매개 변수 이름으로 서버로 전송 -->
<input type="password" name = "userPw" id="userPw" placeholder="암호를 입력하세요.">
<button type="submit">로그인</button>
</form>
</body>
</html>
: 메인 화면을 구성하는 html 소스
: 사용자가 웹 브라우저 URL 입력창에 URI 주소를 입력하고 보이는 첫 화면의 페이지
: action 속성에 작성된 것은, 서블릿에 정의된 매핑 이름이고 이 매핑 이름이 속한 서블릿 클래스로 이동한다.
2) 로그인 시 출력 화면
@WebServlet(name = "LoginTest100402", urlPatterns = { "/login100402" })
public class LoginTest extends HttpServlet {
private static final long serialVersionUID = 1L;
private ServletContext ctx = null;
//ServletContext : 하나의 서블릿이 서블릿 컨테이너와 통신하기 위해서 사용되어지는 메서드들을 가지고 있는 클래스
private List<String> userList = new ArrayList<>(); //로그인 아이디 저장, 목록 정보 가져오기
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//요청 처리
request.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=utf-8");
PrintWriter pw = response.getWriter();
String data =
"<!DOCTYPE html>"
+ "<html>"
+ "<head>"
+ "<meta charset=\"UTF-8\">"
+ "<title>Login Result</title>"
+ "</head>"
+ "<body>";
String userId = request.getParameter("userId");
String userPw = request.getParameter("userPw");
LoginImpl loginUser = null;
HttpSession mySession = null;
if(userId != null && userId.length() != 0) { //userId의 값이 0이 아닐 때 = 로그인 페이지를 거쳤을 때
loginUser = new LoginImpl(userId, userPw); ////현재 로그인 한 사용자 아이디와 암호를 저장한 HttpSessionListener 인터페이스의 LoginImpl 구현 객체를 생성합니다.
//HttpSession 객체 생성 또는 가져옴
mySession = request.getSession(); //요청 객체에서 session 객체 가져오기
ctx = getServletContext(); // ServletContext 객체 가져오기
if(mySession.isNew()) {//isNew : 새로 만든 객체인지 검증 (boolean 반환)
mySession.setAttribute("loginUser", loginUser);//바인딩 걸기
userList.add(userId); //userId 배열에 넣기
ctx.setAttribute("userList", userList); //setAttribute : 선택한 요소(element)의 속성(attribute) 값을 정합니다.
} else {// 새로고침만 할 경우 실행
System.out.println("총 접속자 수: " + LoginImpl.totalUser);
}
data
+= "접속 아이디 :" + loginUser.getUserId() + "<br>" //loginUser: private 이기 때문에 getter 사용
+ "현재 접속자 수 : " + LoginImpl.totalUser + "명 <br><br>" //정적 필드는 공유 메모리 영역에 있음 -> 클래스이름.필드이름 로 호출
+ "현재 접속자 목록<br>"
+ "=================<br>";
@SuppressWarnings("unchecked")
List<String> currentUserList = (ArrayList<String>)ctx.getAttribute("userList"); //오류 : Type safety: Unchecked cast from Object to ArrayList<String>
// 오류 : Type mismatch: cannot convert from Object to List<String>
//getAttribute 가 오브젝트 타입이므로 캐스팅 해야함
for(String _userId : currentUserList) { //이름은 배열에 있음 -> for문으로 꺼냄
System.out.println(_userId);
data += _userId + "<br>";
}
} else { //로그아웃 페이지를 거치지 않고, 서블릿 결과 화면을 새로고침
mySession = request.getSession();
loginUser = (LoginImpl)mySession.getAttribute("loginUser");
if(loginUser != null) {
ctx = getServletContext();
data
+="접속 아이디: " + loginUser.getUserId() +"<br>"
+ "현재 접속자 수: " + LoginImpl.totalUser + "명<br><br>"
+ "현재 접속자 목록<br>"
+ "=============<br>" ;
@SuppressWarnings("unchecked")
List<String> currenList = (List<String>)ctx.getAttribute("userList");
for(String _userId : currenList) {
System.out.println(_userId);
data += _userId + "<br>";
}
} else {
data
="<script>"
+ " alert('로그인 페이지로 이동합니다.');"
+ " location.href='/pro10/login.html';"
+ "</script>" ;
//비즈니스 로직 처리
pw.write(data);
pw.flush();
pw.close();
mySession.invalidate();
return;
}
: 한포도, 1234란 비밀번호를 입력했을 때 실행 될 코드이다.
하지만 사용자의 행동엔 변수가 있기 때문에 크게 세가지로 나누어 if-else문으로 경우의 수를 나눴다.
1) 아이디, 비밀번호 두 가지를 다 작성했을 경우
2) 로그인 후, 로그아웃을 하지 않고 새로고침만 할 경우
3) 아이디, 비밀번호 두 가지 다 작성하지 않을 경우
사용자에게 전달 받은 값이 없는 경우, alert로 경고창을 띄우며 진행 페이지로 안내하던가 현 상황을 변함 없이 유지시켜 출력하는 등의 결과를 이끌어내도록 했다.
3) 로그아웃 시 출력 화면
data
+="=============<br>"
+ "<a href=\"/pro10/logout100402?userId=" + loginUser.getUserId() +"\"><button type='button'>로그아웃</button></a>"
//로그아웃 클릭 시 logout100402 서블릿으로 로그아웃 하는 접속자ID를 전송해 로그아웃 합니다.
+ "</body>"
+ "</html>" ;
: 해당 실습에선 첫번째 아이디로 "한포도"를 입력해 캡쳐 상단의 링크 끝이 '?userId=한포도' 로 변경 되었다.
//요청 처리
request.setCharacterEncoding("utf-8");
String userId = request.getParameter("userId");
HttpSession mysSession = request.getSession();
mysSession.invalidate();
ctx = getServletContext();
@SuppressWarnings("unchecked")
List<String> userList = (ArrayList<String>)ctx.getAttribute("userList");
userList.remove(userId);
ctx.removeAttribute("userList");
ctx.setAttribute("userList", userList);
//응답 처리
response.setContentType("text/html; charset=utf-8");
PrintWriter pw = response.getWriter();
String data = "<script>"
+ " alert('로그아웃 되었습니다.\\n현재 접속인원수:"
+ LoginImpl.totalUser + "');"
+ " location.href='/pro10/login.html';"
+ "</script>" ;
pw.write(data) ;
pw.flush() ;
pw.close() ;
}
: 로그아웃 진행 시, 그동안 입력했던 값들의 배열을 불러와 지우는 일을 한다.
: 값을 지운 후 로그아웃이 되었단 팝업이 뜸과 동시에 다시 로그인 페이지로 돌아가게 만든다.
4) 브라우저별 동시 실행 결과
: 한 사이트에 한 사람만 가입하여 로그인 하는 경우는 극히 드물다.
그렇다면, 실습처럼 한 브라우저에서 하나의 아이디로만 로그인하는 것이 아닌 여러 명이 로그인 하고 로그아웃까지 하는 경우를 따져봐야 한다.
하나의 브라우저에서 로그인 하는 경우엔, 로그인 후 로그아웃을 하지 않고 되돌아가지 못하므로 항상 하나의 아이디로 로그인과 로그아웃을 반복하는 수밖에 없었다.
그렇다면 각각 다른 브라우저로 진행 시엔 어떤 결과일까 진행해 보았다.
: 시간 순으로 로그인한 아이디가 목록에 차곡차곡 쌓였다.
: 세번째 아이디가 로그아웃 했을 경우 나타나는 창이다.
: 로그인 시와 마찬가지로 현재 접속 인원 수에도 변동이 있다.