Dec 17, 2021 - iOS netrwork library 인 alamofire 에서 내는 에러

responseSerializationFailed(reason: 
Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(error: 
Error Domain=NSCocoaErrorDomain Code=3840 "Unexpected end of file during string parse 
(expected low-surrogate code point but did not find one). around line 1, column 340." 
UserInfo={NSDebugDescription=Unexpected end of file during string parse 
(expected low-surrogate code point but did not find one). around line 1, column 340., 
NSJSONSerializationErrorIndex=340}))
() $R0 = {}

아이폰에서 이모지

뒷글자 3자리를 자르는 작업시, 이모지가 정상적으로 완성되지 않음. 웹, 안드로이드는 깨진글자가 그대로 동작하나, 아이폰은 json 자체가 파싱이 안되는 현상이 있음.

자바 1.8부터 제공하는


new StringBuilder().appendCodePoint(
  emostring.codePointAt(emostring.offsetByCodePoints(0, 1))).toString()

copePointAt을 이용하고자했는데, 그냥 BreakIterator.getCharacterInstance() 를 이용하여 처리했습니다. (회사의 테크리더 팀장님이 공유해주심. )


public class GraphemeStringProxy {
    private final String originString;
    private final List<String> parsedString;

    public GraphemeStringProxy(String originString) {
        this.originString = originString;
        this.parsedString = splitChars(originString);
    }

    private List<String> splitChars(String str) {
        List<String> parsed = new ArrayList<>();

        BreakIterator it = BreakIterator.getCharacterInstance();
        it.setText(str);
        int index1 = 0;
        int index2 = it.next();
        while (index2 != BreakIterator.DONE) {
            parsed.add(str.substring(index1, index2));
            index1 = index2;
            index2 = it.next();
        }

        return parsed;
    }

    public int length() {
        return parsedString.size();
    }

    public boolean isEmpty() {
        return parsedString.isEmpty();
    }

    public String substring(int startIndex, int endIndex) {
        StringBuffer sb = new StringBuffer();
        parsedString.stream().skip(startIndex).limit(endIndex - startIndex).forEach(sb::append);
        return sb.toString();
    }
}

위와 같이 최종적으로 처리함.

글자 수를 정확하게 카운트 하는 법 [참고주소] : https://engineering.linecorp.com/ko/blog/the-7-ways-of-counting-characters/

Dec 13, 2021 - locust 부하 테스트

부하테스트를 jmatter 를 사용해서 처리하는데,
SRE팀에서 locust 를 적용하여 간략하게 작한 한 내용을 정리함.

참고주소 : (https://locust.io/)

pip3 install locust
locust --version

locust 작성 문법

해당 소스는 외부 에러를 발생했을 때,
서버가 내려가는 현상을 확인하여, 해당 현상 재현을 위한 부타테스트.


from locust import HttpUser, TaskSet, task, between, constant, SequentialTaskSet
from locust.contrib.fasthttp import FastHttpUser

class MetricsTaskSet(SequentialTaskSet):
  _body = None
  _body_delivery_possible = None
  _header_authoriztion = None
  _test_set_number = 0


  def on_start(self):
    print('on_sart is called on task class!', self._test_set_number)

  def on_stop(self):
    print('on_stop is called on task class!', self._test_set_number)

  def setup(self):
    print('setup is called on task class!')

  def teardown(self):
    print('teardown is called on task class!')

  @task
  def case_1(self):

    print(f'[{self._test_set_number}] Case #1 go')

    self.client.get(
      f'/c:%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255fboot.ini%23vt/test',
      name=f'/c:%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255fboot.ini%23vt/test',
      headers=self._header_authoriztion
    )

  @task
  def case_2(self):

    print(f'[{self._test_set_number}] Case #2 go')

    self.client.get(
      f'/c:.....//.....//.....//.....//.....//.....//etc/passwd%23vt/test',
      name=f'/c:.....//.....//.....//.....//.....//.....//etc/passwd%23vt/test',
      headers=self._header_authoriztion
    )


class MetricsLocust(FastHttpUser):
  tasks = {MetricsTaskSet}
  wait_time = constant(1)

  def setup(self):
    print('MetricsLocust setup is called on locust class !')

  def teardown(self):
    print('MetricsLocust teardown is called on locust class!')

locust 실행

locust --host={테스트할도메인} --locustfile=./{실행파일}.py

부하 테스트 결과는 외부 공격에 의해 fluent 가 OOM 이 발생함.
fluent 는 로그성 컨테이너이므로, 해당 컨테이너가 내려가더라도 java application 이 중지되면 안되는 것을 확인하여, 테스트 종료.

해당 TASK 안에 컨테이너가 2개가 올라가있음 사이드카로 fluent가 동작되고 있음.
Essential container 의 필수값 옵션에 의해 fluent가 죽으면 자동으로 JVM 컨테이너도 강제 재시작됨.

Oct 13, 2021 - kcp res_msg

KCP 에서 본인인증 성공 시, 보내는 메시지가 EUC-KR 로 인코딩되서 오는데,

서버의 스프링부트가 자동으로 UTF-8 로 인코딩되면서 발생하는 오류.

res_msg=%C1%A4%BB%F3%C3%B3%B8%AE

해당값을, 그대로 복사해서 인코딩 작업을 하게 되면,

String message = "%C1%A4%BB%F3%C3%B3%B8%AE";
String encodeData = URLDecoder.decode(message,"EUC-KR");
System.out.println("URL 인코딩 : "+encodeData);

‘정상처리’라는 글을 볼 수 있으나, 실제 넘어오면 이상한 인코딩 (흡사 UTF-8로 인코딩했을 때 발생하는 오류처럼 보이는 메시지가 보입니다.

URL 인코딩 : ����ó��

원인은, 스프링부트에서 form POST 방식(application/x-www-form-urlencoded)으로 데이터를 보낸 경우 RequestBody는 자동으로 UTF-8로 urlEncoding 되기 때문입니다.

spring.http.encoding.force=false
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CharacterEncodingFilter;
 
@Configuration
public class filterConfig {
    
    @Bean
    public FilterRegistrationBean encodingFilterBean() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setForceEncoding(true);
        filter.setEncoding("UTF-8");
        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns("/xxxxx/*");
        return registrationBean;
    }
}
 

참고 주소 : https://m.blog.naver.com/PostView.nhn?blogId=duco777&logNo=220605479481&proxyReferer=https:%2F%2Fwww.google.com%2F

12/13 결과적으로 서버에서 KCP측에 한번더 검증 API 를 호출하여 서버에서 안전하게 인코딩처리함. (이 문제는 프론트에서 처리된 KCP 인코딩 문자를 백엔드에서 변환하여 처리하려다보니 생긴 이슈였음.)