ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 서버공부 시작 { HTTP } : 간단한 웹서버 띄우기
    백엔드 : 서버공부 2023. 10. 2. 14:06
    728x90

    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
    
    1. String startLine = br.readLine();
      • 이 부분에서 startLine은 "GET /index.html HTTP/1.1"이 됩니다.
    2. String[] startLineTokens = startLine.split(" ");
      • startLineTokens 배열에는 **["GET", "/index.html", "HTTP/1.1"]**이 저장됩니다.
    3. String method = startLineTokens[0];
      • method에는 "GET"이 저장됩니다.
    4. 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>
    
    1. String startLine = br.readLine();
      • startLine은 "HTTP/1.1 200 OK"이 됩니다.
    2. 응답 코드 해석
      • HTTP/1.1: HTTP 프로토콜 버전.
      • 200: HTTP 응답 코드. 여기서는 "OK"를 나타냅니다. 성공적인 응답을 나타냅니다.
      • Content-Type: text/html: 응답의 내용이 HTML임을 나타내는 헤더.
      • Content-Length: 1234: 응답의 본문 길이가 1234바이트임을 나타내는 헤더.
    3. 헤더 정보 추출
      • 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.
    
    1. Start Line
      • GET /path/to/page.html HTTP/1.1
      • 이 부분은 요청 메서드 (GET), 요청 URL (/path/to/page.html), 그리고 프로토콜 버전 (HTTP/1.1)으로 구성됩니다.
    2. Headers
      • Host: www.example.com
      • User-Agent: ...
      • Accept: ...
      • Content-Length: 20
      • Cookie: sessionId=abc123; loggedIn=true
      • 헤더는 key: value 형태로 되어 있으며, 각 헤더는 요청에 대한 부가 정보를 제공합니다. 여기서는 Content-Length와 Cookie 헤더를 추출할 예정입니다.
    3. 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);
    
    1. GET 요청이고 확장자가 .html로 끝나는 경우
      • 해당 HTML 파일을 읽어와 응답으로 전송합니다.
    2. 루트 URL인 경우 (/)
      • 홈 페이지의 HTML 파일을 읽어와 응답으로 전송합니다.
    3. /user/signup URL로 요청이 들어온 경우
      • 회원 가입 처리를 수행합니다.
    4. /user/login URL로 요청이 들어온 경우
      • 로그인 처리를 수행합니다.
    5. /user/userList URL로 요청이 들어온 경우
      • 사용자 목록 조회 처리를 수행합니다.
    6. GET 요청이고 확장자가 .css로 끝나는 경우
      • 해당 CSS 파일을 읽어와 응답으로 전송합니다.
    7. 그 외의 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
    1. 클라이언트는 POST 메서드를 사용하여 /user/signup URL에 요청을 보냅니다.
    2. 요청 헤더에는 Content-Type이 application/x-www-form-urlencoded로 설정되어 있으며, 요청 바디의 길이는 51입니다.

     

    handleSignup 메소드가 호출되면 다음과 같이 동작합니다....

    1. IOUtils.readBody(br, contentLength)를 통해 요청 바디를 읽어옵니다. 이 메소드는 전달된 BufferedReader를 사용하여 바디의 내용을 읽어옵니다.
    2. HttpRequestUtils.parseQueryParameter(requestBody)를 사용하여 바디에서 파라미터를 추출하고 맵 형태로 저장합니다. 여기서는 userId, password, name, email을 추출합니다. (여기서도 split 사용)
    3. 추출한 데이터를 사용하여 새 User 객체를 생성합니다.
    4. repository.addUser(newUser)를 호출하여 사용자를 저장합니다.
    5. 마지막으로, 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);
            }
        }
    
    1. Cookie 체크하기
      • 클라이언트의 요청에서 쿠키를 확인합니다. 쿠키는 "logined=true"로 설정되어 있는지 확인합니다.
    2. 로그인 상태 User 처리
      • 쿠키가 존재하고 "logined=true"로 설정되어 있다면, 로그인 상태로 간주하여 사용자 목록 조회 처리를 수행합니다.
      • 사용자 목록을 읽어와 응답을 생성합니다.
    3. 로그인 상태가 아닌 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
    
    1. 메서드가 클라이언트의 요청에서 파일 경로를 추출합니다. 예시에서 "/path/to/file.html”에 해당
    2. 파일이 존재하는지 확인하기 위해 해당 경로에 파일이 있는지 검사.
    3. 파일이 존재하면 해당 파일을 읽어 바이트 배열로 가져오기
    4. 파일의 MIME 타입을 확인하기 위해 Files.probeContentType 메소드를 사용, 이 메소드는 파일의 MIME 타입을 반환하는 메소드!
    5. 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
Designed by Tistory.