ソース管理にGitLabを使っていて、サブディレクトリで動く設定にしていますが、この設定、結構面倒くさいんです(ファイル4つ書き換える)。また、アップデート後のreconfigureでこの設定がなくなってしまい、gitlab.rbでも指定できないみたいなので、いつも手作業です。

さらに、GitLabは毎月確実にアップデートされていて、細かいリリースは逐次行われるので、うかうかしていると新しくなりすぎていてバージョンアップできるかどうかを結構気にしないといけないことにもなります。アップデート自体はyumでやってくれるんですが、サブディレクトリ設定は毎回ちゃんとやり直さないといけないのが大変です。

で、先ほどGitLabを最新にして、いつも通りにサブディレクト利用の設定を書き換えていたら、設定方法が変わってました。

基本的には、gitlab.yml,unicorn.rb,config.yml,application.rbの4つに手を入れるんですが、application.rbにあるはずの「config.relative_url_root = “/gitlab”」のコメント行がなくなっていました。

調べてみたら、Install GitLab under a relative URLこう言うことみたいです。

application.rbの設定は、relative_url.rbというファイルに書かないといけなくなったみたいですね。relative_url.rb.sampleには、「config.relative_url_root = “/gitlab”」と書いてあるので、このファイルが有効になっていれば読み込まれると言うことでしょう。

まとめるとこんな感じになります。(/gitlabで動かす場合)

cp /opt/gitlab/embedded/service/gitlab-rails/config/initializers/relative_url.rb.sample \
   /opt/gitlab/embedded/service/gitlab-rails/config/initializers/relative_url.rb

vim /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
    relative_url_root: /gitlab のコメントアウト外す

vim /var/opt/gitlab/gitlab-rails/etc/unicorn.rb
    ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" を追記

vim /var/opt/gitlab/gitlab-shell/config.yml
    gitlab_url: "http://localhost:8080/gitlab" に書き換え

iOSの開発なんかもちょっと面倒を見ないといけなくなったこともあり、作業環境がMac Book Proになりました。これまでほとんど触ったことはなかったのですが、やっと慣れてきました。
ベースがunixなだけあって、PHPとかWebが関連するようなものを扱う場合は非常に便利ですね。Web系の人たちがみんなMacを使っているのもよくわかります。

現在に至るまでの最大の難関はキーボードでした。

ながらくWindowsで、キー配置を少しカスタマイズして使ってきたこともあり、体に染みついている動きを変えるのは困難……というか不可能だったので、カスタマイズ不可欠です。特に、修飾キー周りは、Control,Option,Commandが全くなじめません。へんてこりんな記号が未だに覚えられません。
キーボードは愛用している外付けのもの(FILCOのMinira)でMac用のキートップではないので、ショートカットキーで「(上矢印)(クローバーみたいなやつ)G」みたいな表示をされても、いったいどれなんだかわからない……

さらに、環境がMacになったとはいえWindows系の仕事もあるので、VMWareFusionを使って、仮想のWindows環境での作業もありますので、今まで通りの操作ができないと結構厳しいです。

で、あれこれ試行錯誤した結果、

  • キーボードのdepスイッチでctrlとcapsを入れ替え
  • Macの修飾キーを設定を変更
  • Karabinerを使って一部キーをカスタマイズ
  • VMWareの環境設定
  • VMWare上のWindowsの「のどか」で右AltをToggleIME

という風にして、ストレスのない環境ができました。

キーボードのdepスイッチでctrlとcapsを入れ替え

FILCOのキーボードは、depスイッチで一部のキー配置を変更できますので、ctrlとcaps入れ替えをonにします。
この設定は、Windowsの時は「のどか」でやっていました。

Macの修飾キー設定を変更

keyboard

Windowsで言うCtrl+なんとかな操作が、Macだと軒並みCommand+なんとかになってしまい、キーの位置が非常に使いづらいですね。
なので、Macの環境設定で、ControlとCommandを入れ替えます。あと、Caps LockはVMWare上のWindowsでややこしい動きをするので、アクションなしにしています。

Karabinerを使って一部キーをカスタマイズ

Karabinerは、かなり細かくいろいろカスタマイズできますが、カスタマイズしたのは2つだけ。

  • Option_RをKana Eisuu
  • VMWareの時にCommand_LをControl_Lにする

Option_RをKana Eisuu

Windowsの時に、「右AltをIME ON/OFF」にしていた設定を再現しています。
外付けキーボードの右AltがMacだとOption_Rになるので、これで同じ動作をしてくれます。

VMWareの時にCommand_LをControl_Lにする

ややこしいんですが、Aの左にあるやつの設定です。
Macの修飾キー設定で「Aの横はCommand」になっているんですが、このままだとWindows環境の時に、「Aの左がCtrl」になってくれないので、VMWareの時にはまた元に戻す、ってことになります。

VMWareの環境設定

VMWareの環境設定-キーボードとマウスで、

  • キーマッピングの「キーマッピングを有効にする」「言語固有のキーマッピングを有効にする」をオフに。→ここでもカスタマイズはできそうだけど、特に必要ない。
  • Macホストショートカットで「Windowsキーでは、以下を使用」を「右コマンドキーにする」→キーボードに右コマンドキーはないので、実質「Windowsキーをなくす」設定

Windows側の「のどか」設定

で、最後にWindowsに入れてある「のどか」でRightAltをToggleIMEにする。
(ほかにもいくつかカスタマイズはしているけど、Windows上だけの話で特に関係ない)

おしまい

こんな感じで、今までと同じような感覚で操作ができるようになりました。

ただ、一点だけどうしようもないのが、Macの「Command+h」。
これを押すと、最前面のアプリが最小化されるみたいなんですが、このショートカットはかなり深いところで設定されているらしく、カスタマイズする方法が見つからないのです。かろうじて、「ヘルプが出るようにすればアプリは最小化されないよ」みたいなのが見つかったんですが、コレジャナイ感満載……

Windows、特にMicrosoft系のアプリケーションだと、Ctrl+hは置換のショートカットで、上記カスタマイズを行った環境で置換操作をしようとすると、画面が突然消えるのです。何でこいつだけこんな特別扱いなんですかね。

Windows10タブレットで運用するWPFアプリケーションの作業をしています(UWPではなくWPF)。
いちいちビルド結果をタブレットにコピーしたり、ましてやタブレットにVisual Studioを入れるというのはさらに面倒くさいので、手元の開発環境での動作確認と、タブレットでの動作確認を切り分けて行えるようにしています。

基本

タブレットにリモートデバッガをインストール

まず、Remote Tools for Visual Studio 2015 Update 1をタブレットにインストールします。

debugger

インストール後、Remote Debuggerを起動するとデフォルトだとTCPの4020で待ち受け状態になります。(開発環境のあるPCからネットワーク的にアクセスできる状態になっている必要があります)

Visual Studioプロジェクトの設定

次に、開発環境のあるPCで、プロジェクトの設定をします。
まず、プロジェクトのプロパティ-デバッグの設定で、「リモートコンピュータを使用する」に、タブレットのIPアドレス(もしくは名前で解決できるのであればそれでも可)とポート(4020)を指定します。

option1

次に、ビルドイベントの「ビルド後イベントのコマンドライン」に下記を指定します。
ソリューションは、c:\work\HelloWorldWPFにおいてあることを想定しています。
あとタブレット側はcドライブ自体が共有でアクセスできるようにしてあります。

option2

xcopy $(ProjectDir)bin\$(ConfigurationName)\ \\wintab\c\work\$(SolutionName)\$(ProjectName)\bin\$(ConfigurationName) /e /c /h /y /d /i

ちょっとごちゃごちゃしていますが、「ビルドが完了したらバイナリをタブレットの同じ位置にコピーする」と言うことをしています。
というのも、どうも開発環境でのディレクトリ構成と同じパスになっていないとダメみたいで、ビルド結果が「c:\work\HelloWorldWPF\HelloWorldWPF\bin\Debug」に出力されるようになっていれば、タブレット側も同じパスじゃないとダメってことです。
ビルド後コマンドも、中途半端にマクロを使うことになっていますが、最後に\がつくとxcopyがエラーになるのでこういうことになっています。

要は、「開発環境と同じパスにビルド結果が入ればいい」と言うことなので、コマンドプロンプトでちゃんとそのようにコピーされるコマンドを確認してから貼り付けてもいいですし、何か別のツールが実行されるようにしてもいいですね。

ここまで設定した上で、一度ビルドをしてみます。
タブレット側のディレクトリにDebugの内容が入っていればOKです。
デバッグ実行すると、タブレット側にウインドウが開きます。

この状態で、開発環境にちゃんとデバッグの出力がされますし、ブレークポイントもちゃんと機能します。画面が出ているのがタブレットなだけで、基本的なことは全部同じです。

まとめると、

  • リモートデバッガのインストール
  • プロジェクトでリモート実行するように設定
  • ビルド結果を開発環境と同じ構成のパスにコピー

という段取りになります。

実用的な設定

今回のプロジェクトの場合、常にタブレットでデバッグする必要はないので、通常のデバッグは開発環境で行い、タブレットでテストしたいときだけリモートデバッグする、という風な使い方になるので、ちょっと工夫しています。

ビルドの出力先を基準が固定されたところにする

「c:\buildwork\$(SolutionName)\$(ConfigurationName)」とかに出力されるようにする、ということにすれば、開発環境、タブレット環境ともにc:\buildworkだけ作っておけば、ビルドとxcopyが面倒を見てくれます。

構成マネージャで”Remote”など別の構成を作る

標準では、DebugとReleaseの2つの構成がありますが、そこに”Remote”という名前の構成をDebugをコピーして作成します。そして、プロジェクトのプロパティでRemote構成の場合はリモートでデバッグするように設定しておきます。
そうすると、Debug構成の時は開発環境で、Remote構成を選んだらタブレットで動くようになります。

ビルド後のスクリプトを修正

コピーするときのスクリプトに「Remote構成の時は」という条件を入れておかないと、タブレットが起動してないとxcopyがエラーになってうざいですね。

if "$(ConfigurationName)" == "Remote" (
xcopy ...
)

こんな感じに設定しておいて、普段はDebug、タブレットで動かすときはRemoteを使えば便利です。

複数メンバーで同じソリューションやプロジェクトを使う場合などは、みんな同じディレクトリ構成が要求されるので事前の取り決めや準備が必要、もしくは各自でプロジェクト設定を変更する、などの手間が出るかもしれません。
あと、タブレットの共有設定が自由にできるかどうかなども問題もあるかもしれません(借りている端末や実機などだといろいろ設定を触れないかもしれないですからね)。

とはいえ、逐一バイナリをコピーして……というのも手間ですし、ステップ実行や開発環境でのモニタリングができる状態での検証は生産効率も上がりますよね。

Slim Frameworkの3.0が12/7に正式リリースされました。

Slim 3.0.0 released!

ちょうど、あるC#で作っているアプリケーションの動作確認用サーバーAPIをSlimで書いていたので、それを3.0にしてみました。
結論から言うとそのまま動きません。いろいろ書き方が変わってます。

Slim 2.6のコード

下記がSlim2.6仕様のコード。アプリケーション側のコードを試すためだけに書いたので、非常に単純なものです。index.phpにベタ書きしています。
最初の方のいくつかは、Slim自体の動作確認用に書いてあるものです。

<?php
require 'vendor/autoload.php';

$app = new \Slim\Slim();

$app->get('/', function () {
    echo "Index";
});

$app->get('/hello', function () {
    echo "Hello!";
});

$app->get('/hello/:name', function ($name) {
    echo "Hello, $name!";
});

$app->get('/json', function () {
    $json_array = array(
        'name' => 'fumihiro fukuda',
        'company' => 'dalian spacekey'
    );

    echo json_encode($json_array);
});

$app->post('/post', function () use ($app) {
    $array = json_decode($app->request->getBody(), true);

    $json_array = array(
        'response_name' => $array["name"],
        'response_company' => $array["company"]
    );

    echo json_encode($json_array);
});

$app->post('/login', function () use ($app) {
    $array = json_decode($app->request->getBody(), true);

    $login_id = $array["login_id"];
    $login_pw = $array["login_pw"];

    if ($login_id == 'success') {
        $json_array = array(
            'user_name' => 'test name',
            'user_serial' => 'ABCDEFGH'
        );

        $app->response->setStatus(200);
        echo json_encode($json_array);
    } else {
        $app->response->setStatus(401);
    }
});

$app->post('/list', function () use ($app) {

    $json_array = array(
        array(
            'report_key' => 'key1',
            'customer_id' => 'customer1'
        ),
        array(
            'report_key' => 'key2',
            'customer_id' => 'customer2'
        )
    );

    $app->response->setStatus(200);
    echo json_encode($json_array);
});

$app->post('/detail/:report_key', function ($report_key) use ($app) {

    if ($report_key == 'key1') {
        $json_array = array(
            'report_key' => $report_key,
            'customer_id' => 'customer1',
            'report_contents' => 'key1-customer1-contents'
        );
    } else {
        $json_array = array(
            'report_key' => $report_key,
            'customer_id' => 'customer2',
            'report_contents' => 'key2-customer2-contents'
        );
    }

    $app->response->setStatus(200);
    echo json_encode($json_array);
});

$app->run();

Slim3.0仕様のコード

で、Slim3.0で最低限動くようにしたコードがこれです。

<?php
require 'vendor/autoload.php';

$app = new \Slim\App();

$app->get('/', function () {
    echo "Index";
});

$app->get('/hello', function () {
    echo "Hello!";
});

$app->get('/hello/{name}', function ($request, $response, $args) {
    echo 'Hello, ' . $args['name'] . '!';
});

$app->get('/json', function () {
    $json_array = array(
        'name' => 'fumihiro fukuda',
        'company' => 'dalian spacekey'
    );

    echo json_encode($json_array);
});

$app->post('/post', function ($request, $response, $args) {
    $array = $request->getParsedBody();

    $json_array = array(
        'response_name' => $array['name'],
        'response_company' => $array["company"]
    );

    echo json_encode($json_array);
});

$app->post('/login', function ($request, $response, $args) {
    $array = $request->getParsedBody();

    $login_id = $array["login_id"];
    $login_pw = $array["login_pw"];

    if ($login_id == 'success') {
        $json_array = array(
            'user_name' => 'test name',
            'user_serial' => 'ABCDEFGH'
        );

        $response = $response->withStatus(200);

        $body = $response->getBody()->write(json_encode($json_array));
        //echo json_encode($json_array);
    } else {
        $response = $response->withStatus(302);
    }
    return $response;
});

$app->post('/list', function ($request, $response, $args) {

    $json_array = array(
        array(
            'report_key' => 'key1',
            'customer_id' => 'customer1'
        ),
        array(
            'report_key' => 'key2',
            'customer_id' => 'customer2'
        )
    );

    $response = $response->withStatus(200);
    $body = $response->getBody()->write(json_encode($json_array));

    return $response;
});
$app->post('/detail/{report_key}', function ($request, $response, $args) {

    if ($args['report_key'] == 'key1') {
        $json_array = array(
            'report_key' => $args['report_key'],
            'customer_id' => 'customer1',
            'report_contents' => 'key1-customer1-contents'
        );
    } else {
        $json_array = array(
            'report_key' => $args['report_key'],
            'customer_id' => 'customer2',
            'report_contents' => 'key2-customer2-contents'
        );
    }

    $response = $response->withStatus(200);
    $body = $response->getBody()->write(json_encode($json_array));

    return $response;
});

$app->run();

もしかしたらもうちょっと3.0に適した書き方があるのかもしれませんが、これで2.6と同じ動きをするようになってます。
一カ所コメントで、

//echo json_encode($json_array);

となっているところは、$response->getBody()->writeじゃなくても、echoでも結果は同じです。

変更点

new \Slim\App();

\Slim\Slim()って書いてたのが、\Slim\App()になっています。

requestとresponse

use ($app)してから、$app->requestとして取得していたものを、パラメータでもらうように変わってます。

URLから取るパラメータ

/hello/:name→ function($name)→ $nameという段取りで取得していたURLからのパラメータ指定方法が、{}書きに変わって、$argsから取得するように変わってます。

JSON文字列からオブジェクトへのパース

bodyを取得してからjson_encodeをしていたものが、$request->getParsedBody()一発でできるようになってます。

response

responseを何か操作したら、ちゃんと最後にreturnで戻さないとそのresponseが返りません。

ステータスコード

$app->response->setStatus(401)としていたのですが、$response->withStatus(401)として、そのresponseを戻さないといけません。

おわり

これだけ単純なコードでもいろいろ手を加えないといけないので、もっと作り込んでいるコードだとちょっと対応は大変ですね。
とはいえ、あえてSlimを選んで構築したサイトであれば、もともとフレームワーク自体シンプルに機能が絞られてるところから開発しているのでしょうし、現状で不満がないのであれば無理に3.0で作り替える必要もないのでしょう。
(まあ、composerでとれなくなったりするようであれば困りますけどね)

LightNodeという、OWINで動くサーバーAPIを構築するのに適したフレームワークがあります。とにかくシンプルに構築できて、動作も高速というところに特化して作られているそうです。仕事でもしかしたら使うかもしれないので、ちょっと触ってみることにしました。

とりあえず、右も左もわからないので、Nancy からLightNode へ移行のススメを参考にさせてもらって、Hello Worldしてから、swaggerで動作確認できるところを目標にして、まずはサンプルソースを動かしたり、LightNodeの説明を読んだりして何となくわかった感じになったので、サラの状態から書いてみたら……APIについてはちゃんとできるんですが、swaggerがちゃんと表示されない。
ちゃんとレスポンスは来るんですが、HTMLがそのまま表示されて、swaggerのページとして表示されないのです。
参考ページのサンプルではちゃんと動くのを見てるんですが、自分で作ったものはだめ。参照しているコンポーネントとかも特に問題ないし、なによりHelloWorld自体はちゃんとできてるのでLightNode.Swaggerになにかありそう……

と言うことで、LightNodeのソースをあたってみましたら、最新の1.6.3でswaggerのUIが更新されていました。参考ページのソースはよく見ると1.6.0を使っているようです。

試しに自分のソースのLightNodeを1.6.0に落としてやってみると、これがちゃんと表示されちゃう。1.6.0と1.6.3の何が違うのかとdiffって見たりしてたんですが、なんかそれっぽいところが見当たらず困っていたんですが、1.6.0に1.6.3の変更点を部分部分マージしながら確認していたら、なんか1.6.3のソースでもちゃんと表示されるようになってしまいました。

だいぶ途方に暮れてしまったので、2,3時間放置して他のことをしていたらハッと気がつきました。
HTMLファイルがBOM付きで保存されているとこうなることがあるということを……

確認したら付いてました、BOMが。LightNode.Swagger/SwaggerUI/index.htmlに。
あらためて、1.6.3のソースでBOMなしにしたら動きました。

1.6.0にマージしていって動いたのはファイル丸ごとマージしなかったからBOMがない状態だったんですね。
こればっかりは、コードを差分で見てもわかりませんし気がつくのは難しいですよね。

なんかどうやってこれを知らせたら良いのかわからなかったので、githubのissuesに登録しておきました。
英語かけないので、タイトルだけ英語で……

« Prev Next »