본문 바로가기

Web Sever 개발과 CS 기초/자바

JAVA 간단한 HTTP Server 직접 구현하기.

이 글에서는 간단한 기능을 하는 HTTP Server를 소개한다. 특징으로는 Spring과 JAVA에서 제공하는 http 관련 클래스(jdk.httpserver, com.sun.net.httpserver 등등)를 사용 하지 않는다. 기본적으로 JAVA에서 제공하는 네트워크 기능(소켓 통신)만을 이용하여, 서버를 구현한다.

 

http 라이브러리를 사용하지 않기 때문에, HTTP 메세지를 직접 해석하고, 직접 구성해야 하는 수고가 필요하다. 하지만, 이런 수고, 직접 타이핑을 통해 HTTP 프로토콜(리퀘스트, 리스폰스 메세지)에 대한 이해가 높아지는 좋은 기회가 될 것이다.

<HTTP 프로토콜이란>

https://coding-business.tistory.com/6

 

HTTP란 무엇인가/ HTTP1.0, 1.1 특징

HTTP 프로토콜 1.0 버전과 1.1 버전에 대해서 알아보자. 각 버전을 알아 보기 전에 HTTP의 의미와 원리에 대해서 간단히 알아보자. HTTP 프로토콜은 인터넷에서 데이터를 주고 받을 수 있게 하는 통신

coding-business.tistory.com

https://coding-business.tistory.com/7

 

리퀘스트 메소드와 HTTP 상태 코드

리퀘스트 메소드 종류와 특징. GET 메소드 클라이언트가 서버에 있는 데이터를 요청할 때 사용한다. (사용 방법) (쿼리 스트링 방식) URL 끝에 ? 를 붙이고 그 다음에, 요청 정보를 쿼리 스트링 방식

coding-business.tistory.com

 

HTTP 서버 구현해야 하는 기능

이 글에서 소개하는 간단한 HTTP 서버가 구현해야 하는 기능을 알아보자.

  1. GET /time -> 현재시간을 json 에 담아서 알려준다.
  2. 웹브라우저에서도 테스트POST /text/{textid} -> Body로 전달된 문자열을 서버가 저장GET 
  3. GET /text/{textid} -> 저장된 문자열을 알려준다. 
  4. 응답코드 리턴 필요 404, 200, 201 (리스폰스 상태 라인)

 

HTTP 서버 소스 코드 주소

https://github.com/dae0hwang/MoreSimpleHTTPServer

 

HTTP 서버 소스 코드에 대한 설명

MoreSimpleHTTPServer 프로젝트안에, HTTPServer를 실행 하기 위한 다양한 Class, Enum 파일이 들어 있다.

각 파일들에 대한 이해를 높이고, 각 파일간의 상호 연결성을 더 잘 이해하기 위해, 각 파일들에 대한 설명을 제시한다.

 

1. Class - HTTPSever

→ Main Class 역할을 한다. ServerSocekt을 생성한다. 브라우저에서 보내는 다양한 Request에 대한 반응 처리를 하는 Thread(Class - RequestMethod) 객체를 여기에 생성하여 HTTP 서버 작동을 책임진다.

 

2. Class - RequestMethod

→ 브라우저에서 보내는 Request에 대한 처리를 하는 Thread이다. 처리의 예로는, Request 메세지 분석하고 저장하기, 메세지에 따라서, 시간을 보내주거나, 내용을 저장하는 기능 등 이 있다.

Thread로 구성한 이유는 2개 이상의 브라우저 접속을 허용하기 위해서 이다. Thread가 아니라면, 한 브라우저 접속이 끝나야만, 다른 브라우저 접속이 가능하다.

 

3. Class - ResponseService

→깔끔하고 관리하기 쉬운 소스 코드를 작성하기 위해, Class - RequestMethod의 실질적 기능 메소드를 작성한 클래스이다.

 

4. Class - RequestTool

→클라이언트의 Request Message 프로토콜 형식에 따라서, 헤더 정보와 바디 정보를 추출할 수 있는 메소드를 작성한 클래스이다.

 

5. Class - TimeResponseMessageBody

→이 프로젝트에선 클라이언트에게 시간 정보를 Json형식으로 보낸다. 그래서 TimeReponseMessageBody 객체를 사용해서, 시간 정보를 저장하고 Json으로 변환한다.

 

6. Enum - ResponseStateCode

→클라이언트에게 보낼 리스폰스 메세지를 관리하기 위한 Enum이다. Enum을 사용하면, 깔끔하게 상수를 관리할 수 있다. 각 상수 별로 리스폰스 메세지 String을 가지고 있다.

ex) 상수 response200OK 는 "HTTP/1.1 200 OK\\r\\n\\r\\n" 정보를. response404NotFound 는 "HTTP/1.1 404 Not Found\\r\\n\\r\\n" 정보를 가지고 있다.

 

7. Enum - TreatStateCode

→Request 에 대한 처리 결과에 따라 Response 메세지가 달라진다. 성공했다면 HTTP/1.1 200 OK를 보내고 실패 햇다면, HTTP/1.1 404 Not Found 를 보내는 것이다. 그래서 처리 결과(SUCCESS, FAIL)를 담은 Enum이다.

 

 

HTTP 프로토콜에 대한 이해와, 소스 코드 분석

이 프로젝트에서 HTTP서버를 만드는데 도움을 주는 라이브러리를 사용하지 않은 이유는, 리퀘스트, 리스폰스 프로토콜을 직접 파악하고 작성하기 위함이다. 그래서, HTTP 프로토콜과 관련된 소스코드를 분석하면서, HTTP 메세지 프로토콜에 대한 간단한 파악을 해보았다.

 

1. Request Message의 State Line 저장하기.

public class ResponseService {

		public  RequestMethod readFirstLIneAndImplementRequestMethod(BufferedReader bufferedReader)
        throws IOException {
        RequestMethod requestMethod = new RequestMethod();
        String firstLine = bufferedReader.readLine();
        String[] firstLineArray = firstLine.split(" ");
        requestMethod.setMethod(firstLineArray[0]);
        String requestTypeAndTextId = firstLineArray[1].substring(1, firstLineArray[1].length());
        if (requestTypeAndTextId.contains("/")) {
            String[] requestTypeAndTextIdArray = requestTypeAndTextId.split("/");
            requestMethod.setRequestType(requestTypeAndTextIdArray[0]);
            requestMethod.setTextId(requestTypeAndTextIdArray[1]);
        } else {
            requestMethod.setRequestType(requestTypeAndTextId);
        }
        return requestMethod;
    }
}

브라우저 검색창에

이렇게 입력할 경우, HTTP 프로토콜에 의해 HTTP 서버가 받게 되는 메세지는 아래와 같다.

GET / time HTTP/1.1이 클라이언트가 요청하는 것이 담겨 있는, State LIne이다.

RequestMethod readFirstLIneAndImplementRequestMethod 메소드를 통해서 위 State LIne 정보를

RequestMethod(method=GET, requestType=time, textId=null) 객체로 저장한다.

이 객체 정보를 바탕으로 HTTP 서버는 처리를 하고 그 결과에 따라서 Response 상태 메세지를 클라이언트에게 보낸다.

 

2. Request Header 저장하고 Content-Length 바탕으로 메세지 바디 문자열 받기.

위 정보는 “aa”라는 문자열을 “texid”아이디에 저장하라는 POST 메세지이다.

그래서 서버는 메세지 바디 문자열을 추출하고 ,”textid” = “aa” 서버 저장소에 저장해야 한다. 그러기 위해서는 aa라는 문자열을 추출해야 한다.

그것을 하기 위한 Class가 RequestTool이다.

public class RequestTool {

    public String readDate(BufferedReader br, int contentLength) throws IOException {
        char[] messageBody = new char[contentLength];
        br.read(messageBody, 0, contentLength);
        return String.copyValueOf(messageBody);
    }

    public Map<String, String> readHeader(BufferedReader br) throws IOException {
        Map<String, String> headerInformation = new HashMap<>();
        String headerLine;
        while ((headerLine = br.readLine()) != null) {
            if (!headerLine.equals("")) {
                String key = headerLine.split(":")[0];
                String value = headerLine.split(":")[1].trim();
                headerInformation.put(key, value);
            } else {
                break;
            }
        }
        return headerInformation;
    }
}

readerHeader메소드를 통해서, 헤더 정보를 해쉬맵 형식으로 저장한다.

헤더 정보 중에서 Content-Length = 2 정보와 readDate메소드를 사용해서 “aa”라는 문자열을 저장한다.

 

3. 서버가 Response Message를 보내는 방법.

public class ResponseService {
	
		public void responseFromPostAndText(TreatStateCode treatStateCode, DataOutputStream dataOutputStream)
        throws IOException {
        if (treatStateCode == TreatStateCode.SUCCESS) {
            dataOutputStream.writeBytes(ResponseStateCode.response201Created.getStateMessage());
        } else if (treatStateCode == TreatStateCode.FAIL) {
            dataOutputStream.writeBytes(ResponseStateCode.response404NotFound.getStateMessage());
        }
    }
}

responseFromPostAndText POST 메소드는 처리 결과에 따라서, Response Message를 보내는 메소드이다. 예를 들어 POST 요청에 따라서, 정보가 잘 저장 되었다면, "HTTP/1.1 201 Created" 문자열을 클라이언트에게 돌려준다.

Socket 통신의 DataOutputStream을 열어서, "HTTP/1.1 201 Created" 문자열을 전송하는 방식으로 Resoponse Message를 전송한다.

그러면 클라이언트, 브라우저는 이런 메세지를 받을 수 있다.