컴파일할 때 "머리를 먼저 보내고 응답 주체를 보내야 합니다"를 검증합니다.


모티프


나는 노드로 간단한 서버 사이드 응용 프로그램을 만들고 싶다.
const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.write('Hello World\n');
  res.end();
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});
이 코드가 뭘 하는지 정확히 알 수 있어요. 노드를 모르는 사람도 다소나마 알아맞힐 수 있죠.그러면 HTTP의 응답은 처음에 상태 줄이 되고 그 다음에 머리가 되며 마지막에 응답 주체의 순서에 따라 보내는 프로토콜을 말한다. 예를 들어 다음과 같다.
HTTP/1.0 200 OK
Content-Type: text/plain

Hello World
그런 프로토콜이기 때문에 응답 주체를 보낸 후 헤더를 보낼 수 없습니다.예를 들어 다음과 같이 코드를 변경해 봅시다.
  res.write('Hello World\n');
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end();
변경된 코드에 write에 응답 주체를 쓴 후 setHeader에 헤더를 썼습니다.이 동작을 실행하면 서버가 이상 실행으로 사망합니다.
http_outgoing.js:371
    throw new Error('Can\'t set headers after they are sent.');
    ^

Error: Can't set headers after they are sent.
    at ServerResponse.setHeader (_http_outgoing.js:371:11)
    at Server.http.createServer (C:\dev\nodehttptest\test.js:9:7)
    at emitTwo (events.js:106:13)
    at Server.emit (events.js:194:7)
    at parserOnIncoming (_http_server.js:563:12)
    at HTTPParser.parserOnHeadersComplete (_http_common.js:99:23)
따라서 setHeaderwrite 앞에 써야 한다.
...'조심해'가 뭐야!너 바보야!'조심해'이런 마음가짐이 정말 실수를 막으면 아무도 힘들지 않아요!자동차의 액셀러레이터와 브레이크를 잘못 밟지 말라고 하면 불행한 사고가 줄어들 것 같습니까?그리고 실수는 부주의하거나 지식이 부족한 사람만 저지르는 것이 아니다.누구나 피로와 수면 부족으로 머리가 비정상적이어서 알 수 없는 코드를 쓸 때가 있다.빈번한 변경으로 인해 복잡하고 기이한 코드는 단순한 오류라도 쉽게 찾을 수 없다.오류에 대한 효과적인 대책은'주의해야 한다'는 식의 정성이 아니라 기계가 오류를 발견하면 자동으로 보고하는 기관을 준비하는 것이다.
따라서 사이트 서버 측면 프레임워크Hyper의 방법을 간단하게 소개한다.Hyper 컴파일할 때'헤더를 다 쓴 후에 응답체를 쓴다'는 조건이 충족되는지 확인할 수 있다.

Hyper를 통한 해결


Hyper의 Hello World는 다음과 같습니다.이 코드는 PureScript의 코드이며 코드에 대한 자세한 내용은 신경 쓸 필요가 없습니다.느낌으로 읽으세요1.
main = runServer defaultOptionsWithLogging {} do 
    writeStatus statusOK
    contentType textPlain
    closeHeaders
    toResponse "Hello, Hyper!" :>>= send
    end
writeStatus statusOK는 노드의 res.statusCode = 200 조작에 해당하고 contentType textPlain 대응res.setHeader('Content-Type', 'text/plain')이다.따라서 이 코드는 당연히 정상적으로 컴파일할 수 있지만, 아래처럼 이 코드를 다시 써 보세요.
main = runServer defaultOptionsWithLogging {} do 
    toResponse "Hello, Hyper!" :>>= send
    writeStatus statusOK
    contentType textPlain
    closeHeaders
    end
send에 응답 주체를 보낸 후writeStatus에 쓰기 상태가 잘못된 것이다.이것은 분명히 잘못된 것이지만, 컴파일하면 컴파일러가 다음과 같은 오류 정보를 보낼 것이다.컴파일러가 이런 오류를 발견하여 사고의 발생을 방지하였다.
Error found:
in module Main
at src\Main.purs line 15, column 13 - line 19, column 16

  Could not match type

    StatusLineOpen

  with type

    BodyOpen


while trying to match type t6 StatusLineOpen
  with type t2 BodyOpen
while checking that expression writeStatus statusOK
  has type Middleware t0
             { request :: t1
             , response :: t2 BodyOpen
             , components :: t3
             }
             t4
             t5
in value declaration main
잘못된 정보는 언뜻 보면 뜻이 분명하지 않지만 자세히 보면 형식적인 오류일 뿐이다.컴파일러가 특별한 정적 분석을 하고 있다는 것은 아니다.Could not match type StatusLineOpen with type BodyOpen 위에서 말한 바와 같이 컴파일러가 말한 것은 함수가 제공하는 매개 변수의 유형StatusLineOpen일 뿐 함수가 요구하는 유형BodyOpen에 부합되지 않는다.원리적으로 그뿐. 이런 유형의 오류에 대한 자세한 내용을 이해하려면 말이 길어지기 때문에 멈춰야 한다는 인덱스 모나드(Indexed Monad) 구조를 배워야 한다.또 모나도야!
이번에 쓴 코드가gist에 실렸다.
  • https://gist.github.com/aratama/1b3b9a2c1672f4ecfdf4183820cef28e
  • 최후


    이런 사소한 함정을 쓸 때 주의를 기울이면 쉽게 외면할 수 있지 않겠느냐는 의견도 있다.학습 표두순의 문제와 Indexed Monad 등 추상적인 기능의 비용을 천평에 두고'학습 비용과 맞지 않는다'는 의견도 있다고 생각한다.
    확실히 이 함정만 생각하면 그렇다고 생각해요.문제는 현실 코드에는 이런 작은 함정이 많이 숨겨져 있다는 점이다.퓨어스크립트처럼 언어를 사용하는 사람들은 함정마다 사소하지만 이를 무시하고 방치하지는 않는다.왜 그렇게 말할까. 이렇게 무시당하는 단순한 함정이 조만간 한데 모여 복잡도를 높이고 머지않아 많은 번거로운 문제가 생길 수 있다는 것을 우리는 알고 있기 때문이다.작은 함정이기 때문에 퓨어스크립트 같은 언어는 놓치지 않고 근본적인 처리를 해주며 이런 지루한 문제로 개발자의 머릿속에 고민을 가득 채우지 않는다.바로 컴파일러가 개발자를 대신해서 이런 무료한 오류를 검출할 수 있기 때문에 개발자는 원래의 문제를 해결하는 데 집중할 수 있다.
    만약 한 작가가 프로그램 라이브러리를 만들었다면, 1000명의 개발자가 그것을 사용했을 것이다.그 프로그램 라이브러리에는 작은 함정이 하나 있는데, 저자는 ReadMe와 API 참고서에 그 프로그램 라이브러리의 주의사항을 썼다.1000명의 개발자가 그 ReadMe를 읽지도 않고 읽어도 소홀할 때가 있다. 그곳에서 수백 명의 개발자가 그 모든 실수로 수십 분을 낭비했다.이 가능하다, ~할 수 있다,...그 개발자는 그 시간을 낭비한 후에 ReadMe와 참고서에 주의사항이 있다는 것을 발견하고 나쁜 것은 작가가 아니라 자신이라는 것을 알게 되었다.근데 그게 형식만 틀렸다면 어떻게 됐을까요?컴파일할 때의 형식 오류는 함수 매개 변수 형식 오류입니다.기계는 확실히 관건으로 문제점을 지적하고 개발자도 API 참고를 통해 한꺼번에 문제를 이해할 수 있다.1000명의 말단 개발자들이 각자의 노력으로 반드시 주의해야 할 부분은 라이브러리 개발자가 조금만 노력하면 문제의 본질을 매우 간단한 것으로 고쳐 1000명이 구할 수 있다는 것이다.
    가능한 한 컴파일할 때 프로그래밍에 있는 모든 함정을 검사합니다. 이것이 바로 PureScript와 같은 언어와 Hyper와 같은 프레임워크의 목표입니다.퓨어스크립트나 하이퍼 같은 소품들이 상세히 따지기 시작하면 복잡한 부분이 있지만, 지금까지 각 개발자가'조심'을 해왔고, 하이퍼라는 프레임이'조심'의 결과를 대신했다.절대로 먹구름을 복잡하게 하는 것은 아니다.이밖에도'어차피 시행 시 오류가 발생하니 컴파일 오류가 필요 없다'는 생각도 들고...이를 설명하면 또 화제가 길어진다(약
    이번에는 특별히 기술에 대한 상세한 설명을 제어했다.어떻게든 자세한 것을 알고 싶다면 Hyper와purescript-indexed-monad의 코드를 보십시오.그러나 이번에 소개된 하이퍼의 기법을 다른'보통'프로그래밍 언어로 구현하는 것은 불가능하다.함수형 프로그래밍의 기교를 파악하고 일반적인 프로그래밍 언어로 활용하는 것은 함수형 프로그래밍의 입문을 익힌 사람(그리고 입문만 물어뜯고 함수형 프로그래밍의 전체를 익힌 줄 아는 사람)만 쉽게 품은 환상이다.하스켈, 퓨어스크립트, 스칼라 등 진정한 함수형 프로그래밍 언어로 옮겨 안전한 세상에서 편하게 살 것인가, 아니면 자바, 자바스크립트처럼'보통'프로그래밍 언어로 함정과 인접해 살 것인가는 오직 한 가지 방법뿐이다.
    Hyper는 아직 젊은 프레임이지만 그 방향성과 PureScript의 목표가 완전히 일치하고 지금도 적극적으로 개발하고 있어 유망한 프레임이라고 생각한다.PureScript의 진정한 네트워크 서버 측면 프레임워크는 단지 한 손으로 셀 수 있는 것과 같다.필자는 PureScript의 사용자가 더 많고 이렇게 안전한 프레임워크도 많이 성장하기를 바란다.

    참고 문헌


  • hyper.wickstrom.tech Hyper의 공식 웹 사이트입니다.로고 귀엽다
  • 사실 더 짧게 쓸 수도 있지만 아까 자바스크립트의 예와 상대적으로 지루하게 쓰기 위해서다.또한 가져오기 목록과 주석이 너무 길어서 본질적인 코드만 발췌했다. 

    좋은 웹페이지 즐겨찾기