JUNのブログ

JUNのブログ

活動記録や技術メモ

Dockerのポートマッピングのデフォルト設定は危ない

あらすじ

公衆WiFiに繋いだ状態でいつものように docker container run -p 8080:80 nginx のような感じでDockerコンテナを動かしていたら、外部からリクエストを受信した。

ファイアウォールを設定し、外部からのアクセスを拒否しているはずなのになぜアクセスできたんだ...

環境

  • Docker desktop for mac with apple silicon 4.21.0

何が起きた?

Dockerはデフォルトの設定では-p 8080:80のようにポートマッピングするとファイアウォールの設定を書き換え、外部からそのポートへのアクセスを許可するようになっている。 その結果LAN内の他のPCから対象ポートにアクセス出来てしまう。

ちなみにこれはDocker公式からも注意が出ている。

Publishing container ports is insecure by default. Meaning, when you publish a container’s ports it becomes available not only to the Docker host, but to the outside world as well.

If you include the localhost IP address (127.0.0.1) with the publish flag, only the Docker host can access the published container port.

$ docker run -p 127.0.0.1:8080:80 nginx docs.docker.com

上記の注意の通り、デフォルトでは全てのホストからアクセスできるようにDockerがファイアウォール設定を書き換えてしまう。

対策

ポートマッピングコマンドライン引数などを修正する

-p 127.0.0.1:8080:80 のようにポートの公開範囲を指定すれば、LAN全体に向けてポートを公開することはない。

docker-compose.yml の場合も同じく各サービスコンテナのポートマッピングの指定時に port: "127.0.0.1:8080:80" のように公開先IPアドレスを明示的に指定することでポートが外部に公開されなくなる。

Docker Engine の設定を書き換える (おすすめ)

コマンドライン引数やdocker-compose.ymlを書き換えるというのは毎回大変だし、サーバーで動かす時はポートのIPアドレスの指定を消さないといけないし、どうせ書き換えるのを忘れる日がいつか来るので、手元の開発マシンに関してはデフォルトでポートの公開範囲を制限するのがいいと思います。

"ip": "127.0.0.1" をDockerEngineの設定に追加してあげれば、デフォルトの公開範囲をローカルホスト内だけにできるので設定しましょう。

docs.docker.com

Docker for Mac だったら以下のようなイメージ。

試してみる

以下のようにホスト側の8080番ポートをコンテナの80番ポートにマッピングする。(よく見かけるオプションですね)

$ docker container run -p 8080:80 nginx:latest

そんでローカルIPアドレスに対してリクエストを送ってアクセスできるか見てみる。

$ curl <local_ip>:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

nginxのコンテナにアクセスできていることがわかる。

また、nmapで確認してみると8080ポートが開放されていることがわかる。

$ nmap 172.20.10.8
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-01 22:11 JST
Nmap scan report for 172.20.10.8
Host is up (0.000036s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT     STATE SERVICE
5000/tcp open  upnp
7000/tcp open  afs3-fileserver
8080/tcp open  http-proxy

Nmap done: 1 IP address (1 host up) scanned in 0.08 seconds

これはつまり、同じLAN内であれば他のPCやスマホからもアクセスできる。やばいわよ!

このインセキュアなデフォルト挙動は、前述した設定(-p 127.0.0.1:8080:80 または「Docker Engine の設定を書き換える (おすすめ)」)を施すと解決する。

以下Docker Engineの設定を書き換えた後のnmap

$ nmap 172.20.10.8
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-01 22:44 JST
Nmap scan report for 172.20.10.8
Host is up (0.000048s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT     STATE SERVICE
5000/tcp open  upnp
7000/tcp open  afs3-fileserver

Nmap done: 1 IP address (1 host up) scanned in 0.05 seconds

無事8080ポートが公開されないようになった。良かった。

感想

Publishing container ports is insecure by default.

なんでデフォルトがinsecureやねん!

Dockerを数年触っているが、Dockerがファイアウォールの設定を書き換えることは知らなかった。改めて自分が使うツールについてはしっかりと理解し、ドキュメントをちゃんと読もうと思った。

Dockerのインストール方法について解説している記事を読んでもこのようなことが書かれているのを自分は見たことがなかったし、結構この危ない仕様知らない人が多そうで気づかずポート開放してる人がいそうで怖いですね。

この記事を見て皆様のDockerが少しでも安全になれば幸いです。

ではでは〜👋👋

参考リンク

tec.tecotec.co.jp

yoshinorin.net

2022年振り返り

はじめに

2022年も終わりなんで振り返ります。

今年は42の課題を色々したり、オンラインゲームにハマったり、インターンしたり、就職先が決まったりしました。

本当は毎月の振り返りを書くことによって、1年の振り返りを書かなくていいようにしようと思っていたのですが、5ヶ月目で飽きて辞めてしまってので例年どおり振り返りを書こうと思います。

↓1ヶ月の振り返り記事ってのはこういうやつね。

jun-networks.hatenablog.com

↓1年振り返り去年のやーつ

jun-networks.hatenablog.com

1月

何やったっけな~~覚えてねぇなぁ~~~って思ったら、振り返り記事に色々書いていたのでした。そっち見てください。

jun-networks.hatenablog.com

この記事読んで思ったけど、やっぱり振り返り記事書いた方が便利だな...でも書くの面倒くさいんだよな...

そういえばC++STL再実装の課題はこの時期にやっていたのでした。この課題ではインターフェースの共通化(Iterator Patternとか)の強力さとかを実感したのと、mapがなぜ O(log(n)) なのかを実装を通じてしっかり理解できました。あとは純粋にC++書くのに慣れた。

Mixi Git Challenge もやった。なんか去年くらいの気分だけど今年の話だった。

2月

これも同様に振り返り記事書いてるからそっち見たほうが良い。なぜなら情報の鮮度が良いので。

jun-networks.hatenablog.com

2月は短くてなんかパッとしないね。Kotlinしたくらいかな?結局使わなかったけど。

最近AndroidとかiOSアプリ開発も面白そうだなぁってちょっと思った。思っただけ。

3月

これも同様に振り返り記事書いてるからそっち見たほうが良い。なぜなら情報の鮮度が良いので。

jun-networks.hatenablog.com

インターンシップしたよ!楽しかったよ! Clean Architecture の変化への強さを知ったよ!

ソフトウェアアーキテクチャデザインパターン、やはり先人は偉大だ。

4月

これも同様に振り返r(ry

jun-networks.hatenablog.com

内定GET! 就活おわり!

5月

これも同様に振り返り記事を...って無い!?

はい。ここで力尽きました。力尽きたというか、なんか書くより先に6月になっちゃって、「あ~6月なっちゃったなぁ~」って感じで書かなくなった。

ってことでここからは頭とインターネットから情報を集めて思い出します。

この月はApexにハマったり、淡路島にドライブに行ったり、競馬したりって感じでした。いっぱい遊んだ。

NginxっぽいWebServerをC++で再実装する課題(webservという)に取り組み始めた。この頃はソケットやHTTPのRFCを読んだり、コンフィグのBNFを書いたりしてた。

↓この記事のやつね。

jun-networks.hatenablog.com

(このwebserv記事は少し反省点があるので、以下にメモ)

自分のwebserv記事なんで読みにくいんだって思ったら、いつもと違って雑になぐり書きしたのもそうだけど、そもそも構成が良くないな。 今までの自分の記事はまずはじめに実装するために必要な知識の俯瞰図と実装ロードマップみたいなものを提示し、それについての説明などを行った後に自分の実装はそれらの要素をどのように活用し解決するようなコードを書くようにしていたから、読んでいて「あ~この辺のはこういうふうに実装したのね」ってなるんだけど、今回の自分のwebserv記事はロードマップや必要知識の俯瞰図みたいなものを事前にあまり提示せずにいきなり自分の実装の話をしているから読んでいて「なんか急にわけわからん話しだしたな」っていう感覚が強いんだな。 ちゃんと課題の話と自分の実装の話は分けないといい記事にはならないな。

6月

引き続きwebservに取り組んでいた。全体のシーケンス図を書き、それに基づいたコードを書き、CGIRFCを読み、Apacheなどの挙動を確認し、デバッグし、etc...

webservマジおもしれ~~C++クソだけど楽しい~~って感じで夜10時くらいから朝7時位までずっとC++書いてた。

あと宝塚記念負けた。

7月

引き続きwebservに取り組んでいた。「CGIのレスポンスタイプ4種類あるんだけど!?」を実装したり、「この辺のコード汚すぎ!!」みたいなのを綺麗にしたり、ファイルアップロード実装したり、TCP FINについての対応をしたり、ソケットのコードを見るためにLinuxソースコードを見るなどしていた。相変わらず生活リズムは終わってた。

トップガンマーヴェリック見た。めっちゃ良かった。

8月

webserv完成した。提出した。合格した。

goでOnionArchitectureな感じのWebAPIプログラムを作る課題をやった。 読みやすいし、変更に強そうでいいんだけど、ボイラープレートコードがかなり多かったので、実際に実践で使うならそれらを自動生成する仕組みは必要だろうなと思った。

マジカルミライ大阪(初音ミクのライブ)行った。最高だった。

内定者インターン始めた。Railsわからなくて泣いてた。

9月

Railsがわからなくて泣いてた。でもインターンシップは勉強になるから良き良き。

インターンシップのおかげで生活リズムが正常になった。

Transcendence っていう42のfirst circle(義務教育的な感じで全員がするやつ)の最後の課題に取り組み始めた。インターンシップで疲れててあんまりコミットできなかった。すまん。

10月

インターンシップとTranscendence。

CyberPunk EdgeRunners 見た。最高だった。みんな見て。

11月

インターンシップ。ソフトウェアエンジニアを半世紀くらいやってる人の話を聞いた。コンピュータ昔話大好き。

Rails慣れてきた。

Transcendenceも少しやった?やってないか。すまん。

12月

インターンシップ。仕事がスイスイできるようになってきた。Rails完全に理解した。

福井で行われたハッカソンに参加した。旅行しつつハッカソンだった。雪がめっちゃ降って大変だった。ハッカソンは久しぶり(2年ぶり?)に参加したけど、ユーザー数が増えたときのこととか、短時間で開発するなどもあり、技術的な深掘りがしにくいと感じた。昔はなにか動くものが作れたらそれだけで楽しかったのに、今は新しい技術や、負荷を考えた設計などができる環境のほうが楽しいと感じるようになった気がする。

Transcendence提出した。優秀なチームメンバがほとんどやってくれた。ありがとう。

ぼっちざろっく最高だった。

振り返って

今年は就職先決定という大きな決断ができました。また、42の課題によって色々知識も増えたと思います。そんなこともあり、今年の体感時間は結構長かったんじゃないかな?と思います。

ただ、途中オンラインゲーム(特にApex)にハマってしまい、数ヶ月ほぼゲームしてたときもありました。今は落ち着いたので全盛期ほどはやってませんが。Apex楽しいですね。これはやばい。

本はもう少し多く読めばよかったですね。多分5冊くらいしか読んでないんじゃないかな?DB系とアーキテクチャ系の本だけ。後はwebservようにUNIX系の本をちょびっと。
月に一冊本を読めるのが理想。1年で12冊。2023年はそれ目指して頑張ろうかな。

42の課題とインターンシップ以外に特に個人開発してなかったのはちょっと反省かも。でもアイデアが浮かばないのに、モチベ低い状態で無理やり開発しても楽しくないし、まぁこれはいいか。今は少しだけアイデアが頭に浮かんでいるので、2023年はなにか作るかも。

あと、この記事を書くにあたってDiscordのtimesサーバーのログを漁ったのだけれど、Discordの検索コマンドが優秀でとても体験が良かった。

来年からは就職するので、仕事以外でどれだけコード書くか(42含めて)わからないけれど、来年も楽しく健康にコード書きつつ遊びつつ生きたいですね。ほな2022年はこの辺で。皆様良いお年をお迎えください。

ばいばい~~👋👋

C++でHTTPサーバーを作った

42tokyo Advent Calendar 2022 の5日目を担当する、42tokyo在校生のJUNです。

42Tokyo の課題の1つである webserv という課題をクリアしたので、その課題の完走した感想を書きます。

課題概要

課題の概要を箇条書きで書くと、以下のようになります。

  • C++98 でイベント駆動、非同期IOを使ってHTTPサーバーを書く。
    • ただし、Nginxのように1つのMasterプロセスと複数のWorkerプロセスで稼働するマルチプロセスの構成ではなく、Workerプロセスが1つだけあるシングルプロセスシングルスレッドの構成。
  • OSのソケットインターフェースを用いてクライアントと通信する。
    • ソケットより下のレイヤーはOSにおまかせする。
  • NginxのようにノンブロッキングI/OとI/O多重化を用いてリクエストを処理する。ApacheのようにリクエストごとにForkしない。
  • CGIに対応させる。
    • NginxではFastCGIだが、この課題は昔ながらのCGIのみ対応。
  • 1チーム2~3人のチーム課題。

提出したコード

github.com

42Tokyoでは課題を学生同士でレビューし、レビュワーがレビューイーの提出したコードを確認することで課題が合格基準に満たしているかを確認します。そのための資料を review.md として用意しました。基本的な動作確認や、何ができるかなどはこのファイルを見ればわかるようになっています。

プログラム全体の流れ

プログラム全体の流れとしては

  • Configをパースして、それをもとにServerインスタンスを作成
  • イベントループを開始する。以下のイベントループを繰り返す。
    • タイムアウトしたソケットが無いか確認する
    • epoll_wait でイベントが発生したfdが出現するまで待つ
    • イベントが発生したfdがあればそれに紐付けられたハンドラー(関数ポインタ)が呼ばれる。
      • イベントが接続待ちソケット(参考: listen)から発生した場合は新規クライアントの接続要求なのでそれを処理する
      • イベントが接続済みソケットから発生した場合は以下の2つのパターンがある
        • Readイベントが発生したときには、HTTPのパースを行い、メソッドやリクエスト先のパスなどに応じて処理を行い、レスポンスを生成する。
        • Writeイベント(writableと言った方がわかりやすいかも?)が発生したときには、生成しておいたレスポンスデータをソケットに書き込むことでクライアントにデータを送信する。
      • UNIXドメインソケット(親プロセスとCGIプロセスの通信用)から発生した場合は以下の2パターン
        • Writeイベントが発生したときには、UNIXドメインソケットにデータを書き込み、CGIプロセスへデータを渡す。
        • Readイベントが発生したときには、UNIXドメインソケットからデータを読み込み、CGIプロセスが出力したデータを受け取る。
    • イベントループの最初に戻る

といったような内容になっています。見て分かる通り、基本的にはイベントループ内の処理がコードの大部分を占めます。

用語説明

この課題に取り組む上で重要な単語をいくつかご紹介。

イベント駆動型プログラム

まずイベント駆動型プログラムについて説明する前に、より馴染みのあるフロー駆動型プログラムについて説明する。

フロー駆動型プログラムとは、上から下に順次実行され、条件分岐や繰り返し処理があるとそれに従う。プログラムは流れ(Flow)を記述することになるのでフロー駆動型プログラムと呼ばれる。

それに対し、イベント駆動型プログラムは、様々なイベントに対応する処理を定義し、イベントの発生に応じてプログラムの流れは変化するようなプログラムのことを指す。このようなプログラムの場合、必要なことは「イベントの監視」と「イベントに対応する処理の定義」である。

イベントには様々なものがあるが、イメージしやすいものだと以下のようなものがある。

  • GUIアプリケーショにおけるユーザーの操作。マウスクリック、キー入力など。
  • ネットワークからのデータ受信。
  • シグナルの受信。

ノンブロッキングI/O

ノンブロッキングI/Oとは、I/O処理を行おうとした際にOS側がまだI/Oを行う準備が出来ていない場合に、I/Oが処理が完了するまで待つのではなく、即座にI/Oシステムコールが返るようなものである。

UNIX系の場合、fcntl を使い、fcntl(fd, F_SETFL, O_NONBLOCK) とすることで対象fdをノンブロッキングI/Oとして扱える。

例えば、以下のように標準入力をノンブロッキングI/Oにしてみるとわかりやすい。

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/errno.h>

#define BUF_SIZE 100

// エラーチェックは省略
int main(int argc, char** argv){
  char buf[BUF_SIZE];
  int rd;

  #ifdef NONBLOCK
    fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
  #endif

  while ((rd = read(STDIN_FILENO, buf, BUF_SIZE - 1)) < 0) {
    printf("ノンブロッキングI/Oで返された。rd: %d, errno: %d, %s\n", rd, errno, strerror(errno));
    sleep(1);
  }
  buf[rd] = '\0';
  printf("入力された。rd: %d, %s\n", rd, buf);
  return 0;
}

ブロッキングI/O(fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);しない)で実行すると以下のように、read()システムコールでデータが読み取れるまで(ユーザーが入力するまで)read()からは返らない。

$ gcc io_test.c && ./a.out < /dev/tty
hoge
入力された。rd: 5, hoge

これに対し、ノンブロッキングI/O(fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);する)で実行すると、read() システムコールでデータが読み込めない(ユーザーがまだ入力してない)場合にもread()から返り、返り値は-1で、errnoは35(EAGAIN)となった。

$ gcc -DNONBLOCK io_test.c && ./a.out < /dev/tty
ノンブロッキングI/Oで返された。rd: -1, errno: 35, Resource temporarily unavailable
ノンブロッキングI/Oで返された。rd: -1, errno: 35, Resource temporarily unavailable
ノンブロッキングI/Oで返された。rd: -1, errno: 35, Resource temporarily unavailable
hoge
入力された。rd: 5, hoge

このように、ノンブロッキングI/Oをfdにセットすると、I/Oシステムコールを呼び出した際に対象fdがまだ対応できる状態ではない(読み込めない or 書き込めない)場合に、そのI/O処理が完了するまで待つ(ブロッキング)のではなく、即座に返す(ノンブロッキング)。

I/O多重化

I/Oの多重化とは、複数のI/Oデバイス(fd)を同時に扱う方法であり、UNIXにおいては select, poll, epoll(Linux限定), kqueue(FreeBSD系) などのシステムコールを用いて実現されるものである。また、今回のイベントループにおけるイベント監視もこれを用いて実現する。

これらのシステムコールの使い方としては、まず初めに監視したいfdを登録し、次にイベントが発生したかどうか取得するシステムコールを呼び出して、I/O可能になったfdを取り出す。その後そのfdに対してどのようなイベントが可能になったかを取得し、それに応じた処理をする。

今回の課題では、I/O多重化を行い以下のfdを監視した

  • 接続待ちソケット
  • 接続済みソケット
  • CGIプロセスとの通信用のUNIXドメインソケット

また、I/O多重化を行うシステムコールと非同期I/Oを組み合わせることで、非常に効率的に多くのfdを同時に扱うことができる。

注意として、ここでfdを同時に扱うと言っているのは、同時に入出力ができるという意味ではなく、1つのプロセスで多くのfdの状態を同時に監視できるということである。

epoll の場合だと、以下のようなシステムコールがある。

  • epoll_create: Epollインスタンスの初期化
  • epoll_ctl: Epollに対して監視するfdを追加したり削除したり。
  • epoll_wait: 監視しているfdの中でI/Oイベントが発生したものを取得。

linuxjm.osdn.jp

ちなみに、ここで紹介した select, poll, epoll, kqueue だが、I/Oイベントの取得の計算量に違いがあり、n を監視対象のfdの数とすると、selectpollO(n) で、epollkequeueO(1) である。

自分のチームは今回動作環境をLinuxに限定し、使うAPIepollにした。

ソケット

ソケットとは、ネットワーク通信やプロセス間通信のAPIである。これによりプログラマーは簡単にTCPUDPを用いたネットワーク通信、UnixDomainSocketを使ったプロセス間通信が可能になる。

en.wikipedia.org

Config

ここから実装した内容とかそのTipsとかに付いて書いていく。

Configに関してはNginxのものを参考に仕様を定め、BNFを書き、それに基づきシンプルな再帰下降構文解析法でパースした。

github.com

イベントループ

イベントループは今回の課題の肝となる部分

Epollの抽象化

epoll に関しては Android のソースコード を参考に、Epollクラスを作成した。このクラスはただ単に epoll のシステムコールをクラスのメソッドにするのではなく、イベントも独自に再定義し、epoll のイベントから独自に定義したイベントに変換できるようにした。これによって、タイムアウトなどの独自で定義したイベントを扱えるようになった。

github.com

初期化

Configでの情報をもとに必要なTCP接続待ちソケットを作成する。

例えば以下のようなConfigが渡された場合

server {
  listen 127.0.0.1:80;

  location / {
     // 省略
  }
}

server {
  listen 127.0.0.1:80;
  server_name webserv.com;

  location / {
    // 省略
  }
}



server {
  listen 127.0.0.2:80;

  location / {
    // 省略
  }
}

server {
  listen 8080;

  location / {
    // 省略
  }
}

以下の接続待ちTCPソケットを作成し、イベントループ開始前に Epoll に登録する。

listen 127.0.0.1:80 が2つあるが、これはHTTPのHostヘッダーをもとに振り分けを行い、1つの接続先であたかも2つのサーバーが動いているかのように動作する機能である。

nginx.org

イベントループのコード

上記のEpollクラスによる抽象化が功を奏し、イベントループは以下のようにシンプルなものになった。

int StartEventLoop(Epoll &epoll) {
  // イベントループ
  while (1) {
    // タイムアウト処理
    std::vector<FdEventEvent> timeouts = epoll.RetrieveTimeouts();
    for (std::vector<FdEventEvent>::const_iterator it = timeouts.begin();
         it != timeouts.end(); ++it) {
      FdEvent *fde = it->fde;
      unsigned int events = it->events;
      InvokeFdEvent(fde, events, &epoll);
    }

    // 監視しているfdにイベントがあれば即座に返る。無ければ100ms待つ。
    // 100ms待ってるのは、このくらいならタイムアウトの誤差としては許容範囲内と考えたのと、
    // 監視しているfdにイベントがない場合も即座に返るようにしてしまうと、CPU使用率が高くなってしまうから。
    Result<std::vector<FdEventEvent> > result = epoll.WaitEvents(100);
    if (result.IsErr()) {
      utils::ErrExit("WaitEvents");
    }

    // イベントが発生したfdを取得し、それに対応した関数を呼ぶ。
    std::vector<FdEventEvent> fdees = result.Ok();
    for (std::vector<FdEventEvent>::const_iterator it = fdees.begin();
         it != fdees.end(); ++it) {
      FdEvent *fde = it->fde;
      unsigned int events = it->events;
      InvokeFdEvent(fde, events, &epoll);
    }
  }
}

また、InvokeFdEvent() では事前にepollにfdを登録する際に設定したハンドラー(関数ポインタ)を呼び出すような仕組みになっており、以下のような共通のインターフェースを持つようにした。

void HandleHogeEvent(FdEvent *fde, unsigned int events, void *data, Epoll *epoll)

ここでの各種引数は以下のようになっている。

  • fde: 監視対象のfdの情報。タイムアウトの秒数やどのイベントを監視するかを保持している。
  • events: epoll_wait で取得したイベントを独自のイベントに変換したもの。
  • data: 渡したいデータ。クラスインスタンスのポインタであることが多い。 reinterpret_cast<ConnSocket *>(data); こんな感じでキャストして使っている。
  • epoll: epollインスタンスのポインタ。

基本的にこれらの引数があれば今回の課題の範囲内ではすべての処理が行えました。

今回は以下のようなイベントハンドラー関数を定義しました。

  • HandleListenSocketEvent: 接続待ちソケットに対するイベントハンドラ
    • Readイベントが発生した場合は、accept() して接続済みソケットインスタンスを作成し、Epollの監視対象に加える。
  • HandleConnSocketEvent: 接続済みソケットに対するイベントハンドラ
    • Readイベントが発生した場合は、クライアントからのデータを読み取り、HTTPリクエストとしてパースする。
    • Writeイベントが発生した場合は、返すべきレスポンスがあればソケットに書き込み、クライアントに送信する。
  • HandleCgiEvent: CGIプロセスとの通信用のUnixDomainSocketに対するイベントハンドラ
    • Writeイベントが発生した場合は、CGIリクエストとして送信するべきBodyがあればソケットに書き込み、CGIプロセスが標準入力として受け取れるようにする。
    • Readイベントが発生した場合は、CGIレスポンスがある場合はそれを読み取り、CGIレスポンスの種類を解析する。(CGIレスポンスの種類については後述)

HTTPリクエストのパース

HTTPリクエストのパースに関してはチームメンバーの人がやってくれたので自分は特に詳しくは知らないですが、基本的には RFC 9112 — HTTP/1.1 に従う形で実装したはず。偉大。

静的ファイルに対するリクエス

HTTPリクエストを解析した結果、リクエストが先がCGIではなく、尚且つディレクトリでも無い場合には標準ファイルへのリクエストと判断する。

ちなみにディレクトリへのGETリクエストはautoindexの画面を動的に生成し、返す。(あのファイル一覧が載ってるページね)

ファイルへのリクエストがGETメソッドだった場合は普通にHTTPレスポンスを生成し、クライアントへ返す。

ファイルへのリクエストがPOSTもしくはDELETEの場合でかつ、リクエスト先がアップロード可能なディレクトリの場合にはファイル作成や削除を行う。この課題要件は正直あまり好きではないのだが、共有ファイルサーバーと考えれば受け入れられるかも...?

CGI

リクエスト先がCGIディレクトリ(Configで特定のディレクトリに対する操作はCGIとして扱う設定を規定している)だった場合、CGIの処理を開始する。

CGIの実装は基本的に RFC3875 - The Common Gateway Interface (CGI) Version 1.1 日本語訳 をベースに実装した。

CGI実行方法

CGIリクエストが来たら、CGIとして実行するプログラムを特定し、CGIプログラムが存在した場合に実行する。実行時にはCGIプロセスとの通信用のUnixDomainSocketを準備し、RFCにて定められている環境変数をセットしてから fork() & execve() する。

ちなみにCGIに関するConfigは以下のようにConfigファイルに書いてある。

server {
  listen 127.0.0.1:80;
  server_name webserv.com;

  location / {
    allow_method GET;

    root /var/webserv/server2/;
    index index.html;
  }

  location /cgi-bin {
    is_cgi on;
    cgi_executor python3;
    allow_method GET POST DELETE;

    root /var/webserv/server2/cgi-bin;
    index index.cgi;
  }

  location_back .py {
    is_cgi on;
    cgi_executor python3;
    allow_method GET POST DELETE;

    root /var/webserv/server2/cgi-py/;
  }

  location /cgi-bash {
    is_cgi on;
    cgi_executor bash;
    allow_method GET POST DELETE;

    root /var/webserv/server2/cgi-bash/;
  }
}

is_cgi on; と書いてあるところがCGIディレクトリである。

課題要件で、CGIを実行するプログラムを指定できるようにしろと書いてあったのでこのように cgi_executor という設定を追加し、これをもとに実行する際には <cgi_executor> <request_path> みたいな形で実行する。個人的に、これではバイナリファイルが実行できないし、普通に shebang で実行すればいいじゃないかと思うのだが、課題がこうなっているので仕方ない。

CGIとして実行するプログラムを特定するというふうに書いたが、ここに少し罠があり、実は純粋に <cgi_executor> <request_path> という形で実行するのでは足りなくて、 <request_path> の先頭部分文字列のみが実行可能である可能性があるのである。

どういうことか例を出して説明すると、HTTPリクエストで以下のような要求が来たとして、

GET /cgi-bin/cgi.py/subdir/subdir2

この際に、CGIスクリプトファイルは /cgi-bin/cgi.py とする。

既にお察しの方もいるかもしれないが、 /cgi-bin/cgi.py/subdir/subdir2 はそのまま python3 /cgi-bin/cgi.py/subdir/subdir2 のようには実行できない。 RFC的に正しい挙動としては、python3 /cgi-bin/cgi.py を実行し、その際にPATH_INFOという環境変数/subdir/subdir2 をセットする必要がある。

このCGIプロセスに渡すべき環境変数のセットやCGIの実行やプロセス間通信は色々めんどい要素があるのだが、チームメンバーがやってくれました。偉大。

CGIレスポンスの種類

RFCによるとCGIレスポンスには以下のような種類があり、それぞれWebサーバー側で行うべき処理が異なるので、CGIレスポンスのパースという処理がwebservに必要である。

CGI-Response = document-response | local-redir-response | client-redir-response | client-redirdoc-response

RFC 3875 - The Common Gateway Interface (CGI) Version 1.1 日本語訳

Document Response

これはもっとも標準的なCGIの出力である。

document-response = Content-Type [ Status ] *other-field NL response-body

RFC 3875 - The Common Gateway Interface (CGI) Version 1.1 日本語訳

基本的にCGIの出力を前からパースして、Content-Type が来たら Document Response と判断して構いません。

HTTPレスポンスの生成もContent-Typeセットして、StatusがあればHTTPレスポンスのStatus Codeにセットして、そんでresponse-bodyをそのままHTTPレスポンスBodyとして返して終わりです。

Client Redirect Response、Client Redirect Response with Document

client-redir-response = client-Location extension-field NL client-redirdoc-response = client-Location Status Content-Type other-field NL response-body

RFC 3875 - The Common Gateway Interface (CGI) Version 1.1 日本語訳

こいつは、クライアント側でリダイレクトするというCGIレスポンスです。

HTTP Status Code がわかる人向けに言えば、300番系のレスポンスをWebサーバー側で生成してあげて返すだけです。

Client Redirect Response と Client Redirect Response with Document の違いはBNFを見てわかる通り、bodyがあるかどうかだけです。

Local Redirect Response

local-redir-response = local-Location NL

RFC 3875 - The Common Gateway Interface (CGI) Version 1.1 日本語訳

Local Redirect Response はwebserv初期設計を破壊した異端児です。こいつの対応のために設計を一部変えました。

何が異端児なのかというと、こいつだけ処理の流れが逆転するんですね。

まぁそれはともかく、こいつの説明を。

この Local Redirect Response はどのようなCGIレスポンスなのかというと、自分自身に対してもう一度リクエストを送るようWebサーバーに依頼し、その結果を返す というものです。

The CGI script can return a URI path and query-string ('local-pathquery') for a local resource in a Location header field. This indicates to the server that it should reprocess the request using the path specified.

RFC 3875 - The Common Gateway Interface (CGI) Version 1.1 日本語訳

図にすると、コイツ以外のCGIレスポンスや静的ファイル、AutoIndexの処理は概ね以下のような流れになっています。

CGI Local Redirect Response 以外の処理の流れ

この Local Redirect Response の処理の流れはこうなっています。

CGI Local Redirect Response の処理の流れ

何しとんねん~~~~~~~

というわけで私の初期設計は破壊されましたが、賢いチームメンバーが「これLocalRedirectを反映させたリクエストを接続済みソケットのHTTPリクエストキューの2番目に入れれば良くね?」という天才的なアドバイスをくれたので一命を取り留めました。偉大。

github.com

Tips

ここからは開発中に「便利だなぁ~」「へぇ~~」って思ったことを書いてみます。

C++の例外とResult<T> クラス

C++の例外を使うなーー!!(クソでか大声)

...は言い過ぎにしても、個人的には例外のような大域脱出はあまり好きではなく、さらに言えばC++98には例外の発生する可能性の有無をコンパイル時にチェックしてくれるような機能が無いので余計に使いたくないのです。

個人的には go のような、関数の返り値の型からエラーが発生する可能性があるかどうか、エラーが発生したのならその返り値からエラー情報が欲しいわけです。

// go だとエラーはこんな風に関数の返り値と一緒に返ってくる。
f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
// do something with the open *File f

Error handling and Go - The Go Programming Language

C++98でもこんなことしたいな~と思っていたら良さげなものをまたまたAndroidソースコードで見つけました。

https://cs.android.com/android/platform/superproject/+/master:system/libbase/include/android-base/result.h

Result<T> クラスです。

自分の実装はこちら

github.com

使い方はこんな感じ

#include "result.hpp"

#include <iostream>

using namespace result;

// ===== Usage =====
Result<int> succeed() {  // 成功
  return 10;
}

Result<void> succeedVoid() {  // 成功 ()
  return Result<void>();
}

Result<void> fail() {  // エラー
  return Error("This is error!");
}

int main() {
  Result<int> a = succeed();
  std::cout << "----- a -----" << std::endl;
  if (a.IsOk()) {
    std::cout << "val: " << a.Ok() << std::endl;
  }
  if (a.IsErr()) {
    std::cout << "err: " << a.Err().GetMessage() << std::endl;
  }

  Result<void> b = succeedVoid();
  std::cout << "----- b -----" << std::endl;
  if (b.IsOk()) {
    // 返り値がvoidの場合は result.Ok() は呼べない
    // std::cout << "val: " << a.Ok() << std::endl;  // コンパイルエラー!
    std::cout << "OK" << std::endl;
  }
  if (b.IsErr()) {
    std::cout << "err: " << b.Err().GetMessage() << std::endl;
  }

  std::cout << "----- c -----" << std::endl;
  Result<void> c = fail();
  if (c.IsOk()) {
    std::cout << "OK" << std::endl;
  }
  if (c.IsErr()) {
    std::cout << "err: " << c.Err().GetMessage() << std::endl;
  }
}

// 出力
//
// ----- a -----
// val: 10
// ----- b -----
// OK
// ----- c -----
// err: This is error!

便利でしょ。

Result<T> クラスの実装にはAndroidソースコードの他にRustのResult<T>クラスも参考にしました。

doc.rust-lang.org

webservにローカルネットワークの他端末からアクセス

頑張って実装したWebサーバー、せっかくならスマホからアクセスしてみたいです。

というわけでwebservにローカルネットワークからアクセスする方法をご紹介します。

Discordのログから拾ってきました。

あるポート(80番ポートとか)を全ネットワークインターフェース(0.0.0.0)でlistenしている場合にはmDNSという機能を使って <computer名>.local(コンピュータ名が roxy なら roxy.local) で同じネットワークに接続された他のコンピュータからアクセスできます。これを使えばHerokuなどを使ってグローバルに公開せずともローカルネットワーク内限定ではありますがスマホからアクセスできます。 https://e-words.jp/w/mDNS.html

またWindows勢でWSLを使って開発をしている場合はWindowsの方でWindowsのポートをWSLのポートに接続してあげる必要があるので注意が必要です。 https://zenn.dev/solufa/articles/accessing-wsl2-from-mobile

って感じです。

この方法は別にwebservに限定したものではなく、汎用的に使える方法だと思うので、Web開発とかする機会があれば使えると思います。

CGIの動作確認

NginxではFastCGIしか使えないので、CGIの挙動を確認したい場合にはApacheを使うと良いと思いますよ。これは自分のチームメンバーがCGI確認ツールを作ってくれました。偉大。

見落としそうなエラーハンドリング

いくつか見落としそうなエラーハンドリングがあったので雑に紹介

Broken Pipe シグナルは無視しよう

クライアント側が一方的に接続を切った際にBroken PipeのシグナルSIGPIPEが飛んできます。そしてこの SIGPIPE のデフォルト動作は プログラムをエラーとして終了 なのでちゃんとsigaction などで無視するようにして、write() の返り値でエラーはチェックするようにしましょう。

christina04.hatenablog.com

1クライアントがRSTパケットでネットワークを切断したらその時接続が確率されているすべてのソケットの接続が切れるなんで洒落にならんのでな。

TCP FINパケット受信後

TCP FINパケットが飛んできたとき、それは正常なTCP接続の終了であり、その時にepollは EPOLLRDHUP というイベントを通知する。

この EPOLLRDHUP はmanによるとこういうものらしい。

ストリームソケットの他端が、コネクションの close 、 またはコネクションの書き込み側の shutdown を行った。 (このフラグを使うと、エッジトリガーの監視を行う場合に、 通信のもう一端が閉じられたことを検知するコードを 非常に簡潔に書くことができる。)

「ほえー、TCP FINパケットが送られてきたらEPOLLRDHUPイベントが通知されるんか。ほなEPOLLRDHUPが来たらソケットをcloseしてしまえばええんか」

粉バナナ!!(これは罠だ)

dic.pixiv.net

これは罠で、クライアント側で後から送信したTCP FINパケットがネットワークの都合で先に送信したパケットよりも先にサーバーに到着する可能性がある。これを避けるためには EPOLLRDHUPを受け取った後にread()を行い、0が返ってくるのを確認する必要がある。

ymmt.hatenablog.com

epoll で標準ファイルの fd が監視できない理由

poll などは標準ファイルのfdを監視できるのだが、epollはできない。これは epoll_ctl() の man にも書いてある。

EPERM 対象ファイル fd が epoll に対応していない。 このエラーは fd が例えば通常ファイルやディレクトリを参照している場合にも起こり得る。

Man page of EPOLL_CTL

なぜ登録できないのかって思ったので調べてみた。

一応調べた結論としては、「標準ファイルにはpollインターフェースが無いからepollに登録できない」ということだった。

ほんとかなぁ?って思ってLinuxソースコードを少し読んでみたのでその記録を貼っておく。一応pollインターフェースっぽいものが標準ファイルには無いことは確認できたけど、なんで無いのかは知らない。賢い人教えてください。。

以下Disocrdのログコピペ。

regular file が epoll に登録できない件について調べている際にLinuxのコード読んだよなぁ~
regular file は epoll に登録できなくて、その理由が poll インターフェースが無いからという風にサイトには書いてあるんだけど、実際にその判定を行ってる箇所のコード昔見たよなーって思って調べ直した。

(番号は文章内で参照するために付けてるだけで意味はない)

1. pollインターフェースが無いからepollに登録できない: https://codehunter.cc/a/linux/epoll-on-regular-files
Linuxソースコード
2. epoll_ctl: https://elixir.bootlin.com/linux/latest/source/fs/eventpoll.c#L2077
3. file_can_poll: https://elixir.bootlin.com/linux/latest/source/include/linux/poll.h#L79
4. file構造体: https://elixir.bootlin.com/linux/latest/source/include/linux/fs.h#L940
5. file->f_op の型 file_operations: https://elixir.bootlin.com/linux/latest/source/include/linux/fs.h#L2093
6. file->f_op->poll がセットされている箇所を見ればわかるかもな

7. 例えば pipe だと pipe_poll がセットされている: https://elixir.bootlin.com/linux/latest/source/fs/pipe.c#L1223

8. Linuxのデファクトスタンダードのファイルシステムのext4の file->f_op を見ると poll が登録されていない! これじゃね?: https://elixir.bootlin.com/linux/latest/source/fs/ext4/file.c#L919
9. 上記の ext4_file_operation は regular file だった場合にセットされていることが確認できる: https://elixir.bootlin.com/linux/latest/source/fs/ext4/inode.c#L5003

完走した感想

(記事を書く)判断が遅い!(ここで鱗滝左近次からビンタを食らう)

とほほ。すいません(´・ω・`)

このwebservという課題は実は8月に終わっていて、記事書きたいな~でも面倒くさいな~って思ってたら4ヶ月経ってました。12月になってました。もう年が終わりそうです。

記事書くのが遅い件はこれくらいにして完走した感想(激ウマギャグ)ですが、とにかく楽しくて学びの多い最高の課題でした。

42Tokyoに入学した当初からこの課題楽しそうだなぁ~と思っていて、実際にやってみたら想像通り楽しかったです。なんならチームメンバーに恵まれたおかげもあって想像より楽しかったです。そして学びも多かった。

学びの面ではこの記事に書いた内容ももちろんそうだし、それ以外にもクライアントとのコネクション維持やC++のクラス設計、ソケットプログラミングやI/Oの種類、NginxのアーキテクチャRFCを読める力、などなどかなりたくさんの学びがあって大満足でした。

開発期間は確か5~8月の3ヶ月くらい(多分)なはずで、自分はその間特に働いているわけでもなく42TokyoしてるかTwitterしてるかだったので、起きてる時間はだいたいwebserv書いてたような気がします。そして生活リズムが終わってた。

自分含めチームメンバー全員が無職で42Tokyoをやっている俗に言うフルコミット勢なんですが、全員の生活リズムが狂っていたので、活動時間は夜0時から朝6時とかでした。その時間VCを繋いで、あーだこーだ言いながら毎日コード書いてました。そして朝7時位に寝て昼3時のおやつタイムに起きてました。終わっとる。でも、最高に楽しかったです。

42Tokyoは課題も楽しいですが、コミュニティにいる人間も楽しいので、ぜひみんなも42Tokyo入りましょう。(ダイレクトマーケティング)

42tokyo.jp

まぁ一応この記事は 42 Tokyo Advent Calendar 2022 の5日目の記事なのでね。42Tokyoの宣伝もしておきました。

明日は @sudo00 さんが 「zshのmanを見ながらプロンプトを自作してみる」という記事を書くみたいです。面白そうですね!

現在時刻 2022年12月5日23時45分か...だいぶギリギリになっちゃったな...

そんなわけでギリギリアドベントカレンダーの担当日を超える前に記事を書き終えたので今日はここまで! ほなさいなら~~👋👋

参考資料

Nginx

HTTP

ソケットプログラミング / IO多重化

CGI

C++

2022年4月振り返り

2022年4月を振り返ります。

就活

内定承諾をし、就活が無事終わりました。来年から働くぞい。

結局面接は12,13社くらい受けたと思います。面接回数は20回以上やったかな?結構大変でしたがなんとか就職が決まり良かったです。面接を担当してくれた企業の皆様にはお世話になりました。

バックエンドチューニングコンテンスト

42Tokyoとドリーム・アーツの共催のイベント、バックエンドチューニングコンテンストに参加しました。イメージとしてはISUCONみたいなやつです。

バックエンドのチューニングや、DBやAPIサーバーのパフォーマンス向上について学べました。

twitter.com

読んだ本

ネットワークはなぜつながるのか

いろんなところで勧められている本なのに読んだことなかったので読んでみました。ISPのネットワークやソケット通信の概要などが知れて、ネットワーク全体の概要を掴むのにとても良かったです。

ゼロからはじめるデータベース操作

SQLをしばらく書いてなかったので読んでみました。知ってることも多かったですが良い復習になりました。

その他

就活が終わった勢いのまま4月後半は遊びまくりました。Apex、ドライブ、競馬と色々やってました。たくさん遊びました。ただ、あまりにも毎日遊びすぎたので、5月からはまた気持ちを切り替えて頑張っていきたいと思います。5月はC++でWebサーバーを書く課題があるので、それがある程度終わりましたと5月の振り返り記事で言えるようにしたいですね。では来月も頑張るぞ~えいえいむん!

2022年3月振り返り

2022年3月を振り返ります。

インターンシップ

3月前半は株式会社CyberZにてサーバーサイドエンジニアとして2週間のインターンシップに参加させて頂いてました。

www.cyberagent.co.jp

このインターンシップでは主に新機能の開発と既存機能の修正などを行いました。

自分が担当したOPENREC.tvというプロダクトはクリーンアーキテクチャをしっかりと意識してコードが書かれており、各層の役割が明確に分けられていてとてもきれいなコードでした。

ちょうどインターンシップを行っている最中に「Clean Architecture 達人に学ぶソフトウェアの構造と設計」を読んでいたこともあり、出勤前に本を読んで、業務で実際に Clean Architecture が適用されたコードを見るという最高のサイクルを2週間回してました。ソフトウェアの設計などについてインターンシップ参加前に比べて詳しく具体的なコードをもとに理解できました。

ちなみに OPENREC.tv の Android チームの方がが書かれた以下の記事がとても良いので読んだことない方は読んでみるとよいと思います。

note.com

また、テーブル設計やテーブル設計の変更の管理などはあまり知らなかったので実際の現場ではどのようにそれらを行っているのかが知れてとても勉強になりました。

ランチや交流会などのイベントもあり、大変楽しく学びとやりがいのあるインターンシップでした。

OPENREC.tvチームの皆さんありがとうございました。

ft_containers

C++STLコンテナ群(vector, stack, map, set)を再実装する課題をやっとクリアしました。

2月の時点でほとんど終わってはいたのですが、実装漏れなどもあり、結局3月までかかりました。

この課題についてはそのうち記事を書こうと思っているので、書いたらリンク貼っときます。

読んだ本

Clean Architecture

インターンシップのところでちょっと名前を上げましたが、この本を読みました。

本の内容としては Clean Architecture というソフトウェア設計の考え方みたいなところを説明する本です。基本的には依存関係の方向性と凝縮性をどのような意識のもとで管理すれば変更しやすく運用しやすいソフトウェアが作れるのかということを様々な参考文献や経験をもとに書かれている本でした。

具体的なコードはほとんど出てこないので Clean Architecture を本当の意味で理解するには実際にそれを用いてソフトウエア開発を行うのが必須だと思いました。幸い今回のインターンシップでその実践部分を完璧に行えたのでとても良く理解できました。

雑多なこと

携帯変えた

キャリアでキャンペーンをやっていたので iPhone12 買いました。7000円くらいで iPhone 12 128GB が買えたのでとっても良かったです。

メイン開発機がWindowsになった

2月末に湾曲UWQHDモニターを買ってからというもののメイン開発機がWindowsに移行しました。前までは Thinkpad X1 Carbon 2017 に Ubuntu を入れて使っていたのですが、せっかく性能の良いゲーミングPCがあるのでこっちを使うようにしました。めっちゃ快適です。

自分がプログラミングを始めた2016年くらいってWindowsで開発しようとしたらVirtualBoxUbuntu動かすか、PowerShellで頑張るかって感じだったんですが、今はWSLというすばらしいものがあり、普通にWindowsでコードが書けてとってもいい感じです。

Windows11のアップデートが来てたのでアップデートしたのですが、新旧のUIがごちゃまぜでこれはいまいちですね。Windowsは頑張ってモダンな設定画面やUIを取り入れようとしているんだけれども、ちょっと込み入った設定を行おうとすると昔ながらの設定が出てくるので開発者の苦労が見えますね。後方互換性とかいろいろ気にすることが多そうで大変そうです。

今回メイン開発機となったゲーミングPCは Ryzen 5600X と GeForce 3060Ti、メモリ32GB を積んだ元々VRゲームとかやるために買ったゲーミングPCですが、開発するには十分すぎるスペックなのでとても快適に開発できています。

あとはフォントとか高dpi時の表示とかが綺麗になればパーフェクトなんですけどね。

お出かけ

免許を2021年11月に取ったんですけど、2022年3月になってやっと1週間に1回程度ですが車に乗るようになりました。温泉行ったりホームセンター行ったり、あまり移動範囲は広くないですが安全運転で楽しくドライブできるようになってきたような気もします。

また、インターンシップで2週間東京に行ってたのもあり42Tokyoの校舎に行ったり、渋谷の美味いハンバーグ屋に行ったり、42Tokyoの友達や高専時代の友達に会ったりして楽しかったです。

3月後半に就活関連で東京にもう一度行くことがあり、その際にスーツの動画に影響されて夜行バスのプレミアシートで移動してみました。

www.youtube.com

夜行バスプレミアシートで大阪東京間移動してみた感想ですが、自分は新幹線のほうが好きですね。

夜行バスプレミアシートは普段乗っている4列シートと違い、ほぼ水平までリクライニング倒せるし、アイマスクや耳栓、ブランケットなどのアメニティあるし、カーテンで仕切られていてほぼ個室みたいな感じだしでとても良いのですが、やはりバスなので振動などはありますね。あと致命的だったのは乾燥で、とにかく空気が乾燥していました。寝始めたらとても寝れるんですが、寝るに至るまでに乾燥で口が乾きまくってきついです。

ただ、寝てる間に移動でき、朝起きたら東京に付いているので、朝から用事あるときはいいかも...?

帰りは新幹線で帰りました。

アニメとかゲーム

3月といえば冬クールのアニメが最終回を迎えるということで色々見てました。冬アニメは以下のような感じで見てました。

  • 進撃の巨人: やばい。面白すぎる。記事を書いてる日の前日(2020/04/03)に最終回が放送されましたがあの状態で2023年まで続きを見れないのはなんてこったって感じです。
  • 着せ替え人形は恋をする: 喜多川さんすこだ。俺の考えた最強のオタクにやさしいギャルって感じですばらしい。
  • 86(延期してたやつ): 第一話放送時から待ち焦がれていたシーンがついに放送されてオタクワイ号泣。シン×レーナ 良すぎる... 永遠に幸せでいてくれ...

あと、「タコピーの原罪」を読んでました。なかなかにきつい漫画でしたが最後は幸せそうな感じで終わってよかったです。面白かった。

東京滞在中に書籍を買おうと思ったら売り切れてました。とっても人気なんだっピね!

ゲームは初めてAPEXというのをやってみました。流行ってるらしいのでやってみましたがFPSゲームをあまりやらないので敵を見つけても弾が当たらなくてすぐ死んじゃいます。あとルールがよくわかっていないので多分これ友達と一緒にボイチャつないでやったほうがよいのだろうなと思いました。

完走した感想

前半インターンシップ、後半色々って感じの月でした。2月はマジで一瞬で終わりましたが。3月はとても充実していたように思えます。

前半のインターンシップはソフトウェア設計や継続的に開発ができるプロダクトを作るために意識していることが体験できたとてもいい機会でした。今までも実際のプロダクトに触れるインターンシップなどには参加したことがあるのですが、このような設計などについて学びを得られるのは今回のインターンシップ先が設計をしっかりしていたというのもありますが、自分が設計にまで意識が回るレベルまで成長したのかなぁと思ったりします。

後半は結構遊んでる感じがしますが一応STL再実装の課題終わらしたり、就活したり、別の課題やったりと遊びとコーディング両方できたと思います。

アニメとかゲームについてのセクションが明らかオタクのノリって感じですが、まぁいいでしょう。

3月は東京行ったりドライブしたりとお出かけも色々しました。普段は家に引きこもってコード書いてるんですけど、週1くらいでお出かけすると楽しいし、リフレッシュできてとても良いですね。

3月はとっても楽しかったです。4月は出会いと別れの季節らしいですが、自分は引き続き42Tokyoで学習をしていきます。

ではでは~👋👋

2022年2月振り返り

42Tokyo

先月に引き続きSTL再実装の課題をやってました。赤黒木に必要なメソッドやイテレータの昨日を追加し、 mapset を作りました。

今月で課題が終わればよかったのですが、いくつか抜けていたメソッドやtypedefがあったので再提出しなくちゃいけないことになりました。提出前の確認が甘かったですね。

提出前の確認って面倒くさいなぁって思うけれど、結局提出前にしっかり時間掛けて確認してから出した方が抜けが少なくて一発で課題通せる可能性が高まりそうです。

なんかTDDとかの文脈でよく語られる質とスピードみたいですね。質を落としてスピードを上げても超短期的には早くなったように見えるけど、実際は時間を掛けてしっかりと質を上げた方がスピードが早いみたいな。

Kotlin

インターンシップでKotlinを使うことになったのでKotlinとKotlinのWebフレームワークであるKtorを勉強してました。

Kotlinは昔Androidアプリ開発チュートリアルで触って以来触って無かったですがなかなかに良い言語ですね。綺麗な設計(Clean Architectureとかデザインパターン)ができるように言語が作られているような感じですし、実際のコードを見てもそんな感じでした。

買い物

UWQHDウルトラワイドモニターを買いました。めちゃくちゃ良いです。

自分のThinkpadでも問題なく画面出力でき、文字のサイズもちょうど良い感じで素晴らしいです。

jun-networks.hatenablog.com

1年前4Kモニタ買ったけど、自分のThinkpadでは4Kモニタを上手く使いこなせなかった...

よく4KとUWQHDどっちが良いのという話がありますが、個人的にはUWQHDの方が良いですね。

理由

  • コード書くなら複数ディスプレイより1枚の横長のディスプレイの方がエディタのウィンドウ1つで多くのファイルを編集できる
  • 4Kモニタは字がちっちゃい。どうせスケーリング150%か200%にするので意外と表示できる情報量が少ない。WQHDモニタ2枚とかの方が良いかも。

って感じです。

ただ、少し気になっているところもあって、ゲーミングモニタなので目に優しいモードがイマイチで少し目が疲れやすくなったかも?

ゲームとかアニメ

Inscryption ってゲームをやってました。なかなか今までにない感じのゲームで面白いですね。ただ、ちゃんとしたエンディングにたどり着けて無いっぽいので、クリアしたと思っていたけど実はクリアしていないらしい?

store.steampowered.com

あとウマ娘が1周年記念とか新シナリオとかで色々追加コンテンツが来てたので遊んでました。キタサンブラック来てくれました。可愛い。

完走した感想

2月はっっっっっっっっっや!一瞬で終わったんですけど。バグですか?

2月は単純に日数が少ないのと、2月後半あたりは生活リズムが終わってたので1日の体感時間が短くて余計に短く感じたかな。寝る前のスマホは駄目ですね。生活リズムが狂います。

ここに書いた以外にも就活とかもやってました。面接とか受けました。

STL再実装の課題は2月中に完了する予定だったのですが、実装漏れなどが見つかりもうちょっとだけ掛かりそうです。

3月は前半はインターンシップ、後半はSTL再実装の課題を提出したいと思います。それ以外は何しようかな...GoでWebサーバーでも書こうかな...

って感じで3月も頑張るぞー。えい、えい、むん!

2022年1月振り返り

42Tokyo

STLの再実装

去年の12月くらいからC++STLのコンテナ群を作る課題をやっています。

今は map の内部で使われているデータ構造の赤黒木を実装し終わり、その赤黒木に map で使うメソッドなどを実装しているところです。この課題も終わったら振り返り記事を書くと思います。

Mixi Git Challenge 番外編

2022年1月28日 に 「Mixi Git Challenge 番外編 Git Quiz」 がMixi協力の元42Tokyo向けに開催されたので参加しました。

このイベントでは Mixi が行っている Git Challenge という Git で発生した問題を解決するイベントの番外編として Git Quiz を行いました。Git Quiz では主に Git Challenge 本編で難易度が高くてボツになった問題が四択形式の問題として出題され、それに対してみんなで答えて、最後解説が入るといった内容でした。

クイズの内容としては Git の内部構造の知識を要求する問題が多く、とてもむずかしかったです。ただ、Git の内部構造やコミットなどののデータ構造、そしてそれらに起因して発生する問題や挙動について知れてとても勉強になりました。

具体的には以下のような記事を事前インプットとして読みました。

git-scm.com

また、スライドの方もブログにURLを載せても良いとのことなので以下が今回のイベントで使われたスライドです。

docs.google.com

このイベントでは GBAエミュレータ や Git などを自作されている @shumon_84 さんが講師として説明をしてくれました。大変わかり易く勉強になりました。

また、後半では Mixi の会社紹介と質疑応答のコーナーがあり、いくつか質問もさせていただきました。

以下に聞いた質問と答えを自分用のメモとして貼っておきます(自分が覚えている分)。

Webサービスアーキテクチャの設計(DBやキャッシュ)についてはどのように勉強すれば良いでしょうか」という質問に対しては、「基本的には書籍での勉強がおすすめ。サイトなどでも色々情報があるが、書籍のほうが周辺知識なども含め網羅的にまとめられている」とのことでした。

また、おすすめの書籍を聞いたところ以下のものを紹介していただきました。


GBA自作以外にもおすすめの自作〇〇などはありますか?」という質問の答えとして以下のようなものが上がりました。

私生活

放送大学 単位認定試験

2022年1月14日〜1月21日まで放送大学単位認定試験がありました。

jun-networks.hatenablog.com

放送大学通信制大学ということもあり基本的な科目は全て家から受講出来るんですが、モチベーションの維持が大変でした。(というかあまり上手く行かなかった)

キャンパスなどに行って仲間とかが出来たら良いのでしょうが、某感染症のせいであまりキャンパスでの学習は勧められていないので、この辺りのモチベーション管理に関しては難しい課題でした。

とりあえず受講科目数が多すぎたのと、来期は多分色々ライフイベントやら42Tokyoやらで忙しいと思うので受講はしないか、多くても1,2科目になりそうです。

就活

自分で探した企業とエージェントから紹介された企業を受けました。

42Tokyo でやったことが結構評価されて嬉しかったです。

面接のスケジュール管理とか面接の多さなどが大変ですが今のところなんとかなっています。

ちょっとしたTipsなんですが、たまにエントリーシートを書いている途中でタイムアウトで内容が吹っ飛ぶことがあったので、事前にローカルのテキストファイルに書いてから最後にコピペするようにしました。これで安心ですね。

It Takes Two

It Takes Two という2人用ゲームを弟とプレイしてクリアしました。

www.ea.com

控えめに言って神ゲーでした。美しくて細部まで作り込まれたグラフィック、雰囲気に合ったサウンドとBGM、ロードなどが存在しないかのような滑らかなシーン遷移、飽きが一切来ないほどの多彩なアトラクションとミニゲーム、詰まっても少し考えたり探索すれば先に進める絶妙な難易度、2人プレイという特性を最大限活かしたゲーム構成、と褒めるところしか見当たらないのですが、本当に良かったです。自分はあまりゲームを多くプレイするタイプの人間ではないのですが、このゲームは今までプレイしたゲームの中で最上位に位置するほど素晴らしいゲームでした。

2人プレイ専用という独特なゲームですが、この「2人プレイ専用」という特性を最大限活かした物凄いゲームなので是非機会があればやってみてください。(1人買えばもう1人もプレイできるらしいっすよ?)

アニメ

進撃の巨人 をファイナルシーズンが始まるとのことで今更ながら見始めたら面白すぎてNetflixで全話見てしまいました。いや〜面白い!毎週日曜日の放送が楽しみです。

買い物

HHKB を買いました。現在進行系でこの記事を書くのに使っていますが快適です。

jun-networks.hatenablog.com

他にはあまり買ってないので今月は少なかったですね。(先月 自作PC を組んだり、Steam のセールで買いまくったりしましたが...)

完走した感想

実は今年に入ってから句読点を全角にしました。気づきましたか?

前までは, こんな風に半角のカンマとピリオドを使っていました. しかし、最近他の日本語の記事を読んでいて、「普通に全角の句読点使うの読みやすいかもな」と思って全角にしてみました。しばらくこれで書いてみてようと思います。

また、最近夜に寝て朝に起きる健康的な生活が出来ているような気がします。これは意識的にリベンジ夜ふかしを辞めて、夜にやりたいと思ってもその場ではやらずに翌朝に持ち越すようにしたのが大きいと思います。実際この生活の方がよく集中出来ていると思うので今の所良い感じです。

news.yahoo.co.jp

あとは昼飯後の眠気に対してガッツリ布団で寝ずに軽く机の上で10分くらい寝るのが良いですね。ガッツリ布団で昼寝すると夜寝れないので。

この月末振り返り記事もいつまで続くかわからないですけど、あまり気張らず、続けることを重視して気軽にやっていきたいです。