---
title: [作って学ぶ]ブラウザの仕組みを読んで HTTPクライアント実装してみた
tags: 
author: [NishimuraWataru](https://www.docswell.com/user/_n13u_)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/PJR5XQPLE9.jpg?width=480
description: HTTPクライアントをmacOS上でRustを用いて実装した経緯やプロセスについて説明しています。Implementing a simple GET request using HTTP/1.1 standards, 主に技術書を参考にしながらも、一部機能に関してはChatGPTのサポートを受けています。また、実装中に直面した課題や、HTTPプロトコルの背景についても触れています。最後に、AIを活用した開発経験についても報告し、Rustに不慣れであったにもかかわらず、学習を自身で進めた過程を明らかにしています。
published: May 12, 25
canonical: https://www.docswell.com/s/_n13u_/Z6V2D4-2025-05-12-000814
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/PJR5XQPLE9.jpg)

[作って学ぶ]ブラウザの仕組みを読んで
HTTPクライアント実装してみた
n13u / #low_layers_sapporo / 2025.05.11


# Page. 2

![Page Image](https://bcdn.docswell.com/page/PEXK65N6JX.jpg)

自己紹介
●
●
2002年生まれ、札幌市みてきて北区生、あつべつ・く育、市電のふるさと中央区在住
Nishimura Wataru（_n13u_）
○
○
○
○
●
最近のお仕事
○
●
ECMAScript
好きなWebフロントエンドフレームワーク
○
●
Next.js App Router 移行
好きな言語
○
●
ちょっと株式会社 受託事業部 第二ユニットのエンジニアをやってます.
札幌市事業Sapporo Engineer Base 運営
一般社団法人未完 代表理事（予定）
他開発の副業お仕事 etc…
Next.js（App Router）
最近ハマっていること
○
CANDY TUNE / こっちゃん
2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/3EK516NGJD.jpg)

今日する話
●
●
●
[作って学ぶ]ブラウザの仕組み
HTTPクライアントを実装した話
macOS w/Rust でHTTPクライアント作った


# Page. 4

![Page Image](https://bcdn.docswell.com/page/L73KQ6V575.jpg)

HTTPクライアント作ったよ
4hくらい?


# Page. 5

![Page Image](https://bcdn.docswell.com/page/87D39V8YJG.jpg)

昨日（資料とセットで）作りました （今日もちょっとやった）
●
●
シーシャ吸いながら作ってました（キラキラ系アピール）
本読んだだけの話にしようかなと思ったけど、“甘い”と思いやめました


# Page. 6

![Page Image](https://bcdn.docswell.com/page/VJP4ZV8278.jpg)

https://github.com/WataruNishimura/rusty-http


# Page. 7

![Page Image](https://bcdn.docswell.com/page/2EVXWZNXJQ.jpg)

ところで、低レイヤー...？
●
●
●
どこからが“低”なのか..
自称プロフロントエンドエンジニアなので、HTTPは低めではある
「ネットワークから作ろうな」
Web Application
ーーーーーー
Browser
ーーーーーー
HTTP, browser engines, etc.
👈ChatGPTに作らせたOSIとTCP/IPの画
像


# Page. 8

![Page Image](https://bcdn.docswell.com/page/57GVD4KR7L.jpg)

[作って学ぶ]ブラウザの仕組み
●
●
●
●
●
積読してた
技術評論社から出ている書籍
ブラウザの仕組みと銘打ち、HTML/CSSのパースとか網羅的に知識をカバーしている
Rustで書くことが前提👈いいね
書籍で出てくるブラウザはWasabiOSというhikaliumsさん作成の自作OS(!?)の上で動くことを
目標に作られている
○
○
●
https://github.com/hikalium/wasabi
Web Browser + sabi（rust）でwasabiらしい、おしゃれすぎ
今回のhttpクライアントは第三章の実装をほぼ参考に作りました
○
macOS向けの実装だけはChatGPT先生とかに聞いてます
https://gihyo.jp/book/2024/978-4-297-14546-0


# Page. 9

![Page Image](https://bcdn.docswell.com/page/4EQ61GNYEP.jpg)

今回作ったもの
●
●
●
HTTP 1.1準拠（らしい）でGETメソッドだけを実装したもの
標準入出力に値を入れたら返ってくるだけのもの
もちろんリダイレクトとかはしていない(((
○
●
RFC9011だと推奨らしいのでRFC上はなくてもいいらしいけど、ないと使えないよね
WasabiOSのインストールが面倒だったのでmacOS上で動くように
○
○
○
ちょこちょこ実装が異なります
WasabiOSではRustのstdライブラリが使えないが、実装の都合上macOSで使うのでstdを使うように
DNSライブラリ、TCPやurlパーサーが著者提供のものからstdかcrate.io経由で使うように


# Page. 10

![Page Image](https://bcdn.docswell.com/page/KJ44R5GZJ1.jpg)

ちなみに


# Page. 11

![Page Image](https://bcdn.docswell.com/page/LE14QWDDEG.jpg)

HTTPのGETだけなら 実装は簡単
●
HTTPはテキストベースのプロトコル
○
○
○
空白と改行で構造を作っている
詰まるところ、文字列操作がメイン
文字コードは慣習的にUTF-8になっている
■
■
■
●
もとは、ISO-8859-1（Latin-1）が標準
本来はContent-TypeかHTMLで指定されたchartsetを利用する
HTMLも基本的にはUTF-8なので問題ない
なので、TCP接続を作成して文字列をバイトコードとしてやり取りするだけ


# Page. 12

![Page Image](https://bcdn.docswell.com/page/GEWXM6Y872.jpg)

簡単だね!


# Page. 13

![Page Image](https://bcdn.docswell.com/page/47Z6QYX973.jpg)

そんなわけはない
●
URLのパーサーは？
○
○
●
●
●
●
●
●
Clineに書かせればおk
ちゃんと第二章で書いてるので、良い子のみんなは第二章からやろう
リダイレクト処理はRFCだと推奨だけど慣習的には必須
文字コードだって本来はしっかりとデータの中身を読んで判別するべき
HTTPのバージョン切り替えやHTTP/3のQUIC対応、
RFCを踏襲した実装とは程遠い...orz
我々が普段使っているブラウザは相当なコストをかけて作られている
動けばいいコードなら確かに早いかも
○


# Page. 14

![Page Image](https://bcdn.docswell.com/page/YJ626D4D7V.jpg)

とりあえず動かしてみる


# Page. 15

![Page Image](https://bcdn.docswell.com/page/GJ5263Q8J4.jpg)

という感じ


# Page. 16

![Page Image](https://bcdn.docswell.com/page/LE3KQ6VGE5.jpg)

ざっくりコード解説


# Page. 17

![Page Image](https://bcdn.docswell.com/page/8ED39V8N7G.jpg)

コード解説
●
●
●
メインの処理は src/http.rs で書いてる
get関数の中に全て詰めている
他のファイルは必要な構造体とかだけを出している
○
response.rs でレスポンスの定義と実装だけしている


# Page. 18

![Page Image](https://bcdn.docswell.com/page/V7P4ZV8NE8.jpg)

流れ
●
●
DNS名前解決
TCP接続作成
○
●
●
●
ライブラリに任せるとSYN/ACKとかやらなくて良い
バッファを読み込み
文字列ゴニョゴニョ
出力


# Page. 19

![Page Image](https://bcdn.docswell.com/page/2JVXWZNY7Q.jpg)

DNS名前解決
●
●
ホスト情報から名前解決を行う
今回はTrust-DNSを利用
○
●
非同期でやるとやや面倒だったので同期で実装(((
○
●
本の方はなんとWasabiOS用のDNSライブラリがあります!?
std環境でやるなら大変じゃないかも、no-stdだとasync/awaitが大変
今回はもらったIPアドレスのうち0番目を利用
let resolver = Resolver::new(ResolverConfig::default(),
ResolverOpts::default()).unwrap();
let ips = match resolver.lookup_ip(&amp;host) {
Ok(response) =&gt; response.iter().collect::&lt;Vec&lt;IpAddr&gt;&gt;(),
Err(_e) =&gt; {
return Err(Error::Network(&quot;Failed to find IP addresses&quot;.to_string()));
}
};


# Page. 20

![Page Image](https://bcdn.docswell.com/page/5EGVD4KWEL.jpg)

TCP接続作成
●
●
ソケットアドレスをIPとportから作成
TCP接続を作成
○
本の方だとこれも自作ライブラリのはず
let socket_addr = SocketAddr::new(ips[0], port);
// TCPコネクションを構築
let mut stream = match TcpStream::connect(socket_addr) {
Ok(stream) =&gt; stream,
Err(_e) =&gt; {
return Err(Error::Network(&quot;Failed to connect to
server&quot;.to_string()));
}
};


# Page. 21

![Page Image](https://bcdn.docswell.com/page/4JQ61GNQJP.jpg)

GETリクエストを作成
●
リクエストラインと呼ばれる文字列を作成（リクエストの１行目）
○
○
○
Method SP Path SP Version
あとは全部ヘッダー（CRLF改行）
最後にCRLFを２回入れることでヘッダー部の終わりを示せる
let request_line: String = format!(&quot;GET {} HTTP/1.1&quot;, path);
let headers: Vec&lt;String&gt; = vec![
format!(&quot;Host: {}&quot;, host),
&quot;Connection: close&quot;.to_string(),
&quot;User-Agent: RustyHttp/0.1&quot;.to_string(),
&quot;Accept: */*&quot;.to_string(),
];
let request: String = format!(&quot;{}\r\n{}\r\n\r\n&quot;, request_line,
headers.join(&quot;\r\n&quot;));


# Page. 22

![Page Image](https://bcdn.docswell.com/page/K744R5GY71.jpg)

作成したリクエストをTCPソケットに送る
●
●
●
作成した文字列をバイト列にして書き込み
write_allにすると書き込みが完全に終わるまで待ってくれる
最後に、shutdownをして終了を宣言しないとレスポンスが返ってこない
match stream.write_all(request.as_bytes()) {
Ok(_bytes) =&gt; (),
Err(_e) =&gt; {
return Err(Error::Network(&quot;Failed to write to
server&quot;.to_string()));
}
};
stream.shutdown(std::net::Shutdown::Write).ok();


# Page. 23

![Page Image](https://bcdn.docswell.com/page/LJ14QWDNJG.jpg)

UTF-8にして終わり
●
●
receivedをバイト列からUTF-8の文字列にして終了
本来であればContent-Typeとかをみたり、bodyに含まれるHTML内のchartsetをみるべきなの
で、http通信のクライアントとしては責務が大きすぎるかも
match core::str::from_utf8(&amp;received) {
Ok(response_str) =&gt; {
let response =
HttpResponse::new(response_str.to_string());
Ok(response)
}
Err(_e) =&gt; {
Err(Error::Network(&quot;Failed to parse
response&quot;.to_string()))
}
}


# Page. 24

![Page Image](https://bcdn.docswell.com/page/GJWXM68ME2.jpg)

最後に


# Page. 25

![Page Image](https://bcdn.docswell.com/page/4EZ6QY8ME3.jpg)

AI駆動開発の勘所
●
●
●
書籍ベースだったので大して苦しまなかった
他言語の経験はあるけど、Rustの経験がほぼない状態だった
Clineとかのエージェントにやらせると学習の意味がなくなる
○
○
●
わからない概念が多々出てくるけど、それらはChatGPT先生に聞けば解決する
○
○
●
●
ただし、本質からそれるErrorの列挙型の実装とかは任せちゃった
あとURLのパース処理も最低限頑張ってくれるやろと思い...
借用とか、所有権とか
あとそもそものRustの構文とか...
ライブラリの検索も多分ChatGPTで事足りた
RFCの検索は、どういうRFCがある？って聞いて参照しつつ実際は本物を見た方が良さそう


