이전 포스트 [CentOS 7 minimal 설치 #3] MySQL 5.7 설치에서도 한번 언급을 했었습니다만, 이전 버전과 방식이 좀 바뀐것 같습니다.

MySQL 5.6 까지는 설치후 콘솔에서 바로 root 로 접속 해서 비밀번호를 변경할 수 있었지만 MySQL 7 (ver 5.7) 부터는 설치 되면서 랜덤스트링을 암호화 한 비밀번호가 자동으로 부여되고 그것을 찾아서 접속해서 원하는 비밀번호를 부여하는 방식으로 바뀐것 같습니다.

왜 그럴까... 생각해 보면 많은 분들이 MySQL을 설치하고 root 비밀번호를 관리 하지 않아서 외부로 부터 쉽게 칩입을 당하는 케이스들 때문에 변경 된 것이라고 생각합니다.

그런데 이런 것을 모르는 상태에서 MySQL 7 버전을 설치 했다가 root 접속을 못해서 난감했던 기억이 나네요.

일단 설치는 위에 올린 링크를 참고 하시고 (이 포스트와 같은 내용이 설치에도 나옵니다.)  mysqld-safe --skip-grant-tables 명령을 대신헤서 5.7이상 버전에서는 비밀번호 분실시 어떻게 비밀번호를 변경하는지 확인해 보겠습니다.

MySQL 5.7 공식 설치 문서 (https://dev.mysql.com/doc/refman/5.7/en/linux-installation-yum-repo.html)에 보시면 중간쯤에 설치후 superuser 계정 'root'@'localhost' 에 대한 접근 방법이 나옵니다.

shell> sudo grep 'temporary password' /var/log/mysqld.log

이렇게 나오는데요... 유닉스 명령이 익숙하지 않은 분들을 위해 설명하면... root 권한으로 설치 로그에서 'temporary password' 라는 문자열이 들어 있는 행을 표시하라는 내용 정도로 이해하시면 될 것같습니다.

shell> cat /var/log/mysql.log | grep 'temporary password'

이런 거랑 비슷한 명령 일 것 같네요. 

 

설치후 임시 비밀번호 바꾸는 방법

저의 경우 설치 후 데몬을 가동후에 확인해 보니

[root@localhost ~]# grep 'temporary password' /var/log/mysqld.log

2018-02-13T14:49:45.720116Z 1 [Note] A temporary password is generated for root@localhost: tur++-dvf7tI

tur++-dvf7tI 이게 초기 비밀번호 입니다.

임시 비밀번호로 로그인 해서 비번을 바꾸는 내용입니다.

[root@localhost ~]# mysql -u root -p

Enter password : tur++-dvf7tI

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 2

Server version: 5.7.21



Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.



Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.



Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.



mysql> use mysql

ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.


/* 임시 비번을 변경하기 전에는 아무것도 할 수가 없네요 */

/* 새로운 비밀번호로 변경 */
mysql> alter user 'root'@'localhost' identified by '새비밀번호';

Query OK, 0 rows affected (0.00 sec)



mysql> commit;

Query OK, 0 rows affected (0.00 sec)



mysql> quit

Bye

 

비밀번호가 잘 바뀌었는지 확인해 보도록 하겠습니다.

[root@localhost download]# mysql -u root -p

Enter password:변경한비밀번호

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 3

Server version: 5.7.21 MySQL Community Server (GPL)



Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.



Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.



Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.



mysql> use mysql

Reading table information for completion of table and column names

You can turn off this feature to get a quicker startup with -A



Database changed

mysql> show tables;

+---------------------------+

| Tables_in_mysql           |

+---------------------------+

| columns_priv              |

| db                        |

| engine_cost               |

| event                     |

| func                      |

| general_log               |

| gtid_executed             |

| help_category             |

| help_keyword              |

| help_relation             |

| help_topic                |

| innodb_index_stats        |

| innodb_table_stats        |

| ndb_binlog_index          |

| plugin                    |

| proc                      |

| procs_priv                |

| proxies_priv              |

| server_cost               |

| servers                   |

| slave_master_info         |

| slave_relay_log_info      |

| slave_worker_info         |

| slow_log                  |

| tables_priv               |

| time_zone                 |

| time_zone_leap_second     |

| time_zone_name            |

| time_zone_transition      |

| time_zone_transition_type |

| user                      |

+---------------------------+

31 rows in set (0.00 sec)

mysql> quit

Bye

잘 변경이 된 것 같습니다.

 

 

root 계정의 비밀번호를 분실 했을 경우 바꾸는 방법

끝으로 이전 버전에서 root 비밀번호를 분실 했을때 유용하게 이용되던 mysqld-safe 명령에 대해서 알아 보겠습니다.

MySQL 5.7버전을 설치 한 후에 저 명령을 아무리 찾아도 없었습니다. 구글링 끝에 

https://stackoverflow.com/questions/33510184/change-mysql-root-password-on-centos7

이런 내용의 글을 보았습니다.

간단히 요약해 보면

1. mysqld-safe  명령은 사라졌음

2. user 테이블의 구조가 바뀌었음

그래서 아래의 순서에 따라서 비밀번호를 바꿀 수 있습니다.

 

1. MySQL 서버 중지

[root@localhost ~]# systemctl stop mysqld

2. MySQL 환경변수 설정

[root@localhost ~]# systemctl set-environment MYSQLD_OPTS="--skip-grant-tables"

3. MySQL  서버 가동

[root@localhost ~]# systemctl start mysqld

4. 비번 없이  root 로그인

[root@localhost ~]# mysql -u root

5.  root 비밀번호 업데이트

mysql> UPDATE mysql.user SET authentication_string = PASSWORD('새비밀번호')

    -> WHERE User = 'root' AND Host = 'localhost';

mysql> FLUSH PRIVILEGES;

mysql> quit

6.  MySQL 중지

[root@localhost ~]# systemctl stop mysqld

7. MySQL 환경변수 초기화

[root@localhost ~]# systemctl unset-environment MYSQLD_OPTS

8. MySQL 서버 시작

[root@localhost ~]# systemctl start mysqld

9. 새 비밀번호로 로그인 확인

[root@localhost ~]# mysql -u root -p

참고로  user 테이블이 변경 된것은 user 테이블의 접근을 쿼리가 아닌 시스템 명령으로 하면 큰 차이는 없을 것 같습니다.

MySQL 공식 문서에서도

alter user 'root'@'localhost' identified by '새비밀번호';

이런 형태를 권장 하는 것 같습니다.

유저 생성 및 비밀번호, 권한 부여는 [CentOS 7 minimal 설치] MySQL DB생성, User 생성 글을 참고 하시기 바랍니다.



고전적인 Spring MVC에서

Controller -> Service Interface -> Service Implement -> Dao (또는 Dao Interface -> Dao Implement) -> DB

이런식으로 구현한 소스가 흔했었는데요, 살펴보면 대부분의 경우가 Service Interface 하나에 구현체도 하나, Dao도 그렇게 일대일로 개발해 놓은 경우가 많았습니다.

OOP Polymorphism 사상에서 본다면 Service 구현체가 여러가지 케이스가 있을 수 있기 때문에 그렇게 개발하는 것이라고 생각했지만, 그동안 여러곳의 프로잭트를 경험하면서 구현체가 여러개 였던 적이 한번도 없습니다. 대부분 그때 그때 조건에 따라 if~else if~ else 이러식으로 필요한 부분에서 분기를 해서 로직을 처리 하는 경우가 대부분 이었습니다.

단순한 웹페이지의 경우 하나의 화면을 만들기 위해서

  • Controller 메서드 한개
  • Service 인터페이스 메소드 원형 한개
  • Service 구현체에 매소드 한개, 내용은 Dao 메소드 호출 후 리턴하는 코드 한줄
  • Dao 인터페이스 메소드 원형 한개
  • Dao 구현체에 매소드 한개, 내용은 쿼리맵을 호출해서 결과를 리턴하는 코드 한줄
  • 결과를 보여주는 페이지

거의 모든 페이지가 이렇게 개발되어 있는 홈페이지 소스를 본적이 있는데... 유지보수를 하면서 조회 쿼리에 파라메터를 String에서 콘렉션으로 바꾸려면 콘트롤러에 Service를 호출 하는 부분부터 줄줄이 수정 해야 했습니다.

너무 비합리적인 구조인데 왜 이렇게 만들었을까... 해묵은 논잭이긴 하지만 저랑 비슷한 생각을 하시는 개발자들이 많았었는지 요즘은 서비스나 DAO에 Interface를 만들지 않고 바로 클래스를 호출하는 방식으로 간편하게 많이들 하시는 것 같습니다.

어디서 본 소스에는 Controller에 비즈니스 로직을 모두 구현하고 Controller에서 바로 DAO를 호출 하는 경우도 본적이 있는데... 스프링 MVC 사상에는 어긋나긴 하지만... 예외처리와 Data Rollback만 잘 되게 해놓았다면 문제 없다는 생각입니다.

그런데 조금 아쉬운게... 서비스에서 상황별로 여러가지 분기를 해야 하는 경우 if ~ else if ~ else 이렇게 구현하다 보니 소스도 엄청 복잡해지고 OOP 답지 않게 후져 보이고... 로직이 많이 복잡한경우 if ~ else fi 블럭사이에 수백줄의 코드가 들어가야 하는 경우도 있습니다.

얼마전 그런일이 있어서 이것을 펙토리 패텬으로 바꿔 볼까 해서 시작했는데...

상품을 관리하는 프로그램인데 상품이 딱 정해진게 아니고 계속 늘어나는 상황입니다. 편의상 A상품, B상품, C상품... E상품 이렇게 향후 계속 상품이 늘어나는 상황인데 A상품과 B상품은 로직의 유사도가 높아서 하나의 ProductService 라는 클래스에 담았는데 C상품은 기본적인 내용외에 처리해야 하는 로직이 상당히 다른 형태였습니다. 앞서 말한대로 if ~ else if ~ else 형태로 가기에는 소스가 너무 지저분 해지는 상황이 되었습니다.

ProductController.java

@Autowired 
ProductService productService; 

@RequestMapping("/productInfo") 
@ResponseBody 
public ResponseEntity<Map<String,Object> productInfo ( 
             @RequestParam(value="prodCd", required=true) String prodCd) { 

    return new ResponseEntity(productService.getProductInfo(prodCd), HttpStatus.OK); 
} 

 

ProductService.java

public Map<String,Object> getProductInfo(String prodCd) { 

    Map<String,Object> productInfo = null; 

    // 공통 처리 로직 

    switch (prodCd) { 
        case "A" : 
            // A상품 로직 
            break; 
        case "B" : 
            // B상품 로직 
            break; 
        case "C" : 
            // C상품 로직 
            break; 
         case ... 
    } 

    return productInfo; 
} 

대충 이런 상황이었습니다.

상품별로 처리해야 하는 로직이 늘어나거나, 상품에 대한 공통 로직과 상품개별 조직을 번갈아 가면서 수행해야 하는 경우 중간 중간에 계속 분기문이 나타나서 엄청 지저분하고 가족성이 떨어지는 코드가 되어 버렸습니다.

결국 상품 서비스 인터페이스를 만들고 상품별로 각기 다른 서비스를 구현체로 분리하고 간단하게 Factory Pattern을 적용해서 콘트롤러에서 상품 서비스 인터페이스를 이용하여 호출 하는 방식으로 구현하기로 생각했습니다.

ProductService.java (상품 서비스 인터페이스)

public interface ProductService { 
    public Map<String,Object> getProductInfo(String prodCd); 
} 

 

AProductServiceImpl.java (A상품 서비스 구현체)

@Autowired 
ProductDao productDao; 

public class AProductServiceImpl implements ProductService { 
    @Override 
    public Map<String,Object> getProductInfo(String prodCd) { 

        // 로직 처리 

        Map<String,Object> productInfo = productDao.getProductInfo(prodCd); 

        // 로직 처리 

        return productInfo; 
    } 
} 

 

그리고 이 서비스들을 상품코드에 따라 골라쓸 ProductServiceFactory.java

public ProcuctService getService(String prodCd) { 
    switch (prodCd) { 
        case "A" : return new AProductService(); 
        case "B" : return new BProductService(); 
        case "C" : return new CProductService(); 
    } 
} 

 

콘트롤러의 내용입니다.

ProductService productService; 

@RequestMapping("/productInfo") 
@ResponseBody 
public ResponseEntity<Map<String,Object> productInfo ( 
             @RequestParam(value="prodCd", required=true) String prodCd) { 

    return new ResponseEntity(ServiceFactory.getService(prodCd).getProductInfo(prodCd), HttpStatus.OK); 
} 

대략 이런 식으로 구현했던 것 같은데. 기억을 더듬어 급하게 만든거라 생략된 부분도 많고 실제 동작하는 소스는 아닙니다.

그런데 별로 마음에 안드는게... 상품이 신규로 늘어날 때 마다 상품별 Service Class만 새로 만들어 주면 되는게 아니고, 몇줄 안되기 하지만 ProductServiceFactory.java에 코드를 몇출 추가해햐 합니다. 좋은 방법이 없을까 고민하다가 런타임에 클래스를 동적으로 로딩할 수 있는 Class.forName(...) 이 생각났습니다.

Class.forName(...)은 낮설지가 않은게 Java를 처음 배울 때 JDBC 연결을 처음 구현하면서 대부분의 개발자들이 사용해 보는 방식입니다.

Class.forName("com.mysql.jdbc.Dirver");

이런 코드 생각나시죠?


Service Factory를 바꿨습니다.

public ProcuctService getService(String prodCd) { 
    return Class.forName("mypackage.service."+prodCd+"ProductService"); 
} 

정확한 코드는 기억이 안나고 이렇게 비슷하게... 했던 것 같네요.

그리고 실행을 해보니 아뿔싸... ProductService에 Autowired로 선언되어 있는 prodDao가 null 입니다.

그렇습니다 스프링의 Autowired 어노테이션은 스프링이 처음 실행되면서 콤포넌트 스켄에 지정된 위치의 콤포넌트 들을 메모리에 로드하면서 이 어노페이션을 만나면 해당 프로포티를 자동으로 초기화 시켜주는 역활을 합니다.

디버그 모드로 톰켓을 올리면 우리가 만든 Controller, Service, Dao 들이 메모리에 올라가는 것이 콘솔창에 보입니다.

제가 사용하고자 하는 방식은 스프링 런타임 중에 사용자의 선택에 따라 실시간으로 클래스를 로딩하는 방식을 생각했기 때분에 내부에 Autowired 어노테이션은 작동하지 않은 것입니다.

Autowired 어노테이션을 사용하지 않고 productDao를 new 로 생성해서 사용하는 방법도 있었지만 웬지 후져 보이고 이런 생각을 나만 한것이 아닐 것 이라 생각하고 여러가지 키워드로 구글링을 해보았습니다.

생각보다 원하는 방식의 예제를 찾기가 매우 어려웠습니다. 혹시 스프링에서 다른 더 좋은 깔끔한 방식이 있는 것인지? 아니면 동적으로 클래스를 로드하는 것에 대한 성능이슈가 있는 것인지는 모르겠지만, 상당히 오랜 기간 이런 저런 키워드로 구글링을 한 기억이 납니다. 역시나 Stackoverflow 에서 원하는 답을 찾았습니다.

org.springframework.beans.factory.config.AutowireCapableBeanFactory

이런 놈이 있었네요, 이놈을 가지고 이렇게 만들어 봤습니다.

ProductServiceFactory.java 에 넣으려고 하다가... 클래스의 Full 패키지 네임을 넣으면 해당 빈을 리턴하는 공통용으로 만들어 보자 싶어서 

DynamicAutowiredImplementsService.java

@Service 
public class DynamicAutowiredImplementsService { 

    @Autowired 
    private AutowireCapableBeanFactory beanFactory; 
     
    public Object getBean(String beanFullName) { 
        Object bean = null; 
         
        try { 

            Class<?> beanClass = Class.forName(beanFullName); 
             
            bean = beanFactory.createBean(beanClass, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false); 
            beanFactory.initializeBean(bean, beanFullName); 
             
        } catch (Exception e) { 
            throw new IllegalArgumentException("Couldn't instantiate class " + beanFullName, e); 
        } 
        return bean; 
    } 
}

 

그리코 콘트롤러에서 

@Autowired 
DynamicAutowiredImplementsService dynamicAutowiredImplementsService;

//ProductService productService; 

@RequestMapping("/productInfo") 
@ResponseBody 
public ResponseEntity<Map<String,Object> productInfo ( 
             @RequestParam(value="prodCd", required=true) String prodCd) { 

    ProductService productService = dynamicAutowiredImplementsService("mypackage.service."+prdtCd+"ProductService");
    return new ResponseEntity(productService.getProductInfo(prodCd), HttpStatus.OK); 
} 

이렇게 했습니다.

결과는? 잘~ 동작합니다.

추가 (커뮤니티에 올리니까 바로 문제점을 지적해 주시는 고수 분들이 계시네요, 콘트롤러에서 ProductService 를 전역변수로 사용하면서 경합이 발생할 경우 문제가 될 수 있습니다. 이현재 ProductService 는 매소드 내에 지역변수로 이동 했고 DynamicAutowiredImplementsService 역시 경합이 발생할 경우 문제가 될 수 있습니다. 이것은 어떻게 할 지 고민을 좀 해야봐 할 것 같습니다.)

부하 테스트까지 해보지 않아서... 사실 Autowired로 주입한 것과 어떤 차이가 있는지 검증은 못해 봤습니다. 그리고 스프링에서 Polymophism을 구현하는 방식으로 이렇게 하는 것이 바람직 한 것인지는 짧은 식견으로 좀 더 연구를 해봐야 겠지만... 만들고 있는 어플리케이션이 동시접속자가 많다기 보다는 로직이 매우 복잡하고 다양한 케이스 였기 때문에 상품이 추가 될 때 Service쪽만 새롭게 만들어 주면 되어서 유지보수성이 많이 향상된 것 같습니다.

보시는 분 중에 혹시라도 문제점이나 잘못된 곳이 있다면 지적 부탁드립니다.

읽어주셔서 감사합니다.

서블릿 개발하기 연재목록

#1 개발환경 구축 (https://opensrc.tistory.com/180?category=475522)

#2 프로잭트 생성 (https://opensrc.tistory.com/181?category=475522)

#3 첫번째 JSP 파일 만들기 (https://opensrc.tistory.com/182?category=475522)

#4 첫번째 서블릿 만들기 (https://opensrc.tistory.com/183?category=475522)

#5 포스트 요청을 처리하는 서블릿 만들기 (https://opensrc.tistory.com/203?category=475522)

#6 GET/POST 요청과 함께 파라메터 전달 하기 (https://opensrc.tistory.com/204?category=475522)

#7 Servlet 들여다 보기 (https://opensrc.tistory.com/206?category=475522)

===========================================================

 

이 포스트를 처음 시작한 이유는 요즘 Java Web Application 개발을 가르치는 서적이나 교육기관들을 보면 Java Web Application 의 기본인 Servlet 을 가르치지 않고 처음부터 Spring 같은 잘 만들어진 프레임워크를 이용하여 시작합니다.

실제 현장에서 개발을 할 때도 프레임워크를 사용하지 않고 HttpServlet 을 상속받아서 사용하는 일은 거의 없습니다. 물론 그래도 상관 없을 만큼 국내 Java 기반의 프로잭트의 대부분은 Spring 기반으로 되어 있습니다. 심지어 공공에서 많이 쓰이는 전자정부 프레임워크 조차 Spring기반입니다.

사실 기본적인 Servlet 형태를 모르고 Spring만 잘 해도 개발 하는데 문제가 없겠지만 가끔 Spring Application 은 어떻게 동작 할까... 하는 생각이 들 때가 있을 것 같아서 기본적인 HttpServlet 을 확장해서 Spring MVC Application 과 비슷한 형태로 만들어 보는 것이 목적입니다. 물론 Spring 처럼 수많은 사람들에 의해서 만들어 지고 검증된 프레임워크 처럼 편하고 뛰어난 기능을 모두 갖추기는 어렵지만 Spring은 물론 Java MVC Web Application이 어떻게 동작하는지 이해하는데 도움이 되었으면 좋겠다는 생각에 시작했습니다.

사설이 길었습니다.

 

[서블릿 개발하기] #7 Servlet 들여다 보기

이전 시간까지 우리가 만든 HelloWorldServlet은 javax.servlet.http.HttpServlet을 상속받았습니다.

HttpServlet 의 소스코드는

https://github.com/javaee/servlet-spec/blob/master/src/main/java/javax/servlet/http/HttpServlet.java

 

javaee/servlet-spec

The API and Issue Tracker for the JCP Standard Java Servlet Specification - javaee/servlet-spec

github.com

이곳에서 보실 수 있습니다.

 

HttpServlet 클래스 선언부에 보면 javax.servlet.GenericServlet 을 상속 받은것을 알 수 있습니다.  GenericServlet 의 소스는 이곳에서 살펴 보실 수 있습니다.

https://github.com/javaee/servlet-spec/blob/master/src/main/java/javax/servlet/GenericServlet.java

 

javaee/servlet-spec

The API and Issue Tracker for the JCP Standard Java Servlet Specification - javaee/servlet-spec

github.com

GenericServlet 은 javax.servlet.Servlet, javax.servlet.ServletConfig, java.io.Serializable 의 구현체 이네요 관심있으신 분은 소스를 더 살펴보셔도 좋습니다. 이 포스트에서는 HttpServlet까지만 살펴보겠습니다.

 

 

우리가 만든 HelloWorldServlet 의 GET Request와 POST 요청을 처리하는 goGet(), doPost() 메소드는 상속받은 HttpServlet 의 doGet과 doPost를 각각 재정의(Override)한 것입니다. HttpServlet.java 소스에 각 메소드의 원형을 살펴보면

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException

이렇게 생겼습니다.

사용자로부터 들어오는 요청에 대한 정보는 HttpServletRequest  req 에 담아서 전달 됩니다.

사용자에게 응답해야 하는 정보는 HttpServletResponse resp 에 담아주면 됩니다.

우리가 만든 doGet, doPoat는 이것을 재정의 한 것 입니다. 꼭 있어야 하는 것은 아니지만 우리가 만든 HelloWordServlet.java가 컴파일 될 때 부모 클래스의 기능을 Override 한 것이라고 표시를 해주면 좋을 것 같습니다.

	@Override 
	public void doGet(HttpServletRequest request, HttpServletResponse response)
    	throws ServletException, IOException {

		...

	}
    
 	@Override 
	public void doPost(HttpServletRequest request, HttpServletResponse response)
    	throws ServletException, IOException {

		...

	}  

이렇게 어노테이션을 붙혀주면 더 보기좋네요.

다시 위에 HttpServlet.java 소스를 살펴보면 GET과 POST 외에도 PUT, DELETE 같은 요청을 처리하는 doPut, doDelete 같은 메소드도 있습니다. 전에 말씀드린 대로 POST 요청에 대해 잘 이해를 하신다면 PUT과 DELETE를 사용하시는 데도 문제가 없을 것 같습니다.

다른 것들은 건너 뛰고 좀 더 살펴보면 service 라는 메소드가 보입니다.

    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        ...
    }

이렇게 생겼네요 설명을 보면 

Receives standard HTTP requests from the public service method and dispatches them to the doXXX methods defined in this class...

아마도 doGet, doPost 같은 요청을 이 매소드가 대신 처리 해준다는 소리 같습니다.

소스를 조금 살펴 보면

    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
        ... 생략...
        	doGet(req, resp);
        ... 생략...
        } else if (method.equals(METHOD_HEAD)) {
        ... 생략...
            doHead(req, resp);
        ... 생략...
        } else if (method.equals(METHOD_POST)) {
        ... 생략...
            doPost(req, resp);
        ... 생략...
        } else if (method.equals(METHOD_PUT)) {
        ... 생략...
            doPut(req, resp);
        ... 생략...
        } else if (method.equals(METHOD_DELETE)) {
        ... 생략...
            doDelete(req, resp);
        ... 생략...
        } else if (method.equals(METHOD_OPTIONS)) {
        ... 생략...
            doOptions(req,resp);
        } else if (method.equals(METHOD_TRACE)) {
        ... 생략...
            doTrace(req,resp);
        ... 생략...
        } else {
        ... 생략...
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

request 의 메소드를 확인해서 각각을 처리 하는게 보입니다.

정말 그렇게 되는가 우리가 만든 HelloWorldServlet 에 적용해 보도록 하겠습니다.

service 메소드가 작동 하려면 기존에 있던 doGet, doPost 는 삭제 해야 합니다.

package com.tistory.opensrc.basicservlet.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 첫번째 hello, world 서블릿
 * @author cloud
 *
 */
public class HelloWorldServlet extends HttpServlet {

	/**
	 * serial version id
	 */
	private static final long serialVersionUID = 701289870660846795L;

	/**
	 * 사용자 요청을 처리
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	@Override
	public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html"); // text html 형태로 출력 하겠다고 지정함
		PrintWriter out = response.getWriter(); // response로 부터 출력 장치를 확보
		
		String yourName = request.getParameter("your_name"); // request에서 화면으로 부터 넘어온 파라메터의 값을 추출 합니다. 
		
		// html 내용을 출력
		out.println("<html>");
		out.println("hello, world<br/>");
		out.println("method : " + request.getMethod() + "<br/>");
		out.println("You'r name is " + yourName);
		out.println("</html>");
	}
}

이렇게 바꿔봤습니다.

GET Request

POST Request

네 잘 동작 합니다.

특별히 GET, POST 중 어느 한가지만 처리 하려는 목적이 아니라면 service 를 사용하면 둘다 하나의 로직으로 처리 할 수 있어서 로직을 추가 하기 용이하고 혹시 GET이나 POST 같은 특정 요청만 처리 하겠다고 한다면 위의 소스에서 처럼 request.getMethod()를 받아서 분기 처리 해주시면 될 것 같습니다.

다음 시간에는 Java Web Application에서 한글 처리에 대해 이야기 해보도록 하겠습니다.

서블릿 개발하기 연재목록

서블릿 개발하기 연재목록

#1 개발환경 구축 (https://opensrc.tistory.com/180?category=475522)

#2 프로잭트 생성 (https://opensrc.tistory.com/181?category=475522)

#3 첫번째 JSP 파일 만들기 (https://opensrc.tistory.com/182?category=475522)

#4 첫번째 서블릿 만들기 (https://opensrc.tistory.com/183?category=475522)

#5 포스트 요청을 처리하는 서블릿 만들기 (https://opensrc.tistory.com/203?category=475522)

#6 GET/POST 요청과 함께 파라메터 전달 하기 (https://opensrc.tistory.com/204?category=475522)

#7 Servlet 들여다 보기 (https://opensrc.tistory.com/206?category=475522)

===========================================================

 

 

[서블릿 개발하기] #6 GET/POST 요청과 함께 파라메터 전달 하기

우리가 사용하는 웹 어플리케이션은 데이터 입력 없이 자체 만으로 컨텐츠를 표시하는 경우도 있지만 대부분 사용자로 부터 데이터를 입력 받아서 처리를 하고 결과를 보여주는 경우가 대부분입니다.

예를 들어 이 블로그의 특정 게시물을 읽게 되면 https://opensrc.tistory.com/203?category=475522 이렇게 생긴 도메인 주소와 URI, 파라메텨들이 서버로 전송(Request)이 되고 서버는 그것을 받아서 URI 부분을 파싱하고 URI에 해당하는 어플리케이션에 파라메터를 전달하고 어플리케이션은 그 파라메터에 맞는 데이터를 가공하여 다시 브라우저로 응답(Response)되는 구조입니다.

이제 우리가 만들었던 어플리케이션에 사용자가 입력한 값을 받아서 처리 하는 것을 구현해 보도록 하겠습니다.

index.jsp 에 다음 내용을 추가 합니다.

What is your name ? <input type="text" name="my_name"/><br/>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
hello, world</br>
<form action="/hello" method="POST">
	What is your name ? <input type="text" name="your_name"/><br/>
	<input type="submit" value="Submit"/>
</form>
</body>
</html>

http://localhost 로 접속해 보면 아래와 같은 화면이 나옵니다.

우리가 추가한 입력창 의 이름을 your_name 으로 지정 한것을 기억하고 서버에서 저 값을 받아서 화면에 보여주도록 해야 합니다.

POST Request  파라메터 값 추출

우리가 만든 화면은 아직 POST로 호출 하므로 HelloWorldServlet의 doPost 메서드를 먼저 수정해 보겠습니다. 아래 소스를 잘 보시고 두줄을 추가 합니다.

String yourName = request.getParameter("your_name"); // request에서 화면으로 부터 넘어온 파라메터의 값을 추출 합니다. 

out.println("You'r name is " + yourName);

package com.tistory.opensrc.basicservlet.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 첫번째 hello, world 서블릿
 * @author cloud
 *
 */
public class HelloWorldServlet extends HttpServlet {

	/**
	 * serial version id
	 */
	private static final long serialVersionUID = 701289870660846795L;

	/**
	 * GET 요청을 처리
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html"); // text html 형태로 출력 하겠다고 지정함
		PrintWriter out = response.getWriter(); // response로 부터 출력 장치를 확보
		
		// html 내용을 출력
		out.println("<html>");
		out.println("hello, world<br/>");
		out.println("method : " + request.getMethod());
		out.println("</html>");
	}
	

	/**
	 * POST 요청을 처리
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html"); // text html 형태로 출력 하겠다고 지정함
		PrintWriter out = response.getWriter(); // response로 부터 출력 장치를 확보
		
		String yourName = request.getParameter("your_name"); // request에서 화면으로 부터 넘어온 파라메터의 값을 추출 합니다. 
		
		// html 내용을 출력
		out.println("<html>");
		out.println("hello, world<br/>");
		out.println("method : " + request.getMethod() + "<br/>");
		out.println("You'r name is " + yourName);
		out.println("</html>");
	}
}

이제 화면에서 이름을 입력해 보겠습니다.

아래와 같이 나왔다면 성공입니다.

주의깊게 살펴 보셔야 할 것은 브라우저 주소창에 localhost/hello 만 표시된다는 것 입니다.

이제 doGet 메서드에도 같은 코드를 추가해 보겠습니다.

package com.tistory.opensrc.basicservlet.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 첫번째 hello, world 서블릿
 * @author cloud
 *
 */
public class HelloWorldServlet extends HttpServlet {

	/**
	 * serial version id
	 */
	private static final long serialVersionUID = 701289870660846795L;

	/**
	 * GET 요청을 처리
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html"); // text html 형태로 출력 하겠다고 지정함
		PrintWriter out = response.getWriter(); // response로 부터 출력 장치를 확보

		String yourName = request.getParameter("your_name"); // request에서 화면으로 부터 넘어온 파라메터의 값을 추출 합니다. 
		
		// html 내용을 출력
		out.println("<html>");
		out.println("hello, world<br/>");
		out.println("method : " + request.getMethod());
		out.println("You'r name is " + yourName);
		out.println("</html>");
	}
	

	/**
	 * POST 요청을 처리
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html"); // text html 형태로 출력 하겠다고 지정함
		PrintWriter out = response.getWriter(); // response로 부터 출력 장치를 확보
		
		String yourName = request.getParameter("your_name"); // request에서 화면으로 부터 넘어온 파라메터의 값을 추출 합니다. 
		
		// html 내용을 출력
		out.println("<html>");
		out.println("hello, world<br/>");
		out.println("method : " + request.getMethod() + "<br/>");
		out.println("You'r name is " + yourName);
		out.println("</html>");
	}
}

 

그리고 사용자 요청을 GET으로 변경합니다.

index.jsp 에 <form action="/hello" method="POST"> 부분을 GET으로 변경합니다.

그리고 주소창에 http://localhost/ 를 다시 입력하고 이름을 입력하고 Submit 을 클릭해 봅니다.

POST 요청에서는 주소창에 localhost/hello 만 표시되었던 반면, GET 요청에서는 localhost/hello?your_name=CloudNine 라고 ? 뒷 부분에 파라메터 명과 값이 표시되어 있습니다. F5 키를 눌러서 새로고침 하거나 다른 브라우저 창을 열고 복사해서 붙혀 넣어도 똑같은 화면이 출력 됩니다.

그럼 다시 index.jsp의 <form ... method="GET"/> 부분을 POST 로 바꾸고 http://localhost/ 를 호출해서 값을 전송해 보겠습니다. 그리고 GET요청 화면에서 했던 것 처럼 F5 키를 눌러보면

이런 화면을 만나게 됩니다 '계속' 버튼을 누르면 이전 요청에 값들을 이용해서 다시 처리 하겠지만. GET요청 처럼 바로 실행되지는 않습니다. 그리고 주소창의 내용을 복사해서 다른 브라우저 창에 붙혀 넣으면

GET 요청에 You;'s name is null 로 표시됩니다.

브라우저 주소창에 주소를 직접 입력하는 것은 GET 요청으로 간주 된다는 것을 기억하시기 바랍니다.

 

 

여기까지 간단한 서블릿 개발에 대해 알아봤습니다. 지금 까지 한 것을 조금 복잡하게 이야기 하면 JSP Model 1 방식의 Java Application 이라고 할 수 있습니다.

 

6번의 연재에서 기초적인 것은 제외 하고 

  • JSP는 수정후 최초 실행시 Servlet 형태로 변환된후 컴파일 되어 (Tomcat 같은)Application Server에 적재 되어 실행 된다는 점.
  • 모든 Servlet 은 javax.servlet.http.HttpServlet 을 상속 받는다는 점
  • GET, POST 요청의 차이점과 그 목적이 GET은 읽기 요청에 사용되고 POST는 쓰기/수정/삭제 등의 특성에 맞게 고안되어 있다는 것

이정도 이해 하시고 기억하시면 될 것 같습니다.

여기까지 서블릿 개발하기 기초 편을 마무리하고

차후에 시간이 되는 대로 JSP Model 1 에서 확장해서 JSP Model 2 Java Web Application, 우리가 흔히 말하는 Java MVC 방식에 대해 알아보도록 하겠습니다.

 

감사합니다.

서블릿 개발하기 연재목록

#1 개발환경 구축 (https://opensrc.tistory.com/180?category=475522)

#2 프로잭트 생성 (https://opensrc.tistory.com/181?category=475522)

#3 첫번째 JSP 파일 만들기 (https://opensrc.tistory.com/182?category=475522)

#4 첫번째 서블릿 만들기 (https://opensrc.tistory.com/183?category=475522)

#5 포스트 요청을 처리하는 서블릿 만들기 (https://opensrc.tistory.com/203?category=475522)

#6 GET/POST 요청과 함께 파라메터 전달 하기 (https://opensrc.tistory.com/204?category=475522)

#7 Servlet 들여다 보기 (https://opensrc.tistory.com/206?category=475522)

===========================================================

 

너무 오랫만에 포스팅을 하게 되었습니다.

스프링 같은 Java Web Application개발용 프레임워크를 이용하지 않고 옛날 방식의 Servlet 개발 방식을 살펴보고 있습니다. 물론 요즘에는 거의 사용되고 있지 않지만 스트러츠나 스프링 같은 고도화된 프래임워크의 작동 방식을 이해하는데 꼭 필요하다는 생각에서 스프링 개발을 진행하기 전에 또는 스프링 기반의 개발 방식에 익숙한 분들께 도움이 될까... 하는 생각에 시작한 연재입니다.

 

HTTP Method 이해

일반적으로 우리가 브라우저 창에 주소를 치거나 웹페이지에서 링크를 클릭해서 화면을 표시하는 방식을 GET 방식이라고 합니다.

다른 페이지에서 우리가 만든 페이지를 찾아오기 쉽게 주소를 기술해 놓은 것을 브라우저 주소창에 붙혀넣거나 클릭하는 것 만으로 해당페이지에 접근 할 수 있는 방식입니다.

이미 모든 분들이 누군가에게 어느 사이트의 주소를 알려줄 때 많이 사용해 보았을 것입니다.

HTTP 프로토콜에는 GET 외에도 POST, PUT, DELETE, HEAD, OPEIONS... 등등 몇가지 방식이 있습니다. 실질적으로 어느 방식을 쓰든 클라이언트(브라주저)에서 서버쪽으로 요청을 보내는 것은 동일하지만 전달하는 방식은 약간 차이가 있습니다.

그 차이는 차차 알아가시면 되고 여기서 설명 할 것은 GET과 POST의 차이만을 설명하도록 하겠습니다.

우선 GET방식의 요청은 브라우저 주소창에 해당 컨텐츠의 주소와 파라메터 까지 그대로 기술 됩니다. 그 주소 만으로 해당 컨텐츠가 온전하게 표시 될 수 있어야 합니다.

반면 POST 방식은 해당 컨텐츠의 주소와 URI 까지만 표시를 하고 파라메터와 그 값은 주소창에 표시 되지 않습니다.

이런 특성을 잘 이용해서 개발을 한다면 GET방식은 읽기에 유용하고 POST는 서버로 데이터를 전송해서 저장, 삭제 하는 기능에 유용합니다.

만일 어느 게시판의 글을 읽는 기능을  GET 방식으로 http://somesite.co.kr/read?artecleId?=10  와 같이 구현 했다면 이것만 복사 해서 카카오톡에도 붙혀넣고 블로그에도 붙혀넣고 누구든 (글을 읽을 수 있는 권한이 있는 경우) 저 주소만 클릭하면 글을 볼수가 있습니다. 

만일 삭제하는 기능도 역시 GET방식으로 http://somesite.co.kr/delete?artecleId?=10 이렇게 구현 했다면 저 주소를 브라우저 창에 입력해서 실행 하는 것 만으로 글 삭제를 시도 할 것입니다.(물론 삭제 할 수 있는 권한이 있는 경우에 그렇게 되겠죠)

그래서 주로 GET은 읽기 엑션에 적당하고 POST, PUT 같은것은 저장/수정, DELETE는 삭제 액션에 적당한 방식입니다.

앞서도 말했지만 여기서는 GET과 POST만을 다루겠습니다. POST를 이해하시면 PUT, DELETE를 사용하시는데 문제가 없습니다.

 

Request Method 확인 하기

이전 포스팅에서 우리가 만들었던 hello world 서블릿은 사용자로부터 GET 방식의 요청을 처리하는 매서드만 가지고 있습니다. 이제 사용자 요청이 POST로 들어오는 것을 처리할 매서드를 추가해 보도록 하겠습니다.

우선 우리가 만들었던 HelloWorldServlet.java 의 doGet(...) 메서드가 정말 GET방식의 요청인지 확인해보도록 하겠습니다.

HelloWorldServlet.java

package com.tistory.opensrc.basicservlet.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 첫번째 hello, world 서블릿
 * @author cloud
 */
public class HelloWorldServlet extends HttpServlet {
	
	/**
	 * serial version id
	 */
	private static final long serialVersionUID = 701289870660846795L;

	/**
	 * GET 요청을 처리
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		response.setContentType("text/html"); // text html 형태로 출력 하겠다고 지정함
		PrintWriter out = response.getWriter(); // response로 부터 출력 장치를 확보
		
		// html 내용을 출력
		out.println("<html>");
		out.println("hello, world");
		out.println("method : " + request.getMethod()); // 요청 메서드 확인
		out.println("</html>");
	}
}

out.println("out.println("method : " + request.getMethod()); // 요청 매서드 확인

코드를 한줄 추가 합니다.

HttpServletRequest request 의 getMethod() 는 Request 헤더를 파싱해서 사용자 요청의 Method를 확인합니다.

이제 서블릿을 호출 해 봅니다.

위와 같은 결과가 나왔으면 정상적으로 처리 된 것입니다.

위와 같이 나오지 않는다면 [서블릿 개발하기] #1 개발환경 구축 편의 4. 서버 실행 파트를 참고 해서 서버를 실행 하셔야 합니다.

POST 로 요청(Request) 하기

이제 우리가 만든 서블릿을 POST로 호출 하도록 준비를 해보겠습니다. POST로 호출하기 위해서는 간단한 페이지를 만들어야 합니다. [서블릿 개발하기] #3 첫번째 JSP 파일 만들기에서 처음 만들었던 hello, world 를 출력하는 index.jsp 파일을 수정해서 만들어 보겠습니다.

<!@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
hello, world</br>
<form action="/hello" method="POST">
	<input type="submit" value="Submit"/>
</form>
</body>
</html>

우리가 출력했던 hello, world 아래에 아무것도 없는 폼 엘리먼트를 하나 추가 하고 액션은 /hello 매서드는 POST로 지정했습니다. Submit 버튼을 클릭하면 우리가 만든 HelloWorldServlet POST Request를 호출 하겠다는 이야기 입니다.

action="/hello" 가 어떻게 HelloWorkdServlet 과 연결이 되는지 이해가 안가시는 분은 [서블릿 개발하기] #4 첫번째 서블릿 만들기 를 다시 한번 보시기 바랍니다.

 

실행을 하면 이런 결과가 나옵니다.

Submit 버튼을 클릭해 봅니다.

우리가 만든 HelloWorldServlet에는 GET 요청만을 처리하는 doGet() 메서드만 추가되어 있기 때문에 아래와 같이 

HTTP Status 405 - HTTP method POST is not supported by this URL

에러가 나오고 있습니다.

이제 HelloWorldServlet에 POST Method Request 에 대응하는 메서드를 추가하도록 하겠습니다.

아래 내용처럼 HelloWorldServlet.java 에 doGet 메서드를 복사해서 doPost 메서드를 추가 합니다.

package com.tistory.opensrc.basicservlet.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 첫번째 hello, world 서블릿
 * @author cloud
 *
 */
public class HelloWorldServlet extends HttpServlet {

	/**
	 * serial version id
	 */
	private static final long serialVersionUID = 701289870660846795L;

	/**
	 * GET 요청을 처리
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html"); // text html 형태로 출력 하겠다고 지정함
		PrintWriter out = response.getWriter(); // response로 부터 출력 장치를 확보
		
		// html 내용을 출력
		out.println("<html>");
		out.println("hello, world<br/>");
		out.println("method : " + request.getMethod());
		out.println("</html>");
	}
	

	/**
	 * POST 요청을 처리
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html"); // text html 형태로 출력 하겠다고 지정함
		PrintWriter out = response.getWriter(); // response로 부터 출력 장치를 확보
		
		// html 내용을 출력
		out.println("<html>");
		out.println("hello, world<br/>");
		out.println("method : " + request.getMethod());
		out.println("</html>");
	}
}

 

이제 톰켓 서버를 재시작 하고

앞의 http://localhost 화면에서 Submit 버튼을 클릭해 봅니다.

정상적으로 POST 요청에 대한 응답이 성공하였습니다.

다음 편에서는 GET/POST 요청에 변수(Parameter)를 전송하는 방법을 알아보겠습니다.

 

사소한 그룹웨어를 봐주고 있는 회사가 있는데 언젠가 접속을 해볼 수 없는 상황에서 서버가 다운되었다는 연락을 받았다.

강제로 전원을 내렸다 올려도 수동으로 톰켓을 실행 시켜주어야 한다.

언제나 서버가 재부팅이 필요 할 때에는 엔지니어가 콘솔에 붙어 있는 상황에서만 가능하다는 나의 고집 때문인 듯...

다행이 업무 시간이 지난 때여서 양해를 구하고 저녁에 집에 가서 접속해서 확인해 주고 서버를 살려 놓은 적이 있는데

여전히 불안하다...

한번 시간 내서 작업을 해봤는데 CentOS 7 로 오면서 자잘한 것들이 많이 바뀐 듯... 예전에 

예전에 하던 service 등록 하는 방식으로 잘 되지 않는다.

구글링을 좀 해보니 systemctl 등록 하는 방법이 나와서 작업하고 내용을 정리해본다.


0. 사전 준비
당연히 톰켓이 설치되어 있고 정상적으로 동작해야 한다.
나의 경우 /usr/local/tomcat7 경로에 설치를 했다.
Tomcat 실행 유저는 webservice 계정 이다
평소에 톰켓을 가동 할 때는
  /usr/local/tomcat7/bin/startup.sh
종료할 때는
  /usr/local/tomcat7/bin/shutdown.sh
문제 없이 잘 동작 해야 한다.

위 상황에 문제가 없다면 아래 내용에서 유저나 경로는 자신의 환경에 맞게 바꿔주면 됩니다.


1. tomcat.service  파일 생성
모든 작업은 root 권한으로 진행한다.
/usr/lib/systemd/system 디렉토리에 아래와 같은 내용으로 tomcat.service 파일을 생성한다.

[Unit]
Description=Apache Tomcat 7
After=network.target syslog.target

[Service]
Type=forking
User=webservice
Group=webservice

ExecStart=/usr/local/tomcat7/bin/startup.sh
ExecStop=/usr/local/tomcat7/bin/shutdown.sh

[Install]
WantedBy=multi-user.target

2. 데몬 재로드

$ systemctl daemon-reload


3. 시작서비스 등록

systemctl enable /usr/lib/systemd/system/tomcat.service


4. 테스트

$ systemctl start tomcat

Job for tomcat.service failed because the control process exited with error code. See "systemctl status tomcat.service" and "journalctl -xe" for details.

$

에러가 나면서 잘 되지 않는다.

내용을 읽어 보니 "$systemctl status tomcat.service" 으로 자세한 내용을 확인 할 수 있는 듯 하다

$ systemctl status tomcat.service

● tomcat.service - Apache Ttomcat 7

   Loaded: loaded (/usr/lib/systemd/system/tomcat.service; enabled; vendor preset: disabled)

   Active: failed (Result: exit-code) since 목 2018-11-15 00:22:12 KST; 44s ago

  Process: 2017 ExecStart=/usr/local/tomcat7/bin/startup.sh (code=exited, status=1/FAILURE)


11월 15 00:22:12 localhost.localdomain systemd[1]: Starting Apache Ttomcat 7...

11월 15 00:22:12 localhost.localdomain startup.sh[2017]: touch: cannot touch `/usr/local/tomcat7/logs/catalina.out': 허가 거부

11월 15 00:22:12 localhost.localdomain systemd[1]: tomcat.service: control process exited, code=exited status=1

11월 15 00:22:12 localhost.localdomain systemd[1]: Failed to start Apache Ttomcat 7.

11월 15 00:22:12 localhost.localdomain systemd[1]: Unit tomcat.service entered failed state.

11월 15 00:22:12 localhost.localdomain systemd[1]: tomcat.service failed.

아까 테스트 하느라 logs 디렉토리를 비우고 실수로 tomcat을 root 권한으로 실행하면서 catalina.out 파일이 root 소유로 생성된 듯...

/usr/local/tomcat7 아래 모든 파일의 오너쉽을 webservice:webservice로 변경
$ chown webservice:webservice /usr/local/tomcat7 -R
$ ls -alR /usr/local/tomcat7

다시 실행하니 에러 없이 잘 된다.

4. 재부팅 후 서비스 확인
재부팅

$ sync

$ sync

$ sync

$ reboot

부팅이 완료 된 후 서비스가 정상으로 올라왔는지 확인

$ ps -ef | grep tomcat

webserv+  1327     1 31 00:34 ?        00:00:18 /usr/bin/java -Djava.util.logging.config.file=/usr/local/tomcat7/conf/logging.properties -..

.... 생략 ... org.apache.catalina.startup.Bootstrap start

webserv+  1600  1569  0 00:35 pts/0    00:00:00 grep --color=auto tomcat

$

webservice 계정으로 잘 실행 되어 있다.

브라우저로 접속 역시 문제 없음


끝~


중요

본 작업은 서버 콘솔에서 작업하기를 권합니다. 만약 원격에서 하는경우 매우 신중하게 해야 합니다. SSH설정을 드랍하고 새로운 제한을 추가 한것을 적용했다가 IP나 문법이 잘못 적용된 경우 원격에서 접속이 불가능한 상태가 되어 버릴 수 있습니다. 결국 야밤에 택시타고 IDC로 달려가는 귀찮은 경험을 하고 싶지 않으면 매우 신중하게 하고 작업 후 반드시 외부 접속을 테스트 해보기를 권합니다.


이전 포스트에서도 언급 한 적 있지만 CentOS 최근 버전부터 기존 방화벽인 iptables를 좀 더 쉽게(?) 사용 할 수 있도록 개선한 firewalld 가 제공되고 있습니다.

(firewalld 방화벽에 대한 소개는 https://sepiros.tistory.com/7 에 잘 되어 있습니다.)


설정 파일을 xml 형태로 확인 가능하고 그 xml 파일을 직접 편집해서 룰을 추가 가능하며 기존 iptables  처럼 명령라인으로도 조작 가능합니다.


방화벽이 불편하다고 제대로 설정하지 않거나 서비스를 내려놓고 서비스를 한다면 머지않아 외부에서 침투해서 불순한 목적의 프로세스가 실행되어 엄청난 트래픽이 발생하고, 급기야 IDC 관제실로 부터 랜포트를 제거 한다는 신박한 연락을 받는 경험을 할 수도 있을것 입니다. Traffic Shared 회선을 사용하는 경우 같은 대역폭을 사용하는 타 업체에게 엄청난 민폐를 끼치고 끝나지만, 종량제를 사용하는 경우 트래픽 사용료 폭탄을 맞을 수도 있습니다.

그리고 한번 외부에 털린(?) 서버는 백프로 다음 접근을 위해 백도어가 설치되었다고 보는게 맞을 것이며, 상대도 최대한 들키지 않도록 백도어를 꽁꽁 숨겨놓아서 그것을 찾아서 깨끗이 제거하는 것은 결코 쉬운일이 아닙니다. 결국 꼭 필요한 소스만 백업받고 서버를 재설치 하는 경우를 많이 보아왔습니다.

이런 문제는 실제 서비스를 운영하고 있는 서버라면 엄청난 리스크이자 시스템 엔지니어에게는 크나큰 스트래스입니다. 그러므로 꼭 필요한 포트와 접근 가능한 IP를 지정 하도록 하는 습관을 들이도록 권합니다.


방화벽 설정에 대한 기본 지식은

https://www.lesstif.com/pages/viewpage.action?pageId=22053128

이곳을 참고 하도록 하고 여기서는 우리서버의 외부 접근을 모두 차단하고

1. http (80)는 모든 곳에서 접근 할 수 있도록

2. ssh(21), mysql(3306)은 내부망(192.168.0.*)과 특정 IP(xxx,xxx.xxx.xxx)에서만 접근 가능

두가지를 설정해 보겠습니다.

[root@localhost ~]# firewall-cmd --get-default-zone

public

[root@localhost ~]#

현재 우리 서버의 기본 방화벽 존은 public 으로 되어 있습니다.

영구(permanent) 등록된 서비스를 확인해 보겠습니다.

[root@localhost ~]# firewall-cmd --permanent --list-all --zone=public

public

  target: default

  icmp-block-inversion: no

  interfaces:

  sources:

  services: dhcpv6-client ssh

  ports:

  protocols:

  masquerade: no

  forward-ports:

  source-ports:

  icmp-blocks:

  rich rules:


[root@localhost ~]#

기본적으로 IPV6 DHCP 클라이언트와 SSH 가 허용되어 있습니다.

이것은 아래와 같이 xml 파일로도 확인 가능합니다.

[root@localhost ~]# cat /etc/firewalld/zones/public.xml

<?xml version="1.0" encoding="utf-8"?>

<zone>

  <short>Public</short>

  <description>For use in public areas. You do not trust the other computers on networks to not harm your computer...</description>

  <service name="dhcpv6-client"/>

  <service name="ssh"/>

</zone>

[root@localhost ~]#

DHCP 클라이언트는 그냥 두고 어디서나 제한 없이 SSH 접속 할 수 있도록 되어있는 것을 내부망(192.168.0.*) 특정 IP xxx.xxx.xxx.xxx에서만 접근 가능 하도록 변경하고 역시 같은 곳에서 mysql(3306)으로 연결 할 수 있도록 설정 해보겠습니다.

그리고 끝으로 어디서나 제한 없이 http로 접근 할 수 있도록 설정을 추가 하도록 설정 해보겠습니다.

[root@localhost ~]# firewall-cmd --permanent --zone=public --remove-service=ssh

success

[root@localhost ~]# firewall-cmd --permanent --zone=public --add-rich-rule="rule family='ipv4'  source address='192.168.0.0/24'  service name='ssh' accept"

success

[root@localhost ~]# firewall-cmd --permanent --zone=public --add-rich-rule="rule family='ipv4'  source address='xxx.xxx.xxx.xxx'  service name='ssh' accept"

success

[root@localhost ~]# firewall-cmd --permanent --zone=public --add-rich-rule="rule family='ipv4'  source address='192.168.0.0/24'  port protocol='tcp' port='3306' accept"

success

[root@localhost ~]# firewall-cmd --permanent --zone=public --add-rich-rule="rule family='ipv4'  source address='xxx.xxx.xxx.xxx'  port protocol='tcp' port='3306' accept"

success

[root@localhost ~]# firewall-cmd --permanent --zone=public --remove-service=http

설정이 제대로 되었는디 확인해 보겠습니다.

[root@localhost ~]# cat /etc/firewalld/zones/public.xml

<?xml version="1.0" encoding="utf-8"?>

<zone>

  <short>Public</short>

  <description>For use in public areas. You do not trust the other computers on networks to not harm your computer...</description>

  <service name="dhcpv6-client"/>

  <service name="http"/>

  <rule family="ipv4">

    <source address="192.168.0.0/24"/>

    <service name="ssh"/>

    <accept/>

  </rule>

  <rule family="ipv4">

    <source address="192.168.0.0/24"/>

    <port protocol="tcp" port="3306"/>

    <accept/>

  </rule>

  <rule family="ipv4">

    <source address="xxx.xxx.xxx.xxx"/>

    <service name="ssh"/>

    <accept/>

  </rule>

  <rule family="ipv4">

    <source address="xxx.xxx.xxx.xxx"/>

    <port protocol="tcp" port="3306"/>

    <accept/>

  </rule>

</zone>

[root@localhost ~]#

잘 적용 된것 같습니다.

이제 방화벽 정책에 반영하겠습니다.

[root@localhost ~]# firewall-cmd --reload

success

[root@localhost ~]# netstat -ntl

Active Internet connections (only servers)

Proto Recv-Q Send-Q Local Address           Foreign Address         State

tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN

tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN

tcp6       0      0 :::3306                 :::*                    LISTEN

tcp6       0      0 :::22                   :::*                    LISTEN

tcp6       0      0 ::1:25                  :::*                    LISTEN

[root@localhost ~]#


netstat 명령으로 포트 오픈 상태를 확인 할 수 있습니다. mysql 과 ssh 데몬이 실행 되고 있다면 위와 같은 결과가 나올 것입니다.

http (80)은 아직 포트를 listen 하고 있는 데몬이 없어서 활성화 되지 않았습니다.

접속 확인은 다른 SSH  창을 열고 접속 해보시고 잘 되었다면  MySQL 접속도 테스트 해보시면 됩니다.


여기까지 CentOS 7 minimal 설치 에 대한 포스팅을 마칩니다.



이전 편에서 CentOS 7에 MySQL 5.7을 설치 하는 법을 살펴 보았습니다.

MySQL 5.7에서 User 테이블에 구조가 변경되었다는 언급이 있었는데요 어떻게 바뀌었는지는 살펴보지는 않겠습니다.

MySQL 시스템 명령어로  datebase와 user을 생성하면 실제 테이블 구조와는 관계 없이 작업이 가능합니다.

USER 테이블에 Insert 쿼리 문장을 만들어서 사용자를 생성했던 분들은 MySQL 버전이 올라가면서 바뀌면서 테이블 구조가 약간씩 달라지고 하는 통에 애를 먹었을 것입니다.

오라클에서 처럼 문장으로 만들어진 명령어로 사용할 DB와 사용자를 생성해 보도록 하겠습니다.

 

Character set  확인

요즘은 대부분 UTF-8이 표준이기 때문에 나중에 골치 아픈 문제를 만나기 전에 미리 설정을 확인하고 넘어가겠습니다.

[root@localhost ~]# mysql -uroot -p

Enter password:

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 2

Server version: 5.7.21 MySQL Community Server (GPL)

 

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

 

Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.

 

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

 

mysql> status;

--------------

mysql  Ver 14.14 Distrib 5.7.21, for Linux (x86_64) using  EditLine wrapper

 

Connection id:          2

Current database:

Current user:           root@localhost

SSL:                    Not in use

Current pager:          stdout

Using outfile:          ''

Using delimiter:        ;

Server version:         5.7.21 MySQL Community Server (GPL)

Protocol version:       10

Connection:             Localhost via UNIX socket

Server characterset:    utf8

Db     characterset:    utf8

Client characterset:    utf8

Conn.  characterset:    utf8

UNIX socket:            /var/lib/mysql/mysql.sock

Uptime:                 22 sec

 

Threads: 1  Questions: 5  Slow queries: 0  Opens: 105  Flush tables: 1  Open tables: 98  Queries per second avg: 0.227

--------------

 

mysql>

 

붉은색 표시한 부분이  utf8로 되어 있는지 확인합니다.

만일 latin1 같은 형식이라면 /etc/my.cnf 에 아래 두줄을 추가 한 후  MySQL 데몬을 재시작 합니다.(systemctl restart mysqld)

[mysqld]

collation-server = utf8_unicode_ci

character-set-server = utf8

... 생략 ...

한글 설정이 확실히 정해지지 않은 상태에서 database를 생성하는 경우 나중에 테이블에 한글 입력 등의 문제가 봉착 될 수 있으니 반드시 확실하게 utf8로 지정이 되었는지 확인하고 넘어가도록 합니다.

다음은 WEBService 라는 DataBase를 생성해 보겠습니다.

[root@localhost ~]# mysql -u root -p

Enter password:

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 5

Server version: 5.7.21 MySQL Community Server (GPL)

 

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

 

Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.

 

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

 

mysql> show databases;

+--------------------+

| Database           |

+--------------------+

| information_schema |

| mysql              |

| performance_schema |

| sys                |

+--------------------+

5 rows in set (0.00 sec)

 

mysql> create database WEBService ;

Query OK, 1 row affected (0.05 sec)

 

mysql> show databases;

+--------------------+

| Database           |

+--------------------+

| information_schema |

| WEBService          |

| mysql              |

| performance_schema |

| sys                |

+--------------------+

5 rows in set (0.00 sec)

 

 

coludnine 라는 사용자를 추가 하도록 하겠습니다.

mysql> use mysql

Reading table information for completion of table and column names

You can turn off this feature to get a quicker startup with -A

 

Database changed

mysql> create user 'cloudnine'@'localhost' identified by '사용할비밀번호';

Query OK, 0 rows affected (0.00 sec)

 

mysql> create user 'cloudnine'@'%' identified by '사용할비밀번호';

Query OK, 0 rows affected (0.00 sec)

 

mysql> select host, user, password from user;

ERROR 1054 (42S22): Unknown column 'password' in 'field list'

※ mysql 구버전에서 사용하던 password 컬럼은 authentication_string 으로 이름이 바뀌었음

mysql> select host, user, authentication_string from user;

+-----------+---------------+-------------------------------------------+

| host      | user          | authentication_string                     |

+-----------+---------------+-------------------------------------------+

| localhost | root          | *암호화된비밀번호문자열 |

| localhost | mysql.session | *암호화된비밀번호문자열 |

| localhost | mysql.sys     | *암호화된비밀번호문자열 |

| localhost | cloudnine      | *암호화된비밀번호문자열 |

| %         | cloudnine      | *암호화된비밀번호문자열 |

+-----------+---------------+-------------------------------------------+

mysql 특성상 user가 같아도 host가 다르면 별개의 사용자로 간주합니다.

위의 예에서 하나는 localhost용 이고 또 하나는 외부 모든 IP로 부터 접속을 허용한다는 의미입니다.

보안상 문제가 있을 수 있지만 우리는 방화벽에서 외부 접속을 엄격히 차단 할 것이므로 크게 상관은 없어 보입니다.

만일 특정 IP에서만 접속을 허용하고 싶다면 '%' 대신 IP주소를 입력하면 됩니다.

 

위에서 User테이블의 스키마가 약간 바뀐것 같다고 말씀드렸었는데 바로 password 컬럼이 authentication_string 컬럼으로 변경된것 같습니다.

user 테이블을 조회 해 보면 계정이 잘 생성 되었습니다.

 

이제 생성한 user에 database 사용권한을 부여하고 잘 접속 되는지 확인 하도록 하겠습니다.

mysql> grant all privileges on WEBService.* to 'cloudnine'@'localhost';

Query OK, 0 rows affected (0.00 sec)

 

mysql>  grant all privileges on WEBService.* to 'cloudnine'@'%';

Query OK, 0 rows affected (0.00 sec)

 

mysql> flush privileges;

Query OK, 0 rows affected (0.00 sec)

 

mysql> quit

Bye

[root@localhost ~]# mysql -u cloudnine-p

Enter password:

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 8

Server version: 5.7.21 MySQL Community Server (GPL)

 

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

 

Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.

 

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

 

mysql> use WEBService

Database changed

mysql> show tables;

Empty set (0.01 sec)

 

mysql> quit

 

권한 역시 localhost용과 외부접속용 두가지 모두  부여합니다.

 

 

https://opensrc.tistory.com/191?category=475519현재 MySQL 8 버전이 올라와 있습니다. 작년 까지만 해도 5.x 대 였는데 어떻게 된 것인가... 싶었는데 아마도 5.8 버전 부터 MySQL 8 이라고 부르는 것 같습니다. 

MySQL이 Oracle에 인수되고나서 오픈소스 진영에서 동일한 클론으로 배포되던 Maria DB와도 5.6 후로는 완벽히 동일하다는 말이 사라진 것으로 보아 이후 버전은 소스코드가 별개로 가지 않나 싶은 생각이 드네요

아직은 MySQL 8버전에 대한 자료가 많지 않고 레퍼런스가 많지 않다고 판단되어 5.7버전을 선택하기로 했습니다.

실제로 설치 해보니 5.7버전도 시작부터 몇가지 기존 버전과 다른 점이 있어 몇번 삽질을 했습니다.

가장 문제가 된 점은 설치 root로 접속을 할 수 없는 점 이었습니다. rpm 패키지를 yum 으로 인스톨 하면서 비밀번호 설정하는 곳이 없는데 비밀번호를가 틀려서 접속 할 수가 없습니다.

공식문서(https://dev.mysql.com/doc/refman/5.7/en/linux-installation-yum-repo.html) 에 살펴보니 설치되면서 관리자 계정 root@localhost 이 생성되고 임시 비밀번호는 /var/log/mysqld.log 에 기록된다고 하네요.

설치 후 

shell> sudo grep 'temporary password' /var/log/mysqld.log

명령으로 확인 가능하다고 합니다.

이점 참고 해서 MySQL 5.7 버전을 설치 하고 관리자 계정의 비번을 원하는대로 변경 하도록 하겠습니다.

그리고 마지막으로 비밀번호를 분실 했을 경우 기존 버전에서  mysqld-safe --skip-grant-tables 명령르로 실행 후 접속해서 비번을 변경하던 방식을 mysqld-safe 명령이 사라진 5.7 버전에서는 어떻게 하는지 알아보았습니다.


가장 먼저 설치할 패키지의 주소를 확인합니다.

CentOS 7은 Red Hat Enterparise Linux 7  과 동일한 오픈 버전입니다.

https://dev.mysql.com/downloads/repo/yum/

로그인 하지 않은 상태에서 다운로드를 클릭하면 로그인 하라고 창이 먼저 나옵니다.

Oracle 계정이 없으신 분은 하나 만들어 두시면 두고 두고 쓸일이 있을 것이니 하나 생성하시길... 공짠데 뭐 ㅋ

로그인을 완료 하면 아래 처럼 다운로드 화면으로 자동으로 이동합니다. 크롬에서 검사를 눌려 보면 실제 파일의 경로가 보입니다

현재 Red Hat 7(CentOS 7)용 MySQL 5.7의 패키지 경로는 /get/mysql57-community-release-el7-11.noarch.rpm 입니다.

이것을 도메인과 합치면 https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm 가 됩니다.


이것을 로컬에 다운 받아서 ftp로 서버에 올려서 설치해도 되고, 서버에서 wget 명령으로 직접 다운받아서 해도 되고 패키지의 경로로 위 주소를 직접 지정해서 설치도 가능합니다.

이번에는 세번째 방법으로 설치 하도록 하겠습니다.

root 권한으로 실행합니다.

[root@localhost ~]# rpm -ivh https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm

[root@localhost ~]# yum -y install mysql-community-server

mysql-community-server 를 설치함으로써 의존성이 걸려있는 mysql-community-server, mysql-community-client, mysql-community-libs, mysql-community-common 네개의 패키지가 설치 됩니다.


요즘은 대부분 케릭터셋이 utf-8로 통일 되고 있는 추세 입니다.

혹시 나중에 문제가 될 수 있으니 /etc/my.cnf 에 한줄 추가 하도록 하겠습니다.

[mysqld]

collation-server = utf8_unicode_ci

character-set-server = utf8

... 생략


설치 완료 후 데몬을 실행하도록 하겠습니다.

[root@localhost ~]# systemctl start mysqld


로그인 하기 전에 mysql이 설치되면서 입력된 임시 비밀번호를 확인해야 한다.

[root@localhost ~]# grep 'temporary password' /var/log/mysqld.log

2018-02-13T14:49:45.720116Z 1 [Note] A temporary password is generated for root@localhost: tur++-dvf7tI

부여된 패스워드는 붉은 부분(실제로는 붉은 글씨가 아님)입니다.


임시 비번으로 로그인 해서 새로운 비밀번호를 부여하도록 하겠습니다.

[root@localhost ~]# mysql -u root -p

Enter password : tur++-dvf7tI

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 2

Server version: 5.7.21


Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.


Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.


Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


mysql> use mysql

ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.


헐... 임시 비밀번호를 바꾸기 전에는 아무것도 할 수 없는 듯...


mysql> alter user 'root'@'localhost' identified by '새비밀번호';

Query OK, 0 rows affected (0.00 sec)


mysql> commit;

Query OK, 0 rows affected (0.00 sec)


mysql> quit

Bye



이제 비밀번호가 잘 바뀌었는지 확인 해보도록 하겠습니다.

[root@localhost download]# mysql -u root -p

Enter password:

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 3

Server version: 5.7.21 MySQL Community Server (GPL)


Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.


Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.


Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


mysql> use mysql

Reading table information for completion of table and column names

You can turn off this feature to get a quicker startup with -A


Database changed

mysql> show tables;

+---------------------------+

| Tables_in_mysql           |

+---------------------------+

| columns_priv              |

| db                        |

| engine_cost               |

| event                     |

| func                      |

| general_log               |

| gtid_executed             |

| help_category             |

| help_keyword              |

| help_relation             |

| help_topic                |

| innodb_index_stats        |

| innodb_table_stats        |

| ndb_binlog_index          |

| plugin                    |

| proc                      |

| procs_priv                |

| proxies_priv              |

| server_cost               |

| servers                   |

| slave_master_info         |

| slave_relay_log_info      |

| slave_worker_info         |

| slow_log                  |

| tables_priv               |

| time_zone                 |

| time_zone_leap_second     |

| time_zone_name            |

| time_zone_transition      |

| time_zone_transition_type |

| user                      |

+---------------------------+

31 rows in set (0.00 sec)

mysql> quit

Bye


정상적으로 설치가 된것 같습니다.


끝으로 이전 버전에서 root 비밀번호를 분실 했을때 유용하게 이용되던 mysqld-safe 명령에 대해서 알아 보겠습니다.

MySQL 5.7버전을 설치 한 후에 저 명령을 아무리 찾아도 없었습니다. 구글링 끝에 

https://stackoverflow.com/questions/33510184/change-mysql-root-password-on-centos7

이런 내용의 글을 보았습니다.


간단히 요약해 보면

1. mysqld-safe  명령은 사라졌음

2. user 테이블의 구조가 바뀌었음

그래서 아래의 순서에 따라서 비밀번호를 바꿀 수 있습니다.

1. MySQL 서버 중지

[root@localhost ~]# systemctl stop mysqld

2. MySQL 환경변수 설정

[root@localhost ~]# systemctl set-environment MYSQLD_OPTS="--skip-grant-tables"

3. MySQL  서버 가동

[root@localhost ~]# systemctl start mysqld

4. 비번 없이  root 로그인

[root@localhost ~]# mysql -u root

5.  root 비밀번호 업데이트

mysql> UPDATE mysql.user SET authentication_string = PASSWORD('새비밀번호')

    -> WHERE User = 'root' AND Host = 'localhost';

mysql> FLUSH PRIVILEGES;

mysql> quit

6.  MySQL 중지

[root@localhost ~]# systemctl stop mysqld

7. MySQL 환경변수 초기화

[root@localhost ~]# systemctl unset-environment MYSQLD_OPTS

8. MySQL 서버 시작

[root@localhost ~]# systemctl start mysqld

9. 새 비밀번호로 로그인 확인

[root@localhost ~]# mysql -u root -p

참고로  user 테이블이 변경 된것은 user 테이블의 접근을 쿼리가 아닌 시스템 명령으로 하면 큰 차이는 없을 것 같습니다.

MySQL 홈페이지에서도 

alter user 'root'@'localhost' identified by '새비밀번호';

이런 형태를 권장 하는 것 같습니다.


이제 다음번에는 db를 생성하고 user 를 생성해서 공간을 할당해 보도록 하겠습니다.



MySQL DB 생성과 사용자 추가는 아래 링크의 [CentOS 7 minimal 설치] MySQL DB생성, User생성 편 를 참고 하세요

https://opensrc.tistory.com/191?category=475519

경고 : 이 페이지에 나오는 내용은 중요한 시스템 작업의 내용을 포함하고 있으므로 내용을 정확히 이해하지 못하고 작업 중 에러가 발생해서 시스템이 정상적으로 동작 하지 않을 경우 복구하는 방법까지 기술 하지 않고 있기 때문에 현재 운영중인 서버에 적용은 신중하게 선택하시기 바랍니다.

이 문서를 참고하여 작업 한 후 발생하는 문제에 대해서 본 문서의 작성자는 책임이 없음을 유의하시기 바랍니다.


CentOS 7 minimal 설치 후 확인해보니

[root@localhost ~]# df -h

Filesystem               Size  Used Avail Use% Mounted on

/dev/mapper/centos-root  50G  2.2G  1.9T   1% /

devtmpfs                 7.7G     0  7.7G   0% /dev

tmpfs                    7.7G     0  7.7G   0% /dev/shm

tmpfs                    7.7G  8.5M  7.7G   1% /run

tmpfs                    7.7G     0  7.7G   0% /sys/fs/cgroup

/dev/sda1               1014M  143M  872M  15% /boot

tmpfs                    1.6G     0  1.6G   0% /run/user/0

/dev/mapper/centos-home  1.8T  1G  1.8T   1% /

[root@localhost ~]# df -h

/ 에 약 50Gbyte 

/home 1.8Tbyte 가 할당되어 있습니다.


인스톨 할 때 조절 하는 화면을 주의 깊게 보지 못하고 그냥 깔았더니 이렇게 되었습니다.

https://blog.asamaru.net/2015/10/14/centos-7-minimal-install/

위 링크에 보면 설치 할 때 파티션 용량을 정해주는 내용이 있으니 참고 하시기 바랍니다.


실제 서버용으로 사용하려면 /usr~ /local~ /var 같은 디렉토리 들이 더 많이 사용되게 됩니다.

/home 디렉토리를 사용해도 되지만 보통 서비스용으로 사용하는 서버스에는 왠지 잘 사용하지 않게 되더군요.


디렉토리 마운트를 나누지 않고 / 로 몰아버리는 것도 방법 입니다.


검색해 보니 나랑 같은 문제로 이미 먼저 눈길을 걸어간 이가 있습니다

http://wincloud.link/pages/viewpage.action?pageId=1736706

그의 발자욱을 따르기로 했습니다.

링크의 예 중 첫 번째 방법은 /home에 할당된 크기를 조정 하다가 에러가 발생합니다.


두번째 방법 시도.

[root@localhost ~]# mv /home /home.back                                                 # home 디렉토리 내용을 백업

[root@localhost ~]# umount /home                                                           # home 디렉토리 마운트 해제

[root@localhost ~]# lvremove /dev/mapper/centos-home                               # home 파티션을 삭제

[root@localhost ~]# lvextend -r -l+100%FREE /dev/mapper/centos-root             # 남은 공간을 모두 루트 파티션에 할당

[root@localhost ~]# mv /home.back /home                                                 # 홈 디렉토리 내용을 복원 

[root@localhost ~]# vi /etc/fstab                                                               # 부팅시 에러 방지를 위해 /home 에 해당하는 라인을 주석처리

[root@localhost ~]# df -h

Filesystem               Size  Used Avail Use% Mounted on

/dev/mapper/centos-root  1.9T  2.2G  1.9T   1% /

devtmpfs                 7.7G     0  7.7G   0% /dev

tmpfs                    7.7G     0  7.7G   0% /dev/shm

tmpfs                    7.7G  8.5M  7.7G   1% /run

tmpfs                    7.7G     0  7.7G   0% /sys/fs/cgroup

/dev/sda1               1014M  143M  872M  15% /boot

tmpfs                    1.6G     0  1.6G   0% /run/user/0

[root@localhost ~]#

잘 재 할당 되었습니다.


한 가지 참고 할 것은 원작자의 글에 나온대로 mv 명령을 실행해서는 /home -> /home.back -> /home  이렇게 복원이 잘 되지 않는다.

이글 에서도 mv 명령은 파티션 재 할당 완료 후에 기억을 재조합 해서 편집한 명령에 불과 하므로 개념적인 참고만 하고 제대로 백업이 되었는지 복원후 제대로 복원이 되었는지 필히 확인하는 것이 좋겠습니다.


나보다 앞서 눈길을 걸어가고 뒤 따르는 이에게 이정표가 될 발자국을 남겨주신 winCloud님께 감사드립니다.


+ Recent posts