Jun 3, 2019 - Base64 인코딩, 디코딩하기

Base64는 64문자의 영숫자를 이용하여 멀티 바이트 문자열이나 이진 데이터를 다루기 위한 인코딩 방식.
Simple 하게 java.util.Base64 에서 제공하는 기능을 사용.
인코딩에는 java.util.Base64.Encoded#encode() 메소드, 디코딩에는 java.util.Base64.Decode#decode() 메소드를 사용.
Base64 인코딩, 디코딩을 자주하는데, 자체 라이브러리가 아닐 경우 byte 를 다시 String 으로 변환해서 테스트하는 게 귀찮아서 history로 남깁니다.

Base64
Base64.Encoder
Base64.Decoder

import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import java.nio.charset.StandardCharsets;


public class Base64EncodeDecodeSample {

    public static void main(String[] args) throws UnsupportedEncodingException {
        String target = "암호화할내용을적어주세요";      
        byte[] targetBytes = target.getBytes(StandardCharsets.UTF_8);

        // Base64 인코딩 ///////////////////////////////////////////////////
        Encoder encoder = Base64.getEncoder();

        // Encoder#encode(byte[] src) :: 바이트배열로 반환
        byte[] encodedBytes = encoder.encode(targetBytes);
        System.out.println(new String(encodedBytes));

        // Encoder#encodeToString(byte[] src) :: 문자열로 반환
        String encodedString = encoder.encodeToString(targetBytes);
        System.out.println(encodedString);

        // Base64 디코딩 ///////////////////////////////////////////////////
        Decoder decoder = Base64.getDecoder();

        // Decoder#decode(bytes[] src)
        byte[] decodedBytes1 = decoder.decode(encodedBytes);
        // Decoder#decode(String src)
        byte[] decodedBytes2 = decoder.decode(encodedString);

        // 디코딩한 문자열을 표시
        String decodedString = new String(decodedBytes1, StandardCharsets.UTF_8);
        System.out.println(decodedString);

        System.out.println(new String(decodedBytes2, StandardCharsets.UTF_8));
    }

}

참조


May 16, 2019 - IP 대역폭 필터 기능

2012년, IP를 수치로 변환, 수치를 IP로 변환하는 법에 대해 블로그를 한 경험이 있습니다.
특정 아이피가 왔을 때 해당 대역이 어떤 지역에 속한지 분석하여 필터링 하는 로직이었습니다. ip 대역폭을 계산해야하기 때문에 long 형으로 변환하여,

public Long ipToInt(String ipAddr) {
		String[] ipAddrArray = ipAddr.split("\\.");

		long num = 0;
		for (int i=0;i<ipAddrArray.length;i++) {
			int power = 3-i;
			/*
			 * i의 값으 범위는 0~255 사이이며, 그 값을 256으로 나눈 나머지 값에
			 * 256을 power의 값인
			 * 1~9 사이는 2,
			 * 10~99 사이는 1,
			 * 100 이상은 0 의 값에 누승하여 num 값에 더함
			 */
			num += ((Integer.parseInt(ipAddrArray[i])%256 * Math.pow(256,power)));
		}
		return num;
	}

해당 대역폭에 속한 IP인지를 판단하는 로직이었습니다.
추후, 해당 로직이 조금더 간편하게 변경되었던 걸로 기억하는데,

/**
 * Returns the long version of an IP address given an InetAddress object.
 *
 * @param address the InetAddress.
 * @return the long form of the IP address.
 */
private static long bytesToLong(byte [] address) {
    long ipnum = 0;
    for (int i = 0; i < 4; ++i) {
        long y = address[i];
        if (y < 0) {
            y+= 256;
        }
        ipnum += y << ((3-i)*8);
    }
    return ipnum;
}

회사에서는 InetAddress 클래스를 별도로 구성하여, 이런 형태로 변경되었던 것으로 기억합니다.

참고주소로 보면, 조금더 디테일한 소스를 볼 수 있습니다.

public static final long[] IP_RANGE = { 16777216L, 65536L, 256L };
/**
* long형태의 아이피 정보를 "."(127.0.0.1과 같은)으로 구분된 아이피 정보로 변환하여
* 반환한다.
* @param address
* @return
*/
public static String longToIp(long address) {
String ip = "";
for (int i = 0; i < 4; i++) {
if (i < 3) {
ip += (address / IP_RANGE[i]) % 256 + ".";
} else {
ip += address % 256;
}
}
return ip;
}
/**
* 특정 IP를 "."을 기준으로 파싱한 String[]으로 받아 long IP로 반환한다.
* @param address
* @return
*/
public static long ipToLong(String[] address) {
if (address == null || address.length < 4) {
return 0L;
}
long ip = 0L;
for (int i = 0; i < 4; i++) {
if (HoverStringUtils.isDigit(address[i])) {
if (i < 3) {
ip += IP_RANGE[i] * HoverStringUtils.toDigit(address[i]);
} else {
ip += HoverStringUtils.toDigit(address[i]);
}
} else {
return 0L;
}
}
return ip;
}
/**
* 특정 아이피(IP)를 long으로 변환하여 반환한다.
* @param ip
* @return
*/
public static long ipToLong(String ip) {
return ipToLong(getBytesByInetAddress(ip));
}
/**
* 특정 아이피의 값을 byte[]받아 long으로 반환한다.
* @param address
* @return
*/
public static long ipToLong(byte[] address) {
if (address == null || address.length < 4) {
return 0L;
}
long ip = 0L;
for (int i = 0; i < 4; ++i) {
long y = address[i];
if (y < 0) {
y += 256;
}
ip += y << ((3 - i) * 8);
}
return ip;
}
/**
* 특정 아이피를 byte[]로 변환하여 반환한다.
* @param ip
* @return
*/
public static byte[] getBytesByInetAddress(String ip) {
InetAddress addr = null;
try {
addr = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
return new byte[0];
}
return addr.getAddress();
}

급한 기능을 추가할 때, 옛날에 사용했던 내용들을 정리해두면, 역시 편하다는 것을 다시한번 느꼈습니다.

May 16, 2019 - Cookie Header Size 오류

5월 15, 2019 10:34:56 오전 org.apache.coyote.http11.AbstractHttp11Processor process
정보: Error parsing HTTP request header
 Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level.

서버에 해당 값 오류가 발생하기 시작했습니다.

헤더 정보를 파싱하다 발생한 오류입니다. 파싱을 실패하는 이유는 여러가지 존재하지만, 현재 시스템이 cookie를 많이 사용하는 기반이기 때문에, cookie 설정을 확인했습니다.

tomcat 의 server.xml 을 보면,

<Connector  connectionTimeout="20000"
       port="8080"
       protocol="HTTP/1.1"
       redirectPort="8443"
       maxHttpHeaderSize="8192"
       maxThreads="150"
       minSpareThreads="25"
       enableLookups="false"
       acceptCount="100"
       disableUploadTimeout="true"
       maxPostSize="0"
       URIEncoding="UTF-8"/>

에서 확인할 수 있습니다. (local의 connector를 예제로 적었습니다. ) 여기서, maxHttpHeaderSize 를 수정하면 되는데, 예전에 해당 값을 늘렸었습니다.

그래서 원인을 못찾고 있다가, 실제 메시지 요청에서도 400 bad request 가 발생하는 현상을 확인하게 되었습니다.

175.213.173.54 - - [17/May/2019:07:59:47 +0900] "GET /rtb/xxx HTTP/1.1" 400 - "https://googleads.g.doubleclick.netxxx" "Mozilla/5.0 (Linux; Android 9; SM-G965N Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.128 Whale/1.0.0.0 Crosswalk/23.69.590.22 Mobile Safari/537.36 NAVER(inapp; search; 592; 10.3.2)" 0ms "IP_info: 175.213.173.54.58834 , fp: - ,Start_Time: 2018081901"

400 bad request 발생 조건 중에 하나가 cookie 의 예정된 길이를 초과했을 경우에 발생하는 것을 확인할 수 있었습니다. 그래서 다시 확인해보니,

http (80) 은 수정이 되어있었는데 https(443) 은 인증키를 바꾸는 과정에서 누군가, 해당 maxHttpHeaderSize 를 지운 것으로 확인되어, 해당값을 늘리면서 해결할 수 있었습니다.

         <Connector port="443" keystoreFile="keystore_path"                
                keystorePass="password" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="4000" SSLEnabled="true" scheme="https" secure="true"
               clientAuth="false" connectionTimeout="8000" maxHttpHeaderSize="40960" />

maxHttpHeaderSize=”40960” 로 설정하여, 문제를 해결했습니다.


참조