http — HTTP/1.1 클라이언트 구현
http — HTTP/1.1 클라이언트 구현
Tcl에서 웹 서버에 요청을 보내 데이터를 가져와야 할 때가 있어요. GET, POST, HEAD를 지원하고 프록시 설정까지 갖춘 http 패키지가 그 역할을 해요.
본문
사용 준비
package require http ?2.9?
http 패키지는 RFC 7230~7235에 정의된 HTTP/1.1 프로토콜의 클라이언트 쪽을 구현해요(RFC 2616을 대체). HTTP/1.1의 GET, POST, HEAD 연산을 구현하고, 방화벽을 통과하기 위한 프록시 호스트 설정을 지원해요. Safesock 보안 정책과 호환돼서 신뢰할 수 없는 애플릿이 제한된 호스트 집합에서 URL을 가져오는 용도로도 쓸 수 있어요. ::http::register로 커스텀 소켓 명령어를 제공하면 HTTPS 같은 추가 HTTP 전송 프로토콜을 지원하도록 확장할 수도 있어요.
내보내는 명령어: http 네임스페이스는 config, formatQuery, geturl, quoteString, register, registerError, reset, unregister, wait를 내보내요. 반면 cleanup, code, data, error, meta, ncode, size, status는 내보내지 않아요.
주요 동작
::http::geturl은 HTTP 트랜잭션을 수행해요. 옵션에 따라 GET, POST, HEAD 중 하나로 동작해요. 반환값은 트랜잭션의 토큰(token) 인데, 이 값은 ::http 네임스페이스 안에 있는 트랜잭션 상태 정보를 담는 배열의 이름이기도 해요.
-command 옵션을 지정하면 HTTP 작업이 백그라운드로 진행돼요. ::http::geturl은 HTTP 요청을 만든 뒤 즉시 반환하고, 트랜잭션이 끝나면 콜백을 호출해요. 그러려면 Tcl 이벤트 루프가 활성화되어 있어야 해요. Tk 애플리케이션에선 항상 그렇지만, 순수 Tcl 애플리케이션에선 ::http::geturl 뒤에 ::http::wait를 호출해 이벤트 루프를 시작할 수 있어요. -command 옵션이 없어도 이벤트 큐는 사용되므로, http::geturl이 도는 동안 임의의 명령어가 처리될 수 있어요.
구성 명령어 ::http::config
::http::config는 프록시 서버, 포트, HTTP 요청에 쓰는 User-Agent 이름을 설정하고 조회해요.
-accept mimetypes— 요청의 Accept 헤더. 기본은*/*(모든 문서 타입 수용). 쉼표로 구분한 mime 타입 패턴 목록을 줄 수 있어요(예:image/gif, image/jpeg, text/*).-pipeline boolean— 영속 소켓에서 HTTP/1.1 트랜잭션을 파이프라인으로 처리할지. 기본은1.-postfresh boolean— POST 메서드 요청이 항상 새 소켓을 쓰도록 해http::geturl의-keepalive옵션을 무시할지. 기본은0.-proxyhost hostname— 프록시 호스트 이름. 빈 문자열이면 URL 호스트에 직접 접속.-proxyport number— 프록시 포트 번호.-proxyfilter command—::http::geturl중 주어진 호스트에 프록시가 필요한지 판단하는 콜백. 호출 시 호스트 이름 인자가 하나 추가돼요. 프록시가 필요하면 프록시 서버와 프록시 포트를 담은 두 요소 리스트를, 아니면 빈 리스트를 반환해요. 기본 필터는-proxyhost·-proxyport값이 비어 있지 않으면 그 값을 반환해요.-repost boolean— 영속 연결에서 POST 요청이 서버가 연결을 반쯤 닫아서 실패했을 때 어떻게 할지.true면 자동 재시도,false면 재시도를 위해 사용자 확인을 받도록 애플리케이션에 맡겨요.true는 특정 조건에서만 써야 해요. 기본은0.-urlencoding encoding—::http::formatQuery·::http::quoteString이 x-url-encoded URL을 만들 때 쓰는 인코딩. 기본은 RFC 2718이 정한utf-8. http 2.5 이전엔 불특정이었고, 빈 문자열({})을 지정하면 그 동작으로 돌아가요. 다만iso8859-1을 권장해요(비 라틴-1 문자 처리 시 오류를 내지 않는 비슷한 동작).-useragent string— HTTP 요청의 User-Agent 헤더 값. 안전하지 않은 인터프리터에서 기본값은 OS와 http·Tcl 버전에 따라 다르며 예를 들면Mozilla/5.0 (Windows; U; Windows NT 10.0) http/2.9.0 Tcl/8.6.9예요. 안전한 인터프리터는 OS를 알 수 없어 기본적으로 Windows 10 값에 현재 http·Tcl 버전 번호를 써요.-zip boolean—true면 기본적으로 요청이Accept-Encoding: gzip,deflate,compress헤더를 보내고,false면 보내지 않아요. 두 경우 모두 개별 요청에서http::geturl의-headers옵션으로 커스텀 Accept-Encoding 헤더를 주면 재정의할 수 있어요. 기본은1.
요청 명령어 ::http::geturl
::http::geturl은 패키지의 핵심 프로시저예요. -query 옵션은 POST, -validate 옵션은 HEAD 동작을 만들고, 그 외에는 GET을 수행해요. -command 옵션으로 완료 시 불릴 콜백을 지정하지 않으면 트랜잭션이 끝날 때까지 블로킹돼요. 주요 옵션:
-binary boolean— URL 데이터를 강제로 바이너리로 해석할지. 보통은 자동 판별돼요(텍스트 콘텐츠 타입으로 시작하지 않거나 콘텐츠 인코딩이 gzip·compress이면 바이너리로 간주).-blocksize size— URL을 읽을 때 한 번에 읽는 최대 바이트 수. 블록마다-progress콜백(지정 시)을 호출해요.-channel name— URL 내용을state(body)에 저장하는 대신 채널name으로 복사.-command callback— HTTP 트랜잭션이 끝난 뒤 콜백 호출. 이 옵션이 있으면::http::geturl이 즉시 반환해요. 콜백은::http::geturl이 반환한 토큰을 추가 인자로 받아요. 템플릿:proc httpCallback {token} { upvar #0 $token state # Access state as a Tcl array }-handler callback— HTTP 데이터가 유효할 때마다 콜백 호출. 있으면 HTTP 데이터로 다른 일을 하지 않아요. 이 프로시저는 HTTP 데이터용 소켓과 토큰 두 추가 인자를 받고, 소켓에서 읽은 바이트 수를 반환해야 해요. 템플릿:proc httpHandlerCallback {socket token} { upvar #0 $token state set data [read $socket 1000] set nbytes [string length $data] return $nbytes }-handler는 압축·청크 전송 인코딩과 호환되지 않아서, 지정하면 http::geturl은 HTTP 프로토콜을 1.0으로 낮추고-zip옵션을 무시해요.-handler와-channel을 함께 쓰면 핸들러가 HTTP 소켓에서 지정 채널로 데이터를 복사하는 책임을져요(채널 이름은-channel요소로 접근 가능).-headers keyvaluelist— HTTP 요청에 추가할 헤더. 키-값이 번갈아 나오는 짝수 요소 리스트여야 해요. 키가 헤더 필드 이름이 되고, 값의 개행 문자는 제거돼서 헤더가 훼손되지 않아요. 예:Pragma no-cache→Pragma: no-cache.-keepalive boolean—true면 여러 요청을 처리하기 위해 연결을 열어 두려고 시도. 기본0.-method type— HTTP 요청 메서드를 강제. 보통 다른 옵션으로 GET/POST/HEAD가 자동 선택되지만 이 옵션이 재정의하며, WebDAV 지원을 위한 PUT·DELETE 같은 선택지를 쓸 수 있게 해요. 헤더와 요청 본문이 요청 메서드 요구에 맞는지 확인할 책임은 호출자에게 있어요(예: 빈 본문의 POST는-headers {Content-Length 0}도 함께 줘야 함).-myaddr address— 여러 인터페이스가 있을 때 내부 소켓 호출에 특정 로컬 주소를 전달.-progress callback— URL에서 데이터를 전송할 때마다 콜백 호출. 토큰, Content-Length 메타데이터의 예상 총 크기, 지금까지 전송된 바이트 수의 세 추가 인자를 받아요. 예상 총 크기를 모르면 0이 전달돼요.-protocol version— 사용할 HTTP 프로토콜 버전.1.0또는1.1(기본). HTTP/1.1을 이해 못 하거나 불평하는 서버일 때만 필요.-query query— 값이 비어 있지 않으면 POST 요청을 만들어query문자열을 요청 페이로드 그대로 서버에 전달. 콘텐츠 형식·인코딩은-type옵션이 정한 Content-Type 헤더가 알려줘요.-type을 지정하지 않으면 기본application/x-www-form-urlencoded이고, 이 경우query는 x-url-encoding 형식이어야 해요(HTML 폼에서 제출되는 POST와 동일). 포맷은::http::formatQuery로 만들 수 있어요.-queryblocksize size— URL에 쿼리 데이터를 보낼 때 한 번에 쓰는 최대 바이트 수. 블록마다-queryprogress콜백(지정 시) 호출.-querychannel channelID— POST 요청으로channelID의 데이터를 서버에 전달.-type옵션을 쓰지 않으면 데이터는 x-url-encoding 형식이어야 해요.-headers로 Content-Length 헤더를 지정하지 않으면::http::geturl이 post 데이터 크기를 알아내 그 헤더를 만들려고 해요. 크기를 알 수 없으면 오류를 반환해요.-queryprogress callback— URL에 데이터를 보낼 때마다(POST) 호출.-progress와 동일하게 동작·형식.-strict boolean— 요청에 RFC 3986 URL 검증을 적용할지. 기본1.-timeout milliseconds— 0이 아닌 값이면 그 밀리초 후 타임아웃을 설정해요. 타임아웃이 되면::http::reset과(지정 시)-command콜백을 호출해요. 타임아웃 후::http::status의 반환값은timeout이에요.-type mime-type— POST 작업 시 기본값(application/x-www-form-urlencoded) 대신 이 값을 Content-Type으로 사용.-validate boolean— 0이 아닌 값이면 HTTP HEAD 요청을 수행. URL의 메타 정보만 반환하고 내용은 반환하지 않아요. 메타 정보는 트랜잭션 후state(meta)변수에서 볼 수 있어요.
유틸리티 명령어
::http::formatQuery key value ?key value ...?— 쿼리 데이터를 x-url-encoding. 짝수 인자를 받아 키·값을 인코딩하고&와=구분자가 맞게 들어간 하나의 문자열을 생성해요.::http::geturl의-query값으로 적합해요.::http::quoteString value— 단일 문자열을 x-url-encoding.::http::reset token ?why?—token이 식별하는 트랜잭션을 리셋.state(status)를why(기본reset)로 설정하고 등록된-command콜백을 호출.::http::wait token— 트랜잭션이 끝날 때까지 블로킹·대기하는 편의 프로시저.vwait을 쓰므로 신뢰할 수 있는 코드에서만 동작해요.-command없이 호출한 경우엔::http::geturl이 이미 끝날 때까지 반환하지 않으므로 쓸모없어요.::http::data token—state배열의body요소(URL 데이터) 반환.::http::error token—state배열의error요소 반환.::http::status token—state배열의status요소 반환.::http::code token—state배열의http요소 반환.::http::ncode token—http요소에서 숫자 반환 코드(200, 404 등)만 추출.::http::size token—currentsize요소 반환(URL에서 받은 바이트 수).::http::meta token— HTTP 응답 헤더를 담는meta요소 반환.::http::cleanup token—token이 식별하는 연결과 관련된 상태를 정리. 이 호출 후에는::http::data같은 프로시저로 정보를 얻을 수 없어요. HTTP 요청을 끝낸 뒤엔 이 함수를 호출할 것을 강력히 권장해요. 호출하지 않으면 메모리가 해제되지 않아,::http::geturl을 충분히 여러 번 호출하면 메모리 누수가 성능 저하...아니면 더 나쁜 결과를 낼 수 있어요.::http::register proto port command— HTTPS 같은 커스텀 HTTP 전송 타입을 지원하도록 접두사·기본 포트·Tcl 채널 생성 명령어를 등록. 예:package require http package require tls ::http::register https 443 ::tls::socket set token [::http::geturl https://my.secure.site/]::http::registerError port ?message?— 등록된 프로토콜 핸들러가 http에 전달할 오류 메시지를 제공. 이 명령어는 오류를 일으키지 않아요. 등록된 프로토콜이 문제(예: 유효하지 않은 TLS 인증서)를 감지해 http로 오류가 전파될 때 유용하며, http가 일반 오류보다 정확한 오류 메시지를 내도록 해요. 마지막 호출에서 인자message로 준 값을 반환하고, 그런 호출이 없었으면 빈 문자열을 반환해요.::http::unregister proto— 이전에::http::register로 등록한 프로토콜 핸들러를 등록 해제. 이전에 설치된 핸들러가 있었으면 기본 포트와 핸들러 명령어의 두 항목 리스트를 반환하고, 없었으면 오류를 반환해요.
오류 (ERRORS)
::http::geturl은 다음 경우에 오류를 일으켜요: 잘못된 명령줄 옵션, 잘못된 URL, 존재하지 않는 호스트의 URL, 존재하는 호스트의 잘못된 포트 URL. 이런 오류는 네트워크 트랜잭션을 시작조차 못 한다는 뜻이에요. HTTP 요청 헤더를 쓰다 I/O 오류가 나도 오류를 일으켜요. 동기 호출(-command 미지정)에선 응답 헤더나 데이터를 읽다 I/O 오류가 나도 오류를 일으켜요. 이 경우 ::http::geturl은 토큰을 반환하지 않으므로 필요한 정리를 모두 수행해 ::http::cleanup을 호출할 필요가 없어요.
비동기(-command) 호출에선 위 오류 상황이 모두 적용되지만, 응답 헤더·데이터를 읽는 중 오류가 나도 예외는 발생하지 않아요. HTTP 헤더를 쓴 뒤 ::http::geturl이 반환되고 나머지는 백그라운드에서 일어나기 때문이에요. 명령어 콜백은 ::http::status로 상태를 확인하고 오류면 ::http::error로 메시지를 얻을 수 있어요. 또는 주 프로그램 흐름이 비동기 요청 결과를 알아야 하는 지점에 도달하면 ::http::wait를 호출한 뒤 콜백처럼 status·error를 확인할 수 있어요.
::http::geturl은 -command, -handler, -proxyfilter 콜백을 catch 안에서 실행해요. 그래서 콜백의 오류는 bgerror 핸들러를 호출하지 않아요. 이런 콜백을 디버깅할 땐 콜백 안에서 catch를 써 오류를 stdout에 쓰는 게 편리해요. 어떤 경우든 끝나면 ::http::cleanup으로 상태 배열을 삭제해야 해요.
::http::status로 확인하는 트랜잭션의 다른 결과 상태는 다음과 같아요.
ok— 트랜잭션이 완전히 끝나면ok. 그래도::http::code값을 확인해 HTTP 상태를 봐야 해요.::http::ncode는 숫자 오류(200, 404, 500)만,::http::code는HTTP 404 File not found같은 값을 반환해요.eof— 서버가 응답 없이 소켓을 닫으면 오류는 없지만 상태가eof.error— 오류 메시지가state(error)요소에도 저장돼::http::error로 접근 가능.timeout— 트랜잭션이 끝나기 전에 타임아웃 발생.reset— 사용자 리셋.
또 다른 오류 가능성은 ::http::geturl이 서버가 응답하고 소켓을 닫기 전에 모든 post 쿼리 데이터를 쓰지 못하는 경우예요. 오류 메시지는 state(posterror) 요소에 저장되고 ::http::geturl은 트랜잭션 완료를 시도해요. 서버 응답을 읽을 수 있으면 ok, 아니면 eof 상태가 돼요.
상태 배열 (STATE ARRAY)
::http::geturl이 반환하는 토큰으로 트랜잭션 상태에 Tcl 배열로 접근할 수 있어요. upvar #0 $token state로 쓰기 쉬운 배열 변수를 만들 수 있어요. URL 데이터가 더 이상 필요 없으면 상태 배열을 unset해 저장 공간을 해제해야 하고, 그 용도로 ::http::cleanup이 제공돼요.
지원되는 배열 요소:
binary— (Content-Encoding응답 헤더가 지정한 압축을 디코딩한 후) HTTP 응답이 바이너리면true, 텍스트면false.body— URL의 내용.-channel옵션을 지정하면 비어 있어요.::http::data가 반환하는 값.charset— Content-Type 메타데이터의 charset 속성 값. 지정되지 않으면 RFC 표준iso8859-1또는$::http::defaultCharset값. 들어오는 텍스트 데이터는 이 charset에서 utf-8로 자동 변환돼요.coding— Content-Encoding 메타데이터 값의 복사본.currentsize— URL에서 지금까지 가져온 바이트 수.::http::size가 반환하는 값.error— 정의돼 있으면 트랜잭션이 중단될 때 본 오류 문자열.http— 서버의 HTTP 상태 응답.::http::code가 반환하는 값. 형식은HTTP/1.1 code string. code는 HTTP 표준의 세 자리 숫자로, 200이 OK. 4·5로 시작하면 오류, 3으로 시작하면 리다이렉션 오류(이 경우 Location 메타데이터가 새 URL을 지정).meta— HTTP 프로토콜이 반환하는 URL 내용 설명 메타데이터. 키와 값의 리스트로,array set meta $state(meta)처럼 메타데이터만 담는 배열 초기화에 적합한 형식이에요. 주요 키:Content-Type(내용 타입, 예: text/html, image/gif, application/x-tcl),Content-Length(광고된 크기 — 실제 크기는state(currentsize)),Location(요청한 데이터를 담은 대체 URL). HTTP 표준이 더 많은 키를 정의하고 서버가 자체 키를 추가할 수도 있어요.posterror— post 쿼리 데이터를 서버에 쓰는 동안 발생한 오류(있다면).status— 위 ERRORS 장 참고. 트랜잭션 중엔 빈 문자열.totalsize— Content-Length 메타데이터 값의 복사본.type— Content-Type 메타데이터 값의 복사본.url— 요청한 URL.
영속 연결 (PERSISTENT CONNECTIONS)
기본: 영속 연결은 여러 HTTP/1.1 트랜잭션을 같은 TCP 연결로 처리하게 해요. 파이프라이닝은 클라이언트가 각 응답을 기다리지 않고 영속 연결에 여러 요청을 보내는 것이고, 서버는 요청을 받은 순서대로 응답을 보내요. POST 요청이 실패하면 보통 재전송 전에 사용자 확인이 필요해요(실패한 POST가 서버를 수정했는지 확인하고 싶을 수 있으므로).
HTTP 요청은 http::geturl 호출에 -keepalive true 옵션이 있으면 영속 소켓을 사용해요. http::config 옵션 -pipeline이 true(기본값)면 허용되는 곳에서 파이프라이닝을 사용해요.
http 패키지는 각 서버(즉 각 domain:port 값)에 영속 연결을 하나만 유지해요. 영속 연결이 다른 요청으로 바쁜데 그 위로 요청하면, 연결이 자유로울 때까지 큐에 보관돼요. http 패키지는 Keep-Alive 헤더로 제어되는 HTTP/1.0 영속 연결은 지원하지 않아요.
특수 경우: RFC 7230에 따라 http::geturl은 POST 메서드를 쓰는 요청을 파이프라인으로 처리하지 않아요. POST가 영속 연결을 쓰는데 그 연결의 첫 요청이 아니면, 이전 요청의 응답을 받을 때까지 기다리거나(-postfresh가 false면), POST마다 새 연결을 써요(-postfresh true). 서버가 파이프라인 요청들을 처리하다 마지막이 아닌 응답에 Connection: close 헤더를 보내면 이후 응답은 이행되지 않아요. http::geturl은 이행되지 않은 요청을 새 연결로 다시 보내요.
HTTP 클라이언트가 한동안 유휴 상태였던 영속 연결에 요청을 보낼 때 어려움이 생겨요. HTTP 서버가 겉보기에 유휴인 연결을 반쯤 닫아버렸는데 클라이언트가 요청을 보내는 중이라면(요청이 서버에 도착하기 전에, '비동기 닫힘 이벤트'), 요청은 실패해요. HEAD·GET 요청이면 http::geturl이 다른 연결을 열어 재전송하지만, POST면 RFC 7230이 기본적으로 자동 재시도를 금지해요(사용자 확인 또는 애플리케이션을 의미론적으로 이해하는 user-agent 소프트웨어의 확인을 권장). http::config 옵션 -repost로 두 가능성을 모두 다룰 수 있어요.
비동기 닫힘 이벤트는 짧은 시간 간격에서만 일어날 수 있어요. http 패키지는 각 영속 연결을 서버가 닫는지 모니터링하고, 감지하면 클라이언트 쪽에서도 연결을 닫고 이후 요청은 새 연결을 사용해요. -keepalive true로 호출하면 기존 영속 연결을 쓰려 하고, 서버에 Connection: keep-alive 요청 헤더를 보내 미래 요청을 위해 연결을 열어 두게 요청해요.
http::config 옵션 -pipeline, -postfresh, -repost는 영속 연결과 관련돼요.
-pipeline이true면 영속 연결의 GET·HEAD 요청을 파이프라인으로 처리해요. POST는 파이프라인하지 않아요 — 연결의 첫 트랜잭션이 아니면 이전 응답이 끝날 때까지 보내지 않아요. POST 이후의 GET·HEAD도 POST 응답이 전달될 때까지 보내지 않고, POST가 실패하면 보내지 않아요.-postfresh가true면http::geturl옵션-keepalive를 무시하고 POST 요청에 항상 새 연결을 열어요.-repost가true면 서버가 반쯤 닫은 영속 연결을 써서 실패한 POST(비동기 닫힘 이벤트)를 자동 재시도하게 해요. 실패한 파이프라인의 이후 GET·HEAD 요청도 재시도돼요. 재시도가 적절하다고 애플리케이션이 이해할 때만 써야 해요 — 특히 실패한 POST가 서버 상태를 수정했더라도 반복 POST가 나쁜 영향을 주지 않는다는 걸 알아야 해요.
프로토콜 업그레이드 (PROTOCOL UPGRADES)
HTTP/1.1 Connection과 Upgrade 클라이언트 헤더는 클라이언트가 기존 연결에서 사용할 프로토콜을 바꾸고 싶다는 걸 서버에 알려줘요(RFC 7230). 이 메커니즘으로 WebSocket(RFC 6455), 더 높은 HTTP 버전(HTTP 2), TLS 암호화를 요청할 수 있어요. 서버가 업그레이드 요청을 수락하면 응답 코드는 101이에요.
http::geturl에서 프로토콜 업그레이드를 요청하려면 -headers 옵션으로 적절한 Connection·Upgrade 값을 줘야 하고, -command 옵션으로 요청 프로토콜을 구현하면서 서버가 거부할 때 응답도 처리할 수 있는 명령어를 줘야 해요. 업그레이드 요청에서 http::geturl은 -keepalive 값을 무시하고 항상 0을 사용해, 여러 HTTP 요청용으로 의도된 연결에 업그레이드 요청이 가해지지 않게 해요.
Tcllib 라이브러리의 websocket은 WebSocket을 구현하며 http 패키지의 명령어들을 필요한 대로 호출해요. 현재 HTTP/2용 네이티브 Tcl 클라이언트 라이브러리는 없어요.
Upgrade 메커니즘은 웹 브라우저에서 TLS를 요청하는 데 쓰이지 않아요 — http와 https는 다른 포트에서 서비스되기 때문이에요. IPP(Internet Printing Protocol)처럼 http(s) 위에 구축되고 보안·비보안 트래픽에 같은 TCP 포트를 쓰는 프로토콜이 사용해요. 브라우저에서 기회적 암호화는 Upgrade-Insecure-Requests 클라이언트 헤더로 구현돼요. 보안 서비스가 가능하면 서버 응답 코드는 307 리다이렉트이고 Location 헤더가 대상 URL을 지정해요. 브라우저는 이 URL을 가져오기 위해 http::geturl을 다시 호출해야 해요.
예제
URL을 파일로 복사하면서 진행률 표시기를 출력하고 URL 메타데이터를 찍는 프로시저:
proc httpcopy { url file {chunk 4096} } {
set out [open $file w]
set token [::http::geturl $url -channel $out \
-progress httpCopyProgress -blocksize $chunk]
close $out
# This ends the line started by httpCopyProgress
puts stderr ""
upvar #0 $token state
set max 0
foreach {name value} $state(meta) {
if {[string length $name] > $max} {
set max [string length $name]
}
if {[regexp -nocase ^location$ $name]} {
# Handle URL redirects
puts stderr "Location:$value"
return [httpcopy [string trim $value] $file $chunk]
}
}
incr max
foreach {name value} $state(meta) {
puts [format "%-*s %s" $max $name: $value]
}
return $token
}
proc httpCopyProgress {args} {
puts -nonewline stderr .
flush stderr
}
더 알아보기
socket로 네트워크 채널 열기format으로 메타데이터 출력 정리- Tcllib의
websocket라이브러리로 WebSocket 구현