HTTP ํ†บ์•„๋ณด๊ธฐ with Rust

HTTP๋Š” TCP ํ”„๋กœํ† ์ฝœ ์œ„์— ์ž‘์„ฑ๋œ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜์˜ ํ”„๋กœํ† ์ฝœ์ด๋‹ค.

์†Œ์œ„ "์›น"์ด๋ผ๊ณ  ํ•˜๋Š” ๊ฒƒ๋“ค์€ ์ „๋ถ€ HTTP๋กœ ์ž‘์„ฑ๋œ ์„œ๋ฒ„๋ฅผ ๋งํ•œ๋‹ค.
๊ทธ๋ž˜์„œ ๋ชจ๋“  ์›น์‚ฌ์ดํŠธ๋Š” HTTP๋กœ ์ž‘์„ฑ๋œ๋‹ค.




๋ฒ„์ „ ํžˆ์Šคํ† ๋ฆฌ



HTTP 1.1

1999๋…„์— ์ฒ˜์Œ ๋‚˜์™”๋‹ค.
์ง„์ •ํ•œ HTTP์˜ ์—ญ์‚ฌ๋Š” 1.1๋ถ€ํ„ฐ ์‹œ์ž‘ํ•œ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.
๋Œ€๋ถ€๋ถ„์˜ ์›น ํ™˜๊ฒฝ์€ ๋‹ค HTTP 1.1์„ ์ง€์›ํ•˜๊ณ , ์‚ฌ์šฉํ•˜๋Š”๋ฐ๋‹ค, ๋Œ€๋ถ€๋ถ„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ๋„ ์ด๊ฒŒ ๊ธฐ๋ณธ๊ฐ’์ด๋‹ค.

๊ทธ๋ƒฅ ๋ธŒ๋ผ์šฐ์ € ์ผœ์„œ ์–ด๋”” ๋“ค์–ด๊ฐ€๋ฉด ๋‹ค HTTP 1.1๋กœ ๋™์ž‘ํ•œ๋‹ค.



HTTP 2

2015๋…„์— ๋‚˜์™”๋‹ค.
HTTP 1.1์€ ๋‹จ์ˆœํ•˜๊ณ  ์ž˜ ๋™์ž‘ํ–ˆ์ง€๋งŒ ๋‹จ์ ์ด ๊ฝค ๋งŽ์•˜๋Š”๋ฐ, ์„ฑ๋Šฅ์ ์ธ ์ธก๋ฉด์—์„œ ๊ฐœ์„ ์„ ๋งŽ์ด ๊ฐ€ํ•œ ๊ฒŒ ๋ฒ„์ „ 2๋‹ค.
gRPC ํ”„๋กœํ† ์ฝœ์ด HTTP 2 ์œ„์—์„œ ๋™์ž‘ํ•œ๋‹ค.

1. Multiplexed Stream

์˜ˆ๋ฅผ ๋“ค์–ด, 1.1์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐจ๋ก€๋Œ€๋กœ๋งŒ ๋กœ๋“œํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•œ ๋ฆฌ์†Œ์Šค๊ฐ€ ๋А๋ ค์ง€๋ฉด ๋‹ค๋ฅธ ๋ฆฌ์†Œ์Šค๋“ค๋„ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ•˜๋Š” ๋น„ํšจ์œจ์ ์ธ ๋™์ž‘์ด ์žˆ์—ˆ๋Š”๋ฐ, HTTP2์—์„œ๋Š” ๋™์‹œ ํš๋“์ด ๊ฐ€๋Šฅํ•œ multiplexed stream ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋๋‹ค.
์œ„ ๊ทธ๋ฆผ๊ณผ ๊ฐ™๋‹ค.

2. ํ—ค๋” ์••์ถ•
๋™์ผํ•œ Header ์ „์†ก ๋ฐ˜๋ณต์œผ๋กœ ์ธํ•œ ์„ฑ๋Šฅ ๋‚ญ๋น„๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ํ—ค๋”๋ฅผ ์••์ถ•ํ•ด์„œ ์ „์†กํ•˜๋Š” HPACK ๊ธฐ๋ฒ•์ด ์ถ”๊ฐ€๋๋‹ค.

3. ์„œ๋ฒ„ ํ‘ธ์‹œ
HTTP 2๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์—๊ฒŒ ์ผ๋ฐฉ์ ์œผ๋กœ ๋ณด๋‚ด๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ, ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ณด๋‚ด๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.
์š”์ฒญํ•  ๊ฒƒ์ด๋ผ๊ณ  ์˜ˆ์ƒ๋˜๋Š” ์ •์  ๋ฆฌ์†Œ์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๊ธฐ ์ „์— ๋ฏธ๋ฆฌ ๋ณด๋‚ด์ฃผ๋Š” ๊ฑฐ๋ผ์„œ, ์บ์‹œ์˜ ๊ฐœ๋…์— ๊ฐ€๊น๋‹ค.



HTTP 3 (quic)

2022๋…„์— ๋ฐœํ‘œ๋๋‹ค.
๋Œ€๊ฒฉ๋ณ€์ด ์ผ์–ด๋‚œ ๋ฒ„์ „์ด๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” TCP๋ฅผ ๋ฒ„๋ฆฌ๊ณ  UDP ์†Œ์ผ“์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์žฌ๊ตฌํ˜„์„ ํ–ˆ๋‹ค! TCP์— ๋ถˆํ•„์š”ํ•œ ๋ ˆ๊ฑฐ์‹œํ•œ ๊ตฌ์„ฑ์š”์†Œ๋“ค์ด ๋„ˆ๋ฌด ๋งŽ์•„์„œ ๊ทธ๊ฑธ ๋œ์–ด๋‚ด๋Š” ๊ฒƒ์ด ์ฃผ ๋ชฉ์ ์ด์—ˆ๋‹ค.

์•„์ง ์—์ฝ”์‹œ์Šคํ…œ์ด ์ถฉ๋ถ„ํžˆ ๊ฐ–์ถฐ์ ธ์žˆ์ง€๋Š” ์•Š๋‹ค๋Š” ๊ฒƒ์ด ํฐ ๋‹จ์ ์ด๋‹ค.

์ „๋ฐ˜์ ์œผ๋กœ ์„ฑ๋Šฅ์ด ๋” ๋น ๋ฅธ๋ฐ๋‹ค, HTTP2 ๋Œ€๋น„ ํŒจํ‚ท ์†์‹ค์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ๋” ์ข‹์•„์กŒ๋‹ค.

IP ๋ณ€๊ฒฝ
IP๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ์—ฐ๊ฒฐ์ด ์œ ์ง€๋˜๋Š” ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋๋‹ค.

TLS
TLS(SSL)๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ๋“ค์–ด๊ฐ„๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ํ•ธ๋“œ์…ฐ์ดํฌ์™€ ๋™์‹œ์— ์ฒ˜๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ๋นจ๋ผ์ง„๋‹ค.

Zero RTT(Round Trip Time)
HTTP์—๋Š” ์™•๋ณต ์‹œ๊ฐ„(RTT)์ด๋ผ๋Š” ๊ฐœ๋…์ด ์žˆ์—ˆ๋‹ค.
ํ•ธ๋“œ์…ฐ์ดํฌ๋ฅผ ์œ„ํ•ด์„œ ๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ ์ง€์—ฐ์‹œ๊ฐ„์ด ๋งŽ์ด ๋ฐœ์ƒํ–ˆ์—ˆ๋Š”๋ฐ, HTTP3์—์„œ๋Š” ์ด๋ฏธ ์—ฐ๊ฒฐ๋œ ์„œ๋ฒ„์—์„œ๋Š” ํ•ธ๋“œ์…ฐ์ดํฌ๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ฌผ๋ก  ์ตœ์ดˆ ์—ฐ๊ฒฐ์‹œ์—๋Š” ํ•œ๋‹ค.




์žฅ๋‹จ์ 

์š”์ฒญ<->์‘๋‹ต์˜ ๊ตฌ์กฐ๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌ๋˜์–ด์žˆ๊ณ , ์บ์‹œ, ์ธ์ฝ”๋”ฉ ๋“ฑ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค์ด ์ „๋ถ€ ๋‹ค ์™„๋น„๋œ๋ฐ๋‹ค, ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜์˜ ํ”„๋กœํ† ์ฝœ์ด๋ผ ์‚ฌ์šฉ์ด๋‚˜ ๋ถ„์„์ด ๋งค์šฐ ์‰ฝ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.
๊ฒŒ๋‹ค๊ฐ€ ๋ชจ๋“  ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ ์™„๋ฒฝํ•œ ๊ตฌํ˜„์ฒด๋“ค์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์—์„œ MSA์—์„œ์˜ RPC์—๋„ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ์žฅ์ ์„ ๊ฐ–๊ณ  ์žˆ๋‹ค. ์ด๊ฑด gRPC๋„ ๋”ฐ๋ผ์˜ค์ง€ ๋ชปํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค.

๋Œ€์‹  ํ†ต์งœ ํ…์ŠคํŠธ๋กœ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋‹ค๋ณด๋‹ˆ, ๋ฌด๊ฑฐ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์–ด๋‚˜๋ฅผ๋•Œ๋Š” ์กฐ๊ธˆ ๋А๋ฆด ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. ๊ทธ๊ฒŒ ๊ฑธ๋ฆฌ๋ฉด gRPC๋ฅผ ์“ฐ๋ฉด ๋œ๋‹ค.

์•„์ฃผ ๋งŽ์€ ๊ณณ์— ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๊ณ , ์•ž์œผ๋กœ๋„ ๋”์šฑ ๊ทธ๋Ÿด ๊ฒƒ์ด๋‹ค.




๊นŒ๋ณด๊ธฐ

TCP ์†Œ์ผ“์„ ๊ธฐ๋ฐ˜์œผ๋กœ HTTP 1.1๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณด๋ฉด์„œ ๊ทธ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•ด๋ณด๋Š” ์‹œ๊ฐ„์„ ๊ฐ–๋„๋ก ํ•˜๊ฒ ๋‹ค.
์‚ฌ์‹ค ๊ณจ์น˜์•„ํ”„๊ณ  ๋ณต์žกํ•œ๊ฑด ์ด๋ฏธ TCP ๋ ˆ์ด์–ด์—์„œ ๋‹ค ์ฒ˜๋ฆฌํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ์ƒ๊ฐ๋ณด๋‹ค ๊ทธ๋ฆฌ ๋ณต์žกํ•˜๊ฑฐ๋‚˜ ์–ด๋ ค์šด๊ฑด ๋ณ„๋กœ ์—†๋‹ค.

๋งŒ์•ฝ ๋‹ค์Œ๊ณผ ๊ฐ™์ด API๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด

๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ TCP ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š”๋‹ค.
์•„๋ž˜๋Š” wireshark์˜ tcp stream์œผ๋กœ ์กฐํšŒํ•œ ๋‚ด์šฉ์ด๋‹ค.

๋ป˜๊ฑด์ƒ‰์ด ์š”์ฒญ์ด๊ณ , ํผ๋Ÿฐ์ƒ‰์ด ์‘๋‹ต ๋ฐ์ดํ„ฐ๋‹ค.
๊ทธ๋ƒฅ ์ €๊ฑธ ์ฃผ๊ณ ๋ฐ›๊ณ  ํŒŒ์‹ฑํ•˜๋Š” ๊ฒƒ๋งŒ ๊ตฌํ˜„ํ•ด์ฃผ๋ฉด ๋œ๋‹ค..




Server: ์š”์ฒญ ๋ฐ›์•„๋ณด๊ธฐ

๋จผ์ € ์„œ๋ฒ„ ๊ป๋ฐ๊ธฐ๋ถ€ํ„ฐ ๋งŒ๋“ค์–ด๋ณด๊ฒ ๋‹ค.
์šฐ์„  TCP ์š”์ฒญ์„ ๋ฐ›์•„์„œ, ๊ทธ ์š”์ฒญ์— ๋”ธ๋ฆฐ ํŒจํ‚ท ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ฒˆ ๊นŒ๋ณด๊ฒ ๋‹ค.

use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = TcpListener::bind("0.0.0.0:8888").await?;

    loop {
        let (mut socket, _) = listener.accept().await?;

        tokio::spawn(async move {
            let mut read_buffer = [0; 1024];

            loop {
                let n = match socket.read(&mut read_buffer).await {
                    // socket closed
                    Ok(n) if n == 0 => return,
                    Ok(n) => n,
                    Err(e) => {
                        eprintln!("failed to read from socket; err = {:?}", e);
                        return;
                    }
                };

                let text = String::from_utf8(Vec::from(read_buffer)).unwrap();

                println!("Client Says: {:?}", text);

                // ๋Œ€๋‹ตํ•˜๊ธฐ...
                let write_buffer = [0; 1024];

                if let Err(e) = socket.write_all(&write_buffer[0..n]).await {
                    eprintln!("failed to write to socket; err = {:?}", e);
                    return;
                }
            }
        });
    }
}

์‹คํ–‰ํ•˜๊ณ 

๋ธŒ๋ผ์šฐ์ €๋กœ ์ฐ”๋Ÿฌ๋ณด๋ฉด

์ ‘์†์€ ๋˜์ง€ ์•Š๋”๋ผ๋„, ๋ธŒ๋ผ์šฐ์ €์—์„œ ์˜์•„๋ณด๋‚ธ ์š”์ฒญ ๋ฐ์ดํ„ฐ๋Š” ์ฝ์–ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.


๊ทธ๋Ÿผ ๋Œ€๋žต ์ด๋Ÿฐ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚ ๋ผ์˜จ๋‹ค.
์ €๊ธฐ ๋“ค์–ด์žˆ๋Š”๊ฒŒ ๋‹ค ์š”์ฒญ headers๋“ค์ด๋‹ค.
ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜์ด๋ผ ์•Œ์•„๋ณด๊ธฐ๋Š” ์ฐธ ์‰ฝ๋‹ค.

์„œ๋ฒ„์—์„œ๋Š” ์ €๊ฑธ ๊ทธ๋Œ€๋กœ ํŒŒ์‹ฑํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

์ด๋Ÿฐ์‹์œผ๋กœ๋‹ค๊ฐ€




Server: ์‘๋‹ต ๋ณด๋‚ด๊ธฐ

์ด๋ฒˆ์—๋Š” ์‘๋‹ต๊นŒ์ง€ ๊ตฌ์„ฑํ•ด์„œ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์ •์ƒ ๋™์ž‘์ด ๋˜๊ฒŒ๋” ๋งŒ๋“ค์–ด๋ณด๊ฒ ๋‹ค.

๋ฐฉ๋ฒ•์€ ๊ทธ๋ฆฌ ์–ด๋ ต์ง€ ์•Š๋‹ค.
์‘๋‹ต ํ…์ŠคํŠธ์˜ ํฌ๋งท์€ ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ์ธ๋ฐ

์‘๋‹ต ์ƒํƒœ
์‘๋‹ต ํ—ค๋”1
์‘๋‹ต ํ—ค๋”2
...
์‘๋‹ต ํ—ค๋”N

์‘๋‹ต ๋ณธ๋ฌธ

์ €๋Œ€๋กœ ๋”ฑ ๊ตฌ์„ฑํ•ด์„œ ๋ณด๋‚ด์ฃผ๋ฉด ๋œ๋‹ค.

ํ•„์ˆ˜ ํ—ค๋”๋Š” Content-Length ์ •๋„๋‹ค.

์ €๋ž˜๊ฐ–๊ณ  ๋‹ค์‹œ ์‹คํ–‰ํ•ด๋ณด๋ฉด


์ด์ œ ๋  ๊ฒƒ์ด๋‹ค.

์ „์ฒด ์ฝ”๋“œ

use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = TcpListener::bind("0.0.0.0:8888").await?;

    loop {
        let (mut socket, _) = listener.accept().await?;

        tokio::spawn(async move {
            let mut read_buffer = [0; 1024];

            loop {
                let n = match socket.read(&mut read_buffer).await {
                    // socket closed
                    Ok(n) if n == 0 => return,
                    Ok(n) => n,
                    Err(e) => {
                        eprintln!("failed to read from socket; err = {:?}", e);
                        return;
                    }
                };
                println!("read {} bytes", n);

                let text = String::from_utf8(Vec::from(&read_buffer[0..n])).unwrap();

                println!("Client Says: {:?}", text);

                let mut method = "NONE";
                let mut uri = "";
                let mut host = "";
                for line in text.split("\r\n") {
                    match line {
                        line if line.starts_with("GET") => {
                            method = "GET";
                            uri = line.split(" ").nth(1).unwrap();
                        }
                        line if line.starts_with("POST") => {
                            method = "POST";
                            uri = line.split(" ").nth(1).unwrap();
                        }
                        line if line.starts_with("Host") => {
                            host = line.split(" ").nth(1).unwrap();
                        }
                        _ => {}
                    }
                }
                println!("{method} {host}{uri}");

                // ๋Œ€๋‹ตํ•˜๊ธฐ...
                let response_headers =
                    vec!["HTTP/1.1 200 OK", "Content-Length: 11", "", "Hello World"];
                let response_messages = response_headers.join("\r\n");

                println!("{response_messages}");

                if let Err(e) = socket.write_all(response_messages.as_bytes()).await {
                    eprintln!("failed to write to socket; err = {:?}", e);
                    return;
                }
            }
        });
    }
}




Client ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ

์ด๋ฒˆ์—๋Š” client๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์„œ ์›๋ž˜ ์žˆ๋˜

์ด ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋‚ ๋ ค๋ณด๊ฒ ๋‹ค.
๋‚ด๊ฐ€ ๋งŒ๋“ ๊ฒŒ ์•„๋‹ˆ๋ผ express๋กœ ๊ตฌํ˜„๋œ ์„œ๋ฒ„๋‹ค.

Client๋„ TCP ์œ„์—์„œ ๊ตฌํ˜„ํ•˜๋Š”๊ฑฐ๋ผ๋ฉด ์‰ฝ๋‹ค.
์•„๋ž˜์˜ ํ˜•ํƒœ๋กœ ์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ๋ง์•„์„œ tcp๋กœ ์˜๊ณ , ๋‹ค์‹œ ์‘๋‹ต์„ ๋ฐ›์•„์˜ค๋ฉด ๊ทธ๋งŒ์ด๋‹ค.

METHOD ๊ฒฝ๋กœ HTTP๋ฒ„์ „
์š”์ฒญ ํ—ค๋”1
์š”์ฒญ ํ—ค๋”2
...
์š”์ฒญ ํ—ค๋”N

์š”์ฒญ ๋ฐ”๋””

์ด๋ ‡๊ฒŒ ๋ง์ด๋‹ค.

๊ทธ๋Ÿผ ์ž˜ ๋Œ์•„๊ฐˆ ๊ฒƒ์ด๋‹ค.

์ „์ฒด ์ฝ”๋“œ

use tokio::{
    io::{AsyncReadExt, AsyncWriteExt},
    net::TcpStream,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = TcpStream::connect("localhost:8110").await.unwrap();

    let messages = vec![
        "GET / HTTP/1.1",
        "Host: localhost:8110",
        "Connection: keep-alive",
        "",
        "",
    ];

    client
        .write_all(messages.join("\r\n").as_bytes())
        .await
        .unwrap();

    let mut buffer = [0; 1024];
    client.read(&mut buffer).await.unwrap();

    println!("{}", String::from_utf8_lossy(&buffer));

    Ok(())
}


์ฐธ์กฐ
https://developer.mozilla.org/ko/docs/Web/HTTP/Messages
https://doc.rust-lang.org/book/ch20-01-single-threaded.html
https://www.cloudflare.com/ko-kr/learning/performance/http2-vs-http1.1/