본문으로 건너뛰기

Web API

ECMAScript 자체 외에도 Phasis는 여러 계층형 WHATWG / W3C API 팩을 제공합니다.

  • 웹 플랫폼 팩 — I/O가 없는 순수 값 유형: URL, URLSearchParams, URLPattern, TextEncoder, TextDecoder, atob, btoa, structuredClone, performance, DOMException.
  • Fetch 팩 — JavaScript의 실제 HTTP: fetch, Request, Response, Headers, Body, AbortController, AbortSignal, Blob, File, FormData, EventTarget, Event, WHATWG Streams 전체(ReadableStream, WritableStream, TransformStream, 큐잉 전략, BYOB), CompressionStream / DecompressionStream(zlib를 통한 gzip / deflate / deflate-raw, kjdev/php-ext-brotli PECL 확장 로드 시 brotli), navigator, 그리고 HTML 사양의 self 전역 별칭 + reportError.
  • 암호화 — crypto.getRandomValues, crypto.randomUUID, SubtleCrypto 전체: digest(SHA-1/256/384/512), HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA(P-256/384/521), ECDH, HKDF, PBKDF2 및 JWK 가져오기/내보내기. ext-openssl를 기반으로 합니다.
  • WebSocket — 생성자, RFC 6455 프레임 코덱, 이벤트를 제공합니다. 기본 전송은 stream_socket_client를 사용하며 ReactPHP / Ratchet / 테스트 목의 경우 Engine::setWebSocketTransport()로 교체할 수 있습니다.
  • XMLHttpRequest — fetch 전송 계층 위에 구축되어 같은 정책 훅과 교체 가능한 전송을 적용합니다.
  • 이벤트 루프 — setTimeout, setInterval, clearTimeout, clearInterval, queueMicrotask를 제공합니다. 또한 마이크로태스크 / 타이머 경계를 넘어 값을 전파하는 AsyncContext(Stage 3)도 지원합니다.

모든 팩은 기본적으로 활성화되어 있습니다. 플래그나 설정 없이 새 new Engine()에서 typeof fetch가 "function"를 반환합니다.

적합성

가져온 Web Platform Tests 모음(tests/Wpt/fixtures/)은 AbortController / AbortSignal, atob / btoa, Blob / File, TextEncoder / TextDecoder, EventTarget / Event, FormData, Headers, performance.now, Streams Standard 전체(Readable/Writable/Transform + BYOB + 큐잉), structuredClone, URL / URLSearchParams, crypto / SubtleCrypto 전체 범위(digest, HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA, ECDH, HKDF, PBKDF2 — hkdf 및 pbkdf2의 수천 가지 derive-bits 조합 포함), WebSocket 상태 머신(생성자 URL 및 하위 프로토콜 검증, 종료 코드 의미론, 문자열 / ArrayBuffer / 형식화된 배열 뷰 / Blob를 위한 send(), binaryType, bufferedAmount, 연결 중 비동기 종료, 이벤트 핸들러 null 허용 여부), 그리고 Fetch + XMLHttpRequest 의 다양한 부분(responseType 상태 머신, 중단/전송 전환, CORS 허용 헤더, 멀티파트 FormData 업로드, XMLHttpRequestUpload + ProgressEvent, Response/Headers 초기화 검증, 정적 Response.json / Response.error 생성자, 리디렉션 체인에서 Authorization 제거, URL.createObjectURL Blob URL, 본문 믹스인 소비(text / blob / json / formData / arrayBuffer), structuredClone-d 티 스트림을 사용하는 Response.clone, 동기 XHR의 네트워크 오류 발생)을 다룹니다. 가져온 모든 하위 테스트가 통과합니다.

범위를 더 넓히려면 새 *.any.js를 tests/Wpt/fixtures/<category>/에 추가하면 됩니다. 실행기가 다음 실행에서 자동으로 처리합니다. XHR/Fetch 테스트는 WPT resources/*.py 핸들러를 단일 파일로 에뮬레이션하는 tests/Wpt/fetch-server.php가 실행합니다. WebSocket 테스트는 실행기 내부의 비활성 전송으로 연결/에코/종료를 시뮬레이션하며, 가져온 픽스처를 실행하기에 충분합니다. 직접 테스트 모음을 실행하려면 다음을 사용하세요.

셸
bin/wpt                # everything
bin/wpt --category headers --verbose   # one area, per-subtest output
bin/wpt --json         # machine-readable

fetch() 예제

JavaScript
const res = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "alice" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();

기본 설정만으로 작동합니다. 기본 전송 방식은 PHP ext-curl입니다. 본문은 WHATWG ReadableStream Phasis를 통해 스트리밍되므로 for await (const chunk of res.body)도 작동합니다.

AbortController 예제

JavaScript
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5000);
try {
  const res = await fetch(slowUrl, { signal: ctrl.signal });
} catch (e) {
  if (e.name === "AbortError") {
    // request was cancelled
  }
}

AbortSignal.timeout(ms)도 구현되어 있습니다(지연 마감 방식 — 이벤트 루프 도중 이벤트를 발생시키는 대신 aborted 읽기 시 확인합니다. 전송 중 신호를 확인하는 fetch 전송과 함께 작동합니다).

임베더 제어(PHP 측)

Phasis\Engine 클래스의 세 가지 훅으로 fetch 동작을 제어합니다.

PHP
$engine = new Phasis\Engine();

// 1. Swap the HTTP transport. The callable receives a request descriptor
//    and an optional AbortSignal; returns a response descriptor.
$engine->setFetchTransport(function (array $request, $signal) {
    // ... use Guzzle, Symfony HttpClient, your stub, etc.
    return [
        'status' => 200,
        'statusText' => 'OK',
        'headers' => [['content-type', 'application/json']],
        'body' => '{"data":"value"}',
    ];
});

// 2. Pre-flight policy hook — rewrite, deny, or allow.
$engine->setFetchPolicy(function ($request) {
    $url = $request->get('url')->value;
    if (!str_starts_with($url, 'https://')) {
        throw new \RuntimeException('HTTPS only');
    }
    return null;  // allow as-is; return modified Request to rewrite
});

// 3. Cookie jar. Opt-in. Accepts any object with get(url):string and
//    set(url, header):void methods. JsObject or PHP object both work.
$engine->setCookieJar($myJar);

setFetchTransport()를 호출하지 않으면 기본 CurlTransport가 실행됩니다. 쿠키 저장소는 선택 사항입니다. 기본 저장소가 없으며 직접 제공하지 않는 한 쿠키가 저장되지 않습니다.

Streams 예제

JavaScript
// Producer: ReadableStream that yields numbers 1..5
const rs = new ReadableStream({
  start(controller) {
    for (let i = 1; i <= 5; i++) controller.enqueue(i);
    controller.close();
  },
});

// Consumer: async iteration
for await (const value of rs) {
  console.log(value);  // 1, 2, 3, 4, 5
}

// Pipe through a transform
const ts = new TransformStream({
  transform(chunk, controller) { controller.enqueue(chunk * 2); },
});
const doubled = new ReadableStream({
  start(c) { [1, 2, 3].forEach(n => c.enqueue(n)); c.close(); }
}).pipeThrough(ts);
for await (const v of doubled) console.log(v);  // 2, 4, 6

Streams 사양 전체: ReadableStream, WritableStream, TransformStream, ReadableStreamDefaultController, ReadableByteStreamController, ReadableStreamDefaultReader, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy, 그리고 pipeTo / pipeThrough / tee.

이벤트 루프

setTimeout, setInterval, clearTimeout, clearInterval, queueMicrotask는 전역 객체에 있습니다. 마이크로태스크(Promise 핸들러, queueMicrotask)는 매크로태스크 사이에 모두 처리되며, 타이머는 영역별 큐에서 마감 시간 순으로 실행됩니다.

JavaScript
console.log('sync');
queueMicrotask(() => console.log('microtask'));
setTimeout(() => console.log('timer'), 0);
// → sync, microtask, timer

const id = setInterval(() => console.log('tick'), 1000);
// ...later
clearInterval(id);

await new Promise(r => setTimeout(r, 50));   // common "delay" pattern works

Phasis의 Engine::runEventLoop()를 기반으로 합니다. 동기 코드가 끝나면 eval()와 evalAsModule()가 자동으로 호출됩니다. 루프는 기본적으로 가상 시간을 사용합니다. setTimeout(cb, 5000)는 PHP 동안 대기하지 않습니다. 대신 루프가 마감 시간까지 시계를 진행시켜 프로그램을 신속히 완료합니다. 실시간 임베더는 직접 tickEventLoop()를 구동합니다. 호스트 측 API는 API: 이벤트 루프를 참조하세요.

제공되지 않는 기능

브라우저 전용 API 또는 런타임 모델 API는 의도적으로 범위에서 제외했습니다. 자세한 내용은 알려진 제한 사항을 참조하세요.

  • window, document, DOM 트리
  • localStorage, sessionStorage, IndexedDB
  • Worker, SharedWorker, MessageChannel
  • Service Worker, Push API, 알림, BroadcastChannel

필요한 경우 대부분은 임베더가 $engine->setGlobal()를 통해 폴리필로 구현할 수 있습니다.

WPT 테스트 모음 확장

tests/Wpt/upstream/는 web-platform-tests/wpt의 sparse-checkout으로, fetch/, streams/, FileAPI/, xhr/formdata/, dom/abort/, dom/events/를 포함합니다. 범위를 넓히려면 upstream/<area>/의 *.any.js 파일을 tests/Wpt/fixtures/<category>/에 복사하세요. 다음 실행부터 실행기가 자동으로 처리하며, 실패는 bin/wpt --verbose를 통해 기록됩니다.