はじめに
- vibe.dの薄い本買いました
- 簡単なWeb APIサーバーを作ってみて、困ったこととその解決策を共有します
- とてもためになる公式ドキュメント
- サンプル集
- この記事はshibuya.d #2の発表内容に加筆修正を加えたものです
おことわり
- やること
- 簡単なWeb APIサーバーを作ります
- やらないこと
- GUIはやらないです
- DBは使いません
- D言語くんはでてきません
環境について
- マルチプラットホーム
- Windowsでも普通に動くぞ!やったぜ!
- Dockerは?
-
https://github.com/zckri/docker-vibed
- 上記を参考に自分で作ったほうが良いかも
- Ubuntuにd-apt入れるだけ
-
https://github.com/zckri/docker-vibed
待ち受けアドレスとポートを変更する
- コマンドラインオプションで指定したい
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を使ってみる
- メソッド名でルーティングが決定される
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!";
}
}
$ curl localhost:8080/test
"Hello from test resource!"
もう少しRESTfulにリソースを作ってみる
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);
}
}
$ 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
に変更する
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_
にする
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_);
}
}
curl -X POST -H "Content-Type: application/json" -d '{"body": "test"}' localhost:8080/resources
{"id":1,"body":"test"}
とにかくエラーを返す
- enforceHTTPを使う
resource.d
Resource getResource(long _id)
{
enforceHTTP(_id > 0, HTTPStatus.notFound, httpStatusText(HTTPStatus.notFound));
return Resource(_id, "test");
}
- Bad Requestもある
$ 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"}
$ 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っぽく書いてみる
app.d
+ router.post("/resources", &test.postResources);
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);
}
}
$ 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}
ログレベル
まとめ
- Ruby on Rails等と比べると機能の貧弱さは否めない
- 大体のことは公式情報とソースを読めば解決できそう