지금부터 JAVA WAS에서 사용중인 Servlet객체를 직접한번 만들어 볼겁니다.
우선 동작원리부터 살펴보겠습니다.
Servlet 동작원리를 설명하기 위해선, Socket통신에 대한 깊은 고찰이 필요합니다.
우선, Servlet이 나오게된 계기부터 알아보아야 합니다.
원시에 우리는 통신을 하기위해 Socket이라는 것을 사용했습니다.
Servlet에 들어가기전, 멀티쓰레드 환경이 필요한 이유에 대해 먼저 설명해 드리겠습니다.
서버가 ServerSocket을 이용하여 연결을 기다리고,
A라는 사용자가 서버에 요청을 하면 서버는 요청에 대한 응답을 해주겠죠.
그런데, A, B, C, D, E 라는사용자가 동시에 서버에 요청을 보낸다면?
서버는 먼저 들어온 요청에 대한 응답이 끝날때까지 다른 요청을 처리할 수 없기 때문에
다른 사용자는 먼저 요청한 사용자의 요청이 끝날때까지 기다려야 하는 일이 발생해요.
동시에 여러개의 요청이 들어오게 되더라도, 로직을 쓰레드에 실어서 실행하면 요청에 대한 처리를 동시에 진행할 수 있어요.
자 이제 우리는 서버가 요청을 받으면, 처리로직을 쓰레드를 만들어서 실행한다는 것을 알았어요.
하지만 우리는 브라우저 통신을 할때 HTTP 프로토콜을 사용하기때문에 정해진 약속들이 많이 있어요.
하지만 Servlet은 그런 로직들을 다 조각내서 딱 우리가 원하는 로직만 짤 수 있도록 해주기 위하여 탄생했어요.
지금부터 이해를 돕기위해 소켓을통해 한번 구현해 보도록 하겠어요.
[java servlet이랑은 약간 다른점이 있습니다. 이해를 돕기위한 코드입니다.]
Porolet.class
public abstract class Porolet {
// 현재 인스턴스가 어떤객체인지 알아보기 위해 찍어보았음.
public void init() {
System.out.println("----------init----------" + this.getClass().getName());
}
public abstract void service(InputStream in, PoroRes response)throws Exception;
}
HelloPorolet.class
public class HelloPorolet extends Porolet {
@Override
public void service(InputStream in, PoroRes response) throws Exception {
response.setContentType("text/html");
Thread.sleep(3000);
response.print("<h1>Hello World</h1>");
}
}
PoroRes.class
@AllArgsConstructor
@Getter
public class PoroRes {
private OutputStream out;
// 응답헤더 설정
public void setContentType(String type) {
try {
out.write(new String("HTTP/1.1 200 OK\r\n").getBytes());
out.write(new String("Content-Type: "+ type +" \r\n\r\n").getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// body에 메세지 실어서 응답
public void print(String msg) {
try {
out.write(msg.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Server.class 의 생성자 코드
public MainServer() {
letMap = new HashMap<>();
Properties prop = new Properties();
try {
prop.load(new FileInputStream("C:\\zzz\\ui.properties"));
prop.keySet().forEach(key -> {
String value = prop.getProperty((String)key);
System.out.println(value);
try {
Class clz = Class.forName(value);
Porolet obj = (Porolet)clz.getConstructor(null).newInstance(null);
obj.init();
letMap.put((String)key, obj);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
클라이언트의 요청이 들어왔을때
public void runServer() {
try {
ServerSocket server = new ServerSocket(5555);
System.out.println("server running.................");
while(true) {
Socket socket = server.accept();
new Thread(() -> {
try {
InputStream in = socket.getInputStream();
Scanner inScanner = new Scanner(in);
OutputStream out = socket.getOutputStream();
String firstLine = inScanner.nextLine();
String target = firstLine.split(" ")[1];
PoroRes res = new PoroRes(out);
try {
Porolet let = letMap.get(target);
let.service(in, res);
} catch (Exception e) {
out.write("HTTP/1.1 500 Internel Server Error\r\n".getBytes());
}
in.close();
out.close();
}catch(Exception e) {
}
}).start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/hello=zserver.server.let.HelloPorolet
/lotto=zserver.server.let.LottoPorolet
이제 우리는 요청받을 주소를 properties에 객체와함께 설정해주고,
Porolet 추상클래스를 상속받은 객체만 만들어 추가하면 무한한 확장이 가능하게 됩니다.