URL Parsing 내부 로직 구현
URL 파싱은 주어진 URL을 파트별로 나누는 작업입니다. URL은 다음과 같은 형식을 따릅니다.
scheme://host:port/path?query#fragment
각 부분을 나누어 추출하는 C 코드 예제를 아래에 작성하였습니다.
URL 파싱 C 코드
#include
#include
#include
#define MAX_URL_LEN 2048
// Structure to store parsed URL components
typedef struct {
char scheme[32];
char host[256];
int port;
char path[512];
char query[256];
char fragment[256];
} URL;
// Function to parse the URL
int parse_url(const char *url, URL *parsed_url) {
// Copy URL into a modifiable string
char temp_url[MAX_URL_LEN];
strncpy(temp_url, url, MAX_URL_LEN);
// Initialize parsed URL structure
memset(parsed_url, 0, sizeof(URL));
parsed_url->port = -1; // Default port to -1 if not found
// Parse the scheme (e.g., http, https)
char *scheme_end = strstr(temp_url, "://");
if (scheme_end == NULL) {
return -1; // Invalid URL (scheme not found)
}
*scheme_end = '\\\\0'; // Null-terminate scheme
strncpy(parsed_url->scheme, temp_url, sizeof(parsed_url->scheme));
char *remainder = scheme_end + 3; // Move past '://'
// Parse the host
char *host_end = strpbrk(remainder, ":/?#");
if (host_end == NULL) {
// No path/query/fragment, this is the end of the URL
strncpy(parsed_url->host, remainder, sizeof(parsed_url->host));
return 0;
}
strncpy(parsed_url->host, remainder, host_end - remainder);
// Parse the port if present
if (*host_end == ':') {
char *port_start = host_end + 1;
char *port_end = strpbrk(port_start, "/?#");
if (port_end == NULL) {
parsed_url->port = atoi(port_start); // No path/query/fragment
return 0;
}
char port_str[6];
strncpy(port_str, port_start, port_end - port_start);
port_str[port_end - port_start] = '\\\\0';
parsed_url->port = atoi(port_str);
host_end = port_end; // Update the host_end to the path/query start
}
// Parse the path if present
if (*host_end == '/') {
char *path_end = strpbrk(host_end, "?#");
if (path_end == NULL) {
// No query/fragment
strncpy(parsed_url->path, host_end, sizeof(parsed_url->path));
return 0;
}
strncpy(parsed_url->path, host_end, path_end - host_end);
host_end = path_end; // Move to the query/fragment start
}
// Parse the query if present
if (*host_end == '?') {
char *query_end = strchr(host_end, '#');
if (query_end == NULL) {
// No fragment
strncpy(parsed_url->query, host_end + 1, sizeof(parsed_url->query));
return 0;
}
strncpy(parsed_url->query, host_end + 1, query_end - (host_end + 1));
host_end = query_end; // Move to the fragment start
}
// Parse the fragment if present
if (*host_end == '#') {
strncpy(parsed_url->fragment, host_end + 1, sizeof(parsed_url->fragment));
}
return 0;
}
int main() {
const char *url = "<https://example.com:8080/path/to/resource?query=value#fragment>";
URL parsed_url;
if (parse_url(url, &parsed_url) == 0) {
printf("Scheme: %s\\\\n", parsed_url.scheme);
printf("Host: %s\\\\n", parsed_url.host);
printf("Port: %d\\\\n", parsed_url.port);
printf("Path: %s\\\\n", parsed_url.path);
printf("Query: %s\\\\n", parsed_url.query);
printf("Fragment: %s\\\\n", parsed_url.fragment);
} else {
printf("Failed to parse URL.\\\\n");
}
return 0;
}
설명:
- URL 구조체: URL의 각 부분을 저장하는 구조체 URL을 정의했습니다. 여기에는 scheme, host, port, path, query, fragment 등이 포함됩니다.
- 파싱 함수: parse_url 함수는 주어진 URL을 파싱하여 URL 구조체에 각 부분을 저장합니다.
- 포인터 사용: C 언어에서는 문자열을 처리할 때 포인터와 strpbrk, strstr 등의 함수를 사용하여 자를 찾고, 그 위치를 기준으로 문자열을 자릅니다.
- 테스트 URL: main 함수에서 파싱할 URL을 지정하고, 파싱 결과를 출력합니다 https://example.com:8080/path/to/resource?query=value#fragment와 같은 URL을 처리하며, 각 부분을 잘라내어 출력합니다.
Go에서 표준 패키지인 net/url을 사용하지 않고 URL 파싱을 직접 구현하려면, 문자열을 분석하여 URL의 각 부분을 추출해야 합니다. 이는 C언어와 유사한 방식으로, 문자열 탐색 및 구분자를 사용해 URL의 구조를 직접 분해하는 방식입니다.
아래는 Go에서 URL을 수동으로 파싱하는 코드를 작성한 예시입니다.
URL 파싱 Go 코드 (패키지 없이 직접 구현)
package main
import (
"fmt"
"strings"
)
// URL 구조체
type URL struct {
Scheme string
Host string
Port string
Path string
Query string
Fragment string
}
// URL 파싱 함수
func parseURL(rawURL string) (*URL, error) {
var parsedURL URL
// 1. 스킴 (scheme) 분리
schemeEnd := strings.Index(rawURL, "://")
if schemeEnd == -1 {
return nil, fmt.Errorf("invalid URL: scheme not found")
}
parsedURL.Scheme = rawURL[:schemeEnd]
rawURL = rawURL[schemeEnd+3:]
// 2. 호스트와 포트 분리
hostEnd := strings.IndexAny(rawURL, "/?#")
if hostEnd == -1 {
hostEnd = len(rawURL)
}
hostPort := rawURL[:hostEnd]
rawURL = rawURL[hostEnd:]
// 호스트와 포트 분리
colonPos := strings.LastIndex(hostPort, ":")
if colonPos != -1 {
parsedURL.Host = hostPort[:colonPos]
parsedURL.Port = hostPort[colonPos+1:]
} else {
parsedURL.Host = hostPort
parsedURL.Port = "" // 포트가 없을 때는 빈 문자열
}
// 3. 경로 (path) 분리
pathEnd := strings.IndexAny(rawURL, "?#")
if pathEnd == -1 {
pathEnd = len(rawURL)
}
parsedURL.Path = rawURL[:pathEnd]
rawURL = rawURL[pathEnd:]
// 4. 쿼리 (query) 분리
if len(rawURL) > 0 && rawURL[0] == '?' {
queryEnd := strings.Index(rawURL, "#")
if queryEnd == -1 {
queryEnd = len(rawURL)
}
parsedURL.Query = rawURL[1:queryEnd]
rawURL = rawURL[queryEnd:]
}
// 5. 프래그먼트 (fragment) 분리
if len(rawURL) > 0 && rawURL[0] == '#' {
parsedURL.Fragment = rawURL[1:]
}
return &parsedURL, nil
}
func main() {
rawURL := "<https://example.com:8080/path/to/resource?query=value#fragment>"
parsedURL, err := parseURL(rawURL)
if err != nil {
fmt.Println("Error:", err)
return
}
// 결과 출력
fmt.Printf("Scheme: %s\\\\n", parsedURL.Scheme)
fmt.Printf("Host: %s\\\\n", parsedURL.Host)
fmt.Printf("Port: %s\\\\n", parsedURL.Port)
fmt.Printf("Path: %s\\\\n", parsedURL.Path)
fmt.Printf("Query: %s\\\\n", parsedURL.Query)
fmt.Printf("Fragment: %s\\\\n", parsedURL.Fragment)
}
설명:
- parseURL 함수: 주어진 URL 문자열을 수동으로 파싱하는 함수입니다. 각 단계에서 URL의 일부(스킴, 호스트, 포트, 경로, 쿼리, 프래그먼트)를 찾아내고 이를 URL 구조체에 저장합니다.
- 스킴 파싱: ://를 기준으로 스킴을 분리합니다. 스킴이 없다면 잘못된 URL로 처리합니다.
- 호스트 및 포트 파싱: 호스트와 포트를 /, ?, # 중 첫 번째 구분자를 기준으로 찾아내며, 마지막 :를 찾아 포트를 구분합니다.
- 경로 파싱: 쿼리나 프래그먼트 전에 나타나는 경로를 분리합니다.
- 쿼리 파싱: ? 이후부터 # 이전까지의 부분을 쿼리로 처리합니다.
- 프래그먼트 파싱: # 이후의 부분을 프래그먼트로 처리합니다.
실행 결과:
https://example.com:8080/path/to/resource?query=value#fragment라는 URL을 입력으로 주었을 때, 출력은 다음과 같습니다.
Scheme: https
Host: example.com
Port: 8080
Path: /path/to/resource
Query: query=value
Fragment: fragment
이렇게 표준 패키지를 사용하지 않고 문자열 처리만으로도 URL을 직접 파싱할 수 있습니다.
'Interview > Network' 카테고리의 다른 글
AES 알고리즘 (0) | 2024.09.29 |
---|---|
RSA 알고리즘 (0) | 2024.09.29 |
대칭키와 비대칭키 비교 (0) | 2024.09.29 |
TLS handshake 과정 (0) | 2024.09.29 |
웹브라우저에서 도메인을 접근하기 위한 내부 트래픽 로직 (1) | 2024.09.28 |