m_shige1979のときどきITブログ

プログラムの勉強をしながら学習したことや経験したことをぼそぼそと書いていきます

Github(変なおっさんの顔でるので気をつけてね)

https://github.com/mshige1979

Perl6のインストールを試す

環境

vagrantのCentOS6.x

インストール

git clone https://github.com/rakudo/rakudo.git
cd rakudo/
perl Configure.pl --prefix=$HOME/perl6 --gen-moar --gen-nqp --backends=moar
make
make install

バージョン確認

$ $HOME/perl6/bin/perl6 -v
This is perl6 version 2015.07.1-160-gb56d593 built on MoarVM version 2015.07-57-gec051f5
$

環境変数に追加

echo 'export PATH=$PATH:$HOME/perl6/bin' >> ~/.bash_profile
source ~/.bash_profile

サンプル実行

sample.pl
say "hello world";
say "aaaaaaa";

$ perl6 sample1.pl
hello world
aaaaaaa
$

make testはできなかった

[vagrant@localhost rakudo]$ make test
/usr/bin/perl t/harness --moar t/01-sanity t/04-nativecall
Can't locate Test/Harness.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at t/harness line 14.
BEGIN failed--compilation aborted at t/harness line 14.
make: *** [m-coretest] エラー 2
[vagrant@localhost rakudo]$

※なんか必要だったみたいだけどインストールはできたので保留


perl6の構文の書き方はまた別途見てみる

Amon2のチュートリアルをやってみた

最初はチュートリアルから

基本的なデータベースやルーティングなどを理解する意味でやってみる

手順

雛形を作成して必要なモジュールをインストールしておく
amon2-setup.pl BBS
cd BBS/
carton install
sql/sqlite.sql を編集
CREATE TABLE IF NOT EXISTS member (
    id           INTEGER NOT NULL PRIMARY KEY,
    name         VARCHAR(255)
);

CREATE TABLE IF NOT EXISTS sessions (
    id           CHAR(72) PRIMARY KEY,
    session_data TEXT
);

CREATE TABLE IF NOT EXISTS entry (
    entry_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    body varchar(255) not null
);
sqlを実行
sqlite3 db/development.db < sql/sqlite.sql
テーブルを確認
$ sqlite3 db/development.db
SQLite version 3.6.20
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
entry     member    sessions
sqlite> 
config/development.pl で接続用の設定を追加
use File::Spec;
use File::Basename qw(dirname);
my $basedir = File::Spec->rel2abs(File::Spec->catdir(dirname(__FILE__), '..'));
my $dbpath = File::Spec->catfile($basedir, 'db', 'development.db');
+{
    'DBI' => [
        "dbi:SQLite:dbname=$dbpath", '', '',
        +{
            sqlite_unicode => 1,
        }
    ],
};
一度起動する

f:id:m_shige1979:20141221234559p:plain
※hostは’0.0.0.0’にしておく

lib/BBS/DB/Schema.pmにテーブルなどの設定を追加
package BBS::DB::Schema;
use strict;
use warnings;
use utf8;

use Teng::Schema::Declare;

base_row_class 'BBS::DB::Row';

table {
    name 'sessions';
    pk 'id';
    columns qw(session_data);
};

table {
    name 'entry';
    pk 'entry_id';
    columns qw(entry_id body);
};

1;
lib/BBS/Web/Dispatcher.pmでルーティングの設定
package BBS::Web::Dispatcher;
use strict;
use warnings;
use utf8;
use Amon2::Web::Dispatcher::RouterBoom;

any '/' => sub {
    my ($c) = @_;

    my @entries = $c->db->search(
        entry => {
        }, {
            order_by => 'entry_id DESC',
            limit    => 10,
        }
    );
    return $c->render( "index.tx" => { entries => \@entries, } );
};

post '/post' => sub {
    my ($c) = @_;

    if (my $body = $c->req->param('body')) {
        $c->db->insert(
            entry => +{
                body => $body,
            }
        );
    }
    return $c->redirect('/');
};

1;
tmpl/index.txでテンプレートを編集
: cascade "include/layout.tx"

: override content -> {

<form method="post" action="<: uri_for('/post') :>">
    <input type="text" name="body" />
    <input type="submit" value="Send" />
</form>

<ul>
    <: for $entries -> $entry { :>
    <li><: $entry.entry_id :>. <: $entry.body :></li>
    <: } :>
</ul>

: }
実行

f:id:m_shige1979:20141222013553g:plain

所感

よく勉強会とかではamon2で作った的なことを聞いていてMojoliciousで作ったようなことを聞いてみないのでやってみました。
まだ、チュートリアルだけなので実感がわかないけどamon2はいろいろなモジュールをたくさん使用している感じでMojolicousのように1つになっていないのでモジュールを使用する場合はcartonで制限したほうが動かすときに安全な感じがしました。

また、いろいろと触ってみて理解していこう

今日はここまで

perlの復習(map)

なぜかたいていはfor文とか使ってしまう

mapとかも使えたほうがイケてる感じがするので調べ直さないと…

map

配列の一覧を返す機能、値に加工などを行うことも可能

書式
map {
    なんかの処理
} 配列;

※{}で挟まれた部分でなんかの処理を行う
※セミコロンを忘れないようにしないと

例1
#!/usr/bin/env perl

use strict;
use warnings;

my $list = [111, 222, 333, 444, 555];

print "map sample\n";
map {
    print $_ . "\n";
} @{$list};

$ perl sample1.pl 
map sample
111
222
333
444
555
$

※単純に値を出力するサンプル

例2
#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;

my $list = [111, 222, 333, 444, 555];

print "map sample\n";
my @list2 = map {
    "[" . $_ . "]";
} @{$list};

# あえてリファレンスで渡すとかわけわからんことしているような…
print Dumper(\@list2);

$ perl sample1.pl 
map sample
$VAR1 = [
          '[111]',
          '[222]',
          '[333]',
          '[444]',
          '[555]'
        ];
$

※配列に値を返す場合は%を指定することをわすれないようにしないといけない

例3
#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;

my $list = [111, 222, 333, 444, 555, 111, 333];

print "map sample\n";
my %list2 = map {
    $_ => ""
} @{$list};

# あえてリファレンスで渡すとかわけわからんことしているような…
print Dumper(\%list2);

$ perl sample1.pl 
map sample
$VAR1 = {
          '444' => '',
          '555' => '',
          '111' => '',
          '333' => '',
          '222' => ''
        };
$

※なんかちょっと強引だけどハッシュ化することで重複キーを除去とかもできるかもしれない

grepは?

今回はやりませんでした。
私は普通の人より理解が遅いので1つずつやっていかないと混乱してしまう(´・ω・`)

一応こんな感じ
#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;

my $list = [111, 222, 333, 444, 555];

print "map sample\n";
my @list2 = grep {
    if($_ % 2 == 0){
        $_
    }
} @{$list};

# あえてリファレンスで渡すとかわけわからんことしているような…
print Dumper(\@list2);

$ perl sample3.pl 
map sample
$VAR1 = [
          222,
          444
        ];
$

所感

1行で書くのが普通みたいだけどなんか他の情報がまざると間違いやすくなるのでブロック構文のように複数の行にしています。
何度も書けば慣れていくかも…
使いドコロを間違えないようにしないと

perlでtwitterAPI(retweet、favorite)

自分のツイートをリツイートできないことに

やっと気づいた。

だってなんどやっても 403が帰ってくるから…

実装

sample1.pl
#/usr/bin/env perl

use strict;
use warnings;
use Net::Twitter;
use utf8;
use Data::Dumper;
binmode STDOUT, ":utf8";

my $consumer_key = "<consumer_key>";
my $consumer_secret = "<consumer_secret>";
my $access_token = "<access_token>";
my $access_token_secret = "<access_token_secret>";

my $tw_app = Net::Twitter->new({
    traits => [qw/API::RESTv1_1 OAuth /],
    consumer_key => $consumer_key,
    consumer_secret => $consumer_secret,
    access_token => $access_token,
    access_token_secret => $access_token_secret});

if (@ARGV == 2){
    my $text = $ARGV[0];
    my $mode = $ARGV[1];
    my $res;

    if($mode eq "R"){
        $res = $tw_app->retweet($text);
    }else{
        $res = $tw_app->create_favorite($text);
    }

    print Dumper($text);
    print Dumper($res);

}else{
    print "1:id 2:mode('R':retweet, 'F':favorite)\n";
}

引数

引数はツイートの固有のIDらしい
f:id:m_shige1979:20141216233659p:plain

所感

最初、自分にリツイートできなくて戸惑いましたけど、きちんとリツイートできたので良かったです。
この方法と検索やstreamを併用することで自動リツイートBOTなどを作成できそう。
自分自身に通知代わりになにかを送りたいと思っていたのでそろそろやってみるかも

PerlのExport確認

以前


Perlのモジュールでモジュール名とかを省略する - m_shige1979のささやかな抵抗と欲望の日々

exportで勉強したんだけどちょっと忘れそうなので再度学習

とりあえずサンプル

lib/Sample1.pm
package Sample1;

sub test1{
    return "aaa";
}

sub test2{
    return "bbb";
}

sub test3{
    return "ccc";
}

# 忘れがち
1;

※超基本

sample1.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Sample1;

print Sample1::test1() . "\n";
print Sample1::test2() . "\n";
print Sample1::test3() . "\n";

$ perl -Ilib sample1.pl 
aaa
bbb
ccc
$

まあ、普通に出ます。

sample2.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Sample1;

print test1() . "\n";
print test2() . "\n";
print test3() . "\n";

$ perl -Ilib sample2.pl 
Undefined subroutine &main::test1 called at sample2.pl line 8.
$

でません

Exporterを指定

Sample1.pm
package Sample1;

use base qw(Exporter);
our @EXPORT = qw/test1/;
our @EXPORT_OK = qw/test1/;

sub test1{
    return "aaa";
}

sub test2{
    return "bbb";
}

sub test3{
    return "ccc";
}

# 忘れがち
1;

@EXPORT、@EXPORT_OKにサブルーチンを指定


sample2.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Sample1;

print test1() . "\n";
print test2() . "\n";
print test3() . "\n";

$ perl -Ilib sample2.pl 
aaa
Undefined subroutine &main::test2 called at sample2.pl line 9.
$

test1は指定しているので実行できる

EXPORT_TAGSでグループ分け

lib/Sample1.pm
package Sample1;

use base qw(Exporter);
our @EXPORT = qw/test1/;
our @EXPORT_OK = qw/test1 test2/;
our %EXPORT_TAGS = (
    all  => [ @EXPORT, @EXPORT_OK ],
    test3 => [ qw(test3) ]
);

sub test1{
    return "aaa";
}

sub test2{
    return "bbb";
}

sub test3{
    return "ccc";
}

# 忘れがち
1;

EXPORT_TAGSは%定義なので間違わないようにする

sample3.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Sample1 qw(:all);

print test1() . "\n";
print test2() . "\n";
print test3() . "\n";

$ perl -Ilib sample3.pl 
aaa
bbb
Undefined subroutine &main::test3 called at sample3.pl line 10.
$ 

test1とtest2のサブルーチンを取り込める

sample3_2.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Sample1 qw(:test3);

print test3() . "\n";
print test1() . "\n";
print test2() . "\n";

$ perl -Ilib sample3_2.pl 
"test3" is not exported by the Sample1 module
Can't continue after import errors at sample3_2.pl line 6.
BEGIN failed--compilation aborted at sample3_2.pl line 6.
$

test3は@EXPORT_OKに設定していないのでだめ?

使用するサブルーチンを制限したい場合

実際にモジュール名::サブルーチン名で定義できるので厳密には難しいと思いますけど使用するものを一部に制限することは可能らしい

lib/Sample1.pm
package Sample1;

use base qw(Exporter);
our @EXPORT = qw/test1/;
our @EXPORT_OK = qw/test1 test2/;
our %EXPORT_TAGS = (
    all  => [ @EXPORT, @EXPORT_OK ],
    test2 => [ qw(test2) ],
    test3 => [ qw(test3) ]
);

sub test1{
    return "aaa";
}

sub test2{
    return "bbb";
}

sub test3{
    return "ccc";
}

# 忘れがち
1;

※test2を用意、test2は@EXPORT_OKにある

sample3_3.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Sample1 qw(:test2);

print test2() . "\n";
print test1() . "\n";

$ perl -Ilib sample3_3.pl 
bbb
Undefined subroutine &main::test1 called at sample3_3.pl line 9.
$ 

※test1は呼び出し失敗しているよう

所感

ちょっとむずかしいような気がする。なんかプライベートメソッドみたいなことを期待していたけど、モジュール名を指定することで参照できるのであまりそのへんは難しいかも。
なんかモジュールを作るときに役たつかもしれない。


Mojolicious+Angularjsでwebsocket2

なんかStarmanではだめのよう


今更ながら、nginx+Mojolicious+WebSocketでチャットアプリに挑戦 - 鈍足ランナーのIT日記

ここの設定をまるごと拝借して構成

上記の設定を元に設定

nginxの設定ファイル
upstream chat {
     server 127.0.0.1:8012;
}

server {
    listen 80;
    server_name dev.example.com;
    root /var/www/tools/oreoreapp/reoreotsubuyakikun/public;
    access_log  /var/log/chat.access.log  main;

    location / {
        proxy_set_header X-Forwarded-Proto "http";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://chat;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
supervisord
[program:oreoreapp]
user=root
command=/var/www/tools/oreoreapp/app.sh
autostart=true
autorestart=true
stopsignal=QUIT
/var/www/tools/oreoreapp/app.sh
#!/bin/sh

# export
export PATH="/var/www/tools/app1/local/bin:/root/.plenv/shims:/root/.plenv/bin:/root/.phpenv/shims:/root/.phpenv/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin"

# app start
cd /var/www/tools/oreoreapp/reoreotsubuyakikun
exec /root/.plenv/shims/hypnotoad -f app.psgi

※なんとなくフォアグラウンドで起動
※実行権限を別途付与

アプリ側の修正として

app.conf
{
  hypnotoad => {
    listen  => ['http://*:8012'],
    workers => 1
  }
};

※アプリ用のパラメータ設定を用意して

app.psgi
use Mojolicious::Lite;
use DateTime;
use Encode;
use JSON;
use utf8;

# 設定ファイルを読み込み
my $config = plugin('Config', {file => 'app.conf'});

# 接続人数
my $clients = {};

# Template with browser-side code
get '/' => 'index';

# WebSocket echo service
websocket '/echo' => sub {
  my $c = shift;

  my $id = sprintf "%s", $c->tx;
  $clients->{$id} = $c->tx;

  # Opened
  $c->app->log->debug('WebSocket opened.');

  # Increase inactivity timeout for connection a bit
  $c->inactivity_timeout(300);

  # Incoming message
  $c->on(message => sub {
    my ($c, $msg) = @_;

    $c->app->log->debug('mesage->' . $msg);

    my $dt   = DateTime->now( time_zone => 'Asia/Tokyo');

    for (keys %$clients) {
      my $_msg = "$msg";
      $clients->{$_}->send(JSON->new->utf8(0)->encode({
        hms  => $dt->hms,
        text => $_msg,
       }));
    }

  });

  # Closed
  $c->on(finish => sub {
    my ($c, $code, $reason) = @_;
    $c->app->log->debug("WebSocket closed with status $code.");
  });
};

app->start;

※app.confを読み込み処理を追加

所感

websocketなどはStarmanではうまく動かないのでmorboなどで対応するしかない。
morboにパラメータを付与して対応することもできるけど今回はhypnotoadで対応
しかし、なんかしょーもない感じのもの作ってしまったwww

複数接続に対応しているように見せかけで実際はあまり繋がらん(´・ω・`)
実際にあげてからクライアントの状態がどうなっているか確認しよう

Mojolicious+Angularjsでwebsocket

angularjsでwebsocket

Controllerだけではうまくいかないことがあるのでfactoryなどが必要になってくる

実装

default.html.ep
<!DOCTYPE html>
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel="shortcut icon" href="<%= url_for '/favicon.ico' %>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="utf-8">
    <link rel='stylesheet prefetch' href='/css/bootstrap.min.css'>
    <link rel='stylesheet prefetch' href='/css/main.css'>
    <script src='/js/jquery-2.1.1.min.js'></script>
    <script src='/js/bootstrap.min.js'></script>
    <script src='/js/angular.min.js'></script>
    <script src='/js/ui-bootstrap-tpls-0.12.0.min.js'></script>
    <script src='/js/main.js'></script>
  </head>
  <body>
    <div class="header">
        <div class="title">
            <a href="/"><%= title %></a>
        </div>
    </div>
    <div class="content">
    <%= content %>
    </div>
    <div class="footer">
        <div class="copyright">
            @m_shige1979
        </div>
    </div>
  </body>
</html>

このへんはいままでのものを流用しているのでぶっちゃけ手抜きです

index.html.ep
% layout 'default';
% title 'おれおれつぶやき君';
<div class="index" ng-app="myApp">
  <div ng-controller="MainCtrl">
        <div class="row head">
            さみしくなるまでなんかをつぶやけ
        </div>
        <div class="row detail">
            <input type="hidden" ng-model="EchoScoket.path" ng-init="EchoScoket.path='<%= url_for('echo')->to_abs %>'" />
            <div class="col-md-12 block1">
                <form class="form" role="form">
                  <div class="form-group">
                    <div class="input-group" style="width: 100%;">
                      <input type="text" class="form-control" placeholder="Message" ng-model="message" ng-keydown="handleKeydown($event)">
                    </div>
                  </div>
                  <button type="button" class="btn btn-default" ng-click="messageClear()">clear</button>
                  <button type="button" class="btn btn-primary" ng-click="messageSend()">send</button>
                </form>
            </div>
        </div>
        <div class="row detail">
            <div class="col-md-12 block1">
                <div class="message_block">
                    <div class="message_item" ng-repeat="item in EchoScoket.messageList">
                        俺:{{item.text}}
                    </div>
                </div>

            </div>
        </div>
    </div>
</div>

※echoのURLをどのようにしてng-modelに割り当てようかと思ったけどng-initでなんとかなりました。

main.js
var myApp = angular.module('myApp', ['ui.bootstrap']);

myApp.factory('EchoScoket', function($rootScope, $log){

    var service = {};
    var ws;

    service = {
        // websocket path
        path: "",

        //
        messageList: [],

        // 接続
        connect: function(){
            // オブジェクト生成
            ws = new WebSocket(service.path);
            $log.log(ws);

            // 接続
            ws.onopen = function(){
                $log.log("websocket connect");
            };

            // メッセージ受信
            ws.onmessage = function(message){
                $log.log("websocket connect")

                var res = JSON.parse(message.data);

                // 先頭に追加する
                service.messageList.unshift(res);

                // すぐに反映されないのでここで同期する
                $rootScope.$apply(service.messageList);
            };

            // 切断
            ws.onclose = function(){
                $log.log("websocket disconnect")
            }

        },

        // メッセージ送信
        send: function(message){
            if(message != ""){
                ws.send(message);
            }
        }
    };

    return service;
});

myApp.controller('MainCtrl', function($scope, $log, EchoScoket){

    $scope.EchoScoket = EchoScoket;
    $scope.message = "";

    // message clear
    $scope.messageClear = function(){
        $scope.message = "";
    }

    // message send
    $scope.messageSend = function(){
        EchoScoket.send($scope.message);
        $scope.message = "";
    }

    // enter key send
    $scope.handleKeydown = function(e) {
        if (e.which == 13) {
            EchoScoket.send($scope.message);
            $scope.message = '';
        }
    }

    // 初回にのみ実行され、websocketの接続処理を実施
    $scope.$watch('EchoScoket.path', function(newVal, oldVal) {
        EchoScoket.connect();
    });
});

factoryをシングルトンパターンにしてscopeにすることで値を変更されたことを監視する$watchと連携できるようにしています。
websocketの通信部分は基本そのままで値の反映に誤差があるので$rootScope.$applyで同期をとるようにしました。

app.psgi
use Mojolicious::Lite;
use DateTime;
use Encode;
use JSON;
use utf8;

# 接続人数
my $clients = {};

# Template with browser-side code
get '/' => 'index';

# WebSocket echo service
websocket '/echo' => sub {
  my $c = shift;

  my $id = sprintf "%s", $c->tx;
  $clients->{$id} = $c->tx;

  # Opened
  $c->app->log->debug('WebSocket opened.');

  # Increase inactivity timeout for connection a bit
  $c->inactivity_timeout(300);

  # Incoming message
  $c->on(message => sub {
    my ($c, $msg) = @_;

    $c->app->log->debug('mesage->' . $msg);

    my $dt   = DateTime->now( time_zone => 'Asia/Tokyo');

    for (keys %$clients) {
      my $_msg = "$msg";
      $clients->{$_}->send(JSON->new->utf8(0)->encode({
        hms  => $dt->hms,
        text => $_msg,
       }));
    }

  });

  # Closed
  $c->on(finish => sub {
    my ($c, $code, $reason) = @_;
    $c->app->log->debug("WebSocket closed with status $code.");
  });
};

app->start;

※ベースとしたものは
http://mojolicio.us/perldoc/Mojolicious/Guides/Cookbook#WebSocket-web-service
であとは以前作成したものを流用しました。

問題点

これ外部にあげて動くかな?
外部のサーバではnginxなどのサーバを経由するのでちょっと動かせるか不安なことがある。
ちょっとローカルvmで実験してみよう

なんか、本番環境が絡むとモチベーションが下がってしまうのでなんとかしないといけない…

以前試したこと

starmanをそのまま試したら動かなかったハンドシェイクとかが上手く渡っていないようなのでmorboだけでできるか実験していく予定

websocketを使用したアプリを作成してみたいので私の今後の課題になるかも…