'ITWeb/개발일반'에 해당되는 글 489건

  1. 2020.04.09 [Slack] Incoming WebHooks 사용하기.
  2. 2020.04.09 [Spring] WebClient 사용 예제
  3. 2020.04.09 [Spring] Scheduler + WebClient Hello ~
  4. 2020.04.07 [Shell] Bash read, if then fi
  5. 2020.03.25 [Ubuntu] add-apt-repository: command not found 발생 시
  6. 2020.03.23 [Certificate] 확장자에 따른 인증서 종류
  7. 2020.03.20 [Reactor Netty] WebClient connection pool?
  8. 2020.03.17 [Shell] Ubuntu 에서 Reboot 시 자동 스크립트 실행.
  9. 2020.03.16 [Gitlab] HTTP Basic: Access denied 해결 하기
  10. 2020.03.16 [Redmine] Ubuntu 에 Redmine 설치 하기

[Slack] Incoming WebHooks 사용하기.

ITWeb/개발일반 2020. 4. 9. 17:59

개발 하면서 Slack API 와 연동해서 메시지 보내는 경우가 많이 있습니다.

예전에는 그냥 개인 계정으로 API Token 만들어서 사용했던 기억이 있었는데, 이제는 아닌가 봅니다.

 

회사에서 특정 Workspace 를 만들고 이를 통해서 Notification  구현을 하려고 보니 엄한데서 API 만들고 있었습니다.

 

- 개인 계정으로 App 개발을 위해서 사용 하세요.

http://api.slack.com 

 

- 특정 Workspace 내 에서 App 개발을 위해서 사용 하세요.

http://WORKSPACE.slack.com/apps 

 

담부터는 실수 하지 말아야 겠습니다.

:

[Spring] WebClient 사용 예제

ITWeb/개발일반 2020. 4. 9. 17:01

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.RequestHeadersSpec.html#retrieve--

- retrieve()
Perform the HTTP request and retrieve the response body.
This method is a shortcut to using exchange() and decoding the response body through ClientResponse.

- exchange()
Perform the HTTP request and return a ClientResponse with the response status and headers. You can then use methods of the response to consume the body

 

https://spring.io/guides/gs/reactive-rest-service/

 

[Non Blocking]

// retrieve() 예제
  @Override
  public void runner() {
    Mono<String> response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .retrieve()
        .bodyToMono(String.class);

    response.subscribe(result -> {
      logger.debug("{}", result);
    }, e -> {
      logger.debug("{}", e.getMessage());
    });
  }
  
// exchange() 예제  
  @Override
  public void runner() {
    Mono<String> response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .exchange()
        .flatMap(clientResponse -> clientResponse.bodyToMono(String.class));

    response.subscribe(result -> {
      logger.debug("{}", result);
    }, e -> {
      logger.debug("{}", e.getMessage());
    });
  }

 

[Blocking]

  @Override
  public void runner() {
    String response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .exchange()
        .block()
        .bodyToMono(String.class)
        .block();

    logger.debug("{}", response);
  }
  
  @Override
  public void runner() {
    Mono<String> response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .retrieve()
        .bodyToMono(String.class);

    String result = response.block();
    
    logger.debug("{}", result);
  }

 

WebClient  는 기본 async 방식으로 동작 합니다.

그리고 어디선가 문서에서 봤는데 Connection Pool 관련 고민을 하지 않고 사용해도 된다고 했던 것으로 기억 합니다.

구글링 하다 걸린 코드 걸어 둡니다.

 

[Timeout 설정]

// TcpClient 이용
TcpClient tcpClient = TcpClient.create()
                 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000) // Connection Timeout
                 .doOnConnected(connection ->
                         connection.addHandlerLast(new ReadTimeoutHandler(10)) // Read Timeout
                                   .addHandlerLast(new WriteTimeoutHandler(10))); // Write Timeout
WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
    .build();


// ReactorClientHttpConnector 이용
ReactorClientHttpConnector connector =
            new ReactorClientHttpConnector(options ->
                    options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000));
WebClient webClient = WebClient.builder().clientConnector(connector).build();

 

보시면 아시겠지만 방법은 다양하게 많이 있습니다.

직관적이고 이해 하기 쉬운 방법을 찾아서 사용하시면 될 것 같습니다.

 

:

[Spring] Scheduler + WebClient Hello ~

ITWeb/개발일반 2020. 4. 9. 15:09

필요한 Dependency)

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-quartz'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

 

Main Application 에서 @EnableScheduling 을 작성 해 주시면 아주 쉽게 Annotation 설정 만으로 사용 하실 수 있습니다.

예제는 아래 문서 보시면 너무 쉽게 되어 있습니다.

https://spring.io/guides/gs/scheduling-tasks/

 

추가적인 내용을 조금 더 작성 하자면,

- fixedDelay

실행 시간을 포함해서 완료 된 이후 타이머가 동작 하는 방식 입니다.

 

- fixedRate

실행 시간 상관 없이 그냥 지정된 타이머 주기로 동작 하는 방식 입니다.

결국, 실행 시간이 길 경우 중복 실행이 될 수 있습니다.

 

- PeriodicTrigger

특정 주기에 맞춰서 실행 됩니다.

fixedRate 과 유사한 방식 이라고 보시면 됩니다.

 

- CronTrigger

cron 설정 주기에 맞춰서 실행 됩니다.

 

저는 그냥 Web Service 형태로 해서 구성을 해서 아래와 같이 적용했습니다.

 

Scheduler 에 대한 Abstract 를 만들어서 사용했습니다. (구글링 해보면 예제 코드 많이 나옵니다.)

  @PostConstruct
  public void init() {
    this.startScheduler();
  }

  @Override
  public void runner() {
    String response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .exchange()
        .block()
        .bodyToMono(String.class)
        .block();

    logger.debug("{}", response);
  }

  @Override
  public Trigger getTrigger() {
    return new PeriodicTrigger(10, TimeUnit.SECONDS);
  }

  @Override
  public String getCheckQuery() {
    ...중략...
    return checkQuery;
  }

GenericScheduler 를 Singleton 으로 만들어서 Abstract 를 하나 구성했습니다.

  public void startScheduler() {
    GenericScheduler.getInstance()
        .schedule(getRunnable(), getTrigger());
  }

  public void stopScheduler() {
    GenericScheduler.getInstance().shutdown();
  }

  private Runnable getRunnable(){
    return new Runnable(){
      @Override
      public void run() {
        runner();
      }
    };
  }

  public abstract void runner();

  public abstract Trigger getTrigger();

  public abstract String getCheckQuery();

대략적인 구성은 아래 처럼 나옵니다.

@SpringBootApplication
@EnableScheduling
public class MegatoiMonitorWatcherApplication { ... }

public class GenericScheduler { ... }

public abstract class AbstractGenericScheduler { ... }

@Component
public class WatcherCpuChecker extends AbstractGenericScheduler { ... }

 

:

[Shell] Bash read, if then fi

ITWeb/개발일반 2020. 4. 7. 20:07
#!/usr/bin/env bash

pwd

echo "Are you sure current directory? (Y/N)"
read answer

if [ "$answer" == "y" ] || [ "$answer" == "Y" ]
then
  echo "Yes!!"
fi

echo "done!!"
#!/usr/bin/env bash

pwd

echo "Are you sure current directory? (Y/N)"
read ans
answer=`echo $ans| tr a-z A-Z`

if [ "$answer" == "Y" ]
then
  echo "Yes!!"
fi

echo "done!!"

 

:

[Ubuntu] add-apt-repository: command not found 발생 시

ITWeb/개발일반 2020. 3. 25. 18:32

# add-apt-repository: command not found 에러가 발생 하면 아래와 같이 설치 하시면 됩니다.
$ sudo apt-get install software-properties-common

:

[Certificate] 확장자에 따른 인증서 종류

ITWeb/개발일반 2020. 3. 23. 10:56

원본 글)

https://www.securesign.kr/guides/kb/54

 

SSL 인증서 파일 포맷 종류 - crt, cer, csr, pem, der, pfx, p12, jks, key - SecureSign

SSL 인증서 파일 포맷 종류 - crt, cer, csr, pem, der, pfx, p12, jks, key - SecureSign

www.securesign.kr

.pem
PEM (Privacy Enhanced Mail)은 Base64 인코딩된 ASCII 텍스트 이다. 파일 구분 확장자로 .pem 을 주로 사용한다.  노트패드에서 열기/수정도 가능하다. 개인키, 서버인증서, 루트인증서, 체인인증서 및  SSL 발급 요청시 생성하는 CSR 등에 사용되는 포맷이며, 가장 광범위하고 거의 99% 대부분의 시스템에 호환되는 산업 표준 포맷이다. (대부분 텍스트 파일)

.crt
거의 대부분 PEM 포맷이며, 주로 유닉스/리눅스 기반 시스템에서 인증서 파일임을 구분하기 위해서 사용되는 확장자 이다. 다른 확장자로 .cer 도 사용된다. 파일을 노트패드 등으로 바로 열어 보면 PEM 포맷인지 바이너리 포맷인지 알수 있지만 99% 는 Base64 PEM 포맷이라고 봐도 무방하다. (대부분 텍스트 파일)

.cer
거의 대부분 PEM 포맷이며, 주로 Windows 기반에서 인증서 파일임을 구분하기 위해서 사용되는 확장자 이다. crt 확장자와 거의 동일한 의미이며, cer 이나 crt 확장자 모두 윈도우에서는 기본 인식되는 확장자이다. 저장할때 어떤 포맷으로 했는지에 따라 다르며, 이름 붙이기 나름이다.

.csr
Certificate Signing Request 의 약자이며 거의 대부분 PEM 포맷이다. SSL 발급 신청을 위해서 본 파일 내용을 인증기관 CA 에 제출하는 요청서 파일임을 구분하기 위해서 붙이는 확장자 이다. (대부분 텍스트 파일)

.der
Distinguished Encoding Representation (DER) 의 약자이며, 바이너리 포맷이다. 노트패드등으로 열어 봐서는 알아 볼수 없다.  바이너리 인코딩 포맷을 읽을수 있는 인증서 라이브러리를 통해서만 내용 확인이 가능하다.  사설 또는 금융등 특수 분야 및 아주 오래된 구형 시스템을 제외하고는, 최근 웹서버 SSL 작동 시스템 에서는 흔히 사용되는 포맷은 아니다. (바이너리 이진 파일)

.pfx / .p12
PKCS#12 바이너리 포맷이며, Personal Information Exchange Format 를 의미한다. 주로 Windows IIS 기반에서  인증서 적용/이동시 활용된다. 주요 장점으로는 개인키,서버인증서,루트인증서,체인인증서를 모두 담을수 있어서 SSL 인증서 적용이나 또는 이전시 상당히 유용하고 편리하다. Tomcat 등 요즘에는 pfx 설정을 지원하는 서버가 많아지고 있다.  (바이너리 이진 파일)

.key
주로 openssl 및 java 에서 개인키 파일임을 구분하기 위해서 사용되는 확장자이다. PEM 포맷일수도 있고 DER 바이너리 포맷일수도 있으며, 파일을 열어봐야 어떤 포맷인지 알수가 있다. 저장할때 어떤 포맷으로 했는지에 따라 다르며, 확장자는 이름 붙이기 나름이다.

.jks
Java Key Store 의 약자이며, Java 기반의 독자 인증서 바이너리 포맷이다. pfx 와 마찮가지로 개인키,서버인증서,루트인증서,체인인증서를 모두 담을수 있어서 SSL 인증서 파일 관리시 유용하다. Tomcat 에서 SSL 적용시 가장 많이 사용되는 포맷이다. (바이너리 이진 파일)

:

[Reactor Netty] WebClient connection pool?

ITWeb/개발일반 2020. 3. 20. 19:12

RestTemplate 을 사용 할 까 하다, WebClient 로 변경 했습니다.

더불어 늘 Connection Pool 에 대한 고민을 하는데요.

 

https://projectreactor.io/docs/netty/snapshot/reference/index.html#_connection_pool

By default, the TCP client uses a “fixed” connection pool with 500 
as the maximum number of the channels, 
45s as the pending acquire timeout and 1000 as the maximum number of 
the registered requests for acquire to keep in the pending queue. 
This means that the implementation creates a new channel 
if someone tries to acquire a channel but none is in the pool. 
When the maximum number of the channels in the pool is reached, 
new tries to acquire a channel are delayed 
until a channel is returned to the pool again. 
The implementation uses FIFO order for channels in the pool. 
By default, there is no idle time specified for the channels in the pool.

Channel 500개

Queue 1000개

 

그냥 신경쓰지 말고 쓰라고 합니다.

:

[Shell] Ubuntu 에서 Reboot 시 자동 스크립트 실행.

ITWeb/개발일반 2020. 3. 17. 11:30

급하게 필요한 것들을 구성 하다 보니 좀 엉성하게 구성이 되어서 챙겨야 할 게 많네요.

Jenkins 를 설치 해 두었는데 instance 가 재부팅 되면 매번 수동으로 실행을 해줘야 해서 자동으로 실행 되도록 서비스 등록을 했습니다.

 

환경)

- Ubuntu

- Jenkins war 설치

 

자동 스크립트 구성)

$ cd /etc/init.d

$ sudo vi jenkins

#! /bin/sh

JENKINS_HOME=/home/mzc/app/jenkins
JENKINS_BIN=$JENKINS_HOME/bin

sudo -u jenkins $JENKINS_BIN/stop.sh
sudo -u jenkins $JENKINS_BIN/start.sh

exit 0

 

$ sudo update-rc.d jenkins defaults

$ sudo reboot

$ service --status-all

 

이제 자동으로 잘 올라 오는지 확인해 보시면 되겠습니다.

:

[Gitlab] HTTP Basic: Access denied 해결 하기

ITWeb/개발일반 2020. 3. 16. 16:14

이 에러가 저장된 credential 정보가 틀려서 나는 것일 수도 있고 저 처럼 2FA 설정을 한 경우에 발생을 하는 경우도 있습니다.

 

Case 1) 저장된 Credentail 정보가 틀렸을 경우 아래와 같이 Reset 한번 합니다.

 

$ git config --system --unset credential

 

Case 2) 2FA 설정을 했을 경우 아래와 같이 access token을 생성 해서 Password 대신 사용을 합니다.

remote: HTTP Basic: Access denied
remote: You must use a personal access token with 'read_repository' or 'write_repository' scope for Git over HTTP.
remote: You can generate one at https://gitxxxxx.net/profile/personal_access_tokens

# 매번 입력하기 힘드니까 ID/PWD(Access Tokens) 를 저장해 둡니다.
$ git config --global credential.helper store

 

:

[Redmine] Ubuntu 에 Redmine 설치 하기

ITWeb/개발일반 2020. 3. 16. 13:24

팀에서 사용할 workspace 도구로 뭘 쓸까 하다가 Redmine 으로 결정을 했습니다.

설치는 아래 문서 보시고 따라 하시면 됩니다.

 

[설치 문서]

https://www.redmine.org/projects/redmine/wiki/HowTo_Install_Redmine_on_Ubuntu_step_by_step

 

[설치 환경]

Ubuntu Server 18.04 LTS (HVM), SSD Volume Type 

AWS EC2 c5.xlarge

 

[문서 대로 설치하기 + 일부 수정]

$ sudo apt-get install apache2 libapache2-mod-passenger
$ sudo apt-get install mysql-server mysql-client

# MySQL 설치 이후 최초 root 계정 접속 시 아래와 같이 접속 하여 계정 추가/변경 등의 작업을 수행 합니다.

$ sudo mysql -proot

 

[MySQL root 계정 접근 허용]

INSERT INTO `user` (`Host`, `User`, `Select_priv`, `Insert_priv`, `Update_priv`, 
`Delete_priv`, `Create_priv`, `Drop_priv`, `Reload_priv`, `Shutdown_priv`, 
`Process_priv`, `File_priv`, `Grant_priv`, `References_priv`, `Index_priv`, 
`Alter_priv`, `Show_db_priv`, `Super_priv`, `Create_tmp_table_priv`, 
`Lock_tables_priv`, `Execute_priv`, `Repl_slave_priv`, `Repl_client_priv`, 
`Create_view_priv`, `Show_view_priv`, `Create_routine_priv`, `Alter_routine_priv`, 
`Create_user_priv`, `Event_priv`, `Trigger_priv`, `Create_tablespace_priv`, 
`ssl_type`, `ssl_cipher`, `x509_issuer`, `x509_subject`, `max_questions`, 
`max_updates`, `max_connections`, `max_user_connections`, `plugin`, 
`authentication_string`, `password_expired`, `password_last_changed`, 
`password_lifetime`, `account_locked`) 
VALUES ('%','root','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y',
'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',
0,0,0,0,'mysql_native_password',password('************'),'N',NOW(),NULL,'N')
$ sudo apt-get install redmine redmine-mysql
$ sudo gem update
$ sudo gem install bundler

 

[passenger.conf 수정]

$ sudo vi  /etc/apache2/mods-available/passenger.conf

<IfModule mod_passenger.c>
  PassengerRoot /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini
  PassengerDefaultRuby /usr/bin/ruby
  PassengerDefaultUser www-data		  
</IfModule>

문서에는 아래와 같이 수정 하게 되어 있으나 동작 하지 않아서 변경했습니다.

<IfModule mod_passenger.c>
    PassengerDefaultUser www-data
    PassengerRoot /usr
    PassengerRuby /usr/bin/ruby
</IfModule>

 

[ports.conf 수정]

$ sudo vi /etc/apache2/ports.conf

Listen xxxx

# 기본 80 포트를 사용 하나 다른 프로그램에서 사용을 하고 있어서 포트를 수정 하였습니다. 

 

[000-default.conf 수정]

$ sudo vi /etc/apache2/sites-available/000-default.conf

<VirtualHost *:xxxx>
....

    <Directory /var/www/html/redmine>
        RailsBaseURI /redmine
        PassengerResolveSymlinksInDocumentRoot on
    </Directory>
</VirtualHost>

 

[최종 마무리 단계]

$ sudo ln -s /usr/share/redmine/public /var/www/html/redmine
$ sudo touch /usr/share/redmine/Gemfile.lock
$ sudo chown www-data:www-data /usr/share/redmine/Gemfile.lock
$ sudo service apache2 restart

 

[접속]

http://localhost:xxxx/redmine

admin/admin

 

: