728x90
반응형
728x90
반응형
728x90

저는 심플한 게임을 만들어 보려고 합니다. 이번에 필요한 것은 키 입력 시 캐릭터를 이동하는 겁니다.

먼저 Input Action은 초보이므로 제공하는걸 사용합니다.

그리고 Player Input 컴포넌트의 Behavior는 쉬운 Send Messages (기본값) 와 Invoke Unity Events (입문자 권장) 를 사용해 보려 합니다.

 

처음의 생각은 아주 순진하게도 특정키를 입력하면 이벤트 트리거 같은 것이 캐치하는 걸 생각 했습니다.

하지만 이젠 권장하지 않는다고 해서 New Input System 이라는 먼 길을 돌아왔습니다.

( 이전글 [unity6] New Input System - 01. 기본 가이드 또는 가이드 영상 참조)

 

사용된 캐릭터 오브젝트에 적용된 C# 스크립트 파일은 Player.cs 입니다.

 

1. 캐릭터 오브젝트에 Player Input 컴포넌트 추가 (Actions 에는 이미 제공되어 있는 Input Action 파일이 자동으로 들어감)

Actions에 파일을 더블 클릭하면 Input Action이 작성된 것을 확인할 수 있습니다.

여기에 작성된 정보는 "Player는 이런 액션을 사용할 수 있는데 각 액션에는 어떤 입력을 바인딩 한다." 라고 정의해 둔 것 입니다.

위 이미지의 노란색 박스가 Behavior를 설정하는 부분입니다.

 

1-1. Behavior를 Send Messages 사용시

액션이 Move 라고 정의되어 있다면 C# 코드의 OnMove 함수를 호출하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using UnityEngine;
using UnityEngine.InputSystem;
 
public class Player : MonoBehaviour
{
 
    private Vector2 inputDirection; // 입력받은 방향을 저장할 변수
 
    /// <summary>
    /// Action Input > Behavior > Send Message 사용시
    /// : 이동 입력을 받았을 때 호출되는 함수 (이름이 정해져 있음: On + Action이름)
    /// </summary>
    /// <param name="value"></param>
    void OnMove(InputValue value)
    {
        Logger.Log("Call OnMove()", LogLevel.Debug);
        // 키 입력 방향 세팅
        inputDirection = value.Get<Vector2>();
        MoveAction(inputDirection);
    }
 
    /// <summary>
    /// Move 액션에 대한 상세
    /// </summary>
    /// <param name="inputDirection"></param>
    void MoveAction(Vector2 inputDirection)
    {
        Logger.Log($"현재 입력 방향: {inputDirection}", LogLevel.Debug);
 
        // 타일만큼 이동시
        // 키를 누르는 순간(값이 0이 아닐 때)에만 이동
        if (inputDirection != Vector2.zero)
        {
            // 이동거리 (타일사이즈)
            float tileSize = 1f;
 
            // 1. 대각선 입력을 방지하고 상하좌우 중 가장 큰 값만 취함 (선택 사항)
            // WASD를 같이 눌러 (0.7, 0.7)이 되는 것을 방지합니다.
            if (Mathf.Abs(inputDirection.x) > Mathf.Abs(inputDirection.y))
                inputDirection = new Vector2(Mathf.Abs(inputDirection.x) / inputDirection.x, 0);
            else
                inputDirection = new Vector2(0, Mathf.Abs(inputDirection.y) / inputDirection.y);
 
            // 2. 현재 위치 + (방향 * 타일 사이즈)
            Vector3 targetPosition = transform.position + new Vector3(inputDirection.x * tileSize, inputDirection.y * tileSize, 0f);
 
            // 3. 위치 업데이트
            transform.position = targetPosition;
 
            Logger.Log($"이동 방향: {inputDirection}, 목표 좌표: {targetPosition}", LogLevel.Debug);
        }
    }
}
 
cs

코드를 간단히 리뷰하면 아래와 같습니다.

1-1-1. 방향키 입력시 Move에 정의된 Input에 의해 OnMove를 호출

1-1-2. 매개변수에서 Vector2 값을 가져와 inputDirection에 방향에 해당되는 값을 적용

1-1-3. MoveAction을 호출하여 실제 캐릭터의 포지션을 이동

※ Logger.Log()는 Debug.Log()로 대체해도 됩니다.

 

1-2. Behavior를 Invoke Unity Events 사용시

먼저 사용할 코드를 아래와 같이 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
 
public class Player : MonoBehaviour
{
 
    ...
 
 
    // Action Input > Behavior > Invoke Unity Events 사용시
    public void OnMoveInvokeEvent(InputAction.CallbackContext context)
    {
        // 1. 오직 '완전히 눌렸을 때(Performed)'만 로직을 실행하도록 제한
        if (!context.performed) return;
 
        Logger.Log("Call OnMoveInvokeEvent()", LogLevel.Debug);
 
        // 키 입력 방향 세팅
        inputDirection = context.ReadValue<Vector2>();   // value.Get<Vector2>();
        MoveAction(inputDirection);
 
    }
 
    ...
 
}
 
cs

해당 코드에서 주의할 점은 13번 라인입니다. 해당 코드가 없는 경우 키를 누를때, 뗄때 총 2번 이벤트가 발생되어 의도한 것 보다 2배 이동합니다.

 

이제 작성한 코드를 액션에 연결 하겠습니다.

1-2-1. Player Input 컨포넌트에서 Events 항목에서 사용할 Action Map을 열기 (여기서는 Player)

 

 

1-2-2. Events > Player > Move 액션에 추가 (추가 전은 List is Empty) > 액션에 대한 C# 코드를 가진 오브젝트를 넣기 (여기서는 자기 자신인 캐릭터 오브젝트) > No function > C# 코드 > 코딩한 액션 함수 (함수명은 개발자가 임의로 지정해도 됨)

 

 

 

이렇게 하면 원하는 바와 같이 방향키 입력 시 지정한 만큼 캐릭터가 이동됩니다.

 

2. 키보드를 누르고 있는 만큼 이동

위 예시는 키를 누르고 있어서 1번 지정된 만큼만 이동됩니다.

그렇다면 누르고 있는 만큼 거리를 계속해서 이동하는 코드의 예시입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
...
 
public class Player : MonoBehaviour
{
 
    private Vector2 inputDirection; // 입력받은 방향을 저장할 변수
 
    private float moveSpeed = 1f;
 
    // Update is called once per frame
    void Update()
    {
        /**
         * 부드러운 이동시 사용할 셈플
         */
        // 1. 방향 * 속도 * 프레임보정(deltaTime)을 계산합니다.
        // 현재 2D 기준(x, y)으로 작성하셨으므로 그대로 유지합니다.
        Vector3 moveAmount = new Vector3(inputDirection.x, inputDirection.y, 0f) * moveSpeed * Time.deltaTime;
 
        // 2. 최종 위치 이동
        transform.position += moveAmount;
    }
 
    /// <summary>
    /// Action Input > Behavior > Send Message 사용시
    /// : 이동 입력을 받았을 때 호출되는 함수 (이름이 정해져 있음: On + Action이름)
    /// </summary>
    /// <param name="value"></param>
    void OnMove(InputValue value)
    {
        Logger.Log("Call OnMove()", LogLevel.Debug);
        // 키 입력 방향 세팅
        inputDirection = value.Get<Vector2>();
    }
 
    // Action Input > Behavior > Invoke Unity Events 사용시
    public void OnMoveInvokeEvent(InputAction.CallbackContext context)
    {
        // 1. 오직 '완전히 눌렸을 때(Performed)'만 로직을 실행하도록 제한
        //if (!context.performed) return;
 
        Logger.Log("Call OnMoveInvokeEvent()", LogLevel.Debug);
 
        // 키 입력 방향 세팅
        inputDirection = context.ReadValue<Vector2>();   // value.Get<Vector2>();
 
    }
}
 
cs

간단히 리뷰하면 아래와 같습니다.

2-1. Update 매소드는 프레임 마다 실행됩니다.

2-2. 각 프레임마다 실행되므로 누르고 있는 동안 계속해서 이동합니다.

2-3. 실행되는 기기마다 성능이 달라서 이동 값 Vector3 * moveSpeed * Time.deltaTime 를 합니다.

2-4. 40번 라인을 주석처리 하지 않으면 키 입력이 종료될 때를 감지하지 못해서 계속해서 이동합니다.

 

오늘은 여기까지... OTL

 

 

 

### 목차 ###

[unity6] New Input System - 01. 기본 가이드

[unity6] New Input System - 02. 단순 구현하기

 

728x90
반응형
728x90

유니티의 New Input System이 탄생한 근본적인 이유는 입력의 '추상화'에 있습니다. 기존 시스템이 특정 키보드나 마우스 같은 하드웨어에 종속적이었다면, 새로운 시스템은 하드웨어와 로직 사이에 '액션(Action)'이라는 가상화 계층을 둡니다. 이를 통해 개발자는 장치에 상관없이 액션 단위로만 코드를 작성하면 되며, 멀티 플랫폼 대응과 키 바인딩 교체가 이전에 비해 비약적으로 쉬워지게 되었습니다.

 

 

이글은 버젓이 공개되어 모두를 위한 거처럼 썼지만 ^^; 제가 나중에 찾아보기 위한 가이드 

 

저는 unity6를 설치해서 자동 적용되어 있지만 이전 버전이라면 1,2번을 진행해야 합니다.

(Input System은 Unity6에서 기본으로 적용되고 합니다.)

 

  1. 패키지 설치 : Window > Package Manager > Input System을 설치 > 완료 후 unity 재시작
  2.  Action Input Handling 설정 : Edit > Project Setting > Playe > Other Setting > Configuration > Active Input Handing을 Input System Package (New) > 완료 후 재시작
    1. Input System Package (New)로 변경시 기존의 Input.GetKeyDown 코드는 작동하지 않음
    2. Both : 기존 시스템과 새 시스템을 동시에 지원. 마이그레이션 중이거나 외부 에셋을 함께 사용하는 경우 권장
    3. Active Input Handing을 찾을 수 없는 경우 좌측 상단의 검색 영역에서 검색
  3. Input Action 에셋 생성 및 설정 
    1. 2021 LTS 이후 URP 템플릿으로 프로젝트 생성 하고 Input System을 설치하면 unity가 기본적으로 이동(Move), 시야(Look), 점프(Jump) 등이 매핑된 .inputactions 파일을 생성하거나 라이브러리를 가져와 연결해 준다. 
      1. 이미 생성되어 있는 Input System을 사용하는 경우 아래 2번는 참고만 할것.
    2. 직접 Input Action 생성시
      1. Projectject 창 > Create > Input Action > 생성된 Input Action 파일 선택 > Inspacter에서 Edit Asset (또는 파일 더블클릭) > 플러스 버튼을 추가 > Scheme 정의 > Action 정의 > 우측 상단의 Save Asset 버튼을 눌러 저장

      2. C# Class 자동화 스크립트 생성 : 작성한 Input Action 파일 선택 > Inspacter > Generate C# Class 체크 > C# Class File의 우측 ... 버튼 클릭 > 파일 생성(파일명은 Input Action 파일과 동일하게 작성됨) > Apply
        1. 작성되는 C# 파일은 Input Action으로 정의한 모든 Action을 스크립트 파일을 자동 작성하는 것으로 개발자는 별도 작업은 없음
           
  4. 캐릭터 오브젝트 (Action이 필요한 오브젝트)에 Player Input 컴포넌트 추가 : 
    1. 자동 생성된 Input System을 사용시
    2. 직접 생성한 Input System을 사용시 : Actions 에서 이전에 생성한 Input Action 파일을 추가 > Behavior 설정
       
      1. Behavior 옵션
        1. Send Messages (기본값) : 가장 설정이 간편, 대규모 프로젝트에서는 성능과 유지보수 면에서 권장하지 않음
          1. Input System에 작성된 Action명이 Move인 경우 OnMove를 찾아 연결하여 실행
        2. Broadcast Message : 본인 오브젝트만이 아니라 자식 오브젝트에 붙은 모든 컴포넌트의 관련 함수까지 호출
        3. Invoke Unity Events (입문자 권장) : 유니티 UI의 Button Click 이벤트와 동일한 방
          1. 인스펙터 창에 Events 섹션이 생기며, 각 액션마다 실행할 함수를 드래그 앤 드롭 하여 연결
        4. Invoke C Shar Events (숙련자 권장) : 순수 C#코드로 이벤트를 구독하는 방식
          1. 코드내에서 playerInput.onActionTrigger += MyFuntion; 과 같이 이벤트를 직접 연결
          2. 가장 성능이 빠르고 코드의 가독성이 높아 모든 입력처리를 코드 한 곳에서 관리할 수 있어 대규모 프로젝트에 적합
          3. 딜리게이트와 이벤트 프로그래밍에 대한 이해 필요
        5. 옵션 연결 방식 성능 권장 상황
          Send Messages 함수 이름 매칭 (OnJump) 낮음 빠르게 프로토타입을 만들 때
          Broadcast Messages 자식까지 이름 매칭 매우 낮음 부모-자식 간 계층적 입력 처리가 필요할 때
          Invoke Unity Events 인스펙터 드래그 앤 드롭 보통 디자이너/기획자와 협업하거나 시각적 관리가 필요할 때
          Invoke C# Events 코드에서 스크립팅 높음 성능 최적화 및 체계적인 코드 구조가 중요할 때
  5. 스크립트 작성 및 로직 구현
    1. 스크립트를 통해 액션에 대한 액션에 대한 처리 작성 (예 : 특정 자표로 이동, 아이템 사용)

 

 

 

이걸 몇시간 동안 작성한거여! 아우 지쳐!!

 

 

### 목차 ###

[unity6] New Input System - 01. 기본 가이드

[unity6] New Input System - 02. 단순 구현하기

 

 

 

 

728x90
반응형
728x90

docker Container에서 GUI firefox 실행을 해봅시다.

 

환경정보

ubuntu 20.04.5

docker 20.10.19

 

주의! 컨테이너 생성부터 모두 docker host의 GUI 데스크톱에서 진행해야 합니다.

 

1. GUI사용을 위한 컨테이너 생성

docker run -d -it -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$DISPLAY --name con_test ubuntu:focal

여기서 GUI와 관련하여 중요한 포인트는 -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$DISPLAY 입니다.

--name con_test는 컨테이너명 지정한 것이고, ubuntu:focal은 ubuntu의 버전을 명시한 것입니다.

 

2. docker의 host에서 인증쿠키 확인

xauth list

참고로 GUI 연결 시 마다 인증쿠키가 변경됩니다. 

컨테이너 접속 전에 사용하기 위해 미리 복사해 둡시다.

 

반응형

 

3. 컨테이너에 접속 후 업데이트 및 필요한 패키지 설치

apt-get update -y
apt-get install xauth -y
apt-get install firefox -y

 

4. GUI 사용을 위해 인증쿠키 설정

xauth add 복사한인증쿠키

제대로 작성 했다면 에러가 나더라도 정상입니다.

 

5. firefox 실행

firefox

명령어를 입력하면 해당 컨테이너의 firefox가 GUI로 실행됩니다.

 

참고로 이 단계에서 겼었던 에러 2가지가 있습니다.

1) ubuntu 20.04.5 (focal)

Unable to init server: Broadway display type not supported: unix
Error: cannot open display: unix

CLI 모드에서 컨테이너를 생성한 경우입니다. GUI에서 생성해서 해결 되었습니다.

 

2) ubuntu 22.04.1

ommand '/usr/bin/firefox' requires the firefox snap to be installed.
Please install it with:

snap install firefox

이 에러는 짧게 나마 구글 검색을 해봤지만 만족스러운 답을 찾지 못했습니다.

 

* 참고한 글

 

Container 내에서 GUI Application(firefox, 계산기) 실행하기

<선결 조건> docker host를 반드시 GUI 버전으로 설치한다(Server with GUI 설치) container를 실행할 때도 GUI Desktop으로 액세스하여 Terminal을 띄워서 해야 한다 (즉, ssh로 접속하여 GUI Package를 실행해서는 안

cloudsns.wordpress.com

 

docker와 GUI 환경 연결

docker container에서 GUI 애플리케이션을 수행할 필요가 있는 경우가 많다. 이를테면 vscode를 실행시킨다던지, 심심하니까 xeyes를 실행시켜 놓는다던지.. 아래 설명한 방법을 사용하면 docker container가

driz2le.tistory.com

 

 

번외

.... 사실이건 지난 주말에 어떤 문제를 해결하기 위해 적용했던 겁니다만....

그 문제에는 전~혀 소용이 없더군요..... 하하핫.. 

그래도 하나 알았으니.. 위안을..

728x90
반응형
728x90

로그를 별도로 관리,  logback에서 propreties 값 사용, logback으로 exception 기록

 

 

이번에 작업했던 내용은 변화가 많고 수시로 수정해야 하는 내용이 있는 기능 이었다.

그래서 매번 로컬에서 테스트 한다는 것은 현실성이 없었기 때문에 로컬환경과 같이 이클립스의 console창에 표시되는 정보를 운영서버에서도 바로바로 얻어 확인할 필요성이 있었다.

이런 상황에서 적당한 것이 logback 이다.

 

 

먼저 관리 페이지에 테스터를 만들었다.

그런데 console 창 로그를 가져오고 싶은데 어떻게 해야 할까?

서버에 기본으로 남기는 로그를 가져와야 하나?

기본으로 남기는 로그는 자칫 사막에서 바늘을 찾아야 하는 상황이 될 가능성이 크다.

필터링 해서 찾는 것도 뭔가 쓸데 없는 짓으로 느껴졌다.

 

혹시나 싶어 logback에 대해 알아보기로 했다.

기존에 나는 logback에서 로그를 어떤 것을 남길 것이냐, 어느 레벨로 남기냐 정도로 사용했다.

그런데 이번에 logback을 보면서 이외에 많은 부가적인 기능이 있음을 알게 되었다.

먼저 spring logback에 대한 자세한 정보는 구글에서 검색하자.

이미 많은 현자들께서 자세히 설명해 두셨다.

 

아래는 이번에 설정한 내용이다.

 

1. logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    
    <!-- 서버 환경별 properties를 읽기 위한 방법 -->
    <property resource="application.properties" />
    <property resource="application-${spring.profiles.active}.properties" />
    
    <!-- logging 패턴 정의 -->
    <appender name="INFO_LOG_OVER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${my.log.write.path}/${my.tester.log.file.name}.log</file>    <!-- 파일을 저장할 경로를 정한다 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch> <!-- 해당 레벨만 기록한다. -->
            <onMismatch>ACCEPT</onMismatch> <!-- DENY 다른 수준의 레벨은 기록하지 않는다.(상위 레벨도 기록 안함), ACCEPT : 상위 수준의 레벨에 대한 기록된다. -->
        </filter>     <!-- 레벨별 필터링이 필요없을 경우 filter class 관련된 부분을 삭제하면 됨-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- rollover daily -->
            <fileNamePattern>${my.log.write.path}/${my.tester.log.file.name}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>    <!-- 해당 패턴 네이밍으로 이전 파일이 기록됨 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>     <!-- 한 파일의 최대 용량 -->
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>2</maxHistory> <!-- 한 파일의 최대 저장 기한 -->
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyyMMdd HH:mm:ss.SSS} [%thread] %-3level %logger{5} - %msg %n</pattern>  <!-- 해당 패턴 네이밍으로 현재 로그가 기록됨 -->
        </encoder>
    </appender>
    
    <!-- name : 콘솔에 출력된 패키지 파일에서 남긴것 에 대해서 아래 작업을 실행한다. additivity : false는 로그 파일로만, true는 콘솔도-->
    <logger name="packagename.utils.DataUtil" additivity="true">
        <!-- INFO 레벨 이상에서만 실행한다. -->
        <level value="INFO"/>
        <appender-ref ref="INFO_LOG_OVER" />
    </logger>
    <logger name="packagename.admin.TesterService" additivity="true">
        <level value="INFO"/>
        <appender-ref ref="INFO_LOG_OVER" />
    </logger>
 
    <!-- 중략 -->
 
</configuration>
cs

4~6번줄 : 환경변수를 사용하기 위해 properties을 읽는다.

기본은 logback.properties 또는 application.properties에 logging.file.path, logging.file.name를 설정해서 사용하면 된다.

다만 나의 경우 로컬과 테스트, 운영 서버의 환경이 달라 패스가 달라지는데

이에 맞게 설정하려고 하니 logging.file.path, logging.file.name를 사용할 수 없었다.

그래서 4~6번줄과 같이 서버 환경에 따른 properties 파일의 값을 가져오는 방법을 사용했다.

(6번 라인의 spring.profiles.active를 모른다면 구글 검색)

 

환경별 properties 파일에 아래와 같이 설정값을 추가해 준다.

1
2
3
## logback 설정값
my.log.write.path=C:/logs
my.tester.log.file.name=tester
cs

logback.xml 내에서는 읽어온 변수값은 10번, 18번 줄과 같이 ${변수명}으로 사용하면 된다.

(주의! properties 파일에서 패스에서 역슬러시(\)는 사용할 수 없다)

 

9~27번줄 : 로그패턴을 정의한다.

10번줄 : 로그파일 패스와 파일명

18번줄 : 백업 파일 패스와 파일명

20번줄 : 1개 로그 파일의 최대 용량

22번줄 : 로그 보유기간(일)

 

30~38번줄 : 로그패턴을 어디서 어떻게 사용할 것인지 설정한다.

30번줄 : name은 로그패턴을 사용할 메소드의 패키지 위치, additivity는 false인 경우 지정한 로그파일로만 기록, true는 console(메인로그)도 기록

 

이제 지정된 패키지에서 남기는 로그가 별도로 기록되고 된다.

 

2. exception이 기록되지 않는다?

그런데 확인해 보니 1번 소스 30~38번줄에서 excetion이 일어났지만 로그파일에는 기록되지 않고 있었다.

logback의 설정이 필요한가? 검색해 봤는데 원인은 catch 문 안에서 로그를 표시하는 방법에 문제가 있었다.

1
2
3
4
5
6
try {
    ......
catch(Exception e) {
    // e.printStackTrace(); // logback으로 관리되지 않음
    logger.error("", e);
}
cs

 

이제 내가 계획한 대로 원하는 메소드에서 남기는 로그가 별도로 기록되게 되었다.

이렇게 남겨진 로그를 메소드 호출시간 부터 검색해서 찾아 테스터 화면에 뿌려주면 원하는 기능이 완성된다.

 

* 참고한 글

 

Logback 으로 쉽고 편리하게 로그 관리를 해볼까요? ⚙️

Spring Boot…

tecoble.techcourse.co.kr

 

Spring-boot Logback을 활용한 로그저장

📝Spring-boot의 로그를 파일로 저장해보자!!📌

velog.io

 

Dveamer

현실에서 살고 있지만 이상에 대한 꿈을 버리지 못한 몽상가의 홈페이지 입니다. 개인적인 기록을 주 목적으로 하며 일상과 프로그래밍 관련 글을 포스팅합니다.

dveamer.github.io

 

728x90
반응형
728x90

크롤링으로 본문을 수집할 때 순수한 본문 내용만을 수집하고자 하지만

포스팅되는 많은 글들은 내부 링크, 좋아요 또는 댓글, 광고 등 많은 것들이 붙어 있어

이것들을 제거해야할 필요가 있었다.

 

사실 이 글의 내용은 기존에 포스팅 한 Selenium의 연장선에서 작업한 내용이다.

기존 Selenium 포스팅은 아래 글 참조.

 

[java, selenium] web crawling (웹 크롤링)

최근 좀 길게 웹 크롤링에 대해 작업을 했다. 넓은 범위로 크롤링 중인지라 길어지게 되었고, 어느정도 정리가 되어가고 있어 내용을 정리해 보고자 한다. 참고로 나는 docker에 ubuntu + tomcat + seleni

deonggi.tistory.com

 

Selenium의 연장선이라면서 Jsoup을 쓰고 있는 이유가 있다.

1차 결과물을 수집해 보았더니 원하지 않는 내용들도 딸려 들어오고 있었다.

이런 것들을 제거하는 기능이 필요했으나 작업하던 당시

Selenium에서는 이런 레퍼런스를 찾을 수 없었기 때문에 Jsoup을 섞어서 쓰게 되었다.

 

이러한 결과로 만들어진 결과를 기록해 본다.

 

1. html에서 필요하지 않는 텍스트를 태그 단위로 제거한다.

내가 얻고자 했던 것은 본문 내용 부분이다.

하지만 본문 영역의 html에 대한 텍스트를 모두 가져온다면 필요하지 않은 텍스트들이 의외로 많이 가지고 있다.

그래서 나는 이런 것들의 제거가 필요하다고 생각했고 태그 단위로 제거하길 원했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * jsoup을 이용하여 element 제거
 * @param strHtml
* @param removeSelectorList 제거할 셀렉터 리스트
 * @return
 */
public static String removeElementByJsoup(String strHtml, List<String> removeSelectorList) {
    if (removeSelectorList != null && removeSelectorList.size() > 0) {
        // String 형태의 html 문서를 html로 변경한다.
        org.jsoup.nodes.Document html = org.jsoup.Jsoup.parseBodyFragment(strHtml);
        for (String selector : removeSelectorList) {
            // html에서 제거할 태그를 찾아 제거한다.
            org.jsoup.select.Elements els = html.select(selector);
            if (els != null && els.size() > 0) {
               for (org.jsoup.nodes.Element el : els) {
                   el.remove();
               }
           }
        }
        // 제거된 html을 스트링으로 리턴한다.
        return html.toString().trim();
    }        
    return strHtml.trim();
}
cs

위 소스에서 removeSelectorList에는 이러한 제거할 태그에 대한 셀렉터다.

(ex : 내부 글 링크, 구글 광고와 같은 것, SNS 공유 버튼 텍스트, 좋아요 댓글 버튼 텍스트 등)

이 메소드의 리턴 값은 html 태그를 가지고 있다.

그러므로 이제 html 태그를 제거하고 텍스트만 가져오면 된다.

 

2. html에서 태그를 제거하고 텍스트만 남긴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * jsoup을 이용하여 태그를 제거한 text만 수집
 * @param strHtml
 * @return
 */
public static String getElementTextByJsoup(String strHtml) {
    if (!StringUtils.isBlank(strHtml)) {
        String REGEX_NEW_LINE = "(\r\n|\r|\n)";
        String NEW_LINE = System.getProperty("line.separator");
        /**
         * 1. p, br 태그와 텍스트를 남긴다. (이외 개행상태 유지)
         * 2. br 태그를 개행한다.
         * 3. 남은 태그를 제거한다.
         */
       String cleanText = org.jsoup.Jsoup.clean(
                   org.jsoup.Jsoup.clean(
                           strHtml.replace("&nbsp;"" ")            // 공백 태그 변경
                           , "", org.jsoup.safety.Whitelist.none().addTags("br""p")
                           , new org.jsoup.nodes.Document.OutputSettings().prettyPrint(true))            // 1. 
                   .replace("<br>", NEW_LINE)                                                        // 2.
                   , "", org.jsoup.safety.Whitelist.none(), new org.jsoup.nodes.Document.OutputSettings().prettyPrint(false));    // 3.
           
       // 개행문자 통일
        cleanText = cleanText.replaceAll(REGEX_NEW_LINE, NEW_LINE);
        return StringEscapeUtils.unescapeHtml4(cleanText.trim());        // html 특수문자 제거
    }
    return null;
}
cs

위 소스에서는 몇가지 포인트가 있다.

1) 개행문자가 os에 따라 다르므로 한가지로 변경하면 차후 작업에 유리하다.

2) br, p에 대한 태그는 개행으로 유지한다. 

3) html 특수문자는 &nbsp;는 공백으로 변경하고, 나머지는 제거한다.

 

 

자세한 설명은 부족하겠으나 많은 테스트 후 나온 결과물이다.

제거할 대상 태그들에 대한 셀렉터들은 다소 사전 조사가 필요하다.

눈에 보이는 것들도 있겠으나 눈에 보이지 않는 요소들도 상당히 많다.

그러므로 2번째 소스를 먼저 사용해서 정보를 수집하고 1번 소스를 사용하면 될 것 같다.

 

 

 

728x90
반응형
728x90

몇일만에 서버 스케줄 작동 결과를 확인 했는데 빈 스케줄이 발견 되었다.

원인은 트랜잭션에 잡혀 있는 테이블들을 수정하려고 했던것.

 

Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

 

 

응급조치로 문제가 되는 스케줄을 정지시켰다.

다만 이게 완벽한 해결책이 될수 없는 것이 문제.....

너무 촘촘히 서버가 돌아가고 있는게 문제인 건데... 어떻게 해야하지? =ㅅ=;

그리고 이게 문제가 된다면 준비 중인 것도 문제가 되겠다. 고민 좀 해보자.

728x90
반응형
728x90

문제 없이 작동하던 메소드에서 에러가 난다.

원인은 파일명 또는 패스에 공백 또는 사용할 수없는 문자 포함하는 경우.

 

java.io.IOException: Invalid file path while creating and writing in a File

I am trying to create a file inside a directory but when the code executes it brings back the error 'java.io.IOException: Invalid file path'. And the code does create the directory called 'ServerU...

stackoverflow.com

 

아래는 에러 전문

java.io.FileNotFoundException: Invalid file path

at java.base/java.io.FileOutputStream.(FileOutputStream.java:229)

at java.base/java.io.FileOutputStream.(FileOutputStream.java:184)

at packagename.core.common.utils.Utils.writeFile(Utils.java:496)

at packagename.core.common.utils.Utils.getWEList(Utils.java:416)

at packagename.core.common.utils.Utils.run(Utils.java:236)

at packagename.core.schedule.service.ScheduleService.commonUWorking(ScheduleService.java:648)

at packagename.core.schedule.service.ScheduleService.startTUWorking(ScheduleService.java:589)

at packagename.core.schedule.service.ScheduleService$$FastClassBySpringCGLIB$$5a06c0f7.invoke()

at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

at packagename.core.schedule.service.ScheduleService$$EnhancerBySpringCGLIB$$397471ba.startTUWorking()

at packagename.web.BaseController.getTest(BaseController.java:115)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:568)

at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)

at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)

at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)

at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)

at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)

at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)

at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)

at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)

at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)

at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887)

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684)

at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)

at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)

at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.base/java.lang.Thread.run(Thread.java:833)

 

 

728x90
반응형
728x90

이 에러는 왜 나오는지 모르겠다.

아래 소스의 19번 라인에서 발생된 에러이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    @Transactional(rollbackOn = {Exception.class})
    public void workingProcess() {
        // 스케줄 로그를 남기기 위한 DTO 선언
        SchdScheduleLogReqDTO logReqDTO = new SchdScheduleLogReqDTO(Thread.currentThread().getStackTrace()[1].getMethodName());
        Integer instCnt = 0;
        Integer updCnt = 0;
        Integer delCnt = 0;
        try {
            List<String> pathList = mapperClass.getPathList();
            if (pathList != null && pathList.size() > 0) {
                // row insert, update 작업 진행.
                   // logReqDTO = callMethod(pathList, logReqDTO);
                   if (logReqDTO != null && logReqDTO.getInstCnt() != null && logReqDTO.getInstCnt() > 0) { // break point instCnt에 11로 수정
                       SearchSchdUReqDTO reqDTO = new SearchSchdUReqDTO();
                       reqDTO.setUseYn("Y");
                       reqDTO.setNotNullWrtUpdDt(true);
                       reqDTO.setLastUpdHours(1);
                       reqDTO.setDList(pathList);
                       List<SchdUResDTO> newUList = mapperClass.getUList(reqDTO);    // exception 발생
 
                    /* .... 중간 작업 생략 .... */
 
                   }
               }
            logReqDTO.setInstCnt(logReqDTO.getInstCnt() + instCnt);
            logReqDTO.setUpdCnt(logReqDTO.getUpdCnt() + updCnt);
            logReqDTO.setDelCnt(logReqDTO.getDelCnt() + delCnt);
            mapperClass.insertScheduleLog(logReqDTO);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
cs

에러를 재현하면 아래와 같다.

1. 테스트를 위해 12번 라인은 주석 처리 했다.

2. 14번 부터 실행하기 위해 13번 라인에서 break point를 잡고 logReqDTO의 instCnt를 11로 변경했다.

3. 19번에서 해당 에러가 발생한다.

 

해당 부분의 Array 형태라고는  18번 라인의 reqDTO.setDList(pathList); 부분 뿐이다.

또한, 동일한 값으로 해당 mybatis를 실행하기 위해서 별도의 메소드에서 따로 실행했을때는 문제가 없었다.

마지막으로 java.lang.ArrayIndexOutOfBoundsException: Index 11 out of bounds for length 1 에서 11은 

내가 임의로 넣어준 instCnt의 값과 동일하게 변하면서 에러가 난다.

 

당황해서 1시간 이상 삽질을 했는데.. 결론은 사용하지도 않는 값이 index로 값으로 사용한다는 것이다. (왜?)

해당 값을 19번 라인에서 사용되는 reqDTO에서는 쓰지도 않는데... 왜일까?

에러가 발생한 상황은 테스트를 위한 것으로 정상 실행시는 문제가 없다.

그래서 현재로서는 이 문제는 미궁에 빠진체 알 수 없게 되었다.

 

참고로 java.lang.ArrayIndexOutOfBoundsException: Index 11 out of bounds for length 1

이와 같은 에러는 일반적으로 Array에서 없는 index를 사용하려고 할 발생한다.

 

 

* 에러 전문 ===================================================================================

java.lang.ArrayIndexOutOfBoundsException: Index 11 out of bounds for length 1

at org.apache.ibatis.reflection.ParamNameResolver.getNamedParams(ParamNameResolver.java:115)

at org.apache.ibatis.binding.MapperMethod$MethodSignature.convertArgsToSqlCommandParam(MapperMethod.java:309)

at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:142)

at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)

at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:144)

at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)

at jdk.proxy2/jdk.proxy2.$Proxy136.getUrlList(Unknown Source)

at packagename.core.schedule.service.ScheduleService.workingProcess(ScheduleService.java:107)

at packagename.core.schedule.service.ScheduleService$$FastClassBySpringCGLIB$$efabd1f.invoke()

at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

at packagename.core.schedule.service.ScheduleService$$EnhancerBySpringCGLIB$$20ec79ba.getPostInfoFromTistoryBlog()

at packagename.web.BaseController.testPost9(BaseController.java:121)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:568)

at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)

at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)

at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)

at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)

at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)

at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)

at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)

at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)

at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)

at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887)

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684)

at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)

at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)

at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.base/java.lang.Thread.run(Thread.java:833)

 

 

728x90
반응형

+ Recent posts