Murayama blog.

プログラミングと、その次の話

フレームワーク初学者のためのSpark Framework入門

最近、Javaのマイクロフレームワークがきてるようです。

http://postd.cc/java-micro-frameworks-the-new-trend-you-cant-ignore/

Javaフレームワーク事情

Javaに限らず、Webアプリケーション開発の世界ではフレームワークを使って開発することが一般的です。JavaにはSpringフレームワークや標準のJavaEEなど多くのフレームワークが存在しますが、これらのフレームワークは様々な要件を満たすため、多くの機能が含まれています。そのため、フレームワーク初学者にとって、フレームワークとはどういうものかを学習するという意味では全体像が掴みにくく、学習コストの高いものになっています。

昔はStrutsを勉強して、それからHibernateを勉強して、それからSpringやSeasar2を勉強する、という流れがありました。最近はいきなりSpringから入ることが多い?ようなので大変だろうなと思っています。Spring Bootもよさげですけどね。

マイクロフレームワーク

2016年現在、Webフレームワークの標準を作っているのはやはりRuby on Railsでしょう。Railsからインスパイヤされた(であろう)Webフレームワークは言語を問わず数多く存在します。これらは大変素晴らしいフレームワークですが、小さなWebアプリを開発する場合、たとえば簡易な掲示板を作成するようなシーンにおいては、やや大げさなものになります。

Railsが登場した後、Ruby界隈で、SinatraというWebアプリケーションフレームワークが注目されるようになります。SinatraRailsとは違って、シンプルで軽量なフレームワークです。簡単に言えば、HTTPリクエストと処理を関連づけるルーティング処理だけを実装したフレームワークです。またDSLで簡潔に処理を記述できるところも特徴の一つです。具体的には次のようなコードを実装します。

require 'sinatra'

get '/hi' do
  "Hello World!"
end

初めて見るとRubyのコードなの?という印象を受けるかもしれませんが、これはRubyの文法にしたがって記述されています。getというキーワードがありますが、これはHTTPのGETリクエストを処理することを意味しており、'/hi'の部分はURLを意味しています。つまり、GETリクエストで'/hi'というURLが要求された場合、あとのdo - endブロックが実行され、レスポンスとして"Hello World"が返却される仕組みになっています。

特定のドメイン(目的)に特化したコード表記法をDSLDomain Specific Language)と呼びます。Sinatraの場合はHTTPのルーティングに特化した表記法と考えることができます。こうすることで直感的で読みやすいコードを作ることができます。

Spark Frameworkとは

SparkはJavaで実装されたマイクロフレームワークです。Java8構文に従うことで少ない労力でWebアプリケーションを開発できます。

http://sparkjava.com/

ドキュメントが充実しているので、APIの使い方を一通り見ておくと良いでしょう。

http://sparkjava.com/documentation.html

チュートリアル1 - Hello World

ここではMavenでプロジェクトを作成し、Sparkフレームワークを使ったWebアプリケーションを作成します。また、Eclipseで開発できるようにプロジェクトをカスタマイズします。

まずはMavenでプロジェクトの雛形を作成します。グループIDはcom.example、アーティファクトIDはspark-appとしています。

$ mvn archetype:generate -DgroupId=com.example -DartifactId=spark-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

プロジェクトが作成されたら、cd spark-appを忘れないように実行しておきましょう。

次にpom.xmlを開いて、タグの中に以下の定義(Sparkフレームワーク)を追記します。

<dependency>
  <groupId>com.sparkjava</groupId>
  <artifactId>spark-core</artifactId>
  <version>2.5</version>
</dependency>

次にEclipseで作業できるように以下のmvnコマンドを実行します。

$ mvn eclipse:eclipse

これはeclipseプラグインeclipseゴールを指定しています。mvnコマンドにはフェーズやゴールといった引数を指定できます。mvn eclipse:eclipseによってEclipseプロジェクトとして必要な設定ファイルが作成できます。

mvn eclipse:cleanコマンドを実行すると設定ファイルを削除できます。

次にApp.javaを開いて、以下のように処理を実装します。import static spark.Spark.*; の指定を忘れないようにしてください。

package com.example.app;

import static spark.Spark.*;

import spark.Request;
import spark.Response;
import spark.Route;

public class App {
    public static void main(String[] args) {
        get("/hello", new Route() {
            @Override
            public Object handle(Request req, Response resp) throws Exception {
                return "Hello World";
            }
        });
    }
}

完成したApp.javaを実行します。サーバが起動するので以下のURLにアクセスします。

http://localhost:4567/hello

ブラウザに"Hello World"が表示されればOKです。

結果を確認したらApp.javaを忘れずに停止しておきましょう。Consoleビューの赤いボタンを押すとApp.javaは停止します。標準ではTomcatのようにオートリロードされないので、コードを修正する度にプログラムを再起動する必要があります。

チュートリアル2 - リクエストパラメータの表示

mainメソッドの中に2つ目のルーティング( "/echo")を追加してみましょう。ここではクエリパラメータではなく、URLの一部をリクエストパラメータと受け取るようにしています。

       get("/echo/:name", new Route() {
            @Override
            public Object handle(Request req, Response resp) throws Exception {
                return "echo:" + req.params("name");
            }
        });

完成したApp.javaを実行します。サーバが起動するので以下のURLにアクセスします。

http://localhost:4567/echo/Hello

ブラウザに"echo:Hello"が表示されればOKです。

チュートリアル3 - ビューテンプレートの使用

Thymeleafのインストール

Sparkは様々なビューテンプレートをサポートしています。ここではSpringでよく利用されるThymeleafを使ってみます。Thymeleafの詳細な情報については以下の公式サイトを参考にしてください。

SparkフレームワークからThymeleafを利用するためにMavenのpom.xmlに以下のライブラリを追記します。Sparkフレームワークの定義の下に追記すると良いでしょう。

<dependency>
  <groupId>com.sparkjava</groupId>
  <artifactId>spark-template-thymeleaf</artifactId>
  <version>2.3</version>
</dependency>

それからThymeleafのビューテンプレートを配置するディレクトリを作成しておきましょう。アプリケーションのルートフォルダで以下のコマンドを実行します。

$ mkdir -p src/main/resources/template

Windowsの場合は mkdir src¥main¥resources¥templateとしてください。

次にEclipseに更新を反映するために以下のmvnコマンドを実行します。

$ mvn eclipse:eclipse

その後Eclipse側でプロジェクトを選択して、Refreshを選択してください。thymeleafが反映されます。

Thymeleafを使うには

mainメソッドの中に3つ目のルーティング( "/books")を追加してみましょう。/booksではshopNameとbooksをモデルとして作成し、ビューにレンダリングします。

       get("/books", new TemplateViewRoute() {
            @Override
            public ModelAndView handle(Request req, Response resp) throws Exception {
                Map<String, Object> model = new HashMap<String, Object>();
                model.put("shopName", "MyBookStore");

                List<String> books = Arrays.asList("Java Book", "PHP Book", "Ruby Book");
                model.put("books", books);
                return new ModelAndView(model, "index");
            }
        }, new ThymeleafTemplateEngine());

getメソッドの第2引数にはこれまでのRouteインタフェースではなく、TemplateViewRouteインタフェースを指定している点に注意してください。TemplateViewRouteインタフェースは戻り値にModelAndViewオブジェクトを返却します。ModelAndViewにはバインドするモデルと、レンダリングするビューのプレフィックスを指定します。プレフィックスには"index"を指定しているので、src/main/resources/templatesディレクトリにあるindex.htmlが呼び出されるようになります。

ビューテンプレート(index.html)の作成

Thymeleafのビューテンプレートindex.htmlを作成します。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>BookStore</title>
</head>
<body>
    <h1 th:text="${shopName}">BookStore</h1>
    <ul>
        <li th:each="book : ${books}" th:text="${book}">hoge</li>
    </ul>
</body>
</html>

ThymeleafはXMLのバリデーションが働くのでタグの閉じ忘れに注意してください。

それではApp.javaを実行します。サーバが起動するので以下のURLにアクセスします。

http://localhost:4567/books/

ショップタイトルと本の一覧が表示されればOKです。

例外処理について

ビューファイル名を間違えたり、Thymeleafで正しくデータバインディング出来ない場合は例外が発生します。標準では例外のスタックトレースが表示されないので、ルーティング定義の後に、以下のように例外ハンドラを定義しておきましょう。

    exception(Exception.class, new ExceptionHandler() {
      @Override
      public void handle(Exception e, Request req, Response resp) {
        e.printStackTrace();
        resp.status(200);
        resp.body(e.getMessage());
      }
    });

これでルーティングの中で例外が発生しても、コンソールにスタックトレースが表示されるようになります。

App.java

ここまでのApp.javaプログラムを再掲しておきます。

package com.mycompany.app;

import static spark.Spark.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import spark.ExceptionHandler;
import spark.ModelAndView;
import spark.Request;
import spark.Response;
import spark.Route;
import spark.TemplateViewRoute;
import spark.template.thymeleaf.ThymeleafTemplateEngine;

public class App {
    public static void main(String[] args) {

        get("/hello", new Route() {
            @Override
            public Object handle(Request req, Response resp) throws Exception {
                return "Hello World";
            }
        });

        get("/echo/:name", new Route() {
            @Override
            public Object handle(Request req, Response resp) throws Exception {
                return "echo:" + req.params("name");
            }
        });

        get("/books", new TemplateViewRoute() {
            @Override
            public ModelAndView handle(Request req, Response resp) throws Exception {
                Map<String, Object> model = new HashMap<String, Object>();
                model.put("shopName", "MyBookStore");

                List<String> books = Arrays.asList("Java Book", "PHP Book", "Ruby Book");
                model.put("books", books);
                return new ModelAndView(model, "index");
            }
        }, new ThymeleafTemplateEngine());

        exception(Exception.class, new ExceptionHandler() {
            @Override
            public void handle(Exception e, Request req, Response resp) {
                e.printStackTrace();
                resp.status(200);
                resp.body(e.getMessage());
            }
        });
    }
}

マイクロフレームワーク、シンプルでいいですね。

Java8で書く

もっとシンプルです。

package com.mycompany.app;

import static spark.Spark.exception;
import static spark.Spark.get;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import spark.ModelAndView;
import spark.template.thymeleaf.ThymeleafTemplateEngine;

public class App {
    public static void main(String[] args) {

        get("/hello", (req, resp) -> "Hello World");

        get("/echo/:name", (req, resp) -> "echo:" + req.params("name"));

        get("/books", (req, resp) -> {
            Map<String, Object> model = new HashMap<String, Object>();
            model.put("shopName", "MyBookStore");
            List<String> books = Arrays.asList("Java Book", "PHP Book", "Ruby Book");
            model.put("books", books);
            return new ModelAndView(model, "index");
        } , new ThymeleafTemplateEngine());

        exception(Exception.class, (e, req, resp) -> {
            e.printStackTrace();
            resp.status(200);
            resp.body(e.getMessage());
        });
    }
}

Java8いいですね。

プログラミング習得の法則

もうすぐ6月も終わります。この記事は4月から入社して、新入社員研修で初めてプログラミングを学んだ方へのメッセージです。

はじめに

プログラミングメンター(指導者)の仕事をしていると、プログラミングの習得には3つのステップがあることに気づきます。

  1. 覚える
  2. わかる
  3. できる

ここでは覚える、わかる、できるの違いについて考察します。

ステップ1:覚える

初めてプログラムを学ぶとき、変数や配列、if文やfor文といった様々なルールを覚えないといけません。プログラミング習得の第1歩は「覚える」ところからスタートします。たとえばJavaプログラミングを習得する場合は、次のようなプログラムを書くところから始まります。

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

これはHello Worldプログラムという有名なものですが、学び始めの頃は、publicってなんだろう、staticってなんだろう、voidって?という状態です。最初の頃は一つひとつのキーワードを無理して読み解こうとせずに、まずはこのとおり書けば動くということを「覚える」ところが大切です。「ぱぶりっくすたてぃっくぼいどめいん・・・」と何度も読んで(ときには復唱して)覚えます。「覚える」ためには読む、聞くといったインプット作業が中心となります。

ステップ2:わかる

コードの書き方を「覚えた」として、次の段階に進むには「覚える」から「わかる」へステップアップする必要があります。それでは「覚える」と「わかる」の違いはどこにあるのでしょうか。

System.out.println("Hello World");

たとえば上記のコードを見て「標準出力へHello World文字列を出力している」とか「Systemクラスのstatic変数outのprintlnメソッドを呼び出している」とか、自分の言葉でコードを解説できる人は「わかっている」人です。「覚える」と「わかる」の違いは単純で、自分の言葉で説明できるかどうかです。

「覚える」から「わかる」の段階へステップアップするためには、覚えたことを自分の言葉で説明する練習をすれば良いのです。自分の言葉で説明する場合、説明した内容を評価してくれる相手が必要です。相談できるメンターや共に学ぶ仲間の身近にいることはプログラミングの理解を深める大事な要素になるでしょう。

自分の言葉で説明するとは、話すという動作に限定するわけではありません。このブログのように文章にしてみるのも、自分の言葉で説明する練習の一つです。自分の言葉で説明するとは覚えた知識をアウトプットすることなのです。

ステップ3:できる

野球やサッカーのようなスポーツ、ピアノやギターといった楽器もそうですが「わかる」と「できる」は別物です。ギターを例に考えてみましょう。6本の弦を左手で抑えて、右手で弦を弾けば音が鳴る、というのは多くの人がわかります。またCコードやGコードといったコード進行について理解のある方もいるでしょう。しかし、当たり前のことですが、ギターの弾き方はわかったとしても、必ずしも上手にギターが弾けるというわけではありません。

プログラミングの世界においても同じで「わかる」と「できる」は大きく違います。プログラムの「わかる」人はコミュニケーションに参加することはできますが、コンピュータに向き合ってプログラミングできるとは限りません。

「わかる」から「できる」の段階へステップアップするには実践練習しかありません。つまり、コンピュータに向き合って、キーボードをタイプしてプログラミングする時間を作らなければなりません。「できる」ようになるためには疲れやストレスを伴う反復作業が必要なのです。これは楽器やスポーツの習得と同じです。繰り返しますが大事なのは反復練習です。プログラミングの習得の早い人の多くは、学んだことを丁寧に復習する人です。

「僕はスポーツの練習じゃなくて”武井壮を動かす練習”をしている」

武井壮さんの「僕はスポーツの練習じゃなくて”武井壮を動かす練習”をしている」という話があります。

matome.naver.jp

プログラミングも同じで、頭の中でイメージしていることと、実際に体を使って表現することの間には乖離があって、それを埋めるためには質の良い練習をしないといけません。

「1万回のキックを1度だけ練習したものを恐れないが、ひとつのキックを1万回練習したものを恐れる」

ブルース・リーの名言も引用しておきます。

kyouki.hatenablog.com

プログラミングの反復練習は丁寧に時間をかけてやりましょう。練習の前にコーヒーを準備しておくくらいの余裕があると良いかもしれません。

ブルームの法則

教育の世界には「ブルーム分類学」というものがあります。

www.sidaiigakubu.com

ブルームの6分類法とは、技術を人間が習得するときの、人間の発達段階と、各段階における学習目標を示したものです。レベルの低い方から、記憶、理解、応用、分析、評価、創造、と続きますが、基本的に学習は、この順序で、各段階の目標をクリアしながら進めていかなくてはいけません。

ブルーム分類学では6つのレベルで学習目標を分類しています。

  • 記憶
  • 理解
  • 応用
  • 分析
  • 評価
  • 創造

個人的にはこの分類はよくできている(凄い)と思っています。ただ初見だと、難解に感じるので、6段階の上位3つ(分析、評価、創造)はひとまず置いておいて、最初の3つ(記憶、理解、応用)だけに絞って学習目標を考えるとわかりやすいです。

プログラミング習得の法則

まとめです。プログラミング習得の法則は3つのステップを踏みます。ブルームの分類も添えておきましょう。

  1. 覚える(=記憶)
  2. わかる(=理解)
  3. できる(=応用)

各ステップでの具体的な学習方法を織り交ぜると次のようになります。

  1. 覚える(記憶)
    • 読む、聞くといったインプット作業
  2. わかる(理解)
    • 自分の言葉でアウトプットする練習
  3. できる(応用)
    • 手を動かしてアウトプットする練習

本を読んでいるだけではプログラミングはできるようになりません。学んだことを言葉やコードにしてアウトプットする習慣を身につけましょう。

そのためにはGitHubのようなサービスを上手く使えるようになるといいですよ。

Vagrant+VirtualBoxでUbuntuを起動する

何をするのか

WindowsとかMacの中でLinuxUbuntu)を起動します。

Linuxの学習にもオススメです。

手順

  1. VirtualBoxをインストール
  2. Vagrantをインストール
  3. ターミナルソフトをインストール(Windowsのみ)
  4. VagrantUbuntuを起動する
  5. Ubuntuに接続する

1. VirtualBoxのインストール

VirtualBoxORACLEの提供する仮想化ソフトです。VirtualBoxを使えば現在実行中のOSの上で、別のOSを起動できます。たとえば、Macの中でLinuxを動かすみたいなことができます。

ちなみにパソコンに直接インストールしているOSをホストOS、仮想化ソフトによってインストールされたOSをゲストOSと呼びます。

VirtualBoxは以下のサイトからダウンロードします。

https://www.virtualbox.org/

インストールはウィザードに従って進めましょう。

2. Vagrantをインストール

VagrantVirtualboxのような仮想化ソフトを便利に扱うためのツールです。Vagrantをインストールすると、コマンドライン上でvagrantコマンドを利用できるようになります。vagrantコマンドを使えばゲストOSイメージのダウンロード、ゲストOSの起動、ゲストOSへの接続といったあらゆる操作を実行できます。

Vagrantは以下のサイトからダウンロードします。

https://www.vagrantup.com/

こちらもインストールはウィザードに従って進めましょう。

3. ターミナルソフトをインストール(Windowsのみ)

ターミナルソフトというのは、ホストOSからゲストOSに接続するためのものです。WindowsだとPuttyか何かで良いと思います。

http://ice.hotmint.com/putty/

4. VagrantUbuntuを起動する

VagrantはゲストOSのイメージをBoxという単位で扱います。UbuntuCentOSといった一般的なBoxは以下のWebサイト(Vagrantbox.es)で公開されているので利用すると便利です。

http://www.vagrantbox.es/

ここでは以下の手順でゲストOS(Ubuntu)を起動します。

  • VagrantにBoxを追加する
  • Vagrantfileを作成する
  • VagrantfileからゲストOSを起動する

VagrantにBoxを追加する

Vagrantbox.esで公開されているUbuntu(Precise 32bit)を追加します。

$ vagrant box add precise32 http://files.vagrantup.com/precise32.box

Boxのダウンロードには数分かかります。ダウンロード完了後、追加済みのBox一覧を確認できます。

$ vagrant box list

Vagrantfileを作成する

任意のフォルダに移動してVagrantfileを作成します。

$ vagrant init precise32

上記のコマンドでカレントフォルダにVagrantfileが生成されます。

VagrantfileからゲストOSを起動する

Vagrantfileが作成できたので、Ubuntuを起動してみましょう。

$ vagrant up

数秒待つとUbuntuが起動します。

5. Ubuntuに接続する

Macの場合はターミナル上で以下のコマンドを実行します。

$ vagrant ssh

Windowsの場合は、Puttyを起動して以下のとおり接続します。

  • 接続先ホスト・・・127.0.0.1
  • 接続先ポート・・・2222
  • ユーザ名・・・vagrant
  • パスワード・・・vagrant

ゲストOSを停止するには

ホストOS上で次のコマンドを実行します。

$ vagrant halt

EC2にDockerをインストールしてRedmineを起動する

EC2(Amazon Linux)上にDockerをインストールして、Redmineを起動するまでのまとめです。たぶん9分くらいでできます。

Dockerのインストール

詳細は公式のドキュメントに載ってるとおり。

Docker Basics - Amazon EC2 Container Service

要点だけ抜粋すると、

yumのアップデートして

[ec2-user ~]$ sudo yum update -y

Dockerのインストールして

[ec2-user ~]$ sudo yum install -y docker

Dockerサービスを起動する。

[ec2-user ~]$ sudo service docker start
Starting cgconfig service:                                 [  OK  ]
Starting docker:                                       [  OK  ]

あとはec2-userをdockerグループに追加すればOK。

[ec2-user ~]$ sudo usermod -a -G docker ec2-user

ここで一旦ログアウトして再度ログインする。先のグループ権限を有効にするため。

docker infoコマンドでインストールを確認する。

[ec2-user ~]$ docker info
Containers: 2
Images: 24
Storage Driver: devicemapper
 Pool Name: docker-202:1-263460-pool
 Pool Blocksize: 65.54 kB
 Data file: /var/lib/docker/devicemapper/devicemapper/data
 Metadata file: /var/lib/docker/devicemapper/devicemapper/metadata
 Data Space Used: 702.3 MB
 Data Space Total: 107.4 GB
 Metadata Space Used: 1.864 MB
 Metadata Space Total: 2.147 GB
 Library Version: 1.02.89-RHEL6 (2014-09-01)
Execution Driver: native-0.2
Kernel Version: 3.14.27-25.47.amzn1.x86_64
Operating System: Amazon Linux AMI 2014.09

以上でDockerのインストールは完了です。

Redmineのインストール

DockerでRedmineをインストールします。ここではDocker Hubで公開されているsameersbn/redmineにお世話になります。

docker pull sameersbn/redmine:latest
docker pull sameersbn/mysql:latest
docker run --name=mysql -d -e 'DB_NAME=redmine_production' -e 'DB_USER=redmine' -e 'DB_PASS=password' sameersbn/mysql:latest
docker run --name=redmine -it --rm -p 80:80 --link mysql:mysql  sameersbn/redmine:latest

ここではMySQLRedmineのDockerイメージをPULLしています(これには数分かかります)。

Dockerコンテナが起動した後、ブラウザでアクセスすると初期アカウントadmin / adminでログインできます。

EC2の起動時間は無視するとここまでで4分くらい。EC2の起動時間合わせても9分でいける、、かな。

バックグラウンドでRedmineを起動するには

先のやり方だと、Redmineのコンテナがフォアグラウンドで起動しているので、バックグラウンドで起動したい場合は、一回止めて次のように起動します。

docker run --name=redmine -d -p 80:80 --link mysql:mysql  sameersbn/redmine:latest

起動中のコンテナの中でごにょごにょしたい場合はdocker execコマンドを使います。

docker exec -it redmine /bin/bash

参考

Dockerエキスパート養成読本[活用の基礎と実践ノウハウ満載!]

Dockerエキスパート養成読本[活用の基礎と実践ノウハウ満載!]

CronでMySQLのデータをエクスポートしてS3にアップするまで

MySQLのデータをエクスポートしたいで。

qiita.com

Cronの登録するで。

www.server-memo.net

たしかcrontabコマンドは気をつけなあかんで。

d.hatena.ne.jp

Cronで動いたで。でもダンプファイルあれへんで。。

qiita.com

次はコマンドラインでS3にアップしたいで。

s3tools.org

s3cmdよさそうやで。使い方調べるで。

d.hatena.ne.jp

シークレットキーがいるやで。

awsのs3を操作する為のaccess keyとsecret keyを取得する(IAM)

、、とりあえず警告は/dev/nullや、、で。

いい加減、>/dev/null 2>&1と書くのをやめたらどうか (追記あり) · DQNEO起業日記

ApacheでLaravel5アプリケーションを動かす

Laravel5を使ったWebアプリケーションを開発しています。

開発時はビルトインサーバで開発して、運用時はApacheでということがやりたかったのでまとめです。OSにはUbuntu14.04を使っています。Vagrantも使ってます。

開発時 - ビルトインサーバの起動

開発時はphp artisan serveでビルトインサーバを使ってます。手軽で便利です。

$ php artisan serve

IP指定で起動することもできます。

$ php artisan serv --host 192.168.33.10

運用時 - Apacheで動かすには

基本的にはApacheでLaravelアプリケーションのバーチャルホストを定義するだけ。Laravelアプリケーションのpublicフォルダをドキュメントルートに指定します。

バーチャルホストの定義

Apacheにバーチャルホストの定義を追加します。

Ubuntu14の場合、/etc/apache2/sites-available/ディレクトリにバーチャルホストの定義ファイルを新規作成します。

ここでは/etc/apache2/sites-available/myapp.confを次のように作成します。

<VirtualHost *:80>
        ServerName myapp.com
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/myapp/public

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        <Directory "/var/www/myapp/public">
                AllowOverride All
        </Directory>
</VirtualHost>

ここでは/var/www/myapp/にLaravelアプリケーションが存在するものとしています。

DocumentRootにはmyapp/publicを指定します。 またLaravelは.htaccessを利用するので、myapp/publicディレクトリにAllowOverride Allを定義します。

/etc/apache2/sites-available/に作成したmyapp.confはこのままではApacheにロードされません。そのため、/etc/apache2/sites-enabled/からシンボリックリンクを定義します。

$ cd /etc/apache2/sites-enabled/
$ sudo ln -s ../sites-available/myapp.conf

変更を反映するためにApacheを再起動します。

$ sudo service apache2 restart

Apacheの実行ユーザを変更する

Vagrant上で開発している場合は、Apacheの実行ユーザをvagrantに変更します。Ubuntu14のApacheにはenvvarsというファイルで実行ユーザを変更できます。envvarsは環境変数を管理するファイルのようです。

$ sudo vi /etc/apache2/envvars

envvarsを以下のとおり変更します。

export APACHE_RUN_USER=vagrant
export APACHE_RUN_GROUP=vagrant

.htaccessが機能しない問題

Laravelアプリケーションにはpublicディレクトリ下に.htaccessファイルが用意されています。.htaccessファイルではmod_rewriteによるURLの書き換えが定義されています。

ちょっとはまったのは、デフォルトのApacheだとmod_rewriteが無効になっていた件です。そのため、URLの書き換えが機能せず、以下のような問題に遭遇しました。

laravel 4 all routes except home result in 404 error

Laravelアプリケーションのルートディレクトリ以外は404になってしまうという。。 このときの現象として、以下のURLでログイン画面にアクセスしたいのに404になってしまいます。

http://myapp.com/auth/login

その代わり、次のURLでアクセスできるという。。index.phpて。。

http://myapp.com/index.php/auth/login

この場合、mod_rewriteを有効にすると解決します。

$ sudo a2enmod rewrite

Apacheの再起動も忘れずに。

$ sudo service apache2 restart

Laravelアプリケーションの設定

Laravelアプリケーションに特に変更箇所はありませんが、Gitのようなバージョン管理システムからコードを取得して来た場合、composer isntallを叩いて、それから.envファイルを作成する必要があります。

$ composer install

このへんは必要に応じてsudoするかんじで。

$ cp .env.example .env

.envに定義するAPP_KEYはphp artisan key:generateで取得できます。

$ php artisan key:generate
Application key [0wzwvICe8xa0vJ7RUpOM1z7F03NeZxxx] set successfully.

もう1つアプリケーションのログファイルを生成しておく必要がありました。

$ touch /var/www/myapp/storage/logs/laravel.log

これくらいでたぶん動くはず。

補足

Apacheのインストール

$ sudo apt-get install -y apache2

sudo apt-get updateもさきにしとくように。

PHPのインストール

sudo apt-get install php5-common libapache2-mod-php5 php5-cli php5-json php5-sqlite php-pear php5-dev

SQLiteならこんなかんじでいけます。

Composerのインストール

$ curl -sS https://getcomposer.org/installer | php
$ mv composer.phar /usr/local/bin/composer

ログファイル

Apacheのログは/var/logs/apache2/error.log

Laravelのログは/var/www/myapp/storage/logs/laravel.log

DigitalOceanでやるときのメモ

とりあえずDigitalOcean(rootユーザ)で環境構築するときは、Laravelアプリケーションの所有者をApacheユーザ(www-data)にしていおいた。

$ chown -R www-data:www-date /var/www/myapp

Apache 2.4の場合

パーミッションの設定でエラーが出る。

AH01630: client denied by server configuration

stackoverflow.com

<VirtualHost *:80>
        ServerName myapp.com
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/myapp/public

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        <Directory "/var/www/myapp/public">
                AllowOverride All
                Require all granted
        </Directory>
</VirtualHost>

JAX-RS 非同期処理 Asynchronous Services

JAX-RSの非同期処理の実装サンプルです。

参考記事

JerseyのTutorialを参考にしました。

Asynchronous Server API

まずはベタな実装から。こんなふうにするとサーバースレッドはすぐに終わるので再利用できる。

  • @Suspendアノテーションを付与したAsyncResponse引数を用意する
  • 別スレッドの処理終了時にasyncResponse.resume(response);でレスポンスを返す

タイムアウトの設定は次のように実装する。

Asynchronous Server-side Callbacks

非同期処理のコールバックメソッドを作る場合は次のように実装する。

  • asyncResponse.registerでコールバックを登録する
  • このサンプルではCompletionCallbackを実装して登録している
  • CompletionCallbackのonCompleteメソッドはThrowable型の引数を受け取る(正常な場合はnull)

ちょっとだけはまったのが、別スレッドでWebApplicationExceptionのような例外をスローすると引数に代入されないところ。

Interface CompletionCallbackを参照すると、引数Throwableにはunmapped exception instanceが代入される的なことが書いている。

コールバックには以下の2種類がある。

  • CompletionCallback リクエスト終了時に呼び出される(正常でも、異常でも)
  • ConnectionCallback クライアントコネクションがクローズされるときに呼び出される(正常なクローズの場合は呼ばれない)