TIL: TypeScript로 강력한 형식의 HTTP 헤더 가져오기

목표



저는 백엔드 프레임워크 개발자입니다. TypeScript로 작성되었습니다. 내가 하고 싶은 것:
  • 실제 요청 객체( http.IncomingMessage )를 내 사용자에게 숨기기
  • 그러나 요청 시 사용자에게 HTTP 헤더에 대한 액세스 권한을 제공합니다( http.IncomingHttpHeaders).
  • IntelliSense(자동 완성)를 제공하여 사람들이 사용하려는 헤더를 더 쉽게 찾을 수 있도록 합니다.
  • 헤더에 유형이 없는지 컴파일 타임 검사를 제공합니다.
  • 사용자가 사용할 수 있는 헤더를 제한하지 마십시오. 따라서 헤더 목록은 서비스에서 확장할 수 있어야 합니다.

  • 그 모든 것이 가능하다는 것이 밝혀졌습니다.

    구현


    http.IncomingHttpHeaders 인터페이스를 고려하십시오.

    interface IncomingHttpHeaders {
        'accept-patch'?: string;
        'accept-ranges'?: string;
        'accept'?: string;
        
        'warning'?: string;
        'www-authenticate'?: string;
        [header: string]: string | string[] | undefined;
    }
    


    헤더 이름이 하드 코딩되어 있지만 문제는 다음과 같습니다.
  • 은 해당 목록을 확장하는 방법을 제공하지 않습니다.
  • 인덱스 서명을 제공합니다. 즉, 모든 유형의 안전성이 창 밖으로 사라집니다.

  • 따라서 사용자로부터 실제 요청을 숨기기 위해 Context라는 클래스가 있고 각 요청에 대한 처리기에게 해당 인스턴스를 나눠줍니다.

    export class Context {
        constructor(private req: http.IncomingMessage) { }
        
        getHeader(name: ?) {
            return req.headers[name];
        }
    }
    
    


    우리가 하고자 하는 것은 ? 대신에 어떤 종류의 유형을 도입하여 하드 코딩된 http.IncomingHttpHeaders의 헤더만 허용하도록 하는 것입니다. 이를 "알려진 키"라고 부를 것입니다.

    또한 사용자가 이 목록을 쉽게 확장할 수 있기를 바랍니다.

    문제 1



    인터페이스에 인덱스 서명이 있기 때문에 simpletype StandardHeaders = keyof http.IncomingHtppHeaders을 사용할 수 없습니다. 이 서명은 StandardHeaders 모든 것을 수락하여 자동 완성 및 컴파일 시간 검사가 작동하지 않습니다.

    솔루션 - 인터페이스에서 색인 서명을 제거하십시오. TypeScript 4.1 이상에서는 키 재매핑을 허용하고 TypeScript 2.8 이상에는 조건부 유형이 있습니다. 여기서는 4.1 버전만 제공합니다.

    
    type StandardHeaders = {
        // copy every declared property from http.IncomingHttpHeaders
        // but remove index signatures
        [K in keyof http.IncomingHttpHeaders as string extends K
            ? never
            : number extends K
            ? never
            : K]: http.IncomingHttpHeaders[K];
    };
    
    


    그러면 색인 서명이 제거된 http.IncomingHttpHeaders 사본이 제공됩니다.
    ‘a’ extends stringtrue 이지만 string extends ’a’false 라는 사실에 근거합니다. number 도 마찬가지입니다.

    이제 다음을 수행할 수 있습니다.

    type StandardHeader = keyof StandardHeaders;
    


    이것이 VSCode가 생각하는 것입니다StandardHeader.



    알려진 헤더만 있는 멋진 유형 리터럴. getHeader(name: StandardHeader)에 연결하고 사용해 보겠습니다.



    자동 완성이 작동하고 거기에 잘못된 것을 입력하면 컴파일이 중단됩니다.



    문제 2.



    우리는 프레임워크입니다. 이 헤더 집합은 매우 좁기 때문에 사람들에게 확장할 수 있는 기능을 제공해야 합니다.

    이것은 이전 것보다 해결하기가 더 쉽습니다. Context 제네릭을 만들고 몇 가지를 추가해 보겠습니다.
  • 제네릭을 문자열 유형 리터럴로 제한
  • 합리적인 기본값을 제공

  • export class Context<TCustomHeader extends string = StandardHeader> {
        constructor(private req: http.IncomingMessage) { }
        
        getHeader(name: StandardHeader | TCustomHeader) {
            return req.headers[name];
        }
        
    }
    


    이제 사용자는 다음과 같이 작성할 수 있습니다.

    const ctx = new Context<'X-Foo' | 'X-Bar'>(...);
    const foo = ctx.getHeader('X-Foo');
    const bar = ctx.getHeader('X-Bar');
    


    그리고 해당 헤더를 자동 완성합니다.



    또한 컴파일 시간 검사에 포함합니다.



    추가 개선 사항



    우리는 프레임워크이기 때문에 사용자가 Context 클래스의 인스턴스를 직접 생성하지 않고 배포합니다. 따라서 대신 클래스ContextHeaders를 도입하고getHeader(header: StandardHeader)를 제네릭 메서드headers< TCustomHeader extends string = StandardHeader>: ContextHeaders<StandardHeader | TCustomHeader>로 교체해야 합니다.

    그것은 독자를 위한 연습으로 남겨둡니다 =).

    좋은 웹페이지 즐겨찾기