vibe.d Webアプリ開発ことはじめ

# はじめに

* vibe.dの[薄い本](https://www.packtpub.com/web-development/d-web-development)買いました
* 簡単なWeb APIサーバーを作ってみて、困ったこととその解決策を共有します
* とてもためになる公式ドキュメント
  * http://vibed.org/docs
  * http://vibed.org/api
* サンプル集
  * https://github.com/rejectedsoftware/vibe.d/tree/master/examples
* この記事は[shibuya.d #2](https://connpass.com/event/41717/)の発表内容に加筆修正を加えたものです
 
## おことわり

* やること
  * 簡単なWeb APIサーバーを作ります
* やらないこと
  * GUIはやらないです
  * DBは使いません
  * D言語くんはでてきません

# 環境について

* マルチプラットホーム
  * Windowsでも普通に動くぞ！やったぜ！
* Dockerは？
  * https://github.com/zckri/docker-vibed
    * 上記を参考に自分で作ったほうが良いかも
    * Ubuntuにd-apt入れるだけ

# 待ち受けアドレスとポートを変更する

* コマンドラインオプションで指定したい
  * http://vibed.org/api/vibe.core.args/

```d:app.d
import vibe.d

void main()
{
  string address = "127.0.0.1";
  ushort port    = 8080;

  readOption("bindAddress|b", &address, "Sets the address for listning.");
  readOption("port|p", &port, "Sets the port for listning.");

  if (!finalizeCommandLineOptions()) return;
  lowerPrivileges();

  auto settings = new HTTPServerSettings;
  settings.port = port;
  settings.bindAddresses = [address];

  auto router = new URLRouter;
  router.get("/", &hello);
  listenHTTP(settings, router);

  // イベントループ呼び出し
  runEventLoop();
}

void hello(HTTPServerRequest req, HTTPServerResponse res)
{
  res.writeBody("Hello, World!");
}
```

# REST Interfaceを使ってみる

* メソッド名でルーティングが決定される
  * http://vibed.org/docs#web
  * http://vibed.org/api/vibe.web.rest/registerRestInterface

```d:app.d
import vibe.d;

void main()
{
  string address = "127.0.0.1";
  ushort port    = 8080;

  readOption("bindAddress|b", &address, "Sets the address for listning.");
  readOption("port|p", &port, "Sets the port for listning.");

  if (!finalizeCommandLineOptions())
    return;
  lowerPrivileges();

  auto settings = new HTTPServerSettings;
  settings.port = port;
  settings.bindAddresses = [address];

  auto router = new URLRouter;
  router.get("/", &hello);

  auto test = new TestImplementation;
  router.registerRestInterface(test);

  listenHTTP(settings, router);

  runEventLoop();
}

void hello(HTTPServerRequest req, HTTPServerResponse res)
{
  res.writeBody("Hello, World!");
}

interface TestInterface
{
  @path("test")
  string getTest();
}

class TestImplementation : TestInterface
{
  string getTest()
  {
    return "Hello from test resource!";
  }
}
```

```bash
$ curl localhost:8080/test
"Hello from test resource!"
```

## もう少しRESTfulにリソースを作ってみる

```d:app.d

import vibe.d;
import resource;

void main()
{
  string address = "127.0.0.1";
  ushort port    = 8080;

  readOption("bindAddress|b", &address, "Sets the address for listning.");
  readOption("port|p", &port, "Sets the port for listning.");

  if (!finalizeCommandLineOptions())
    return;
  lowerPrivileges();

  auto settings = new HTTPServerSettings;
  settings.port = port;
  settings.bindAddresses = [address];

  auto router = new URLRouter;
  auto test = new TestImplementation;
  router.registerRestInterface(test);

  listenHTTP(settings, router);

  runEventLoop();
}
```

```resource.d
module resource;
import vibe.d;

struct Resource
{
  long id;
  string name;
}

interface TestAPI
{
  Resource getResource(long id);
  Resource postResource(string name);
}

class TestImplementation : TestAPI
{
  Resource getResource(long id)
  {
    return Resource(id, "test");
  }

  Resource postResource(string name)
  {
    return Resource(1, name);
  }
}
```

```bash
$ curl -X POST -H "Content-Type: application/json" -d '{"name": "test"}' localhost:8080/resource
{"id":1,"name":"test"}
$ curl localhost:8080/1/resource
{"id":1,"name":"test"}
```

## 任意のパスを設定する

* `/resources/:id` に変更する

```diff:resrouce.d

 interface TestAPI
 {
-  Resource getResource(long id);
+  @path("resources/:id")
+  Resource getResource(long _id);
   Resource postResource(string name);
 }

 class TestImplementation : TestAPI
 {
-  Resource getResource(long id)
+  Resource getResource(long _id)
   {
-    return Resource(id, "test");
+    return Resource(_id, "test");
   }

   Resource postResource(string name)
```

# JSONのキー名に"body"を使う

* 変数名を`body_`にする

```diff:resource.d
@@ -4,14 +4,14 @@ import vibe.d;
 struct Resource
 {
   long id;
-  string name;
+  string body_;
 }

 interface TestAPI
 {
   @path("resources/:id")
   Resource getResource(long _id);
-  Resource postResources(string name);
+  Resource postResources(string body_);
 }

 class TestImplementation : TestAPI
@@ -21,8 +21,8 @@ class TestImplementation : TestAPI
     return Resource(_id, "test");
   }

-  Resource postResources(string name)
+  Resource postResources(string body_)
   {
-    return Resource(1, name);
+    return Resource(1, body_);
   }
 }
```

```bash
curl -X POST -H "Content-Type: application/json" -d '{"body": "test"}' localhost:8080/resources
{"id":1,"body":"test"}
```

# とにかくエラーを返す

* enforceHTTPを使う
  * http://vibed.org/api/vibe.http.common/enforceHTTP

```d:resource.d
 Resource getResource(long _id)
 {
   enforceHTTP(_id > 0, HTTPStatus.notFound, httpStatusText(HTTPStatus.notFound));
   return Resource(_id, "test");
 }
```

* Bad Requestもある
  * http://vibed.org/api/vibe.http.common/enforceBadRequest

```bash
$ curl -i localhost:8080/resources/1
HTTP/1.1 200 OK
Server: vibe.d/0.7.30
Date: Wed, 14 Dec 2016 14:50:15 GMT
Keep-Alive: timeout=10
Content-Type: application/json; charset=UTF-8
Content-Length: 22

{"id":1,"body":"test"}
```

```bash
$ curl -i localhost:8080/resources/0
HTTP/1.1 404 Not Found
Server: vibe.d/0.7.30
Date: Wed, 14 Dec 2016 14:50:20 GMT
Keep-Alive: timeout=10
Content-Type: application/json; charset=UTF-8
Content-Length: 29

{"statusMessage":"Not Found"}
```

---

# MVCっぽく書いてみる

```diff:app.d
+  router.post("/resources", &test.postResources);
```

```d:resource.d
module resource;
import vibe.d;

class Resource
{
  static int count = 0;
  long id;
  string body_;

  this (string body_)
  {
    this.id    = ++count;
    this.body_ = body_;
  }

  static Resource create(Json json)
  {
    string body_ = json["body"].get!(string);
    return new Resource(body_);
  }
}

interface TestAPI
{
  @path("resources/:id")
  Resource getResource(long _id);
}

class TestImplementation : TestAPI
{
  Resource getResource(long _id)
  {
    enforceHTTP(_id > 0, HTTPStatus.notFound, httpStatusText(HTTPStatus.notFound));
    return new Resource("test");
  }

  void postResources(HTTPServerRequest req, HTTPServerResponse res)
  {
    auto resource = Resource.create(req.json);
    res.writeJsonBody(serializeToJson(resource), HTTPStatus.created);
  }
}
```

```bash
$ curl -X POST -H "Content-Type: application/json" -d '{"body": "test:1"}' localhost:8080/resources
{"body":"test:1","id":1}
$ curl -X POST -H "Content-Type: application/json" -d '{"body": "test:2"}' localhost:8080/resources
{"body":"test:2","id":2}
``` 

# ログレベル

* [`setLogLevel`](http://vibed.org/api/vibe.core.log/setLogLevel)で設定する
  * http://vibed.org/api/vibe.core.log/
  * http://vibed.org/api/vibe.core.log/LogLevel

# まとめ

* Ruby on Rails等と比べると機能の貧弱さは否めない
* 大体のことは公式情報とソースを読めば解決できそう