스타일을 바꿨는데 예전 모습이 보이면 캐시부터 의심하기 쉽다. 하지만 다른 파일을 수정했거나 새 주소를 일부 페이지에만 넣은 경우도 있다. 과거 인수인계의 버전 표기 주의를 읽고 현재 홈페이지의 응답을 대조하는 작은 검사로 발전시켰다.

문서가 어느 스타일 파일을 읽는지 먼저 찾는다

로컬 편집기에서 연 파일이 공개 문서가 읽는 파일인지 먼저 확인한다. 문서의 link 요소에 있는 주소와 쿼리, 여러 스타일 파일의 순서를 읽는다. 같은 이름의 파일이 다른 폴더에 있으면 파일명만 비교해서는 맞는 위치를 찾기 어렵다. 새 모듈은 실제 문서 주소를 기준으로 상대 경로를 풀고 참조 순서를 남긴다.

이 모듈은 HTML에 명시된 stylesheet 링크를 읽는다. 자바스크립트가 나중에 추가한 스타일이나 CSS 안의 import까지 모두 추적하는 브라우저 도구는 아니다. 외부 주소는 구별해 표시하며 검토하지 않은 주소를 자동으로 따라 읽지 않는다. 문서 참조와 실제 브라우저 실행 결과를 구별해 어느 단계가 아직 남았는지 설명한다.

공개 응답의 바이트를 선택한 출력과 대조한다

서버가 정상 응답했다고 해도 원하는 버전의 파일인지 확인해야 한다. 새 검사에서는 응답 상태와 CSS 형식, 실제 바이트의 해시를 읽어 선택한 로컬 출력과 비교한다. 이번 사이트의 대표 화면과 글 목록에서 세 스타일 파일을 각각 확인했고 여섯 응답 모두 로컬 출력과 같았다. 확인한 것은 그 시점의 그 파일이다.

해시가 같으면 파일 전달은 맞지만 화면의 최종 모양까지 같은 것은 아니다. 다른 규칙의 우선순위나 미디어 쿼리, 글꼴과 자산, 스크립트가 바꾼 클래스가 남아 있을 수 있다. 반대로 해시가 다르면 브라우저 디자인을 다시 만지기 전에 출력 폴더와 업로드 버전부터 좁혀 볼 수 있다. 검사 결과가 다음 판단의 출발점이 된다.

코드와 구현 살펴보기

참조 목록과 CSS 응답을 읽는 검사 함수

"""Read stylesheet references and inspect exact same-origin response bytes.

No browser cache reset, purge or publishing operation is performed.
"""
import hashlib
from html.parser import HTMLParser
from urllib.parse import urljoin,urlsplit

class StylesheetLinks(HTMLParser):
    def __init__(self):
        super().__init__(convert_charrefs=True);self.links=[]
    def handle_starttag(self,tag,attrs):
        attributes=dict(attrs)
        if tag=='link' and 'stylesheet' in attributes.get('rel','').lower().split() and attributes.get('href'):
            self.links.append(attributes['href'])

def stylesheet_urls(html,page_url):
    parser=StylesheetLinks();parser.feed(html)
    page=urlsplit(page_url)
    if page.scheme!='https' or page.username or page.password:raise ValueError('Explicit HTTPS page required')
    result=[]
    for reference in parser.links:
        resolved=urljoin(page_url,reference);resource=urlsplit(resolved)
        same_origin=(resource.scheme,resource.hostname,resource.port)==(page.scheme,page.hostname,page.port)
        result.append({'url':resolved,'same_origin':same_origin,'query':resource.query})
    return result

def probe_stylesheet(url,session,expected_sha256=None,max_bytes=2*1024*1024):
    parsed=urlsplit(url)
    if parsed.scheme!='https' or parsed.username or parsed.password:raise ValueError('Credential-free HTTPS resource required')
    response=session.get(url,timeout=30,allow_redirects=False,stream=True)
    with response:
        if response.status_code!=200:
            return {'url':url,'status':response.status_code,'redirect':response.headers.get('Location'),'checked_bytes':False}
        parts=[];size=0
        for part in response.iter_content(65536):
            size+=len(part)
            if size>max_bytes:raise ValueError('Resource exceeds inspection size')
            parts.append(part)
        sha=hashlib.sha256(b''.join(parts)).hexdigest()
        return {'url':url,'status':response.status_code,'content_type':response.headers.get('Content-Type'),'cache_control':response.headers.get('Cache-Control'),'etag':response.headers.get('ETag'),'age':response.headers.get('Age'),'edge_cache_status':response.headers.get('CF-Cache-Status'),'sha256':sha,'expected_matches':sha==expected_sha256 if expected_sha256 else None,'checked_bytes':True}

버전 주소를 바꿀 때 모든 참조를 함께 본다

과거 작업 기록에는 쿼리 버전을 바꿔야 한다는 주의와 여러 페이지에 같은 값을 반영해야 한다는 조건이 남아 있다. 현재 원본 HTML 서른다섯 개를 읽었을 때 그 테마 파일을 참조하는 페이지는 서른세 개였다. 기록의 페이지 수를 모든 파일 수로 단정하지 않고 실제 참조가 있는 범위를 따로 셌다.

쿼리나 파일 이름을 바꾸면 다른 자원 주소가 되지만 실제 캐시 설정이 그 차이를 어떻게 사용하는지도 확인해야 한다. 새 주소를 만들고도 예전 주소를 읽는 하위 페이지가 남으면 문제가 계속될 수 있다. 실제 내용과 연결된 파일명이나 빌드 해시를 사용하는 구조에서도 최종 HTML이 그 새 자산을 가리키는지가 마지막 확인이다.

캐시 헤더는 근거로 읽고 모양의 원인으로 단정하지 않는다

Cache-Control과 ETag, Age와 엣지 캐시 상태는 응답을 이해하는 자료다. 어느 계층에서 어떤 조건으로 응답했는지 판단할 때 도움이 되지만 그것만으로 현재 브라우저가 화면에 사용한 파일을 알 수는 없다. 새 함수는 이런 값을 기록하되 정상인지 잘못인지 임의로 하나의 점수로 묶지 않는다.

응답 검사에는 인증 헤더를 넣지 않은 별도 세션을 사용하고 다른 곳으로 이동하는 응답은 자동으로 따라가지 않았다. 외부 주소와 이동 경로는 먼저 검토해야 한다. 캐시를 모두 지우거나 운영 자산을 다시 배포하는 변경은 이 읽기 전용 검사에 포함되지 않는다. 현재 자료를 읽는 단계와 운영 상태를 바꾸는 단계를 구별한다.

확인한 파일과 아직 보지 않은 화면을 구별한다

현재 검사 일곱 개는 여섯 CSS 응답의 바이트와 형식, 참조 순서와 외부 주소 구별을 확인했다. 새 브라우저의 캐시를 조작하거나 최종 스타일 계산을 읽은 검사는 아니다. 기존 Studio 작업을 보존하면서 확인할 수 있는 범위를 근거로 남겼다. 파일이 같다는 결과가 화면 검수 완료로 바뀌지 않게 한다.

실제 적용에서는 문제가 보이는 정확한 페이지와 폭을 정하고 사용한 참조, 응답과 계산된 스타일을 이어서 읽는다. 이전 CSS가 남은 문제인지 새 CSS가 다른 규칙에 덮이는 문제인지 구별한 뒤 수정한다. 나는 캐시라는 말로 모든 원인을 덮기보다 다음 확인이 무엇인지 좁혀 주는 작은 도구를 남기려 한다.