개인참고자료/자바(네트워크)

TCP 프로그래밍 - 간단한 웹 서버

경진 2008. 7. 19. 11:36
브라우저의 요청 정보 출력

브라우저가 웹 서버에게 요청정보를 보낸다는 것은 일단 웹 서버에 연결된다는 것을 의미한다. 따라서 성공적으로 연결되면 브라우저는 웹 서버에게 요청정보를 전달하게 된다.

이와 같은 원리를 이용해서 브라우저가 요청한 정보를 단순히 화면에 출력하는 프로그램이다. 즉, 응답은 보내지 않고 요청정보만 읽어 들여 출력하는 프로그램이다.

import java.net.*;               
import java.io.*;               
public class SimpleWebServerBasic {                
               
    public static void main(String[] args) {           
        Socket sock = null;       
        BufferedReader br = null;       
        try{       
            ServerSocket ss = new ServerSocket(80);   
            sock = ss.accept();   
            br = new BufferedReader(new InputStreamReader(sock.getInputStream()));   
            String line = null;   
            while((line = br.readLine()) != null)   
                System.out.println(line);
        }catch(Exception ex){       
            System.out.println(ex);   
        }finally{       
            try{   
                if(br != null) br.close();
            }catch(Exception ex){}   
            try{   
                if(sock != null) sock.close();
            }catch(Exception ex){}   
        } // finally       
    }           
}               

웹 서버는 기본적으로 80번 포트에서 동작한다 따라서 ServerSocket을 80번 포트로 동작하게 생성한 후, accept() 메소드로 클라이언틔 접근을 기다린다.

            ServerSocket ss = new ServerSocket(80);   
            sock = ss.accept();   

소켓으로부터 InputStream을 얻은 후, BufferedReader로 변환했다. 그 후, BufferedReader의 readLine() 메소드를 이용해서 클라이언트의 요청정보를 더 이상 읽어 들일 수 없을 때까지(읽어 들인 문자열이 null일 경우까지) 읽어 들이며 읽어 들인 내용을 화면에 출력한다.

            br = new BufferedReader(new InputStreamReader(sock.getInputStream()));   
            String line = null;   
            while((line = br.readLine()) != null)   

SimpleWebServerBasic 실행

컴파일 후 실행한다.

실행 화면

웹 브라우저의 주소 창에 입력한다.


브라우저에서 위와 같이 입력한 후 엔터 키를 입력하면 아래와 같이 브라우저의 요청내용이 출력된다. 출력된 내용 중에서 가장 중요한 정보는 첫째 줄이다. 첫째 줄은 브라우저가 GET 방식으로 '/' 페이지를 HTTP 1.1 프로토콜 방식으로 요청했다는 것을 의미한다. 나머지는 브라우저의 버전과 브라우저가 어떤 언어를 사용하는지 등을 나타낸다.

실행 결과

다시 실행 한후 브라우저에 입력한다.


역시 첫째 줄을 보면 GET 방식으로 /hello/hello.html 파일을 요청했다.
웹 서버는 첫째 줄을 분석해서 브라우저가 요청한 파일을 읽어 들여 브라우저 쪽으로 출력하는 일을 하기 때문이다.

실행 결과

만약, 80번 포트에 이미 ㅇ웹 서버가 동작 중이라면, 다음과 같이 java.netBindException이 발생하면서 실행이 종료된다

예외 화면

간단한 웹 서버 프로그래밍

SimpleWebServer는 브라우저 여러 개가 동시에 요청해도 문제가 없도록 장성한다. 이를 위해 SimpleWebServerBasic을 수정해서 작성한다

SimpleWebServer가 동작하는 방식

SimpleWebServer의 동작 순서

SimpleWebServer의 동작 순서

SimpleWebServer.java는 EchoThreadServer의 동작 원리와 순서가 비슷하다. 다른 점은 브라우저의 요청정보를 읽어 들이고 응답을 보내는 것이 단 한 번씩만 이뤄진다.

import java.net.*;               
import java.io.*;                
               
public class SimpleWebServer {               
    public static void main(String[] args) {           
        try{       
            ServerSocket ss = new ServerSocket(80);   
            while(true){   
                System.out.println("접속을 대기합니다.");
                Socket sock = ss.accept();
                System.out.println("새로운 쓰레드를 시작합니다.");
                HttpThread ht = new HttpThread(sock);
                ht.start();
            } // while   
        }catch(Exception ex){       
            System.out.println(ex);   
        }   
    } // main       
} // class            
           
class HttpThread extends Thread{           
    private Socket sock = null;       
    BufferedReader br = null;       
    PrintWriter pw = null;       
    public HttpThread(Socket sock){       
        this.sock = sock;   
        try{   
            br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
            pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
        }catch(Exception ex){   
            System.out.println(ex);
        }   
    }           
    public void run(){           
        BufferedReader fbr = null;       
        try{       
            String line = br.readLine();   
            int start = line.indexOf(" ") + 2;   
            int end = line.lastIndexOf("HTTP") -1;   
            String filename = line.substring(start, end);   
            if(filename.equals(""))   
                filename = "index.html";
            System.out.println("사용자가 " + filename + "을 요청하였습니다.");   
            fbr = new BufferedReader(new FileReader(filename));   
            String fline = null;   
            while((fline = fbr.readLine()) != null){   
                pw.println(fline);
                pw.flush();
            }   
        }catch(Exception ex){       
            System.out.println(ex);   
        }finally{       
            try{   
                if(fbr != null) fbr.close();
            }catch(Exception ex){}   
            try{   
                if(br != null) br.close();
            }catch(Exception ex){}   
            try{   
                if(pw != null) pw.close();
            }catch(Exception ex){}   
            try{   
                if(sock != null) sock.close();
            }catch(Exception ex){}   
        } // finally
    } // run   
}       

브라우저의 요청을 받아들여 응답하는 것은 HttpThread 객체가 담당한다. HttpThread 객체는 인자로 접속한 브라우저의 소켓을 받아들인다. 소켓으로부터 InputStream과 OutputStream을 구한 후, 각각 BufferedReader와 PrintWriter로 변환시켰다.

    public HttpThread(Socket sock){       
        this.sock = sock;   
        try{   
            br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
            pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
        }catch(Exception ex){   
            System.out.println(ex);
        }   
    }    

실제로 브라우저의 요청을 읽어 들인 후, 요청한 내용을 브라우저에게 응답하는 것은 HttpThread의 run() 메소드에서 구현한다.

SimpleWebServerBasic에서 처럼 브라우저의 요청정보중 첫째 줄이 사용자가 원하는 파일이기 때문에 readLine() 메소드를 이용해서 한 줄을 읽어 들인 후, 문자열에 관련되 메소드(indexOf(), lastIndexOf(), subString())를 이용해서 요청한 파일이 무엇인지 알아낸다.

만약, 알아낸 파일명이 아무것도 없는 문자열일 경우에는 index.html을 기본 페이지로 지정한다. 그런 후 FileReader를 이용해서 사용자가 요청한 파일을 읽어 들이고 소켓을 통해서 쓴다.

    public void run(){            
……
        try{       
            String line = br.readLine();   
            int start = line.indexOf(" ") + 2;   
            int end = line.lastIndexOf("HTTP") -1;   
            String filename = line.substring(start, end);   
            if(filename.equals(""))   
                filename = "index.html";
            System.out.println("사용자가 " + filename + "을 요청하였습니다.");   
            fbr = new BufferedReader(new FileReader(filename));   
            String fline = null;   
            while((fline = fbr.readLine()) != null){   
                pw.println(fline);
                pw.flush();
            }    
……
    }

index.html 파일작성

사용자가 파일명을 전달하지 않았을 경우, 기본적으로 읽어 들이도록 지정한 index.html 파일을 다음과 같이 작성한다. (.class 파일과 같은 경로에 있어야 한다)

<HTML>
 <HEAD>
  <TITLE>안녕</TITLE>
 </HEAD>
 <BODY>
 <H1>안녕?</H1>
 </BODY>
</HTML>

브라우저를 이용해서 요청 정보를 보내고 응답 받기

브라우저를 실행한 후, http://localhost/index.html을 요청하면 다음과 같이 index.html파일을 읽어 들여 출력되는 것을 알 수 있다.

브라우저로 실행한 결과

SimpleWebServer를 실행한 창에는 아래와 같이 출력된다.

실행 결과

SimpleWebServer의 문제점

SimpleWebServer는 보안상 아주 심각한 문제를 내포하고 있다. 만약 사용자가 브라우저에 아래와 같이 입력한다.


위 요청은 웹 서버가 동작하는 디렉토리의 이전, 이전, 이전 디렉토리에 있는 passwd.txt 파일을 요청하게 되는 것이다.

passwd.txt 파일이 아래와 같이 작성되어 있다면 브라우저는 아래와 같이 읽어 들인 내용을 출력하게 된다.

root:0000
admin:1234
aith:5678

※ IE7에서는 읽지를 못했다 그러나 역시 다른 방법으로는 보안상 취약점이 있었다


사용자의 중요한 파일이 브라우저를 통해서 유출될 수 있음을 의미한다. 실제로 초창기의 웹서버는 이러한 문제가 종종 발생했다. 하지만 현제의 많은 웹 서버들은 사용자의 요청을 좀더 엄격하게 다루기 때문에 이제 이런 문제는 거의 발생하지 않는다.

SimpleWebServer도 이러한 문제를 해결하기 위해서 요청정보를 좀더 엄밀히 분석할 필요가 있다.