JavaScript 스크립팅
Chute는 고급 요청/응답 수정, 사용자 정의 규칙 일치, DNS 해석 및 예약 작업을 위한 JavaScript 스크립팅을 지원합니다. 스크립트는 Apple 플랫폼에서는 JavaScriptCore, Android에서는 QuickJS에서 실행되며 Surge 호환 스크립트 API를 따릅니다.
스크립트는 구성 파일의 [Script] 섹션에 정의됩니다.
구성
[Script]
MyScript = type=http-request, script-path=/path/to/script.js, pattern=^https?://example\.com, requires-body=true, max-size=65536, timeout=10, argument=myArg
CronJob = type=cron, script-path=/path/to/cron.js, cron-expression=*/30 * * * *
스크립트 매개변수
| 매개변수 | 필수 | 기본값 | 설명 |
|---|---|---|---|
type |
아니오 | http-request |
스크립트 트리거 유형 (아래 참조) |
script-path |
예 | — | JS 스크립트의 로컬 파일 경로 또는 HTTP(S) URL |
pattern |
아니오 | (모두 일치) | 스크립트 트리거 시점을 필터링하는 URL 정규식 패턴. 요청의 전체 URL(복호화된 요청은 https://…, 일반 HTTP는 http://…) 어느 위치에서든 일치하며, 경로만 따로 놓고 대조되는 일은 없으므로 ^/api 같은 패턴은 일치하지 않습니다 |
requires-body |
아니오 | 자동 감지 | 스크립트가 전체 요청/응답 본문을 수신하도록 강제 |
max-size |
아니오 | 131072 (128KB) | 본문 접근 스크립트의 최대 본문 크기(바이트). 수집된 본문이 이 크기를 초과하면 해당 요청에 대해 스크립트를 건너뜁니다(본문이 잘리지 않음). HTTP/1.x — 일반 HTTP와 복호화된 HTTP/1.1 — 에서는 이 값과 관계없이 Chute가 스크립트용으로 최대 131072바이트만 버퍼링하므로, 그곳에서는 이 값으로 한도를 낮출 수만 있습니다. 복호화된 HTTP/2 메시지는 통째로 전달되며 max-size로만 검사됩니다 |
timeout |
아니오 | 5.0초 | 실행별 시간 제한이며 실행 시작부터 계산됩니다: 소스 로드(원격 스크립트라면 다운로드), 코드 실행, 타이머와 요청 대기가 모두 이 시간을 함께 씁니다. 30초를 초과하는 값은 30초로 제한됩니다 |
argument |
아니오 | — | JS에서 $argument로 사용 가능한 사용자 정의 문자열 인자 |
full-header-mode |
아니오 | false | true이면 $request.headers와 $response.headers가 객체 대신 헤더 줄마다 하나씩 {field, value}를 담은 배열이 됩니다(Surge의 형식). http-request, http-request-before-send, http-response 스크립트에 적용되며, 어느 쪽이든 $done()은 두 형식을 모두 받습니다. 여러 줄로 된 헤더 참조 |
debug |
아니오 | false | 예약됨. 현재는 효과가 없습니다 |
cron-expression |
아니오 | — | Cron 일정 표현식 (cron 유형 전용); cronexp=(Shadowrocket 표기 cronexpr=)도 같은 키로 허용 |
event-name |
아니오 | (모든 이벤트) | event 스크립트가 기다리는 이벤트 — network-changed, engine-started, profile-reloaded. 이름을 적지 않으면 세 가지 모두에서 실행됩니다(Event 스크립트 참고) |
wake-system |
아니오 | false | 예약됨. 파싱되지만 현재는 효과가 없습니다 |
enable |
아니오 | true | 이 스크립트 활성화 또는 비활성화 |
script-update-interval |
아니오 | 0 | 원격 스크립트 업데이트 폴링 (실행 모델 참조) |
binary-body-mode |
아니오 | — | Surge 호환을 위해 허용되며(binary-mode 표기도 가능) 프로필을 저장할 때 유지되지만, 아무것도 바꾸지 않습니다: 원시 바이트는 항상 .bodyBytes에 있습니다 |
engine |
아니오 | — | 허용되고 유지되지만 무시됩니다: 모든 스크립트는 플랫폼 자체의 엔진에서 실행됩니다 |
스크립트 유형
| 유형 문자열 | 열거형 | 설명 |
|---|---|---|
http-request |
HTTP 요청 | 업스트림 전 HTTP 요청 가로채기 및 수정 |
http-response |
HTTP 응답 | 클라이언트 전 HTTP 응답 가로채기 및 수정 |
http-request-before-send |
HTTP 요청 전송 전 | 업스트림으로 전송하기 직전에 요청 수정 |
rule |
규칙 | 사용자 정의 규칙 일치 로직 |
dns |
DNS | 사용자 정의 DNS 해석 |
cron |
Cron | 예약/타이머 스크립트 |
event |
이벤트 | 시스템 이벤트 핸들러 (예: network-changed) |
generic |
범용 | Surge 호환을 위해 허용됩니다. 요청, 규칙, DNS, 이벤트 어디에도 연결되지 않으므로 Chute가 스스로 실행하지 않습니다. Chute iOS나 Chute Android의 스크립트 목록에서 실행으로, 또는 HTTP 제어 API의 POST /api/scripts/run으로 실행하세요 |
알 수 없는 type=은 로그에 보고되고 http-request로 처리됩니다.
본문 자동 감지:
http-request및http-response스크립트의 경우, 소스에$request.body또는$response.body가 포함되어 있으면 본문이 자동으로max-size까지 제공됩니다. 이 동작을 강제하려면requires-body=true를 사용하세요..bodyBytes나.rawBody만 읽는 스크립트는 감지되지 않으며,http-request-before-send스크립트는 어떤 경우에도 감지되지 않습니다: 이런 스크립트에는requires-body=true가 필요합니다.
JavaScript API 참조
스크립트는 다음 전역 객체가 사용 가능한 샌드박스 JavaScript 환경 — Apple 플랫폼에서는 JavaScriptCore, Android에서는 QuickJS — 에서 실행됩니다.
$request (읽기 전용)
사용 가능: http-request, http-response, http-request-before-send, rule, dns
| 속성 | 유형 | 설명 |
|---|---|---|
.url |
String | 전체 요청 URL. 포트는 스킴의 기본 포트가 아닐 때만 표기되며, IPv6 호스트는 대괄호로 묶입니다. 요청 대상이 이미 절대 URL이면 그대로 쓰이고, /로 시작하지 않는 대상에는 /가 붙으며, 호스트를 알 수 없으면 경로만 남습니다. rule 스크립트에서는 목적지 호스트, dns 스크립트에서는 조회 중인 도메인입니다 |
.method |
String | HTTP 메서드 (GET, POST 등) 또는 DNS의 경우 QUERY |
.headers |
Object 또는 Array | 이름마다 값을 가진 요청 헤더. 여러 줄로 된 헤더는 각 줄의 값이 하나의 문자열로 합쳐집니다(여러 줄로 된 헤더 참조). full-header-mode=true이면 {field, value} 배열입니다. 이름은 대소문자와 관계없이 찾을 수 있습니다(헤더 이름 참조) |
.body |
String 또는 null | 요청 본문 (UTF-8 디코딩) |
.bodyBytes |
Bytes 또는 null | 원시 요청 본문 바이트 (아래 주의 참고) |
.hostname |
String | 대상 호스트명 |
.destPort |
Number | 대상 포트 |
.processPath |
String | 요청 프로세스 경로 (macOS; Android에서는 VPN이 잡은 TCP 연결에 대해 앱의 APK 경로) |
.userAgent |
String | User-Agent 헤더 값 |
.sourceIP |
String | 소스 IP 주소 |
.listenPort |
Number | 프록시 수신 포트 |
.requestId |
String | 고유 요청 ID |
.dnsResult |
String | 해석된 IP 주소 |
.srcPort |
Number | 소스 포트 |
.protocol |
String | 감지된 프로토콜: http, https, tcp, dns |
$response (읽기 전용)
사용 가능: http-response
| 속성 | 유형 | 설명 |
|---|---|---|
.status |
Number | HTTP 상태 코드 |
.headers |
Object 또는 Array | 이름마다 값을 가진 응답 헤더. 여러 줄로 된 헤더는 각 줄의 값이 하나의 문자열로 합쳐집니다(여러 줄로 된 헤더 참조). full-header-mode=true이면 {field, value} 배열입니다. 이름은 대소문자와 관계없이 찾을 수 있습니다(헤더 이름 참조) |
.body |
String 또는 null | 응답 본문 (UTF-8 디코딩) |
.bodyBytes |
Bytes 또는 null | 원시 응답 본문 바이트 (아래 주의 참고) |
.rawBody |
Bytes 또는 null | .bodyBytes의 별칭 |
.body,.bodyBytes,.rawBody는 같은 본문을 담습니다: 스크립트에 전달되기 전에 chunked 분할이 제거되고 gzip 또는 deflateContent-Encoding이 풀립니다..bodyBytes와.rawBody에는 바이트 객체가 들어 있습니다. 이 객체는 바이트를 받는 호출에 넘기세요 — 결과 역시 바이트 객체인$utils.ungzip(), 또는 이를 원시 본문으로 받아들이는$done()의body가 그런 호출입니다. 이 객체가 무엇인지는 엔진에 따라 다릅니다: Apple 플랫폼에서는.length도 인덱싱도 없는 불투명한 래퍼이고 Android에서는Uint8Array이므로, 모든 플랫폼을 위한 스크립트는.length나 인덱싱에 기대지 않아야 합니다. 내용을 읽으려면 같은 바이트를 UTF-8로 디코딩한.body를 쓰세요.
$done(value) — 완료 핸들러
스크립트 종료 시 완료를 알리기 위해 반드시 정확히 한 번 호출해야 합니다. 이를 호출하지 않고 반환한 스크립트는 곧바로 통과로 완료됩니다. 다만 setTimeout 타이머나 $httpClient 요청이 아직 남아 있으면 그때까지 기다립니다. timeout=은 실행이 시작된 순간부터 흐르며, 소스 로드, 코드 실행, 그 기다림이 모두 이 시간에 포함됩니다.
$done({}) // 통과 — 수정 없음
$done() // 연결 중단
$done({matched: true}) // 규칙 일치 결과 (규칙 스크립트 전용)
$done({address: "1.2.3.4"}) // DNS 결과 (DNS 스크립트 전용)
http-request, http-request-before-send, http-response 스크립트에서 인자 없이(또는 객체가 아닌 값으로) $done()을 호출하면 연결이 중단됩니다: Chute는 그 요청이나 응답 대신 아무것도 보내지 않으며, 중단된 요청은 서버에 도달하지 않습니다. 이미 클라이언트로 가고 있던 데이터는 먼저 내보낸 다음 연결이 즉시 닫힙니다. HTTP/2에서는 그 요청의 스트림만 리셋되고(RST_STREAM, 오류 코드 CANCEL) 그 대신 아무것도 보내지 않으며, 연결과 그 위의 다른 요청은 그대로 계속됩니다.
스크립트가 준 헤더와 url은 쓰이기 전에 검사됩니다. 다음과 같은 헤더는 버려지고 로그에 경고가 남습니다:
- 이름이나 값에 줄바꿈(CR 또는 LF)이나 NUL 문자가 들어 있는 경우:
[JS] Dropped header <name>: a header name or value cannot contain a line break - 이름이 올바른 HTTP 필드 이름이 아닌 경우(RFC 9110 §5.1). 올바른 이름은 하나 이상의 영문자, 숫자 또는
!#$%&'*+-.^_`|~중의 문자로 이루어집니다. 따라서 빈 이름이나 공백, 콜론, ASCII가 아닌 문자가 들어 있는 이름은 버려집니다:[JS] Dropped header <name>: not a valid header name
결과의 나머지는 그대로 적용됩니다. 배열 형식으로 준 헤더와 요청 스크립트가 반환한 response의 헤더도 같은 방식으로 검사됩니다. 줄바꿈, NUL 또는 공백이 들어 있는 url은 무시되고, 역시 경고가 남습니다. url의 경로와 쿼리는 쓰인 그대로 전송되며, %20 같은 이스케이프는 풀리지 않습니다.
HTTP 요청 스크립트 반환 값:
$done({
url: "https://new.example.com/path", // URL 재작성
headers: {"X-Custom": "value"}, // 헤더 수정
body: "new request body", // 본문 수정
response: { // 합성 응답 반환 (업스트림 건너뛰기)
status: 200,
headers: {"Content-Type": "text/html"},
body: "<html>Blocked</html>"
}
})
response가 제공되면 요청이 즉시 종결됩니다: Chute는 클라이언트에 직접 합성 응답을 반환하며, 요청은 업스트림 서버로 전송되지 않습니다. 이는 Chute가 요청을 처리하는 모든 경로(HTTP/2 포함)에서, 그리고 스크립트가 헤더에서 실행되든, 본문 전체를 받아 실행되든, http-request-before-send로 실행되든 마찬가지입니다. HTTP/1.x에서는 상태 줄에 해당 코드의 표준 사유 문구가 붙고(HTTP/1.1 404 Not Found) 응답을 다 보내면 연결이 닫힙니다. HTTP/2에서는 그 요청의 스트림에만 응답합니다. status는 200–599 범위의 숫자이거나, 앞뒤 공백을 제거하면 그런 정수 하나만 남는 문자열("404")입니다. 그 밖의 값이거나 없으면 200입니다. Content-Length는 headers에 무엇이 쓰여 있든 body로 계산됩니다. 이는 차단, API 모의 또는 캐시된 콘텐츠 반환에 유용합니다.
Chute가 직접 만드는 응답 — 모의 응답(Map Local), 스크립트의
response, URL 재작성과 REJECT 계열 정책의 응답과 오류 페이지 — 은 HTTP를 따릅니다: HEAD 요청에 대한 응답은 헤더뿐이며 Content-Length는 GET일 때와 같고, 상태가 204, 205 또는 304인 응답에는 본문도 Content-Length도 없습니다.
HTTP 응답 스크립트 반환 값:
$done({
status: 200, // 상태 코드 수정
headers: {"X-Custom": "value"}, // 응답 헤더 수정
body: "new response body", // 응답 본문 수정
url: "https://other.example.com" // 리디렉션(기본값 302)
})
body는 응답의 본문만 교체합니다: 상태 줄과 헤더는 서버의 것이 유지되고 반환한 status와 headers가 여기에 병합되며, 서버의 원래 본문은 버려집니다. 스크립트가 헤더만으로 실행된 경우에는 그 뒤에 본문 재작성 규칙과 본문을 받는 http-response 스크립트가 새 본문에 대해 그대로 실행됩니다. HEAD 요청에 대한 응답이나 상태가 1xx, 204, 205, 304인 응답에는 본문이 없으므로, 그런 응답에서는 반환한 body가 무시되지만 status와 headers는 적용됩니다.
status는 200–599 범위의 숫자이거나, 앞뒤 공백을 제거하면 그런 정수 하나만 남는 문자열("301")입니다. 그 밖의 값은 true와 false를 포함해 무시되며, 응답은 원래 상태를 유지합니다. 코드를 바꾸는 status는 상태 줄의 사유 문구도 새 코드의 표준 문구로 바꿉니다. status가 204, 205 또는 304이면 응답은 본문(서버의 본문도, 반환한 body도)도 길이 헤더도 없이 전송되며, HTTP/1.x에서는 헤더를 다 보내면 연결이 닫힙니다.
url이 제공되면 Chute는 원래 응답 대신 그 URL로의 리디렉션으로 응답합니다. 상태는 반환한 status(예: 301, 307, 308)이며, 없거나 무시되면 302입니다. 반환한 headers는 평소대로 병합되고, Location은 url이 됩니다. body가 없으면 리디렉션에도 본문이 없으며, 서버의 본문은 전송되지 않습니다. HTTP/2에서는 서버의 트레일러도 전송되지 않습니다. url은 status / headers / body 중 하나 이상과 함께 반환해야 합니다. url만 포함된 결과는 통과로 처리됩니다.
DNS 스크립트 반환 값:
$done({address: "1.2.3.4"}) // 단일 IP
$done({addresses: ["1.2.3.4", "5.6.7.8"]}) // 여러 IP
$done({address: "10.0.0.1", ttl: 300}) // 사용자 정의 TTL 포함 (초, 기본값 60)
$done({server: "1.1.1.1"}) // 이 서버로 해석
$done({servers: ["tls://dns.google", "8.8.8.8#disable-ipv6"]})
server/servers는 답을 주는 대신 해석할 서버를 고릅니다. 각 항목은 평범한 DNS 서버 항목이며(주소,tls://,https://, 평소의#옵션 포함) 함께 질의해 먼저 온 답을 씁니다. 쓸 수 있는 항목이 하나도 없으면 설정된 풀로 넘어가지 않고 그 조회가 실패합니다.
여러 줄로 된 헤더
헤더 하나가 여러 줄에 나타날 수 있습니다. 쿠키 두 개를 설정하는 응답에는 Set-Cookie 줄이 두 개 있습니다. http-request, http-request-before-send, http-response 스크립트는 모든 줄을 읽고 여러 줄을 쓸 수 있습니다.
읽기
기본적으로 $request.headers와 $response.headers는 헤더 이름마다 문자열 하나를 대응시킨 객체입니다. 헤더가 여러 줄이면 각 줄의 값이 순서대로 합쳐집니다: Cookie의 값은 "; "로, 그 밖의 헤더(Set-Cookie 포함)의 값은 ", "로 이어집니다.
$response.headers["Set-Cookie"] // "a=1; Path=/, b=2; Path=/"
$request.headers["Cookie"] // "a=1; b=2"
쿠키의 Expires 날짜에는 쉼표가 들어 있으므로, 합쳐진 Set-Cookie 문자열을 쿠키별로 안정적으로 다시 나눌 수 없습니다. 줄마다 따로 읽으려면 스크립트 줄에 full-header-mode=true를 추가하세요. 그러면 $request.headers와 $response.headers는 줄마다 {field, value} 객체 하나를 담은 배열이 되며, Surge와 같은 형식입니다. 이름이 같은 줄들은 순서가 유지됩니다.
// full-header-mode=true
const cookies = $response.headers
.filter(h => h.field.toLowerCase() === "set-cookie")
.map(h => h.value) // ["a=1; Path=/", "b=2; Path=/"]
HTTP/2에서도 마찬가지입니다: 클라이언트가 쿠키를 여러 cookie 필드로 나누어 보내면 스크립트는 이를 "; "로 합친 하나의 Cookie로 읽으며, 각 set-cookie는 각각 한 줄이 됩니다.
쓰기
$done()의 headers는 거기에 이름이 있는 헤더만 바꾸고, 다른 헤더는 그대로 둡니다. 요청 스크립트가 응답으로 반환하는 response의 headers에도 같은 규칙이 적용됩니다.
| 값 | 효과 |
|---|---|
| 문자열 | 그 헤더는 이 값으로 된 한 줄만 남으며, 기존 줄은 모두 교체됩니다 |
| 문자열 배열 | 값마다 한 줄씩, 순서대로 그 헤더의 줄을 교체합니다. {"Set-Cookie": ["a=1", "b=2"]}는 Set-Cookie 두 줄을 보냅니다 |
[] |
그 헤더를 삭제합니다 |
그 밖의 값(숫자, null, 객체, 문자열이 아닌 요소가 든 배열) |
무시되며, 그 헤더는 그대로 남습니다 |
스크립트가 읽은 합쳐진 문자열을 그대로 반환하면 그 헤더는 원래 줄을 유지합니다. 따라서 const h = $response.headers; h["X-A"] = "1"; $done({headers: h})는 X-A만 바꾸고, Set-Cookie 두 줄은 여전히 두 줄로 나갑니다. 그 밖의 문자열은 그 헤더의 모든 줄을 한 줄로 교체합니다. 쿠키를 추가하려면 전체 목록을 배열로 쓰세요: full-header 모드에서 기존 줄을 읽은 다음 {"Set-Cookie": [...existing, "c=3"]}를 반환합니다.
요청에는 Cookie 줄이 하나만 실립니다: http-request 또는 http-request-before-send 스크립트가 headers에서 Cookie에 준 배열은 "; "로 합쳐지므로, {"Cookie": ["a=1", "b=2"]}는 Cookie: a=1; b=2를 보냅니다.
$done()은 full-header-mode 여부와 관계없이 {field, value} 객체의 배열로 된 headers도 받습니다:
$done({headers: [{field: "Set-Cookie", value: "a=1"},
{field: "Set-Cookie", value: "b=2"},
{field: "X-A", value: "1"}]})
항목은 이름별로(대소문자 구분 없이) 묶이며, 각 묶음이 주어진 순서대로 그 헤더의 줄을 교체합니다. 배열에 이름이 없는 헤더는 그대로 남으므로, 헤더를 빠뜨려도 삭제되지 않습니다. 삭제하려면 객체 형식에서 []를 쓰세요. 문자열 field와 문자열 value가 없는 항목은 무시됩니다.
- 헤더 이름은 대소문자를 구분하지 않습니다:
set-cookie와Set-Cookie는 같은 헤더이므로 각 이름은 한 번만 쓰세요. 올바른 헤더 이름이 아닌 이름은$done에서 설명한 대로 버려집니다. - 줄바꿈이나 NUL이 들어 있는 값은
$done에서 설명한 대로 버려집니다. 배열에서는 그 값만 버려지며, 모든 값이 버려지면 그 헤더는 그대로 남습니다. - HTTP/2에서는 각 줄이 독립된 필드가 되고 이름은 소문자가 됩니다.
Connection,Keep-Alive처럼 한 연결에만 의미가 있는 헤더는 전송되지 않습니다.
$request.headers와 $response.headers의 헤더 이름
$request.headers와 $response.headers는 이름이 어떤 대소문자로 쓰였든 헤더를 찾습니다: $response.headers['ETag'], $response.headers['etag'], $response.headers['Etag']는 모두 같은 헤더를 읽습니다. 이는 in('etag' in $response.headers), 대입(headers['content-type'] = 'text/html'은 이름을 하나 더 추가하는 대신 객체에 이미 있는 Content-Type을 바꿉니다), delete에도 적용됩니다.
이름을 나열하면(Object.keys(), for…in, JSON.stringify()) 각 헤더는 한 번만, Chute가 저장하는 표기로 나타납니다. 클라이언트나 서버가 어떻게 썼든 각 단어의 첫 글자가 대문자입니다: Content-Type, Etag, X-Api-Key, Www-Authenticate.
이렇게 이름을 맞춰 찾는 것은 $request.headers나 $response.headers에서 읽은 객체뿐입니다. Object.assign({}, $request.headers)나 {...$response.headers}처럼 직접 만든 복사본은 일반 객체이므로, 복사본에서는 위의 표기를 쓰세요. full-header-mode=true이면 headers는 {field, value} 객체의 배열이고 field도 같은 표기이며, 이 찾기는 적용되지 않습니다. 대신 field.toLowerCase()로 비교하세요.
$httpClient — 비동기 HTTP 클라이언트
스크립트 내에서 HTTP 요청을 수행합니다. 모든 요청은 $done() 또는 시간 초과 시 취소됩니다.
$httpClient.get(url, function(error, response, data) {
if (error) {
console.log("Request failed: " + error)
} else {
console.log("Status: " + response.status)
console.log("Response: " + data)
}
})
$httpClient.post(url, {headers: {...}, body: "...", timeout: 5}, callback)
$httpClient.put(url, options, callback)
$httpClient.del(url, options, callback)
$httpClient.head(url, options, callback)
$httpClient.options(url, options, callback)
$httpClient.patch(url, options, callback)
옵션 객체에는 프로필이 정의한 정책 이름인 policy도 담을 수 있습니다. 그러면 요청은 기본 경로 대신 그 정책을 통해 나갑니다. 프로필이 정의하지 않은 이름은 경고로 기록되고, 요청은 기본 경로로 나갑니다. Surge의 인라인 policy-descriptor 형식은 지원되지 않습니다: 경고로 기록되고 요청은 마찬가지로 기본 경로로 나갑니다 — 프로필에 정책을 정의하고 그 이름을 전달하세요.
콜백 시그니처: callback(error, response, data)
error: 오류 문자열 또는nullresponse:{status: Number, headers: Object}또는null.headers는 이름의 대소문자와 관계없이 헤더를 찾으며, 나열되는 이름은$request.headers와 같은 표기(각 단어의 첫 글자가 대문자:Content-Type,Etag,X-Api-Key)입니다.policy여부와 관계없이 같습니다data: UTF-8 문자열로 디코딩한 응답 본문. 올바른 UTF-8이 아닌 본문은null이 아니라.bodyBytes처럼 바이트 객체로 전달됩니다.null은 본문이 아예 없을 때뿐입니다
$httpClient는http-request,http-response,http-request-before-send,cron,event스크립트에서 사용할 수 있습니다.rule및dns스크립트에서는 사용할 수 없습니다.스크립트 하나는 최대 8개, 모든 스크립트를 합쳐서는 최대 16개의 요청을 동시에 진행 중으로 둘 수 있습니다. 어느 한도든 넘는 요청은 전송되지 않고 콜백도 실행되지 않으며, 로그에는 실행당 한 번 그렇게 기록됩니다. 4 MB보다 큰 응답 본문은 요청을 실패시킵니다.
$persistentStore — 키-값 저장소
스크립트 및 프로세스 재시작 간에도 유지되는 영구 키-값 저장소이며, 모든 스크립트가 같은 키를 읽고 씁니다. Android에서는 Chute의 비공개 저장소에 보관되며 기기 백업에서 제외됩니다.
$persistentStore.write(data, key) // 값 저장
$persistentStore.read(key) // 값 조회
$persistentStore.remove(key) // 값 제거
$notification — 로컬 알림
로컬 시스템 알림을 게시합니다. Chute Apple TV는 알림을 표시하지 않습니다.
$notification.post("Title", "Subtitle", "Notification body text")
선택적인 네 번째 인자로 Surge의 옵션을 전달합니다. url(알림을 탭했을 때 열 링크), action(url이 있으면 open-url로 간주되며, 앱이 처리하는 동작은 이것뿐입니다 — Surge의 clipboard 같은 다른 동작은 함께 전달되지만 아무것도 처리하지 않습니다), auto-dismiss(초)입니다. 이 값들은 알림에 첨부되며, 그 밖의 키는 경고와 함께 무시됩니다. url은 Chute iOS, Chute Mac, Chute Android가 처리하여 알림을 클릭하면 엽니다. auto-dismiss는 Chute Android만 처리하며, 그 초 수가 지나면 알림을 제거합니다. Apple 플랫폼의 앱은 알림을 그대로 둡니다.
$notification.post("Title", "", "Tap to open", {url: "https://example.com", "auto-dismiss": 5})
스크립트 알림은 notification-text가 붙은 규칙의 알림과 마찬가지로 앱의 알림 스위치를 따릅니다 — Chute iOS에서는 알림 허용, Chute Mac에서는 이벤트 보고 알림 표시, Chute Android에서는 Chute에 대한 시스템 알림 권한입니다. 이것을 끄면 아무것도 표시되지 않습니다. Chute Android는 스크립트 알림을 전용 알림 채널인 스크립트 알림에 게시하며, 이 채널은 시스템의 알림 설정에서 따로 끌 수 있습니다. 알림 보고를 참고하세요.
$network — 네트워크 정보
읽기 전용 네트워크 상태 정보입니다.
$network.dns // DNS 서버 IP 배열
$network.wifi // {ssid: "WiFiName", bssid: null}
$network.v4 // {primaryAddress, primaryRouter}
$network.v6 // {primaryAddress, primaryRouter}
$network.primaryRouter // IPv4 기본 게이트웨이, 없으면 null
$network.cellularData // {radio: "LTE" | "5G" | ..., carrier: null}
ssid와bssid는 터널이 Wi-Fi 신원을 읽을 수 있는 iOS에서, 그리고 Android에서는 Chute가 네트워크 이름을 읽는 데 Android가 요구하는 권한을 가지고 있을 때 채워집니다(Android 시작하기 참고). macOS와 tvOS에서는ssid가 비어 있고bssid는null입니다.primaryAddress는 터널 자신의 주소와 링크 로컬 주소를 건너뛰므로, 기기가 네트워크로 나갈 때 쓰는 주소입니다.carrier는 항상null입니다 — iOS 16에서 통신사 이름이 제거되었습니다.
$environment — 런타임 정보
$environment.system // "iOS", "macOS" 또는 "Android"
$environment.appVersion // KLNEKit SDK 버전 문자열
$environment.surgeVersion // 엔진 자체 버전, Surge 스크립트가 읽는 이름으로 제공
$environment.language // BCP 47 태그 형식의 선호 언어, 예: "en-US"
$environment.deviceModel // "iPhone", "Mac" 또는 "AppleTV"; Android에서는 기기 모델명, 예: "Pixel 8"
$utils — 유틸리티
$utils.geoip("1.2.3.4") // 국가 코드 (예: "US")
$utils.ipasn("1.2.3.4") // ASN 번호 (예: "13335")
$utils.ipaso("1.2.3.4") // ASN 소속 조직 이름(예: "CLOUDFLARENET")
$utils.ungzip(data) // gzip 데이터 압축 해제
$klne — 프록시 제어 API
스크립트에서 프록시 런타임을 제어합니다. $klne은 모든 스크립트 유형에서 사용할 수 있습니다.
$klne.getPolicyGroups() // 모든 정책 그룹 가져오기
$klne.selectGroupDetails() // 같은 그룹을 Surge 형태로
$klne.selectPolicy("Group", "Proxy") // 그룹의 정책 전환
$klne.getActiveConnections() // 활성 연결 나열
$klne.closeConnection("id") // getActiveConnections()가 알려준 id의 연결을 닫습니다
$klne.flushDNS() // DNS 캐시 제거
$klne.startURLTest("Group") // 그룹의 URL 테스트 트리거
$klne.reloadConfiguration() // 모든 구성 재로드
$klne.setOutboundMode("rule") // 모드 설정: "global"/"proxy", "direct", "rule"
$klne.setHTTPCaptureEnabled(true) // MitM 활성화/비활성화
$klne.setRewriteEnabled(true) // 재작성 계열 활성화/비활성화
getPolicyGroups()는 선택 가능한 정책 그룹을 반환합니다:
{
count: 1, // Number — 그룹 수
policyGroups: [{
name: "MainGroup", // String — 그룹 이름
type: 0, // Number — 0 select, 1 url-test, 2 fallback, 3 ssid, 5 load-balance
policyNames: ["A", "B"], // Array of String — 그룹의 실제 멤버 (구독으로 들어온 노드 포함)
selectedIndex: 0, // Number — policyNames 안에서 활성 정책의 인덱스 (확인할 수 없으면 없음)
selectedPolicy: "A" // String — 활성 정책의 이름 (확인할 수 없으면 없음)
}]
}
selectGroupDetails()는 같은 그룹을 Surge의 형태로 반환합니다 — policyGroups는 각 그룹 이름을 그 멤버 이름들에, decisions는 각 그룹 이름을 현재 선택된 멤버에 대응시킵니다:
{
policyGroups: {MainGroup: ["A", "B"]},
decisions: {MainGroup: "A"}
}
선택이 확정되지 않은 그룹은 decisions에서 빠집니다.
getActiveConnections()는 현재 연결을 설명하는 Array를 반환합니다:
[{
id: 1042, // Number — 연결 번호이며 closeConnection이 받는 값
host: "example.com", // String — 대상 호스트 (알 수 없으면 없음)
port: 443 // Number — 대상 포트
}]
나머지 메서드는 일반 인자를 받고 아무것도 반환하지 않습니다:
selectPolicy(group, policy)—policy(해당 그룹의policyNames에 있는 이름)를group의 활성 정책으로 만듭니다. 알 수 없는 그룹 또는 정책 이름은 로그에 경고를 남기고 무시됩니다.closeConnection(id)— 해당 id의 연결을 닫습니다. id는getActiveConnections()가 보고하는 번호입니다. 열려 있지 않은 id는 경고와 함께 무시됩니다.flushDNS()— DNS 캐시를 제거합니다.startURLTest(group)—url-test,fallback,load-balance그룹에 대해 비동기 지연 시간 테스트를 시작합니다; 다른 그룹 유형과 알 수 없는 이름은 경고와 함께 무시됩니다.reloadConfiguration()— 업데이트 간격을 기다리지 않고 프로필의#!MANAGED-CONFIG원본을 지금 다시 가져와, 달라졌으면 적용합니다. 엔진이 이미 들고 있는 구성을 다시 적용해도 달라지는 것이 없으므로, 관리 원본이 없는 프로필에서는 경고만 기록됩니다.setOutboundMode(mode)—"global"과"proxy"는 모두 모든 트래픽을 프록시로 보내고,"direct"는 모든 트래픽을 직접 보내며, 그 외의 값은 규칙 모드를 선택합니다.setHTTPCaptureEnabled(enabled)— 런타임에 HTTPS 복호화(MitM)를 활성화하거나 비활성화합니다; 불리언 값을 받습니다.setRewriteEnabled(enabled)— 런타임에 재작성 계열 전체(URL 재작성, 헤더 재작성, 본문 재작성, 모의 응답)를 활성화하거나 비활성화합니다; 불리언 값을 받습니다.
Surge 스크립트를 위해 $surge도 제공되며, 의미가 같은 호출은 다음과 같습니다. $surge.setSelectGroupPolicy(group, policy)(selectPolicy와 동일), $surge.selectGroupDetails()(selectGroupDetails와 동일), $surge.setOutboundMode(mode), $surge.setHTTPCaptureEnabled(enabled), $surge.setRewriteEnabled(enabled)(URL 재작성, 헤더 재작성, 본문 재작성, 모의 응답을 아우르는 재작성 기능 스위치), $surge.retestGroup(name). 그 밖의 Surge $surge 기능은 여기에 대응이 없어 undefined로 읽히므로 스크립트에서 확인할 수 있습니다.
$httpAPI — 제어 API 브리지
스크립트에서 엔진 자체의 HTTP 제어 API를 호출합니다. $httpAPI는 모든 스크립트 유형에서 사용할 수 있습니다.
$httpAPI("GET", "/api/status", null, function(result) {
console.log(result.statusCode) // Number — HTTP 상태
console.log(result.body.data) // Object — 파싱된 JSON 본문
})
$httpAPI("/api/status") // 인자 하나: 그 경로에 대한 GET
$httpAPI("DELETE", "/api/dns/cache") // 메서드와 경로
var result = $httpAPI("GET", "/api/status") // 같은 객체가 반환값으로도 돌아옵니다
이 호출은 동기입니다 — 요청은 엔진 내부에서 라우팅되고, {statusCode, body} 객체는 콜백에 전달되는 동시에 반환값으로도 돌아옵니다. path는 /로 시작해야 합니다. 문자열 body는 적힌 그대로 전송되고, 그 밖의 값은 JSON으로 인코딩됩니다. 요청이 리스너를 거치지 않으므로 토큰도 필요 없고 API를 켜 두지 않아도 됩니다. POST /api/scripts/run은 호출한 스크립트가 붙잡고 있는 엔진을 다시 필요로 하므로 409와 would_reenter로 거부되며, 12초 안에 응답하지 않는 경로는 504를 돌려줍니다.
$script — 스크립트 메타데이터
$script.name // 구성의 스크립트 이름
$script.type // 스크립트 유형 문자열
$script.startTime // 단조(monotonic) 타임스탬프 (시스템 부팅 기준 초, Epoch 아님)
$script.sessionID // 실행마다 다르며, 한 번의 실행 상태를 묶는 데 사용
실행별 전역 변수
다음 변수는 스크립트 실행마다 주입되며 특정 스크립트 유형에만 해당됩니다.
$argument — 스크립트 인자
스크립트 구성의 argument= 매개변수에서 전달된 문자열 값이며, 스크립트에 인자가 없으면 null입니다. 사용 가능: http-request, http-response, http-request-before-send, rule, dns, cron, event.
console.log("Argument: " + $argument)
$domain — DNS 도메인 (DNS 스크립트 전용)
쿼리 중인 도메인 이름입니다. dns 스크립트에서만 사용 가능합니다.
var domain = $domain // 예: "example.com"
$cronexp — Cron 표현식 (Cron 스크립트 전용)
스크립트 구성의 cron 일정 표현식입니다. cron 스크립트에서만 사용 가능합니다.
console.log("Schedule: " + $cronexp) // 예: "*/30 * * * *"
$event — 이벤트 정보 (이벤트 스크립트 전용)
트리거 이벤트에 대한 정보입니다. Chute는 network-changed, engine-started, profile-reloaded를 발생시킵니다 — 이벤트 스크립트를 참고하세요.
console.log("Event: " + $event.name) // "network-changed"
$event.name은 실제로 발생한 이벤트입니다. event-name=이 붙은 스크립트는 그 이벤트에서만 실행되므로 이름은 늘 자신이 선언한 것이고, event-name=이 없는 스크립트는 세 이벤트 모두에서 실행되므로 $event.name으로 어느 쪽인지 구분합니다.
console — 로깅
console.log("Debug message") // 상세 로그
console.warn("Warning message") // 경고 로그
console.error("Error message") // JavaScript 오류로 태그된 경고 로그
각 메시지는 256자에서 잘리며, 메시지 안의 자격 증명 — Authorization이나 Cookie 헤더, password=나 token= 값 등 — 은 <redacted>로 기록됩니다. console.log는 verbose 수준으로 기록하므로 loglevel이 verbose일 때만 보입니다. console.warn과 console.error는 기본 수준인 warning에서 보입니다.
setTimeout(fn, seconds) — 타이머
지연 후 실행할 함수를 예약합니다.
setTimeout(function() {
console.log("Delayed execution")
}, 2.5) // 2.5초
$scriptImport(subScriptPath) — 하위 스크립트 로더
다른 JavaScript 파일을 로드하고 평가합니다. 로컬 파일 경로만 지원됩니다. http(s):// 및 file:// URL은 거부됩니다. ($script는 스크립트 메타데이터 객체를 위해 예약되어 있습니다.)
$scriptImport("/path/to/helper.js")
// $persistentStore를 사용하여 스크립트 간 데이터 전달
스크립트 유형 세부 정보
세 가지 HTTP 스크립트 유형은 일반 HTTP 요청은 Chute의 HTTP 프록시를 거쳐 들어올 때만, HTTPS 요청은 그 호스트가 복호화될 때만 봅니다 — HTTPS 복호화의
hostname에 호스트를 추가하세요.
HTTP 요청 스크립트
요청 헤더가 수신될 때 실행됩니다. 요청이 전달되기 전에 URL, 헤더 및 본문을 수정할 수 있습니다.
[Script]
ModifyHeaders = type=http-request, script-path=modify.js, pattern=^https://api\.example\.com
HTTP 응답 스크립트
응답 헤더가 수신될 때 실행됩니다. 클라이언트에 반환되기 전에 상태, 헤더 및 본문을 수정할 수 있습니다.
[Script]
ModifyResponse = type=http-response, script-path=response.js, pattern=^https://api\.example\.com
본문을 받는 스크립트는 본문이 전부 도착한 뒤에 실행됩니다. HTTP/1.x에서 그 전에 서버가 연결을 닫으면, Content-Length도 청크 인코딩도 없는 본문은 연결이 끝나는 곳이 본문의 끝이므로 완전한 것으로 보고 스크립트가 평소대로 실행됩니다. Content-Length나 청크 인코딩으로 구획되는데 연결 종료로 잘린 본문은 스크립트에 전달되지 않고 받은 그대로 클라이언트에 전달되며, 그런 다음 연결이 닫힙니다. 같은 메시지에 일치하는 본문 재작성 규칙이 먼저 적용되며, 스크립트는 재작성된 본문을 받습니다.
HTTP 요청 전송 전 스크립트
요청이 업스트림으로 전송되기 직전에 실행됩니다. 본문이 보류될 때(이 스크립트에 requires-body=true가 있거나, 본문 재작성 규칙이나 http-request 스크립트가 본문을 가져갈 때)는 본문이 모두 도착한 뒤 실행됩니다. 본문이 없거나 max-size보다 큰 요청은 보류되지 않으며, 보류하는 도중 max-size를 넘은 본문은 들어온 그대로 전달됩니다. 어느 경우든 스크립트는 헤더가 나갈 때 빈 본문으로 실행되고 requires-body=true인 스크립트는 건너뜁니다. POST/PUT 요청 본문 수정에 유용합니다. 이 스크립트는 마지막에 실행됩니다: 본문 재작성 규칙과 본문을 받는 http-request 스크립트 다음에 실행되며, 그들이 만든 본문을 받습니다. 스크립트가 돌려준 본문은 요청의 원래 본문을 대체하며(원래 본문은 버려짐), Content-Length도 그에 맞게 설정됩니다.
[Script]
BeforeSend = type=http-request-before-send, script-path=before-send.js, pattern=^https://api\.example\.com, requires-body=true
규칙 스크립트
사용자 정의 규칙 일치입니다. 스크립트는 $done({matched: true}) 또는 $done({matched: false})를 호출해야 합니다.
[Rule]
SCRIPT,MyRuleScript,DIRECT
[Script]
MyRuleScript = type=rule, script-path=rule.js
DNS 스크립트
사용자 정의 DNS 해석입니다. $domain을 받고 해석된 주소(들)를 반환합니다.
// dns.js
var domain = $domain
if (domain === "internal.example.com") {
$done({address: "10.0.0.1", ttl: 300})
} else {
$done({}) // 일반 DNS 해석으로 통과
}
[Host]의 <domain> = script:<name> 항목은 일치하는 도메인의 조회를 지정한 이름의 DNS 스크립트로 보냅니다. 로컬 DNS 매핑을 참고하세요.
Cron 스크립트
Cron 표현식을 사용한 예약 실행입니다. 최소 간격은 60초입니다.
[Script]
HalfHourlyTask = type=cron, script-path=task.js, cron-expression=*/30 * * * *
표현식은 정확히 다섯 개의 필드를 한 칸 공백으로 구분해 적어야 합니다. 그 밖의 형태 — 두 칸 공백, 탭, 네 필드, 여섯 필드 — 는 스크립트를 조용히 비활성화합니다: 일정에 등록되지 않고, 로그에도 아무것도 남지 않습니다.
다섯 필드 가운데 분 필드만 적용됩니다: */N은 N분마다 실행됩니다. 그 밖의 분 필드는 60초마다 실행되며, 스크립트가 직접 현재 시각을 확인하여 동작 여부를 결정해야 합니다.
이벤트 스크립트
시스템 이벤트에 의해 트리거됩니다. 세 가지 이벤트가 발생합니다:
| 이벤트 | 발생 시점 |
|---|---|
network-changed |
Wi-Fi 또는 셀룰러 네트워크가 바뀌었을 때 |
engine-started |
엔진 시작이 끝나 정책·규칙·리스너가 살아났을 때 |
profile-reloaded |
구성 재로드가 끝났을 때. 재로드된 프로필 자신의 스크립트에서 발생합니다 |
[Script]
NetChange = type=event, script-path=network-changed.js
$event 객체 사용 가능:
$event.name // "network-changed", "engine-started" 또는 "profile-reloaded"
Surge는 event-name=으로 이벤트를 지정하며, Chute도 이를 읽습니다. event-name=engine-started는 그 이벤트에서만 스크립트를 실행합니다. 이벤트를 지정하지 않은 스크립트는 세 이벤트 모두에서 실행되며, $event.name으로 어느 것이 발생했는지 확인합니다. 그 밖의 이벤트(Surge는 notification 같은 이벤트도 발생시킵니다)에 연결된 스크립트는 Chute에서 실행되지 않으며, 구성을 로드할 때 로그에 그 사실이 남습니다.
실용 예제
모바일 기기 리디렉션
User-Agent를 기반으로 모바일 사용자를 리디렉션하는 http-request 스크립트:
[Script]
MobileRedirect = type=http-request, script-path=mobile-redirect.js, pattern=^https://example\.com
// mobile-redirect.js
var ua = $request.headers["User-Agent"] || ""
if (/Mobile|Android|iPhone/.test(ua)) {
$done({
response: {
status: 302,
headers: {"Location": "https://m.example.com" + $request.url.replace(/.*example\.com/, "")},
body: ""
}
})
} else {
$done({})
}
API 응답에서 콘텐츠 차단
JSON API 응답에서 광고 및 스폰서 콘텐츠를 제거하는 http-response 스크립트:
[Script]
RemoveAds = type=http-response, script-path=remove-ads.js, pattern=^https://api\.example\.com/feed, requires-body=true
// remove-ads.js
var body = JSON.parse($response.body)
if (body.ads) {
delete body.ads
}
if (body.recommendations) {
body.recommendations = body.recommendations.filter(function(r) {
return !r.sponsored
})
}
$done({body: JSON.stringify(body)})
전송 전 요청 본문 수정
POST 페이로드를 정리하는 http-request-before-send 스크립트:
[Script]
SanitizePayload = type=http-request-before-send, script-path=sanitize.js, pattern=^https://api\.example\.com/submit, requires-body=true
// sanitize.js
var body = JSON.parse($request.body)
body.clientSecret = "[REDACTED]"
body.timestamp = Math.floor(Date.now() / 1000)
$done({body: JSON.stringify(body)})
사용자 정의 규칙: 시간 기반 라우팅
시간대에 따라 다른 프록시를 선택하는 rule 스크립트:
[Rule]
SCRIPT,TimeBasedRule,ProxyA
[Script]
TimeBasedRule = type=rule, script-path=time-rule.js
// time-rule.js
var hour = new Date().getHours()
if (hour >= 9 && hour < 18) {
$done({matched: false}) // 근무 시간에는 다음 규칙으로 폴스루
} else {
$done({matched: true}) // 근무 외 시간에는 ProxyA 사용
}
내부 도메인용 사용자 정의 DNS
내부 호스트명을 로컬 IP로 해석하는 dns 스크립트:
[Script]
InternalDNS = type=dns, script-path=internal-dns.js
// internal-dns.js
var internalHosts = {
"gitlab.local": "10.0.0.10",
"registry.local": "10.0.0.11",
"monitor.local": "10.0.0.12"
}
if (internalHosts[$domain]) {
$done({address: internalHosts[$domain], ttl: 3600})
} else {
$done({}) // 일반 DNS로 통과
}
네트워크 변경 시 자동 정책 전환
네트워크 변경 후 연결성을 확인하고 그에 따라 정책 그룹을 전환하는 event 스크립트:
[Script]
NetSwitch = type=event, script-path=network-switch.js, timeout=15
// network-switch.js
$httpClient.head("https://www.google.com/generate_204", {timeout: 5},
function(error, response, data) {
if (error) {
// 새 네트워크에서 프로브 실패 — 백업 그룹으로 전환
$klne.selectPolicy("MainGroup", "BackupProxy")
console.log("Probe failed after " + $event.name)
} else {
$klne.selectPolicy("MainGroup", "MainProxy")
}
$done({})
})
주의:
$network는 모든 스크립트에서 보이지만wifi.ssid에 값이 들어가는 것은 iOS와 Android뿐입니다($network의 참고 사항 참조). macOS·tvOS에서는 비어 있으므로, 어느 플랫폼에서나 동작해야 하는 event 또는 cron 스크립트는 SSID를 읽는 대신 위처럼$httpClient로 프로브하세요.
주기적 상태 확인
30분마다 프록시 상태를 확인하는 cron 스크립트:
[Script]
HealthCheck = type=cron, script-path=health-check.js, timeout=15, cron-expression=*/30 * * * *
// health-check.js
$httpClient.head("https://www.google.com/generate_204", {timeout: 10},
function(error, response, data) {
if (error || response.status !== 204) {
console.error("Health check failed: " + (error || "status " + response.status))
$notification.post("Chute Alert", "Health Check", "Cannot reach Google")
} else {
console.log("Health check OK")
}
$done({})
}
)
콜백 내부에서
$done({})을 호출하세요 — 스크립트가 완료되면 대기 중인 모든$httpClient요청이 취소되므로, 스크립트 끝에서 동기적으로$done()을 호출하면 응답이 도착하기 전에 상태 확인이 취소됩니다. 같은 이유로 요청 자체의timeout은 스크립트의 것보다 짧아야 합니다: 스크립트의timeout(기본값 5초)이 먼저 끝나면 요청은 취소되고 콜백은 실행되지 않습니다. 위의 두 예제는 모두[Script]줄에timeout=15를 설정합니다.
외부 데이터로 API 응답 보강
보조 API를 호출하여 사용자 데이터를 보강하는 http-response 스크립트:
[Script]
EnrichUsers = type=http-response, script-path=enrich.js, pattern=^https://api\.example\.com/users, requires-body=true, timeout=20
// enrich.js — 진행 중인 요청은 최대 8개, 스크립트별 한도
var users = JSON.parse($response.body)
var next = 0
var pending = 0
function launch() {
while (pending < 8 && next < users.length) {
fetchAvatar(next++)
}
if (pending === 0) {
$done({body: JSON.stringify(users)})
}
}
function fetchAvatar(index) {
pending++
$httpClient.get("https://internal-api.example.com/avatar/" + users[index].id,
function(error, resp, data) {
if (!error && resp.status === 200) {
users[index].avatar = JSON.parse(data).url
}
pending--
launch()
}
)
}
launch()
실행 모델
- 모든 스크립트는 스레드 안전을 위해 전용 직렬 큐에서 실행됩니다.
- 각 스크립트 실행에는 스크립트별 시간 초과가 있으며, 실행 시작부터 계산됩니다: 소스 로드(원격 스크립트는 이 시간 안에 다운로드됩니다), 코드 실행, 타이머와 요청 대기가 모두 이 시간을 함께 씁니다. 시간 초과 내에
$done()이 호출되지 않으면 스크립트는 통과로 처리됩니다.setTimeout타이머나$httpClient요청이 남아 있지 않은 상태에서$done()없이 반환한 스크립트는 곧바로 통과로 처리됩니다. - 미리 준비된 3개의 컨텍스트 풀이 유지됩니다. 각 실행 후 컨텍스트는 폐기되고 새 컨텍스트로 교체되므로 전역 변수가 실행 간에 누출되지 않습니다.
- Chute는 스크립트 엔진이 시작될 때 프로세스의 메모리 사용량을 기록합니다. 그 뒤 사용량이 iOS와 tvOS에서는 10 MB, macOS에서는 512 MB를 넘게 늘어나면 모든 스크립트를 건너뛰고 해당 메시지는 그대로 통과합니다. Android에서는 사용 중인 Java 힙을 기준으로 하며 여유분은 512 MB입니다. 이 여유분은 스크립트 하나가 아니라 프로세스 전체에 대한 것이며, 기준점은 Chute가 실행되는 동안 다시 잡히지 않습니다.
- iOS, tvOS, Android에서는 동시에 최대 16개(macOS에서는 64개)의 스크립트 실행이 진행될 수 있으며, 이들의 스크립트 소스는 합쳐서 최대 4 MB(macOS에서는 8 MB)입니다. 어느 한도든 넘는 실행은 건너뛰고 해당 메시지는 그대로 통과하며, 로그에
execution admission is full이 남습니다. - 스크립트 소스는 macOS에서 최대 1 MB, iOS·tvOS·Android에서 최대 512 KB입니다. 구성이 선언할 수 있는 스크립트 개수에는 제한이 없습니다. 제한되는 것은 동시에 로드할 수 있는 스크립트 소스의 개수로, macOS 32개, iOS·tvOS·Android 8개입니다. 이를 넘기면 로그에
source load queue is full이 남고 그 실행은 소스 없이 진행되므로, 결국 통과가 됩니다. - 원격 스크립트(HTTP/HTTPS 경로)는 매 실행 시 가져옵니다.
script-update-interval이 양수이거나 정확히-1이면 Chute는 추가로 10분마다 조건부 HEAD 요청으로 URL을 폴링합니다. 이 값은 주기가 아니라 스위치일 뿐이어서, 숫자가 무엇이든 폴링은 10분마다 일어납니다.0(기본값)과 그 밖의 음수 값은 폴링을 켜지 않습니다.
모듈 스크립트 통합
스크립트는 모듈 파일(.sgmodule)의 [Script] 섹션에서도 정의할 수 있습니다.
이 페이지는 영어판의 번역본입니다. 내용이 다를 경우 영어판이 우선합니다.