티스토리 뷰

donaricano-btn
반응형

웹 서버의 가장 기본적인 기능은 클라이언트의 요청을 받아 요청된 자원을 넘겨주는 것입니다. 그러려면 일단 클라이언트가 보내는 HTTP Requset를 받고 해석할 수 있어야 합니다. 그럼 먼저 HTTP Request 가 어떤 모습으로 전송 되는지를 알아 보겠습니다.

 

https://developer.mozilla.org/ko/docs/Web/HTTP/Messages

실제로 웹 브라우저가 보내는 HTTP Request의 모습입니다. 가장 첫번째 줄에는 요청에 대한 요약된 정보가 전해집니다. 가장 처음에 등장하는 것은 HTTP method 종류입니다(참고). 이 메소드는 이 요청이 수행해야할 동작에 대한 정보를 담고 있습니다. 그리고 나오는 것은 요청된 URL입니다. 이 URL은 요청한 자원의 절대경로일 수도 있고 매핑된 서블릿의 이름일 수도 있습니다. 그리고 마지막으로 HTTP 버전 정보가 표시됩니다.

 

두번째 줄 부터는 HTTP Request header가 이어집니다. 대소문자 구분 없는 문자열을 콜론(' ; ')으로 구분해서 정보를 전달합니다. 헤더를 통해 사용하는 브라우저에 대한 정보, 인코딩 정보, 데이터 타입, 본문의 길이 등 필요한 정보를 전달합니다. 헤더의 끝에는 공백라인이 한 줄 있습니다. 만약 서버로 전달하는 데이터가 있다면 이 공백라인 다음에 데이터 정보가 이어집니다.

 

본격적으로 HTTP Request 를 전송 받아 보도록하겠습니다. 실제 클라이언트의 요청이 처리되는 과정을 따라가며 코드를 살펴 보겠습니다.

 

(1) Server Socket 을 열고 요청을 기다리기

    public void boot() {
        try {
            ServerSocket serverSocket = new ServerSocket(portNumber);
            Socket socket = serverSocket.accept();
            readRequest(socket.getInputStream());
            setRequest();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

일단 요청을 받을 준비를 해야 합니다. ServerSocket을 열고 브라우저에서 보내는 요청을 기다립니다. 소켓 통신에 대해서는 이곳을 참고하세요.

 

(2) HTTP Request 읽기

    private void readRequest(InputStream inputStream) throws IOException {
        byte[] readBuffer = new byte[128];
        while (!isEndOfRequest()) {
            inputStream.read(readBuffer);
            readData(readBuffer);
        }
    }

서버 소켓으로 접속 요청이 들어오면 InputStream을 열고 요청 정보를 읽어들입니다. 바이트 버퍼의 크기만큼 읽어들인 이후 내용을 분석합니다.

    private void readData(byte[] readBuffer) {
        for (byte thisByte : readBuffer) {
            if (isEndOfRequest()) {
                return;
            }
            if (isBody) {
                readBodyByte(thisByte);
                continue;
            }
            if (isEndOfLine(previousByte, thisByte)) {
                readLine();
                lineBuilder.setLength(0);
                continue;
            }
            lineBuilder.append((char) thisByte);
            previousByte = thisByte;
        }
    }

반복문 안에 첫 번째 분기문이 의미하는 것은 HTTP Request가 끝났는지 입니다. 모든 데이터를 다 읽어서 더이상 읽을 데이터가 없다면 메소드를 빠져 나옵니다. 두 번째 분기문은 지금 읽고 있는 바이트가 헤더인지 바디인지를 판단하는 것입니다. 바디라면 단순히 바디 바이트를 저장하는 메소드를 호출하고 아래에 있는 코드를 무시합니다. 세번째 분기문은 헤더의 한 라인을 끝까지 읽었는지를 판단합니다. 만약 끝이 아니라면 StringBuilder를 통해 문장을 계속 써 내려가고 문장의 끝이라면 헤더맵에 헤더를 추가하는 메소드를 호출합니다.

 

(3) 헤더 추가

    private void readHeaderLine(String oneLine) {
        if (isLastOfHeader(oneLine)) {
            initBodyVar();
            return;
        }
        int indexOfColon = oneLine.indexOf(COLON);
        String headerName = oneLine.substring(0, indexOfColon).trim();
        String headerValue = oneLine.substring(indexOfColon + 1).trim();
        headerMap.put(headerName, headerValue);
    }

헤더라인의 끝이라면 먼저 생각해야 할 것이 이게 헤더 전체의 끝인지를 판단해야 합니다. 헤더라인 전체의 끝이라면 다음부터는 (존재한다면) body 데이터가 들어올 것입니다. 헤더의 마지막 라인이 아니라면 헤더 맵에 콜론(':')을 기준으로 키와 값을 구분해서 저장합니다.

 

(4) 파라미터 값 읽기

    private void setRequestParameter(String query) {
        String[] parameters = query.split(AMPERSAND);
        Map<String, String> parameterMap = new HashMap<>();
        for (String parameter : parameters) {
            String[] keyAndValue = parameter.split(EQUAL_SIGN);
            parameterMap.put(keyAndValue[0], keyAndValue[1]);
        }
        request.setParameter(parameterMap);
    }

만약 쿼리문 등을 통해 파라미터가 넘어왔다면 이 역시 저장해야 합니다. (이 프로젝트는 학습용이므로 파일 업로드에 대해서는 만들지 않고 get 요청과 post로 넘어오는 application/x-www-form-urlencoded 타입의 요청에만 대응하도록 만들었습니다) 파라미터는 앰퍼샌드('&') 와 등호('=')로 구분되어 있습니다. 

 

(5) HTTPServletRequest 객체에 저장

    private void setRequest() {
        setFirstLine(firstLine);
        request.setHeaderMap(headerMap);
        if (hasQuery()) {
            setRequestParameter(request.getRequestUrl().split(QUESTION_MARK)[1]);
        }
        if (hasParameter()) {
            try {
                setRequestParameter(getQuery());
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    }

서블릿 컨테이너는 요청을 받으면 먼저 HTTPServletRequest 객체를 생성해 실제 요청 내용을 저장해 관리하게 됩니다. 이를 위해서 HttpRequest를 모두 읽어들이고 나면 HTTPServletRequest 객체에 저장하는 과정을 거치도록 하겠습니다.

 

이렇게 클라이언트로부터 넘어온 요청을 읽어들이고 저장하는 부분까지를 알아 봤습니다. 다음주에는 요청받은 정적자원을 다시 클라이언트로 보내는 과정에 대해 생각해 보도록 하겠습니다.

반응형
donaricano-btn
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함