요약
kcgi는 웹 입력을 격리된 자식 프로세스에서 해석해 C 웹 앱의 위험을 줄이는 라이브러리입니다.
kcgi는 BSD.lv 프로젝트가 관리하는 C/C++용 CGI·FastCGI 라이브러리입니다. 작고 안전하며 코드를 점검하기 쉬운 것을 목표로 하고, 2025년 11월에 1.0.1판이 나왔습니다.
왜 중요한가
- 믿을 수 없는 웹 입력을 다루는 코드를 따로 떼어 가두므로, 해석 단계의 버그가 본 프로그램까지 번지기 어렵습니다.
- 운영체제마다 다른 격리 기능을 한 API로 감싸, 여러 유닉스 계열에서 같은 코드로 쓸 수 있습니다.
핵심 내용
- 쿼리 문자열·쿠키·멀티파트 입력의 해석과 검증은 샌드박스에 갇힌 자식 프로세스가 맡고, 결과만 소켓으로 넘깁니다.
- 격리에는 리눅스의 seccomp, OpenBSD의 pledge, FreeBSD의 capsicum, 맥의 sandbox_init을 씁니다.
- 대신 비용이 따르는데, CGI에서는 프로세스 생성이, FastCGI에서는 데이터 전달이 주된 부담입니다.
- HTTP 인증, 자동 압축, HTML·JSON·XML 출력, CORS 도우미, 페이지 단위 라우팅도 들어 있습니다.
- ISC 라이선스이며, 회귀 테스트와 AFL 퍼징(무작위 입력으로 버그를 찾는 시험)으로 품질을 관리합니다.
HN 반응
- C로 웹 백엔드를 짜는 것을 두고, 어떤 언어든 정도의 차이라는 쪽과 세그폴트 하나가 서버 전체를 멈춘다는 반론이 맞섰습니다.
- C/C++가 Node.js보다 수십 배 빠르다는 주장에는, DB 조회와 템플릿 처리가 대부분인 앱에서는 이득이 작다는 반박이 나왔습니다.
혹시 놓친 분들을 위해 알려드리면, 하루 이틀 전에 C로 CGI를 써서 웹 애플리케이션을 만드는 이야기가 HN에 올라왔습니다.
https://news.ycombinator.com/item?id=50058528
(이 의사(擬似) 상식 부활 열풍 속에서, 누군가는 적어도 perl, 그러니까 CGI 언어의 원조를 슬쩍이라도 언급해 줘야 하는 것 아닙니까...)
TIMTOWTDI 만세! :)
아, 2000년 이전에 Perl 덕분에 웹에다 저지를 수 있었던 온갖 아이디어들이란.
대부분 끔찍했습니다. 보안이 뚫리는 것도 많았고요. 거의 전부 완전히 난독화된 상태로 짜였죠. 누가 남이 읽으라고 Perl을 쓰겠습니까?
끔찍했습니다.
그리고 찬란했습니다.
무슨 말인지 압니다. 네, 그랬죠.-
(둘 다요) :)
제가 예전에 TodoMVC를 C로 구현했을 때가 좀 떠오르네요. 저는 그때 fcgi를 썼습니다: https://github.com/julian-goldsmith/todomvc-c
이 글타래에서는 다들 C로 웹 애플리케이션을 만드는 것의 문제점을 지적하려 들겠죠.
최근 Talk Python 팟캐스트를 한번 들어 보세요. CPython 핵심 개발자한테 Python은 메모리 안전하다고 했더니 그분 대답이 "그래요?"였습니다.
이제는 이 문제를 좀 더 세련되게 바라봐야 합니다. 우리 하드웨어는 근본적으로 안전하지 않고, 그 위에 만드는 것도 모두 근본적으로 안전하지 않습니다. 더 낫고 더 못한 정도의 차이가 있을 뿐이고, 그게 전부입니다. 중요한 건 일에 쏟는 애정과 엄밀함과 정성뿐입니다.
원하는 대로 하세요. C로 짠 백엔드도 타당하고, LISP로 짠 백엔드도 타당합니다.
그렇다고 해서 개발 속도를 중시하고 해킹당하기 싫을 때 웹 백엔드로 C가 Common Lisp만큼 타당한 선택이 된다는 뜻은 아닙니다. Fossil SCM 정도라면 (어느 정도) 정당화되지만, 평범한 웹사이트라면 훨씬 덜하죠. 높은 성능이 필요하면 더 정당화되고, 특히 2006년이라면 더 그렇습니다. C 라이브러리를 바인딩으로 가져다 쓸 여유가 있다면 덜 정당화되고요. 이런 식입니다.
관점을 바꿔 보려고 C 대신 x86-64 어셈블리를 넣어 보겠습니다. AsmBB( https://board.asm32.info/ )는 두말할 것 없이 멋진 물건입니다. 전자상거래 사이트를 같은 방식으로 개발하는 것도 타당할까요?
"정도의 차이가 있을 뿐이니 결국 뭐든 괜찮다"는 일반적인 패턴이 있습니다. 이걸 알아채고 그 세이렌의 노래를 뿌리치는 게 중요합니다.
https://www.greaterwrong.com/w/fallacy-of-gray
물론이죠, 안 될 게 뭐 있나요? 제 결제를 깔끔하고 안전하게 처리해 주기만 한다면 안 될 이유가 없잖아요?
처리해 주기만 한다면요.
왜 못 하는데요?
엔터프라이즈 웹 백엔드를 날것의 C로 짜지 말아야 할 큰 이유는, 프로그래밍 오류의 가장 흔한 증상인 세그폴트가 프로그램 전체를 중단시킨다는 점입니다. 한 사람이 버그 하나를 만들었는데 그 버그가 터지면 백엔드 전체가 죽습니다.
엔터프라이즈 코드베이스에서 (일정, 숙련도, 기대치가 제각각인) 많은 사람이 일할수록 버그 위험은 커집니다. 따라서 "백엔드 전체가 사라지는" 크래시의 위험도 시간이 갈수록 커집니다. 고수준 언어(HLL)의 큰 장점은 프로그램 전체를 중단시키는 버그의 위험이 훨씬 낮다는 것입니다. "let it crash" 철학을 가진 Erlang조차도, 더 큰 프로그램 안에서 영향을 받는 코드의 경계까지만 죽게 두자는 이야기이지, 모든 트랜잭션을 중단하고 모든 자원을 다시 초기화해 처음부터 재시작하자는 이야기가 아닙니다.
CGI 같은 방식은 피해 범위를 요청 하나의 핸들러로 가둬서 이 문제를 조금 완화합니다(공유 커넥션 풀, 파일 핸들, 시작 시간 같은 부분에서 꽤 심각한 복잡도, 성능, 연산 비용을 치르고요). 하지만 해결하지는 못합니다. 자동 재시작 서비스나 오케스트레이터 같은 것들도 마찬가지입니다.
물론 세그폴트가 나도 중단하지 않기로 할 수는 있습니다만, 그건... 배짱이 두둑하다고 해 두죠.
어셈블리는 C와 비교해도 안전장치가 거의 없어서, 규모가 커질수록 올바른 프로그램을 짜기가 더 어렵습니다. 전자상거래 웹 앱은 온라인에 떠 있는 큰 프로그램이고 공격자에게도 매력적인 표적입니다. 언어 선택 때문에 비로소 가능해지는 버그까지 포함해 버그를 막으려면 더 많은 규율과 경험과 도구가 필요합니다. (예를 들어 어셈블리는 구조화된 제어 흐름이 막아 주는 종류의 제어 흐름 버그를 열어 놓습니다. 이름과 스코프가 있는 변수를 쓰지 않으니 데이터 손상도 더 쉽게 일어나고요.)
"할 수 있느냐"의 문제가 아니라 좋은 결과가 나올 가능성이 얼마나 되느냐의 문제입니다.
저 사람은 못 한다고 주장하니까요. 대충 그런 얘기입니다.
C/C++/Rust를 안 써서 성능을 얼마나 놓치고 있는지 사람들은 모릅니다. uWebSockets를 받아서 데모 서버를 하나 띄워 보세요. Node.js 서버보다 10~50배는 빠릅니다. 정말 말 그대로 성능 차이가 그만큼 극적입니다.
Objective-C는 지금도 이런 용도에 딱 좋은 지점입니다. 제대로 다룰 줄 안다면요.
Smalltalk(Python/Ruby)의 편의성 대부분을 C의 속도 대부분과 함께 얻을 수 있습니다. 사실 정말 잘 하면 둘을 같이 활용해서 양쪽보다 더 나은 결과를 낼 수 있어요. 동시에요.
그렇다고 C의 불편함에 Python/Ruby의 성능을 합친 결과물을 만들어 내는 사람이 없다는 뜻은 아닙니다. 그걸 볼 때마다 저는 어안이 벙벙합니다. 대체... 어떻게... 뭘 한 거죠?
제게는 여기서 Right Thing™이 늘 뻔하고 간단해 보였습니다. 일부러 망치려고 공들이지만 않으면 괜찮다는 거죠. 사람마다 다를 수 있습니다.
홍보: 제 책에 이 좋은 조합을 얻는 방법이 꽤 자세히 나와 있습니다. [1]
또 홍보: 이걸 제대로 하기 쉽게 만드는 것이 Objective-Smalltalk 4단계 중 2단계입니다. [2][3]
[1] https://www.amazon.com/Cocoa-Objective-C-Performance-Tuning-Developers/dp/0321842847
[2] https://blog.metaobject.com/2019/12/the-4-stages-of-objective-smalltalk.html
[3] https://objective.st/
아, Web Objects!
글쎄요, Objective-C 웹 서비스에서 누가 null 체크를 제대로 안 해서 세그폴트가 나면 모든 요청과 트랜잭션이 중단되고 프로그램이 죽습니다. Objective-C(그리고 프레임워크에 따라 어느 정도는 C++)가 C보다 고수준 언어의 편의를 많이 제공하긴 해도, 이 꽤 큰 단점은 똑같이 안고 있습니다.
개발자가 아주 적고 아주 숙련된 프로젝트라면 이게 통할 수도 있습니다. 하지만 팀이 크거나 팀원이 자주 바뀌는 프로젝트에서는 이 단점이 아프게 물어뜯습니다.
사람들도 실제로는 압니다. 다만 소프트웨어를 내놓는 데 있어서 그게 가장 중요한 건 아니라고 판단했을 뿐입니다. Node와 Python이 가장 인기 있는 생태계인 데는 이유가 있습니다.
그게 간단하니까요. 품질은 상관없고요?
성능 ≠ 품질
Node/Bun도 제대로 스케일하면 얼마나 빠를 수 있는지 알면 놀라실 겁니다. Bun을 쓴다면 이미 uWebSockets를 쓰고 있는 거고요. 애플리케이션이 하는 일이 데이터베이스 쿼리와 템플릿 렌더링 정도라면, 이런 컴파일 언어에서 얻는 이득이 실제로는 그리 크지 않을 수도 있습니다.
요즘 해커뉴스 사람들 중 상당수가 스택오버플로 느낌을 풍기기 시작했어요... 어째서인지 그런 사람들은 남들이 뭘 쓸 수 있고 뭘 쓰면 안 되는지를 자기들이 정할 수 있다고 생각하더라고요.
그런 사람들한테는 엿이나 먹으라고 하겠어요 :mid-finger: 이모지.
C++로 웹앱을 짜 볼 만큼 용감한 분 계신가요?
네, 제 커리어 대부분을 그걸로 먹고 살았습니다. 내일 다시 하라고 해도 하겠습니다.
Java와 .NET이 뜨기 전에는 그게 정석이었습니다. 그러다 "어차피 병목은 데이터베이스인데 누가 신경 써" 하면서 느린 인터프리터 언어들로 대체됐죠.
참고로 PHP와 Perl에도 C 코드가 꽤 들어 있었고, Apache/IIS 플러그인과 C, C++ 확장도 충분히 있었습니다.
그런데 이제 "스크립트 언어가 뭐 어때" 하던 언어들이 전부 JIT 컴파일러를 달거나 Go와 Rust로 다시 쓰이고 있습니다.
저는 C를 조금 가지고 노는데, fast cgi를 다루려고 찾아본 라이브러리들은 도저히 못 참겠더군요. 하나같이 속을 알 수 없고, 뭔가 소켓을 넘겨주면 그걸 알아서 다뤄야 하는 부분에서 계속 막혔는데 끝내 이해하지 못했어요.
라이브러리 생각을 접고 그냥 스펙대로 코딩하니까 훨씬 수월해지더라고요, ㅋㅋ 알고 보니 비교적 평범한 소켓 프로그래밍 프로젝트였습니다. 취미 코드이긴 하지만요.