2016๋…„์€ ์›น ์ŠคํŠธ๋ฆผ(web stream)์˜ ํ•ด๋‹ค.


์›๋ฌธ
Jake Archibald, https://jakearchibald.com/2016/streams-ftw/



๊ทธ๋ ‡๋‹ค. ์‹ ๋…„ ์ดˆ๋ถ€ํ„ฐ ํ•œํ•ด์˜ ์ผ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๊ฐ€๋ฒผ์šด ์ผ์€ ์•„๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์›น ์ŠคํŠธ๋ฆผ API์˜ ์ž ์žฌ๋ ฅ์€ ๋‚˜๋ฅผ ๋งค์šฐ ํฅ๋ถ„ ์‹œ์ผฐ๊ธฐ์— ๊ทธ๋Ÿฐ ์ผ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ–ˆ๋‹ค.

์š”์•ฝํ•˜๋ฉด, ์ŠคํŠธ๋ฆผ์€ "cloud"๋ผ๋Š” ๋‹จ์–ด๋ฅผ "butt"๋กœ ๋ณ€๊ฒฝ ํ•˜๊ฑฐ๋‚˜ MPEG๋ฅผ GIF๋กœ ๋ณ€ํ™˜ ํ•˜๋Š” ์ž‘์—…๊ณผ ๊ฐ™์ด ์ต์ˆ™ํ•œ ์ผ๋“ค์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ฐ€์žฅ ์ค‘์š”ํ•œ๊ฒƒ์€ ์ œ๊ณต ๋‚ด์šฉ์„ ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ์„œ๋น„์Šค ์›Œ์ปค๋กœ ๊ฒฐํ•ฉ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์ŠคํŠธ๋ฆผ, ๊ณผ์—ฐ ๋ญ์— ์ข‹์€๊ฐ€?

์ข‹์€ ๊ฒƒ์€ ๋ถ„๋ช…ํ•œ๋ฐ...

ํ”„๋ผ๋ฏธ์Šค(promise)๋Š” ๋‹จ๋… ๊ฐ’์— ๋Œ€ํ•œ ๋น„๋™๊ธฐ ์ „์†ก์„ ๋Œ€์ฒดํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์—ฌ๋Ÿฌ ๊ฐ’์— ๋Œ€ํ•œ ์ „์†ก์ด๋‚˜ ํฐ๊ฐ’์„ ๋ถ„์‚ฐํ•˜์—ฌ ์ „์†กํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์•„๋‹ ์ˆ˜ ์žˆ๋‹ค.

์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ํ‘œํ˜„ํ•˜๊ธฐ ์›ํ• ๋•Œ์—๋Š” ๋‹ค์Œ์˜ ์ ˆ์ฐจ๋Œ€๋กœ ์ง„ํ–‰๋œ๋‹ค.

  1. ๋„คํŠธ์›Œํฌ๋กœ ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  2. ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜๊ณ  ์••์ถ•๋œ ๋ฐ์ดํ„ฐ๋ฅผ raw pixel ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.
  3. ํ™”๋ฉด์— ๋ณด์—ฌ์ค€๋‹ค.

์šฐ๋ฆฌ๋Š” ํ•œ๋ฒˆ์— ํ•˜๋‚˜์˜ ์Šคํƒญ์”ฉ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ์•„๋‹ˆ๋ฉด ์ŠคํŠธ๋ฆผ์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. streaming

๋งŒ์•ฝ ์šฐ๋ฆฌ๊ฐ€ ๋น„ํŠธ๋‹จ์œ„๋กœ ํ•ธ๋“ค๋งํ•˜๊ณ  ๋ณ€ํ™˜ํ•œ๋‹ค๋ฉด, ์ด๋ฏธ์ง€์˜ ์ผ๋ถ€๋ฅผ ๋นจ๋ฆฌ ๋ Œ๋”๋ง ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. ์‹ฌ์ง€์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ‘๋ ฌ๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ „์ฒด ์ด๋ฏธ์ง€๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค. ์ด๊ฒƒ์ด ์ŠคํŠธ๋ฆฌ๋ฐ์ด๋‹ค! ์šฐ๋ฆฌ๋Š” ๋„คํŠธ์›Œํฌ๋กœ๋ถ€ํ„ฐ ์ŠคํŠธ๋ฆผ์„ ์ฝ๊ณ  ์••์ถ•๋ฐ์ดํ„ฐ๋ฅผ ํ”ฝ์…€ ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜ํ•œ ํ›„ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๊ฒŒ ๋œ๋‹ค.

๋‹น์‹ ์€ ์ด๋ฒคํŠธ๋กœ๋„ ๋น„์Šทํ•œ ์„ฑ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ŠคํŠธ๋ฆผ์€ ๋˜๋‹ค๋ฅธ ์ด์ ์„ ์ œ๊ณตํ•œ๋‹ค.

  • ์‹œ์ž‘/์ข…๋ฃŒ ์ง€์  - ์ŠคํŠธ๋ฆผ์€ ๋ฌดํ•œ์ด๋ผ๊ณ  ํ•˜๋”๋ผ๋„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ฝ์ง€ ์•Š์€ ๊ฐ’์˜ ๋ฒ„ํผ๋ง - ๋ฐ˜๋ฉด์— ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋“ฑ๋ก๋˜๊ธฐ ์ „์˜ ์ด๋ฒคํŠธ๋Š” ์†์‹ค๋œ๋‹ค.
  • ํŒŒ์ดํ•‘(piping)์„ ํ†ตํ•œ ๋ณ€๊ฒฝ - ๋‹น์‹ ์€ ๋น„๋™๊ธฐ ์‹œํ€€์Šค ํ˜•์„ฑ๊ณผ ํ•จ๊ป˜ ์ŠคํŠธ๋ฆผ์„ ํŒŒ์ดํ•‘ ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๋‚ด์žฅ - ์—๋Ÿฌ๋Š” ํŒŒ์ดํ”„๋ฅผ ํƒ€๊ณ  ์•„๋ž˜๋กœ ์ „ํŒŒ๋  ๊ฒƒ์ด๋‹ค.
  • ์ทจ์†Œ ์ง€์› - ๊ทธ๋ฆฌ๊ณ  ์ทจ์†Œ ๋ฉ”์‹œ์ง€๋Š” ํŒŒ์ดํ”„๋กœ ๋‹ค์‹œ ์ „๋‹ฌ๋œ๋‹ค.
  • ํ๋ฆ„ ์ œ์–ด - ๋‹น์‹ ์€ ๋ฆฌ๋”(reader)์˜ ์†๋„์— ๋ฐ˜์‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

์œ„์˜ ํ•ญ๋ชฉ ์ค‘ ๋งˆ์ง€๋ง‰ ํ•˜๋‚˜๋Š” ์ •๋ง ์ค‘์š”ํ•˜๋‹ค. ๋‹ค์šด๋กœ๋“œ์™€ ์˜์ƒ์„ ํ‘œํ˜„ํ•˜๋Š”๋ฐ ์ŠคํŠธ๋ฆผ์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. ๋งŒ์•ฝ์— ์ดˆ๋‹น 200 ํ”„๋ ˆ์ž„์˜ ๋น„๋””์˜ค๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ๋””์ฝ”๋”ฉ ํ•œ๋‹ค๊ณ  ํ•ด๋„ ํ™”๋ฉด์—๋Š” ์ดˆ๋‹น 24 ํ”„๋ ˆ์ž„๋งŒ ํ‘œํ˜„๋  ๊ฒƒ์ด๋ฉฐ ๊ฒฐ๊ตญ ๋””์ฝ”๋“œ ํ”„๋ ˆ์ž„์˜ ๊ฑฐ๋Œ€ํ•œ ๋ฐฑ๋กœ๊ทธ์™€ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ์˜ ๊ฒฐ๊ณผ๋งŒ ์–ป๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

์—ฌ๊ธฐ๊ฐ€ ํ๋ฆ„์ œ์–ด๊ฐ€ ๋“ค์–ด์˜ค๋Š” ๊ณณ์ด๋‹ค. ๋ Œ๋”๋ง์„ ํ•ธ๋“ค๋งํ•˜๋Š” ์ŠคํŠธ๋ฆผ์€ ์ดˆ๋‹น 24ํšŒ์˜ ๋””์ฝ”๋” ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ถ€ํ„ฐ ํ”„๋ ˆ์ž„์„ ๊ฐ€์ ธ์˜จ๋‹ค. ๋””์ฝ”๋”๋Š” ์ฝํ˜€์ง€๊ฒƒ ๋ณด๋‹ค ํ”„๋ ˆ์ž„์„ ์ƒ์„ฑํ•˜๋Š”๊ฒƒ์ด ๋” ๋น ๋ฅด๋ฉฐ ์ ์  ๋Š๋ ค์ง„๋‹ค๊ณ  ์•Œ๋ ค์ ธ์žˆ๋‹ค. ๋„คํŠธ์›Œํฌ ์ŠคํŠธ๋ฆผ์€ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋””์ฝ”๋”์—์˜ํ•ด ์ฝํ˜€์ง€๋Š” ๊ฒƒ ๋ณด๋‹ค ๋น ๋ฅด๋ฉฐ ๋Š๋ฆฌ๊ฒŒ ๋‹ค์šด๋กœ๋“œ ๋œ๋‹ค๊ณ  ์•Œ๋ ค์ ธ์žˆ๋‹ค.

์ŠคํŠธ๋ฆผ๊ณผ ๋ฆฌ๋”์˜ ํƒ€์ดํŠธํ•œ ๊ด€๊ณ„ ๋•Œ๋ฌธ์— ์ŠคํŠธ๋ฆผ์€ ์˜ค๋กœ์ง€ ํ•˜๋‚˜์˜ ๋ฆฌ๋”๋งŒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ฝ์ง€ ์•Š์€ ์ŠคํŠธ๋ฆผ์€ โ€œํ‹ฐ๋“œ(teed)โ€ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋Š” ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‘๊ฐœ์˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ์ด ๊ฒฝ์šฐ ํ‹ฐ(tee)๋Š” ์–‘ ๋ฆฌ๋” ๋ชจ๋‘๋ฅผ ํ†ตํ•ด ๋ฒ„ํผ๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค.

์ข‹๋‹ค. ๊ทธ๊ฒƒ์ด ์ด๋ก ์ผ ๋ฟ์ด๊ณ  ๋‚ด ์ด์•ผ๊ธฐ์— ์ „์ ์œผ๋กœ ๋™์˜ํ•œ๋‹ค๊ณ  ๋ณผ ์ˆ˜๋Š” ์—†์ง€๋งŒ, ๋‹น์‹ ์ด ๊ณ„์†ํ•ด์„œ ์ด์•ผ๊ธฐ๋ฅผ ๋“ฃ๊ธธ ๋ฐ”๋ž€๋‹ค.

๋ธŒ๋ผ์šฐ์ € ์ŠคํŠธ๋ฆผ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋งŽ์€ ๊ฒƒ์„ ๋กœ๋“œํ•œ๋‹ค. ๋‹ค์šด๋กœ๋“œํ•˜๋Š” ๊ฒƒ ๊ฐ™์€ ํŽ˜์ด์ง€/์ด๋ฏธ์ง€/๋น„๋””์˜ค ๋“ฑ์˜ ์ผ๋ถ€๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋ณผ ๋•Œ๋งˆ๋‹ค ์ŠคํŠธ๋ฆฌ๋ฐ ๋•๋ถ„์ด๋ผ๋Š” ๊ฒƒ์„ ์•Œ์•„์•ผ ํ•œ๋‹ค. ๋˜ํ•œ ๊ทธ๊ฒƒ์€ ์ตœ๊ทผ์— ์ŠคํŠธ๋ฆผ์„ ์Šคํฌ๋ฆฝํŠธ์— ๋…ธ์ถœ๋˜๊ฒŒ ํ•œ ํ‘œ์ค€ํ™” ๋…ธ๋ ฅ ๋•๋ถ„์ด๊ธฐ๋„ ํ•˜๋‹ค.

์ŠคํŠธ๋ฆผ + ํŒจ์น˜(fetch) API

์‘๋‹ต(response) ๊ฐ์ฒด๋Š” ํŒจ์น˜ ์ŠคํŽ™(fetch spec)์— ์ •์˜๋œ ๋Œ€๋กœ, ๋‹ค์–‘ํ•œ ํ˜•์‹์œผ๋กœ ์‘๋‹ต์„ ์ฝ์„ ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค. ํ•˜์ง€๋งŒ response.body๋Š” ๊ทผ๋ณธ์ ์ธ ์ŠคํŠธ๋ฆผ์— ์—‘์„ธ์Šค ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค. response.body๋Š” ํฌ๋กฌ์˜ ํ˜„์žฌ ์•ˆ์ • ๋ฒ„์ „์—์„œ ์ง€์›ํ•œ๋‹ค.

์ŠคํŠธ๋ฆผ์„ ์ด์šฉํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ์— ์ „์ฒด ์‘๋‹ต์„ ์œ ์ง€ํ•˜์ง€ ์•Š๊ณ  ํ—ค๋”์— ์˜์กดํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ์‘๋‹ต ๋‚ด์šฉ์˜ ๊ธธ์ด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

// fetch() returns a promise that
// resolves once headers have been received
fetch(url).then(response => {
  // response.body is a readable stream.
  // Calling getReader() gives us exclusive access to
  // the stream's content
  var reader = response.body.getReader();
  var bytesReceived = 0;

  // read() returns a promise that resolves
  // when a value has been received
  reader.read().then(function processResult(result) {
    // Result objects contain two properties:
    // done  - true if the stream has already given
    //         you all its data.
    // value - some data. Always undefined when
    //         done is true.
    if (result.done) {
      console.log("Fetch complete");
      return;
    }

    // result.value for fetch streams is a Uint8Array
    bytesReceived += result.value.length;
    console.log('Received', bytesReceived, 'bytes of data so far');

    // Read some more, and call this function again
    return reader.read().then(processResult);
  });
});

๋ฐ๋ชจ ๋ณด๊ธฐ (1.3mb)

๋ฐ๋ชจ์—์„œ๋Š” ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ 1.3mb์˜ gzip์œผ๋กœ ์••์ถ•๋œ(์••์ถ•์ „ 7.7mb) HTMLํŒŒ์ผ์„ ๊ฐ€์ ธ์™”๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ๊ฒฐ๊ณผ๋Š” ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค. ๊ฐ ์กฐ๊ฐ์˜ ์‚ฌ์ด์ฆˆ๋Š” ๊ธฐ๋ก๋˜์ง€๋งŒ ์กฐ๊ฐ๋“ค ์ž์ฒด๋Š” GC(garbage collected)๋œ๋‹ค.

result.value๋Š” ์ŠคํŠธ๋ฆผ์ด ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์€ ๋ฌด์—‡์ด๋“ (string, number, date, ImageData, DOM์—˜๋ฆฌ๋จผํŠธ, โ€ฆ) ๊ฐ„์— ์ƒ์„ฑํ•˜์ง€๋งŒ, ์ด๊ฒฝ์šฐ์˜ ๊ฐ€์ ธ์˜ค๋Š” ์ŠคํŠธ๋ฆผ์€ ํ•ญ์ƒ ์ด์ง„ ๋ฐ์ดํ„ฐ Unit8Array๋‹ค. ์ „์ฒด ์‘๋‹ต์€ ๊ฐ๊ฐ์˜ Unit8Array๋ฅผ ๋ชจ์•„์„œ ๋งž์ถ˜ ๊ฒƒ์ด๋‹ค. ํ…์ŠคํŠธ๋กœ ์‘๋‹ต์„ ์›ํ•œ๋‹ค๋ฉด ์•„๋ž˜ ์˜ˆ์ œ์™€ ๊ฐ™์ด TextDecoder๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

var decoder = new TextDecoder();
var reader = response.body.getReader();

// read() returns a promise that resolves
// when a value has been received
reader.read().then(function processResult(result) {
  if (result.done) return;
  console.log(
    decoder.decode(result.value, {stream: true})
  );

  // Read some more, and recall this function
  return reader.read().then(processResult);
});

{stream: true}๋Š” ๋””์ฝ”๋”์˜ ๋ฒ„ํผ ์œ ์ง€๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ๋งŒ์•ฝ result.value๊ฐ€ UTF-8 ์ฝ”๋“œ ์ง€์ ์„ ํ†ต๊ณผํ•ด ์ค‘๊ฐ„์—์„œ ์ข…๋ฃŒ๋œ๋‹ค๋ฉด, โ™ฅ์™€ ๊ฐ™์€ ๋ฌธ์ž๋Š” 3 bytes([0xE2, 0x99, 0xA5])๋งŒ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค.

TextDecoder๋Š” ํ˜„์žฌ๋Š” ์ž‘๊ณ  ๋ณผํ’ˆ์—†์ง€๋งŒ ํ–ฅํ›„์—๋Š” ๋ณ€ํ™˜ ์ŠคํŠธ๋ฆผ(transform stream)์ด ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค(ํ•œ๋ฒˆ์˜ ๋ณ€ํ™˜ ์ŠคํŠธ๋ฆผ์€ ์ •์˜๋˜์–ด์žˆ๋‹ค). ๋ณ€ํ™˜ ์ŠคํŠธ๋ฆผ์€ .writable์— ์“ฐ๊ธฐ ๊ฐ€๋Šฅํ•œ ์ŠคํŠธ๋ฆผ๊ณผ .readable์— ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์ŠคํŠธ๋ฆผ ๊ฐ์ฒด์ด๋‹ค. ๊ทธ๊ฒƒ์€ ์กฐ๊ฐ๋“ค์„ ์“ฐ๊ธฐ๊ฐ€๋Šฅํ•˜๋ฉฐ ๊ฐ€๊ณต๋œ ์ƒํƒœ๋กœ ์ทจํ•ด, ์ฝ๊ธฐ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ๋‚ด๋ณด๋‚ธ๋‹ค. ๋ณ€ํ™˜ ์ŠคํŠธ๋ฆผ์„ ์‚ฌ์šฉ์˜ˆ์ œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

Hypothetical future-code:
var reader = response.body
  .pipeThrough(new TextDecoder()).getReader();

reader.read().then(result => {
  // result.value will be a string
});

๋ธŒ๋ผ์šฐ์ €๋Š” ์ž์‹ ์ด ์†Œ์œ ํ•œ ์‘๋‹ต ์ŠคํŠธ๋ฆผ๊ณผ TextDocoder ๋ณ€ํ™˜ ์ŠคํŠธ๋ฆผ ๋ชจ๋‘๋ฅผ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ฐ€์ ธ์˜ค๊ธฐ(fetch) ์ทจ์†Œํ•˜๊ธฐ

์ŠคํŠธ๋ฆผ์€ stream.cancel()(ํŒจ์น˜์˜ ๊ฒฝ์šฐ response.body.cancel()์„ ์‚ฌ์šฉํ•œ๋‹ค)์ด๋‚˜ reader.cancel()์„ ์‚ฌ์šฉํ•˜์—ฌ ์ทจ์†Œํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค์šด๋กœ๋“œ๋ฅผ ์ค‘์ง€ํ•˜์—ฌ ๋ฐ˜์‘์„ ์–ป์–ด๋‚ธ๋‹ค.

๋ฐ๋ชจ ๋ณด๊ธฐ

์ด ๋ฐ๋ชจ์—์„œ๋Š” ๊ฒ€์ƒ‰์–ด๋กœ ํฐ ๋ฌธ์„œ๋ฅผ ์ฐพ๋Š”๋ฐ, ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ž‘์€ ์ผ๋ถ€๋ถ„๋งŒ์„ ์œ ์ง€ํ•˜๋ฉฐ, ์ผ์น˜ํ•˜๋Š” ๋ถ€๋ถ„์ด ๋ฐœ๊ฒฌ๋˜๋ฉด ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ๋ฉˆ์ถ˜๋‹ค.

์–ด์จŒ๋“ , ์ด ๋ชจ๋“ ๊ฒƒ์€ 2015๋…„๋„ ๊ฐ€๋Šฅํ–ˆ๋˜ ์ผ์ด๋‹ค. ์ด์ œ๋ถ€ํ„ฐ ์žฌ๋ฏธ์žˆ๋Š” ์ƒˆ๋กœ์šด ๊ฒƒ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ฒ ๋‹ค.

์ž์‹ ์˜ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑํ•˜์ž

ํฌ๋กฌ ์นด๋‚˜๋ฆฌ์•„(Chrome Canary, ๊ฐœ๋ฐœ์ž ๋ฐ ์–ผ๋ฆฌ์–ด๋‹ตํ„ฐ ์šฉ)์—์„œ "์‹คํ—˜์ ์ธ ์›น ํ”Œ๋žซํผ ๊ธฐ๋Šฅ(Experimental web platform features)"์„ ํ™œ์„ฑํ™” ์‹œํ‚ค๋ฉด ๋ฐ”๋กœ ์ž์‹ ์˜ ์ŠคํŠธ๋ฆผ(your own stream)์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

var stream = new ReadableStream({
  start(controller) {},
  pull(controller) {},
  cancel(reason) {}
}, queuingStrategy);
  • start๋Š” ๊ณง๋ฐ”๋กœ ํ˜ธ์ถœ๋œ๋‹ค. ์ด๋Š” ๋ชจ๋“  ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์„ค์ •ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค(๋ฌธ์ž์—ด๊ณผ ๊ฐ™์€ ์ด๋ฒคํŠธ, ๋‹ค๋ฅธ ์ŠคํŠธ๋ฆผ ๋˜๋Š” ๋ณ€์ˆ˜ ๋“ฑ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์–ป์„ ๋•Œ๋ฅผ ์˜๋ฏธํ•จ). ๋งŒ์•ฝ ๋‹น์‹ ์ด ํ”„๋ผ๋ฏธ์Šค(promise)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๊ฑฐ์ ˆํ•œ๋‹ค๋ฉด, ์ŠคํŠธ๋ฆผ์„ ํ†ตํ•ด ์—๋Ÿฌ ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ผ ๊ฒƒ์ด๋‹ค.
  • pull์€ ์ŠคํŠธ๋ฆผ์˜ ๋ฒ„ํผ๊ฐ€ ๊ฐ€๋“์ฐจ์ง€ ์•Š์•˜์„ ๋•Œ ํ˜ธ์ถœ๋˜๋ฉฐ, ๊ฐ€๋“์ฐฐ ๋•Œ ๊นŒ์ง€ ํ˜ธ์ถœ๋œ๋‹ค. ๋‹ค์‹œ๋งํ•ด ๋งŒ์•ฝ ๋‹น์‹ ์ด ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๊ฑฐ์ ˆํ•œ๋‹ค๋ฉด, ์ŠคํŠธ๋ฆผ์„ ํ†ตํ•ด ์—๋Ÿฌ ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ผ ๊ฒƒ์ด๋‹ค. ๋˜ํ•œ, pull์€ ์™„์ „ํ•œ ํ”„๋ผ๋ฏธ์Šค๊ฐ€ ๋ฐ˜ํ™˜๋  ๋•Œ ๊นŒ์ง€ ๋‹ค์‹œ ํ˜ธ์ถœ๋˜์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.
  • cancel์€ ์ŠคํŠธ๋ฆผ์ด ์ทจ์†Œ๋˜๋ฉด ํ˜ธ์ถœ๋œ๋‹ค. ์ด๋Š” ๋ชจ๋“  ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์ทจ์†Œ ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.
  • queuingStrategy๋Š” ํ•˜๋‚˜์˜ ์•„์ดํ…œ์— ๊ธฐ๋ณธ์ ์œผ๋กœ ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์ŠคํŠธ๋ฆผ์ด ์ด๋ก ์ ์œผ๋กœ ์Œ“์ด๋Š”์ง€๋ฅผ ์ •์˜ํ•œ๋‹ค. - ๋” ์ž์„ธํ•œ ์‚ฌํ•ญ์€ ์—ฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค.

controller์— ๋Œ€ํ•ด์„œ๋Š”

  • controller.enqueue(whatever) - ์ŠคํŠธ๋ฆผ ๋ฒ„ํผ์˜ ํ ๋ฐ์ดํ„ฐ
  • controller.close() - ์ŠคํŠธ๋ฆผ์˜ ์ข…๋ฃŒ ์‹ ํ˜ธ
  • controller.error(e) - ํ„ฐ๋ฏธ๋„ ์—๋Ÿฌ ์‹ ํ˜ธ
  • controller.desiredSize - ๋‚จ์•„์žˆ๋Š” ๋ฒ„ํผ์˜ ์ด๋Ÿ‰(๋ฒ„ํผ๊ฐ€ ๊ฐ€๋“์ฐจ๋ฉด ์Œ์ˆ˜์ผ ์ˆ˜ ์žˆ์Œ). ์ด ์ˆซ์ž๋Š” queuingStrategy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ„์‚ฐ๋œ๋‹ค.

๊ทธ๋ž˜์„œ ๋‚˜๋Š” ๋‹ค์Œ ์˜ˆ์ œ์™€ ๊ฐ™์ด 0.9๋ณด๋‹ค ํฐ ์ˆซ์ž๊ฐ€ ๋‚˜์˜ฌ๋•Œ ๊นŒ์ง€ ๋งค ์ดˆ๋งˆ๋‹ค ๋žœ๋ค ์ˆซ์ž๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ์ŠคํŠธ๋ฆผ ์ƒ์„ฑ์„ ์›ํ–ˆ๋‹ค.

var interval;
var stream = new ReadableStream({
  start(controller) {
    interval = setInterval(() => {
      var num = Math.random();

      // Add the number to the stream
      controller.enqueue(num);

      if (num > 0.9) {
        // Signal the end of the stream
        controller.close();
        clearInterval(interval);
      }
    }, 1000);
  },
  cancel() {
    // This is called if the reader cancels,
    //so we should stop generating numbers
    clearInterval(interval);
  }
});

์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ž. (์•Œ๋ฆผ: ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ ค๋ฉด ํฌ๋กฌ ์นด๋‚˜๋ฆฌ์•„์—์„œ chrome://flags/#enable-experimental-web-platform-features ์˜ต์…˜์„ ํ™œ์„ฑํ™”์‹œ์ผœ์•ผ ํ•จ)

controller.enqueue๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ํ†ต๊ณผ ์‹œํ‚ค๋Š” ๊ฒƒ์€ ์ง์ ‘ ํ•ด๋ณด๊ธธ ๋ฐ”๋ž€๋‹ค. ์œ„์˜ ์˜ˆ์ œ์™€ ๊ฐ™์ด ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ, ์ง์ ‘ ๋งŒ๋“  "push source" ์ŠคํŠธ๋ฆผ์„ ๋‹จ์ˆœํžˆ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
๊ทธ๋Œ€์‹ ์— pull์ด ํ˜ธ์ถœ๋  ๋•Œ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฐ ํ›„ ๊ธฐ๋ฐ˜ ์†Œ์Šค๋กœ ๋ถ€ํ„ฐ ์ˆ˜์ง‘ํ•œ ๋ฐ์ดํƒ€ ์‹ ํ˜ธ๋ฅผ ์‚ฌ์šฉํ•œ ๋‹ค์Œ ์ง์ ‘ ๋งŒ๋“  "pull source" ์ŠคํŠธ๋ฆผ์„ ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€ํ•œ๋‹ค. ๋˜๋Š” ์›ํ•œ๋‹ค๋ฉด, ๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์กฐํ•ฉํ•  ์ˆ˜ ๋„ ์žˆ๋‹ค.

controller.desiredSize๋ฅผ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์€ ๊ฐ€์žฅ ์ŠคํŠธ๋ฆผ์ด ํšจ์œจ์ ์ธ ์†๋„์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ์ด๊ฒƒ์€ "๋ฐฑํ”„๋ ˆ์…”(backpressure) ์ง€์›"๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ์•Œ๋ ค์ ธ ์žˆ๋Š”๋ฐ, ์ด๋Š” ๋‹น์‹ ์˜ ์ŠคํŠธ๋ฆผ์ด ๋ฆฌ๋”์˜ ์ฝ๋Š” ์†๋„์— ๋ฐ˜์‘ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค(์ด์ „์˜ ๋น„๋””์˜ค ๋””์ฝ”๋”ฉ ์˜ˆ์ œ์™€ ๋น„์Šทํ•จ). ๊ทธ๋Ÿฌ๋‚˜ ๊ธฐ๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋‹ค ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํ•œ ์–ด๋–ค ๊ฒƒ๋„ desiredSize๋ฅผ ๋ฌด์‹œํ•˜๋Š” ๊ฒƒ์€ ์ค‘๋‹จ์‹œํ‚ฌ ์ˆ˜ ์—†๋‹ค. ํ•ด๋‹น ์ŠคํŽ™์€ ๋ฐฑํ”„๋ ˆ์…”์™€ ์ŠคํŠธ๋ฆผ ์ƒ์„ฑํ•˜๊ธฐ๋ผ๋Š” ์ข‹์€ ์˜ˆ์ œ๋ฅผ ๊ฐ–๊ณ ์žˆ๋‹ค.

๊ทธ ์ž์ฒด๋กœ ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ํŠนํžˆ๋‚˜ ์žฌ๋ฏธ์—†๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฒ˜์Œ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ง€์›ํ•˜๋Š” API๋“ค๋„ ๋งŽ์ง€ ์•Š๋‹ค. ๊ทธ๋Ÿฐ๊ฒƒ๋“ค ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋‹ค์Œ์˜ Response๋‹ค.

new Response(readableStream);

๋ณธ๋ฌธ(body)์ด ์ŠคํŠธ๋ฆผ์ธ HTTP ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ณ , ์„œ๋น„์Šค ์›Œ์ปค๋กœ ๋ถ€ํ„ฐ ์‘๋‹ต๋ฐ›์€ ๊ฒƒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฒœ์ฒœํžˆ ๋ฌธ์ž์—ด ์ œ๊ณตํ•˜๊ธฐ

๋ฐ๋ชจ ๋ณด๊ธฐ (์•Œ๋ฆผ: ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ ค๋ฉด ํฌ๋กฌ ์นด๋‚˜๋ฆฌ์•„์—์„œ chrome://flags/#enable-experimental-web-platform-features ์˜ต์…˜์„ ํ™œ์„ฑํ™”์‹œ์ผœ์•ผ ํ•จ)

๋‹น์‹ ์€ HTML ํŽ˜์ด์ง€๊ฐ€ ์ฒœ์ฒœํžˆ(๊ณ„ํš์ ์œผ๋กœ) ๋ Œ๋”๋ง ๋˜๋Š”๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค. ์ด ์‘๋‹ต์€ ์ „์ ์œผ๋กœ ์„œ๋น„์Šค ์›Œ์ปค์—์„œ ๋ฐœ์ƒ์‹œํ‚จ ๊ฒƒ์ด๋‹ค. ์—ฌ๊ธฐ ๊ทธ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค.

// In the service worker:
self.addEventListener('fetch', event => {
  var html = 'โ€ฆhtml to serveโ€ฆ';

  var stream = new ReadableStream({
    start(controller) {
      var encoder = new TextEncoder();
      // Our current position in `html`
      var pos = 0;
      // How much to serve on each push
      var chunkSize = 1;

      function push() {
        // Are we done?
        if (pos >= html.length) {
          controller.close();
          return;
        }

        // Push some of the html,
        // converting it into an Uint8Array of utf-8 data
        controller.enqueue(
          encoder.encode(html.slice(pos, pos + chunkSize))
        );

        // Advance the position
        pos += chunkSize;
        // push again in ~5ms
        setTimeout(push, 5);
      }

      // Let's go!
      push();
    }
  });

  return new Response(stream, {
    headers: {'Content-Type': 'text/html'}
  });
});

๋ธŒ๋ผ์šฐ์ €์—์„œ ์‘๋‹ต ๋ณธ๋ฌธ์„ ์ฝ์„ ๋•Œ Unit8Array์˜ ์กฐ๊ฐ์œผ๋กœ ์–ป์„ ๊ฒƒ์„ ๊ธฐ๋Œ€ํ•œ๋‹ค. ๋งŒ์ผ ํ”Œ๋ ˆ์ธ ๋ฌธ์ž์—ด๊ณผ ๊ฐ™์ด ์–ด๋–ค ๋‹ค๋ฅธ๊ฒƒ์ด ํ†ต๊ณผ ๋œ๋‹ค๋ฉด ๊ทธ๊ฒƒ์€ ์‹คํŒจํ•œ๋‹ค. ๊ณ ๋ง™๊ฒŒ๋„ TextEncoder๋Š” ๋ฌธ์ž์—ด์„ ๊ฐ€์ง€๊ณ  ๋ฌธ์ž์—ด์„ ํ‘œํ˜„ํ•˜๋Š” ๋ฐ”์ดํŠธ์˜ Unit8Array๋กœ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

TextDecode ์ฒ˜๋Ÿผ, TextEncoder๋Š” ์•ž์œผ๋กœ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ ๋  ๊ฒƒ์ด๋‹ค.

๋ณ€ํ™˜๋œ ์ŠคํŠธ๋ฆผ ์ œ๊ณตํ•˜๊ธฐ

์ด๋ฏธ ๋งํ–ˆ๋“ฏ์ด ๋ณ€ํ™˜ ์ŠคํŠธ๋ฆผ์€ ์•„์ง ์ •์˜๋˜์ง€ ์•Š์•˜๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‹ค๋ฅธ ์ŠคํŠธ๋ฆผ์—์„œ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๊ณต๊ธ‰ํ•˜๋Š” ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์ŠคํŠธ๋ฆผ(readable stream)์„ ์ƒ์„ฑํ•˜์—ฌ ๋น„์Šทํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

"cloud"๋ฅผ "butt"๋กœ

๋ฐ๋ชจ ๋ณด๊ธฐ (์•Œ๋ฆผ: ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ ค๋ฉด ํฌ๋กฌ ์นด๋‚˜๋ฆฌ์•„์—์„œ chrome://flags/#enable-experimental-web-platform-features ์˜ต์…˜์„ ํ™œ์„ฑํ™”์‹œ์ผœ์•ผ ํ•จ)

๋‹น์‹ ์ด ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ์ด ํŽ˜์ด์ง€(์œ„ํ‚คํ”ผ๋””์•„์—์žˆ๋Š” ํด๋ผ์šฐ๋“œ ์ปดํ“จํŒ… ์•„ํ‹ฐํด์—์„œ ์–ป์€)์ด์ง€๋งŒ, ๋ชจ๋“  "cloud"๋ผ๋Š” ๋‹จ์–ด๊ฐ€ "butt"๋กœ ๋Œ€์ฒด๋œ ๊ฒƒ์ด๋‹ค. ์ŠคํŠธ๋ฆผ์œผ๋กœ ํ•˜๋Š” ๊ฒƒ์˜ ์ด์ ์€ ์›๋ณธ์„ ๋‹ค์šด๋กœ๋“œ ํ•˜๋Š” ๋™์•ˆ ํ™”๋ฉด์— ๋ณ€ํ™˜๋œ ์ฝ˜ํ…์ธ ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

๋‹ค์Œ์€ ์—ฃ์ง€ ์ผ€์ด์Šค ์ผ๋ถ€๋ถ„์˜ ์ƒ์„ธ๋‚ด์šฉ์„ ํฌํ•จํ•œ ์ฝ”๋“œ๊ฐ€ ์žˆ๋Š” ๋งํฌ๋‹ค. https://github.com/jakearchibald/isserviceworkerready/blob/master/src/demos/transform-stream/sw.js

MPEG์—์„œ GIF๋กœ

๋น„๋””์˜ค ์ฝ”๋ฑ์€ ์ •๋ง ํšจ์œจ์ ์ด์ง€๋งŒ, ๋ชจ๋ฐ”์ผ์—์„œ ์ž๋™์‹คํ–‰์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. GIF๋Š” ์ž๋™์‹คํ–‰์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ๋น„์šฉ์ด ํฌ๋‹ค. ์ง„์งœ๋กœ ๋ฉ์ฒญํ•œ ์†”๋ฃจ์…˜์ด ์—ฌ๊ธฐ์— ์žˆ๋‹ค.

๋ฐ๋ชจ ๋ณด๊ธฐ (์•Œ๋ฆผ: ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ ค๋ฉด ํฌ๋กฌ ์นด๋‚˜๋ฆฌ์•„์—์„œ chrome://flags/#enable-experimental-web-platform-features ์˜ต์…˜์„ ํ™œ์„ฑํ™”์‹œ์ผœ์•ผ ํ•จ)

MPEG ํ”„๋ ˆ์ž„ ๋””์ฝ”๋”ฉ์„ ํ•˜๋Š” ๋™์•ˆ GIF์˜ ์ฒซ๋ฒˆ์งธ ํ”„๋ ˆ์ž„์ด ํ‘œ์‹œ๋  ์ˆ˜ ์žˆ๋Š” ์ง€๊ธˆ์˜ ์ƒํ™ฉ์—๋Š” ์ŠคํŠธ๋ฆฌ๋ฐ์€ ์œ ์šฉํ•˜๋‹ค.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค! 26mb GIF๋ฅผ ์˜ค๋กœ์ง€ 0.9mb์˜ MPEG๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „๋‹ฌํ•˜๋Š”๋ฐ, ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋˜์ง€ ์•Š๊ณ  CPU๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ œ์™ธํ•˜๋ฉด ์™„๋ฒฝํ•˜๋‹ค! ๋ธŒ๋ผ์šฐ์ €๋Š” ๋ชจ๋ฐ”์ผ์—์„œ ๋น„๋””์˜ค ์ž๋™์‹คํ–‰์„ ์ •๋ง ํ—ˆ์šฉํ•ด์•ผํ•œ๋‹ค. ์Œ์†Œ๊ฑฐ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ๋Š” ํŠนํžˆ๋‚˜ ๊ทธ๋ ‡๋‹ค. ์ด๊ฒƒ์€ ํฌ๋กฌ์ด ์ง€๊ธˆ ๋‹น์žฅ ๋…ธ๋ ฅํ•ด์•ผ ํ•ธ๋‹ค.

์™„์ „ ๊ณต๊ฐœ: ๋‚˜๋Š” ๋ฐ๋ชจ์—์„œ ๊ผผ์ˆ˜๋ฅผ ๋ถ€๋ ธ๋‹ค. ๋ชจ๋“  MPEG๋ฅผ ์‹œ์ž‘์ „์— ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„๋†จ๋‹ค. ๋‚˜๋Š” ๋„คํŠธ์›Œํฌ๋กœ ๋ถ€ํ„ฐ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ์–ป๊ธธ ์›ํ–ˆ์ง€๋งŒ ์Šคํ‚ฌ์ด ๋ถ€์กฑํ•˜์—ฌ(์›๋ฌธ: OutOfSkillError)๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์—ˆ๋‹ค. ๋˜ํ•œ GIF๋Š” ์ •๋ง์ด์ง€ ๋‹ค์šด๋กœ๋“œ ๋™์•ˆ ๋ฐ˜๋ณตํ•˜๋ฉด ์•Š์•„์•ผ ํ•˜์ง€๋งŒ, ์ง€๊ธˆ์€ ๋ฐ˜๋ณต๋˜๊ณ  ์žˆ๋‹ค.

ํŽ˜์ด์ง€ ๋ Œ๋” ์‹œ๊ฐ„์„ ์†Œ๋ชจํ•˜์—ฌ ๋ฉ€ํ‹ฐ ์†Œ์Šค๋กœ๋ถ€ํ„ฐ ํ•˜๋‚˜์˜ ์ŠคํŠธ๋ฆผ ์ƒ์„ฑํ•˜๊ธฐ

์ด๊ฒƒ์€ ์•„๋งˆ๋„ ์„œ๋น„์Šค์›Œํฌ + ์ŠคํŠธ๋ฆผ ์กฐํ•ฉ์˜ ๊ฐ€์žฅ ํ˜„์‹ค์ ์ธ ์ ์šฉ์ด๋‹ค. ์žฅ์ ์€ ์„ฑ๋Šฅ๋ฉด์—์„œ ํฌ๋‹ค.

๋ช‡๊ฐœ์›” ์ „์— ๋‚˜๋Š” ์˜คํ”„๋ผ์ธ ์šฐ์„  ์œ„ํ‚คํ”ผ๋””์•„์˜ ๋ฐ๋ชจ๋ฅผ ๊ฐœ๋ฐœํ–ˆ๋‹ค. ๋‚˜๋Š” ๋น ๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋ฉฐ ํ˜„๋Œ€์ ์ธ ํ™•์žฅ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ ์ •๋ง ํ˜์‹ ์ ์ธ ์›น์•ฑ์„ ๋งŒ๋“ค๊ธฐ๋ฅผ ์›ํ–ˆ๋‹ค.

OSX ์˜ ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ ์ปจ๋””์…”๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•œ ์†์‹ค 3G ์—ฐ๊ฒฐ ๊ธฐ๋ฐ˜์— ๋Œ€ํ•ด ์„ฑ๋Šฅ๊ณผ ์ˆ˜์น˜์ ์ธ ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ์ด์•ผ๊ธฐํ•˜๊ฒ ๋‹ค.

์„œ๋น„์Šค ์›Œ์ปค ์—†์ด ํ‘œํ˜„ ์ปจํ…์ธ ๋Š” ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์กŒ๋‹ค. ์—ฌ๊ธฐ ์„ฑ๋Šฅ์— ๋งŽ์€ ๋…ธ๋ ฅ์„ ๊ธฐ์šธ์˜€๊ณ  ๊ทธ์— ๋Œ€ํ•œ ์„ฑ๊ณผ๋ฅผ ์–ป์—ˆ๋‹ค.

1

๋ฐ๋ชจ ๋ณด๊ธฐ

๋‚˜์˜์ง€๋Š” ์•Š์•˜๋‹ค. ์•ฝ๊ฐ„์˜ ์˜คํ”„๋ผ์ธ ์šฐ์„ ์— ์ข‹์€ ๋ถ€๋ถ„์„ ํ˜ผํ•ฉํ•˜์—ฌ ์„ฑ๋Šฅ ๋”์šฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์„œ๋น„์Šค ์›Œ์ปค๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฒฐ๊ณผ๋Š”?

2

๋ฐ๋ชจ ๋ณด๊ธฐ

์Œ... ์šฐ์„  ๋ Œ๋”๋ง์ด ๋” ๋นจ๋ผ์กŒ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ปจํ…์ธ  ๋ Œ๋”๋ง ์‹œ์— ํฐ ํ‡ด๋ณด๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ•์€ ์บ์‰ฌ์—์„œ ์ง„์ž… ํŽ˜์ด์ง€๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๊ฒƒ์€ ์œ„ํ‚คํ”ผ๋””์•„์˜ ๋ชจ๋“  ์บ์‹ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Œ€์‹ ์— ๋‚˜๋Š” CSS, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ทธ๋ฆฌ๊ณ  ํ—ค๋”๋ฅผ ์ œ๊ณตํ–ˆ๋‹ค. ๋น ๋ฅธ ์ดˆ๊ธฐ ๋ Œ๋”๋ง์„ ์–ป๊ณ  ๊ทธ๋Ÿฐ ํ›„ ๋ฌธ์„œ์˜ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ํŽ˜์ด์ง€์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์„ค์ •์„ ํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ณณ์ด ๋‚ด๊ฐ€ ๋ชจ๋“  ์„ฑ๋Šฅ์„ ์žƒ์–ด๋ฒ„๋ฆฐ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์ด๋‹ค.

์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ง์ ‘ ์ œ๊ณต๋ฐ›๋“  ์•„๋‹ˆ๋ฉด ์„œ๋น„์Šค ์›Œ์ปค๋ฅผ ํ†ตํ•˜๋“ , ๋‹ค์šด๋กœ๋“œ๋œ HTML์„ ๋ Œ๋”๋ง ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‚˜๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•œ ํŽ˜์ด์ง€๋กœ๋ถ€ํ„ฐ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์™”๋‹ค. ๊ทธ๋Ÿฐ ํ›„ ์ŠคํŠธ๋ฆฌ๋ฐ ํŒŒ์„œ๋ฅผ ์šฐํšŒํ•˜์—ฌ innerHTML๋กœ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ์ด ๋•Œ๋ฌธ์— ๋‚ด์šฉ์ด ํ‘œ์‹œ๋˜๊ธฐ ์ „์— ์™„์ „ํžˆ ๋‹ค์šด๋กœ๋“œ ๋˜์—ˆ๊ณ , 2์ดˆ ๋” ๋Š๋ ค์กŒ๋‹ค. ๋‹ค์šด๋กœ๋“œํ•˜๋Š” ์ปจํ…์ธ ๊ฐ€ ๋งŽ์•„์งˆ ์ˆ˜ ๋ก ์ŠคํŠธ๋ฆฌ๋ฐ ์„ฑ๋Šฅ ์†ํ•ด๋Š” ๋” ๋Š˜์–ด๋‚  ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‚˜์—๊ฒŒ๋Š” ๋ถˆํ–‰ํ•˜๊ฒŒ๋„ ์œ„ํ‚คํ”ผ๋””์•„ ๋ฌธ์„œ๋Š” ๋งค์šฐ ํฌ๋‹ค(๊ตฌ๊ธ€ ๋ฌธ์„œ๋Š” 100k).

์ด๊ฒƒ์ด ๋‚ด๊ฐ€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ธฐ๋ฐ˜์˜ ์›น ์•ฑ๊ณผ ํ”„๋ ˆ์ž„์›Œํฌ์— ๋Œ€ํ•ด ํˆฌํ„ธ๋˜๋Š” ์ด์œ ๋‹ค - ๊ทธ๋“ค์€ 0๋‹จ๊ณ„๋กœ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ๋ฒ„๋ฆฌ๋Š” ๊ฒฝํ–ฅ์ด ์žˆ๊ณ  ๊ทธ ๊ฒฐ๊ณผ ์„ฑ๋Šฅ์ด ๋” ์•ˆ์ข‹์•„์ง„๋‹ค.

๋‚˜๋Š” ํ”„๋ฆฌํŒจ์นญ(prefetching)๊ณผ ๊ฐ€์งœ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๋˜๋Œ๋ฆฌ๊ธฐ ์œ„ํ•ด ๋…ธ๋ ธํ–ˆ๋‹ค. ๊ฐ€์งœ ์ŠคํŠธ๋ฆฌ๋ฐ์€ ํŠนํžˆ๋‚˜ ๋”ํ•œ ๊ผผ์ˆ˜๋‹ค. ํŽ˜์ด์ง€๋Š” ๋ฌธ์„œ์˜ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์™€ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ฝ๋Š”๋‹ค. ๋จผ์ € ๋‚ด์šฉ์˜ 9k๋ฅผ ๋ฐ›์•„ innerHTML๋กœ ์ถ”๊ฐ€ํ•˜๊ณ  ๋‹ค์‹œ ๋‚˜๋จธ์ง€ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. ์ด๊ฒƒ์€ ์ผ๋ถ€ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋‘๋ฒˆ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋”์ฐํ•˜์ง€๋งŒ, ๊ทธ๋งŒํผ์˜ ๊ฐ€์น˜๋Š” ์žˆ๋‹ค.

3

๋ฐ๋ชจ ๋ณด๊ธฐ

๊ผผ์ˆ˜๋“ค์€ ๋ฌธ์„œ ๋‚ด์šฉ์ด ๋ณด์—ฌ์ง€๋Š” ์‹œ๊ฐ„์„ ๊ฐœ์„ ํ•˜์ง€๋งŒ, ์—ฌ์ „ํžˆ ๋‚ฉ๋“ํ•˜๊ธฐ ํž˜๋“ค ๋งŒํผ ์„œ๋ฒ„ ๋ Œ๋”๋ง์— ๋น„ํ•ด ๋’ค์ณ์ง„๋‹ค. ๋ฟ๋งŒ์•„๋‹ˆ๋ผ innerHTML์„ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€์— ์ถ”๊ฐ€๋œ ๋‚ด์šฉ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๊ตฌ๋ฌธ ๋ถ„์„๋œ ๋‚ด์šฉ๊ณผ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค. ํŠนํžˆ ์ธ๋ผ์ธ <script>๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.

์—ฌ๊ธฐ๊ฐ€ ์ŠคํŠธ๋ฆผ์ด ๊ฐœ์ž… ํ•  ๊ณณ์ด๋‹ค. ๋นˆ ๊ป๋ฐ๊ธฐ๋งŒ ์ œ๊ณตํ•˜๊ณ  JS์—๊ฒŒ ๋งŒ๋“œ๋Š” ์—ญํ• ์„ ๋งก๊ธฐ๋Š” ๋Œ€์‹  ์บ์‹œ๋กœ ๋ถ€ํ„ฐ ์˜จ ํ—ค๋”์—์„œ ์„œ๋น„์Šค ์›Œ์ปค์—๊ฒŒ ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑํ•˜๋„๋ก ํ–ˆ๋‹ค.๊ทธ๋Ÿฌ๋‚˜ ๋ณธ๋ฌธ์€ ๋„คํŠธ์›Œํฌ๋กœ ๋ถ€ํ„ฐ ์˜จ๋‹ค. ๊ทธ๊ฒƒ์€ ์„œ๋ฒ„ ๋ Œ๋”๋ง๊ณผ ๊ฐ™์ง€๋งŒ ์„œ๋น„์Šค ์›Œ์ปค๋ฅผ ์ด์šฉํ•œ ๊ฒƒ์ด๋‹ค.

4

๋ฐ๋ชจ ๋ณด๊ธฐ (์•Œ๋ฆผ: ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ ค๋ฉด ํฌ๋กฌ ์นด๋‚˜๋ฆฌ์•„์—์„œ chrome://flags/#enable-experimental-web-platform-features ์˜ต์…˜์„ ํ™œ์„ฑํ™”์‹œ์ผœ์•ผ ํ•จ)

์„œ๋น„์Šค ์›Œํฌ + ์ŠคํŠธ๋ฆผ ์กฐํ•ฉ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ฑฐ์˜ ์งง์€ ์ˆœ๊ฐ„์— ์ฒซ๋ฒˆ์งธ ๋ Œ๋”๋ง์„ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋‹ค์Œ ๋„คํŠธ์›Œํฌ๋กœ ๋ถ€ํ„ฐ ๋‚ด์šฉ์˜ ์ž‘์€์–‘๋งŒ ํŒŒ์ดํ•‘ํ•˜์—ฌ ์ผ๋ฐ˜ ์„œ๋ฒ„ ๋ Œ๋”๋ง์„ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋œ๋‹ค.

๋‚ด์šฉ์€ ์ผ๋ฐ˜์ ์ธ HTML ํŒŒ์„œ๋กœ ํ†ต๊ณผ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ๋‹น์‹ ์€ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ์–ป๊ฒŒ๋˜๊ณ  ๊ทธ๊ฒƒ์€ ์ˆ˜๋™์œผ๋กœ DOM์œผ๋กœ ๋ถ€ํ„ฐ ์ถ”๊ฐ€๋œ ์ปจํ…์ธ ๋ฅผ ์–ป๋Š” ๊ฒƒ๊ณผ ๋‹ค๋ฅด์ง€ ์•Š๋‹ค.

๋ Œ๋”๋ง ์‹œ๊ฐ„ ๋น„๊ต

IMAGE ALT TEXT HERE

์ŠคํŠธ๋ฆผ ๊ต์ฐจํ•˜๊ธฐ

๊ฒฐํ•ฉ๋œ ์ŠคํŠธ๋ฆผ์€ ํŒŒ์ดํ•‘(piping)์„ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ์ŠคํŠธ๋ฆผ์„ ๊ฒฐํ•ฉํ•˜๋Š” ๊ฒƒ์€ ์กฐ๊ธˆ ์ง€์ €๋ถ„ํ•˜๊ฒŒ ์ˆ˜๋™์œผ๋กœ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.

var stream = new ReadableStream({
  start(controller) {
    // Get promises for response objects for each page part
    // The start and end come from a cache
    var startFetch = caches.match('/page-start.inc');
    var endFetch = caches.match('/page-end.inc');
    // The middle comes from the network, with a fallback
    var middleFetch = fetch('/page-middle.inc')
      .catch(() => caches.match('/page-offline-middle.inc'));

    function pushStream(stream) {
      // Get a lock on the stream
      var reader = stream.getReader();

      return reader.read().then(function process(result) {
        if (result.done) return;
        // Push the value to the combined stream
        controller.enqueue(result.value);
        // Read more & process
        return read().then(process);
      });
    }

    // Get the start response
    startFetch
      // Push its contents to the combined stream
      .then(response => pushStream(response.body))
      // Get the middle response
      .then(() => middleFetch)
      // Push its contents to the combined stream
      .then(response => pushStream(response.body))
      // Get the end response
      .then(() => endFetch)
      // Push its contents to the combined stream
      .then(response => pushStream(response.body))
      // Close our stream, we're done!
      .then(() => controller.close());
  }
});

์ถœ๋ ฅ๋ฌผ์„ ์ŠคํŠธ๋ฆผํ•˜๊ณ  ํ…œํ”Œ๋ฆฟ ์•ˆ์— ํฌํ•จ๋œ ๊ฐ’์„ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉฐ ๋‚ด์šฉ์— ๋Œ€ํ•œ ํŒŒ์ดํ•‘(piping)๊ณผ ์‹ฌ์ง€์–ด ์ฆ‰์„์—์„œ HTML ์ด์Šค์ผ€์ดํ•‘ํ•˜๋Š” Dust.js์™€ ๊ฐ™์€ ์ผ๋ถ€ ํ…œํ”Œ๋ฆฟํŒ… ์–ธ์–ด๊ฐ€ ์žˆ๋‹ค. ๋น ํŠธ๋ฆฐ ๊ฒƒ์ด๋ผ๊ณค ์›น ์ŠคํŠธ๋ฆผ ์ง€์›์ด๋‹ค.

์ŠคํŠธ๋ฆผ์˜ ๋ฏธ๋ž˜

์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์ŠคํŠธ๋ฆผ ์™ธ์˜ ์ŠคํŽ™์ด ์•„์ง ๊ฐœ๋ฐœ๋˜๊ณ  ์žˆ์Œ์—๋„, ์ด๋ฏธ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์€ ๋งค์šฐ ๋†€๋ผ์šด ์ผ์ด๋‹ค. ๋งŒ์•ฝ ์ปจํ…์ธ ๊ฐ€ ๋งŽ์€ ์‚ฌ์ดํŠธ์˜ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๋ฉด์„œ ๊ตฌ์กฐ์˜ ๊ทผ๋ณธ์ ์ธ ๋ณ€ํ™” ์—†๋Š” ์˜คํ”„๋ผ์ธ ์šฐ์„  ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ธฐ ์›ํ•œ๋‹ค๋ฉด, ์„œ๋น„์Šค ์›Œ์ปค๋กœ ์ŠคํŠธ๋ฆผ์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์ด ๋  ๊ฒƒ์ด๋‹ค. ๊ทธ๊ฒƒ์€ ์–ด์จŒ๋“  ๋‚ด๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ๋ธ”๋กœ๊ทธ๊ฐ€ ์˜คํ”„๋ผ์ธ ์šฐ์„  ์ž‘์—…์„ ํ•˜๊ฒŒํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค!

์›น์—์„œ ์ดˆ๊ธฐ์˜ ์ŠคํŠธ๋ฆผ์„ ๊ฐ–๋Š” ๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ ๋ณด์œ ํ•˜๊ณ  ์žˆ๋Š” ์ŠคํŠธ๋ฆฌ๋ฐ ๋Šฅ๋ ฅ์— ๋Œ€ํ•ด ์Šคํฌ๋ฆฝํŠธ๋กœ์˜ ์ ‘๊ทผ์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ดโ€ฆ

  • Gzip/๋ฌด์†์‹ค ์••์ถ•(deflate)
  • ์˜ค๋””์˜ค/๋น„๋””์˜ค ์ฝ”๋ฑ
  • ์ด๋ฏธ์ง€ ์ฝ”๋ฑ
  • HTMl/XML ์ŠคํŠธ๋ฆฌ๋ฐ ํŒŒ์„œ

์•„์ง์€ ์ดˆ๊ธฐ์ง€๋งŒ ๋งŒ์•ฝ ๋‹น์‹ ์ด ์ŠคํŠธ๋ฆผ์— ๋Œ€ํ•ด ์ž์‹ ์˜ API๋ฅผ ์ค€๋น„ํ•˜๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค๋ฉด, ์ผ๋ถ€ ๊ฒฝ์šฐ์— ๋Œ€ํ•ด์„œ ํด๋ฆฌํ•„(polyfill)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฐธ์กฐ ๊ตฌํ˜„(reference implementation)์€ ์žˆ๋‹ค.

์ŠคํŠธ๋ฆฌ๋ฐ์€ ๋ธŒ๋ผ์šฐ์ €์˜ ๊ฐ€์žฅ ํฐ ์ž์‚ฐ ์ค‘ ํ•˜๋‚˜๋‹ค. ๊ทธ๋ฆฌ๊ณ  2016๋…„์€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์— ์˜ํ•ด ์ž ๊ธˆ ํ•ด์ œ๋˜๋Š” ํ•ด๋‹ค.

๊ฐ•์ง€์›…2016.02.22
Back to list