로그인 후 원래 보던 페이지로 돌려보내기, 페이지 이동 시 검색 조건 유지하기, canonical 주소 만들기. PHP 로 웹을 만들다 보면 "지금 이 페이지 주소가 뭐지?"를 코드에서 알아내야 할 일이 꽤 자주 생깁니다. PHP 에는 location.href 같은 값이 하나로 딱 있는 게 아니라서, $_SERVER 배열의 몇 가지 값을 조합해서 씁니다.
주소를 조각별로 나눠 보면
program1472.com/php/php.php?a=123&b=456 으로 접속했다고 가정하면 각 값은 이렇게 나옵니다.
| 표현식 | 의미 | 결과 |
|---|---|---|
$_SERVER["HTTP_HOST"] |
도메인 | program1472.com |
$_SERVER["REQUEST_URI"] |
도메인 뒤 전체(경로 + 쿼리) | /php/php.php?a=123&b=456 |
$_SERVER["QUERY_STRING"] |
? 뒤에 붙은 GET 값 |
a=123&b=456 |
$_SERVER["PHP_SELF"] |
도메인 뒤에서 쿼리를 뺀 경로 | /php/php.php |
basename($_SERVER["PHP_SELF"]) |
파일 이름만 | php.php |
헷갈리는 건 REQUEST_URI 와 PHP_SELF 의 차이입니다. 앞의 것은 브라우저가 요청한 그대로라 쿼리 문자열까지 들어 있고, 뒤의 것은 실행 중인 스크립트 경로라서 쿼리가 빠집니다. 그래서 "검색 조건까지 포함해서 그대로 되돌아가기"는 REQUEST_URI, "같은 페이지로 폼을 보내되 파라미터는 새로" 같은 경우는 PHP_SELF 를 씁니다.
HTTP_HOST 는 기본 포트가 아니면 포트 번호까지 붙어서 나옵니다. 로컬에서 localhost:8080 으로 띄워 놓고 테스트하면 값이 localhost:8080 이 되니, 도메인만 비교하는 코드가 있다면 이 부분에서 어긋납니다. 그리고 명령줄(CLI)로 PHP를 실행하면 요청 자체가 없으니 HTTP_HOST, REQUEST_URI 같은 값은 아예 존재하지 않습니다. 크론으로 도는 스크립트에서 이런 값을 쓰면 경고가 나는 이유입니다.
URL 재작성을 쓰는 사이트라면
.htaccess 로 모든 요청을 index.php 하나로 보내는 구조(프런트 컨트롤러)에서는 두 값의 차이가 더 크게 벌어집니다. 사용자가 /board/notice/15 로 접속해도 실제로 실행되는 파일은 index.php 라서, PHP_SELF 는 /index.php 를 가리키고 REQUEST_URI 는 /board/notice/15 를 그대로 갖고 있습니다. 라우팅이나 "원래 보던 페이지로 돌아가기"에는 이런 구조에서 REQUEST_URI 만 의미가 있습니다. 실행 중인 파일 경로만 필요하다면 PHP_SELF 보다 SCRIPT_NAME 을 쓰는 편이 낫습니다. 뒤에 덧붙인 경로가 섞이지 않기 때문입니다.
직접 찍어 보는 테스트 페이지
서버마다 설정에 따라 값이 조금씩 다를 수 있어서, 저는 새 서버에 올리면 이런 페이지를 하나 만들어 실제 값을 먼저 확인합니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>PHP</title>
<style>
body {
font-family: Consolas, monospace;
}
</style>
</head>
<body>
<p><strong>../../../php/php.php?a=123&b=456</strong></p>
<p>
<strong>$_SERVER[ "HTTP_HOST" ] :</strong>
<?php echo $_SERVER[ "HTTP_HOST" ]; ?>
</p>
<p>
<strong>$_SERVER[ "REQUEST_URI" ] :</strong>
<?php echo $_SERVER[ "REQUEST_URI" ]; ?>
</p>
<p>
<strong>$_SERVER[ "QUERY_STRING" ] :</strong>
<?php echo $_SERVER[ "QUERY_STRING" ]; ?>
</p>
<p>
<strong>$_SERVER[ "PHP_SELF" ] :</strong>
<?php echo $_SERVER[ "PHP_SELF" ]; ?>
</p>
<p>
<strong>basename( $_SERVER[ "PHP_SELF" ] ) :</strong>
<?php echo basename( $_SERVER[ "PHP_SELF" ] ); ?>
</p>
</body>
</html>
?a=123&b=456 을 붙여 접속한 결과 화면입니다.

화면에 그대로 찍으면 안 되는 이유
위 예제는 값 확인용이라 그냥 echo 했지만, 실제 페이지에서는 이렇게 쓰면 안 됩니다. REQUEST_URI, QUERY_STRING, PHP_SELF 는 전부 사용자가 주소창에 넣은 문자열이 섞여 들어오는 값입니다. 특히 PHP_SELF 는 /php/php.php/"><script>... 처럼 파일명 뒤에 경로를 덧붙여 접속하면 그 부분까지 담기는 경우가 있어서, 폼 action 에 그대로 넣었다가 XSS 가 뚫리는 일이 흔합니다. 출력할 때는 항상 이스케이프를 거치세요.
<form action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'UTF-8'); ?>" method="post">
HTTP_HOST 도 요청 헤더에서 오는 값이라 클라이언트가 바꿔 보낼 수 있습니다. 비밀번호 재설정 메일의 링크처럼 중요한 절대 주소를 만들 때는 설정 파일에 도메인을 고정해 두고 쓰는 편이 안전합니다.
전체 주소가 필요하면 조합하면 됩니다. 프로토콜은 $_SERVER['HTTPS'] 가 비어 있지 않고 off 가 아니면 https 로 보면 되는데, 리버스 프록시 뒤에 있는 서버에서는 이 값이 비어 있어 항상 http 로 잡히기도 합니다. 그런 구성이라면 프록시가 넘겨주는 헤더를 확인하거나 역시 설정값으로 고정해 두는 게 덜 헷갈립니다.
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$currentUrl = $scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
파라미터 하나만 바꾼 주소 만들기
페이지 번호 링크처럼 "지금 주소에서 page 만 바꾼 주소"가 필요할 때는 QUERY_STRING 을 문자열로 고치는 것보다 $_GET 을 복사해 값만 바꾸고 http_build_query 로 다시 만드는 게 깔끔합니다. 기존 검색 조건은 그대로 따라오고, 한글이나 특수문자도 알아서 인코딩됩니다.
$params = $_GET;
$params['page'] = 2;
$nextUrl = strtok($_SERVER['REQUEST_URI'], '?') . '?' . http_build_query($params);
strtok 은 ? 앞부분, 즉 경로만 떼어 내는 용도입니다. 이렇게 만든 주소도 화면에 넣을 때는 위에서 말한 대로 htmlspecialchars 를 거쳐야 합니다. 특히 & 가 들어간 주소를 HTML 속성에 그대로 넣으면 엄밀히는 잘못된 마크업이 되니, 이스케이프를 습관처럼 붙여 두세요.