CSRF 체크 목적으로 crumb 에러가 발생하는 상황이다.

  • CSRF (Cross-site request forgery) - 사이트 간 요청 위조 

1. host에서 jenkins접근 가능한지 확인

2. git의 webhook을 확인

Payload URL
http://내 URL /github-webhook/
Content type
application/json

ex : URL이 1.2.3.4:5000 일경우 
http://1.2.3.4:5000/github-webhook/

 

3. jenkins 설정 변경

build할 job 클릭 -> config -> build trigger -> GitHub hook trigger for GITScm polling 체크

 

3-1. GitHub hook trigger for GITScm polling이 보이지 않을경우

jenkins - Manage Jenkins- Plugins - 플러그인 설치

  • Strict Crumb Issuer Plugin
  • Git client plugin
  • Git plugin
  • GitHub plugin
  • GitHub API Plugin

3-2. 용량이 없다고 나올경우

3-2-1. 미사용 파일 삭제하기

젠킨스 설치 위치의 미사용, 캐시파일 삭제하기 

(filezilla를 사용했으며 log, .cache, caches, workspace(기존에 빌드 진행했던 내용물) 내용물 삭제하기)

 

3-2-2. 젠킨스 기준 사용 용량 줄이기

에러가 난 node 클릭 -> config -> 사진처럼 값을 바꾸기 (해당 크기보다 사용 가능한 용량이 작을경우 에러가 나오기 때문에 값을 강제로 수정, MB,GB말고 MiB, GiB이다) 

'Spring' 카테고리의 다른 글

젠킨스 로컬에서 자동화하기 - 1  (0) 2024.06.27
젠킨스 로컬에서 자동화하기 - 2  (0) 2024.06.22
Jmeter - 2  (0) 2024.06.12
Jmeter - 1  (0) 2024.06.08
배포흐름  (0) 2024.05.26

기본적인 실행 순서는 다음과 같다

홈페이지 접속 -> 로그인 -> websocket 연결 -> 종료 

다음의 형태로 진행할 예정이다.

 

테스트 생성하기

 

test plan - add - Thread Group 클릭

 

 Thread Group은 테스트의 기초설정을 담당하며 각 옵션은 다음과 같다.

Name: 테스트 계획에 대한 이름을 지정하는 필드입니다. 기본값은 "Thread Group"입니다.
Comments: 해당 Thread Group에 대한 설명이나 주석을 추가할 수 있는 필드입니다. 테스트 계획을 설명하는 데 유용합니다.

Action to be taken after a Sampler error:
Continue: 오류가 발생해도 테스트를 계속 진행
Start Next Thread Loop: 현재 스레드의 남은 작업을 중지하고 다음 루프를 시작
Stop Thread: 현재 스레드를 중지
Stop Test: 전체 테스트를 중지

Thread Properties:
Number of Threads (users): 테스트에 사용할 가상 사용자의 수를 설정 (100명으로 설정)
Ramp-up period (seconds): 모든 스레드를 시작하는 데 걸리는 시간을 설정 -> 0.01초마다 1명씩 총 100명이 스레드 실행
Loop Count: 각 스레드가 샘플러를 실행할 횟수를 설정합니다. "Infinite" 옵션을 선택하면 무한 반복됩니다.
Same user on each iteration: 체크하면 각 스레드가 반복할 때 동일한 사용자로 동작합니다.

Number of Threads (users): 100명의 가상 사용자를 생성하여 테스트를 수행합니다.
Ramp-up period (seconds): 1초 동안 모든 스레드를 시작합니다.
Loop Count: 각 스레드가 테스트 샘플러를 한 번 실행합니다.
Same user on each iteration: 동일한 스레드가 동일한 사용자로 반복 동작합니다.

 

 

주요 내용은 100명의 사용자가 0.01초마다 테스트를 진행할 예정이다.


홈페이지 접속하기

Thread Group - add - Sampler - HTTP Request 클릭

Path - 테스트할 페이지의 URL 입력
Server Name or IP - IP 입력

 

테스트 결과 확인하기

Thread Group - Add - Listener - 원하는 report 클릭 (여기선 View Result Tree 및 플러그인 3 Basic Graph의 Active Thread Over time을 사용)

View Result Tree - 테스트 상세 내역을 확인가능

Active Thread Over time - 전체 테스트 시간 확인가

 

로그인 진행하기

 

동일한 HTTP Request를 생성하며 HTTP Request를 POST로 변경, Body Data를 json형태로 입력 가능하다.

 

401에러가 나타날경우 

Add - Config Element - Http Header Mnager를 다음과 같이 설정해준다. 

CSV의 경우

만일 데이터를 동적으로 할당하고 싶은경우

Add - Config Element -CSV Data Set Config 클릭

filename은 자신이 사용할 CSV파일을 지정해준다

login은 다음과 같이 "${value}"의 형태로 사용한다

 

만약 에러가 나타날경우 추가적인 설정을 진행해줘야 한다. 에러에 대한 자세한 내용은 해당부분에서 확인 가능하다

1. DB랑 통신이 되어있는가?

2. 스프링 시큐리티 설정으로 인해 접근이 안되는가?

3. 로그인 정보가 올바른가?

view Results Tree에서 테스트 결과 확인이 가능하다


WEBSOCKET 테스트 

해당 설정은 Websocket의 설정이지만 내 방식은 STOMP를 활용하기에 조금 다른 형태로 진행한다.

 

처음 테스트를 진행했을때 계속 에러가 나왔는데 확인해보니 STOMP의 경우 URL이 변계속 연결부분이 바뀌는 문제가 있었다 (개발자도구에서 확인 가능)

 

해당 방식이 디폴트이다.

/example/stomp-endpoint/${counter}/${__RandomString(8,abcdefghijklmnopqrstuvwxyz,sessionld)}/websocket

 

  • /example/stomp-endpoint/: WebSocket 서비스의 기본 경로
자신이 만든 WebSocketConfig 에서 Endpoint 입력할것
@Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/example/stomp-endpoint")
                .setAllowedOrigins("*")
                .withSockJS();
    }
  • ${counter}: 랜덤한 index값

Jmeter - ADD - ConfigElemnet - Counter 클릭

111 부터 1씩 올리는 형태로 설정

  • ${__RandomString(8,abcdefghijklmnopqrstuvwxyz,sessionld)}: 각 연결에 대해 고유한 임의의 문자열을 생성한다 (8글자를 사용하며 랜덤으로 사용한다)
  • /websocket: WebSocket 프로토콜을 사용하여 연결을 설정

전체 depth는 다음과 같다.

 

 

 

 

결과

100개의 스레드가 10초간 실행되도록 하였을때 10초 부분까지 점진적으로 증가하다 테스트가 스레드가 종료되는 10초 이후부터 감소하는것을 확인할수 있다.

'Spring' 카테고리의 다른 글

젠킨스 로컬에서 자동화하기 - 2  (0) 2024.06.22
젠킨스 관련 에러 내용 (crumb, 용량부족)  (0) 2024.06.16
Jmeter - 1  (0) 2024.06.08
배포흐름  (0) 2024.05.26
소켓 / 웹소켓  (0) 2024.04.07

해당 포스팅에선 Jemter의 기초 설치방법 및 ngrinder 비교 포스팅만 진행할예정이며 Jmeter 실제 사용법은 Jmeter - 2 포스팅 참고할것

 

webSocket의 부하테스트를 진행해보기 위하여 테스트 도구를 검색하였고 Ngrinder 과 Jmeter 2가지를 사용해보았고 최종적으로 Jmeter를 사용하였다.

 

Ngrinder

Naver에서 개발한 오픈소스 프로젝트이다.

웹에서 lib를 jar, zip등의 형태로 다운받아 따로 라이브러리 폴더에 올리는 형태로 작업한다.

Jmeter

apache에서 개발한 테스트 툴이다.

jenkins와 연동을 지원한다.

lib를 받기 위해선 market plugin을 다운받고 여기서 필요한 lib를 다운받는 형식이다.

 

 

 

 

처음엔 Ngrinder를 사용하려고 했으나 Jmeter에 비하면 레퍼런스가 적었고 실제로 Groovy로 코드를 작성해봤으나 에러파악 및 이와 관련된 질문이 없는경우도 있어서 Jmeter로 변경하였다.

GUI의 최적화가 덜되어 가끔 렉걸리는 경우가 있었지만 무엇보다.. Jmeter가 Ngrinder보다 용량을 덜 차지한다..


Jmeter 실행

https://jmeter.apache.org/download_jmeter.cgi 
접속하여 zip 다운로드 후 압축해제

cmd -> jmeter 설치위치 이동 (apache-jmeter-5.6.3\bin) -> jmeter.bat 입력후 실행 

 

Apache JMeter - Download Apache JMeter

Download Apache JMeter We recommend you use a mirror to download our release builds, but you must verify the integrity of the downloaded files using signatures downloaded from our main distribution directories. Recent releases (48 hours) may not yet be ava

jmeter.apache.org


문제 업승ㄹ경우 화면과 같은 형태로 나타나며 

 

초기 화면은 다음과 같다.

테스트 저장시 jemter.bat이 위치한 폴더에 저장된다.

 

 

설치된 플러그인 확인 및 추가 플러그인 설치방법

https://jmeter-plugins.org/install/Install/

  1. jmeter-plugins-manager.tar 다운
  2. jmeter가 설치된 폴더로 이동
  3. lib/ext에 tar를 압축해제한 폴더를 드래그 앤 드롭 후 재시작

 

 

websocket이 테스트 목적 플러그인을 다운받았으며

2가지에서 WebSocket Samplers by Peter Doornbosch를 사용하였다

Maciej는 마지막 업데이트가 11년 전이다..

 

기본적인 설정은 끝났으니 실제 테스트는 다음 포스팅에서 진행하겠다.

'Spring' 카테고리의 다른 글

젠킨스 관련 에러 내용 (crumb, 용량부족)  (0) 2024.06.16
Jmeter - 2  (0) 2024.06.12
배포흐름  (0) 2024.05.26
소켓 / 웹소켓  (0) 2024.04.07
스프링 시큐리티  (0) 2024.02.20

1. JDK 설치

1. sudo apt install -y openjdk-11-jdk //(pom.xml에서 본인 java 버전에 맞는걸 확인)

2. java -version //버전확인용

2-1. sudo update-alternatives --config java   //만일 여러 버전이 설치되있을경우 해당 명령어로 jdk 버전선택

 

2. maven 설치

sudo apt install -y maven

mvn -version //버전확인용

 

3. 톰캣 설치

1. 톰캣 공식홈페이지의 Core : tar.gz 주소복사
2. sudo useradd -m -U -d /opt/tomcat -s /bin/false tomcat	//사용자 추가하기

3 cd /tmp	//폴더로 이동
4. curl -O https://downloads.apache.org/tomcat/tomcat-8/v8.5.100/bin/apache-tomcat-8.5.100.tar.gz
	//curl -O 1.에서 복사한 주소
5. sudo mkdir -p /opt/tomcat	//디렉토리 만들기
6. sudo tar xzvf apache-tomcat-8.5.100.tar.gz -C /opt/tomcat --strip-components=1
	//압축해제 및 설치되있던 최상단 폴더 제거
7. sudo chown -R tomcat: /opt/tomcat	//권한설정 (소유자 변경)
   sudo chmod -R 755 /opt/tomcat		//권한설정 (소유자에게 RWX)
   //R - read(읽기), W - wirte(쓰기), X - execute
8. sudo nano /etc/systemd/system/tomcat.service //톰캣 서비스 설정
9. 서비스 파일 입력하기
[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking

User=tomcat
Group=tomcat

Environment="JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64"
Environment="CATALINA_PID=/opt/tomcat/temp/tomcat.pid"
Environment="CATALINA_HOME=/opt/tomcat"
Environment="CATALINA_BASE=/opt/tomcat"
Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh

[Install]
WantedBy=multi-user.target
저장하기 (컨트롤 + O)

10. 재시작하기
sudo systemctl daemon-reload
sudo systemctl start tomcat
sudo systemctl enable tomcat
sudo systemctl status tomcat	//실행 확인하기

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 만약 새롭게 설치해야 할경우
sudo systemctl stop tomcat	//기존 실행중인 톰캣 종료
sudo systemctl disable tomcat	 //기존에 설치된 톰캣비활성화
sudo rm /etc/systemd/system/tomcat.service	//기존에 만든 service파일 삭제
sudo systemctl daemon-reload	//시스템 재시작
sudo rm -rf /opt/tomcat	//톰캣 디렉토리 삭제
sudo userdel -r tomcat	//추가한 사용자 삭제

 

확인사항

src\main\resources 여기에 존재해야 빌드가 가능하다
rasd\src\main\webapp\WEB-INF\resources 여기있으면 파일 못찾는다!.. mapper 등 모두 다!

 

'Spring' 카테고리의 다른 글

Jmeter - 2  (0) 2024.06.12
Jmeter - 1  (0) 2024.06.08
소켓 / 웹소켓  (0) 2024.04.07
스프링 시큐리티  (0) 2024.02.20
웹에서 핸드폰으로 메세지 보내기 - 2  (0) 2024.02.05

소켓

논리적인 의미의 소켓은 컴퓨터 네트워크를 경유하는 프로세스간 통신의 종착점을 의미하며 서로 네트워크를 이용해 데이터를 송수신할경우 최종적으로 거치게되는 endpoint를 뜻한다.

 

클라이언트

  1. socket() 함수를 호출하여 클라이언트 소켓을 생성
  2. connect() 함수를 호출하여 서버 소켓에 연결을 요청 - 서버의 IP 주소와 포트 번호를 사용하여 연결을 시도
  3. send() 함수를 사용하여 데이터를 서버로 전송, recv() 함수를 사용하여 서버로부터 데이터를 수신
  4. 통신이 완료되면 close() 함수를 호출하여 소켓을 닫고 연결을 종료

 

서버

  1. socket() 함수를 호출하여 서버 소켓을 생성
  2. bind() 함수를 사용하여 생성된 소켓을 특정 IP 주소와 포트 번호에 바인딩 ->서버가 특정 네트워크 인터페이스와 포트에서 요청을 수신할 수 있도록 설정
  3. listen() 함수를 호출하여 소켓을 수신 대기 상태로 변경
  4. accept() 함수를 사용하여 클라이언트의 연결 요청을 수락
  5. send() 함수를 사용하여 데이터를 클라이언트로 전송, recv() 함수를 통해 클라이언트로부터 데이터를 수신
  6. 통신이 완료되면 close() 함수를 호출하여 소켓을 닫고 연결을 종료

 

역할과 위치

소켓은 OSI 7계층 중 실제 유저들과 상호작용하는 응용 계층(application layer)과 데이터를 송수신하는 전송 계층(transport layer)사이에 위치하며, 응용 프로그램이 데이터를 송수신하기 위해 TCP/IP를 사용하는 인터페이스 역할을 담당한다.

한 프로세스는 여러 개의 소켓을 사용할 수 있으며 소켓 번호를 통해 각 소켓을 구분하며 하나의 호스트에서 여러 호스트와 동시에 통신할 수 있습니다.

또한 통신을 통해 전달되는 모든 데이터 포맷은 5-tuple 이라는 규격에 맞추어 진행되며 여기에 필요로 하는것은 크게 IP 주소와 포트 번호를 필요로한다.

  • IP 주소: 데이터를 송신할 호스트를 식별.
  • 포트 번호: 호스트 내의 특정 프로세스를 식별.
  • TCP: 데이터 전송 전에 연결을 설정하여 신뢰성 있는 통신을 제공합니다.

5-tuple

  1. 프로토콜 (Protocol)
  2. 호스트 IP 주소 (source IP address)
  3. 호스트 port 번호 (source port nunber)
  4. 목적지 IP 주소 (destination IP address)
  5. 목적지 port 번호 (destination port number)

server-client 구조

TCP/UDP 위에서 동작하므로 당연하게 server-client 통신 구조를 갖춘다.

처음에 데이터를 보내는 쪽이 client가 되고, 받는 쪽이 server가 된다. 이후에는 서로가 데이터를 송수신할 수 있다. (자세한 설명은 아래)

양방향 통신

socket은 한 쪽에서 데이터를 보내고 반대 편에서 이를 수신한 뒤 연결이 끊어지는게 아니라 양 쪽에서 실시간으로 데이터를 송수신할 수 있다. 따라서 실시간 스트리밍이나 채팅에 주로 유용하게 사용된다.

프로그래밍 언어나 운영체제에 종속적

socket은 TCP/IP 표준이 아니라 네트워크 프로그래밍 인터페이스다. 따라서 운영체제마다 사용법이 약간씩 다르며, 그 안에서 또 프로그래밍 언어마다 소켓 api 를 구현한 라이브러리가 다 다르다.

 

JAVA의 경우

import java.io.*;
import java.net.*;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);

        while (true) {
            Socket clientSocket = serverSocket.accept();
            OutputStream output = clientSocket.getOutputStream();
            PrintWriter writer = new PrintWriter(output, true);
            writer.println("Welcome to the server!");
            clientSocket.close();
        }
    }
}

 

Node의 경우

const net = require('net');

const server = net.createServer((socket) => {
  console.log('Connection from', socket.remoteAddress, 'has been established.');
  socket.write('Welcome to the server!\n');
  socket.end();
});

server.listen(8080, () => {
  console.log('Server is listening on port 8080...');
});

 

HTTP가 웹에서 데이터를 교환하기 위한 프로토콜이라고 하였다.

해당 방식은 클라이언트가 요청을 보내면 서버가 해당 요청에 따른 응답을 반환하지만 HTTP의 특성인 무연결성으로 인하여 신규 데이터가 등록되더라도 사용자가 이를 보기 위해선 항상 새로운 요청을 보내야 등록된 데이터를 가져올수 있었다. (매 요청마다 새로운 연결을 생헝하고 응답후 연결을 끊는 방식) 

그렇다면 실시간으로 채팅을 구현하기 위해선 어떤 방식을 활용해야 핳까?

 

 

웹소켓

기존 HTTP의 무연결성을 해결하여 서버와 브라우저간 양방향 통신이 가능하게 해주는 기술로 한번 연결이 설정되면 끊어질때까지 서버와 클라이언트 모두 데이터를 전송할수 있게된다

  • 저지연: 데이터를 즉각적으로 전송할 수 있다
  • HTTP 호환성: 웹소켓 연결은 HTTP 요청을 통해 시작되며, ws 또는 보안 연결인 wss 프로토콜을 사용
  • 효율적인 자원 사용: 한 번의 연결 설정으로 지속적인 데이터 교환을 할 수 있어 자원 사용이 효율적입니다. (반복적인 handshake 생략)

동작방식

  1. 연결초기화
    • HTTP 요청을 사용해 서버에 연결하며 Upgrade: websocket 헤더를 포함해 해당 요청이 웹소켓임을 알리며 이에 맞도록 http요청을 웹소켓 연결로 업그레이드
    • HTTP GET 요청에 Upgrade: websocket, Connection: Upgrade 포함되며 해당 헤더는 웹소켓 프로토콜의 업그레이드를 의미한다.
    • 서버는 HTTP/1.1 101 Switching Protocols 상태 코드와 함께 웹소켓으로 업그레이드 요청을 수락한다는 의미의 Upgrade: websocketConnection: Upgrade 헤더를 포함시킵니다.
  2. 핸드셰이크 응답
    • 서버가 요청을 수락하면 http 응답에 Upgrade: websocket 헤더를 포함해 웹소켓 프로토콜로 업그레이드 후 클라이언트에 다시 전달 (TCP 기반 프로토콜)
  3. 데이터 교환(연결된 상태)
    • 연결이 될경우 클라이언트와 서버는 네트워크 지연 없이 실시간으로 메세지를 교환할수 있으며 메세지는 텍스트 또는 바이너리 형태가 될수있다. 
  4. 연결 종료
    • 어느 한쪽이든 연결 종료가 가능하며 종료과정이 합의된 프로토콜에 따라 이뤄진다.

 

웹소켓 (WebSocket): 웹소켓은 OSI 모델의 응용 계층에 속하며 웹 애플리케이션 실시간 통신을 가능하게 하는 프로토콜이다.

HTTP를 기반으로 초기 연결을 수립한 뒤 웹소켓 프로토콜로 업그레이드하여 데이터를 교환합니다. 웹소켓 프로토콜은 HTTP와 같은 다른 응용 계층 프로토콜 위에서 동작하면서도, 그 자체로 응용 계층의 기능을 수행합니다.

'Spring' 카테고리의 다른 글

Jmeter - 1  (0) 2024.06.08
배포흐름  (0) 2024.05.26
스프링 시큐리티  (0) 2024.02.20
웹에서 핸드폰으로 메세지 보내기 - 2  (0) 2024.02.05
웹에서 핸드폰으로 메세지 보내기 - 1  (0) 2024.02.05

STOMP 설정하기 

 

환경설정

pom.xml

<!-- 스프링 웹소켓 의존성 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.3.5</version> <!-- 사용하는 스프링 버전에 맞게 조정 -->
</dependency>

<!-- 스프링 메시징 의존성 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>5.3.5</version> <!-- 사용하는 스프링 버전에 맞게 조정 -->
</dependency>

 

servlet.context.xml

xmlns:websocket="http://www.springframework.org/schema/websocket"	
xsi:schemaLocation="http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.3.xsd

 


 

config 코드

package com.study.config;

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.DefaultContentTypeResolver;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;

import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
    
    @Override
	public void configureMessageBroker(MessageBrokerRegistry registry) {
		registry.enableSimpleBroker("/topic/"); // 클라이언트가 구독할 수 있는 주소의 prefix
	    registry.setApplicationDestinationPrefixes("/app"); // 메시지를 보낼 때 사용할 prefix		
	}
    
    @Override							
    public void registerStompEndpoints(StompEndpointRegistry registry) {
    	System.out.println("초기화 : "+ registry);
        registry.addEndpoint("/stomp-endpoint").withSockJS();
    }
    
	@Override
	public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void configureClientInboundChannel(ChannelRegistration registration) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void configureClientOutboundChannel(ChannelRegistration registration) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public boolean configureMessageConverters(List<MessageConverter> converters) {
	    DefaultContentTypeResolver resolver = new DefaultContentTypeResolver();
	    resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
	    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
	    converter.setObjectMapper(new ObjectMapper());
	    converter.setContentTypeResolver(resolver);
	    converters.add(converter);
	    return false; // 기본 컨버터를 추가하지 않음
	}

	
}

 

 

jsp코드

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
  <html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WebSocket Chatting</title>
  </head>
<body>
    <div>
<button type="button" id="connect" onclick="openSocket();">대화방 참여</button>
        <button type="button" id="disconnect" onclick="closeSocket();">대회방 나가기</button>
    	<br/><br/><br/>
  		메세지 입력 : 
        <input type="text" id="sender" value="${sessionScope.id}" style="display: none;">
        <input type="text" id="messageinput">
        <button type="button" onclick="send();">메세지 전송</button>
        <button type="button" onclick="javascript:clearText();">대화내용 지우기</button>
    </div>
    <div id="messages">
    </div>
        <script src="https://cdn.jsdelivr.net/npm/sockjs-client/dist/sockjs.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/stompjs/lib/stomp.min.js"></script>
    <script type="text/javascript">
    var socket = new SockJS('/assetmanager/stomp-endpoint');
    var stompClient = Stomp.over(socket);

    var messages = document.getElementById("messages");

    function openSocket() {
        if (stompClient !== null && stompClient.connected) {
            writeResponse("WebSocket is already opened.");
            return;
        }
        var socket = new SockJS("/assetmanager/stomp-endpoint");
        stompClient = Stomp.over(socket);
		
        stompClient.connect({}, function(frame) {
            setConnected(true);
            console.log('Connected: ' + frame);
            stompClient.subscribe('/topic/greetings', function(greeting) {
                console.log('Received: ', JSON.parse(greeting.body).content);
            	showGreeting(JSON.parse(greeting.body).content);
            });
        },
        function(error) { // 연결 실패 콜백
         console.log('연결 안됨: ' + error);
     }
        );
        console.log("연결프로세스종료");
    }

    function setConnected(connected) {
        document.getElementById("connect").disabled = connected;
        document.getElementById("disconnect").disabled = !connected;
        if (connected) {
            messages.style.display = "";
        } else {
            messages.style.display = "none";
        }
    }

    function send() {
        var name = document.getElementById("messageinput").value;
        stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));
    }

    function closeSocket() {
        if (stompClient !== null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }

    function showGreeting(message) {
        var messages = document.getElementById("messages");
        messages.innerHTML += "<br/>" + message;
    }

    function clearText() {
        var messages = document.getElementById("messages");
        messages.innerHTML = "";
    }
  </script>
</body>
</html>

 

controller

@MessageMapping("/hello") // 클라이언트에서 보낸 메시지를 받을 경로
	    @SendTo("/topic/greetings") // 메시지를 다시 발행할 경로
	    public GreetingVO greeting(HelloMessageVO message) throws Exception {
	        Date now = new Date();
	        System.out.println(now);
	        return new GreetingVO(HtmlUtils.htmlEscape(message.getName()));
	    }

우리가 함수나 메서드를 호출할때 방식은 크게 2가지의 방법이 있다.

Call by Value, Call by Reference

그렇다면 2가지에 대해서 비교해보자

Call by Value

public class CallByValueExample {

    public static void addTen(int number) {
        number = number + 10; // 매개변수의 값 변경
        System.out.println("Inside addTen: " + number);
    }

    public static void main(String[] args) {
        int a = 5;
        addTen(a); // a의 값은 여전히 5
        System.out.println("In main after addTen: " + a);
    }
}
  • 정의 - Call by value 방식에서는 함수에 인자를 전달할 때, 인자의 실제 값(value)이 복사되어 함수의 매개변수로 전달됩니다.
  • 동작 방식
    • 함수가 호출될 때, 인자의 값이 새로운 메모리 위치에 복사되며 함수 내에서 매개변수의 값을 변경해도, 원래 인자의 값은 불변.
  • 특징
    • 함수 내에서 매개변수의 값을 변경해도 원본 데이터에는 영향을 미치지 않아 부수 효과(side effect)가 없다
    • 큰 데이터를 복사할 때 성능 저하가 발생우려
    • 함수 안에서 인자 값이 변경되더라도, 외부 변수 값은 변경되지 않는다.
    •  addTen(a);를 하여도 기존의 a는 변경되지 않는다

Call by Reference

public class CallByValueOfReferenceExample {

    static class Number {
        int value;
        
        Number(int value) {
            this.value = value;
        }
    }

    public static void addTen(Number number) {
        number.value += 10; // 객체의 상태 변경
        System.out.println("Inside addTen: " + number.value);

        number = new Number(30); // 매개변수에 새로운 객체 할당
        System.out.println("Inside addTen after new assignment: " + number.value);
    }

    public static void main(String[] args) {
        Number myNumber = new Number(5);
        addTen(myNumber); // myNumber 객체의 상태가 변경됨
        System.out.println("In main after addTen: " + myNumber.value); // new Number(30) 할당은 영향을 미치지 않음
    }
}
  • 정의: 함수에 인자를 전달할 때, 인자의 메모리 주소(reference)가 전달됩니다. 즉, 함수는 원본 인자를 직접 참조하게 됩니다.
  • 동작 방식: 함수가 호출될 때, 인자의 메모리 주소가 함수의 매개변수로 전달됩니다. 함수 내에서 매개변수를 통해 원본 데이터를 직접 변경할 수 있습니다.
  • 특징: 함수 내에서의 변경이 원본 데이터에 직접 반영되므로, 원본 데이터를 쉽게 수정할 수 있습니다. 그러나 이로 인해 부수 효과가 발생할 수 있으며, 원본 데이터를 실수로 변경할 위험이 있습니다.
  •  addTen(myNumber);를 진행할경우 myNumber의 값이 변경된다.

자바의 경우 call by value 방식을 사용한다.

원본 데이터를 변경할 위험이 줄어들며

 

하단의 코드는 Call By value를 실제 코드를 비교한 방법이며

callme 함수를 호출한 하여도 원본 객체인 valueTest1의 값이 변경되지 않음을 확인할수있다.

	public static void main(String[] args) {
		String valueTest1 = new String("abc");
		String valueTest2 = valueTest1;
		
		callme(valueTest1);
		
		System.out.println("#3 " + valueTest1.equals("abc")); // true
		System.out.println("#4 " + valueTest1.equals("ccc")); // false
		System.out.println("#5 " + (valueTest1==valueTest2)); // true
	}
	
	static void callme(String str) {
		System.out.println("#1 " +str.equals("abc")); // true 
		str = new String("ccc");
		System.out.println("#2 " + str.equals("ccc")); // true 
	}

 

안정성과 예측 가능성: "Call by Value"를 사용하면 함수에 전달된 값이나 참조의 복사본을 작업하게 되므로, 원본 데이터가변경될 위험이 줄어든다.

디버깅 용이성: "Call by Value" 방식에서는 함수 호출 시 매개변수의 값이나 참조의 복사본이 생성되기에 이로 인해 함수 내에서 발생하는 변경이 외부에 영향을 미치지 않아, 디버깅 시 변수의 값이 어디에서 변경되었는지 알기가 쉽다.

메모리 관리: Call by Value 방식은 함수 호출이 끝나면 매개변수로 사용된 복사본은 스코프를 벗어나 가비지 컬렉터에 의해 처리되어 메모리 누수의 위험이 줄어든다.

멀티스레드 환경에서의 안정성: Java는 멀티스레드 프로그래밍을 광범위하게 지원하는데 "Call by Value" 방식은 멀티스레딩 환경에서 공유 데이터에 대한 동시 접근으로 인한 RaceCondition을 방지할수 있다.

'JAVA' 카테고리의 다른 글

자바 주니어 질문 100건 (기초)  (0) 2024.11.02
DocumentBuilderFactory와 예제  (0) 2024.02.18
스레드풀  (0) 2023.03.07
프로세스와 스레드  (0) 2023.03.07
동일성(identity)과 동등성(equality)  (0) 2023.02.26

구글에 "고양이" 를 검색했을때 버튼이 나타나며 이것을 클릭했을때 현재 마우스 클릭위치를 기준으로 고양이 발바닥이 나타나는 이스터에그가 존재한다.

이것을 만들어볼거다...

기초 흐름

1. 사용할 img를 정한다

2. 클릭을 했을때 특정한 구역(box 등)에 나타난다

3. 랜덤하게 회전된 모습으로 출력한다.

 

조건

1. 이미지는 겹쳐지도록 만들어야한다.

2. 사이즈는 200,200으로 잡았다.

3. 구역의 사이즈는 동적페이지로 구성해야한다 (화면이 커져도 작아져도 그에 맞춰지도록)

 

enjoy.jsp

<section name="form-section">
	<form id="fileUploadForm" enctype="multipart/form-data">
		<input type="file" name="file" id="file" onchange="previewImage(this);" />
		<img id="preview" style="max-width: 200px; max-height: 200px;">
		<button type="button" onclick="uploadFile()">Upload</button>
	</form>
</section>

//이미지가 나올 컨테이너
<figure id="img-section"></figure>
<button id="loadImage">버튼을 클릭하세요</button>
<button id="clear">삭제하기</button>

<script> 
let uploadedImageUrl = null;

//업로드 이미지 미리보기
    function previewImage(input) {
        if (input.files && input.files[0]) {
            var reader = new FileReader();  
            reader.onload = function (e) {
                uploadedImageUrl = e.target.result; // 업로드된 이미지의 URL 저장
                document.getElementById('preview').src = uploadedImageUrl;
            }
            reader.readAsDataURL(input.files[0]);
        }
    }
    
//이미지 랜덤 배치하기
    document.getElementById('loadImage').addEventListener('click', function() {
        if (uploadedImageUrl) {
            const imgSection = document.getElementById('img-section');
            const img = document.createElement('img');
            img.src = uploadedImageUrl;

            // 이미지의 크기를 200x200 픽셀로 설정
            img.style.width = '200px';
            img.style.height = '200px';
            img.style.position = 'absolute';
            //양 끝 위치에 배치시 잘릴 우려가 있어 이미지 크기만큼 제외 
            const maxTop = imgSection.clientHeight - 200; 
            const maxLeft = imgSection.clientWidth - 200; 
            img.style.top = Math.floor(Math.random() * maxTop) + 'px';
            img.style.left = Math.floor(Math.random() * maxLeft) + 'px';
            // 이미지에 랜덤한 회전 각도 적용
            const rotationDegree = Math.floor(Math.random() * 360); // 0도에서 359도 사이의 랜덤한 값
            //ES6 이전 버전이라 이렇게 작성
            //ES6 이후는 밑의 주석처리 코드로 작성
            //img.style.transform = `rotate(${rotationDegree}deg)`;
            
            img.style.transform = 'rotate(' + rotationDegree + 'deg)';
            let scale = 0;
            let opacity = 0;
            const scaleStep = 0.05;
            const opacityStep = 0.05;

            function animate() {
                scale += scaleStep;
                opacity += opacityStep;

                if (scale < 1) {
                    img.style.transform = `rotate(${rotationDegree}deg) scale(${scale})`;
                    img.style.opacity = opacity;
                    requestAnimationFrame(animate);
                } else {
                    img.style.transform = `rotate(${rotationDegree}deg) scale(1)`;
                    img.style.opacity = 1;
                }
            }

            requestAnimationFrame(animate);
            imgSection.appendChild(img);
        } else {
            alert('먼저 이미지를 업로드해주세요.');
        }
    });
    
    //삭제 애니메이션
    document.getElementById('clear').addEventListener('click', function() {
    	 var imgSection = document.getElementById('img-section');
 // 높이와 투명도를 0으로 변경
    imgSection.style.height = '0px';
    imgSection.style.opacity = '0';

    // 전환 효과가 완료되면 내용 삭제
    imgSection.addEventListener('transitionend', function() {
        imgSection.innerHTML = ''; 
        imgSection.style.height = ''; 
        imgSection.style.opacity = '1'; 
    }, { once: true }); 
    });
</script>

 

CSS

#img-section {
  width: 100vw;
  height: 100vh; /* 뷰포트 높이의 80%로 설정 */
  justify-content: center; /* 가로 중앙 정렬 */
  align-items: center;
  margin: 10px;
  position: relative; /* 이미지들이 이 요소 내에서 절대 위치를 가짐 */
    overflow: hidden; /* 내부 요소가 밖으로 넘치지 않도록 함 */
  transition: height 0.5s ease, opacity 0.5s ease; /* 높이와 투명도 변화에 대한 전환 효과 적용 */
  
}

 

결과화면

 

'기타' 카테고리의 다른 글

구글 폰트 적용시키기  (0) 2024.09.17
카카오맵 api 활용하기  (0) 2024.07.21
FileZila 사용법  (0) 2024.06.27

+ Recent posts