Mulai Chromium 105, Anda dapat memulai permintaan sebelum seluruh isi tersedia dengan menggunakan Streams API.
Anda dapat menggunakannya untuk:
- Hangatkan server. Dengan kata lain, Anda dapat memulai permintaan setelah pengguna memfokuskan kolom input teks, dan menyingkirkan semua header, lalu menunggu hingga pengguna menekan 'kirim' sebelum mengirimkan data yang dimasukkan.
- Mengirim data yang dibuat di klien secara bertahap, seperti data audio, video, atau input.
- Buat ulang web socket melalui HTTP/2 atau HTTP/3.
Namun, karena ini adalah fitur platform web tingkat rendah, jangan batasi diri Anda dengan ide saya. Mungkin Anda dapat memikirkan kasus penggunaan yang jauh lebih menarik untuk streaming permintaan.
Sebelumnya dalam petualangan seru pengambilan aliran
Aliran Respons telah tersedia di semua browser modern sejak beberapa waktu lalu. Dengan demikian, Anda dapat mengakses bagian respons saat respons tersebut tiba dari server:
const response = await fetch(url);
const reader = response.body.getReader();
while (true) {
const {value, done} = await reader.read();
if (done) break;
console.log('Received', value);
}
console.log('Response fully received');
Setiap value
adalah Uint8Array
byte.
Jumlah array yang Anda dapatkan dan ukuran array bergantung pada kecepatan jaringan.
Jika menggunakan koneksi cepat, Anda akan mendapatkan lebih sedikit 'potongan' data yang lebih besar.
Jika koneksi Anda lambat, Anda akan mendapatkan lebih banyak potongan yang lebih kecil.
Jika Anda ingin mengonversi byte menjadi teks, Anda dapat menggunakan TextDecoder
, atau aliran transformasi yang lebih baru jika browser target Anda mendukungnya:
const response = await fetch(url);
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
TextDecoderStream
adalah aliran transformasi yang mengambil semua potongan Uint8Array
tersebut dan mengonversinya menjadi string.
Aliran data sangat bagus karena Anda dapat mulai menindaklanjuti data saat data tiba. Misalnya, jika Anda menerima daftar 100 'hasil', Anda dapat menampilkan hasil pertama segera setelah Anda menerimanya, daripada menunggu semua 100 hasil.
Bagaimanapun, itulah aliran respons, hal baru yang menarik yang ingin saya bahas adalah aliran permintaan.
Isi permintaan streaming
Permintaan dapat memiliki isi:
await fetch(url, {
method: 'POST',
body: requestBody,
});
Sebelumnya, Anda memerlukan seluruh isi siap digunakan sebelum dapat memulai permintaan, tetapi sekarang di Chromium 105, Anda dapat memberikan ReadableStream
data Anda sendiri:
function wait(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
const stream = new ReadableStream({
async start(controller) {
await wait(1000);
controller.enqueue('This ');
await wait(1000);
controller.enqueue('is ');
await wait(1000);
controller.enqueue('a ');
await wait(1000);
controller.enqueue('slow ');
await wait(1000);
controller.enqueue('request.');
controller.close();
},
}).pipeThrough(new TextEncoderStream());
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'text/plain'},
body: stream,
duplex: 'half',
});
Di atas akan mengirim "This is a slow request" ke server, satu kata dalam satu waktu, dengan jeda satu detik di antara setiap kata.
Setiap bagian isi permintaan harus berukuran Uint8Array
byte, jadi saya menggunakan pipeThrough(new TextEncoderStream())
untuk melakukan konversi.
Pembatasan
Permintaan streaming adalah kemampuan baru untuk web, sehingga memiliki beberapa batasan:
Half duplex?
Agar aliran dapat digunakan dalam permintaan, opsi permintaan duplex
harus disetel ke 'half'
.
Fitur HTTP yang kurang dikenal (meskipun, apakah ini perilaku standar atau tidak bergantung pada siapa yang Anda tanyai) adalah Anda dapat mulai menerima respons saat Anda masih mengirim permintaan. Namun, metode ini sangat tidak dikenal, sehingga tidak didukung dengan baik oleh server, dan tidak didukung oleh browser mana pun.
Di browser, respons tidak akan tersedia hingga isi permintaan dikirim sepenuhnya, meskipun server mengirimkan respons lebih awal. Hal ini berlaku untuk semua pengambilan browser.
Pola default ini dikenal sebagai 'half duplex'.
Namun, beberapa penerapan, seperti fetch
di Deno, secara default menggunakan 'full duplex' untuk pengambilan streaming, yang berarti respons dapat tersedia sebelum permintaan selesai.
Jadi, untuk mengatasi masalah kompatibilitas ini, di browser, duplex: 'half'
harus ditentukan pada permintaan yang memiliki isi streaming.
Pada masa mendatang, duplex: 'full'
mungkin didukung di browser untuk permintaan streaming dan non-streaming.
Sementara itu, cara terbaik berikutnya untuk komunikasi dupleks adalah melakukan satu pengambilan dengan permintaan streaming, lalu melakukan pengambilan lain untuk menerima respons streaming. Server akan memerlukan cara untuk mengaitkan kedua permintaan ini, seperti ID di URL. Begitulah cara kerja demo.
Pengalihan yang dibatasi
Beberapa bentuk pengalihan HTTP mengharuskan browser mengirim ulang isi permintaan ke URL lain. Untuk mendukung hal ini, browser harus melakukan buffering konten streaming, yang agak tidak sesuai dengan tujuannya, jadi browser tidak melakukannya.
Sebagai gantinya, jika permintaan memiliki isi streaming, dan responsnya adalah pengalihan HTTP selain 303, pengambilan akan ditolak dan pengalihan tidak akan diikuti.
Pengalihan 303 diizinkan, karena secara eksplisit mengubah metode menjadi GET
dan membuang isi permintaan.
Memerlukan CORS dan memicu preflight
Permintaan streaming memiliki isi, tetapi tidak memiliki header Content-Length
.
Ini adalah jenis permintaan baru, sehingga CORS diperlukan, dan permintaan ini selalu memicu preflight.
Permintaan streaming no-cors
tidak diizinkan.
Tidak berfungsi di HTTP/1.x
Pengambilan akan ditolak jika koneksinya adalah HTTP/1.x.
Hal ini karena, menurut aturan HTTP/1.1, isi permintaan dan respons harus mengirim header Content-Length
, sehingga pihak lain tahu berapa banyak data yang akan diterima, atau mengubah format pesan untuk menggunakan enkodean chunked. Dengan encoding chunked, isi dibagi menjadi beberapa bagian, yang masing-masing memiliki panjang kontennya sendiri.
Encoding chunked cukup umum dalam hal respons HTTP/1.1, tetapi sangat jarang dalam hal permintaan, sehingga menimbulkan risiko kompatibilitas yang terlalu besar.
Potensi masalah
Fitur ini masih baru, dan saat ini kurang dimanfaatkan di internet. Berikut beberapa masalah yang harus diperhatikan:
Ketidakcocokan di sisi server
Beberapa server aplikasi tidak mendukung permintaan streaming, dan malah menunggu hingga permintaan lengkap diterima sebelum memungkinkan Anda melihatnya, yang agak tidak sesuai dengan tujuannya. Sebagai gantinya, gunakan server aplikasi yang mendukung streaming, seperti NodeJS atau Deno.
Namun, Anda belum aman! Server aplikasi, seperti NodeJS, biasanya berada di belakang server lain, yang sering disebut "server front-end", yang pada gilirannya mungkin berada di belakang CDN. Jika salah satu dari server tersebut memutuskan untuk melakukan buffering permintaan sebelum memberikannya ke server berikutnya dalam rantai, Anda akan kehilangan manfaat streaming permintaan.
Ketidakcocokan di luar kendali Anda
Karena fitur ini hanya berfungsi melalui HTTPS, Anda tidak perlu mengkhawatirkan proxy antara Anda dan pengguna, tetapi pengguna mungkin menjalankan proxy di komputernya. Beberapa software perlindungan internet melakukan hal ini untuk memungkinkannya memantau semua yang terjadi antara browser dan jaringan, dan mungkin ada kasus di mana software ini melakukan buffering pada isi permintaan.
Jika ingin melindungi diri dari hal ini, Anda dapat membuat 'uji fitur' yang mirip dengan demo di atas, dengan mencoba melakukan streaming beberapa data tanpa menutup streaming. Jika server menerima data, server dapat merespons melalui pengambilan yang berbeda. Setelah hal ini terjadi, Anda tahu bahwa klien mendukung permintaan streaming end-to-end.
Deteksi fitur
const supportsRequestStreams = (() => {
let duplexAccessed = false;
const hasContentType = new Request('', {
body: new ReadableStream(),
method: 'POST',
get duplex() {
duplexAccessed = true;
return 'half';
},
}).headers.has('Content-Type');
return duplexAccessed && !hasContentType;
})();
if (supportsRequestStreams) {
// …
} else {
// …
}
Jika Anda penasaran, berikut cara kerja deteksi fitur:
Jika browser tidak mendukung jenis body
tertentu, browser akan memanggil toString()
pada objek dan menggunakan hasilnya sebagai isi.
Jadi, jika browser tidak mendukung aliran permintaan, isi permintaan akan menjadi string "[object ReadableStream]"
.
Saat string digunakan sebagai isi, header Content-Type
akan ditetapkan ke text/plain;charset=UTF-8
.
Jadi, jika header tersebut ditetapkan, kita tahu bahwa browser tidak mendukung streaming dalam objek permintaan, dan kita dapat keluar lebih awal.
Safari mendukung streaming dalam objek permintaan, tetapi tidak mengizinkannya digunakan dengan fetch
, sehingga opsi duplex
diuji, yang saat ini tidak didukung Safari.
Menggunakan dengan stream yang dapat ditulis
Terkadang lebih mudah untuk menggunakan aliran saat Anda memiliki WritableStream
.
Anda dapat melakukannya menggunakan aliran 'identitas', yang merupakan pasangan yang dapat dibaca/ditulis yang mengambil apa pun yang diteruskan ke ujung yang dapat ditulis, dan mengirimkannya ke ujung yang dapat dibaca.
Anda dapat membuat salah satu di antaranya dengan membuat TransformStream
tanpa argumen:
const {readable, writable} = new TransformStream();
const responsePromise = fetch(url, {
method: 'POST',
body: readable,
});
Sekarang, semua yang Anda kirim ke aliran yang dapat ditulis akan menjadi bagian dari permintaan. Hal ini memungkinkan Anda menyusun aliran bersama-sama. Misalnya, berikut contoh sederhana saat data diambil dari satu URL, dikompresi, dan dikirim ke URL lain:
// Get from url1:
const response = await fetch(url1);
const {readable, writable} = new TransformStream();
// Compress the data from url1:
response.body.pipeThrough(new CompressionStream('gzip')).pipeTo(writable);
// Post to url2:
await fetch(url2, {
method: 'POST',
body: readable,
});
Contoh di atas menggunakan aliran kompresi untuk mengompresi data arbitrer menggunakan gzip.