Skip to content

이호석 2주차 java‐was 학습일지

이호석 edited this page Jul 5, 2024 · 1 revision

목차




✅ 웹 서버 1단계 - index.html 응답

1. 미션 진행

public class Main {

    private static final Logger log = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080); // 8080 포트에서 서버를 엽니다.
        System.out.println("Listening for connection on port 8080 ....");

        while (true) { // 무한 루프를 돌며 클라이언트의 연결을 기다립니다.
            try (Socket clientSocket = serverSocket.accept();
                 BufferedReader requestReader = new BufferedReader(
                         new InputStreamReader(clientSocket.getInputStream()))
            ) { // 클라이언트 연결을 수락합니다.

								// 이렇게 되면 lines()에서 
                requestReader.lines()
                        .forEach(log::info);
                log.info(request.toString());
                System.out.println("Client connected");

                // HTTP 응답을 생성합니다.
                OutputStream clientOutput = clientSocket.getOutputStream();
                clientOutput.write("HTTP/1.1 200 OK\r\n".getBytes());
                clientOutput.write("Content-Type: text/html\r\n".getBytes());
                clientOutput.write("\r\n".getBytes());
                clientOutput.write("<h1>Hello</h1>\r\n".getBytes()); // 응답 본문으로 "Hello"를 보냅니다.
                clientOutput.flush();
            }
        }
    }
}
  • 대충 위 코드가 안되는 이유는 밑도 끝도 없이 forEach로 불러와서 인가?

  • while문을 통해 isEmpty인지 검증하면서 진행하면 SocketException: Broknen Pipe가 발생하지 않음

    // 동작 코드
    public class Main {
    
        private static final Logger log = LoggerFactory.getLogger(Main.class);
    
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8080); // 8080 포트에서 서버를 엽니다.
            System.out.println("Listening for connection on port 8080 ....");
    
            while (true) { // 무한 루프를 돌며 클라이언트의 연결을 기다립니다.
                try (Socket clientSocket = serverSocket.accept();
                     BufferedReader requestReader = new BufferedReader(
                             new InputStreamReader(clientSocket.getInputStream()));
                     OutputStream clientOutput = clientSocket.getOutputStream()) { // 클라이언트 연결을 수락합니다.
    
                    String httpRequest = printRequest(requestReader);
                    log.debug(httpRequest);
    
                    System.out.println("Client connected");
    
                    FileInputStream fileInputStream = new FileInputStream("src/main/resources/static/index.html");
    
                    // HTTP 응답을 생성합니다.
                    clientOutput.write("HTTP/1.1 200 OK\r\n".getBytes());
                    clientOutput.write("Content-Type: text/html\r\n".getBytes());
                    clientOutput.write("\r\n".getBytes());
                    clientOutput.write(fileInputStream.readAllBytes()); // 응답 본문으로 "Hello"를 보냅니다.
                    clientOutput.flush();
    
                    fileInputStream.close();
                }
            }
        }
    
        private static String printRequest(final BufferedReader requestReader) throws IOException {
            StringBuilder httpRequestBuilder = new StringBuilder();
            String requestLine;
            while (!(requestLine = requestReader.readLine()).isEmpty()) {
                httpRequestBuilder.append(requestLine)
                        .append(System.lineSeparator());
            }
    
            return httpRequestBuilder.toString();
        }
    }
  • 1차적으로 완성된 코드

    package codesquad;
    
    import java.io.BufferedReader;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class Main {
    
        private static final Logger log = LoggerFactory.getLogger(Main.class);
    
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8080); // 8080 포트에서 서버를 엽니다.
            System.out.println("Listening for connection on port 8080 ....");
    
            ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 1000L, TimeUnit.MICROSECONDS,
                    new LinkedBlockingQueue<>());
    
            while (true) { // 무한 루프를 돌며 클라이언트의 연결을 기다립니다.
                executor.execute(() -> {
                    try (Socket clientSocket = serverSocket.accept();
                         BufferedReader requestReader = new BufferedReader(
                                 new InputStreamReader(clientSocket.getInputStream()));
                         OutputStream clientOutput = clientSocket.getOutputStream();
                         FileInputStream fileInputStream = new FileInputStream("src/main/resources/static/index.html")
                    ) { // 클라이언트 연결을 수락합니다.
    
                        String httpRequest = printRequest(requestReader);
                        log.debug(httpRequest);
    
                        System.out.println("Client connected");
    
                        // HTTP 응답을 생성합니다.
                        clientOutput.write("HTTP/1.1 200 OK\r\n".getBytes());
                        clientOutput.write("Content-Type: text/html\r\n".getBytes());
                        clientOutput.write("\r\n".getBytes());
                        clientOutput.write(fileInputStream.readAllBytes()); // 응답 본문으로 "Hello"를 보냅니다.
                        clientOutput.flush();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                });
            }
        }
    
        private static String printRequest(final BufferedReader requestReader) throws IOException {
            StringBuilder httpRequestBuilder = new StringBuilder();
            String requestLine;
            while (!(requestLine = requestReader.readLine()).isEmpty()) {
                httpRequestBuilder.append(requestLine)
                        .append(System.lineSeparator());
            }
    
            return httpRequestBuilder.toString();
        }
    }
  • 결국 serverSocket.accept를 통해 클라이언트 소켓을 받아서 처리하는 것이므로, 이런 커넥션이 들어왔을때에 대한 처리를 ConnectionHandler로 분리할 수 있음

    public class ConnectionHandler implements Runnable {
    
        private final Logger log = LoggerFactory.getLogger(Main.class);
    
        private final Socket clientSocket;
    
        public ConnectionHandler(final Socket clientSocket) {
            this.clientSocket = clientSocket;
        }
    
        @Override
        public void run() {
            try (BufferedReader requestReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                 OutputStream clientOutput = clientSocket.getOutputStream();
                 FileInputStream fileInputStream = new FileInputStream("src/main/resources/static/index.html")) {
    
                String httpRequestInformation = printRequest(requestReader);
    
                log.debug(httpRequestInformation);
                log.debug("Client connected");
    
                // HTTP 응답을 생성합니다.
                clientOutput.write("HTTP/1.1 200 OK\r\n".getBytes());
                clientOutput.write("Content-Type: text/html\r\n".getBytes());
                clientOutput.write("\r\n".getBytes());
                clientOutput.write(fileInputStream.readAllBytes()); // 응답 본문으로 "Hello"를 보냅니다.
                clientOutput.flush();
            } catch (IOException e) {
                log.error("요청을 처리할 수 없습니다.");
                throw new RuntimeException("요청을 처리할 수 없습니다.", e);
            } finally {
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    log.error("클라이언트 소켓을 닫을 수 없습니다.");
                    throw new RuntimeException(e);
                }
            }
        }
    
        private String printRequest(final BufferedReader requestReader) throws IOException {
            StringBuilder httpRequestBuilder = new StringBuilder();
            String requestLine;
            while (!(requestLine = requestReader.readLine()).isEmpty()) {
                httpRequestBuilder.append(requestLine)
                        .append(System.lineSeparator());
            }
    
            return httpRequestBuilder.toString();
        }
    }
    
    public class Main {
    
        private static final Logger log = LoggerFactory.getLogger(Main.class);
    
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8080); // 8080 포트에서 서버를 엽니다.
            log.debug("Listening for connection on port 8080 ....");
    
            ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 0, TimeUnit.MICROSECONDS,
                    new LinkedBlockingQueue<>());
    
            while (true) { // 무한 루프를 돌며 클라이언트의 연결을 기다립니다.
                executor.execute(new ConnectionHandler(serverSocket.accept()));
            }
        }
    }

2. 자바 버전별 스레드 기술 사용 변화

  • Java 1.0

    • 기본 스레드 (java.lang.Thread 클래스, java.lang.Runnable 인터페이스 지원)
    • Thread synchronization: synchronized 키워드 및 wait(), notify(), notifyAll()과 같은 메서드로 기본적 동기화 메커니즘 제공함
  • Java 1.2

    • ThreadLocal: ThreadLocal 클래스가 도입되어 스레드별로 독립적인 변수를 쉽게 사용할 수 있게 됨(파라미터를 사용하지 않고 객체를 전파하기 위한 용도)

    image

  • Java 1.4

    • java.util.concurrent.atomic: 원자적 연산을 위한 클래스들 도입됨
    • NIO(New Input/Output): 비동기 I/O를 지원하여 더 효율적인 스레드 관리를 가능하게 함
  • Java 1.5

    • java.util.concurrent 패키지: 동시성 유틸리티가 포함된 패키지 도입됨
      • Executor, ExecutorService, Callable, Future
      • ReentranLock 클래스 및 ReadWriteLock 인터페이스
      • ThreadFactory 인터페이
  • Java 1.7

    • NIO2 등장: File을 다루는 부분이 크게 개선되었다고 함
  • Java 8 (2014)

    • CompletableFuture: 비동기 프로그래밍을 더 쉽게 할 수 있도록 하는 기능이 추가되었습니다.
    • Lambda Expressions: 람다 표현식이 도입되어 스레드 코드가 더 간결해졌습니다.
    • Parallel Streams: 스트림 API가 병렬 처리를 지원하게 되어 스레드 관리가 더 쉬워졌습니다.
  • Java 9

    • Reactive Streams: java.util.concurrent.Flow API로 반응형 스트림을 지원
  • Java 11

    • HttpClient: 새로운 HttpClient API가 비동기 HTTP 호출을 지원하도록 개선됨
  • Java 18

    • Virtual Threads(Project Loom) 정식 도입
  • Java 20

    • Project Loom의 가상 스레드가 정식으로 포함되어 더 최적화되고 안정화

3. 내가 생각하는 향후 지향점

  • 뭔가 유저 스레드와 커널 스레드가 매핑되는것에 대한 부담이 크기에, 스레드를 최대한 경량화하여 성능을 끌어올리기 위해 노력하는게 아닌가? 하는 생각이 든다.
  • 또한 지속적으로 동시성 처리를 잘 할 수 있게끔 하는 라이브러리들이 추가되어왔다.

4. ExecutorServiceThreadPoolExecutor에 대해

  • 개요

    풀링된 여러 스레드 중 하나를 사용하여 제출된 각 작업을 실행하는 ExecutorService로, 일반적으로 Executors 팩토리 메서드를 사용하여 구성

    스레드 풀은 일반적으로 많은 수의 비동기 작업을 실행할 때 작업당 호출 오버헤드가 감소하여 성능이 향상되고, 작업 모음을 실행할 때 소비되는 스레드를 포함한 리소스를 제한하고 관리하는 수단을 제공한다는 점에서 두 가지 문제를 해결합니다. 각 스레드풀실행자는 완료된 작업 수와 같은 몇 가지 기본 통계도 유지합니다.

    다양하게 사용할 수 있도록, 많은 매개변수와 확장성 훅을 제공하는데 일반적으로 Excutors의 팩토리 메소드를 통해(newCachedThreadPool(자동 스레드 회수 기능이 있는 무제한 스레드 풀), newFixedThreadPool(고정 크기 스레드 풀), newSingleThreadExecutor(단일 백그라운드 스레드)) 사용할 것을 권장합니다.

  • 수동 사용을 위해서는 다음 가이드를 참조 해야 한다.

    • core and maximum pool sizes
      • 새 작업이 submit되었고, 실행 중인 스레드가 corePoolSize보다 작다면 요청 처리를 위해 새로운 스레드를 생성합니다.
      • corePoolSize보다 많은 Thread가 수행되고 있지만, maxPoolSize보다 적은 수의 Thread가 수행되고 있는 경우:
        1. Queue가 가득 차지 않은 경우: 즉시 실행하지 않고 Queue에 Runnable을 넣는다.
        2. Queue가 가득 찬 경우: maxPoolSize까지 Thread를 더 만들어 실행한다.
        • 즉 Queue를 채우는 작업을 우선시 한다.
      • 반면에 corePoolSize와 maximumPoolSize가 동일하다면 고정 크기의 스레드 풀이 생성됩니다.
      • 따라서 maximumPoolSize를 본질적으로 무제한 값으로 설정하면 스레드 풀은 임의의 갯수의 동시 작업을 수용하도록 허용할 수 있습니다. 즉, 스레드 풀에 상주하는 스레드의 갯수의 최댓값을 고정시킬 수 있다~
      • 이런 값들은 별도의 setter를 통해 동적으로 변경할 수 있습니다.
    • On-demand construction
      • 코어 스레드는 새 작업이 도착할때 생성 및 시작됩니다. 다만 prestartCoreThread나 prestartAllCoreThreads 메소드를 사용하여 동적으로 비어있지 않은 큐(스레드를 미리 생성해놓고)를 두고 시작할 수 있습니다.
    • Creating new threads
      • 새로운 스레드는 ThreadFactory를 사용하여 생성됩니다. 별도의 지정이 없으면 Executors.defaultThreadFactory 메소드가 사용됩니다.
      • 이때 스레드는 모두 동일 ThreadGroup에, 동일한 NORM_PRIORITY 우선순위(5)와 데몬 스레드가 아닌 상태로 생성됩니다. 만약 다른 ThreadFactory를 제공하면 스레드의 이름, 스레드 그룹, 우선순위, 데몬 상태 등을 변경할 수 있습니다.
      • ThreadFactory가 newThread에서 null을 반환하여 스레드 생성에 실패하면 executor는 실행되어도 작업 실행을 못할 수 있습니다.
      • 스레드에는 modifyThread 런타임 권한이 있어야 합니다. 풀을 사용하는 워커 스레드나 다른 스레드가 이 권한을 가지고 있지 않다면 구성 변경 사항이 적시에 적용되지 않고 종료 풀이 완료되지 않은 상태를 그대로 가지고 종료되어 서비스 성능의 저하 우려가 있습니다.
    • Keep-alive times
      • 풀에 현재 코어 풀 사이즈 스레드보다 많은 스레드가 있는 경우, 초과 스레드가 keepAliveTime보다 오래 유휴 상태라면 해당 스레드를 종료합니다.
      • 풀이 활발히 사용되지 않을때 리소스 소비를 줄이는 수단을 제공합니다.
      • setter를 사용해 동적 변환도 가능합니다
      • 기본적으로 keep-alive 정책은 코어풀 사이즈보다 많은 스레드가 있는 경우에만 적용되지만, allCoreThreadTimeOut(boolean) 메서드를 사용하면 keepAliveTime 값이 0이 아닌 경우 코어 스레드에서도 이 시간 초과 정책을 적용할 수 있습니다.
    • Queuing(대기열)
      • 모든 BlockingQueue는 제출된 작업 전송, 보류하는데 사용할 수 있습니다. 또한 큐의 사용은 풀 크기 조정과 상호작용하게 됩니다.
      • corePoolSize보다 적은 수의 스레드가 실행중이라면, executor는 항상 대기열보다 새로운 스레드를 추가하는 걸 선호합니다.
      • 만약 corePoolSize 이상의 스레드가 실행중이라면, executors는 항생 새 스레드를 추가하는 것보다 요청을 대기열에 추가하는 것을 선호합니다.
      • 만약 요청을 대기열에 넣을 수 없다면, maximumPoolSize를 초과하지 않는 한 새 스레드가 생성되고, 이 경우에는 작업이 거부됩니다.
      • 대기열에는 일반적인 3가지 전략이 존재합니다.
        1. Direct handoffs.

          이 전략을 사용할때 작업 큐에 대한 좋은 기본적인 선택은 스레드에 작업을 넘겨주는 SynchronousQueue입니다. 만약 작업을 즉시 실행 할 수 있는 스레드가 없는 경우 작업을 큐에 넣으려는 시도가 실패하므로 새 스레드가 만들어집니다.

          새로 제출된 작업이 거부되는걸 방지하기 위해 unbounded maximumPoolSizes가 필요합니다. 이는 처리 속도보다 작업이 쌓이는 양이 많을때 무제한으로 스레드가 증가할 수 있는 가능성이 있다는 의미입니다.

        2. Unbounded queues (무제한 대기열)

          무제한 큐(미리 정의된 용량 없는 LinkedBlockingQueue)를 사용하면 모든 corePoolSize 스레드가 사용 중일 때 새 작업이 큐에서 대기하게 됩니다. 따라서 corePoolSize 스레드는 더 이상 생성되지 않게 됩니다. (이때 maximumPoolSize 값은 아무런 영향을 미치지 않음)

          웹 페이지 서버와 같이 각 작업이 서로 완전히 독립적 이어서 서로의 실행에 영향을 미칠 수 없는 경우에 적합할 수 있습니다.

          일시적인 요청 폭주를 원활하게 처리하는데 유용할 수 있지만, 명령이 평균적으로 처리할 수 있는 속도보다 빠르게 도착하면 작업 큐가 무한대로 늘어날 가능성을 인정하게 됩니다.

        3. Bounded queues.

          한정된 큐(ArrayBlockingQueue)는 유한 최대 풀 크기와 함께 사용할 때 리소스 고갈을 방지하는데 도움이 됩니다.

          하지만 이런 사이즈를 조정하거나 제어하는것이 더 어려울 수 있습니다.

          • Queue Size, maximumuPoolSizes는 서로 트레이드 오프 관계에 있습니다.
            • big size queue and small pools: CPU 사용량, OS 리소스 및 컨텍스트 전환 오버헤드가 최소화 됨, 하지만 처리량이 인위적으로 낮아질 수 있음

              작업이 자주 block 되는 경우(I/O 바인딩과 같은) 시스템에서 허용하는 것보다 더 많은 스레드 스케줄 시간이 예약될 수 있습니다.

            • small size queue and large pools: 더 큰 풀 크기가 필요하므로 CPU가 더 바빠지지만, 허용할 수 없는 스케줄링 오버헤드가 발생해 처리량도 감소될 수 있습니다.

  • 생성자

    image

    • corePoolSize: allowCoreThreadTimeOut 이 설정되지 않은 경우 유휴 상태일때 스레드 풀에서 유지할 최대 스레드의 갯수를 말합니다.
    • maximumPoolSize: 풀에 허용할 최대 스레드 갯수
    • keepAliveTime: 스레드 수가 코어보다 많은 경우, 초과 유휴 스레드가 종료되기 전에 새 작업을 대기하는 최대 시간
    • unit: keepAliveTime의 시간 단위
    • workQueue: 작업이 실행되기 전에 대기하는 데 사용할 큐가 된다. 큐는 execute Method가 제공한 Runnable한 작업은 보관할 수 있습니다.



✅ 웹 서버 2단계 - 다양한 컨텐츠 타입 지원

기존 구조 분리

image


Request HTTP Header나 Respose할 컨텐츠의 MIME타입을 찾지 못한다면?

초기에는 예외를 반환하기로 결정했었으나, RFC 7230문서를 보면 HTTP Header를 커스텀하게 사용할 수 있다고 명시되어 있기에 일단 NONE이라는 특수 값을 반환하고 반환받은곳에서는 NONE 헤더인 경우 로깅을 하도록 대처 → 후에 개선이 필요해 보입니다.

MIME타입의 경우에는 알 수 없는 파일 유형의 경우 application/octet-stream을 사용하도록 MDN 문서에서 언급되고 있기에 예외를 던지지 않도록 리팩토링 했습니다~!


HTTP Response

  • Status Line
  • headers
  • Message Body

HTTP Request

  • Request Line
  • HTTP headers
  • HTTP Message Body

MIME Type



✅ 웹 서버 3단계 - GET으로 회원가입

1. Query Parameter 파싱하기

  1. Query Parameterform data로 올 수 있고, URL 쿼리스트링으로 올 수 있음

    URL 쿼리 스트링은 RequestLine에서 관리하고

    Form Data는 Message Body에서 관리하도록 하려고 함

    URL 쿼리스트링form data의 쿼리스트링은 모두 동일한 형식이므로 HttpRequest가 공통으로 관리하도록 리팩토링 했습니다!


2. 요청한 리소스에 알맞은 핸들러 찾기

// 리팩토링 이전
public class ConnectionHandler implements Runnable {

    private final Logger log = LoggerFactory.getLogger(getClass());
    private final Socket clientSocket;

    public ConnectionHandler(final Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        try (InputStream clientInput = clientSocket.getInputStream();
             OutputStream clientOutput = clientSocket.getOutputStream()
        ) {
            HttpRequest httpRequest = new HttpRequest(clientInput);

            log.debug("Http Request = {}", httpRequest);
            log.debug("Client connected");

            HttpResponse httpResponse = createResponse(StatusCodeType.OK, httpRequest);

            clientOutput.write(httpResponse.getResponseBytes());
        } catch (IOException e) {
            log.error("요청을 처리할 수 없습니다.", e);
        }
    }

    private HttpResponse createResponse(final StatusCodeType statusCodeType, final HttpRequest httpRequest)
            throws IOException {
        String requestPath = httpRequest.getRequestPath();

        URL fileUrl = getClass().getClassLoader().getResource("static" + httpRequest.getRequestPath());
        try (InputStream inputStream = fileUrl.openStream()) {
            Headers headers = new Headers();
            headers.add(HeaderType.CONTENT_TYPE, MimeType.findMimeValue(StringUtils.getFilenameExtension(requestPath)));

            return new HttpResponse(
                    new StatusLine(httpRequest.getHttpVersion(), statusCodeType),
                    headers,
                    new ResponseMessageBody(inputStream.readAllBytes()));
        }
    }
}
// 리팩토링 이후
public class ConnectionHandler implements Runnable {

    private final Logger log = LoggerFactory.getLogger(getClass());
    private final Socket clientSocket;
    private final RequestHandlerMapping requestHandlerMapping;

    public ConnectionHandler(final Socket clientSocket, final RequestHandlerMapping requestHandlerMapping) {
        this.clientSocket = clientSocket;
        this.requestHandlerMapping = requestHandlerMapping;
    }

    @Override
    public void run() {
        try (InputStream clientInput = clientSocket.getInputStream();
             OutputStream clientOutput = clientSocket.getOutputStream()
        ) {
            log.debug("Client connected");

            HttpRequest httpRequest = new HttpRequest(clientInput);
            log.debug("Http Request = {}", httpRequest);

            HttpResponse httpResponse = new HttpResponse(clientOutput, httpRequest.getHttpVersion());

            RequestHandler requestHandler = requestHandlerMapping.read(httpRequest.getRequestPath());
            requestHandler.process(httpRequest, httpResponse);
        } catch (IOException e) {
            log.error("요청을 처리할 수 없습니다.", e);
        }
    }
}

redirect를 보내거나, 파일에 대한 포워딩 요청을 직접 보낼때 responseSocketOutputStream에 대한 책임을 갖는것이 좀 더 직관적이라고 생각했기 때문에 찾아온 requestHandler가 로직을 처리하고, HttpResonse에게 직접 요청을 처리하도록 리팩토링을 시도했습니다.


3. HttpResonse가 정적 파일 요청을 직접 마무리 하도록 리팩토링하기

public void forward(final String requestPath) throws IOException {
    URL fileUrl = getClass().getClassLoader().getResource("static" + requestPath);

    try (InputStream inputStream = fileUrl.openStream()) {
        byte[] fileBytes = inputStream.readAllBytes();
        headers.add(HeaderType.CONTENT_TYPE, MimeType.findMimeValue(StringUtils.getFilenameExtension(requestPath)));
        headers.add(HeaderType.CONTENT_LENGTH, String.valueOf(fileBytes.length));
        statusLine.setResponseStatus(StatusCodeType.OK);

        sendResponse(fileBytes);
    }
}

private void sendResponse(final byte[] responseBytes) throws IOException {
    dataOutputStream.writeBytes(getStatusLineMessage() + CRLF + getHeaderMessage() + CRLF + CRLF);
    dataOutputStream.write(responseBytes);
}

HTTP Response에게 파일에 대한 requestPath를 넘겨주면 해당 Path를 통해 forwarding 작업을 합니다.

단순하게 정적인 파일을 서빙하는 용도로도 forwarding이 사용되지만, 요청 URI가 파일이 아니고 /login과 같은경로 매핑이라면 해당 경로 전용 핸들러가 실행을 하고, 알맞은 파일을 forwarding하도록 할 수 있습니다.

이때 WAS의 영역과, WAS를 사용하는 사용자의 영역을 나눠서 생각했는데

Request, Response를 안전하게 제공하는 영역은 WAS의 영역, 핸들러에 대한 비즈니스 로직을 만들고, 적절한 파일을 찾아서 포워딩 하는 작업을 사용자의 영역으로 두고 구분하여 작업했습니다.


4. 리다이렉트

리다이렉트를 수행하는 주체는 브라우저입니다. 따라서 웹 서버에서는 리다이렉트를 위해 응답으로 3xx번을 보내고 ResponseLocation 헤더에는 브라우저가 리다이렉트 할 URL을 넘겨주면, 브라우저는 status code를 보고 Location 헤더에 기록된 위치로 리다이렉트하게 됩니다.

따라서 HttpReponse에 sendRedirect를 호출하면 즉시 dataOutputStream을 통해 리다이렉션 명령을 처리하기 위해 response정보를 write하고 보냅니다.

2주차 웹 서버 만들기 완성된 흐름

image


고민하고 있는 부분들

  • 🤔  RequestMapping을 어떻게 유연하게 관리할 수 있을까?
  • 🤔 단순 Forwarding 로직인데 핸들러 클래스로 일일이 분리해야 할까?
  • 🤔 너무 구현에만 몰두한건 아닌가? 지금 배우는 것에 대해 어떻게 학습해야 할까..?- 자바 버전별 스레드 기술 사용 변화
  • 🤔 Server Socket Client Socket 어떻게 접속되는지!!

👼 개인 활동을 기록합시다.

개인 활동 페이지

🧑‍🧑‍🧒‍🧒 그룹 활동을 기록합시다.

그룹 활동 페이지

🎤 미니 세미나

미니 세미나

🤔 기술 블로그 활동

기술 블로그 활동

📚 도서를 추천해주세요

추천 도서 목록

🎸 기타

기타 유용한 학습 링크

Clone this wiki locally