URL 인코딩 알아보기 (feat. 파이썬 구현)

skyepodium·2022년 11월 13일
0

1. 살다보면

웹 개발 하다보면, 이렇게 % 가 들어간 URL을 보게됩니다.
https://velog.io/@skyepodium/URL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84

대충 경험으로 URL 인코딩 되었구나 하고 넘어갔는데, 왜하는걸까

2. URL 인코딩

1) 정의

URL 인코딩은 URL에 문자를 표현하는 문자 인코딩 방법입니다.

URL에는 아스키만 사용 가능하기 때문에,

  • 아스키가 아닌 문자는 유니코드 인코딩 후 % + 16진수로 변환하고 (한글, 일본어 등)

  • 아스키 문자이면서 예약된 문자는 원래의 목적으로 사용될 수 있도록 16진수 변환합니다. ("!*'();:@&=+$,/?#[]")

  • 예약된 문자에 포함된 슬래시('/') 는 url의 다른 부분을 구분하는 역할을 합니다.
    (참고: https://www.rfc-editor.org/rfc/rfc3986 - 주의: 전부 다 영어, 굳이 안봐도 좋은듯)

2) url 인코딩 안하는 경우

url 인코딩 안하는 경우 axios에서는 제대로 요청하지 못하고, python reuqets 모듈을 요청이 가능한데 다 외우고 다닐것 아니면 인코딩 해줍시다.

const axios = require('axios')

const originalUrl = 'https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현'
const encodedUrl = encodeURI(originalUrl)

const httpGetRequest = async (requestedUrl) => {
    try {
        await axios.get(requestedUrl)
        console.log(`requestedUrl: ${requestedUrl} - 요청성공`)
    } catch (err) {
        console.log(`requestedUrl: ${requestedUrl} - 에러`)
    }
}

httpGetRequest(originalUrl)
// requestedUrl: https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현 - 에러
httpGetRequest(encodedUrl)
// requestedUrl: https://velog.io/@skyepodium/URL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84 - 요청성공

3. 온라인 툴

여러가지가 있는데요, 사이버 쉐프가 제일 편한것 같아요 공유하기도 편하고 자동으로 알고리즘 추천도 해줍니다.
사이버 쉐프 링크: https://gchq.github.io/CyberChef/#recipe=URL_Decode()&input=aHR0cHM6Ly92ZWxvZy5pby9Ac2t5ZXBvZGl1bS9VUkwtJUVDJTlEJUI4JUVDJUJEJTk0JUVCJTk0JUE5LSVFQyU5NSU4QyVFQyU5NSU4NCVFQiVCMyVCNCVFQSVCOCVCMC1mZWF0Li0lRUQlOEMlOEMlRUMlOUQlQjQlRUMlOEQlQUMtJUVBJUI1JUFDJUVEJTk4JTg0Cg

4. 프로그래밍 언어별 API 사용법

예약된 문자열 인코딩 하는 경우/인코딩 하지 않는 경우 분기처리

1) JavaScript

const originalUrl = 'https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현'

// 1) 예약 문자 인코딩 안함
var encodedUrl = encodeURI(originalUrl)
console.log('encodedUrl: ', encodedUrl)
// encodedUrl:  https://velog.io/@skyepodium/URL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84

var decodedUrl = decodeURI(encodedUrl)
console.log('decodedUrl: ', decodedUrl)
// decodedUrl:  https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현


// 2) 예약 문자 인코딩 함
var encodedUrl = encodeURIComponent(originalUrl)
console.log('encodedUrl: ', encodedUrl)
// encodedUrl:  https%3A%2F%2Fvelog.io%2F%40skyepodium%2FURL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84

var decodedUrl = decodeURIComponent(encodedUrl)
console.log('decodedUrl: ', decodedUrl)
// decodedUrl:  https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현

2) python3

from urllib.parse import unquote, quote

originalUrl = 'https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현'
print("originalUrl: ", originalUrl)
# https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현

print()

# 1) 예약 문자 인코딩 안함
encodedUrl = quote(originalUrl, safe='/%:@&=+$,!?*\'()')
print("encodedUrl: ", encodedUrl)
# https://velog.io/@skyepodium/URL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84

# URL 디코딩
decodedUrl = unquote(encodedUrl)
print("decodedUrl: ", decodedUrl)
# https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현

print()

# 2) 예약 문자 인코딩 함
encodedUrl = quote(originalUrl, safe='')
print("encodedUrl: ", encodedUrl)
# https%3A%2F%2Fvelog.io%2F%40skyepodium%2FURL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84

# URL 디코딩
decodedUrl = unquote(encodedUrl)
print("decodedUrl: ", decodedUrl)
# https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현

5. 구현

python3

예약된 문자열 인코딩 하는 경우, 인코딩 하지 않는 경우를 분기처리 했습니다.

  • 원래 URL
    https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현

  • 예약된 문자 인코딩 하는 경우
    https%3A%2F%2Fvelog.io%2F%40skyepodium%2FURL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84

  • 예약된 문자 인코딩 하지 않는 경우 (진짜 벨로그 주소 같아졌다)
    https://velog.io/@skyepodium/URL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84

class urlparser:
    def __init__(self):
        # 예약되지 않은 문자
        self.UNRESERVED_TEXT_SET = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~")
        # 예약된 문자
        self.RESERVED_TEXT_SET = set("!*'();:@&=+$,/?#[]")

    def encode(self, url: str, is_reserved_text_encode: bool) -> str:
        encoded = []

        for char in url:
            # 1) 아스키 문자이고, 예약되지 않은 문자는 그대로 넣습니다.
            if char in self.UNRESERVED_TEXT_SET:
                encoded.append(char)
            # 2) 아스키 문자이고, 예약된 문자는 16진수로 넣습니다.
            elif char in self.RESERVED_TEXT_SET:
                # 예약된 문자도 모두 인코딩 하는 경우
                if is_reserved_text_encode:
                    encoded.append("%{:02X}".format(ord(char)))
                # 예약된 문자를 인코딩 하지 않는 겨웅 그대로 넣습니다.
                else:
                    encoded.append(char)
            # 3) 아스키가 문자가 아니면, UTF-8로 인코딩후 2자리씩 끊어서 대문자로 넣습니다.
            else:
                encoded_str = char.encode('utf-8').hex()
                for i in range(0, len(encoded_str), 2):
                    encoded.append(f"%{encoded_str[i:i + 2].upper()}")

        return "".join(encoded)

    def decode(self, url: str) -> str:
        decoded = []

        # 1) %로 시작하는 문자열을 찾습니다.
        i = 0
        while i < len(url):
            if url[i] == '%':
                hex_str = url[i + 1:i + 3]
                decoded_str = chr(int(hex_str, 16))
                # 1) 아스키 문자이고, 예약된 문자인 경우
                if decoded_str in self.RESERVED_TEXT_SET:
                    decoded.append(decoded_str)
                    i += 3
                # 2) 아스키가 아닌 문자인 경우 3바이트 끊어서 디코딩합니다.
                else:
                    hex_str = url[i:i + 9].replace("%", "")
                    decoded_str = bytes.fromhex(hex_str).decode('utf-8')
                    decoded.append(decoded_str)
                    i += 9
            # 3) 아스키 문자이고, 예약되지 않은 문자는 그대로 넣습니다.
            else:
                decoded.append(url[i])
                i += 1

        return "".join(decoded)


url_parser = urlparser()

original_url = "https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현"
print('원래 url:', original_url)
# 원래 url: https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현

print()

# 1) 예약된 문자열 인코딩하는 경우
encoded_url = url_parser.encode(original_url, True)
print('1) 예약된 문자열 인코딩하는 경우: ', encoded_url)
# 인코딩 url: https%3A%2F%2Fvelog.io%2F%40skyepodium%2FURL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84

decoded_url = url_parser.decode(encoded_url)
print('디코딩 url:', decoded_url)
# 디코딩 url: https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현

print()

# 2) 예약된 문자열은 인코딩 하지 않는 경우
encoded_url = url_parser.encode(original_url, False)
print('2) 예약된 문자열은 인코딩 하지 않는 경우: ', encoded_url)
# 인코딩 url: https%3A%2F%2Fvelog.io%2F%40skyepodium%2FURL-%EC%9D%B8%EC%BD%94%EB%94%A9-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-feat.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B5%AC%ED%98%84

decoded_url = url_parser.decode(encoded_url)
print('디코딩 url:', decoded_url)
# 디코딩 url: https://velog.io/@skyepodium/URL-인코딩-알아보기-feat.-파이썬-구현
profile
callmeskye

0개의 댓글