'ITWeb'에 해당되는 글 798건

  1. 2020.04.21 [Spring] spring security + javax api 사용에 따른 JDK 버전 문제.
  2. 2020.04.21 [Spring] Spring Cloud Config Client 구성 시 주의점
  3. 2020.04.20 [Bash] Bash function, array parameters, str replace 등
  4. 2020.04.20 [Gradle] Task Tutorial
  5. 2020.04.17 [Gradle] Task Zip
  6. 2020.04.09 [Slack] Incoming WebHooks 사용하기.
  7. 2020.04.09 [Spring] WebClient 사용 예제
  8. 2020.04.09 [Spring] Scheduler + WebClient Hello ~
  9. 2020.04.07 [Shell] Bash read, if then fi
  10. 2020.03.25 [Ubuntu] add-apt-repository: command not found 발생 시

[Spring] spring security + javax api 사용에 따른 JDK 버전 문제.

ITWeb/개발일반 2020. 4. 21. 17:56

구글링 하면 많이 나옵니다.

 

spring security 사용 시 JDK 를 11 사용 하게 되면 아래와 같은 오류가 발생 할 떄가 있습니다.

 

에러)

 

Class Not Found javax/xml/bind/DatatypeConverter

 

해당 API 는 JDK 11 에서 삭제 되었기 때문에 그렇습니다.

그래서 해결을 하기 위해서는 두 가지 방법이 있습니다.

 

1. JDK 1.8 을 사용 하거나

2. javax-api dependency 를 잡아 주시면 됩니다.

compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'

 

:

[Spring] Spring Cloud Config Client 구성 시 주의점

ITWeb/개발일반 2020. 4. 21. 16:35

주의점 이라기 보다 그냥 설정을 잘 못 하는 경우가 있어서 기록 합니다.

 

Client 쪽 Dependency 등록)

implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

 

Client 쪽 bootstrap.yml 등록)

spring:
  cloud:
    config:
      uri: http://${CONFIG_SERVER_HOST}:${PORT}

management:
  endpoints:
    web:
      exposure:
        include: refresh

 

Refresh 호출)

- HTTP Method : POST

- http://${CONFIG_CLIENT_HOST}:${PORT}/actuator/refresh

 

bootstrap.yml 에 등록 하지 않게 되면 client 가 config server 를 localhost:8888 로 찾는 오류가 발생 할 수 있습니다.

또한 refresh 등록을 해줘야 actuator/refresh 사용이 가능 합니다.

 

:

[Bash] Bash function, array parameters, str replace 등

ITWeb/개발일반 2020. 4. 20. 11:18

bash script 에서 function 사용은 호출 위치 보다 위에 function 선언이 되어 있어야 합니다.

#!/usr/bin/env bash

function 함수명() {
...
}

## 예제

#!/usr/bin/env bash

function helloWorld() {
  echo 'Hello World!!'
}

helloWorld

 

function  에 다중 array paramters 를 넘기는 예제는 아래와 같습니다.

#!/usr/bin/env bash

function deployElasticStack() {
  local instances=($1)
  local targets=($2)

  for instance in ${instances[@]}
  do

    local hostIpUser=($(echo $instance | tr ":" "\n"))

    for target in ${targets[@]}
    do
...중략...
    done
  done
}

selfHost=$(hostname -I|cut -f1 -d ' ')

instanceArr=("xxx.xxx.xxx.xxx:ubuntu" "xxx.xxx.xxx.xxx:ec2-user" "xxx.xxx.xxx.xxx:ubuntu")
metricbeatArr=("xxx.xxx.xxx.xxx" "xxx.xxx.xxx.xxx" "xxx.xxx.xxx.xxx")

deployElasticStack "${instanceArr[*]}" "${metricbeatArr[*]}" "$selfHost" "metricbeat"

 

해당 장비의 IP 정보를 가져 오는 예제는 아래와 같습니다.

$ hostname -I

$ hostname -I|cut -f1 -d ' '

$ ip route get 8.8.8.8 | sed -n '/src/{s/.*src *\([^ ]*\).*/\1/p;q}'

 

file 내 특정 문자열을 치환 하는 예제는 아래와 같습니다.

$ sed -i "s/소스문자열/치환문자열/g" configuration.yml

# osx 에서는 아래와 같이 합니다.
$ sed -i "" "s/소스문자열/치환문자열/g" configuration.yml

 

:

[Gradle] Task Tutorial

ITWeb/개발일반 2020. 4. 20. 10:34

Gradle 을 이용한 build.gradle 작성 시 튜토리얼을 한번 보고 해보면 좋습니다.

 

[공식문서]

https://docs.gradle.org/current/userguide/tutorial_using_tasks.html

https://docs.gradle.org/current/dsl/org.gradle.api.Task.html

 

Task 의 실행 단계는 

- Task 본문의 Configuration

- doFirst

- doLast

단계로 실행 됩니다.

 

이 실행 단계를 이해 하기 위해서는 아래 문서를 참고 하세요.

 

[공식문서]

https://docs.gradle.org/current/userguide/build_lifecycle.html

Initialization

Gradle supports single and multi-project builds. 
During the initialization phase, Gradle determines 
which projects are going to take part in the build, 
and creates a Project instance for each of these projects.

Configuration

During this phase the project objects are configured. 
The build scripts of all projects which are part of the build are executed.

Execution

Gradle determines the subset of the tasks, 
created and configured during the configuration phase, to be executed. 
The subset is determined by the task name arguments passed to the gradle command 
and the current directory. 
Gradle then executes each of the selected tasks.

settings.gradle > build.gradle > task configured > task internal configuration > task

 

더불어서 task 가 실행 되지 않는 경우가 있는데 이 경우는 아래의 경우 실행 되지 않습니다.

 

- SKIPPED

Task did not execute its actions.

- NO-SOURCE

Task did not need to execute its actions.

  • Task has inputs and outputs, but no sources. For example, source files are .java files for JavaCompile.

:

[Gradle] Task Zip

ITWeb/개발일반 2020. 4. 17. 10:13

참고문서)

https://docs.gradle.org/current/userguide/working_with_files.html#sec:creating_archives_examplel

 

예제) build.gradle

plugins {
  id 'base'
}

def buildNumber = System.getenv('BUILD_NUMBER')
version = "0.0.${buildNumber}"

task packageDistribution(type: Zip) {
  archiveFileName = "${project.name}-${project.version}.zip"
  destinationDirectory = file("$buildDir/dist")

  from 'docker-compose', 'scripts'
}

 

:

[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

: