はじめに
Diamond is a powerful MVC / Template Framework written in the D Programming Language, inspired by ASP.NET using vibe.d as backend.
DiamondはパワフルなASP.NETインスパイア系MVC/テンプレートです。バックエンドはvibe.dが使われています。
MVCという響きが良いですね。早速チュートリアルを触ってみます。
Features
まず公式ページの機能一覧を眺めます。
Web Application Frameworkとして必要な機能が整っていてとても良い感じです。
- General Features
- Low Memory & CPU Consumption
- MVC & HMVC
- RESTful
- Advanced Routing
- ACL
- Cross-platform
- Website/Webapi Support
- i18n
- Data & Storage
- ORM
- MySql ORM
- Caching
- Mongo
- Redis
- Request-context
- Cookies
- Sessions
- Transactions
- Views & Frontend
- Compile-time Parsing
- Partial Views
- Layouts
- Fast & Performant Rendering
- Dynamic
- Any D Code Can Be Executed
- Sections
- Flash-messages
- Controllers
- Auto-mapping
- View-integration
- Mandatory Actions
- Version-control
- More
- Authentication
- CSRF Protection
- Cryptography
- JSON/BSON
- Asynchronous
- Fibers/Tasks
- Sharding
- Network Security & Restrictions
- Unittesting
- Logging
Tutorials
2017年12月14日現在、下記3つ以外のリンク先はComing soon …
となっています。
Using Diamond without Web
このチュートリアルだけリポジトリがないので写経をしてビルドします。
../../../.dub/packages/diamond-2.5.3/diamond/core/webconfig.d-mixin-85(88,28): Error: file "test.dd" cannot be found or not in a path specified with -J
../../../.dub/packages/diamond-2.5.3/diamond/views/viewgenerator.d(34,7): Error: mixin diamondapp.GenerateViews!().generateViews.LoadViewData!false error instantiating
../../../.dub/packages/diamond-2.5.3/diamond/init/diamondapp.d(18,3): Error: mixin diamondapp.GenerateViews!() error instantiating
dmd failed with exit code 1.
まずは明らかに誤植と思しきファイル名を修正します。
renamed: views/home.dd -> views/test.dd
../../../.dub/packages/diamond-2.4.8/diamond/data/i18n/loader.d(56,9): Error: undefined identifier addMessage
../../../.dub/packages/diamond-2.4.8/diamond/data/i18n/loader.d(82,5): Error: undefined identifier addMessage
dmd failed with exit code 1.
ファイルを確認します。
../../../.dub/packages/diamond-2.4.8/diamond/data/i18n/messages.d
79: package(diamond) void addMessage(string language, string key, string message)
バグっぽいのでdiamondのバージョンを上げます。
--- a/dub.json
+++ b/dub.json
@@ -5,7 +5,7 @@
"homepage": "http://mydiamondwebsiteproject.com/",
"license": "http://mydiamondwebsiteproject.com/license",
"dependencies": {
- "diamond": "~>2.4.5"
+ "diamond": "~>2.5.3"
},
"sourcePaths": ["src", "models"],
"stringImportPaths": ["views", "config"],
../../../.dub/packages/diamond-2.5.3/diamond/http/websockets.d(300,47): Error: mutable method vibe.http.server.HTTPServerRequest.path is not callable using a const object
../../../.dub/packages/diamond-2.5.3/diamond/http/websockets.d(298,64): Error: none of the overloads of 'handleWebSockets' are callable using argument types (void), candidates are:
../../../.dub/packages/vibe-d-0.8.2/vibe-d/http/vibe/http/websockets.d(267,28): vibe.http.websockets.handleWebSockets(void function(scope WebSocket) nothrow @safe on_handshake)
../../../.dub/packages/vibe-d-0.8.2/vibe-d/http/vibe/http/websockets.d(272,28): vibe.http.websockets.handleWebSockets(void delegate(scope WebSocket) nothrow @safe on_handshake)
../../../.dub/packages/vibe-d-0.8.2/vibe-d/http/vibe/http/websockets.d(320,28): vibe.http.websockets.handleWebSockets(void delegate(scope WebSocket) nothrow @system on_handshake)
../../../.dub/packages/vibe-d-0.8.2/vibe-d/http/vibe/http/websockets.d(325,28): vibe.http.websockets.handleWebSockets(void function(scope WebSocket) nothrow @system on_handshake)
../../../.dub/packages/vibe-d-0.8.2/vibe-d/http/vibe/http/websockets.d(330,28): vibe.http.websockets.handleWebSockets(void delegate(scope WebSocket) @safe on_handshake)
../../../.dub/packages/diamond-2.5.3/diamond/http/websockets.d(298,64): ... (3 more, -v to show) ...
dmd failed with exit code 1.
Diamondが対応しているvibe.dのバージョンは現在0.8.1
までなので、明示的に指定します。
--- a/dub.json
+++ b/dub.json
@@ -5,7 +5,8 @@
"homepage": "http://mydiamondwebsiteproject.com/",
"license": "http://mydiamondwebsiteproject.com/license",
"dependencies": {
- "diamond": "~>2.4.5"
+ "vibe-d": "0.8.1",
+ "diamond": "~>2.5.3"
},
"sourcePaths": ["src", "models"],
"stringImportPaths": ["views", "config"],
これでようやく実行できます。
$ ./diamondproject
<p> Hello World!</p>
中を見ていきます。
$ tree
.
|-- config
| `-- views.config
|-- models
| `-- package.d
|-- src
| `-- main.d
|-- views
| `-- test.dd
|-- diamondproject
|-- dub.json
`-- dub.selections.json
config/views.conifg
test|test.dd
ビューの名前とファイル名を{name}|{file}
の形式で書きます。
models/package.d
module models;
public
{
// TODO: Import models here ...
}
モデルをインポートします。今回は空です。
views/home.dd
@*The test view*
<p> Hello World!</p>
1行目はメタデータのブロックです。ビューの設定を記述します。
今回はコメントのみです。
src/main.d
module main;
import diamondapp : getView;
void main()
{
import std.stdio;
auto view = getView("test");
writeln(view.generate());
readln();
}
ビューを取得してその内容を標準出力に出力します。
テンプレートエンジンとしての挙動が確認できます。
Creating a web site
こちらにソースがあるのでcloneします。
前のチュートリアルと同様に、vibe.dのバージョンを0.8.1
に固定してビルドします。
$ ./diamond-webserver-empty
Listening for requests on http://[::1]:8181/
Listening for requests on http://127.0.0.1:8181/
Listening for requests on http://[::1]:8080/
Listening for requests on http://127.0.0.1:8080/
The web-server Empty Diamond WebServer is now running.
$ curl 127.0.0.1:8181
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<p>Hello Diamond!</p>
</body>
</html>
$ curl 127.0.0.1:8080
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<p>Hello Diamond!</p>
</body>
</html>
良いでしょう。中身を見ていきます。
$ tree
.
|-- config
| |-- views.config
| `-- web.json
|-- controllers
| |-- homecontroller.d
| `-- package.d
|-- core
| `-- websettings.d
|-- models
| `-- package.d
|-- views
| |-- home.dd
| `-- layout.dd
`-- dub.json
dub.json
{
"name": "diamond-webserver-empty",
"description": "Empty project template for a web-server",
"authors": ["Jacob Jensen"],
"homepage": "https://github.com/DiamondMVC/Diamond-Template-WebServer",
"dependencies": {
"vibe-d": "~>0.8.1",
"diamond": "~>2.4.5",
"mysql-native": "~>1.1.2"
},
"versions": ["VibeDefaultMain", "Diamond_Debug", "Diamond_WebServer"],
"sourcePaths": ["core", "models", "controllers"],
"stringImportPaths": ["views", "config"],
"targetType": "executable"
}
config/web.json
{
"name": "Empty Diamond WebServer",
"staticFileRoutes": ["public"],
"homeRoute": "home",
"allowFileRoute": false,
"accessLogToConsole": false,
"addresses": [{
"ipAddresses": ["::1", "127.0.0.1"],
"port": 8181
}, {
"ipAddresses": ["::1", "127.0.0.1"],
"port": 8080
}],
"defaultHeaders": {
"general": {
"Content-Type": "text/html; charset=UTF-8",
"Server": "vibe.d - Diamond MVC/Template Framework"
},
"staticFiles": {
"Server": "vibe.d - Diamond MVC/Template Framework"
},
"notFound": {
"Content-Type": "text/html; charset=UTF-8",
"Server": "vibe.d - Diamond MVC/Template Framework"
},
"error": {
"Content-Type": "text/html; charset=UTF-8",
"Server": "vibe.d - Diamond MVC/Template Framework"
}
}
}
-
staticFileRoutes
静的ファイル置き場を指定します -
homeRoute
でデフォルトルートを指定します -
allowFileRoute
はビューをファイル名でルーティングするか否かのフラグです -
accessLogToConsole
はログを標準出力にリダイレクトするか否かのフラグです -
addresses
で応答するIPアドレスを指定します -
defaultHeaders
でデフォルトで使うヘッダーを指定できます-
general
は通常のレスポンスに使用されます -
stticFiles
は静的ファイルに使用されます -
notFound
は404応答時に使用されます -
error
はエラー時に使用されます
-
config/views.config
layout|layout.dd
home|home.dd
ホーム用のビューとレイアウトファイルを指定しています。
controllers/package.d
module controllers;
public
{
import controllers.homecontroller;
}
必要なコントローラをインポートします。MVCっぽくなってきました。
controllers/homecontroller.d
module controllers.homecontroller;
import diamond.controllers;
/// The home controller.
final class HomeController(TView) : Controller!TView
{
public:
final:
/**
* Creates a new instance of the home controller.
* Params:
* view = The view assocaited with the controller.
*/
this(TView view)
{
super(view);
}
/// Route: / | /home
@HttpDefault Status home()
{
return Status.success;
}
}
コントローラの実装です。ロジックがないので200応答を返すだけになっています。
コントローラのメソッドは必ずStatus
を返す必要があります。
core/websettings.d
module websettings;
import diamond.core.websettings;
class DiamondWebSettings : WebSettings
{
import vibe.d : HTTPServerRequest, HTTPServerResponse, HTTPServerErrorInfo;
import diamond.http;
private:
this()
{
super();
}
public:
override void onApplicationStart()
{
}
override bool onBeforeRequest(HttpClient client)
{
return true;
}
override void onAfterRequest(HttpClient client)
{
}
override void onHttpError(Throwable thrownError, HTTPServerRequest request,
HTTPServerResponse response, HTTPServerErrorInfo error)
{
response.bodyWriter.write(thrownError.toString());
}
override void onNotFound(HTTPServerRequest request, HTTPServerResponse response)
{
import std.string : format;
response.bodyWriter.write(format("The path '%s' wasn't found.'", request.path));
}
override void onStaticFile(HttpClient client)
{
}
}
void initializeWebSettings()
{
webSettings = new DiamondWebSettings;
}
-
onBeforeRequest
リクエストが処理される前処理をハンドリングできます -
onAferRequest
リクエストが処理される前処後をハンドリングできます -
onHttpError
エラー時のハンドリングをします -
onNotFound
404エラー時のハンドリングをします -
onStaticFile
静的ファイルが呼ばれた時のハンドリングをします
models/package.d
module models;
public
{
// TODO: Import models here ...
}
モデルは今回も利用しません。
views/layout.dd
@<doctype>
<html>
<head>
<title>@<title></title>
</head>
<body>
@<view>
</body>
</html>
レイアウトを記述します。doctype
はDiamondが提供するデフォルトのドキュメントタイプのプレイスホルダです。
title
ろview
はそれぞれ、このページのタイトルと、このレイアウトを利用するページのビューのプレイスホルダです。
views/home.dd
@[
layout:
layout
---
route:
home
---
controller:
HomeController
---
placeHolders:
[
"title": "Home"
]
]
<p>Hello Diamond!</p>
下記の値はそれぞれの各値に対応します。
-
layout
レイアウト名 -
model
モデル名 -
controller
コントローラ名 -
route
パス
placeHolders
はプレイスホルダを連想配列で記述します。
ここではページのタイトルのみです。
Creating a web site
こちらからcloneし、同様にvibe.dのバージョンを0.8.1
に固定してからビルドします。
controllers/homecontroller.d(11,30): Error: template instance Controller!HomeController Controller is not a template declaration, it is a class
--- a/src/controllers/homecontroller.d
+++ b/src/controllers/homecontroller.d
@@ -8,7 +8,7 @@ module controllers.homecontroller;
import diamond.controllers;
/// The home controller.
-final class HomeController : Controller!HomeController
+final class HomeController : Controller
{
public:
final:
これで一旦ビルドできたので実行します。
$ ./diamond-webapi-empty
unhandledError: object.Exception@../../../../../.dub/packages/vibe-d-0.8.1/vibe-d/data/vibe/data/serialization.d(782): Missing non-optional field 'staticFileRoutes' of type 'WebConfig' (DefaultPolicy(T)).
object.Exception@../../../../../.dub/packages/vibe-d-0.8.1/vibe-d/data/vibe/data/serialization.d(782): Missing non-optional field 'staticFileRoutes' of type 'WebConfig' (DefaultPolicy(T)).
staticFileRoutes
が何かおかしいようです。web.json
に記述が足りていないようなので追記します。
--- a/src/config/web.json
+++ b/src/config/web.json
@@ -1,5 +1,6 @@
{
"name": "Empty Diamond WebApi",
+ "staticFileRoutes": ["public"],
"homeRoute": "home",
"allowFileRoute": false,
"accessLogToConsole": false,
これでビルドできました。実行してみます。
$ ./diamond-webapi-empty
Listening for requests on http://[::1]:8181/
Listening for requests on http://127.0.0.1:8181/
Listening for requests on http://[::1]:8080/
Listening for requests on http://127.0.0.1:8080/
The web-api Empty Diamond WebApi is now running.
$ curl 127.0.0.1:8080 -i
HTTP/1.1 200 OK
Server: vibe.d - Diamond MVC/Template Framework
Date: Wed, 13 Dec 2017 14:37:46 GMT
Keep-Alive: timeout=10
Content-Type: text/json; charset=UTF-8
Transfer-Encoding: chunked
{
"message": "Hello Diamond!"
}
インデントが大分おかしいですがJSONが返ってきました。
中身を見ていきます。
.
|-- config
| |-- controllers.config
| `-- web.json
|-- controllers
| |-- homecontroller.d
| `-- package.d
|-- core
| `-- websettings.d
|-- models
| `-- package.d
`-- dub.json
前のチュートリアルと違うところはコントローラです。
config/controllers.config
Home
Controller
を抜いたコントローラ名を記述します。
controllers/package.d
module controllers;
public
{
import controllers.homecontroller;
}
利用するコントローラをインポートします。
controllers/homecontroller.d
前のチュートリアルとコントローラの差を見てみます。
$ diff Diamond-Template-Web{Server,Api}/src/controllers/homecontroller.d
--- Diamond-Template-WebServer/src/controllers/homecontroller.d 2017-12-13 19:56:08.858793540 +0900
+++ Diamond-Template-WebApi/src/controllers/homecontroller.d 2017-12-13 23:27:14.073648179 +0900
@@ -1,6 +1,6 @@
/**
* Copyright c DiamondMVC 2016-2017
-* License: MIT (https://github.com/DiamondMVC/Diamond-Template-WebServer/blob/master/LICENSE)
+* License: MIT (https://github.com/DiamondMVC/Diamond-Template-WebApi/blob/master/LICENSE)
* Author: Jacob Jensen (bausshf)
*/
module controllers.homecontroller;
@@ -8,23 +8,25 @@
import diamond.controllers;
/// The home controller.
-final class HomeController(TView) : Controller!TView
+final class HomeController : Controller
{
public:
final:
/**
* Creates a new instance of the home controller.
* Params:
- * view = The view assocaited with the controller.
+ * client = The client passed to the controller.
*/
- this(TView view)
+ this(HttpClient client)
{
- super(view);
+ super(client);
}
/// Route: / | /home
@HttpDefault Status home()
{
- return Status.success;
+ return jsonString(`{
+ "message": "Hello Diamond!"
+ }`);
}
}
主な違いはビューの有無とjsonString()
です。ちょっと気持ち悪いですがStatus
を返します。
まとめ
チュートリアルで簡単な動きを確認できました。
各機能についても今後触っていく予定です。