PHP로 뭔가를 만들다 보면 "지금 이 요청이 어디서, 어떤 주소로 들어왔는지"를 알아야 할 때가 자주 옵니다. 접속 로그를 남기거나, 현재 페이지 주소를 기준으로 링크를 만들거나, GET/POST에 따라 처리를 나눌 때가 다 그렇죠. 이런 정보는 전부 $_SERVER 슈퍼글로벌 배열에 들어 있습니다.

아주 오래된 코드를 보면 같은 용도로 $HTTP_SERVER_VARS를 쓰는 경우가 있습니다. 이건 예전 방식이라 폐기(deprecated)됐다가 PHP 5.4에서 아예 없어졌어요. 옛 소스를 옮겨 올 때 이 변수가 보이면 $_SERVER로 바꿔 주면 됩니다. 비슷한 정보를 함수로 꺼내는 getenv()도 있지만, 웹 요청 정보는 $_SERVER를 쓰는 쪽이 일반적입니다.

광고

자주 쓰는 키를 한 번에 찍어 보기

키 이름을 매번 찾아보기 귀찮아서, 자주 쓰는 것만 배열에 넣고 표로 출력하는 테스트 파일을 하나 만들어 두면 편합니다. 값이 없는 키는 -로 표시합니다.

<?php
  // 자주 사용하는 것만... 그 외는 php.net에서 확인
  // http://localhost/testphp/11.php?test=paramiter&test2=para2
  // 위 URL 접속시 나오는 정보입니다.
  $indicesServer = array(
  'PHP_SELF',              // 현재 접속 주소(도메인 제외)
  'SERVER_ADDR',           // 서버 IP
  'SERVER_NAME',           // 서버 네임
  'REQUEST_METHOD',        // 요청 방식
  'QUERY_STRING',          // URL 에 있는 파라미터 반환
  'DOCUMENT_ROOT',         // 서버의 아파치 루트 디렉토리
  'HTTP_ACCEPT',           // 문서 구성 및 타입 헤더 내용
  'HTTP_ACCEPT_CHARSET',   // 캐릭터셋 헤더 내용
  'HTTP_ACCEPT_ENCODING',  // 인코딩 방식 헤더 내용
  'HTTP_ACCEPT_LANGUAGE',  // 언어 헤더 내용
  'HTTP_HOST',             // 현재 도메인
  'HTTP_REFERER',          // 현재 페이지로 오기 전 페이지 URL
  'HTTP_USER_AGENT',       // 현재 페이지 접속한 사용자 환경
  'REMOTE_ADDR',           // 현재 페이지 접속한 사용자 IP
  'REMOTE_HOST',           // 현재 페이지 접속한 사용자 호스트
  'REMOTE_PORT',           // 현재 페이지 접속한 사용자 포트
  'SCRIPT_FILENAME',       // 접속 중인 사이트의 파일명과 경로
  'SERVER_PORT',           // 접속 중인 사이트의 포트
  'REQUEST_URI'            // 현재 페이지의 URL
  ) ;

  echo '<table cellpadding="10">' ;
  foreach ($indicesServer as $arg){
    if (isset($_SERVER[$arg])){
      echo '<tr><td>'.$arg.'</td><td>'.
           $_SERVER[$arg].'</td></tr>';
    }
    else{
        echo '<tr><td>'.$arg.'</td><td>-</td></tr>';
    }
  } // foreach 문 끝
echo '</table>' ;
?>

실제로 찍어 본 값

http://localhost/testphp/11.php?test=paramiter&test2=para2로 접속했을 때 나온 결과입니다. 로컬 아파치 환경 기준이라 경로나 IP는 환경마다 다릅니다.

키 출력값
PHP_SELF /testphp/11.php
SERVER_ADDR 127.0.0.1
SERVER_NAME localhost
REQUEST_METHOD GET
QUERY_STRING test=paramiter&test2=para2
DOCUMENT_ROOT /www/
HTTP_ACCEPT text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
HTTP_ACCEPT_CHARSET -
HTTP_ACCEPT_ENCODING gzip,deflate,sdch
HTTP_ACCEPT_LANGUAGE ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4
HTTP_HOST localhost
HTTP_REFERER -
HTTP_USER_AGENT Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
REMOTE_ADDR 192.168.0.1
REMOTE_HOST -
REMOTE_PORT 55429
SCRIPT_FILENAME /www/testphp/11.php
SERVER_PORT 80
REQUEST_URI /testphp/11.php?test=paramiter&test2=para2

표를 보면 헷갈리기 쉬운 것들이 눈에 들어옵니다. PHP_SELF는 스크립트 경로만, REQUEST_URI는 쿼리스트링까지 포함한 주소 전체를, QUERY_STRING은 ? 뒤만 돌려줍니다. 현재 페이지로 되돌아오는 링크를 만들 때 파라미터를 유지하고 싶으면 REQUEST_URI, 파라미터 없이 파일 경로만 필요하면 PHP_SELF(또는 SCRIPT_NAME)를 쓰는 식으로 구분합니다.

HTTP_REFERER는 주소창에 직접 쳐서 들어오면 비어 있어서 -로 나왔고, REMOTE_HOST도 비어 있는데 이건 웹서버가 IP를 호스트명으로 역조회하도록 설정돼 있어야 채워지기 때문입니다. 대부분의 서버는 그 설정을 꺼 둡니다. HTTP_ACCEPT_CHARSET은 요즘 브라우저가 거의 보내지 않는 헤더라 비어 있는 게 정상입니다.

믿으면 안 되는 값들

HTTP_로 시작하는 키는 전부 브라우저가 보낸 요청 헤더를 그대로 옮긴 것입니다. 클라이언트가 마음대로 바꿔 보낼 수 있다는 뜻이죠. 리퍼러로 "우리 사이트에서 온 요청만 허용"하는 식의 검사는 보안 장치가 못 되고, HTTP_HOST도 조작될 수 있어서 비밀번호 재설정 메일의 링크 주소 같은 데 그대로 쓰면 곤란합니다. 그나마 REMOTE_ADDR은 TCP 연결 기준이라 조작이 어렵지만, 앞단에 프록시나 로드밸런서가 있으면 프록시의 IP가 찍힙니다.

그리고 위 예제는 값을 그대로 echo하고 있어서, 쿼리스트링이나 User-Agent에 스크립트를 넣어 접속하면 화면에 그대로 실행될 수 있습니다. 혼자 보는 테스트 파일이면 상관없지만, 관리자 화면 같은 데 붙일 거라면 출력할 때 htmlspecialchars()로 감싸 주세요.

<?php
// 출력 부분만 이스케이프를 추가한 버전
foreach ($indicesServer as $arg){
    $val = isset($_SERVER[$arg]) ? htmlspecialchars($_SERVER[$arg], ENT_QUOTES, 'UTF-8') : '-';
    echo '<tr><td>'.$arg.'</td><td>'.$val.'</td></tr>';
}
?>

여기 없는 키가 궁금하면 print_r($_SERVER); 한 줄로 전체를 찍어 보면 됩니다. 아파치냐 nginx+PHP-FPM이냐, CLI로 돌리느냐에 따라 들어 있는 키가 꽤 달라서, 서버를 옮길 때 한 번씩 확인해 두면 나중에 덜 헤맵니다.

참고: 로이데스트 - $_SERVER 접속자와 접속 페이지 관련 정보 반환