<이전글>

2020/12/03 - [IT/Git] - [Git] Git 원격 저장소 여러개 연결

 

[Git] Git 원격 저장소 여러개 연결

한 프로젝트를 깃랩과 깃허브 두 곳에 push하고 싶어서 알아봤다. 현재 로컬 저장소인 Mini_WAS 폴더와 깃랩 원격 저장소와 연결되어있다. 이제 내 깃허브 원격 저장소와도 연결하고 싶어서 과정을

song-yujin.tistory.com

 

1. git 연결 끊기


git 연결 끊기
git remote remove origin

 

git init 취소
rm -r .git

 

 현재 연결되어있는 저장소 경로 보기
git remote -v

 

 

2. 새로운 로컬 저장소 연결

깃랩에 있는 Mini_WAS 저장소와 새롭게 연결하고 싶은 로컬 저장소를 연결하려고 한다.

 

새로 연결하고 싶은 원격 저장소명과 URL 
git remote add "저장소명" "url"

 

그런데!!

이전 글을 참고해서 git push origin master과 git push origin +master 둘 다 해봤는데 아래처럼 표시가 되었다.

$ git push origin master
To http://~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'http://gitlab-intern.polloud.com/songij4/mini_was.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

~~~~@~~~ MINGW64 ~/eclipse-workspace/Mini_WAS2 (master)
$ git push origin +master
Enumerating objects: 87, done.
Counting objects: 100% (87/87), done.
Delta compression using up to 4 threads
Compressing objects: 100% (77/77), done.
Writing objects: 100% (87/87), 38.01 KiB | 2.38 MiB/s, done.
Total 87 (delta 12), reused 0 (delta 0), pack-reused 0
remote: GitLab: You are not allowed to force push code to a protected branch on this project.
To http://gitlab-intern.polloud.com/songij4/mini_was.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'http://gitlab-intern.polloud.com/songij4/mini_was.git'

 

원인

 

깃랩은 저장소를 생성하면 기본설정이 '관리자(Maintainers)'만 수정 가능하다.

'개발자(Developers)'는 푸시 허용이 돼 있지 않아서 나는 오류이다.

 

 

해결 방법

 

깃랩 레파지토리에서 > settings > Repository > Protected Branches > 선택하는 부분에 Developers + Maintainers 해주면 된다. 

그리고 다시 git push origin +master 해주면 잘 된다!

'IT > Git' 카테고리의 다른 글

[Git] Git 원격 저장소 여러개 연결  (0) 2020.12.03
[Git]git branch 실습  (0) 2020.11.16

한 프로젝트를 깃랩과 깃허브 두 곳에 push하고 싶어서 알아봤다.

 

현재 로컬 저장소인 Mini_WAS 폴더깃랩 원격 저장소와 연결되어있다. 

이제 내 깃허브 원격 저장소와도 연결하고 싶어서 과정을 기록하려고 한다.

 

새로 연결하고 싶은 원격 저장소명과 URL 
git remote add "저장소명" "url"

 

원격 저장소 목록 확인
git remote -v

 

 

origin(깃랩)으로 push와 저장소명(깃허브)으로 push
git push origin master 
git push 저장소명 master
//ex) git push Mini_WAS master

 

origin(깃랩)으로 pull와 저장소명(깃허브)으로 pull
git pull origin master
git pull 저장소명 master

 

 

그런데!

 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'http://gitlab-intern.polloud.com/songij4/mini_was.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

push 하려고 하니까 이런식으로 에러가 떴다.

git pull을 해줬는데도 안됐다. 그래서 구글링 해봤다.

 

 

원인

 

원인은 .gitignore 파일 또는 README.md 파일로 인해 발생한다고 한다.

 

해결 방법

 

master 앞에 +를 붙이기
git push origin +master

 

 

 

'IT > Git' 카테고리의 다른 글

[Git] git 연결 끊기 & 새로운 로컬 저장소 연결  (0) 2020.12.14
[Git]git branch 실습  (0) 2020.11.16

서블릿 공부를 하면서 WAS를 직접 만들어보는 시간을 가지고 있다. 

아직 완성되지는 않았지만 중간점검(?) 느낌으로 기록하려고 한다. 

 

먼저 코드를 작성하기 전에 아이패드로 그림을 그려서 설계를 해보았다. 

(설계라고 말하기도 민망ㅎㅅㅎ)

전체적인 시나리오? 작성을 하면서 계속 생각한 것이 "먼저 숲을 보고 나무를 보자" 였다. 

 

그래서 일단 나의 목표는!!!!

각 클래스의 역할, 클래스 간의 관계, 전체적인 흐름을 이해하는 것이었다.

그리고 그 안에 내부적인 기능에 대한 코드 작성은 나중에 더 신경쓰는 방향으로 결정했다.

(그냥 ....... 그래서 메소드 안에 코드가 깔끔하지 않을 수 있다는 것..ㅎ)

 

 

깃허브 : github.com/YuJinSong412/Mini_WAS


설계


 

[그림2]

 

일단 GET 요청을 처리하는 것으로 생각하고 작성했다. 그리고 클래스로더도 사용하지 않았다.

이렇게 적어보면서 코드를 작성한다고 했을 때는 어떤식으로 접근해야되는지 생각해봤다.

(필요한 클래스, 메소드 등 생각해봄.. 코드적으로 생각함..)

 

 

 

 

[그림3]
[그림4]

 

이렇게 정리해보니까 빨리 코드 작성하고 싶은 마음이 생겼었다. 

이래서 설계하라는건가~~~~~~~?ㅋㅋ큐ㅠㅜㅜㅎㅎ

 

결과 화면


 

 

제출 누르면

 

 

 

 

자바 소스 코드


설계한 것처럼 일단 트리구조는 아래 사진과 같다. 

 

 

 

설계에서 적었던 순서대로 코드 진행을 했다.

 

0) 서버 실행할 때 컨테이너 실행도 같이 하면서 서블릿 생성하고 초기화(init())를 한다.

더보기
import container.Container;
import server.WebServer;

public class ServerLaunch {

  public static void main(String[] args) {

    Container container = new Container();  
    container.start();   //컨테이너 실행 
    
    WebServer webServer = new WebServer();
    webServer.start();   //웹서버 실행
  }

}

container.start() 메소드를 보면 서블릿을 인스턴스화 하고 초기화를 합니다. 

더보기
package container;

public class Container {

  ServletInit servletInit;

  public Container() {

    servletInit = new ServletInit();
  }

  public void start() {

    servletInit.startServletInit();
  }
}
package container;

import java.io.File;
import java.util.ArrayList;

public class ServletInit {

  static ArrayList<String> servletNames;

  static ArrayList<Servlet> servletClasses;

  public ServletInit() {

    servletNames = new ArrayList<String>();
    servletClasses = new ArrayList<Servlet>();
  }

 public void startServletInit() {

    getServletName();
    
    for (String servletName : servletNames) {
      try {
        Class c = Class.forName("servlet." + servletName);
        Servlet my = (Servlet) c.newInstance();
        
        servletClasses.add(my);
        my.init();
      } catch (ClassNotFoundException e1) {
        //클래스를 찾지 못했을 경우에 대한 예외사항
        e1.printStackTrace();
      } catch(InstantiationException e2) {
        //인스턴스(new) 실패 시에 대한 예외사항
        e2.printStackTrace();
      } catch(IllegalAccessException e3) {
        //파일접근에 대한 예외사항
         e3.printStackTrace();
      }
    }
  }

  private void getServletName() {

    String path = "C:/Users/kev/eclipse-workspace/WebProject/src/servlet";
    
    File dir = new File(path);
    
    File[] files = dir.listFiles();
    
    for (File file : files) {
      int lastSeparator = file.toString().lastIndexOf("\\");
      int startExtension = file.toString().indexOf(".");
      String servletName = file.toString().substring(lastSeparator + 1, startExtension);
      servletNames.add(servletName);
    }
  }
}

 

여기서 try 부분을 보면 Servlet 인터페이스가 왜 필요한지 알게된 것 같다.

만약 Servlet 인터페이스 없이 (정해진 규격없이) 서블릿 클래스를 만들면 하나씩 직접 다 인스턴스화 해줘야 했다. (for문을 사용할 수 없었음)

그래서 Servlet 인터페이스를 만들고 "Servlet my = (Servlet) c.newInstance();" 이렇게 형변환할 때 Servlet으로 주면 그 Servlet 인터페이스의 구현 클래스들을 순서대로 인스턴스화할 수 있었다. 

 

 

 

이제 웹서버 실행을 보면 

더보기
// 클래스 역할 : 1) 요청을 받음 2)정적,동적파일 구분 3)응답
public class WebServer {

  private static final int PORT = 5027;

  private final String SERVER_PATH = "C:\\Users\\kev\\eclipse-workspace\\WebProject\\WebContent\\basic";

  public static ExecutorService executorService; // 스레드풀인 ExecutorService 선언

  private ServerSocket serverSocket;

  public void start() {

    ExecutorService threadPool = new ThreadPoolExecutor(10, // 코어 스레드 개수
        100, // 최대 스레드 개수
        120L, // 놀고 있는 시간
        TimeUnit.SECONDS, // 놀고 있는 시간 단위
        new SynchronousQueue<Runnable>() // 작업 큐
    ); // 초기 스레드 개수 0개,
    executorService = threadPool;
    
    try {
      serverSocket = new ServerSocket();
      serverSocket.bind(new InetSocketAddress(PORT));
      
      // 연결을 수락하는 코드
      while (true) {
        System.out.println("서버가 연결을 기다림");
        Socket socket = serverSocket.accept();
        // socket.setSoTimeout(1000);
        
        // 스레드 풀의 작업 생성 - 따로 빼고 싶음.. 고민..
        Runnable runnable = new Runnable() {

          @Override
          public void run() {

            InputStream inputStream = null;
            BufferedReader bufferedReader = null;
            OutputStream outputStream = null;
            
            try {
              // 요청을 받는 역할 - 요청을 읽어서 request 객체에 정보를 넣음
              Request request = new Request();
              inputStream = socket.getInputStream();
              bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
              
              receiveRequest(request, bufferedReader);
              System.out.println("오청받기 성공");
              
              outputStream = socket.getOutputStream();
              Response response = new Response(outputStream);
              
              // 정적 파일인지 아닌지 구분
              File file = getFile(request);
              if (file.isFile()) {
                sendResponse(file, response); // 정적파일이면 Response 응답을 보냄
              } else {
                new ContainerController(request, response).start(); // 동적파일이면 컨테이너 역할을 하는
              }
              
            } catch (IOException e) {
              e.printStackTrace();
            } finally {
              try {
              
                inputStream.close();
                bufferedReader.close();
                outputStream.close();
                socket.close();
                
              } catch (IOException e) {
                e.printStackTrace();
              }
            }
          }
        };
        executorService.submit(runnable);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    if (!serverSocket.isClosed()) {
      stopServer();
    }
  }

.....이어서

서버가 연결을 기다리다가 요청을 받으면 Runnable 작업 생성을 한다. WebServer 클래스의 역할인 요청 받고, 파일 구분하고, 응답하는 것 모두 runnable 안에서 이루어진다.(이 3가지 수행이 하나의 작업 단위임)

 

runnable 안에 있는 이 두 부분을 아래 설명과 같이 볼 것! 

더보기
// 요청을 받는 역할 - 요청을 읽어서 request 객체에 정보를 넣음
              Request request = new Request();
              inputStream = socket.getInputStream();
              bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
              
              receiveRequest(request, bufferedReader);
              System.out.println("오청받기 성공");
              outputStream = socket.getOutputStream();
              Response response = new Response(outputStream);
              
              // 정적 파일인지 아닌지 구분
              File file = getFile(request);
              if (file.isFile()) {
                sendResponse(file, response); // 정적파일이면 Response 응답을 보냄
              } else {
                new ContainerController(request, response).start(); // 동적파일이면 컨테이너 역할을 하는
              
              }
 

 

 

 

1) 받은 HTTP Request 부분을 클래스 Request에 정보를 담는다.

앞에 Runnable 안에 있는 receiveRequest()메소드가 Request 객체 안에 요청받은 정보를 넣는 행동을 한다.

더보기
  private void receiveRequest(Request request, BufferedReader bufferedReader) {

    try {
      String line = "tmp";
      
      Map<String, String> requestHeaderMap = new HashMap<String, String>();
      
      for (int i = 1; (line = bufferedReader.readLine()) != null && !line.equals(""); i++) {
        // System.out.println(line);
        
        if (i == 1) {
          int firstBlank = line.indexOf(" ");
          int lastBlank = line.lastIndexOf(" ");
         
          request.setMethod(line.substring(0, firstBlank));
          request.setRequestUrl(line.substring(firstBlank + 1, lastBlank).trim());
          request.setHttpVersion(line.substring(lastBlank + 1).trim());
          
        } else {
          
          int indexOfColon = line.indexOf(": ");
          
          if (indexOfColon == -1) {
            continue;
          } else {
            
            String headerName = line.substring(0, indexOfColon).trim();
            String headerValue = line.substring(indexOfColon + 1).trim();
            
            requestHeaderMap.put(headerName, headerValue);
          }
        }
      }
      
      request.setHeaderMap(requestHeaderMap);
      
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

 

Request 클래스를 보면 아래 코드와 같다.

더보기
package communication;

import java.util.HashMap;
import java.util.Map;

public class Request {

  private String method;

  private String requestUrl;

  private String httpVersion;

  private String connection;

  private String accept;

  private String acceptEncoding;

  private Map<String, String> headerMap;

  public Request() {

    headerMap = new HashMap<String, String>();
  }

  public String getMethod() {

    return method;
  }

  public void setMethod(String method) {

    this.method = method;
  }

  public String getRequestUrl() {

    return requestUrl;
  }

  public void setRequestUrl(String requestUrl) {

    this.requestUrl = requestUrl;
  }

  public String getHttpVersion() {

    return httpVersion;
  }

  public void setHttpVersion(String httpVersion) {

    this.httpVersion = httpVersion;
  }

  public String getConnection() {

    return connection;
  }

  public void setConnection(String connection) {

    this.connection = connection;
  }

  public String getAccept() {

    return accept;
  }

  public void setAccept(String accept) {

    this.accept = accept;
  }

  public String getAcceptEncoding() {

    return acceptEncoding;
  }

  public void setAcceptEncoding(String acceptEncoding) {

    this.acceptEncoding = acceptEncoding;
  }

  public Map<String, String> getHeaderMap() {

    return headerMap;
  }

  public void setHeaderMap(Map<String, String> headerMap) {

    this.headerMap = headerMap;
  }
}

 

 

2) 정적 페이지, 동적 페이지 구분하기 (isFile) -> 경로 안에 파일이 있으면 정적!

3) 정적 페이지는 index.html(해당 페이지)를 읽는다.

더보기
  private File getFile(Request request) {

    String changeRequestUrl = request.getRequestUrl().replace("/", "\\");
    String requestUrl = SERVER_PATH + changeRequestUrl + ".html";
    
    File file = new File(requestUrl);
    
    return file;
  }

이 메소드를 이용해서 파일을 return하고 isFile()이 true이면 정적파일이므로 Response 응답을 보낸다. => sendResponse(file, response);

 

 

4) 클래스 Response로 응답하여 화면을 보여준다.

더보기
  public void sendResponse(File file, Response response) throws IOException {

    response.setFirstLine("HTTP/1.1 200 OK");
    response.setContentType("Content-Type: text/html; charset=UTF-8");
    response.setContentLength("Content-Length: " + file.length());
    response.showScreen(file);
    
    System.out.println("정적 파일 응답 보내줌");
  }

 

여기서도 Response 클래스를 보면 아래와 같다.

더보기
package communication;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;

public class Response {

  private String firstLine;

  private String contentType;

  private String contentLength;

  OutputStream outputStream;

  public Response() {}

  public Response(OutputStream outputStream) {

    this.outputStream = outputStream;
  }

  public OutputStream getOutputStream() {

    return outputStream;
  }

  public void setOutputStream(OutputStream outputStream) {

    this.outputStream = outputStream;
  }

  public String getFirstLine() {

    return firstLine;
  }

  public void setFirstLine(String firstLine) {

    this.firstLine = firstLine;
  }

  public String getContentType() {

    return contentType;
  }

  public void setContentType(String contentType) {

    this.contentType = contentType;
  }

  public String getContentLength() {

    return contentLength;
  }

  public void setContentLength(String contentLength) {

    this.contentLength = contentLength;
  }

  public void showScreen(File file) throws IOException {

    PrintWriter out = new PrintWriter(outputStream);
    out.println(firstLine);
    out.println(contentType);
    out.println(contentLength);
    out.println();
    out.flush();

    ArrayList<Byte> fileBytes = getFileBytes(file);
    byte[] byteArr = new byte[fileBytes.size()];
    int writeCount = 0;
    for (byte fileByte : fileBytes) {
      byteArr[writeCount] = fileByte;
      writeCount++;
    }
    outputStream.write(byteArr);
    outputStream.flush();
  }

  private ArrayList<Byte> getFileBytes(File file) {

    ArrayList<Byte> fileBytes = new ArrayList<Byte>();
    try {
      FileInputStream fis = new FileInputStream(file);
      int data = 0;
      while ((data = fis.read()) != -1) {
        fileBytes.add((byte) data);
      }
      fis.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return fileBytes;
  }
}

 

 

5) 동적페이지는 컨테이너의 역할을 하는 클래스인 ContainerController로 Request와 Response객체를 넘긴다. 그리고 HTTPServletRequest와 HTTPServletResponse 객체를 생성한다.

 

앞 코드 다시 보면 "new ContainerController(request, response).start();" 이것이 있다. 이것이 실행된다.

더보기
package container;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import communication.Request;
import communication.Response;

// 클래스 역할 : httpServletRes,req 객체 생성, xml 분석, 스레드 실행 후 서블릿 실행, httpServletReq 응답 처리(?)
public class ContainerController {

  private static final String XML_PATH = "C:\\Users\\kev\\eclipse-workspace\\WebProject\\WebContent\\WEB-INF\\web.xml";

  private static final String FOLDER_NAME = "/basic";

  private static final String SERVLET_FOLDER_NAME = "servlet.";

  HTTPServletRequest httpServletRequest;

  HTTPServletResponse httpServletResponse;

  Map<String, String> servletMap;

  String foundServletName;

  public ContainerController(Request request, Response response) {

    System.out.println("컨테이너 로직까지 왔음");
    httpServletRequest = new HTTPServletRequest(request);
    httpServletResponse = new HTTPServletResponse(response);
    servletMap = new HashMap<String, String>();
  }

  public void start() {

    // xml분석
    servletMap = readDeploymentDescriptor();
    
    if (servletMap.size() == 0) {
      
      System.out.println("서블릿이 없습니다.");
      
    } else {
      
      foundServlet();
      executeServlet();
      
    }
  }
  ....이어서

여기는 start() 메소드 안을 볼 것!

 

<기억할 메소드>

readDeploymentDescriptor();

foundServlet();

executeServlet();  

 

6번인 XML 파일 분석하기 전에 HTTPServletRequest와 HTTPServletResponse 클래스를 먼저 보겠다.

더보기
package container;

import java.util.HashMap;
import java.util.Map;
import communication.Request;

public class HTTPServletRequest {

  Request request;

  private String method;

  private String queryString;

  private String requestUrl;

  public HTTPServletRequest(Request request) {

    this.request = request;
    
    reApply();
  }

  private void reApply() {

    method = request.getMethod();
    
    String[] separationUrl = request.getRequestUrl().split("\\?");
    
    if (separationUrl.length != 0) {
      
      requestUrl = separationUrl[0];
      queryString = separationUrl[1];
      
    }
  }

  public Request getRequest() {

    return request;
  }

  public void setRequest(Request request) {

    this.request = request;
  }

  public String getMethod() {

    return method;
  }

  public void setMethod(String method) {

    this.method = method;
  }

  public String getQueryString() {

    return queryString;
  }

  public void setQueryString(String queryString) {

    this.queryString = queryString;
  }

  public String getRequestUrl() {

    return requestUrl;
  }

  public void setRequestUrl(String requestUrl) {

    this.requestUrl = requestUrl;
  }

  public String getParameter(String name) {

    Map<String, String> queryStringValue = new HashMap<String, String>();
    
    queryStringValue = divideQueryString();
    
    String result = "";
    
    for (String key : queryStringValue.keySet()) {
      if (key.equals(name)) {
        result = queryStringValue.get(key);
      } else {
        result = null;
      }
    }
    
    return result;
    
  }

  private Map<String, String> divideQueryString() {

    Map<String, String> queryStringValue = new HashMap<String, String>();
    
    String[] separationQuery = queryString.split("=");
    
    queryStringValue.put(separationQuery[0], separationQuery[1]);
    
    return queryStringValue;
  }
}
package container;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import communication.Response;

public class HTTPServletResponse {

  Response response;

  private String contentType;

  OutputStream outputStream;

  private int contentLength;

  public HTTPServletResponse(Response response) {

    this.response = response;
    
    reApply();
  }

  private void reApply() {

    this.outputStream = response.getOutputStream();
  }

  public PrintWriter headerInfo() {

    PrintWriter out = new PrintWriter(outputStream);
    
    out.println("HTTP/1.1 200 OK");
    out.println("Content-Type: text/html; charset=UTF-8");
    out.println();
    out.flush();
    
    return out;
  }

  public PrintWriter getWriter() throws IOException {

    PrintWriter out = headerInfo();
    
    return out;
  }

  public void showScreen(ByteArrayOutputStream bout) throws IOException {

    headerInfo();
    
    bout.writeTo(outputStream);
  }

  public int getContentLength() {

    return contentLength;
  }

  public void setContentLength(int contentLength) {

    this.contentLength = contentLength;
  }

  public String getContentType() {

    return contentType;
  }

  public void setContentType(String contentType) {

    this.contentType = contentType;
  }

  public OutputStream getOutputStream() {

    return outputStream;
  }

  public void setOutputStream(OutputStream outputStream) {

    this.outputStream = outputStream;
  }
}

 

 

 

6) XML 파일을 분석하여

 

readDeploymentDescriptor() 메소드를 이용해서 분석한다. 

더보기
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
	id="WebApp_ID" version="4.0">
	<display-name>Mini_WAS</display-name>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.htm</welcome-file>
		<welcome-file>index.jsp</welcome-file>
		<welcome-file>default.html</welcome-file>
		<welcome-file>default.htm</welcome-file>
		<welcome-file>default.jsp</welcome-file>
	</welcome-file-list>

	<servlet>
		<servlet-name>index2</servlet-name>
		<servlet-class>servlet.MyServlet</servlet-class>
		<init-param>
			<param-name>adminEmail</param-name>
			<param-value>ujsong4@naver.com</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>index2</servlet-name>
		<url-pattern>/basic/SelectBeer.do</url-pattern>
	</servlet-mapping>
</web-app>
private Map<String, String> readDeploymentDescriptor() {

    Map<String, String> servletMap = new HashMap<String, String>();
    
    File file = new File(XML_PATH);
    
    try {
      FileInputStream fis = new FileInputStream(file);
      BufferedReader br = new BufferedReader(new InputStreamReader(fis));
      
      String line = "";
      String oldLine = "";
      String servletClass = "";
      String servletName = "";
      
      for (int i = 1; (line = br.readLine()) != null; i++) {
        if (oldLine.contains("<servlet-name>") && line.contains("<servlet-class>")) {
          int name_firstText = oldLine.indexOf(">") + 1;
          int name_lastText = oldLine.indexOf("</") - 1;

          int firstText = line.indexOf(">") + 1;
          int lastText = line.indexOf("</") - 1;

          servletName = oldLine.substring(name_firstText, name_lastText + 1);
          servletClass = line.substring(firstText, lastText + 1);

          servletMap.put(servletName, servletClass);

        } else if (oldLine.contains("<servlet-name>") && line.contains("<url-pattern>")) {
          int name_firstText = oldLine.indexOf(">") + 1;
          int name_lastText = oldLine.indexOf("</") - 1;

          String servletName2 = oldLine.substring(name_firstText, name_lastText + 1);

          if (servletName2.equals(servletName)) {
            int firstText = line.indexOf(">") + 1;
            int lastText = line.indexOf("</") - 1;
            String servletUrl = line.substring(firstText, lastText + 1);

            servletMap.put(servletUrl, servletClass);
          }
        }
        oldLine = line;
      }
      fis.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return servletMap;
  }

 

 

 

7) 찾을 서블릿을 찾으면 그 서블릿의 service(HTTPServletRequest, HTTPServletResponse) 메소드를 실행한다. 

 

foundServlet()메소드에서 찾는 서블릿을 찾고 executeServlet()메소드에서 service()메소드를 실행한다.

더보기
  private void foundServlet() {

    String requestUrl_new = FOLDER_NAME + httpServletRequest.getRequestUrl();
    
    String servletName = "";
    
    for (String key : servletMap.keySet()) {
      if (key.equals(requestUrl_new)) {
        servletName = servletMap.get(key);
        System.out.println(servletName + " 서블릿 찾았다.");
      }
    }
    
    foundServletName = servletName;
    
  }
  private void executeServlet() {

    try {
      int i = 0;
      for (String servletName : ServletInit.servletNames) {
        if (foundServletName.equals(SERVLET_FOLDER_NAME + servletName)) {
          ServletInit.servletClasses.get(i).service(httpServletRequest, httpServletResponse);
        }
        i++;
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

 

 

 

8) service()는 내가 작성한 서블릿의 오버라이딩된 doGet()를 자동 호출하여 실행한다.

 

Servlet 인터페이스 , HttpServlet 추상 클래스, MyServlet 클래스 순서대로..

더보기
package container;

public interface Servlet {

  public void init(ServletConfig servletConfig);

  public void init();

  public void service(HTTPServletRequest httpServletRequest, HTTPServletResponse httpServletResponse);

  public void destroy();

  public String getServletInfo();

  public ServletConfig getServletConfig();
}
package container;

public abstract class HttpServlet implements Servlet {

  private static final String METHOD_GET = "GET";

  ServletConfig servletConfig;

  @Override
  public void init(ServletConfig config) {

    servletConfig = config;
    this.init();
  }

  public void init() {

    System.out.println("서블릿 init()초기화!");
  }

  public void destroy() {}

  // public String getInitParameter(String name) {
  //
  // return getServletConfig().getInitParameter(name);
  // }
  @Override
  public void service(HTTPServletRequest httpServletRequest, HTTPServletResponse httpServletResponse) {

    System.out.println("service() 메소드는 HttpServlet 클래스 여기로");
    
    String method = httpServletRequest.getMethod();
    
    if (method.equals(METHOD_GET)) {
      System.out.println("doGet() 메소드 실행");
      doGet(httpServletRequest, httpServletResponse);
    }
  }

  public void doGet(HTTPServletRequest httpServletRequest, HTTPServletResponse httpServletResponse) {}

  @Override
  public String getServletInfo() {

    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public ServletConfig getServletConfig() {

    // TODO Auto-generated method stub
    return null;
  }
}
package servlet;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import container.HTTPServletRequest;
import container.HTTPServletResponse;
import container.HttpServlet;

public class MyServlet extends HttpServlet {

  private static final String ENCODING = "utf-8";

  @Override
  public void doGet(HTTPServletRequest httpServletRequest, HTTPServletResponse httpServletResponse) {

    ByteArrayOutputStream bout = new ByteArrayOutputStream(8192);
    PrintWriter out;
    
    try {
      out = new PrintWriter(new OutputStreamWriter(bout, ENCODING));
      // out = httpServletResponse.getWriter();
      
      out.println("<html><head><style>body{background-color: #F8F4BD;}</style></head><body>" + "<h1 align=center>유진스의 선택은!!!!???<br></h1>");
      
      String result = httpServletRequest.getParameter("kind");
      
      out.println("<br><h3>맛도 좋고 몸에도 좋은 ~ <span style = 'color: red'>" + result + "</span></h3></body></html>");
      out.flush();
      
      httpServletResponse.setContentLength(bout.size());
      System.out.println(bout.size());
      
      httpServletResponse.showScreen(bout);
      
    } catch (IOException e) {
      
      e.printStackTrace();
      
    }
  }
}

 

 

 

9) HTTPServletResponse 객체로 응답을 보내서 화면을 보여준다.

 

여기서 2가지 방법을 적용시켜봤다. 

1. 처음에는 동적페이지는 응답 헤더에 content-Length를 주지 않아도 된다고 해서 content-Length를 주지 않는 방법

2. 임시 바이트 버퍼를 사용하여 길이를 먼저 측정하고 출력하는 방법

 

MyServlet의 doGet()메소드와 HTTPServletResponse 2개만 다시 보면 된다.  HTTPServletResponse 부분은 다른 메소드를 사용함

[첫 번째 방법!!!]

더보기
public class MyServlet extends HttpServlet {

  private static final String ENCODING = "utf-8";

  @Override
  public void doGet(HTTPServletRequest httpServletRequest, HTTPServletResponse httpServletResponse) {

    //ByteArrayOutputStream bout = new ByteArrayOutputStream(8192);
    PrintWriter out;
    
    try {
      //out = new PrintWriter(new OutputStreamWriter(bout, ENCODING));
       out = httpServletResponse.getWriter();
      
      out.println("<html><head><style>body{background-color: #F8F4BD;}</style></head><body>" + "<h1 align=center>유진스의 선택은!!!!???<br></h1>");
      
      String result = httpServletRequest.getParameter("kind");
      
      out.println("<br><h3>맛도 좋고 몸에도 좋은 ~ <span style = 'color: red'>" + result + "</span></h3></body></html>");
      out.flush();
      
//      httpServletResponse.setContentLength(bout.size());
//      System.out.println(bout.size());
//      
//      httpServletResponse.showScreen(bout);
      
    } catch (IOException e) {
      
      e.printStackTrace();
      
    }
  }
}

 

[두 번째 방법!!!]

더보기
public class MyServlet extends HttpServlet {

  private static final String ENCODING = "utf-8";

  @Override
  public void doGet(HTTPServletRequest httpServletRequest, HTTPServletResponse httpServletResponse) {

    ByteArrayOutputStream bout = new ByteArrayOutputStream(8192);
    PrintWriter out;
    
    try {
      out = new PrintWriter(new OutputStreamWriter(bout, ENCODING));
      // out = httpServletResponse.getWriter();
      
      out.println("<html><head><style>body{background-color: #F8F4BD;}</style></head><body>" + "<h1 align=center>유진스의 선택은!!!!???<br></h1>");
      
      String result = httpServletRequest.getParameter("kind");
      
      out.println("<br><h3>맛도 좋고 몸에도 좋은 ~ <span style = 'color: red'>" + result + "</span></h3></body></html>");
      out.flush();
      
      httpServletResponse.setContentLength(bout.size());
      System.out.println(bout.size());
      
      httpServletResponse.showScreen(bout);
      
    } catch (IOException e) {
      
      e.printStackTrace();
      
    }
  }
}

 

이렇게 다 하면 앞에 캡쳐한 결과 화면이 실행된다~!

 


아직 ServletConfig 적용도 못했고 GET 메소드 처리하는 것밖에 안했고,, 코드도 정리해야하고,, 사실 이 모든 것이 이상할 수도 있는데...

상무님 검사를 받아야한당,,, 

검사를 받고 또 작성할 것임😤😭🥺

'IT > Servlet & JSP' 카테고리의 다른 글

[Servlet & JSP] 3. 속성과 리스너  (0) 2020.11.09
[Servlet & JSP] 2. Servlet 따라가보기  (0) 2020.11.05

Git(버전관리시스템)


  • git의 목적 
  1. 버전관리
  2. 백업
  3. 협업
  • branch

- 필요에 의해서 작업이 분기되는 현상 

예) 개발 진행 중 현재 작업했던 내용을 서버에 반영하기 위해서 여러가지 테스트를 진행하면서 문제점은 없는지 체크하기 위함. main이 되는 작업과 test를 위한 작업을 분기.

 

 

 

 

branch 만들기


1. 브랜치 목록을 볼 때 

git branch

 

2. 브랜치 생성할 때

git branch "새로운 브랜치 이름"

 

3. 브랜치 삭제할 때 

git branch -d

 

4. 병합하지 않은 브랜치를 강제 삭제할 때

git branch -D

 

5. 브랜치를 전환(체크아웃)할 때

git checkout "전환하려는 브랜치 이름"

 

6. 브랜치를 생성하고 전환까지 할 때

git checkout -b "생성하고 전환할 브랜치 이름"

 

 

 

 

branch 정보 확인


1. 브랜치 간에 비교할 때

git log "비교할 브랜치 명 1".."비교할 브랜치 명 2"

 

2. 브랜치 간의 코드를 비교할 때

git diff "비교할 브랜치 명 1".."비교할 브랜치 명 2"

 

3. 로그에 모든 브랜치를 표시하고, 그래프로 표현하고, 브랜치 명을 표시하고, 한줄로 표시할 때 

git log --branches --graph --decorate --oneline

 

 

 

 

branch 병합


1. A 브랜치로 B 브랜치를 병합할 때 (A ← B)

git checkout A
git merge B

 

2. 충돌이 일어났을 때

 

- 충돌이 생기면 "Auto-merging ..." 메시지가 뜸

- git status를 하면 충돌이 일어난 파일 찾을 수 있음

- 충돌 작업을 끝냈다는 것을 git에게 알려줌

git add 'conflicted file name'

 

 

 

 

stash 


stash : 감추다, 숨겨두다의 뜻을 가지고 있음

 

"다른 브랜치로 checkout을 해야 하는데 아직 현재 브랜치에서 작업이 끝나지 않은 경우는 커밋을 하기가 애매합니다. 
이런 경우 stash를 이용하면 작업중이던 파일을 임시로 저장해두고 현재 브랜치의 상태를 마지막 커밋의 상태로 초기화 할 수 있습니다.  그 후에 다른 브랜치로 이동하고 작업을 끝낸 후에 작업 중이던 브랜치로 복귀한 후에 이전에 작업하던 내용을 복원할 수 있습니다. "

 

 

1. 하던 작업을 저장하고 가장 최근 commit 상태로 만듦

git stash

 

2. 저장되어 있는 작업 중 가장 최근 stash를 가져옴

git stash apply

git stash pop   //git stash apply + git stash drop 

pop은 한번 불러오면 stash 목록에 저장한 시점이 삭제되어있고 apply는 해당 stash를 불러와도 list에 남아있음

 

 

3. stash 목록볼 때

git stash list

 

 

4. stash 삭제할 때

git stash drop[stash@[숫자]]

 

4. stash help

git stash --help

 

# git stash는 명시적으로 삭제하지 않는다면 항상 살아있다.

# git stash는 최소한 버전관리가 되는 파일만 적용된다.

 

<참고>

opentutorials.org/course/2708

'IT > Git' 카테고리의 다른 글

[Git] git 연결 끊기 & 새로운 로컬 저장소 연결  (0) 2020.12.14
[Git] Git 원격 저장소 여러개 연결  (0) 2020.12.03

발표자료 4 ~ 7 단원 내용 중 ...

2. Servlet 따라가보기 이어서!

발표자료보다 내용을 더 추가해서 PPT가...없을 수 있다....ㅎ..

 

[이전글]

2020/11/05 - [IT/Servlet & JSP] - [Servlet & JSP] 2. Servlet 따라가보기 

 

[Servlet & JSP] 2. Servlet 따라가보기

발표자료 4 ~ 7 단원 내용 중 ... Servlet 최초 클라이언트의 요청이 있기 전에 서블릿은 항상 로딩되고 초기화됩니다. 로딩되는 시기는 컨테이너 시작 (즉, 톰캣이 실행될 때) 이루어집니다. 그리고

song-yujin.tistory.com

 

속성과 리스너


이제 속성 내용은 앞에서 이야기한 ServletConfig ServletContext내용입니다.

 

 

 

앞에서 이야기한 ServletConfig 가장 역할은 서블릿 초기화 파라미터에 접근할 있는 능력이 있다는 것입니다.

ServletConfig 값을 설정하기 위한 곳이 코드 보이는 것처럼 DD(배포 서술자)이고, 하나의 Servlet에서 계속적으로 읽어와서 사용하고자하는 용도보다는 처음에 Servlet 로딩시 읽어와서 계속 사용하고자 하는 의미가 큽니다.

 

서블릿의 설정이기 때문에 servlet안에다가 작성을 해줘야합니다. 그리고 서블릿 코드에서는 GetServletConfig() 메소드의 리턴값이 servletConfig여서 servletConfig getInitParameter()메소드를 접근하여 사용할 있습니다.

 

 

 

그런데 화면을 나타내는 JSP페이지에서도 서블릿의 초기화 파라미터 값을 읽으려면, 이것은 그대로 서블릿의 설정이기 때문에 가지 작업을 해야합니다.

그리고 만약에 이메일 정보를 모든 애플리케이션에서 공유해야하고, 요청할 때마다 달라지지 않을 , 서블릿에만 적용되는 초기화 파라미터 말고 애플리케이션 적용되는 것이 없나? 대한 한계가 나왔습니다.

 

 

 

한계의 답이 컨텍스트 초기화 파라미터입니다.

이것은 서블릿 초기화 파라미터와 동일하지만, 컨텍스트 초기화 파라미터는 특정 하나의 서블릿만 사용하는 것이 아니라 모든 애플리케이션에서 이용할 있다는 차이가 있습니다. 애플리케이션에 있는 모든 JSP, 서블릿에서 별다른 코딩 없이도 컨텍스트 초기화 파라미터 정보에 접근할 있으며, 모든 서블릿의 DD 수정하는 따위는 하지 않아도 됩니다.

 

코드를 보면 servletConfig 거의 비슷한데 다른점은 servlet 안에다가 작성하면 안되는 부분, 그리고 getServletContext()라는 것입니다.

 

 

 

표는 ServletConfig와 ServletContext 비교하여 정리한 표입니다. 

 

ServletContext에 대해서 더 정리를 하겠습니다.

ServletContext는 JSP나 서블릿을 컨테이너 또는 다른 웹 애플리케이션과 연결하는 끈이라고 할 수 있습니다.

 

 

컨텍스트 파라미터의 한계


컨텍스트 파라미터에는 String 밖에 저장할 수 없습니다. 

초기화 파라미터로 Dog라는 객체를 저장하고 싶을 때나 애플리케이션에 있는 모든 서블릿이 데이터베이스 Connection객체를 공유하고 싶을 때는 어떻게 하지? 라는 한계가 나왔습니다. 

 

가장 일반적인 방법은 "컨텍스트 파라미터에 DataSource 검색명(lookup name)를 저장하는 것입니다.

 

그런데 이것을 또 누가 하느냐? 이야기가 나올 수 있습니다.

서블릿에서 검색명으로 실제 DataSource 객체를 찾아서, 이 객체를 속성에 묶어두면 되지 않을까? 싶지만 안됩니다. 

서블릿 코드 안에다가 넣을 수 없는 이유는 어떤 서블릿이 가장 먼저 호출될지 모르기 때문입니다.

 

 

 

 

리스너


이것에 대한 답은 리스너입니다. 

 

웹 애플리케이션에도 main()처럼 제일 먼저 실행되는 메소드, 어떤 서블릿이나 JSP보다도 먼저 실행되어야 하는 코드를 작성할 메소드!

이것이 바로 리스너입니다.

 

즉, 컨텍스트 초기화 이벤트에 리스닝하는 것입니다. 이를 이용하면 컨텍스트 초기화 파라미터를 읽은 다음, 애플리케이션이 클라이언트에게 서비스하기 전에 특정 코드를 실행할 수 있습니다. 

 

이것은 서블릿이나 JSP가 아닌 다른 자바 객체가 해주는데 "ServletContextListener"입니다.

ServletContextListener의 목적은 애플리케이션 초기화입니다.  

 

이 클래스는 ServletContext 이벤트(초기화(생성)와 소멸)에 귀 기울이고 있어야 합니다. 

javax.servlet.ServletContextListener 인터페이스를 구현하면 이 클래스를 만들 수 있습니다. 

 

빨간색 표시로 되어있는 부분은 구현한 클래스가 이러한 기능들을 가져야 합니다.

 

 

ServletContextListener 사용하기 


지금부터 ServletContextListener를 만들고 실행시키는 테스트를 해서 어떻게 동작하는지 보겠습니다. 

 

이 예제에서는 String 초기화 파라미터를 실제 객체(Dog)로 전환할 것입니다.

 

<시나리오>

  1. 리스너는 컨텍스트 초기화 파라미터에서 개의 품종을 확인한 다음, 이 값을 Dog 객체 생성자 인자로 사용하여 Dog를 생성
  2. 리스너는 Dog 객체를 ServletContext 속성에 묶어 두는 작업

그러면 서블릿이 이를 끄집어 낼 수 있습니다. 

이렇게 하면 서블릿이 애플리케이션 객체(여기서는 Dog)를 공유할 수 있습니다. 이제 서블릿이 컨텍스트 파라미터를 읽을 필요가 없습니다. 

 

초기화 파라미터로 객체를 생성할 수 있다는 것과 이 객체를 모든 애플리케이션에서 공유할 수 있다는 것이 중요합니다. 

 

 

<Dog 예제>

  1. 리스너는 ServletContextEvent에서 ServletContext의 참조를 얻습니다.
  2. 리스너는 ServletContext에서 컨텍스트 초기화 파라미터 품종(breed)이 무엇인지 값을 읽습니다. 리턴되는 값은 품종이 무엇인가 하는 String입니다.
  3. 리스너는 품종 String으로 Dog 생성자를 호출합니다. 
  4. 리스너는 ServletContext의 속성(Attribute)에 생성한 Dog를 물려 놓습니다.
  5. 테스트 서블릿에서 ServletContext에 물려 있는 Dog 객체를 끄집어 내어, 품종이 무엇인지 getBreed() 메소드를 호출하여 확인합니다. 

따라서 정리를 하면,

1. ServletContext 이벤트를 리스닝하기 위해서는 ServletContextListener 인터페이스를 구현

2. 컴파일된 파일을 WEB-INF/classes 디렉토리에 배포

3. 배포 서술자 web.xml에 <listener> 항목을 추가하여 컨테이너에게 알림

 

 

 

ServletContextListener 튜토리얼


3개의 클래스와 하나의 DD가 필요합니다.

 

  1. MyServletContextListener.java (The ServletContextListener) : 컨텍스트 초기화 파라미터를 읽어, Dog 객체를 생성하고 이를 컨텍스트 속성에 묶음
  2. Dog.java (속성 클래스) : 평범한 자바 객체, ServletContextListener에서 생성되어 속성값이 됨. ServletContext의 속성에 묶어 두면, 나중에 서블릿이 꺼내 씀
  3. ListenerTester.java (서블릿) : HttpServlet 클래스를 상속함. 컨테스트에서 Dog 객체를 끄집어 내어 getBreed() 메소드를 호출하여 이 내용을 Response에 출력할 것.
  4. web.xml (배포 서술자) : 컨테이너한테 리스너를 만들었음을 알려줄 때. 

 

 

1. MyServletContextListener 작성

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class MyServletContextListener implements ServletContextListener {

  public void contextInitialized(ServletContextEvent event) {

    ServletContext sc = event.getServletContext();  // event에게 servletContext 요구
    String dogBreed = sc.getInitParameter("breed");   //컨텍스트에서 초기화 파라미터 읽음
    Dog d = new Dog(dogBreed);   //dog 객체 생성
    sc.setAttribute("dog", d);    //컨텍스트 속성에 이름/객체의 쌍으로 묶어 놓음
  }

  public void contextDestroyed(ServletContextEvent event) {

  }

}

컨텍스트 초기화 파라미터에서 개의 품종이 무엇인지 확인하여, 해당 품종의 Dog 객체를 생성한 다음, 이 객체를 컨텍스트 속성으로 묶어 놓은 것입니다. 

 

 

2. Dog 클래스 작성

public class Dog {

  private String breed;

  public Dog(String breed) {

    this.breed = breed;
  }

  public String getBreed() {

    return breed;
  }
}

컨텍스트 초기화 파라미터로 생성한 다음, ServletContext에 이 클래스를 속성으로 저장할 것입니다. 

 

 

3. ListenerTester 작성

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class ListenerTester
 */
@WebServlet("/ListenerTester")
public class ListenerTester extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public ListenerTester() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

	  response.setContentType("text/html");
	  PrintWriter out = response.getWriter();
	  
	  out.println("test context attributes set by listener<br>");
	  
	  out.println("<br>");
	  
	  Dog dog = (Dog) getServletContext().getAttribute("dog");   //리스너가 제대로 작동하지 않았다면, NullPointException 예외를 만날 것임.
	  
	  out.println("Dog's breed is: " + dog.getBreed());
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

이 클래스는 ServletContextListener를 테스트하기 위한 것입니다. 제대로 작동한다면, 서블릿의 doGet()을 실행할 경우 Dog 객체가 서블릿 컨텍스트 안에 기다리고 있습니다. 

 

 

3. ListenerTester 작성

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
  <display-name>Project2</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>

  <context-param>
  	<param-name>breed</param-name>
  	<param-value>Great Dane</param-value>
  </context-param>
  
  <listener>
  	<listener-class>
  		MyServletContextListener
  	</listener-class>
  </listener>
</web-app>

 

'IT > Servlet & JSP' 카테고리의 다른 글

[Mini_WAS] WAS만들기 - 중간점검  (0) 2020.12.02
[Servlet & JSP] 2. Servlet 따라가보기  (0) 2020.11.05

발표자료 4 ~ 7 단원 내용 중 ...

 

Servlet


최초 클라이언트의 요청이 있기 전에 서블릿은 항상 로딩되고 초기화됩니다. 로딩되는 시기는 컨테이너 시작 (즉, 톰캣이 실행될 때) 이루어집니다.

 

그리고 이제 클라이언트 요청을 받았을 때 흐름순서를 보면,

 

  1. 클라이언트 사용자쪽에서 URL을 입력하면 HTTP Request가 servlet Container로 전송합니다. 
  2. 컨테이너는 HttpServletRequest와 HttpServletResponse 두 개의 객체를 생성합니다.
  3. 그리고 URL을 분석하여 어떤 서블릿을 요청했는지 파악하고 해당 서블릿을 처리할 스레드를 생성해서 request와 response 객체 참조를 넘깁니다. 
  4. 컨테이너는 해당 서블릿 service() 메소드를 호출하여, service() 메소드는 doGet()고 doPost()를 자동 호출하는데, 브라우저에서 지정한 방식에 따라 둘 중에 하나를 선택해서 호출합니다.
  5. doGet() 메소드는 동적 페이지를 생성한 후, HttpServletResponse 객체에 응답을 보냅니다. 
  6. 응답이 끝나면 HttpServletRequest와 HttpServletResponse 두 객체를 소멸시킵니다. 

 

이런 서블릿에 관련된 것들은 모두 javax.servlet 이나 javax.servlet.http 둘 중 하나입니다.

http와 관련이 있는 것들은 javax.servlet.http이고 나머지는 javax.servlet입니다.  

 

 

자바 서블릿 클래스는 Servlet 인터페이스를 구현해야 합니다. Servlet 서블릿 컨테이너가 서블릿에 대해 호출하는 메소드 5가지를 정의한 인터페이스입니다. 굵은 표시로 되어있는 것이 생명주기 메소드입니다.

 

GenericServlet이랑 HttpServlet 추상 클래스로

- genericServlet 대부분 서블릿의 서블릿 행위 라고 하는 것들이 여기서부터 나왔다는 것

- http http적인 측면을 반영하기 위해서 service()메소드를 재정의한 것입니다. 메소드는 오로지 http request response 받아드립니다.

 

마지막으로 Myservlet 이런 상위 클래스의 메소드를 상속받음으로써, 여기선 doGet()이나 doPost() 메소드를 재정의하기만 하면 됩니다.

 

 

 

그래서 총 앞의 흐름대로 이야기를 하면

 

  1. 컨테이너가 처음 init() 메소드를 호출합니다. 여기서 init()을 재정의하지 않으면 아까 말한 '서블릿 행위'인 genericServlet의 init()을 호출합니다.
  2. 다음 클라이언트의 요청이 들어오면 새로운 스레드를 만들어서 service()메소드를 호출합니다. 이것도 재정의하지 않으면 httpServlet service()메소드가 실행됩니다.
  3. 그리고 Service()메소드에서 재정의한 doGet()이나 doPost() 하나를 실행하는 것입니다.

이렇게 앞에 나오는 그림이나 글로 봤을 때 이해는 되지만 너무 추상적인 느낌이 들고, 메소드들을 직접 확인해보고 싶었습니다. 

 

 

 

그래서 처음 초기화하는 init() 메소드부터 확인을 해봤습니다.

생성자의 실행은 단지 서블릿이 존재하지 않음상태에서 초기화됨상태로 옮겨감을 의미합니다. 즉, 클라이언트의 요청에 서비스할 준비가 되었다는 뜻입니다.

그리고 생성자는 단지 객체를 만드는 것이지 서블릿을 만드는 것이 아닙니다. 서블릿이 되기 위해서는 서블릿적인 것을 내려받아야하는데, 쉽게 말해서 자기 명함을 갖고 있는 정식 서블릿이 되려면 2가지가 필요합니다.

 

 

 

2가지는, ServletConfig ServletContext 파라미터 입니다.

두가지에 대해서는 뒷부분에 자세히 나오지만 일단 필요한 부분만 먼저 이야기하자면, ServletConfig에서 'config' 설정의 뜻으로, 서블릿마다 설정했던 값을 뜻합니다. ServletConfig 통해 서블릿 이름, 서블릿 서블릿 초기 매개변수 , 서블릿 환경 정보 등을 얻을 있습니다.

 

 

 

그래서 다시 돌아와서 서블릿이 되기 위해서는, 서블릿을 설정하는 servletConfig 필요합니다.

컨테이너가 서블릿을 초기화할 , 사실 init()메소드를 먼저 실행하여 존재하지 않음에서 초기화된 서블릿 객체를 만들고, 단지 객체인 것을 다음 init(ServletConfig) 메소드를 호출하여 리얼 정식 서블릿을 생성합니다.

 

 

 

서블릿마다 하나씩 ServletConfig 생성하는데, 컨테이너는 DD에서 서블릿 초기화 파라미터를 읽어서 정보를 이름, 쌍으로 해서 ServletConfig 넘겨줍니다. 다음에 ServletConfig 서블릿의 init()메소드에 제공합니다.

 

 

 

 

여기서 ServletConfig 사용할 있는 이유는 앞에서 Servlet 인터페이스 부분을 보면 getServletConfig() 메소드가 있는데, 메소드가 리턴한 ServletConfig 객체에 대한 참조로 ServletConfig 메소드를 호출할 있습니다.

 

 

 

 

이렇게 흐름의 첫 번째인 init() 대한 것을 봤습니다.

이제는 마지막 2~3번째 service() 호출하고 실행이 되어서 재정의한 doGet() doPost() 실행에 대한 것을 보겠습니다.

 

 

 

httpServlet service()메소드 내용입니다. 클라이언트가 날린 http 메소드가 무엇인지 보고 메소드 종류에 따라서 메소드를 호출합니다. 그래서 service() 호출해서 doGet()이든 doPost() 실행하는 것입니다.

 

 

 

여기까지 코드를 직접 보면서 흐름 이해를 해봤고 다음에는 doGet() doPost() 인자로 request response객체의 참조를 넘긴다고 했는데 객체를 가지고 있다는 것이 무엇을 의미하는지 확인해봤습니다.

 

 

 

httpServletRequest 인터페이스는 http 프로토콜에 관련된 메소드들이 추가되어 있습니다. 서블릿이 클라이언트/브라우저와 대화하기 위한 것들이고 httpServletResponse 인터페이스는 http 관련된 오류, 쿠키, 헤더 정보에 대한 메소드들이 추가되어있습니다.

 

이 2개의 인터페이스는 책에 따르면,,

 

 

Head First Servlets & JSP 책

이 2개의 인터페이스는 누가 구현하나? API에 구현된 클래스가 있나? 

그 답은 '컨테이너'이고 '없습니다.' 입니다. 컨테이너 벤더가 구현하는데 어떻게 되는지 신경 쓰지 마세요.! 

요점은 컨테이너가 넘겨준 객체에서 어떤 메소드를 호출할 수 있는가를 아는 것입니다.

 

 

 

 

Request 먼저보면, index.html에서 개의 옵션 사용자가 선택한 하나를 color라는 이름의 파라미터로 전송을 합니다.

전송 method 방식은 post 하고 MyServlet.post()메소드를 보면 request.getParameter()폼에 있는 이름과 맞게 color라고 작성해주고 선택한 값이 들어가게 됩니다.

예시 말고도, 파라미터 값이 여러개일 경우 있지만 결국에는 request 클라이언트의 요청을 처리해주는 것입니다.

 

 

 

이제는 response 클라이언트로 돌려보내는 것입니다. 정보를 분석해서 브라우저는 화면을 출력합니다. Response 객체는 제가 언급하고 싶은 하나가 setContentType()메소드 입니다. 

contentType 무엇을 의미하냐면, 브라우저에게 지금 내려보내는 것이 무엇이다라는 것을 알려주는 것입니다. 그래야 브라우저가 이예 대비하여 여기서는 html 화면에 그릴 준비를 합니다. contentType HTTP 응답에 반드시 포함되어야 하는 HTTP 헤더 정보입니다.

 

 

 

그리고 요청에 대한 응답을 누가할지 선택할 수도 있습니다.

요청을 완전히 다른 URL 방향을 바꿀 수도 있고, 애플리케이션에 있는 다른 컴포넌트(JSP)에게 처리를 위임할 수도 있습니다.

리다이렉트에서는 방향을 바꾸겟다고 판단했다면, sendRedirect()메소드만 호출하면 됩니다.

디스패치는 다른 컴포넌트, JSP 작업을 넘기는 것의 코드로 RequestDispatcher 사용합니다.

'IT > Servlet & JSP' 카테고리의 다른 글

[Mini_WAS] WAS만들기 - 중간점검  (0) 2020.12.02
[Servlet & JSP] 3. 속성과 리스너  (0) 2020.11.09

회사에서 자바 프로젝트로 채팅 프로그램을 만들었다. 

swing을 이용한 소켓 채팅 프로그램........

처음에 스윙이 뭐지?하면서 코드가 어색했는데 지금은 너무 익숙하다ㅋㅋㅋ 

 

너무 힘들었던 3가지를 기록해야겠따..흑흑

 

1) 채팅할 때 화면에 나오는 나는 오른쪽, 친구는 왼쪽 정렬이 너무 어려웠다.
하아ㅏㅏㅏㅏ 이걸로 거의 일주일은 날린 것 같다.

처음에 JTextArea를 사용했다. 그런데 왼쪽, 오른쪽 정렬이 따로 되지 않는 것과 이미지는 불가능하다는 것.

그래서 구글링하다가 JTextPane을 발견했다. 이것을 사용하면 될 것 같은데 정보가 많이 없었다..ㅠㅅㅠ

일주일동안 삽질하다가 인턴 같이 하는 오빠가 메소드 하나하나씩 분석해가면서 성공시켰다.!!!!!!!!!!!!!!!!!!!!!!

덕분에 잘 적용할 수 있었다. 고마워요...

 

아 지금 이거 적다가 나머지 힘들었던 2가지가 기억나질 않는다. (큰일) 2가지만 적어야지..

 

   

2) 그룹채팅이 아니라 1대 1 채팅 만든 것. 

이것저것 참고해가면서 채팅 프로그램을 만드는데 보통 다 그룹채팅 형식이었다.
(서버쪽에서 연결된 모든 클라이언트를 저장하는데, 메시지를 받고 보낼 때 모든 클라이언트한테 전송함 - 조건을 주지 않으므로 더 간단함.)    
그래서 처음 소켓 통신 성공시키고 대화하는데 웃겼다. 내 아이디로 로그인해서 A친구 채팅방에 글을 쓰고 전송 누르면, A친구한테만 전달되는 것이 아니라 열려 있는 모든 채팅방 친구들한테 다 전달이 된다.ㅋㅋㅋ

하지만 내 채팅은... 카톡처럼 친구 리스트(?)를 누르면 그 친구랑 대화하도록 GUI를 만들어서 그럴 수 없다구,,,  1:1 이어야 한다구....  
결론은 성공시켰지만 생각보다 간단하지 않았다. 채팅창의 정보도 저장하고 있어야하고 보내는 사람, 받는 사람 비교
도 필요하고! 아 그냥 직접 해봐야 알 수 있는 것..

 

 

 

코드 정리를 더 완벽하게 하고 싶은데 여기서 마무리하려고 한다. 

시간을 길게 가진다고 되는 것이 아니라는 걸 너무 느꼈기 때문에...@!@!

나중에 폭풍성장하고 그때 다시 와서 보면 더 완벽하게 할 수 있지 않을까 싶다ㅎㅎㅎ


KEVotingTalk

  • 자바 스윙을 이용한 소켓 채팅 프로그램
  • 사용 기술 : JAVA
  • 설명 : 1 대 1 채팅 KEVotingTalk입니다. GUI를 구현하기 위해 swing을 이용했고 서버와 클라이언트는 소켓 통신을 통해 연결됩니다. 마지막으로 회원관리를 위해 MySQL DB 사용했습니다.
  • github : github.com/YuJinSong412/KEVotingTalk

 

  • 클래스 설명
    • client.datacommunication.ClientSocket.java : 클라이언트 측 송수신 파일
    • client.frame.ChatWindowFrame.java : 채팅 창 파일
    • client.frame.ChatWindowPanel.java : 채팅 화면 파일
    • client.frame.ErrorMessagePanel.java : 에러 화면 파일
    • client.frame.FriendListPanel.java : 친구 목록 화면 파일
    • client.frame.IndexPanel.java : 메인 화면 파일
    • client.frame.JoinMembershipPanel.java : 회원가입 화면 파일
    • client.frame.LoginPanel.java : 로그인 화면 파일
    • client.frame.MainFrame.java : 애플리케이션 창 파일
    • client.frame.MainPanel.java : 애플리케이션 시작 화면 파일
    • client.ClientLaunch.java : 클라이언트 실행 파일
    • controller.Controller.java : java와 DAO 사이의 중간 역할 파일
    • enums.AlignEnum : 채팅창 화면 오른쪽, 왼쪽 정렬 상수 파일
    • enums.CommonWord : 애플리케이션 상수 파일
    • server.datacommunication.Message.java : 메시지 정보 파일
    • server.datacommunication.ServerHandler.java : 서버 측의 송수신 파일
    • server.userdb.User.java : 사용자 정보 파일
    • server.userdb.UserDAO.java : DB의 데이터 접근 파일
    • server.userdb.userDB.sql : MySQL의 쿼리 파일
    • server.ServerLaunch.java : 서버 실행 파일
    • util.ColorSet.java : 애플리케이션 색깔 모음 파일
    • util.CommonPanel.java : 애플리케이션 기본 화면 파일
    • util.JFrameWindowClosingEventHandler.java : 창 닫는 이벤트 파일
    • util.MainPanelButton.java : 애프릴케이션 시작 버튼 파일
    • util.UseImageFile.java : 애플리케이션 이미지 파일
    • util.UserInfoPanel.java : 애플리케이션 이미지 파일
    • util.UserProfileButton.java : 친구 버튼 파일

 


ScreenShot

 

첫 화면

 

 

 

회원가입, 로그인 화면

 

 

 

에러 화면

 

 

 

메인 화면

 

 

 

나와의 채팅

 

 

 

 

이미지 첨부

 

 

1:1 채팅

 

자바 스윙(java swing)을 이용한 채팅 프로젝트를 하고 있는데 모든 클래스에 노란줄이 있다.. 

사실 바로 전에 계산기 만들 때도.. 계속 함께 했다... 

대체 왜 뜨냐고!!! 

 

>> serializable과 관계가 있다.

 

 

그래도 일단 해결 방법을 앞에다가 적어야지~~

#노란 경고메시지 없애는 해결 방법

1. Add default serial version ID

2. Add generated serial version ID

3. Add @SuppresWarnings 'serial' to Object

그냥 3개 중 선택해서 눌러주면 된다.ㅇㅅㅇ

 

 

이제 이유를 알아볼 차례!!

# serializable(직렬화)

1. serializable(직렬화)가 무엇이지?

- 객체의 데이터를 일렬로 늘어선 연속적인 바이트로 변경하는 것 

 

 

자바는 메모리에 생성된 객체를 파일이나 네트워크로 입출력할 수 있다.  그런데 객체는 문자가 아니기 때문에 바이트 기반 스트림으로 입출력해야 한다. 

 

왜냐하면 자바에서 데이터는 stream을 통해 입출력되는데(java.io 패키지에서 제공), 스트림 클래스는 크게 두 종류로 구분된다.

1. 바이트 기반 스트림 -> 그림, 멀티미디어, 문자 등 모든 종류 데이터

2. 문자 기반 스트림 -> 문자

 

따라서 객체를 출력하기 위해서는 객체의 데이터를 일렬로 늘어선 연속적인 바이트로 변경해야 하는데, 이것을 객체 직렬화라고 한다.

그리고 전송된 객체를 입력 스트림으로부터 읽어들인 것을 복원하는 것을 deserialization(역직렬화)라고 한다.

 

이것은 java.io.Serializable 이라는 인터페이스로 구현되어 있고,

직렬화하려는 클래스에 Serializable 인터페이스를 implements 하게 된다. 

그런데 Serializable 인터페이스를 implements 하게 되면 노락색 경고 메시지가 나온다.

The serializable class MainFrame does not declare a static final serialVersionUID field of type long

나처럼 무시해도 상관없지만..ㅎ 이유는 알아야 하니깐...

 

# Warning 이유

serialVersionUID는 직렬화에 사용되는 고유 아이디인데, 객체를 직렬화할 때 serialVersionUID도 저장이 된다.

만약 선언하지 않으면 JVM에서 default로 자동 생성된다. 

따라서 선언하지 않아도 동작하는데 문제가 없지만, 불안하기 때문에 명시적으로 선언할 것을 권장하는 것이다.

 

고유 아이디? 왜 불안한가?

모든 class는 UID를 가지고 있는데 내용이 변경되면 UID값도 변경된다. 

만약 직렬화하여 통신하는데 UID 값이 바뀌게 되면 다른 class로 인식하기 때문에 명시를 해주는 것이다.

 

 

그래서 노란색 경고 메시지가 뜨는 것이고, 

내가 작성한 클래스에 extends한 상위 클래스나 implements를 따라가보면 implements Serializable 부분을 발견할 것이다.

 

 

한번 찾아보기로 했다.

1. ctrl 를 누른 상태로 JFrame 클릭하면 JFrame 클래스로 이동한다. 

 

2. implements serializable가 없어서 Frame 도 보고 implements한 WindowConstants, Accessible.. 전부 보다가 TransferHandler를 눌렀는데!!!!!!!

 

 

3. 발견했다..!

그런데 TransferHandler 뭐하는 놈이지...

위로 올려서 package를 보니까 "package javax.swing; 이네??

docs 보니까 이 클래스는 Swing의 컴포넌트와........생략하도록 하자. 

 

나는 3번으로 해결했다!

+ Recent posts