3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

D言語Advent Calendar 2017

Day 14

Diamondのチュートリアルを触ってみた

Last updated at Posted at 2017-12-15

はじめに

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

image.png
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エラー時のハンドリングをします
  • onNotFound404エラー時のハンドリングをします
  • 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が提供するデフォルトのドキュメントタイプのプレイスホルダです。
titleviewはそれぞれ、このページのタイトルと、このレイアウトを利用するページのビューのプレイスホルダです。

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を返します。

まとめ

チュートリアルで簡単な動きを確認できました。
各機能についても今後触っていく予定です。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?