-
서버공부 시작 { HTTP } : 간단한 웹서버 띄우기백엔드 : 서버공부 2023. 10. 2. 14:06728x90
Java를 이용해서 간단한 웹서버를 띄워볼 것입니다. 완전한 서버를 구축하기 위해서는 다른 역할을 수행하는 클래스들이 많이 필요하지만, 여기서는 많은 클래스들 중 RequestHandler를 통해 HTTP 메세지를 읽고 쓰는 등의 처리하는 과정을 살펴보겠습니다.
먼저 전체 코드는 아래와 같습니다.
package webserver; import db.MemoryUserRepository; import db.Repository; import model.User; import http.util.IOUtils; import http.util.HttpRequestUtils; import java.io.*; import java.net.Socket; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; public class RequestHandler implements Runnable { private static final String ROOT_URL = "./webapp"; private static final String HOME_URL = "/index.html"; private static final String LOGIN_FAILED_URL = "/user/login_failed.html"; private static final String LOGIN_URL = "/user/login.html"; private static final String LIST_URL = "/user/list.html"; private final Socket connection; private final Logger log = Logger.getLogger(RequestHandler.class.getName()); private final Repository repository; public RequestHandler(Socket connection) { this.connection = connection; this.repository = MemoryUserRepository.getInstance(); } @Override public void run() { log.log(Level.INFO, "* New Client Connect *\n Connected IP: " + connection.getInetAddress() + ", Port: " + connection.getPort()); try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) { BufferedReader br = new BufferedReader(new InputStreamReader(in)); DataOutputStream dos = new DataOutputStream(out); String startLine = br.readLine(); String[] startLineTokens = startLine.split(" "); String method = startLineTokens[0]; String url = startLineTokens[1]; int requestContentLength = 0; String cookie = ""; while (true) { String line = br.readLine(); if (line == null || line.equals("")) { break; } if (line.startsWith("Content-Length")) { requestContentLength = Integer.parseInt(line.split(": ")[1]); } if (line.startsWith("Cookie")) { cookie = line.split(": ")[1]; } } byte[] body = new byte[0]; // Handle various requests if (method.equals("GET") && url.endsWith(".html")) { body = Files.readAllBytes(Paths.get(ROOT_URL + url)); } else if (url.equals("/")) { body = Files.readAllBytes(Paths.get(ROOT_URL + HOME_URL)); } else if (url.equals("/user/signup")) { handleSignup(br, dos, requestContentLength); return; } else if (url.equals("/user/login")) { handleLogin(br, dos, requestContentLength); return; } else if (url.equals("/user/userList")) { handleUserList(dos, cookie); return; } else if (method.equals("GET") && url.endsWith(".css")) { handleCssRequest(dos, url); return; } else { handleOtherRequests(url, dos); return; } response200Header(dos, body.length, "text/html"); responseBody(dos, body); } catch (IOException e) { log.log(Level.SEVERE, e.getMessage()); } finally { try { connection.close(); } catch (IOException e) { log.log(Level.SEVERE, e.getMessage()); } } } private void handleSignup(BufferedReader br, DataOutputStream dos, int contentLength) throws IOException { String requestBody = IOUtils.readBody(br, contentLength); Map<String, String> formData = HttpRequestUtils.parseQueryParameter(requestBody); String userId = formData.get("userId"); String password = formData.get("password"); String name = formData.get("name"); String email = formData.get("email"); User newUser = new User(userId, password, name, email); repository.addUser(newUser); response302Header(dos, HOME_URL); } private void handleLogin(BufferedReader br, DataOutputStream dos, int contentLength) throws IOException { String requestBody = IOUtils.readBody(br, contentLength); Map<String, String> formData = HttpRequestUtils.parseQueryParameter(requestBody); String userId = formData.get("userId"); String password = formData.get("password"); User user = repository.findUserById(userId); if (user != null && user.getPassword().equals(password)) { response302HeaderWithCookie(dos, HOME_URL); } else { response302Header(dos, LOGIN_FAILED_URL); } } private void handleUserList(DataOutputStream dos, String cookie) throws IOException { if (cookie != null && cookie.equals("logined=true")) { byte[] body = Files.readAllBytes(Paths.get(ROOT_URL + LIST_URL)); response200Header(dos, body.length, "text/html"); responseBody(dos, body); } else { response302Header(dos, LOGIN_URL); } } private void handleCssRequest(DataOutputStream dos, String url) throws IOException { String filePath = ROOT_URL + url; byte[] fileData = Files.readAllBytes(Paths.get(filePath)); response200Header(dos, fileData.length, "text/css"); responseBody(dos, fileData); } private void handleOtherRequests(String url, DataOutputStream dos) throws IOException { String filePath = ROOT_URL + url; if (Files.exists(Paths.get(filePath))) { byte[] fileData = Files.readAllBytes(Paths.get(filePath)); String contentType = Files.probeContentType(Paths.get(filePath)); response200Header(dos, fileData.length, contentType + "; charset=UTF-8"); responseBody(dos, fileData); } else { String notFoundMessage = "404 Not Found: " + url; byte[] notFoundBody = notFoundMessage.getBytes(); response404Header(dos, notFoundBody.length); responseBody(dos, notFoundBody); } } private void response200Header(DataOutputStream dos, int lengthOfBodyContent, String contentType) { try { dos.writeBytes("HTTP/1.1 200 OK\r\n"); dos.writeBytes("Content-Type: " + contentType + "\r\n"); dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); dos.writeBytes("\r\n"); dos.flush(); } catch (IOException e) { log.log(Level.SEVERE, e.getMessage()); } } private void response302Header(DataOutputStream dos, String path) { try { dos.writeBytes("HTTP/1.1 302 Redirect\r\n"); dos.writeBytes("Location: " + path + "\r\n"); dos.writeBytes("\r\n"); dos.flush(); } catch (IOException e) { log.log(Level.SEVERE, e.getMessage()); } } private void response302HeaderWithCookie(DataOutputStream dos, String path) { try { dos.writeBytes("HTTP/1.1 302 Redirect\r\n"); dos.writeBytes("Location: " + path + "\r\n"); dos.writeBytes("Set-Cookie: logined=true\r\n"); dos.writeBytes("\r\n"); dos.flush(); } catch (IOException e) { log.log(Level.SEVERE, e.getMessage()); } } private void response404Header(DataOutputStream dos, int lengthOfBodyContent) { try { dos.writeBytes("HTTP/1.1 404 Not Found\r\n"); dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n"); dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); dos.writeBytes("\r\n"); dos.flush(); } catch (IOException e) { log.log(Level.SEVERE, e.getMessage()); } } private void responseBody(DataOutputStream dos, byte[] body) { try { dos.write(body, 0, body.length); dos.flush(); } catch (IOException e) { log.log(Level.SEVERE, e.getMessage()); } } }
이 코드를 바탕으로 설명해 보겠습니다.
HTTP메세지 읽어오기!
HTTP 메세지를 읽어오는 과정
String startLine = br.readLine(); String[] startLineTokens = startLine.split(" "); String method = startLineTokens[0]; String url = startLineTokens[1];
위에 부분은 클라이언트로부터 받은 HTTP 요청 메시지를 파싱하고, 요청에서 필요한 정보를 추출하는 부분입니다.
HTTP 요청 예시
클라이언트가 서버에게 보내는 간단한 HTTP GET 요청 메시지입니다.
GET /index.html HTTP/1.1 Host: www.KUIT.com User-Agent: Mozilla/5.0 Accept-Language: en-US,en;q=0.5
- String startLine = br.readLine();
- 이 부분에서 startLine은 "GET /index.html HTTP/1.1"이 됩니다.
- String[] startLineTokens = startLine.split(" ");
- startLineTokens 배열에는 **["GET", "/index.html", "HTTP/1.1"]**이 저장됩니다.
- String method = startLineTokens[0];
- method에는 "GET"이 저장됩니다.
- String url = startLineTokens[1];
- url에는 "/index.html"이 저장됩니다.
HTTP 응답 예시
서버가 클라이언트에게 보내는 간단한 HTTP 응답 메시지입니다.
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1234 <!DOCTYPE html> <html> <head> <title>Example Page</title> </head> <body> <h1>Hello, World!</h1> </body> </html>
- String startLine = br.readLine();
- startLine은 "HTTP/1.1 200 OK"이 됩니다.
- 응답 코드 해석
- HTTP/1.1: HTTP 프로토콜 버전.
- 200: HTTP 응답 코드. 여기서는 "OK"를 나타냅니다. 성공적인 응답을 나타냅니다.
- Content-Type: text/html: 응답의 내용이 HTML임을 나타내는 헤더.
- Content-Length: 1234: 응답의 본문 길이가 1234바이트임을 나타내는 헤더.
- 헤더 정보 추출
- Content-Type: text/html: 응답의 내용이 HTML임을 나타내는 헤더.
- Content-Length: 1234: 응답의 본문 길이가 1234바이트임을 나타내는 헤더.
HTTP 요청 메시지의 헤더를 파싱하여 필요한 정보를 추출
while (true) { String line = br.readLine(); if (line == null || line.equals("")) { break; } if (line.startsWith("Content-Length")) { requestContentLength = Integer.parseInt(line.split(": ")[1]); } if (line.startsWith("Cookie")) { cookie = line.split(": ")[1]; } }
while (true) 루프(무한루프)는 요청 메시지의 헤더를 한 줄씩 읽어들이고, 특정 조건에 따라 필요한 정보를 추출합니다.
HTTP 요청 메시지 예시
GET /path/to/page.html HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Content-Length: 20 Cookie: sessionId=abc123; loggedIn=true This is the request body.
- Start Line
- GET /path/to/page.html HTTP/1.1
- 이 부분은 요청 메서드 (GET), 요청 URL (/path/to/page.html), 그리고 프로토콜 버전 (HTTP/1.1)으로 구성됩니다.
- Headers
- Host: www.example.com
- User-Agent: ...
- Accept: ...
- Content-Length: 20
- Cookie: sessionId=abc123; loggedIn=true
- 헤더는 key: value 형태로 되어 있으며, 각 헤더는 요청에 대한 부가 정보를 제공합니다. 여기서는 Content-Length와 Cookie 헤더를 추출할 예정입니다.
- Request Body
- This is the request body.
- 본문은 헤더 이후의 내용으로, 여기서는 간단하게 "This is the request body."라는 문자열을 본문으로 가정합니다.
다시 java코드에서 해당 부분을 보자.
BufferedReader br = new BufferedReader(new InputStreamReader(in)); // 시작 라인을 읽어와 HTTP 메서드와 URL을 추출 String startLine = br.readLine(); String[] startLineTokens = startLine.split(" "); String method = startLineTokens[0]; // HTTP 메서드 (예: "GET") String url = startLineTokens[1]; // 요청된 URL (예: "/path/to/page.html") // 헤더를 읽어와 관련 정보 추출하기 위한 빈 바구니 int requestContentLength = 0; String cookie = ""; // 헤더를 읽기 위한 무한 루프 while (true) { String line = br.readLine(); if (line == null || line.equals("")) { break; // 빈 줄을 만나면 루프 종료 (헤더의 끝!) } // Content-Length와 Cookie 헤더 추출 if (line.startsWith("Content-Length")) { requestContentLength = Integer.parseInt(line.split(": ")[1]); } if (line.startsWith("Cookie")) { cookie = line.split(": ")[1]; } }
핵심은 HTTP메세지의 규격화된 형식(약속)을 이용하는 것이다. 이를 이용해 “split”연산자를 사용해 필요한 정보를 추출한다!
처리한 HTTP 요청을 바탕으로 그에 맞는 응답을 생성하는 로직
주석을 좀더 상세하게 달아봤습니다.
if (method.equals("GET") && url.endsWith(".html")) { // GET 요청이고 확장자가 .html로 끝나면 해당 HTML 파일을 읽어옴 body = Files.readAllBytes(Paths.get(ROOT_URL + url)); } else if (url.equals("/")) { // 루트 URL인 경우 홈 페이지의 HTML 파일을 읽어옴 body = Files.readAllBytes(Paths.get(ROOT_URL + HOME_URL)); } else if (url.equals("/user/signup")) { // /user/signup URL로 요청이 들어온 경우 회원 가입 처리를 수행 handleSignup(br, dos, requestContentLength); return; } else if (url.equals("/user/login")) { // /user/login URL로 요청이 들어온 경우 로그인 처리를 수행 handleLogin(br, dos, requestContentLength); return; } else if (url.equals("/user/userList")) { // /user/userList URL로 요청이 들어온 경우 사용자 목록 조회 처리를 수행 handleUserList(dos, cookie); return; } else if (method.equals("GET") && url.endsWith(".css")) { // GET 요청이고 확장자가 .css로 끝나면 해당 CSS 파일을 읽어옴 handleCssRequest(dos, url); return; } else { // 그 외의 URL로 요청이 들어온 경우 기타 처리를 수행 handleOtherRequests(url, dos); return; } // 응답 헤더를 설정하고 응답 바디를 전송 response200Header(dos, body.length, "text/html"); responseBody(dos, body);
- GET 요청이고 확장자가 .html로 끝나는 경우
- 해당 HTML 파일을 읽어와 응답으로 전송합니다.
- 루트 URL인 경우 (/)
- 홈 페이지의 HTML 파일을 읽어와 응답으로 전송합니다.
- /user/signup URL로 요청이 들어온 경우
- 회원 가입 처리를 수행합니다.
- /user/login URL로 요청이 들어온 경우
- 로그인 처리를 수행합니다.
- /user/userList URL로 요청이 들어온 경우
- 사용자 목록 조회 처리를 수행합니다.
- GET 요청이고 확장자가 .css로 끝나는 경우
- 해당 CSS 파일을 읽어와 응답으로 전송합니다.
- 그 외의 URL로 요청이 들어온 경우
- 기타 처리를 수행합니다.
클라이언트가 회원 가입을 요청했을 때 요청된 데이터를 처리하고 회원을 추가하는 부분
private void handleSignup(BufferedReader br, DataOutputStream dos, int contentLength) throws IOException { String requestBody = IOUtils.readBody(br, contentLength); Map<String, String> formData = HttpRequestUtils.parseQueryParameter(requestBody); String userId = formData.get("userId"); String password = formData.get("password"); String name = formData.get("name"); String email = formData.get("email"); User newUser = new User(userId, password, name, email); repository.addUser(newUser); response302Header(dos, HOME_URL); }
HTTP POST 요청 예시
POST /user/signup HTTP/1.1 Host: example.com Content-Type: application/x-www-form-urlencoded Content-Length: 51 userId=johndoe&password=pass123&name=John+Doe&email=john@example.com
- 클라이언트는 POST 메서드를 사용하여 /user/signup URL에 요청을 보냅니다.
- 요청 헤더에는 Content-Type이 application/x-www-form-urlencoded로 설정되어 있으며, 요청 바디의 길이는 51입니다.
handleSignup 메소드가 호출되면 다음과 같이 동작합니다....
- IOUtils.readBody(br, contentLength)를 통해 요청 바디를 읽어옵니다. 이 메소드는 전달된 BufferedReader를 사용하여 바디의 내용을 읽어옵니다.
- HttpRequestUtils.parseQueryParameter(requestBody)를 사용하여 바디에서 파라미터를 추출하고 맵 형태로 저장합니다. 여기서는 userId, password, name, email을 추출합니다. (여기서도 split 사용)
- 추출한 데이터를 사용하여 새 User 객체를 생성합니다.
- repository.addUser(newUser)를 호출하여 사용자를 저장합니다.
- 마지막으로, response302Header(dos, HOME_URL)을 호출하여 회원 가입 후 홈 페이지로 리다이렉션 응답을 보냅니다.
호출 부분
if (url.equals("/user/signup")) { // /user/signup URL로 요청이 들어온 경우 회원 가입 처리를 수행 handleSignup(br, dos, requestContentLength); return; }
파라매터 br을 왜 받아야하나..할수도있는데..handleSignup 메소드 내부에서 IOUtils.readBody(br, contentLength)를 호출하여 클라이언트가 보낸 요청의 본문(body)을 읽어옴
클라이언트로부터 받은 로그인 요청을 처리
private void handleLogin(BufferedReader br, DataOutputStream dos, int contentLength) throws IOException { String requestBody = IOUtils.readBody(br, contentLength); Map<String, String> formData = HttpRequestUtils.parseQueryParameter(requestBody); String userId = formData.get("userId"); String password = formData.get("password"); User user = repository.findUserById(userId); if (user != null && user.getPassword().equals(password)) { response302HeaderWithCookie(dos, HOME_URL); } else { response302Header(dos, LOGIN_FAILED_URL); } }
요청 Body 읽어오기
String requestBody = IOUtils.readBody(br, contentLength);
클라이언트가 보낸 요청 본문을 IOUtils.readBody를 통해 읽어옵니다.
데이터 파싱
Map<String, String> formData = HttpRequestUtils.parseQueryParameter(requestBody);
클라이언트가 보낸 요청 본문을 파싱하여 폼 데이터를 추출합니다.
User 정보 추출
String userId = formData.get("userId"); String password = formData.get("password");
로그인 요청에서 사용자 아이디와 비밀번호를 추출합니다.
로그인 기능 부분 ( 기존 사용자 탐색 및 적절한 응답 )
User user = repository.findUserById(userId); if (user != null && user.getPassword().equals(password)) { response302HeaderWithCookie(dos, HOME_URL); } else { response302Header(dos, LOGIN_FAILED_URL); }
repository.findUserById(userId)를 통해 사용자를 찾습니다.
사용자가 존재하고 비밀번호가 일치하면 response302HeaderWithCookie를 사용하여 로그인 성공 응답을 생성합니다.
사용자가 존재하지 않거나 비밀번호가 일치하지 않으면, response302Header를 사용하여 로그인 실패 응답을 생성~~!
사용자 목록 조회 요청을 처리
private void handleUserList(DataOutputStream dos, String cookie) throws IOException { if (cookie != null && cookie.equals("logined=true")) { byte[] body = Files.readAllBytes(Paths.get(ROOT_URL + LIST_URL)); response200Header(dos, body.length, "text/html"); responseBody(dos, body); } else { response302Header(dos, LOGIN_URL); } }
- Cookie 체크하기
- 클라이언트의 요청에서 쿠키를 확인합니다. 쿠키는 "logined=true"로 설정되어 있는지 확인합니다.
- 로그인 상태 User 처리
- 쿠키가 존재하고 "logined=true"로 설정되어 있다면, 로그인 상태로 간주하여 사용자 목록 조회 처리를 수행합니다.
- 사용자 목록을 읽어와 응답을 생성합니다.
- 로그인 상태가 아닌 User 처리
- 쿠키가 없거나 "logined=true"로 설정되어 있지 않다면, 로그인 페이지로 리디렉션합니다.
HTTP 메세지 예시
클라이언트의 요청
GET /user/userList HTTP/1.1 Host: example.com Cookie: logined=true
메세지서버의 응답 메세지 (인증된 사용자)
HTTP/1.1 200 OK Content-Type: text/html;charset=utf-8 Content-Length: <length_of_body> <html> <!-- HTML content for user list --> </html>
서버의 응답 메세지 (인증되지 않은 사용자)
HTTP/1.1 302 Redirect Location: /user/login.html
아 근데 쿠키를 왜쓰는데???
- logined=true"는 로그인 여부를 나타내는 쿠키 값으로, 사용자가 로그인한 상태임을 나타내는 역할입니다.
- 서버는 클라이언트에게 이 쿠키를 설정하여, 클라이언트가 이후 요청을 보낼 때 이 정보를 함께 전달하게되는데….서버는 클라이언트 상태를 유지하고 인증 상태를 추적할 수 있습니다.
handleOtherRequests 클라이언트가 요청한 URL에 해당하는 파일을 응답
private void handleOtherRequests(String url, DataOutputStream dos) throws IOException { String filePath = ROOT_URL + url; if (Files.exists(Paths.get(filePath))) { byte[] fileData = Files.readAllBytes(Paths.get(filePath)); String contentType = Files.probeContentType(Paths.get(filePath)); response200Header(dos, fileData.length, contentType + "; charset=UTF-8"); responseBody(dos, fileData); } else { String notFoundMessage = "404 Not Found: " + url; byte[] notFoundBody = notFoundMessage.getBytes(); response404Header(dos, notFoundBody.length); responseBody(dos, notFoundBody); } }
클라이언트의 HTTP 요청 메시지
클라이언트가 정적 파일을 요청했다.... 이렇게..
GET /path/to/file.html HTTP/1.1 Host: example.com
- 메서드가 클라이언트의 요청에서 파일 경로를 추출합니다. 예시에서 "/path/to/file.html”에 해당
- 파일이 존재하는지 확인하기 위해 해당 경로에 파일이 있는지 검사.
- 파일이 존재하면 해당 파일을 읽어 바이트 배열로 가져오기
- 파일의 MIME 타입을 확인하기 위해 Files.probeContentType 메소드를 사용, 이 메소드는 파일의 MIME 타입을 반환하는 메소드!
- response200Header 메소드를 호출하여 200 OK 응답 헤더를 생성합니다. Content-Type 헤더에는 파일의 MIME 타입과 문자 인코딩 정보*("charset=UTF-8")*를 포함하여 설정합니다. Content-Length 헤더에는 응답 바디의 길이를 설정!
코드에서 해당 기능을 하는 부분을 다시보면 아래와 같습니다.
byte[] fileData = Files.readAllBytes(Paths.get(filePath)); // 파일 읽기 String contentType = Files.probeContentType(Paths.get(filePath)); // MIME 타입 확인 // 응답 헤더 생성 (200 OK, 파일의 길이, MIME 타입, 문자 인코딩 정보) response200Header(dos, fileData.length, contentType + "; charset=UTF-8");
잠깐 틈새라면처럼 생소한 단어 정리하고 가보겠습니다.
MIME 타입 ?
MIME(Multipurpose Internet Mail Extensions) 타입은 전자 메일 및 웹에서 파일의 형식을 식별하는데 사용되는 식별자입니다. MIME 타입은 파일의 성격과 형식을 특정하기 위해 사용되며, 클라이언트에게 어떻게 파일을 처리할지 알려줍니다. 예를 들어, 텍스트 문서, HTML 페이지, 이미지, 오디오, 비디오 등의 파일 형식을 MIME 타입을 통해 지정할 수 있습니다.
- text/html: HTML 문서
- image/jpeg: JPEG 이미지
- application/pdf: PDF 문서
- audio/mp3: MP3 오디오
- video/mp4: MP4 비디오
응답 바디 길이는 왜 필요한가?
응답 바디의 길이는 클라이언트가 데이터를 안전하게, 효율적으로 처리하고 표시할 수 있도록 도와주며, 데이터 통신의 품질과 효율성을 향상시킵니다.
명시적으로 받아주는 것이 중요합니다!
클라이언트에게 302 Redirect 응답
response302Header와 response302HeaderWithCookie는 HTTP 응답의 상태 코드 302를 생성하는 역할을 합니다. 302 상태 코드는 리다이렉션을 나타냅니다. 클라이언트는 새로운 URL로 이동해야 함을 나타냅니다.
HTTP 응답의 기본적인 구조
HTTP/1.1 302 Redirect Location: [리다이렉트할 URL] [추가 헤더 (옵션)] [빈 줄]
response302Header 메서드는 그냥 302 상태 코드와 리다이렉트할 위치를 반환해주고, response302HeaderWithCookie 메서드는 추가로 쿠키를 설정까지..
response302Header 메서드
private void response302Header(DataOutputStream dos, String path) { try { dos.writeBytes("HTTP/1.1 302 Redirect\\r\\n"); dos.writeBytes("Location: " + path + "\\r\\n"); dos.writeBytes("\\r\\n"); dos.flush(); } catch (IOException e) { log.log(Level.SEVERE, e.getMessage()); } }
위 메서드는 302 상태 코드를 반환하여 클라이언트에게 리다이렉션을 알리고, Location 헤더를 통해 리다이렉트할 위치를 전달합니다.
예시 HTTP 응답
HTTP/1.1 302 Redirect Location: [리다이렉트할 URL]
response302HeaderWithCookie 메서드
private void response302HeaderWithCookie(DataOutputStream dos, String path) { try { dos.writeBytes("HTTP/1.1 302 Redirect\\r\\n"); dos.writeBytes("Location: " + path + "\\r\\n"); dos.writeBytes("Set-Cookie: logined=true\\r\\n"); dos.writeBytes("\\r\\n"); dos.flush(); } catch (IOException e) { log.log(Level.SEVERE, e.getMessage()); } }
위 메서드는 response302Header와 유사하지만, 추가로 Set-Cookie 헤더를 설정하여 클라이언트에게 쿠키를 설정하도록 지시합니다.
예시 HTTP 응답
HTTP/1.1 302 Redirect Location: [리다이렉트할 URL] Set-Cookie: logined=true
이렇게 HTTP 메세지를 분석해서 활용하고 작성하는 것을 살펴보았습니다!
'백엔드 : 서버공부' 카테고리의 다른 글
람다사용하면서 느낀점 (0) 2024.11.24 - String startLine = br.readLine();