JavaScript 스크립팅

Chute는 고급 요청/응답 수정, 사용자 정의 규칙 매칭, DNS 해석 및 예약 작업을 위한 JavaScript 스크립팅을 지원합니다. 스크립트는 Apple의 JavaScriptCore 엔진을 사용하며 Surge 호환 스크립트 API를 따릅니다.

스크립트는 설정 파일의 [Script] 섹션에 정의됩니다.

구성

[Script]
MyScript = type=http-request, script-path=/path/to/script.js, pattern=^https?://example\.com, requires-body=true, max-size=262144, 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 정규식 패턴
requires-body 아니오 자동 감지 스크립트가 전체 요청/응답 본문을 수신하도록 강제
max-size 아니오 131072 (128KB) 본문 접근 스크립트의 최대 본문 크기(바이트). 수집된 본문이 이 크기를 초과하면 해당 요청에 대해 스크립트를 건너뜁니다(본문이 잘리지 않음)
timeout 아니오 5.0초 스크립트별 실행 시간 초과. 30초를 초과하는 값은 30초로 제한됩니다
argument 아니오 JS에서 $argument로 사용 가능한 사용자 정의 문자열 인자
debug 아니오 false 예약됨. 현재는 효과가 없습니다
cron-expression 아니오 Cron 일정 표현식 (cron 유형 전용)
wake-system 아니오 false 예약됨. 파싱되지만 현재는 효과가 없습니다
enable 아니오 true 이 스크립트 활성화 또는 비활성화
script-update-interval 아니오 0 원격 스크립트 업데이트 폴링 (실행 모델 참조)

스크립트 유형

유형 문자열 열거형 설명
http-request HTTP 요청 업스트림 전 HTTP 요청 가로채기 및 수정
http-response HTTP 응답 클라이언트 전 HTTP 응답 가로채기 및 수정
http-request-before-send HTTP 요청 전송 전 전체 본문 수집 후, 전송 전 요청 수정
rule 규칙 사용자 정의 규칙 매칭 로직
dns DNS 사용자 정의 DNS 해석
cron Cron 예약/타이머 스크립트
event 이벤트 시스템 이벤트 핸들러 (예: network-changed)

본문 자동 감지: 스크립트 소스에 $request.body 또는 $response.body가 포함되어 있으면, 본문이 자동으로 max-size까지 제공됩니다. 이 동작을 강제하려면 requires-body=true를 사용하세요.


JavaScript API 참조

스크립트는 다음 전역 객체가 사용 가능한 샌드박스 JavaScriptCore 환경에서 실행됩니다.

$request (읽기 전용)

사용 가능: http-request, http-response, http-request-before-send, rule

속성 유형 설명
.url String 전체 요청 URL
.method String HTTP 메서드 (GET, POST 등) 또는 DNS의 경우 QUERY
.headers Object 키-값 쌍으로 된 요청 헤더
.body String 또는 null 요청 본문 (UTF-8 디코딩)
.bodyBytes Uint8Array 또는 null 원시 요청 본문 바이트
.hostname String 대상 호스트명
.destPort Number 대상 포트
.processPath String 요청 프로세스 경로 (macOS 전용)
.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 키-값 쌍으로 된 응답 헤더
.body String 또는 null 응답 본문 (UTF-8 디코딩)
.bodyBytes Uint8Array 또는 null 원시 응답 본문 바이트
.rawBody Uint8Array 또는 null .bodyBytes의 별칭

$done(value) — 완료 핸들러

스크립트 종료 시 완료를 알리기 위해 반드시 정확히 한 번 호출해야 합니다. $done()이 호출되거나 시간 초과가 만료될 때까지 스크립트 실행이 차단됩니다.

$done({})                        // 통과 — 수정 없음
$done()                          // 연결 중단
$done({matched: true})           // 규칙 일치 결과 (규칙 스크립트 전용)
$done({address: "1.2.3.4"})      // DNS 결과 (DNS 스크립트 전용)

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는 업스트림 서버에 접속하지 않고 클라이언트에 직접 합성 응답을 반환합니다. 이는 차단, API 모의 또는 캐시된 콘텐츠 반환에 유용합니다.

HTTP 응답 스크립트 반환 값:

$done({
    status: 200,                              // 상태 코드 수정
    headers: {"X-Custom": "value"},           // 응답 헤더 수정
    body: "new response body",                // 응답 본문 수정
    url: "https://other.example.com"          // 302 리디렉션 트리거
})

url이 제공되면 Chute는 원래 응답 대신 주어진 URL로 302 리디렉션을 반환합니다. urlstatus / 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)

server / servers 키는 허용되지만 현재는 무시됩니다. 해석은 일반 DNS로 폴스루됩니다.

$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)

콜백 시그니처: callback(error, response, data)

  • error: 오류 문자열 또는 null
  • response: {status: Number, headers: Object} 또는 null
  • data: UTF-8 문자열 응답 본문 또는 null

$httpClienthttp-request, http-response, http-request-before-send, cron, event 스크립트에서 사용할 수 있습니다. ruledns 스크립트에서는 사용할 수 없습니다.

$persistentStore — 키-값 저장소

스크립트 및 프로세스 재시작 간에도 유지되는 영구 키-값 저장소입니다. NSUserDefaults로 지원됩니다.

$persistentStore.write(data, key)   // 값 저장
$persistentStore.read(key)          // 값 조회
$persistentStore.remove(key)        // 값 제거

$notification — 로컬 알림

로컬 시스템 알림을 게시합니다.

$notification.post("Title", "Subtitle", "Notification body text")

$network — 네트워크 정보

읽기 전용 네트워크 상태 정보입니다.

$network.dns   // DNS 서버 IP 배열
$network.wifi  // {ssid: "WiFiName", bssid: null}

bssid는 항상 null입니다. ssid는 iOS에서만 채워집니다.

알림: 현재 릴리스에서는 브리징 제한으로 인해 $network$environment 속성이 스크립트에 표시되지 않으며 undefined로 읽힙니다. $script 메타데이터는 cron 및 event 스크립트에서만 사용할 수 있습니다.

$environment — 런타임 정보

$environment.system     // "iOS" 또는 "macOS"
$environment.appVersion // KLNEKit SDK 버전 문자열

$utils — 유틸리티

$utils.geoip("1.2.3.4")   // 국가 코드 (예: "US")
$utils.ipasn("1.2.3.4")   // ASN 번호 (예: "13335")
$utils.ungzip(data)       // gzip 데이터 압축 해제

$klne — 프록시 제어 API

스크립트에서 프록시 런타임을 제어합니다. $klne은 모든 스크립트 유형에서 사용할 수 있습니다.

$klne.getPolicyGroups()               // 모든 정책 그룹 가져오기
$klne.selectPolicy("Group", "Proxy")  // 그룹의 정책 전환
$klne.getActiveConnections()          // 활성 연결 나열
$klne.closeConnection("id")           // 예약됨 — 현재는 아무 동작도 하지 않음
$klne.flushDNS()                      // DNS 캐시 제거
$klne.startURLTest("Group")           // 그룹의 URL 테스트 트리거
$klne.reloadConfiguration()           // 모든 구성 재로드
$klne.setOutboundMode("rule")         // 모드 설정: "global"/"proxy", "direct", "rule"
$klne.setHTTPCaptureEnabled(true)     // MitM 활성화/비활성화

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 — 활성 정책의 인덱스 (확인할 수 없으면 없음)
    selectedPolicy: "A"        // String — 활성 정책의 이름 (확인할 수 없으면 없음)
  }]
}

getActiveConnections()는 현재 연결을 설명하는 Array를 반환합니다:

[{
  id: "0x600002f01230",  // String — 불투명한 연결 식별자
  host: "example.com",   // String — 대상 호스트 (알 수 없으면 없음)
  port: 443              // Number — 대상 포트
}]

나머지 메서드는 일반 인자를 받고 아무것도 반환하지 않습니다:

  • selectPolicy(group, policy)policy(해당 그룹의 policyNames에 있는 이름)를 group의 활성 정책으로 만듭니다. 알 수 없는 그룹 또는 정책 이름은 로그에 경고를 남기고 무시됩니다.
  • closeConnection(id) — 예약됨. 현재 커널은 개별 연결을 종료할 수 없습니다; 호출해도 경고만 기록될 뿐 아무것도 종료되지 않습니다.
  • flushDNS() — DNS 캐시를 제거합니다.
  • startURLTest(group)url-test 또는 fallback 그룹에 대해 비동기 지연 시간 테스트를 시작합니다; 다른 그룹 유형과 알 수 없는 이름은 경고와 함께 무시됩니다.
  • reloadConfiguration() — 현재 구성을 다시 로드합니다.
  • setOutboundMode(mode)"global""proxy"는 모두 모든 트래픽을 프록시로 보내고, "direct"는 모든 트래픽을 직접 보내며, 그 외의 값은 규칙 모드를 선택합니다.
  • setHTTPCaptureEnabled(enabled) — 런타임에 HTTPS 복호화(MitM)를 활성화하거나 비활성화합니다; 불리언 값을 받습니다.

$script — 스크립트 메타데이터

$script.name       // 구성의 스크립트 이름
$script.type       // 스크립트 유형 문자열
$script.startTime  // 단조(monotonic) 타임스탬프 (시스템 부팅 기준 초, Epoch 아님)

실행별 전역 변수

다음 변수는 스크립트 실행마다 주입되며 특정 스크립트 유형에만 해당됩니다.

$argument — 스크립트 인자

스크립트 구성의 argument= 매개변수에서 전달된 문자열 값입니다. 사용 가능: 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 — 이벤트 정보 (이벤트 스크립트 전용)

트리거 이벤트에 대한 정보입니다. 현재 network-changed만 지원됩니다.

console.log("Event: " + $event.name)  // "network-changed"

console — 로깅

console.log("Debug message")    // 상세 로그
console.warn("Warning message")  // 경고 로그
console.error("Error message")   // JavaScript 오류로 태그된 경고 로그

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 요청 스크립트

요청 헤더가 수신될 때 실행됩니다. 요청이 전달되기 전에 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 요청 전송 전 스크립트

전체 요청 본문이 수집된 후, 업스트림으로 전송 직전에 실행됩니다. POST/PUT 요청 본문 수정에 유용합니다.

[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 해석으로 통과
}

Cron 스크립트

Cron 표현식을 사용한 예약 실행입니다. 최소 간격은 60초입니다.

[Script]
HalfHourlyTask = type=cron, script-path=task.js, cron-expression=*/30 * * * *

표현식에서 분 필드만 적용됩니다: */N은 N분마다 실행됩니다. 그 외의 표현식은 60초마다 실행되며, 스크립트가 직접 현재 시각을 확인하여 동작 여부를 결정해야 합니다.

이벤트 스크립트

시스템 이벤트에 의해 트리거됩니다. 현재 network-changed 이벤트(WiFi 또는 셀룰러 네트워크 변경 시 발생)를 지원합니다.

[Script]
NetChange = type=event, script-path=network-changed.js

$event 객체 사용 가능:

$event.name  // "network-changed"

실용 예제

모바일 기기 리디렉션

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
// 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로 Wi-Fi SSID를 읽는 것이 자연스럽지만, $network는 아직 스크립트에 표시되지 않습니다($network의 알림 참조). 현재 릴리스에서는 $httpClient 프로브로 대신할 수 있습니다.

주기적 상태 확인

30분마다 프록시 상태를 확인하는 cron 스크립트:

[Script]
HealthCheck = type=cron, script-path=health-check.js, 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()을 호출하면 응답이 도착하기 전에 상태 확인이 취소됩니다.

외부 데이터로 API 응답 보강

보조 API를 호출하여 사용자 데이터를 보강하는 http-response 스크립트:

[Script]
EnrichUsers = type=http-response, script-path=enrich.js, pattern=^https://api\.example\.com/users, requires-body=true
// enrich.js
var users = JSON.parse($response.body)
var pending = users.length
if (pending === 0) { $done({}) }

users.forEach(function(user, index) {
    $httpClient.get("https://internal-api.example.com/avatar/" + user.id,
        function(error, resp, data) {
            if (!error && resp.status === 200) {
                users[index].avatar = JSON.parse(data).url
            }
            pending--
            if (pending === 0) {
                $done({body: JSON.stringify(users)})
            }
        }
    )
})

실행 모델

  • 모든 스크립트는 스레드 안전을 위해 전용 직렬 큐에서 실행됩니다.
  • 각 스크립트 실행에는 스크립트별 시간 초과가 있습니다. 시간 초과 내에 $done()이 호출되지 않으면 스크립트는 통과로 처리됩니다.
  • 미리 준비된 3개의 JSContext 풀이 유지됩니다. 각 실행 후 컨텍스트는 폐기되고 새 컨텍스트로 교체되므로 전역 변수가 실행 간에 누출되지 않습니다.
  • iOS/tvOS에서 스크립트는 10MB 메모리 제한이 적용됩니다. macOS에서는 512MB입니다.
  • 원격 스크립트(HTTP/HTTPS 경로)는 매 실행 시 가져옵니다. script-update-interval이 0이 아니면 Chute는 추가로 10분마다 조건부 HEAD 요청으로 URL을 폴링합니다. 0(기본값)은 폴링을 비활성화합니다.

모듈 스크립트 통합

스크립트는 모듈 파일(.sgmodule)의 [Script] 섹션에서도 정의할 수 있습니다.

S. Smart Rabbit LLC © All Rights Reserved            updated 2026-08-18 12:06:42

이 페이지는 영어판의 번역본입니다. 내용이 다를 경우 영어판이 우선합니다.

results matching ""

    No results matching ""