続くのか…(意外)

上記記事を通したやつとか存在しないと思う。けど続きを書く。

前回はコンソールアプリとか作らせたけど、今回はインターネットのあのアレを作ろうじゃないのさ。

こないだ作ったプログラムは、実行(dotnet run)することによりターミナルになんか文字が表示されて幸せだった。

でも世の中のプログラムはもっと複雑な動きしてそう。ずるい。俺もカッコいいことしたい。かっこいい用語を言ってプログラマーっぽい仕草をしたい。

◆サーバー

Webサーバー。「サーバーが落ちた」とかのサーバー。サーバー作るのはめっちゃ難しい技術ってことでもなく、わりと手軽に自分で作ることができる。(大嘘)

サーバーを作れば、インターネットを作ることができる。(大語弊)

サーバーとは何か?それはサーブ、すなわち提供するものだ。「サービス」「(球技の)サーブ」「ウォーターサーバー」あたりと同じ語源のやーつです。サーバーは何らかの方法で命令を受けたら、それに沿った処理を実行してくれる。応答をしてくれる。

例えばこのブログの記事もサーバーから応答されている。サーバーにはブログ用のプログラムが乗っていて、テメーらからのリクエストを受けてデータを処理し、クソ記事を返却している。

サーバーといえば普通はインターネット上に置かれているものを指す。かたや、より狭い意味でのサーバーは「インターフェースを持つ常駐プログラム」を指す。起動しっぱなしのプログラムは入力をずっと待っている。入力を受けたらそれなりの処理をする。

インターネット上に公開せずとも、お前のPC上にサーバーを立てることが可能である。お前のPC内部に立てたサーバーにリクエストを投げ、レスポンスをもらうことができる。

▼おまえ「面白くなさそう」

まぁそうかもな。

例えば「タップしたら花火がパーンって爆発してキレイ」みたいな楽しすぎて笑いが止まらなくなる失禁必死の極限プログラムを作らせてもいいんだけど、それで何をするんだって話ですから。パンツが汚れるだけだ。

面白いことさせたい気持ちはあるんだけど、段階踏ませてくれや。おう。

▼サーバーを構成する者たち(読まんでよし)

サーバーはプログラミングすることにより作ることができる。サーバーに入力が来たとき、その入力に対応する動作をさせたいならプログラミングすればいい。

ただし、サーバーを建てるための基本的なプログラムは過去の人がやり切っているし、今も開発され続けている。有料のサーバーソフトウェアもあれば無料のものもあって、無料のものが十分な機能を持っている。イマドキで言えば有料のものを使う理由もない。

サーバーを用意するためには、だいたい以下が必要。

  • コンピューター
  • コンピューターを動かすためのプログラム
    • いわゆるOS。WindowsとかLinuxとかMacとか。
  • コンピューターがネットワークと通信するための部品
    • NIC(Network Interface Card)とかLANカードとかネットワークアダプターとか呼ばれる。自分で買ってきてパソコンに挿したり、初めからマザーボードに組み込まれていたり。
  • 通信用部品をコンピューターから動かすためのプログラム
    • ドライバ。通信用のドライバはLANドライバとか呼ばれる。
  • LANドライバを利用しつつサーバーとしての口を用意するプログラム
  • サーバーの具体的な動作を決めるプログラム

お前が作ればいいのは、最後の「サーバーの具体的な動作を決めるプログラム」だけ。

お前さんがC#でプログラミングしたいのであれば、C#に対応する「サーバーソフトウェア」を選定する必要がある。有力なプログラミング言語には、大抵の場合その言語に対応するサーバーソフトウェアが存在している。

詳しくは以下を参照しろ理解できないだろうけど。
ASP.NET Core での Web サーバーの実装

今回はまぁC#でやらせます。なぜか。安全で分かりやすいうえに説明しやすいから。俺の都合。

・余談:無料のプログラムってどういう理屈で存在してんの?

無料のソフトウェアのうち、それらがプログラムとしてビルドされる前の、ソースコードの状態から公開されているものがある。「OSS(オープンソースソフトウェア)」と呼ばれる。

プログラムっていうか道具というものはまず、自分が必要だから作るものでしょう?その作ったもののレシピを公開し、共有している人が世界にはたくさんいる。

OSS自体の歴史が深いものであるうえに、短い期間ながらも複雑な色々があった。ここでは詳しく説明しないけど、とにかく無料で使えることに感謝するべし。いま大抵のOSSはGitHubというサイトで公開されている。GitHubは2018年にMicrosoftに買収された。

◆テンプレートからプロジェクトおこす

よぉーしやっていくぞぉー。

前回記事を参照し、WSLにつながったVS Codeを立ち上げてこい。

かつて「dotnet new console -o Prac1」みたいな感じでテンプレートを使ってアプリを作成していたけど、あれはコンソールアプリのテンプレートだった。今回はHTTPサーバーのテンプレートを使いたい。

というので、「Ctrl + @」してから

dotnet new --list

コマンドで使用可能なテンプレートを一覧してみる。

すると「web」ってのがあると思うので、それを使えばいいんじゃない?Server1って名前で作ってみよう。

以下のコマンドを実行すりゃいいんだが、今いる階層に注意。前回作った「programs」ってディレクトリに居る状態で実行したほうが場所がわかりやすかろ。

dotnet new web -o Server1

codeコマンドを叩いて、作成したプロジェクトを開く。

code Server1 -r

そしてもう、実行しちゃおう。「Ctrl + @」して

dotnet run

そうすっとね?

コンソールに以下の表示が見えてくると思うんすよ。

Now listening on: http://localhost:5137

※5137じゃない場合があるかも。

https://~で始まるURLも表示されてると思うんだけど、なんやかんやあって表示しづらいからhttpのリンクのほうを開け。
あと、VS Codeのコンソールに表示されているURLはCtrlキーを押しながらクリックすると規定のブラウザで開くことができる。

Webブラウザで「http://localhost:5137」にアクセスしたくもなるし、開いたら「Hello World!」って表示されてるし。こわ…リスカしょ…

立ち上がってるサーバーを終了するには、コンソールにカーソル当てて「Ctrl + C」です。

▼空からコードをみてみよう

くもじい「くもじいじゃ!」

テッテッテッ♪テッテテッテッテッ♪テッテッテッ♪テッテテッテッテッ♪

・Program.cs

サーバーを立ち上げるにもプログラムでしかないんですから、エントリーポイントがあるはず。それはコンソールアプリでもあったようにProgram.csのMainメソッドだよね。

といいつつ、.NET 6からは記法が省略されてるのでMainメソッドとか存在してなくて、直球でいろいろ書かれている。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

これで終わり。すごい。

俺からしてみたらホントに省略っぷりがエグくて、考えるべきことが極度に隠蔽されている。けど便利っちゃ便利なので深くは考えずに行こうよ。前はStartup.csとかあったんだけど、消えててびっくりした。
Minimal APIs overview | Microsoft Docs
argsってどっから湧いて出てきたんだよキモいわ。実行時のコマンドライン引数を受け取るってのはわかるんだけど、キモい。

コードを読んでみる。

  • WebApplication.CreateBuilderってのが呼ばれてて、じゃあつまり「Webアプリケーションのビルダーがクリエイトされてくる」らしい。
  • そのビルダーが「Build」ってされてる。ので、つまりWebアプリケーションがビルドされた結果がappに返ってきているんだろう。
  • そのappにMapGetってしてる。

意味が分からん。MapGetってなんだよ。知らんわ。

MapGetは後ろに「()」がついてるのでメソッドだったな。おさらいすると、メソッドは引数というものを受け取ることができる。引数はコンマで分けられる。ここでいえば第一引数が「"/"」であり、第二引数が「() => "Hello World!"」。

でもとりあえず、Hello Worldって表示されてたわな。「Hello なまはげ」にしたらどうなるんだろう。

いったんターミナルで「Ctrl + C」してサーバーを止めて、以下のコードに書き直し、「Ctrl + S」で保存。保存したら「Ctrl + @」してコンソールにフォーカスして↑キーを押して「dotnet run」が出てきたらEnter。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello なまはげ!");

app.Run();

「http://localhost:5137」を開いたら「Hello なまはげ!」が表示されたんじゃないかね。

なんかすげぇな。

▼HTTP

Webブラウザで「http://localhost:5137」を開いたときに何が起こんのか。Hello なまはげ!されたけども。

  • ブラウザからしてみたら「https://localhost:5137」に対して「HTTP GET」という操作を実施している。
  • localhostというサーバーからしてみれば5137ポートに対してHTTPのGETが飛んできている。

HTTPとは、Hypertext Transfer Protocol。「プロトコル」って名前についているように、HTTPはプロトコル(とりきめ)の一種。通信のルールの取り決めである。

HTTPには「GET」という操作が定義されている。ブラウザのアドレスバーにアドレスを入力したとき、ブラウザくんはGETの命令を対象のサーバー&ポートに対して試みることになってるんすね。

んで

app.MapGet("/", () => "Hello World!");

上の例で「Get」って文字が見えるかと思うんだけど、それこそがGETがメソッドを表現している。そして「"/"」がパス。パスって言ってんのはURLのあのアレだな。

https://insider.10bace.com/2020/12/04/data/

「https://」が通信の決まり、「insider.10bace.com」がサーバーの名前、「/2020/12/04/data/」がパスって感じ。ポート番号が指定されてない場合はプロトコルのデフォルトが指定される。HTTPなら「80」。HTTPSなら「443」。

わかりやすさを重視してテキトーなことを言っている。正確な情報は以下を参照。
ウェブ上のリソースの識別 | MDN

ポートは、くち。サーバーは通信を食ってプログラムを動かしてるでしょう?とはいえ、サーバーへの通信の全部をプログラムが監視してるってわけでもない。あるプログラムが「このポートは俺が使ってます」みたいに占有して、そこへのアクセスだけ捌いてんですね。たいていの場合は。

「"/"」ってパスはテッペン、ルートを指している。Webブラウザでパスの指定がない任意のURLを開いた時、ルートがGETメソッドで叩かれる。

▼補足:localhost

localhostってのは特別に設定されているホスト。ホストっていうのはシャンパンを女に飲ませる職業のことで、localhostは自分の今使っているPCそのものを指す。開発に便利に使える。lolachostの後ろに書かれている「:5137」とかはポートを指定している。

さっき作ったサンプルをdotnet runで動かすと、プログラムが「http://localhost:5137」をリスンし始める。listen。聴いてる。聴こえたらプログラムが動く。

▼パス追加

「app.MapGet("/", () => "Hello なまはげ!");」って書いてあって、よくわかんないけどブラウザで開いたら「Hello なまはげ!」って表示されたじゃん?

じゃあ「Hello くわがた!」って文字を返すパスを追加できるんじゃないかね。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello なまはげ!");
app.MapGet("/hoge", () => "Hello くわがた!");

app.Run();

いったんコンソールから「Ctrl + C」でサーバーを停止し、プログラムを書き換え、「Ctrl + S」で保存し、再度コンソールから「dotnet run」する。

「http://localhost:5137」と「http://localhost:5137/hoge」で違う文字が表示されて楽しいでしょう。

▼ちょい伸ばす

パスを追加しつつ、ちょい書きっぷりを具体的にする。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello なまはげ!");
app.MapGet("/hoge", () => "Hello くわがた!");
app.MapGet("/piyo", async ctx =>
{
    ctx.Response.ContentType = "text/plain; charset=utf-8";
    await ctx.Response.WriteAsync("Hello くわがた!");
});

app.Run();

「/piyo」のパスも「/hoge」と同じく「Hello くわがた!」って返すだけなんだけど、こんな書き方になる。

二行目にある

ctx.Response.ContentType = "text/plain; charset=utf-8";

って記述がない場合、文字化けする。なぜか。

HTTPという通信ルールに於いては、実際のデータ(ペイロード)のほかに「ヘッダー」なるものも一緒に通信してる。ヘッダーにはデータについてのデータ、「メタデータ」が設定されている。

例えば、リンゴ is 果物だよね。そのように「Hello くわがた!」 is 文字である。プレーンテキストである。そのplain textの中でも「UTF-8」である。みてぇな付属情報をヘッダーに詰めている。

ヘッダーはもっと直接的に操作できるんですけど、ContentTypeヘッダーはほぼ確実に設定するようなものだから別立てでインターフェースが用意されがち。

サーバー側で「リクエストされたコンテンツを送るけど、そのコンテンツはプレーンテキストでUTF-8だよ」っていう風に指定して、どう解釈すればいいのかを教えてあげてるんすね。

◆HTML

サーバーからテキストしか送れないのであれば、今見てるページも「Hello ももんが!」みたいなテキストだけになっちゃう。読みづらい。

文章なんだからもっと段落とか見出しとか一覧とかテーブルとかを表現できるようにしたい。文書構造を指定して送りたい。

そんなあなたは脇目もふらずHTMLを使えばいいじゃない。「text/plain」じゃなくて「text/html」を送ればいいじゃない。

HTML を始めよう | MDN

HTMLは以下のように書く。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>けんま</title>
  </head>
  <body>
    <h1>わっしょい</h1>
    <p>わっしょいしょい!</p>
  </body>
</html>

文字を囲むことで文字に意味を与えるというアイデア。例えば「わっしょい」を「<h1>」と「</h1>」で囲むことにより、そのわっしょいがh1であると表現しておる。

「<h1></h1> 」はタグであり、タグの中でも「headingタグ」である。

bodyタグの内容がブラウザ上に表示されるものであり、かたやheadタグはメタデータである。head内のtitleに指定した文字列が、ブラウザのタブに表示される。

これでもう完全に理解したことだろうヤッタネ。じゃあコードを書き換えてみっか。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.MapGet("/hoge", () => "Hello なまはげさ!");
app.MapGet("/piyo", async ctx =>
{
    ctx.Response.ContentType = "text/html; charset=utf-8";
    await ctx.Response.WriteAsync([email protected]"<!DOCTYPE html>
<html>
  <head>
    <meta charset=""utf-8"">
    <title>ほあああああ</title>
  </head>
  <body>
    <h1>わっしょい</h1>
    <p>わっしょいしょい!</p>
  </body>
</html>");
});

app.Run();

dotnet runし、「/piyo」のパスにアクセスしくされ。気分がわっしょいしてくるから。

説明面倒なので、知りたいんならさっき張った「HTML を始めよう」リンクを読んでみること。HTMLについて知りたいんであれば、とにかくMDNというサイトを参照すりゃいい。検索するときは例えば「html head mdn」といった風に、最後に「mdn」と入れて調べようね。

なお、HTMLとC#は何の関係もない。それぞれ独立したもの。HTMLは文字に過ぎないので、文字を扱えるプログラミング言語であればHTMLを生成できる。つまりほぼ全てのプログラミング言語でHTMLを書くことできるでしょう。

サーバーからHTMLを返却することによって、なんだかWebページっぽいものを返却できるらしいわ噂では。すごいね。

◆静的HTML

複数のページがあるウェブサイトとか普通にあるじゃん。沢山のページをいちいちパス指定してあの書きっぷりするとつれえわ。C#の文字列で「"」を表現しようと思ったら「""」みたいに重ねねばならんし。

だから「HTMLファイル」みたいな感じで別個にファイルを用意して、それをサーバーから返すことにすればいいじゃん。

サーバーからなにかを返却するとき、その返却内容について「動的」とか「静的」とか表現されることがある。動的ってのは「プログラムでどっかのデータを参照しつつ書いて返す」やつ。かたや静的は、用意したファイルをそのまま弄らずに返却してるケースを表現する。

静的なHTMLを返却するサーバーこそ、一番素朴なWebサーバーである。と俺は思っている。大抵のサーバーソフトウェアは静的ファイルを返す仕組みを用意している。

C#ではっていうかASP.NETでは「wwwroot」というフォルダをルートとして、その配下に返却したいファイル群を配置することができるようになってる。ASP.NETじゃのうてApacheってのを使うんだとしたら「htdocs」ってフォルダだったりするな。完全固定ってわけでもなくルートフォルダの指定は変更できると思うけど。

じゃあ静的ファイル(Static File)を使うように設定を有効化してみる。Server2をつくれ。

cd ..
dotnet new web -o Server2
code Server2 -r

ほいでProgram.csを書き換えなさい。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStaticFiles();

app.Run();

MapGetがなくなっちゃって悲しいけど、UseStaticFilesメソッドを叩くことによって静的ファイルを参照するようになる。自動で。うれしい。

じゃあ今いるプロジェクトに静的ファイルを作成しようぜ。「Ctrl + @」からコマンド叩いてファイル作る場合は以下の通り。

mkdir wwwroot
touch wwwroot/index.html

mkdirコマンドで「wwwroot」って名前のフォルダつくって、touchコマンドで「index.html」ってファイルをつくる。wwwrootに。

別にマウスを右クリックポチポチしてGUIからファイル作ってもいいけどよ別にほんとクソが。

ファイルが生えたら「Ctrl + P」を押して「index」とか入力すると「index.html」が選ばれてくるはずだから、そのままEnterキーでファイルを開けオラァン。開いたら以下を貼り付けて保存しろホラァン。

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>インデックスページだよ</title>
</head>

<body>
  <h1>ちんちんかゆい</h1>
  <p>先端がかゆい</p>
</body>

</html>

保存したら「dotnet run」っすね。

ポート番号が先ほどと違ってるかもなので注意しつつ、ブラウザでルートを開いてみそ。

…何も表示されねぇぞ。

ってんで「/index.html」ってパスを指定して開いてみれば、wwwrootに保存したhtmlの内容が表示されたはずです。

▼デフォルトページ

「UseStaticFiles」の一行上に以下を追記しろ。

app.UseDefaultFiles();

これによって、ルートパスにデフォルトで表示するページを指定可能。UseDefaultFilesの引数になにも指定しなければ、以下がデフォルトページとして参照されてくる。

  • default.htm
  • default.html
  • index.htm
  • index.html

保存して再度「dotnet run」したら、index.htmlというパスを指定せずともページが開けてきたはずだ。

別名のファイルを指定したいんなら引数にいい感じに指定していけ。

DefaultFilesExtensions.UseDefaultFiles メソッド

C#のメソッドには「オーバーロード」ってのが存在していて、引数の指定方法を変えることができる。引数の個数が可変。

デフォルトを「unko.html」にしたいのであれば以下の書きっぷりになる。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("unko.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();

app.Run();

なんやかんやしてんな。

既定のドキュメントの提供 | ASP.NET

▼多階層

mkdir wwwroot/abcd
touch wwwroot/abcd/efg.html

こんな感じでwwwrootの下にさらにフォルダ作ったら、それに対応するようなパスでアクセスできるようになるよ。よかったね。

◆デバッグ構成

dotnet runしてブラウザ開くっていう一連の動作をワンボタンで実行したい…したくない?自動化するのがプログラムの本懐じゃないですか。

そういう思いが熱きハートに芽生えたんならVS Codeのデバッグ構成を作ってやればいい。

Debugging |Visual Studio Code

VS Codeで「F5」キーを押したら、場合によっては自動でプロジェクトを検出しつつデバッグ構成を生成してくれる。今回の例では「.NET 5+ and .NET Core」ってのにカーソル当ててEnter押したら生えてくる。

VS CodeがMicrosoft謹製であるがゆえに、おなじくMicrosoftである.NETのデバッグ構成はよしなに生成してくれがち。

ルートに自動で「.vscode」ってフォルダが生成されて、そんなかにlaunch.jonとtasks.jsonがあるじゃん。それがデバッグ構成さ。デバッグ構成が生えたら「F5」から簡単にコードを実行できるようになる。dotnet runを叩く必要なし。

そしてなんと「Ctrl + Shift + F5」によって再起動をすることができる。つまりコードを書き換えて保存したのちに再起動すれば反映されて便利ね。

デバッグを終わりたいんなら「Shift + F5」しろ。

▼httpのほうを開く

2022年1月現在の規定では、デバッグ時に「https」のほうを開きに行っちゃうんで面倒。

例えば「localhostのhttpsであったら表示許可しちゃう」とかブラウザに設定をすることもできる。

chrome://flags/#allow-insecure-localhost

それが嫌であればデバッグ構成ファイルを編集してhttpのほうを自動で開くようにしてしまおう。

  • VS Code上で「Ctrl + Shift + F」押して検索を開く
  • 「serverReadyAction」って文字を入れてEnterして検索
  • 「pattern」って項目に入ってる「https」を「http」に変えたれ

Launch.json attributes | Debugging in Visual Studio Code

▼Live Preview拡張機能

HTMLの内容だけを見たいんであればASP.NETを通す必要がなかったりする。VS Code上で手軽にチェックできる。

「Ctrl + Shift + X」で拡張機能を開いたら「live preview」とか入力して、出てきたやつをインストールしろ。インストールに時間かかるかもなので、できれば服をすべて脱いで燃やしながら踊っていてくれ。

Live Preview - Visual Studio Marketplace

導入できたんなら「Ctrl + Shift + P」から「live prev」とか入力して「internal browser」とか書いてあるやつを選択すればいい。デバッグ実行とかせずともHTMLのレンダリング結果を表示できちゃう。

◆ごく基本的なHTMLの書きっぷり

このへん読んどいて

HTML 要素リファレンス

みどころさん

そして、余裕あればみてみたいやつ

HTMLとCSSとJavaScriptの話をしようとするとケツからクソちびるほどヤベェことになるので、次回に持ち越す。次回がない可能性も十分あるけども。

◆プログラムでHTMLを書く話になる

一番素朴なHTTPサーバーは、用意したhtmlファイルを返す。

その次の段階として、プログラムでコリコリとHTMLを書くみたいな話になった。StaticFilesじゃないやつ作ろう。Server3を作っていく。

cd ..
dotnet new web -o Server3
code Server3 -r

今やもうやっていないようなことを引き続きしていくんだけど、 かつて我々に何が起こったのかを理解していけ。

▼直接書く

たとえばHTMLにランダムな数字を表示してみるだとか、サーバーにアクセスされてきた時刻を表示してみるだとか。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", async ctx =>
{
    ctx.Response.ContentType = "text/html; charset=utf-8";

    // ランダム
    var r = new Random();
    var num = r.Next();
    // 時刻
    var date = DateTime.Now;

    await ctx.Response.WriteAsync([email protected]"<!DOCTYPE html>
<html>

<head>
  <meta charset=""utf-8"">
  <title>インデックスページだよ</title>
</head>

<body>
  <h1>ほげー</h1>
  <h2>ランダム</h2>
  <p>{num}</p>
  <h2>時刻</h2>
  <p>{date}</p>
</body>

</html>");
});

app.Run();

こんなもんで。文字列である「""」の前に「$」とか入れると「{}」で変数を埋め込めたよね。それを便利に使っておる。

$ - 文字列補間 - C# リファレンス | Microsoft Docs

数字とか日付は文字じゃないんだけど、こんな感じで埋め込むと自動でToStringというメソッドが呼ばれて文字列に変換される。正確には違うけど、そう理解しておけばいい。

「テンプレートリテラル」って呼ばれる仕組みなんだけど、これがあるだけマシ。テンプレートリテラルがないとツラい書き方になる。

「@」があると改行を混ぜ込むこともできるね。逐語的識別子。

@ - C# リファレンス | Microsoft Docs

これらがない場合は、文字列を重ねまくってHTMLを構築する羽目になる。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", async ctx =>
{
    ctx.Response.ContentType = "text/html; charset=utf-8";

    // ランダム
    var r = new Random();
    var num = r.Next();
    // 時刻
    var date = DateTime.Now;

    // HTMLの組み立て
    var sb = new System.Text.StringBuilder();
    sb.Append("<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>インデックスページだよ</title></head><body><h1>ほげー</h1><h2>ランダム</h2><p>");
    sb.Append(num.ToString());
    sb.Append("</p><h2>時刻</h2><p>");
    sb.Append(date.ToString());
    sb.Append("</p></body></html>");

    await ctx.Response.WriteAsync(sb.ToString());
});

app.Run();

こういうこと。こういう時代があった。StringBuilderってのは、文字列をビルドするためのクラス。AppendによってStringBuilderの中に文字を蓄積させていくことが出来る。今どきはコンパイラ君が賢いので使わんでもいい説あるけど、かつてはStringBuilderを使ったほうが明確にパフォーマンスよかった。

つぎ。置き換えるパターン。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", async ctx =>
{
    ctx.Response.ContentType = "text/html; charset=utf-8";

    // ランダム
    var r = new Random();
    var num = r.Next();
    // 時刻
    var date = DateTime.Now;

    // テンプレート
    var template = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>インデックスページだよ</title></head><body><h1>ほげー</h1><h2>ランダム</h2><p>%num%</p><h2>時刻</h2><p>%date%</p></body></html>";

    await ctx.Response.WriteAsync(template.Replace("%num%", num.ToString()).Replace("%date%", date.ToString()));
});

app.Run();

HTMLのテンプレートを用意して、例えばだけど「%num%」みたいな文字列を入れておく。その「%num%」を置き換えれば表示したいものにできるわな。

特定の文字列を別のものに置き換えるメソッドがあればそれができる。C#で言えばReplaceがそれ。

▼処理を書く場所

ランダム数値と時刻の取得を、MapGetの外に書いたらどういう動きになるだろうか。実際に試してどうなるか見てみろ。

以下を実行しつつ、ブラウザのF5で更新をかけたりして動作を確認してみ。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// ランダム
var r = new Random();
var num = r.Next();
// 時刻
var date = DateTime.Now;

app.MapGet("/", async ctx =>
{
    ctx.Response.ContentType = "text/html; charset=utf-8";
    await ctx.Response.WriteAsync([email protected]"<!DOCTYPE html>
<html>

<head>
  <meta charset=""utf-8"">
  <title>インデックスページだよ</title>
</head>

<body>
  <h1>ほげー</h1>
  <h2>ランダム</h2>
  <p>{num}</p>
  <h2>時刻</h2>
  <p>{date}</p>
</body>

</html>");
});

app.Run();

HTMLの返し方色々については以上。

◆余談:全部捨てる方法

WSL上で全部やってるので、例えばUbuntuを捨てれば作ったものをすべて捨てることができる。し、.NETのSDKも一緒に捨てられる。

VS CodeだけはWindowsにインストールされているんだけど、拡張機能もUbuntuに入ってるからまとめて捨てられるよ。

Windowsキーを押して「ubuntu」って入力して表示されたものを右クリックしてアンインストールです。開発環境を簡単に作ったり捨てられたりできるのは素晴らしいことです。神よ。

WSL2ごと止めてしまいたい場合は、Windowsキーを押して「optionalfeatures」って入力して出てきたやつを実行したら

  • Linux 用 Windows サブシステム
  • 仮想マシン プラットフォーム

のアレからチェックを外してシステムを再起動すればいい。

◆外部データソース

ランダムな数値とか日付とか出せてもそんなにって感じ。ちまたのサイトってどうやって作ってんだよ。

というので

  • データを保存する場所なるデータベースを用意して
  • プログラムからデータベースにアクセスして読み込んで
  • HTMLを組み立てて
  • 返却する

ということをしてみればいいんじゃない。

もっと原始的な考えでは、サーバー上にテキストファイルを置いておいてその内容をチマチマ読み書きするという方法もある。けどまぁやってられんので省略。

Server4プロジェクトを作ろう。

cd ..
dotnet new web -o Server4
code Server4 -r

ほいで、WSLにMySQLを導入してみる。

データベースへのアクセスもインターネットを経由してやることができる。あるいは、ひとつのPC上でWebサーバーとデータベースのふたつを立ち上げておくことができる。ポート番号を変えてそれぞれ区別してアクセスするんすね。

つまり、データベースもサーバーである。

データベースソフトにはいろいろ種類がある。その中でもMySQLってのはシステム開発でよく利用されているゴミ。人間の屑。MySQL使うくらいならMariaDB使え。

以下ページの「MySQL をインストールする」を参照。出てくるコマンドをWSL上で叩け。難しいこっちゃないはず。

WSL を使用してデータベースを追加または接続する | Microsoft Docs

「sudo mysql_secure_installation」ってコマンドで多少難儀するかもな。YesかNoか問われたら、とりあえず何も入力せずにEnter押しとけばいい。パスワードを設定しろって言われたらとりあえず「manko」にしておこう。

実際に運用するデータベースについては、意味を理解しつつ勉強してから構成しようね。あとMySQLはやめとけ。親を殺される。

インストールが終わって「sudo mysql」コマンドを叩いたら、MySQLをコマンドラインから動かせるようになるんぜ。なるだぜ。

▼MySQL

「sudo mysql」したら、左のが「mysql>」ってなったはず。その状態から入力されるコマンドは、MySQLに対してのものとなる。「mysql>」状態をやめたくなったら「quit」って打ち込んでEnterしろ。

データベースってやつも所詮はプログラムであって、こっちから命令を投げて動かしてやる。その命令はLinuxのコマンドではなく、SQL(エスキューエル)っていうこれまた新しいアレを使います。

MySQLはRDBっていうタイプのデータベース。RDBであれば概ねSQLを実行できるんだけど、各データベースごとに方言があったりもする。加えてこれからMySQLに対して叩くコマンドは、SQLだったりSQLじゃなかったりする。なんやねん。

▼データベース&テーブル作る

データは最終的にテーブルに保存する。テーブルっていうのはその上にご飯を乗せて食べて美味しいねってするやつのこと。そしてテーブルはデータベース上に生やす。だからまずデータベースが必要。

データベースサーバーはデータベースをサーブする。だからデータベースサーバーそのものは厳密にはデータベースではない。データベースサーバー上にサーブしたいデータベースを作る。サーブって言葉は普通使わないけど。あとデータベースって言葉がゲシュタルト崩壊してきた。

サーバー上にデータベース(以下、DBと略記)を作るために、「mysql>」の状態から以下を実行。

CREATE DATABASE tinko;

大文字になってるところは小文字でもいいよ。あと最後のセミコロンがついていないと、ひとつのSQLとして認められない。コンソールが「>」って感じになったら、とりあえず「;」を入力してEnter押せ。

これにより「tinko(てぃんこ)」と名付けたDBを作成することができる。以下の操作で確認できるだろう。

SHOW DATABASES;

USEにより、操作対象DBを指定する。

USE tinko

USEみたいにセミコロンが必要ないコマンドもあったりする。つけても別に問題は起こらないけど。

DBにテーブルを生やす。以下を実行。

CREATE TABLE cats
(
  id              INT unsigned NOT NULL AUTO_INCREMENT,
  name            VARCHAR(150) NOT NULL,
  weight          INT unsigned NULL,
  PRIMARY KEY (id)
);

猫ちゃんのデータをたくさん登録できるテーブル。cats。

catsに対して猫を三匹登録。

INSERT cats (name, weight) VALUES ('にゃん五郎', 3600);
INSERT cats (name, weight) VALUES ('ぺす', 4100);
INSERT cats (name, weight) VALUES ('いぬ', 2200);

▼ユーザーを生やす

プログラムからDBを触るために、専用のユーザーを作りましょう。

CREATE USER penis IDENTIFIED BY 'penis';
GRANT ALL PRIVILEGES ON * . * TO penis;

「penis」って名前の「penis」ってパスワードを持つユーザーをMySQLに登録する。そしてGRANTによりDBへのアクセス権を追加する。

これで晴れて準備完了。「quit」によってmysqlにさよならを告げろ。以下を打ち込んでEnter。

quit

▼どうやしてプログラムからMySQLにつなぐわけ?

うん。MySQLへの接続はTCP/IPで行けんのよ。

さっきまでHTTPの話をしてたと思うんだけど、HTTPって実はTCP/IPの上で動くプロトコルであった。.NETがHTTPでの通信に対応してる(HTTPを扱うライブラリがある)ってことは、順当に考えてTCPも扱える。

というのでTCPのライブラリが入ってるSystem.Net.SocketsをusingしてTcpClientを…

ってことでもなく、MySQLのクライアントを提供するライブラリがあるんで、それをインターネットから持って来ようよ。

「サーバーを利用する側のなにがし」はクライアントと呼称される。

▼ライブラリ導入

C#、ひいては.NETのライブラリはNuGetって場所にたくさんあるんで、そっから持ってくる。また、ライブラリのことをNuGetではパッケージと呼ぶ。

NuGet とは何か。またどのような働きをするのか | Microsoft Docs

MySQLへつなぐために使えるパッケージはMySQL.Dataです。以下のコマンドを叩いて現在のプロジェクトにパッケージを導入したまえ。

dotnet add package MySql.Data

どんなパッケージが世の中に存在してんのかってのは、想像つかないかもしれない。それはググるしかない。例えば「C# MySQL」だとか「dotnet MySQL」だとか「NuGet MySQL」みたいなワードでしょうね。

パッケージを発見したら、お次はそのパッケージの使用方法をググって見つけてこなければならん。できれば公式のドキュメントを掘り出したいところ。

MySQL.Dataの利用法は以下のページだな。

MySQL :: MySQL Connector/NET Developer Guide :: 6.1.1 The MySqlConnection Object

じゃあそれに倣って書いてみる。嘘。参考にしつつ倣わないで書いてみる。

▼接続テスト

using MySql.Data.MySqlClient;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

const string connStr = "server=localhost;user=penis;database=tinko;port=3306;password=penis;";

app.MapGet("/", async () =>
{
  using var conn = new MySqlConnection(connStr);
  await conn.OpenAsync();

  return [email protected]"ほげー";

});

app.Run();

これを実行してブラウザで開いたら、「ほげー」が表示されたことと思う。それは無事にMySQLへの接続に成功したということ。ほげーが表示されず、エラーっぽい画面が表示されたんならMySQLへの接続に失敗したってことでしょう。

じゃあコードについて見ていく。

const

定数。上書き不可の変数。今後変更する必要のない変数については定数にしておけば後々わかりやすい。

・connStr

connStr変数には、接続文字列というものを代入しておる。外部データソースにアクセスするときは大抵の場合で接続文字列を使う。使わない場合もあるけど。接続文字列の書き方はコードサンプル見て想像するんでもいいし、「mysql 接続文字列 書き方」みたいにググってもよかろう。

ポート番号だけわからんと思うんだけど、mysqlで言えば「mysql>」状態でコマンド叩けば知ることができる。

SHOW GLOBAL VARIABLES LIKE 'PORT';

「3306」が確認できるでしょうたぶん。

・using var conn = new MySqlConnection(connStr);

using右側で宣言した変数は、いまいるブロックの外に出たときにDisposeってメソッドが勝手に呼ばれて破棄される。詳しい説明は放棄する。

・await conn.OpenAsync();

MySQLへのコネクションを開く。逆にコネクションを閉じる「CloseAsync」もある。

Openした後はCloseを呼ぶ必要あるんだけど、MySqlConnectionはDisposeの中でCloseしてくれるから意識しなくていい。詳しい説明は放棄する。

▼MySQLからデータを読み込んで表示

もう!書くの疲れた!(激怒)

DBに登録した猫にゃんをブラウザで一覧表示したいわ!

SQLを使ってDBから猫を引き出すぞ!!!

using MySql.Data.MySqlClient;
using System.Text;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

const string connStr = "server=localhost;user=penis;database=tinko;port=3306;password=penis;";

app.MapGet("/", async ctx =>
{
  ctx.Response.ContentType = "text/html; charset=utf-8";
  using var conn = new MySqlConnection(connStr);
  await conn.OpenAsync();

  // SQLの実行
  var sql = "SELECT id, name, weight FROM cats";
  var cmd = new MySqlCommand(sql, conn);
  using var reader = await cmd.ExecuteReaderAsync();

  // HTMLの猫表部分を組み立てる
  var cats  = new StringBuilder();
  if (reader.HasRows)
  {
    while (reader.Read())
    {
      cats.AppendLine($"<tr><td>{reader[0]}</td><td>{reader[1]}</td><td>{reader[2]}</td></tr>");
    }
  }

  await ctx.Response.WriteAsync([email protected]"<!DOCTYPE html>
<html>
  <head>
    <title>ねこたち</title>
  </head>
  <body>
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>なまえ</th>
          <th>重さ(g)</th>
        </tr>
      </thead>
      <tbody>
{cats}
      </tbody>
    </table>
  </body>
</html>");
});

app.Run();

これでどうだー---!!!!!

sqlって名前の変数の中にSELECTとかって文字列が書いてあって、それがSQLだわ。SELECTで列を指定して、FROMでテーブルを指定する。

MySqlCommandのコンストラクタにSQLとコネクションを渡してコマンド作りつつ、それをExecuteしてreaderを作り、whileでループさせながら内容を読みつつTableの行を作ってなんとかしてんだよ。

わかったか!!!!!あほ!!!!!!

試しに、catsテーブルに猫を増やしてから画面更新してみりゃいいと思う。

$ sudo mysql
mysql> USE tinko;
mysql> INSERT cats (name, weight) VALUES ('にゃんタロス', 3800);

にゃんタロスが増えたことと思うよ。

◆以上!!!!

疲れたっつってんだろ!!!いい加減にしろ!!!!誰がやるんだこんなもん!!!!!

うあああああ!

◆結

はい。お疲れさまでした。大変だったでしょう。今日は早く寝ろ。

これどこまで続ければいいんだろうか。どうでもいいか。どうでもいいと思いつつまだまだ教えなければならんことが星の数の子はニシンの魚卵。

は?

うん。じゃあな!

◆次回予告

マスオです。

昨日サザエの後頭部をスリッパで叩いたら「ぐおおぉお…!!」と言っていました。

さて次回は

  • CSSを使うことによってHTMLの見た目をいじる。
  • Web画面から猫の追加、削除をする。
  • ネット上に公開してみる。マネージドクラウドに載せてみる。

の三本です!

来週もまた城之内死す!サービスサービスぅ!