위 글은 해당 카테고리의 수업 강의 자료를 정리한 것입니다.
1. 세션 정의 (사용 목적 및 이해)
- 상태가 없는 프로토콜인 HTTP에서 상태에 대한 보전을 위해 사용
- 온라인 쇼핑몰에서 사용하는 장바구니에 사용되는 기술
- 사용자의 브라우저와 서버 간의 논리적인 연결
- 서버가 자신에게 접속한 클라이언트의 정보를 갖고 있는 상태
2. 쿠키 정의 (사용 목적 및 이해)
- 상태가 없는 프로토콜을 위해 상태를 지속시키기 위한 방법
- 세션과는 달리 클라이언트 자신들에게 그 정보를 저장
- 쿠키를 읽어서 새로운 클라이언트인지 이전에 요청을 했던 클라이언트인지를 판단
- 클라이언트에 대한 정보가 과자 부스러기처럼 남는다 해서 쿠키라 불림
3. HTTP 프로토콜에서 상태를 지속시키기 위한 방법
- 상태 미제공시
- 로그인 한 후 그 사이트 내에서 로그인 한 상태를 계속 가지고 갈 필요 요망
- 매번 페이지를 이동할 때마다 사용자의 정보를 계속 물어야 함
- URL 새로 쓰기 (URL Rewriting)
- <a herf="/ApplicationName/***.jsp?sessionid=%24559000f4c...">메뉴</a>
- 숨겨진 필드 (Hidden Form Variable)
- <form name="frmName" method="post" action="***.jsp">
- ...
- <input type="hidden" name="cofirmId" valude="<%=loginId%>">
- </form>
4. 쿠키를 구현할 수 있는 쿠키 클래스
- 쿠키 생성 (꼬리표 만들기)
- Cookie myCookie = new Cookie("CookieName", "What a Delicious Cookie it is!");
- 쿠키 셋팅 (꼬리표에 정보 기록하기)
- myCookie.setValue("Wow!");
- 쿠키 전달 (꼬리표 붙이기)
- response.addCookie(myCookie);
- 쿠키 읽기 (꼬리표 읽기)
- request.getCookies();
- 쿠키 수명주기
- cookie.setMaxAge(int expiry);
- 쿠키 생성 과정에 대한 절차
- 먼저 쿠키를 생성
- 쿠키에 필요한 설정 (ex 쿠키의 유효시간)
- 즉 쿠키의 유통기한으로 쿠키에 대한 설명, 도메인, 패스, 보안, 새로운 값 설정 등을 적용
- 클라이언트에 생성된 쿠키를 전송
- 쿠키 생성 과정
- 쿠키 생성: Cookie myCookie = new Cookie(String, String)
- 생성된 쿠키에 대한 설정: myCookie.setValue(String)
- 설정이 완료된 쿠키 전송: response.addCookie(myCookie)
- 쿠키를 사용하는 절차
- 클라이언트의 요청에서 쿠키를 얻음
- 쿠키 이름, 값의 쌍으로 되어 있으며 앞 단계에서 얻은 쿠키들에게 쿠키 이름을 가져옴
- 받아온 쿠키 이름을 통해서 해당 쿠키에 설정된 값을 추출
- 쿠키 사용 과정
- 요청에 포함된 쿠키 가져오기: Cookie[] cookies = request.getCookies()
- 쿠키 이름을 읽기: cookies[i].getName()
- 얻어진 이름을 통해 정보 사용: cookies[i].getValue()
예제: 쿠키를 생성하는 페이지를 작성하고 생성한 쿠키를 이용하는 페이지 작성
<%@ page contentType="text/html; charset=UTF-8" %>
<html>
<head>
<title>Cook Cookie</title>
</head>
<%
String cookieName = "myCookie";
Cookie cookie = new Cookie(cookieName, "Apple");
cookie.setMaxAge(60); //One minute
cookie.setValue("Melone");
response.addCookie(cookie);
%>
<body>
<h1>Example Cookie</h1>
쿠키를 만듭니다.<br/>
쿠키 내용은 <a href="tasteCookie.jsp">여기로</a>!!!
</body>
</html>
<%@ page contentType="text/html; charset=UTF-8" %>
<html>
<head>
<title>Taste Cookie</title>
</head>
<body>
<h1>Example Cookie</h1>
<%
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(int i=0; i<cookies.length;++i){
if(cookies[i].getName().equals("myCookie")){
%>
Cookie Name : <%=cookies[i].getName()%><br/>
Cookie Value : <%=cookies[i].getValue()%><br/>
<%
}
}
}
%>
</body>
</html>
5. 세션 객체의 주요 메소드
6. 세션 인터페이스
- 세션 생성
- session.setAttribute("mySession", "session value");
- 세션의 유지시간 설정
- session.setMaxInactiveInterval(60*5);
- 세션 속성 삭제
- session.removeAttribute("mySession");
- 세션 삭제
- session.invalidate();
예제: 세션을 생성하는 페이지를 작성하고 세션에 할당된 이름과 값을 확인하기 위한 페이지를 작성
<%@ page contentType="text/html; charset=UTF-8" %>
<html>
<head>
<title>세션사용예제(세션생성)</title>
</head>
<body>
<%
String id = "rorod";
String pwd = "1234";
session.setAttribute("idKey", id);
session.setAttribute("pwdKey", pwd);
%>
세션이 생성되었습니다.<br/>
<a href="viewSessionInfo.jsp">세션정보를 확인하는 페이지로 이동</a>
</body>
</html>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title>세션사용예제(세션확인)</title>
</head>
<body>
<%
Enumeration en = session.getAttributeNames();
while(en.hasMoreElements()){
String name = (String)en.nextElement();
String value = (String)session.getAttribute(name);
out.println("session name : " + name + "<br/>");
out.println("seesion value " + value + "<br/>");
}
%>
</body>
</html>
7. 쿠키와 세션 비교
7.1 쿠키 (Cookie)
7.2 쿠키의 사용
예제: 각각의 클라이언트 식별 ID를 알아내기 (세션을 생성하기 위한 JSP 페이지를 작성하고 세션 정보, 쿠키의 이름, 값을 출력하는 페이지 작성)
<%@ page contentType="text/html; charset=UTF-8" %>
<html>
<head>
<title>세션사용예제(세션생성)</title>
</head>
<body>
<%
String id = "rorod";
String pwd = "1234";
session.setAttribute("idKey", id);
session.setAttribute("pwdKey", pwd);
%>
세션이 생성되었습니다.<br/>
<a href="viewCookieSessionInfo.jsp">쿠키 및 세션정보를 확인하는 페이지로 이동</a>
</body>
</html>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title>세션사용예제(세션확인)</title>
</head>
<body>
<%
Enumeration en = session.getAttributeNames();
while(en.hasMoreElements()){
String name = (String)en.nextElement();
String value = (String)session.getAttribute(name);
out.println("session name : " + name + "<br/>");
out.println("seesion value " + value + "<br/>");
}
%>
--------------------------------------------------------<br/>
<%
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(int i=0; i<cookies.length;++i){
%>
Cookie Name : <%=cookies[i].getName()%><br/>
Cookie Value : <%=cookies[i].getValue()%><br/>
<%
}
}
%>
</body>
</html>
8. 웹 어플리케이션의 세션과 쿠키의 사용
8.1 쿠키와 세션 예제에서 공통적으로 사용할 자바빈즈 컴파일
- DBConnectionMgr.java 파일을 이클립스로 복사 및 저장
- DBConnectionMgr.class가 classes/ch12 폴더에 생성되었는지 확인
* Copyright(c) 2001 iSavvix Corporation (http://www.isavvix.com/)
*
* All rights reserved
*
* Permission to use, copy, modify and distribute this material for
* any purpose and without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies, and that the name of iSavvix Corporation not be used in
* advertising or publicity pertaining to this material without the
* specific, prior written permission of an authorized representative of
* iSavvix Corporation.
*
* ISAVVIX CORPORATION MAKES NO REPRESENTATIONS AND EXTENDS NO WARRANTIES,
* EXPRESS OR IMPLIED, WITH RESPECT TO THE SOFTWARE, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR ANY PARTICULAR PURPOSE, AND THE WARRANTY AGAINST
* INFRINGEMENT OF PATENTS OR OTHER INTELLECTUAL PROPERTY RIGHTS. THE
* SOFTWARE IS PROVIDED "AS IS", AND IN NO EVENT SHALL ISAVVIX CORPORATION OR
* ANY OF ITS AFFILIATES BE LIABLE FOR ANY DAMAGES, INCLUDING ANY
* LOST PROFITS OR OTHER INCIDENTAL OR CONSEQUENTIAL DAMAGES RELATING
* TO THE SOFTWARE.
*
*/
package ch12;
import java.sql.*;
import java.util.Properties;
import java.util.Vector;
/**
* Manages a java.sql.Connection pool.
*
* @author Anil Hemrajani
*/
public class DBConnectionMgr {
private Vector connections = new Vector(10);
private String _driver = "org.gjt.mm.mysql.Driver",
_url = "jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=UTF-8",
_user = "root",
_password = "1234";
private boolean _traceOn = false;
private boolean initialized = false;
private int _openConnections = 10;
private static DBConnectionMgr instance = null;
public DBConnectionMgr() {
}
/** Use this method to set the maximum number of open connections before
unused connections are closed.
*/
public static DBConnectionMgr getInstance() {
if (instance == null) {
synchronized (DBConnectionMgr.class) {
if (instance == null) {
instance = new DBConnectionMgr();
}
}
}
return instance;
}
public void setOpenConnectionCount(int count) {
_openConnections = count;
}
public void setEnableTrace(boolean enable) {
_traceOn = enable;
}
/** Returns a Vector of java.sql.Connection objects */
public Vector getConnectionList() {
return connections;
}
/** Opens specified "count" of connections and adds them to the existing pool */
public synchronized void setInitOpenConnections(int count)
throws SQLException {
Connection c = null;
ConnectionObject co = null;
for (int i = 0; i < count; i++) {
c = createConnection();
co = new ConnectionObject(c, false);
connections.addElement(co);
trace("ConnectionPoolManager: Adding new DB connection to pool (" + connections.size() + ")");
}
}
/** Returns a count of open connections */
public int getConnectionCount() {
return connections.size();
}
/** Returns an unused existing or new connection. */
public synchronized Connection getConnection()
throws Exception {
if (!initialized) {
Class c = Class.forName(_driver);
DriverManager.registerDriver((Driver) c.newInstance());
initialized = true;
}
Connection c = null;
ConnectionObject co = null;
boolean badConnection = false;
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.elementAt(i);
// If connection is not in use, test to ensure it's still valid!
if (!co.inUse) {
try {
badConnection = co.connection.isClosed();
if (!badConnection)
badConnection = (co.connection.getWarnings() != null);
} catch (Exception e) {
badConnection = true;
e.printStackTrace();
}
// Connection is bad, remove from pool
if (badConnection) {
connections.removeElementAt(i);
trace("ConnectionPoolManager: Remove disconnected DB connection #" + i);
continue;
}
c = co.connection;
co.inUse = true;
trace("ConnectionPoolManager: Using existing DB connection #" + (i + 1));
break;
}
}
if (c == null) {
c = createConnection();
co = new ConnectionObject(c, true);
connections.addElement(co);
trace("ConnectionPoolManager: Creating new DB connection #" + connections.size());
}
return c;
}
/** Marks a flag in the ConnectionObject to indicate this connection is no longer in use */
public synchronized void freeConnection(Connection c) {
if (c == null)
return;
ConnectionObject co = null;
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.elementAt(i);
if (c == co.connection) {
co.inUse = false;
break;
}
}
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.elementAt(i);
if ((i + 1) > _openConnections && !co.inUse)
removeConnection(co.connection);
}
}
public void freeConnection(Connection c, PreparedStatement p, ResultSet r) {
try {
if (r != null) r.close();
if (p != null) p.close();
freeConnection(c);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void freeConnection(Connection c, Statement s, ResultSet r) {
try {
if (r != null) r.close();
if (s != null) s.close();
freeConnection(c);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void freeConnection(Connection c, PreparedStatement p) {
try {
if (p != null) p.close();
freeConnection(c);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void freeConnection(Connection c, Statement s) {
try {
if (s != null) s.close();
freeConnection(c);
} catch (SQLException e) {
e.printStackTrace();
}
}
/** Marks a flag in the ConnectionObject to indicate this connection is no longer in use */
public synchronized void removeConnection(Connection c) {
if (c == null)
return;
ConnectionObject co = null;
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.elementAt(i);
if (c == co.connection) {
try {
c.close();
connections.removeElementAt(i);
trace("Removed " + c.toString());
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
}
private Connection createConnection()
throws SQLException {
Connection con = null;
try {
if (_user == null)
_user = "";
if (_password == null)
_password = "";
Properties props = new Properties();
props.put("user", _user);
props.put("password", _password);
con = DriverManager.getConnection(_url, props);
} catch (Throwable t) {
throw new SQLException(t.getMessage());
}
return con;
}
/** Closes all connections and clears out the connection pool */
public void releaseFreeConnections() {
trace("ConnectionPoolManager.releaseFreeConnections()");
Connection c = null;
ConnectionObject co = null;
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.elementAt(i);
if (!co.inUse)
removeConnection(co.connection);
}
}
/** Closes all connections and clears out the connection pool */
public void finalize() {
trace("ConnectionPoolManager.finalize()");
Connection c = null;
ConnectionObject co = null;
for (int i = 0; i < connections.size(); i++) {
co = (ConnectionObject) connections.elementAt(i);
try {
co.connection.close();
} catch (Exception e) {
e.printStackTrace();
}
co = null;
}
connections.removeAllElements();
}
private void trace(String s) {
if (_traceOn)
System.err.println(s);
}
}
class ConnectionObject {
public java.sql.Connection connection = null;
public boolean inUse = false;
public ConnectionObject(Connection c, boolean useFlag) {
connection = c;
inUse = useFlag;
}
}
- RegisterMgr.java 파일 작성
package ch12;
import java.sql.*;
public class RegisterMgr{
private DBConnectionMgr pool;
public RegisterMgr(){
try{
pool = DBConnectionMgr.getInstance();
}catch(Exception e){
System.out.println("Error : Ŀ�ؼ� ���� ����");
}
}
public boolean loginRegister(String id, String pwd) {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
boolean loginCon = false;
try {
con = pool.getConnection();
String query = "select count(*) from tblRegister where id = ? and pwd = ?";
pstmt = con.prepareStatement(query);
pstmt.setString(1, id);
pstmt.setString(2, pwd);
rs = pstmt.executeQuery();
if(rs.next()&&rs.getInt(1)>0)
loginCon =true;
}catch(Exception ex) {
System.out.println("Exception" + ex);
}finally{
pool.freeConnection(con,pstmt,rs);
}
return loginCon;
}
}
9. 로그인
9.1 쿠키를 사용한 로그인
예제: 쿠키를 사용하여 로그인하기
- 아이디와 패스워드를 입력 받을 로그인 페이지 (cookieLogin.jsp)
<%@ page contentType="text/html; charset=UTF-8"%>
<%
String cookieName = "";
String id = "";
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("idKey")) {
cookieName = cookies[i].getName();
id = cookies[i].getValue();
}
}
if (!id.equals("")) {
%>
<script>
alert("로그인 되었습니다.");
location.href = "cookieLoginOK.jsp"; // 쿠키 정보가 있으면 페이지 이동
</script>
<%
}
}
%>
<html>
<head>
<title>Cookie 로그인</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<h2 align="center">Cookie 로그인</h2>
<form method="post" action="cookieLoginProc.jsp">
<table width="300" border="1" align="center" bgcolor="#FFFF99">
<tr bordercolor="#FFFF66">
<td colspan="2" align="center"><b>Log in</b></td>
</tr>
<tr>
<td width="47%" align="center">ID</td>
<td width="53%" align="center"><input name="id"></td>
</tr>
<tr>
<td align="center">PWD</td>
<td align="center"><input name="pwd"></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="login">
<input type="reset" value="reset">
</td>
</tr>
</table>
</form>
</body>
</html>
- RegisterMgr을 사용하여 아이디, 패스워드를 인자로 받아서 테이블에 아이디와 패스워드가 존재하는지 여부를 체크하는 페이지 (cookieLoginOK.jsp)
<%@ page contentType="text/html; charset=UTF-8"%>
<%
String id = "";
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("idKey")) {
id = cookies[i].getValue();
}
}
if (id.equals("")) {
%>
<script>
alert("로그인 되지 않았습니다.");
location.href = "cookieLogin.jsp";
</script>
<%
}
}
%>
<html>
<head>
<title>Cookie 로그인</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<h2 align="center">Cookie 로그인</h2>
<table width="300" border="1" align="center" bgcolor="#FFFF99">
<tr bordercolor="#FFFF66">
<td colspan="2" align="center"><b>Log On Page</b></td>
</tr>
<tr>
<td align="center"><b><%=id%></b>님이 로그인 하셨습니다.</td>
<td align="center"><a href="cookieLogout.jsp">로그아웃</a></td>
</tr>
</table>
</body>
</html>
- 아이디가 없는 경우 로그인 페이지로 이동하고 아이디가 있는 경우 로그아웃을 시킬 수 있는 페이지 (cookieLoginProc.jsp)
<%@ page contentType="text/html; charset=UTF-8" %>
<jsp:useBean id="regMgr" class="ch12.RegisterMgr" />
<%
String id = "";
String pwd = "";
if(request.getParameter("id") != null)
id = request.getParameter("id");
if(request.getParameter("pwd") != null)
pwd = request.getParameter("pwd");
if(regMgr.loginRegister(id, pwd)){
Cookie cookie = new Cookie("idKey", id);
response.addCookie(cookie);
%>
<script>
alert("로그인 되었습니다.");
location.href="cookieLoginOK.jsp";
</script>
<% }else{ %>
<script>
alert("로그인 되지 않았습니다.");
location.href="cookieLogin.jsp";
</script>
<% } %>
- 쿠키를 만료시켜 로그아웃이 되었다는 메시지가 나오는 페이지 (cookieLogout.jsp)
<%@ page contentType="text/html; charset=UTF-8" %>
<%
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(int i=0; i<cookies.length; i++){
if(cookies[i].getName().equals("idKey")){
cookies[i].setMaxAge(0);
response.addCookie(cookies[i]);
}
}
}
%>
<script>
alert("로그아웃 되었습니다.");
location.href = "cookieLogin.jsp";
</script>
📌 로직
- 정상적으로 로그인이 되면 '로그인이 되었습니다.'라는 alert 창을 띄움
- 확인 버튼 누르면 로그인 완료 페이지로 이동
- 정상적으로 로그인된 사용자만 로그인 완료 페이지를 볼 수 있음
- 로그아웃 버튼을 누르면 '로그아웃 되었습니다.'라는 alert 창을 띄움
- 정상적으로 로그인이 되지 않으면 '로그인 되지 않았습니다'. 라는 alert 창을 띄움
9.2 세션을 사용한 로그인
예제: 세션을 사용하여 로그인 하기
- 아이디와 패스워드를 입력 받을 로그인 페이지 (sessionLogin.jsp)
<%@ page contentType="text/html; charset=UTF-8" %>
<%
String id = (String)session.getAttribute("idKey");
if(id!=null){
%>
<script>
alert("로그인 되었습니다");
location.href = "sessionLoginOK.jsp";
</script>
<% } %>
<html>
<head>
<title>Simple LogIn</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body bgcolor="#996600" topmargin="100">
<h2 align="center">Session 로그인</h2>
<table width="75%" border="1" align="center" bordercolor="#660000" bgcolor="#FFFF99">
<tr bordercolor="#FFFF99">
<td height="190" colspan="7">
<form method="post" action="sessionLoginProc.jsp">
<table width="50%" border="1" align="center" cellspacing="0" cellpadding="0">
<tr bordercolor="#FFFF66">
<td colspan="2"><div align="center">Log in</div></td>
</tr>
<tr >
<td width="47%"><div align="center">ID</div></td>
<td width="53%"><div align="center"><input name="id"></div></td>
</tr>
<tr>
<td><div align="center">PWD</div></td>
<td><div align="center"><input name="pwd"></div></td>
</tr>
<tr>
<td colspan="2"><div align="center">
<input type="submit" value="login">
<input type="reset" value="reset">
</div></td>
</tr>
</table>
</form></td>
</tr>
</table>
</body>
</html>
- RegisterMgr을 사용하여 아이디, 패스워드를 인자로 받아서 테이블에 아이디와 패스워드가 존재하는지의 여부를 체크하는 페이지
- 아이디가 없는 경우 로그인 페이지로 이동하고 아이디가 있는 경우 로그아웃 시킬 수 있는 페이지
- (sessionLoginProc.jsp)
<%@ page contentType="text/html; charset=UTF-8" %>
<jsp:useBean id="regMgr" class="ch12.RegisterMgr" />
<%
String id = "";
String pwd = "";
if(request.getParameter("id") != null)
id = request.getParameter("id");
if(request.getParameter("pwd") != null)
pwd = request.getParameter("pwd");
if(regMgr.loginRegister(id, pwd)){
session.setAttribute("idKey",id);
%>
<script>
alert("로그인 되었습니다.");
location.href="sessionLoginOK.jsp";
</script>
<% }else{ %>
<script>
alert("로그인 되지 않았습니다.");
location.href="sessionLogin.jsp";
</script>
<%}%>
- 쿠키를 만료시켜 로그아웃이 되었다는 메시지가 나오는 페이지 (sessionLogout.jsp)
<%@ page contentType="text/html; charset=UTF-8" %>
<% session.invalidate(); %>
<script>
alert("로그아웃 되었습니다.");
location.href="sessionLogin.jsp";
</script>
이전처럼 TBLREGISTER 테이블에 있는 정보를 사용해야 함
📌 로직
- 테이블에 있는 아이디와 패스워드를 입력했다면 '로그인 되었습니다.'라는 alert 창이 뜸
- 확인 버튼을 누르면 다음 화면(로그인 완료 페이지)으로 이동
- 로그인 완료 페이지에서 로그아웃 버튼을 누르면 '로그아웃 되었습니다.'라는 alert 창이 뜸
- 확인 버튼을 누르면 로그인 페이지로 이동 (이 페이지에서 모든 세션을 해제)
- 로그인 페이지에서 reset 버튼을 누르면 input 값을 리셋 시켜줌
'강의 > KOSTA' 카테고리의 다른 글
[JSP/Servlet] Model2 구현 (Day25) (0) | 2022.04.11 |
---|---|
[JSP/Servlet] Model1 구현 (Day25) (1) | 2022.04.11 |
[JSP/Servlet] Database integration with JDBC (Day22) (0) | 2022.04.04 |
[JSP/Servlet] JSP and JavaBeans (Day22) (0) | 2022.04.04 |
[JSP/Servlet] Servlet Basic Syntax (Day22) (0) | 2022.04.04 |