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, debug=true
CronJob = type=cron, script-path=/path/to/cron.js, cron-expression=* * * * *, wake-system=true
스크립트 매개변수
| 매개변수 | 필수 | 기본값 | 설명 |
|---|---|---|---|
type |
예 | http-request |
스크립트 트리거 유형 (아래 참조) |
script-path |
예 | — | JS 스크립트의 로컬 파일 경로 또는 HTTP(S) URL |
pattern |
아니오 | (모두 일치) | 스크립트 트리거 시점을 필터링하는 URL 정규식 패턴 |
requires-body |
아니오 | 자동 감지 | 스크립트가 전체 요청/응답 본문을 수신하도록 강제 |
max-size |
아니오 | 131072 (128KB) | 본문 접근 스크립트의 최대 본문 크기(바이트) |
timeout |
아니오 | 5.0초 | 스크립트별 실행 시간 초과 |
argument |
아니오 | — | JS에서 $argument로 사용 가능한 사용자 정의 문자열 인자 |
debug |
아니오 | false | 디버깅을 위해 컴파일 캐시 건너뛰기 |
cron-expression |
아니오 | — | Cron 일정 표현식 (cron 유형 전용) |
wake-system |
아니오 | false | cron 스크립트 실행을 위해 시스템 깨우기 (iOS 전용) |
enable |
아니오 | true | 이 스크립트 활성화 또는 비활성화 |
script-update-interval |
아니오 | 0 | 원격 스크립트 업데이트 간격(초) (-1 = 안 함, 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 디코딩) |
.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 디코딩) |
$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 리디렉션을 반환합니다.
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: "8.8.8.8"}) // 특정 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: 오류 문자열 또는nullresponse:{status: Number, headers: Object}또는nulldata: UTF-8 문자열 응답 본문 또는null
$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: "aa:bb:cc:dd:ee:ff"}
$environment — 런타임 정보
$environment.system // "iOS" 또는 "macOS"
$environment.appVersion // 앱 버전 문자열
$utils — 유틸리티
$utils.geoip("1.2.3.4") // 국가 코드 (예: "US")
$utils.ipasn("1.2.3.4") // ASN 번호 (예: "13335")
$utils.ungzip(data) // gzip 데이터 압축 해제
$klne — 프록시 제어 API
스크립트에서 프록시 런타임을 제어합니다.
$klne.policyGroups // 모든 정책 그룹 가져오기
$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 활성화/비활성화
$script — 스크립트 메타데이터
$script.name // 구성의 스크립트 이름
$script.type // 스크립트 유형 문자열
$script.startTime // Epoch 타임스탬프
실행별 전역 변수
다음 변수는 스크립트 실행마다 주입되며 특정 스크립트 유형에만 해당됩니다.
$argument — 스크립트 인자
스크립트 구성의 argument= 매개변수에서 전달된 문자열 값입니다. 사용 가능: http-request, http-response, http-request-before-send, rule, dns, cron.
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") // [JS-ERROR] 접두사가 붙은 경고 로그
setTimeout(fn, seconds) — 타이머
지연 후 실행할 함수를 예약합니다.
setTimeout(function() {
console.log("Delayed execution")
}, 2.5) // 2.5초
$script(subScriptPath) — 하위 스크립트 로더
다른 JavaScript 파일을 로드하고 평가합니다.
$script("/path/to/helper.js")
$script("https://example.com/remote-script.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]
HourlyTask = type=cron, script-path=hourly.js, cron-expression=0 * * * *
간소화된 cron 구문이 지원됩니다(예: 30분마다 */30 * * * *, 쉼표로 구분된 값).
이벤트 스크립트
시스템 이벤트에 의해 트리거됩니다. 현재 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
if (!$network.wifi.ssid) {
// 셀룰러 — 저데이터 그룹 사용
$klne.selectPolicy("MainGroup", "LowDataProxy")
console.log("Switched to cellular profile")
} else if ($network.wifi.ssid === "Office") {
$klne.selectPolicy("MainGroup", "DIRECT")
console.log("Switched to office profile")
}
주기적 상태 확인
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()
외부 데이터로 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()이 호출되지 않으면 스크립트는 통과로 처리됩니다. - JSContext 풀(크기 3)이 성능을 위해 사용됩니다. 컨텍스트는 실행 간에 재사용됩니다.
- 스크립트 컴파일은 기본적으로 캐시됩니다. 캐시를 우회하려면
debug=true를 사용하세요. - iOS/tvOS에서 스크립트는 10MB 메모리 제한이 적용됩니다. macOS에서는 512MB입니다.
- 원격 스크립트(HTTP/HTTPS 경로)는 시작 시 가져오며, 선택적으로
script-update-interval초마다 다시 가져옵니다.
모듈 스크립트 통합
스크립트는 [Script] 섹션 아래의 모듈 파일(.sgmodule)에서도 정의할 수 있습니다.
```