[Java] 자바로 구현하는 HTTP 서버

JM·2022년 10월 19일
1

웹 서버와 관련 포스팅 중, 웹 서버 프로그램을 사용하는 당위성을 설명하기 위해 직접 Java를 사용하여 HTTP 서버를 구현하고자 합니다. 간단하게 GET 요청만 받는 HTTP 서버를 구현합니다.

HTTP서버를 구현하기 위한 요구사항

  • 서버 호스트 주소
  • HTTP서버 포트 번호
  • 클라이언트의 HTTP Request 파싱
  • HTTP Request 클래스
  • HTML 파일을 HTTP Reponse body에 집어넣기
  • HTTP Response 클래스
  • HTTP 요청을 캐치하는 서버 프로그램

Server Socket API
ServerSocket의 클래스는 서버 프로그램을 구현하는데 사용됩니다. 일반적인 서버 프로그램의 과정은 Step1 ~ Step 6으로 나눌 수 있습니다.
Step 1. 서버 소켓 생성, 포트 바인딩
Step 2. 클라이언트로부터의 연결을 기다리고(Connect Listen) 요청이 오면 수락
Step 3. 클라이언트 소켓에서 가져온 InputStream(클라이언트 쪽에서는 OuputStream 이 됩니다)을 읽음
Step 4. 응답이 있다면 OutputStream을 통해 클라이언트에 데이터를 보냄
Step 5. 클라이언트와의 연결을 닫음
Step 6. 서버 종료
출처: https://woolbro.tistory.com/29 [개발자 울이 노트:티스토리]


구현 계획

  1. 구현 계획을 기반으로 클래스 다이어그램 작성
  2. 클라이언트가 서버 프로그램에 요청을 보냈을 때, 서버 프로그램이 요청을 catch할 수 있는 지 테스트
  3. 서버가 HTTP Request을 받아들일 때 어떠한 구조로 받아들이는지 출력문을 통해 확인
  4. 입력 받은 HTTP RequestType 을 확인하고, 해당 타입을 DTO로 전환할 수 있도록 클래스 작성
  5. 서버 프로그램에서 DTO를 활용하여 HTTP Request 를 객체로 전환
  6. HTTP Request 객체의 헤더를 확인하여 적절한 handler method 호출
  7. handler method 가 반환한 HTTP Response 객체를 클라이언트에게 응답

구현

WebSocket.java

// 이 예제는 소켓을 활용하여 HTTP서버를 만들어 보기 위한 예제입니다.
// 테스트를 위해 HTTP 동작만 확인했기 때문에, 코드를 참조할 경우
// HTTP request or response 코드만 확인하시고,
// 소켓 관련 코드는 추가적인 정보를 찾아 올바른 코드를 활용하시기 바랍니다.
public class WebSocket {
    public static void main(String[] args) {
        System.out.println("Server has started on 127.0.0.1:8080 \nWaiting for a connection...");
        try {
            while(true){
                ServerSocket socket = new ServerSocket(8080);
                Socket client = socket.accept();
                System.out.println("Client connected!");

                InputStream in = client.getInputStream();
                OutputStream out = client.getOutputStream();
                Scanner s = new Scanner(in,"UTF-8");
                String requestData = null;
                if(s.hasNextLine()){
                    requestData = s.nextLine();
                }

                HttpRequestDto httpRequestDto = new HttpRequestDto(requestData);
                Dispatcher dispatcher = new Dispatcher();
                HttpHandler httpHandler = dispatcher.dispatch(httpRequestDto);
                HttpResponseDto httpResponseDto = httpHandler.handle();
                out.write(httpResponseDto.getResponseData().getBytes());

                s.close();
                in.close();
                out.close();
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

HttpRequestDto.java

public class HttpRequestDto {
    private String method;
    private String requestUrl;
    public HttpRequestDto(String httpRequest){
        ArrayList<String> requestList = new ArrayList<>(Arrays.asList(httpRequest.split(" ")));
        this.method = requestList.get(0);
        this.requestUrl = requestList.get(1);
    }

    public String getMethod() {
        return method;
    }

    public String getRequestUrl() {
        return requestUrl;
    }
}

HttpResponseDto.java

public class HttpResponseDto {
    private String responseData;

    public HttpResponseDto(String responseData){
        this.responseData = responseData;
    }

    public String getResponseData() {
        return responseData;
    }
}

HttpHandler.java(interface)

public interface HttpHandler {
    HttpResponseDto handle();
}

DefaultHttpHandler.java(implementation)

public class DefaultHttpHandler implements HttpHandler{

    @Override
    public HttpResponseDto handle() {
        String responseData = "HTTP/1.1 200 OK\r\n" +
                "Server: \r\n" +
                "Content-type: text/html\r\n\r\n" +
                "<HTML>" +
                "<HEAD><TITLE>200</TITLE></HEAD>" +
                "<BODY>This is \"/\" Response Data!" +
                "</BODY></HTML>";
        return new HttpResponseDto(responseData);
    }
}

HelloHttpHandler.java(implementation)

public class HelloHttpHandler implements HttpHandler {

    @Override
    public HttpResponseDto handle() {
        String responseData = "HTTP/1.1 200 OK\r\n" +
                "Server: \r\n" +
                "Content-type: text/html\r\n\r\n" +
                "<HTML>" +
                "<HEAD><TITLE>200</TITLE></HEAD>" +
                "<BODY>This is \"/hello\" Response Data!" +
                "<br>hello world!"+
                "</BODY></HTML>";
        return new HttpResponseDto(responseData);
    }
}

Dispatcher.java

public class Dispatcher {
    public HttpHandler dispatch(HttpRequestDto requestDto) throws Exception {
        String method = requestDto.getMethod();
        String requestUrl = requestDto.getRequestUrl();
        if(method.equals("GET")){
            if(requestUrl.equals("/")){
                return new DefaultHttpHandler();
            }else if(requestUrl.equals("/hello")){
                return new HelloHttpHandler();
            }else if(requestUrl.equals("/favicon.ico")){
                return new HelloHttpHandler();
            }
        }else{
            throw new Exception("Not supported Method!");
        }
        return null;
    }
}
profile
나는 사는데로 생각하지 않고, 생각하는데로 살겠다

2개의 댓글

comment-user-thumbnail
2023년 2월 1일

구현을 정말 깔끔하게 잘하셨네요!! 혹시 출처를 남기고 블로그에 관련 글을 적어도 될까요???

1개의 답글