[WebServer] 웹서버 만들기

ho's·2022년 6월 15일
0

⛳ WebServer

🎣 WebServer의 동작

  • 동시에 여러 번 동작한다.
    클라이언트가 접속할 때까지 대기
    클라이언트가 접속을 하면 연결이 된다.
    클라이언트가 보내주는 정보를 읽어들인다.(빈 줄까지)
    • 첫 줄 (GET/ )
    • 헤더들(여러줄) 헤덩명: 헤더값
    • 빈줄

서버는 응답을

  • 첫줄 (200 OK)
  • 헤더들 (Body의 크기)
  • 빈줄
  • Body내용이 전달된다.

연결이 끊어진다.

🎣 WebServer코드 작성 순서

🎯 1. ServerSocket을 만들어 준다.

package webserver;

import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    public static void main(String[] args) throws IOException {
        // 클라이언트가 접속할 때까지 대기할때 필요한 객체가 ServerSocket;
        ServerSocket serverSocekt = new ServerSocket(10000);
        System.out.println("1");
        Socket clientSocket = serverSocekt.accept();
        System.out.println("2");
    }
}

serverSocekt.accept() 는 Socket을 반환한다.

🎯 2. 클라이언트의 접속 정보 읽어들이기

🔊 첫 번째 줄을 읽어 들여보자!

package webserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    public static void main(String[] args) throws IOException {
        // 클라이언트가 접속할 때까지 대기할때 필요한 객체가 ServerSocket;
        ServerSocket serverSocekt = new ServerSocket(10000);
        System.out.println("1");
        Socket clientSocket = serverSocekt.accept();
        System.out.println("2");

        InputStream inputStream = clientSocket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        System.out.println(br.readLine());
    }
}
  • clientSocket을 getInputStream()으로 읽어 들여서, inputStream객체에 넣는다.
  • 한 줄씩 읽기 위해서 BufferedReader를 받아 들인다.
  • 출력한다.

🔊 결과


클라이언트가 위와 같이 요청한다면, 아래와 같은 응답을 한다.

클라이언트에서 서버로 요청 보냈을때 정보들

위의 결과들을 어떻게 출력할까?

  1. System.out.println(br.readLine());
    을 갯수에 맞게 써도 되지만, 정보의 수가 달라질 때마다 바뀐다. 위와 같은 생각을 코드로 바꾸면 아래와 같다.
package webserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    public static void main(String[] args) throws IOException {

        // 클라이언트가 접속할 때까지 대기할때 필요한 객체가 ServerSocket;
        ServerSocket serverSocekt = new ServerSocket(10000);
        System.out.println("1");
        Socket clientSocket = serverSocekt.accept();
        System.out.println("2");

        InputStream inputStream = clientSocket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        System.out.println(br.readLine());
        String line = null;
        line = br.readLine();
        if(!line.equals("")) // 빈 줄이 아니라면
            System.out.println(line);
        line = br.readLine();
        if(!line.equals("")) // 빈 줄이 아니라면
            System.out.println(line);
        line = br.readLine();
        if(!line.equals("")) // 빈 줄이 아니라면
            System.out.println(line);
        line = br.readLine();
        if(!line.equals("")) // 빈 줄이 아니라면
            System.out.println(line);
        line = br.readLine();
        if(!line.equals("")) // 빈 줄이 아니라면
            System.out.println(line);
        line= br.readLine();
        if(!line.equals("")) // 빈 줄이 아니라면
            System.out.println(line);
        line= br.readLine();
        if(!line.equals("")) // 빈 줄이 아니라면
            System.out.println(line);
        line= br.readLine();
        if(!line.equals("")) // 빈 줄이 아니라면
            System.out.println(line);
    }
}

결과는,

🔊 while문을 이용해 코드를 깔끔하게 정리해보자

package webserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    public static void main(String[] args) throws IOException {

        // 클라이언트가 접속할 때까지 대기할때 필요한 객체가 ServerSocket;
        ServerSocket serverSocekt = new ServerSocket(10000);
        System.out.println("1");
        Socket clientSocket = serverSocekt.accept();
        System.out.println("2");

        InputStream inputStream = clientSocket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

        System.out.println(br.readLine());

        String line = null;
        while(!(line = br.readLine()).equals("")){
            System.out.println(line);
        }
        // 빈줄까지 읽어들이면 끝
        System.out.println("3 - 응답을 한다.");
    }
}

훨씬 깔끔해 졌다..!

🎣 응답은 어떻게 하지?

🎯 HTTP 프로토콜

응답 메세지로 첫 줄에는 위와 같은 메세지가 있다.

🔊 응답을 하기 위해서는 socket을 통해 메세지를 써주어야 한다.

🔊 코드 작성 순서

  1. clientSocket을 통해 출력객체를 받는다.
OutputStream outputStream = clientSocket.getOutputStream();
  1. 한줄 씩 출력을 해주자!
PrintWriter pw = new PrintWriter(new OutputStreamWriter(outputStream));
  • 응답 메세지 작성
System.out.println("3- 응답을 한다.");
pw.println("HTTP/1.1 200 OK"); 
pw.println("name: kim");
pw.println("email:urstory@gmail.com");
pw.println();
pw.println("hello world");
pw.println("</html>");
  • 종료하기
	pw.flush();
    br.close();
    pw.close();
    clientSocket.close();
    serverSocket.close();

🔊 전체 코드

package webserver;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    public static void main(String[] args) throws IOException {
        // 클라이언트가 접속할 때까지 대기할때 필요한 객체가 ServerSocket;
        ServerSocket serverSocekt = new ServerSocket(10000);
        System.out.println("1");
        Socket clientSocket = serverSocekt.accept();
        System.out.println("2");

        InputStream inputStream = clientSocket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

        OutputStream outputStream = clientSocket.getOutputStream();
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(outputStream));

        //        System.out.println(br.readLine());
        String line = null;
        while(!(line = br.readLine()).equals("")){
            System.out.println(line);
        }
        // 빈줄까지 읽어들이면 끝
        System.out.println("3 - 응답을 한다.");

        pw.println("HTTP/1.1 200 OK"); // 응답
        pw.println("name : kim"); // 응답
        pw.println("email:eoho115@naver.com"); // 응답
        pw.println();
        pw.println("<html>"); // 응답
        pw.println("hello world"); // 응답
        pw.println("</html>"); // 응답

        pw.flush();
        br.close();
        pw.close();
        clientSocket.close();
        serverSocekt.close();

    }
}

🔊 결과

🎯 요청 정보에 따라 다르게 반응하기

localhost:10000/ ~~~

/뒤에 있는 부분이 클라이언트가 서버에게 요청하는 요청 정보이다.
서버는 클라이언트의 요청정보에 따라 다른 정보를 주어야한다...!!

어떻게 해야할까?

  String msg = "";
        if(firstLine.indexOf("/hello") >= 0)
            msg = "hello";
        else if(firstLine.indexOf("/hi") >= 0)
            msg = "hi";
  • 빈 String 문자열 String을 생성한다.
  • 첫번째 라인에 /hello가 포함되어 있다면 "hello"를 저장한다.
  • 첫번째 라인에 /hi가 포함되어 있다면 "hi"를 저장한다.

🔊 전체 코드 및 결과

package webserver;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    public static void main(String[] args) throws IOException {
        // 클라이언트가 접속할 때까지 대기할때 필요한 객체가 ServerSocket;
        ServerSocket serverSocekt = new ServerSocket(10000);
        System.out.println("1");
        Socket clientSocket = serverSocekt.accept();
        System.out.println("2");

        InputStream inputStream = clientSocket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

        OutputStream outputStream = clientSocket.getOutputStream();
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(outputStream));

        //        System.out.println(br.readLine());
        String firstLine = br.readLine();
        String msg = "";
        if(firstLine.indexOf("/hello") >= 0)
            msg = "hello";
        else if(firstLine.indexOf("/hi") >= 0)
            msg = "hi";


        String line = null;
        while(!(line = br.readLine()).equals("")){
            System.out.println(line);
        }
        // 빈줄까지 읽어들이면 끝
        System.out.println("3 - 응답을 한다.");

        pw.println("HTTP/1.1 200 OK"); // 응답
        pw.println("name : kim"); // 응답
        pw.println("email:eoho115@naver.com"); // 응답
        pw.println();
        pw.println("<html>"); // 응답
        pw.println( msg + "world"); // 응답
        pw.println("</html>"); // 응답

        pw.flush();
        br.close();
        pw.close();
        clientSocket.close();
        serverSocekt.close();

    }
}


🎯 여기서 나타나는 한계점

위와 같이 연속해서 클라이언트가 서버에 요청할 경우, 연결이 되지 않는다.!!

어떻게 해야할까?

while문을 이용해 무한루프를 해주자.

🔊 코드


package webserver;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    public static void main(String[] args) throws IOException {
        // 클라이언트가 접속할 때까지 대기할때 필요한 객체가 ServerSocket;

                ServerSocket serverSocekt = new ServerSocket(10000);
                System.out.println("1");
                try{
                    while (true) {
                Socket clientSocket = serverSocekt.accept();
                System.out.println("2");

                InputStream inputStream = clientSocket.getInputStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

                OutputStream outputStream = clientSocket.getOutputStream();
                PrintWriter pw = new PrintWriter(new OutputStreamWriter(outputStream));

                //        System.out.println(br.readLine());
                String firstLine = br.readLine();
                String msg = "";
                if (firstLine.indexOf("/hello") >= 0)
                    msg = "hello";
                else if (firstLine.indexOf("/hi") >= 0)
                    msg = "hi";


                String line = null;
                while (!(line = br.readLine()).equals("")) {
                    System.out.println(line);
                }
                // 빈줄까지 읽어들이면 끝
                System.out.println("3 - 응답을 한다.");

                pw.println("HTTP/1.1 200 OK"); // 응답
                pw.println("name : kim"); // 응답
                pw.println("email:eoho115@naver.com"); // 응답
                pw.println();
                pw.println("<html>"); // 응답
                pw.println(msg + "world"); // 응답
                pw.println("</html>"); // 응답

                pw.flush();
                br.close();
                pw.close();
                clientSocket.close();
            }
        }finally {
            serverSocekt.close();
        }
    }
}

🔊 결과

클라이언트가 서버에게 연속으로 요청해도 끊기지 않고 정보를 보내준다.

🎯 더 좋은 방법 (Thread)

🎨 1. 메소드로 Refactor하기

package webserver;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    public static void main(String[] args) throws IOException {
        // 클라이언트가 접속할 때까지 대기할때 필요한 객체가 ServerSocket;
        ServerSocket serverSocekt = new ServerSocket(10000);
        System.out.println("1");
        try{
            while (true) {
                Socket clientSocket = serverSocekt.accept();
                System.out.println("2");

                run(clientSocket);
            }
        }finally {
            serverSocekt.close();
        }
    }

    private static void run(Socket clientSocket) throws IOException {
        InputStream inputStream = clientSocket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

        OutputStream outputStream = clientSocket.getOutputStream();
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(outputStream));

        //        System.out.println(br.readLine());
        String firstLine = br.readLine();
        String msg = "";
        if (firstLine.indexOf("/hello") >= 0)
            msg = "hello";
        else if (firstLine.indexOf("/hi") >= 0)
            msg = "hi";


        String line = null;
        while (!(line = br.readLine()).equals("")) {
            System.out.println(line);
        }
        // 빈줄까지 읽어들이면 끝
        System.out.println("3 - 응답을 한다.");

        pw.println("HTTP/1.1 200 OK"); // 응답
        pw.println("name : kim"); // 응답
        pw.println("email:eoho115@naver.com"); // 응답
        pw.println();
        pw.println("<html>"); // 응답
        pw.println(msg + "world"); // 응답
        pw.println("</html>"); // 응답

        pw.flush();
        br.close();
        pw.close();
        clientSocket.close();
    }
}

🎨 2. Thread 클래스 만들기.

ClientThread 클래스를 만들고 Thread 를 상속받자.

Client Thread 클래스에서는 Socket형태의 clientSocket 변수를 가지고 있고, 이를 생성자를 이용해 초기화 해준다.

ClientThread 클래스에서 위에서 Refactor 했던 run() 메소드의 내용을 붙이자.

완성된 ClientThread 코드

class ClientThread extends Thread{
    private Socket clientSocket;
    public ClientThread(Socket clientSocket){
        this.clientSocket = clientSocket;
    }

    public void run(){
        try{
            InputStream inputStream = clientSocket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

            OutputStream outputStream = clientSocket.getOutputStream();
            PrintWriter pw = new PrintWriter(new OutputStreamWriter(outputStream));
            //        System.out.println(br.readLine());
            String firstLine = br.readLine();
            String msg = "";
            if (firstLine.indexOf("/hello") >= 0)
                msg = "hello";
            else if (firstLine.indexOf("/hi") >= 0)
                msg = "hi";

            String line = null;
            while (!(line = br.readLine()).equals("")) {
                System.out.println(line);
            }
            // 빈줄까지 읽어들이면 끝
            System.out.println("3 - 응답을 한다.");

            pw.println("HTTP/1.1 200 OK"); // 응답
            pw.println("name : kim"); // 응답
            pw.println("email:eoho115@naver.com"); // 응답
            pw.println();
            pw.println("<html>"); // 응답
            pw.println(msg + "world"); // 응답
            pw.println("</html>"); // 응답

            pw.flush();
            br.close();
            pw.close();
            clientSocket.close();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

🎨 3. Thread 실행시키기

ClientThread 객체를 만들고, 객체.start()를 이용해 스레드를 작동시키자.

ClientThread ct = new ClientThread(clientSocket);
ct.start();

전체 코드

package webserver;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class WebServer {
    public static void main(String[] args) throws IOException {
        // 클라이언트가 접속할 때까지 대기할때 필요한 객체가 ServerSocket;
        ServerSocket serverSocekt = new ServerSocket(10000);
        System.out.println("1");
        try {
            while (true) {
                Socket clientSocket = serverSocekt.accept();
                System.out.println("2");
                ClientThread ct = new ClientThread(clientSocket);
                ct.start();
            }
        } finally {
            serverSocekt.close();
        }
    }
}

class ClientThread extends Thread{
    private Socket clientSocket;
    public ClientThread(Socket clientSocket){
        this.clientSocket = clientSocket;
    }

    public void run(){
        try{
            InputStream inputStream = clientSocket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

            OutputStream outputStream = clientSocket.getOutputStream();
            PrintWriter pw = new PrintWriter(new OutputStreamWriter(outputStream));
            //        System.out.println(br.readLine());
            String firstLine = br.readLine();
            String msg = "";
            if (firstLine.indexOf("/hello") >= 0)
                msg = "hello";
            else if (firstLine.indexOf("/hi") >= 0)
                msg = "hi";

            String line = null;
            while (!(line = br.readLine()).equals("")) {
                System.out.println(line);
            }
            // 빈줄까지 읽어들이면 끝
            System.out.println("3 - 응답을 한다.");

            pw.println("HTTP/1.1 200 OK"); // 응답
            pw.println("name : kim"); // 응답
            pw.println("email:eoho115@naver.com"); // 응답
            pw.println();
            pw.println("<html>"); // 응답
            pw.println(msg + "world"); // 응답
            pw.println("</html>"); // 응답

            pw.flush();
            br.close();
            pw.close();
            clientSocket.close();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

실행 결과


위와 같이 world 계속 출력 되는 것을 알 수 있다.

🎨 4. 응답부분에 firstLine을 출력시키는 코드 작성 후, 실행시키기

🎣 Response(반응)할 때, firstLine가 어떤 타입을 우리는 모른다.

만약 클라이언트가 GET / hello HTTP/1.1 의 요청을 보냈다면 서버는 /hello에 대한 파일을 읽어서 출력해야 한다. 하지만 파일의 형태가 무엇인지는 알 수 없다.

이미지 파일을 요청하면 이미지파일을 출력해줘야 한다.

위의 그림과 같이 네이버 도메인의 이미지들은 각각 content-lengthcontent-type 을 가지고 있다.

🎯 좋은 웹서버를 만드려면.

클라이언트의 이미지 요청이 오면, 이미지의 파일의 크기를 읽어들여서 Header부분에서 content-length, content-type을 읽고 우리의 브라우저는 서버가 요청해준 만큼만 준비를 하면 된다!

여기서 잠깐! 위에서 문제점을 찾아보자

위에서 우리는 좋은 웹 서버를 만들기 위해서는 이미지나 파일들의 크기와 타입들을 클라이언트에게 전달을 해주어야 한다고 했다. 이 역할은 flush(); 메소드를 통해 이루어 진다.

즉 위의 코드의 문제점은 flush();firstList()을 출력하고나서 전달을 한다는 것이다.
flush()를 앞에서 한번 더 사용해 미리 정보를 클라이언트에게 전달해 주자!

🎣 더 알아볼 것들

  • apache, nginx 같은 서버는 어떻게 돌아갈까?
profile
그래야만 한다

0개의 댓글