DockerCompose + Nim + Jester + Nginxの環境を作る

# はじめに
Nimの記事を24日にアップしようとしてたけど、リアル(仕事)が忙しくて記事が作成できませんでしたので、謹んでお詫び申し上げます。
Nimでの開発するにあたり、Webフレームワークでどんなものがあるのかを調べて見たらJesterというフレームワークがあったので触って見ました。
今回は環境構築と、画面表示までを試して見たのでJesterに興味がある方は是非一度、構築して試して見てください。

# Jesterとは
JesterとはRubyのSinatraのようなNimの軽量Webフレームワークです。
https://github.com/dom96/jester

# やること
今回はNimのチュートリアルにある以下のマイクロサービスを作っていきます。
https://github.com/nim-lang/Nim/wiki/Tutorial:-Creating-a-(micro)-service

作成したソースはこちら
https://github.com/nemui-fujiu/docker_compose_nim_jester

## Docker-Composeの準備

構成はこんな感じ

```
.
├── README.md
├── docker-compose.yml
├── mysql
├── nginx
│   ├── Dockerfile
│   └── server.conf
├── nim
│   └── Dockerfile
└── work
```

今回はNim + Nginx + MySQL + Redisの環境を作るのでそれぞれ、必要なimageとDockerfileを用意します。

docker-composeファイルはこんな感じ

```:docker-compose.yml
version: "3.4"
services:
  nginx:
    container_name: std_nginx
    build:
      context: ./nginx/
      dockerfile: Dockerfile
    links:
      - app
    volumes:
      - ./work/nim_jester:/opt/nim_jester
    ports:
      - "9090:80"
  app:
    container_name: std_nim
    build:
      context: ./nim/
      dockerfile: Dockerfile
    tty: true
    privileged: true
    volumes:
      - ./work/nim_jester:/opt/nim_jester
    links:
      - mysql
      - redis
  mysql:
    image: mysql:latest
    container_name: std_mysql
    volumes:
      - ./mysql:/var/lib/mysql
    restart: always
    environment:
      - MYSQL_DATABASE=nim_db
      - MYSQL_ROOT_PASSWORD=example
      - MYSQL_USER=nim_app
      - MYSQL_PASSWOR=nim_app_pw
      - MYSQL_ALLOW_EMPTY_PASSWORD=yes
    ports:
      - "43306:3306"
  redis:
    image: redis:latest
    container_name: std_redis
    ports:
      - "46379:6379"

```

DockerやDocker Composeに慣れている人にとっては難しくはないと思います。
今回Nimのソースを好きなエディタでいじれるように、workディレクトリ配下に来るようにしています。

NginxのDockerfileはこんな感じ

```Dockerfile:nginx/Dockerfile
FROM nginx:latest
MAINTAINER satoshi.fujiu

ADD ./server.conf /etc/nginx/conf.d/default.conf

RUN apt-get update && apt-get install -y vim curl

WORKDIR /opt/nim_server
```

サーバ用のconfファイルを追加するのと、あとで操作しやすいようにライブラリを入れておきます。

```nginx:nginx/server.conf
server {
    listen 80;
    server_name  localhost;
    index index.html index.htm

    charset utf-8;

    # アクセスログの設定
    access_log /var/log/nginx/app_access.log;
    # エラーログの設定
    error_log /var/log/nginx/app_error.log;


    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        # Jester listens on port 5000 by default
        proxy_pass http://app:5000;
    }

    client_max_body_size 8M;

    #--------------------
    # 静的ファイル設定
    #--------------------
    # 画像やCSSにはキャッシュの有効期限を設定＆アクセスログOFF
    location ~ \.(html|css|js|jpe?g|png|gif|svg|mpg|flv|swf)$ {
        expires 10d;
        access_log off;
        break;
    }

    # .gitや.htaccessにはアクセス禁止
    location ~ /\.(ht|git|svn) {
        deny all;
    }

    # faviconへのアクセスは記録しない
    location = /favicon.ico {
        access_log off;
        log_not_found off;
    }

    # robots.txtへのアクセスは記録しない
    location = /robots.txt {
        access_log off;
        log_not_found off;
    }
}
```

Jesterはデフォルトで5000ポートで起動するのでNginxからproxyを5000ポートに向けてください。
同じサーバ内であればlocalhostで問題ないのですが、今回はnginxとnimのアプリケーションを別にDockerコンテナとして立ち上げるので

```
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        # Jester listens on port 5000 by default
        proxy_pass http://app:5000; # <= コンテナをわけないならhttp://localhost:5000を指定する。
    }
```
として、docker-composeで定義した通りにリンク先( http://app:5000 )を指定しておきます。

次にNimのDockerfileはこんな感じ

```Dockerfile:nim/Dockerfile
FROM nimlang/nim:latest
MAINTAINER satoshi.fujiu

RUN apt-get update && apt-get install -y vim curl

RUN nimble -y install jester

WORKDIR /opt/nim_jester
```

こちらもNginxとほとんど一緒ですが、今回WebフレームワークとしてJesterを使うので、nimbleコマンドでjesterをインストールしておきます。

これで準備完了したので起動して見ましょう。

```bash
$ docker-compose up -d --build
```

## プロジェクトの構成

```bash
.
└── work
    └── nim_jester
        ├── bin # コンパイル後のソースを配置する。
        ├── nim_jester.nimble # nimbleの設定
        └── src # 実際のソース
            ├── nim_jester.nim
            └── resources
                ├── conf
                │   └── conf.json
                └── templates	
                    ├── base.tmpl
                    └── home.tmpl
```

workディレクトリ配下にソースを作っていきます。
まずはプロジェクトの作成から始めましょう。
nimbleを使ってプロジェクトを作ります。
https://github.com/nim-lang/nimble

nimbleはNimのビルドツールで、パッケージ管理やタスク定義などができます。
タスクはNimScriptで記述します。
イメージとしてはJavaで言う所の、AntやMaven,Gradle
PHPでいうならcomposerとかって感じでしょうか。

今回は環境をDockerで作っているのでまずはDockerに入って作業をしましょう。
「docker ps」コマンドでコンテナを確認すると先ほどDocker Composeで作成したコンテナが並んでいるはずです。

```bash
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                     NAMES
1e72f858790d        dockercomp_nginx    "nginx -g 'daemon ..."   About an hour ago   Up About an hour    0.0.0.0:9090->80/tcp      std_nginx
7919c797ba80        dockercomp_app      "/bin/bash"              About an hour ago   Up About an hour                              std_nim
e6e7e27d7efd        mysql:latest        "docker-entrypoint..."   About an hour ago   Up About an hour    0.0.0.0:43306->3306/tcp   std_mysql
e780d77338fe        redis:latest        "docker-entrypoint..."   About an hour ago   Up About an hour    0.0.0.0:46379->6379/tcp   std_redis
```

std_nimがnimアプリケーション用のコンテナになるので、そこで作業します。

```bash
$ docker exec -it std_nim bash
/opt/nim_jester#
```

nimbleは入れてあるので、initコマンドでプロジェクトを作成します。

```bash
/opt/nim_jester# nimble init
      Info: In order to initialise a new Nimble package, I will need to ask you
        ... some questions. Default values are shown in square brackets, press
        ... enter to use them.
    Prompt: Package name? [nim_jester]
    Answer:
    Prompt: Initial version of package? [0.1.0]
    Answer:
    Prompt: Your name? [Anonymous]
    Answer:
    Prompt: Package description?
    Answer: jester description
    Prompt: Package license? [MIT]
    Answer:
    Prompt: Lowest supported Nim version? [0.17.2]
    Answer:
   Success: Nimble file created successfully
```

対話モードにて質問を受けるので適宜質問に回答すれば以下のようなファイルが作成されます。

```bash
/opt/nim_jester# ll
-rw-r--r-- 1 root root  165 Dec 30 09:33 nim_jester.nimble
```

中身を確認して見ましょう。

```bash
/opt/nim_jester# cat nim_jester.nimble
# Package

version       = "0.1.0"
author        = "Anonymous"
description   = "jester description"
license       = "MIT"

# Dependencies

requires "nim >= 0.17.2"
```

先ほど入力した値が入っていると思います。
今回はjesterを使ってアプリを作るので、jesterを追記しておきましょう。
合わせて、いくつか記載します。

```bash
/opt/nim_jester# vim nim_jester.nimble
# Package

version       = "0.1.0"
author        = "Anonymous"
description   = "jester description"
license       = "MIT"

# Dependencies

requires "nim >= 0.17.2" 
requires "jester >= 0.2.0" #①

# build dir　 #②
srcDir = "src"
binDir = "bin"
bin = @["nim_jester"] #③
```

1.jesterの0.2.0を必須にする設定を記述

```bash
requires "jester >= 0.2.0"
```

2.ビルド用ディレクトリ定義でそれぞれソースディレクトリとコンパイル後ソースの出力先を定義する。

```bash
srcDir = "src"
binDir = "bin"
```

3.実行するmain関数を指定します。
今回はnim_jesterという名前で作っていこうと思っています。

```bash
bin = @["nim_jester"]
```

準備ができたらソースを書いていきましょう。

```bash
/opt/nim_jester# mkdir src
/opt/nim_jester# vim src/nim_jester.nim
```

```nim:　nim_jester.nim
import jester, posix, json, asyncdispatch, htmlgen, os, osproc, logging

let config_file_name = "bin/resources/conf/conf.json"

let conf = parseFile(config_file_name)
# Traditional logging to file. To use the more featureful journald you might
# use https://github.com/FedericoCeratto/nim-morelogging
let fl = newFileLogger(conf["log_fname"].str, fmtStr = "$datetime $levelname ")
fl.addHandler

# the "debug" and "info" macros from the logging module are not flushing the buffer
proc log_debug(args: varargs[string, `$`]) =
  debug args
  fl.file.flushFile()

proc log_info(args: varargs[string, `$`]) =
  info args
  fl.file.flushFile()

include resources/templates/base.tmpl
include resources/templates/home.tmpl

routes:
  get "/":
    resp base_page(generate_home_page())
  get "/hello":
    resp h1("Hello world test")

when isMainModule:
  log_info "starting"
  runForever()
```

こんな感じでソースを書きます。

コンフィグ設定の読み込み
プロジェクトカレントディレクトリからの相対パスを記述しています。

```nim
let config_file_name = "bin/resources/conf/conf.json"
```

コンフィグファイルをParseして情報を変数へ格納

```nim
let conf = parseFile(config_file_name)
```

コンフィグのログファイル名を取得してロガーの作成

```nim
let fl = newFileLogger(conf["log_fname"].str, fmtStr = "$datetime $levelname ")
fl.addHandler
```

ログの出力をわかりやすくするのにちょっとまとめとく。

```nim
proc log_debug(args: varargs[string, `$`]) =
  debug args
  fl.file.flushFile()

proc log_info(args: varargs[string, `$`]) =
  info args
  fl.file.flushFile()
```

画面表示用のテンプレートを読み込んでおく。
includeはソースのカレントディレクトリからのパスになります。

```
include resources/templates/base.tmpl
include resources/templates/home.tmpl
```

ルーティングの設定。
HTMLを生成してレスポンスを返すこともできるし、tmplの中身を返すこともできます。
実際には*.tmpl内で各Procの定義がしてあります。

```
routes:
  get "/":
    resp base_page(generate_home_page())
  get "/hello":
    resp h1("Hello world test")
```

Jesterの起動処理。

```
when isMainModule:
  log_info "starting"
  runForever()
```

次に、tmplファイルとconfファイルを作りましょう。

```bash
/opt/nim_jester# mkdir -p src/resources/templates
```

```html:src/resources/templates/base.tmpl
#? stdtmpl | standard
#proc base_page(content: string): string =
#  result = ""
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>nim_jester</title>
    <link rel="shortcut icon" href=""/>
    <meta charset="utf-8">
    <meta name="description" content="">
    <meta name="author" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    </style>
  </head>
  <body>
    <div class="container">
    ${content}
    </div>
  </body>
</html>
```

```html:src/resources/templates/home.tmpl
#? stdtmpl | standard
#proc generate_home_page(): string =
#  result = ""
<h5>Welcome to nim_jester</h5>
```

```bash
/opt/nim_jester# mkdir src/resources/conf
```

```html:src/resources/conf/conf.json
{
  "log_fname": "/var/log/nim_jester.log",
}
```

これで一通りの準備が完了です。
それではビルドして見ましょう。

```bash
/opt/nim_jester# nimble build
  Verifying dependencies for nim_jester@0.1.0
      Info: Dependency on jester@>= 0.2.0 already satisfied
  Verifying dependencies for jester@0.2.0
   Building nim_jester/nim_jester using c backend
```

こんな感じでビルドが完了します。
実行ファイルが作られていることを確認しましょう。

```
/opt/nim_jester# ll bin/
total 1368
drwxr-xr-x 3 root root      96 Dec 30 11:18 ./
drwxr-xr-x 5 root root     160 Dec 30 11:16 ../
-rwxr-xr-x 1 root root 1398464 Dec 30 11:18 nim_jester*
```

これで実行ファイルの準備が整いました。
ただし、このまま起動するとconfファイルが読み込めないので以下のようなエラーになります。
これはsrc配下にconf.jsonを作っているのに、ソース内でbin配下を見ているためです。

```
/opt/nim_jester# bin/nim_jester
Traceback (most recent call last)
nim_jester.nim(16)       nim_jester
json.nim(1234)           parseFile
Error: unhandled exception: cannot read from file: bin/resources/conf/conf.json [IOError]
```

できればソースとコンパイル後のファイルについては別にしたいので、最初からプロジェクトディレクトリのsrc配下を参照しておいてもいいのですが、
せっかくなのでnimbleのタスクでファイルの移動もやってしまいましょう。
nimbleに新たにタスクを追加します。

```
/opt/nim_jester# vim nim_jester.nimble
# task
task serverstart, "start jester server":
  rmDir "bin"
  mkDir "bin/resources/conf"
  exec "cp -rf src/resources/conf bin/resources/"
  exec "nimble build"
  exec "bin/nim_jester"
```

色々試行錯誤したのですが、結果こんな感じになりました。
もっと良い書き方があると思うのですが、パッとかけたのがこれだったので取り合えずこれでリソースファイルの移動と起動もついでに追記してしまいます。

※タスク名にハイフン（-）を入れると正常に動作しないので注意してください。

それでは実行して見ましょう。

```bash
/opt/nim_jester# nimble serverstart
  Executing task serverstart in /opt/nim_jester/nim_jester.nimble
  Verifying dependencies for nim_jester@0.1.0
      Info: Dependency on jester@>= 0.2.0 already satisfied
  Verifying dependencies for jester@0.2.0
   Building nim_jester/nim_jester using c backend
```

そして、ブラウザから表示を確認する。
http://localhost:9090

![スクリーンショット 2017-12-30 20.29.13.png](https://qiita-image-store.s3.amazonaws.com/0/50276/f014c03d-6380-0a62-fa35-a5c8cb3849b3.png)

正常に表示できることが確認できました！
あとはみなさん好き勝手にNimでのJesterライフをお楽しみください！