saba1024のブログ

どうでも良い思いついた事とかプログラミング関係のメモとか書いていきます。

Groovyでお手軽にカレンダーを生成する

Groovyだと、2つの日付同士のレンジが..で生成できるので非常にお手軽にカレンダーを生成することが出来ます。
うるう年もちゃんと考慮されるので、例えば以下のようなことが出来ます。

String df = "yyyy/MM/dd"

// うるう年のある年
Date date1 = Date.parse(df, "2016/01/01")
Date date2 = Date.parse(df, "2016/12/31")

assert (date1..date2).size() == 366
List<Date> february2016 = (date1..date2).findAll {it.getAt(Calendar.MONTH) == 1}
assert february2016.size() == 29
assert february2016.last() == Date.parse(df, "2016/02/29")

// うるう年の無い年
Date date3 = Date.parse(df, "2017/01/01")
Date date4 = Date.parse(df, "2017/12/31")

assert (date3..date4).size() == 365
List<Date> february2017 = (date3..date4).findAll {it.getAt(Calendar.MONTH) == 1}
assert february2017.size() == 28
assert february2017.last() == Date.parse(df, "2017/02/28")

非常にお手軽ですね!

Nginx Unitを試してみた

最近リリースされた、Nginx Unitを試してみました。

インストール

こういう新しいツールを試すのにはやっぱりDockerですね!
とうことで、Docker上にNginx Unitをインストールして見ます。 今回はUbuntuで試してみました。

# デタッチモードで起動
sudo docker run -itd --name nginx-unit -p 8300:8300 ubuntu
# Dockerコンテナにアクセス
sudo docker exec -it nginx-unit bash

Dockerコンテナにアクセスできたら、まず必要なライブラリをインストールします。

root@88e2db6c51bc:~#apt-get update
root@88e2db6c51bc:~#apt-get install -y wget vim

インストールが完了したら、基本的には公式リポジトリのREADME通りに勧めるだけでOKです。
なお、今回はソースからコンパイルではなく、apt-getを使ってNginx Unitをインストールします。

root@88e2db6c51bc:~# cd
root@88e2db6c51bc:~# wget http://nginx.org/keys/nginx_signing.key
root@88e2db6c51bc:~# apt-key add nginx_signing.key
OK

/etc/apt/sources.listに以下の2行を追加。

deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx

これで準備完了です。後はapt-getで普通にインストールできます。

apt-get update
apt-get install unit

起動と停止

service unitd [start | stop]

Unit の設定

READMEを読む限り、全てAPI経由でNginxUnitサーバの設定を行うようです。
ということで、先ずはNginx Unitを起動しておきます。

service united start

Unitdが起動したら、以下のコマンドを実行してみます。

root@88e2db6c51bc:/var/lock# curl --unix-socket /run/control.unit.sock http://localhost/
{
        "listeners": {},
        "applications": {}
}

何やら空の値が帰ってきました。
NginxUnitでは、listenersapplicationsという2つの重要な概念があるようです。
指定したIPとPortにアクセスが来たら、指定したapplicationを実行する、という指定のlisteners、そして実際のアプリケーションの登録を行うapplications

API経由でJSONファイルを渡してあげることで登録できます。
まず、READMEにある通り、以下の内容のファイルを用意します。(とりあえずstart.jsonという名前にしましたが、何でもOKです)

{
     "listeners": {
         "*:8300": {
             "application": "blogs"
         }
     },
     "applications": {
         "blogs": {
             "type": "php",
              "workers": 20,
              "root": "/www/blogs/scripts",
              "index": "index.php"
         }
     }
}

そしてこれをlocalhostに投げてあげます。

root@88e2db6c51bc:~# curl -X PUT -d @/root/start.json --unix-socket /run/control.unit.sock http://localhost/                                                                                                                                                                    
{
        "success": "Reconfiguration done."
}

これで完了です。
実際に再度以下のように確認してみると、何やら値が設定されていることが確認できます。

root@88e2db6c51bc:~# curl --unix-socket /run/control.unit.sock http://localhost/
{
        "listeners": {
                "*:8300": {
                        "application": "blogs"
                }
        },

        "applications": {
                "blogs": {
                        "type": "php",
                        "workers": 20,
                        "root": "/www/blogs/scripts",
                        "index": "index.php"
                }
        }
}
root@88e2db6c51bc:~# 

PHP

では実際にコードを設置してみましょう!
先ほどのstart.jsonで、実は既にドキュメントルートを指定しています。(/www/blogs/scripts)
なので、先ずはそのディレクトリを作成します。

root@88e2db6c51bc:~# mkdir -p /www/blogs/scripts

で、後はそのディレクトリにPHPファイルを設置するだけでOKです。

echo "<?php phpinfo();?>" > /www/blogs/scripts/index.php

これで、http://localhost:8300/にアクセスすればphpinfoが表示されるはずです。

ちなみに、unitのインストールで必要なPHPのライブラリはインストールされているっぽい。楽ちん。

root@88e2db6c51bc:~# dpkg -l | grep php
ii  libphp-embed               1:7.0+35ubuntu6                       all          HTML-embedded scripting language (Embedded SAPI library) (default)
ii  libphp7.0-embed            7.0.22-0ubuntu0.16.04.1               amd64        HTML-embedded scripting language (Embedded SAPI library)
ii  php-common                 1:35ubuntu6                           all          Common files for PHP packages
ii  php7.0-common              7.0.22-0ubuntu0.16.04.1               amd64        documentation, examples and common module for PHP
root@88e2db6c51bc:~# 

感想

簡単に実行できました。
ただ、今一メリットを理解できていないのが現状です。
一つのNginx Unitさえあれば、一つのunitedプロセスで、様々な言語(現在だとPHP、Go、Pythonがサポートされている。そのうちJavaとNode.jsも予定)で実装されたサービスを動かせる、という特徴はありますが、これも今までそれぞれが持っていたアプリケーションサーバから乗り換えるほどのメリットなのか?と言われると微妙な感じです。
ただ、今回のPHPだけで考えてみると、今まではNginx(Web)とPHP-FPM(App)を別々の物として管理運用していましたが、Nginx Unitだとコレが一つに纏まる訳なので、運用面でのそういったメリットは確かにあるのかな?とぼんやりと考えています。

まぁとは言っても今回のようにPHPだけではなくて、他の言語のアプリケーションも同時にNginx Unit上で実際に運用してみて初めてメリットが見えてくるような気もしますので、Javaが正式にサポートされたタイミングなどで再度試してみて、また色々確認してみようかなと思います。

Rust 1.20の新機能 Associated constants(関連定数)

概要

最近Rustの勉強をしています。
今まで基本的にJavaApache Groovy畑で生きてきたので新しい概念が多くかなり苦戦しています。
さて、8月31日にRust1.20がリリースされました。この新しいバージョンでAssociated constants(関連定数)という機能が追加されました。
ざっくり言うと、既にあったAssociated functions(関連関数)の定数版という感じのようです。
まだまだ自身を持ってどうこう言えるほどRustを理解できているわけではありませんが、実際に使ってみたので以下のそのサンプルコードを示します。

実際のコード

// Associated constants with struct
struct Struct;
impl Struct {
    // associated constants
    const PI:f32 = 3.14;

    fn double(&self) -> f32 {
        Self::PI * 2.0
    }
}

// Associated constants with trait.
trait Trait {
    // associated constants
    const PI:f32;

    fn double(&self) -> f32 {
        Self::PI
    }
}
struct Circle;
impl Trait for Circle {
    const PI:f32 = 3.14;
}

fn const_via_associated_constants<T: Trait>(_v: T) -> f32 {
    T::PI * 2.0
}

fn const_via_method <T: Trait>(v: T) -> f32 {
    v.double() * 2.0
}

fn main () {
    // use direct
    debug_assert!(Struct::PI == 3.14);
    debug_assert!(Circle::PI == 3.14);

    // use direct via method.
    debug_assert!(Struct{}.double() == 6.28);

    // use from a function(direct and via method)
    debug_assert!(const_via_associated_constants(Circle{}) == 6.28);
    debug_assert!(const_via_method(Circle{}) == 6.28);

    println!("Rust 1.20");
}

参考

Announcing Rust 1.20

GroovyのGrape/Grabでdownload failedが発生する際の対処方法

Groovyでは、プログラムの実行時にMavenリポジトリから自動的に依存ライブラリのダウンロード、クラスパスの設定まで行ってくれるGrape/Grabという機能が有ります。 使い方自体は簡単で、コードの先頭に、

@Grab('org.apache.solr:solr-core:6.6.0')
@Grab('org.slf4j:slf4j-simple:1.7.12')
@Grab('commons-codec:commons-codec:1.10')

もしくは、

@Grapes([
    @Grab(group='commons-codec', module='commons-codec', version='1.10'),
    @Grab(group='org.slf4j', module='slf4j-simple', version='1.7.12', scope='test'),
    @Grab(group='org.apache.solr', module='solr-core', version='6.6.0')
])

のような指定をするだけです。
基本的にこれだけで問題ないのですが、時々以下のようなエラーに遭遇することが有ります。

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during conversion: Error grabbing Grapes -- [download failed: commons-codec#commons-codec;1.10!commons-codec.jar, download failed: org.slf4j#slf4j-api;1.7.7!slf4j-api.jar]

java.lang.RuntimeException: Error grabbing Grapes -- [download failed: commons-codec#commons-codec;1.10!commons-codec.jar, download failed: org.slf4j#slf4j-api;1.7.7!slf4j-api.jar]
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
...Stacktraceが続く

ダウンロードが出来ない、ということなのですが、一体なぜなのか。。。
対処療法的ではありますが、手動でjarをダウンロードすることでこの問題を回避することが出来ます。
手動ダウンロードかよ。。。という話ではありますが、classpathを通さなくても良いのでGroovyConsoleなどから簡単にこれらの外部依存ライブラリを扱うことが出来る点は変わりありません。

標準だと、Grape/Grabでダウンロードされるjarファイル群は全て$HOME/.groovy/grapes配下に保存されます。
更にそのディレクトリから、グループ名/モジュール名 というディレクトリ構成になっています。
今回問題になっている、commons-codec1.10なので実際にそのディレクトリを見てみます。

[koji:~]$ cd $HOME/.groovy/grapes/commons-codec/commons-codec
[koji:commons-codec]$ ls -al
合計 40
drwxr-xr-x 3 koji koji  4096 Aug  2 14:47 .
drwxr-xr-x 3 koji koji  4096 Aug  2 14:34 ..
-rw-r--r-- 1 koji koji 10943 Jan  2  2017 ivy-1.10.xml
-rw-r--r-- 1 koji koji 11609 Jan  2  2017 ivy-1.10.xml.original
-rw-r--r-- 1 koji koji   873 Aug  2 14:39 ivydata-1.10.properties
[koji:commons-codec]$ 

何やらファイルが存在しています。
本来は、このディレクトリにjarsというディレクトリが有り、その中に実際のjarファイルが格納されています。(本来はGrape/Grabによってダウンロードされる)
それが存在していないので、手動でjarsを作って、Mavenリポジトリからjarを手動でダウンロードしてきます。

[koji:commons-codec]$ mkdir jars
[koji:commons-codec]$ wget http://central.maven.org/maven2/commons-codec/commons-codec/1.10/commons-codec-1.10.jar -P jars

これで完了です。 エラーになっていたGrape/Grabを使っているコードを再度実行すれば問題なく実行されるようになっています。

Groovyで簡単なS式をパースするサンプル

無駄に複雑なコードになってしまっていますが、一応四則演算は実行できるS式のパーサが出来ました。
間違いなくもっとスマートな方法があるはずですが(普通にググれば10行程度でRubyで実装している方もいらっやいます)、今まで基本的にWebプログラミングしかして来なかったのでこういったパーサ系なんかも全部ライブラリ任せにしてしまってたので、まずは自分のための勉強ということで。。。
これを機会にこういったまた普段とは違ったコードも書いていきたいなと思います。

trait Parser {

    Closure findLastList = {l, next ->
        if (next <= 0 || l == [] || !(l instanceof List)) {
            l
        } else {
            call(l.last(), next-1)
        }
    }

    Closure normalizeSpaces = {String str ->
        str = str.replaceAll(/\(/, ' ( ')
        str = str.replaceAll(/\)/, ' ) ')
        // 先頭か末尾にスペースがある場合は1個に正規化されるけど必要ないのでtim()
        str.replaceAll(/\s{1,}/, ' ' ).trim()
    }
    
    Closure parseToList = {List<String> tokens ->
        List list = []
        Integer nest = 0
        tokens.each {
            if (it == '(') {
                if (nest > 1) {
                    findLastList(list, nest-1) << []
                } else {
                    list << []
                }
            nest++
            } else if (it == ')') {
                nest--
            } else {
                if (nest > 1) {
                    findLastList(list, nest-1) << it
                } else {
                    list << it
                }
            }
        }
        list.tail()
    }
    
    List<String> parse(String sourceCode) {
        String normalized = normalizeSpaces(sourceCode)
        List<String>tokens = normalized.tokenize(' ')
        parseToList(tokens)
    }
}

class MyLanguage implements Parser {

    Map operations = [
        '+': {a, b -> (a as Integer) + (b as Integer)},
        '-': {a, b -> (a as Integer) - (b as Integer)},
        '*': {a, b -> (a as Integer) * (b as Integer)},
        '/': {a, b -> (a as Integer) / (b as Integer)},
    ]
    
    Closure execute = {List code -> 
    
        String operation = code.head()
        List<String> operands = code.tail()

        def o = []
        for (obj in operands) {
            o << ( (obj instanceof List) ? call(obj) : obj )
        }
        o.inject(operations[operation])
    }

    def eval(String sourceCode) {
        List<String> parsedAsList = parse(sourceCode)
        execute(parsedAsList)
    }
}

// 以下のように、ネストしたS式でもちゃんと四則演算ができている。
def engine = new MyLanguage()
assert engine.eval('( + 1 2 3 ( - 1 2 ) )') == 5
assert engine.eval('(* 3 5)') == 15
assert engine.eval('(/ 20 2 5)') == 2
assert engine.eval('(* 2 (/ 10 5))') == 4
assert engine.eval('(+ (* 2 4) 5)') == 13
assert engine.eval('(- (+ (* 2 4) 5 (/ 6 2)) 10)') == 6

Ratpackを使ってJsonBuilderとJsonSlurperを試す

概要

GroovyでJSONを生成する(JsonBuilder)という記事と、 GroovyでJSONをパースする(JsonSlurper) という記事を書きました。

それぞれ独立して紹介しても実際にどういう場合に使うの?というイメージが湧きづらいので、実際にRatpackを使って簡単なWebアプリを作ってみました。
このWebアプリは単純に、localhost:5050に、{id:1, name:koji}というようなJSONをPOSTで送信すると、そのJSONに現在時刻を追加してクライアントにJSONとして返す、というものです。

なお、Ratpackは標準ではJacksonを使ってJSONを取り扱います。
今回はJsonBuilderとJsonSlurperを使ったサンプルを書きたかったので利用していませんが、通常であればRatpackではJacksonを利用します。

RatpackはNonBlockingなWebフレームワークであると共に、とても軽量かつシンプルに処理を記述する事が出来ます。
またGroovyとの相性も抜群で、Groovyをインストールしていれば、以下のコードを適当なファイルに保存して、groovyコマンドで実行すればRatpackが起動してポート番号5050で処理を待ち受けるようになります。

ソース

@Grab('io.ratpack:ratpack-groovy:1.4.5')
@Grab('org.slf4j:slf4j-simple:1.7.12')
import static ratpack.groovy.Groovy.ratpack
import groovy.json.*

ratpack {
    handlers {
       post() {
          it.request.body.then() { 
             // Ratpackのメモ。request.getBody()はPromise<TypedData>を返してくれている 
             assert it instanceof ratpack.http.TypedData
    
             // 受け取ったJSON(テキスト)をJsonSlurperでパース 
             def gotJson = new JsonSlurper().parseText(it.text)
    
             // レスポンス用のJSONをJsonBuilderで生成 
             def json = new JsonBuilder()
             json([id: "${gotJson.id}", name:"${gotJson.name}", accessedDate: new Date().format("yyyy/MM/dd HH:mm:ss")])
    
             // クライアントにJSONを返す
             // 通常Jacksonを利用するのであれば、態々ヘッダーを指定する必要なし
             response.headers.set 'Content-Type', 'application/json'
             render json.toString()
          } 
       }
    }
}

このソースを適当にtest.grovyというような名前で保存して、groovy test.groovyと実行するだけでRatpackが起動します。

実行結果

[koji:~]$ curl -v http://localhost:5050/ -H "Content-type: application/json" -X POST -d '{"id": 1, "name": "koji"}'
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 5050 (#0)
> POST / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:5050
> Accept: */*
> Content-type: application/json
> Content-Length: 25
> 
* upload completely sent off: 25 out of 25 bytes
< HTTP/1.1 200 OK
< Content-Type: application/json
< content-length: 61
< connection: keep-alive
< 
* Connection #0 to host localhost left intact
{"id":"1","name":"koji","accessedDate":"2017/07/05 12:04:28"}%                                                                                                                                                                                                                  [koji:~]$ 

ちゃんとJSONを投げて、JSONを受け取ることが出来ました。
実際にJsonBuilderとJsonSlurperを利用している部分は

// 受け取ったJSON(テキスト)をJsonSlurperでパース 
def gotJson = new JsonSlurper().parseText(it.text)
    
// レスポンス用のJSONをJsonBuilderで生成 
def json = new JsonBuilder()
json([id: "${gotJson.id}", name:"${gotJson.name}", accessedDate: new Date().format("yyyy/MM/dd HH:mm:ss")])

の2行です。とってもシンプルですね。

このように、Groovyを利用することで簡単にJSONを扱うことが出来、さらに巨大なエコシステムを利用してRatpackのような素晴らしいフレームワークもお手軽に利用することが出来ることが分かりますね。

GroovyでJSONをパースする(JsonSlurper)

概要

JsonBuilderを使えば、Groovy上で簡単にJSONを生成できますが、同様に既存のJSON(テキスト)を解析してGroovyで扱う事も非常に簡単です。
そのためには、JsonSlurperというクラスを利用します。

使い方

本当に簡単なので以下のソースを見て頂ければ一目瞭然です。
単純にJsonSlurperインスタンスを生成して、そのparseTextメソッドにJSON(テキスト)を渡してあげるだけです。

import groovy.json.*
String jsonText = '''
{
    "name": "koji",
    "age": 32,
    "hobbies": [
        {
            "id": 1,
            "name": "hobby1"
        },
        {
            "id": 2,
            "name": "hobby2"
        }
    ],
    "address": {
        "country": "japan",
        "city": "tokyo"
    }
}
'''

def json = new JsonSlurper().parseText(jsonText)
assert json.name == 'koji'
assert json.age == 32
assert json.hobbies.size() == 2

// 配列(リスト)に入っている各オブジェクトにも普通にアクセスできる。
assert json.hobbies[0].id == 1
assert json.hobbies[0].name == 'hobby1'
assert json.hobbies[1].id == 2
assert json.hobbies[1].name == 'hobby2'

//オブジェクト内のオブジェクトにも当然普通にアクセスできる。
assert json.address.country == 'japan'
assert json.address.city == 'tokyo'

// 実は各オブジェクトは、Mapでは無くてJSON専用のLazyMapなので注意。
assert json.hobbies[0] instanceof groovy.json.internal.LazyMap
assert json.hobbies[1] instanceof groovy.json.internal.LazyMap
assert json.address instanceof groovy.json.internal.LazyMap

Webアプリを開発する際にWebフレームワークを使うのであれば、通常はリクエストから自動でJSONオブジェクトを生成するライブラリや機能が付属していると思いますので中々JsonSlurper自体を直接触る事はないかも知れませんが、どこかから提供されているデータがJSON形式の場合で、それを使ってサクッと何か計算なりをしたい場合には非常に役立つ機能だと思います。

参考:
JsonSlurper