技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

ユダヤの教え タルムード 説話集

書籍

レビュー

子供に聞かせる「昔話」には教育的側面、知恵などが含まれる。
例えば日本昔ばなしのそれは大抵が「勧善懲悪」「良いことは帰ってくる」「悪いことはいつかしっぺ返しが来る」「ズルはしてはならない」といった話が非常に多い。
これは悪いとは言わないし、むしろ被災しようが整然と並ぶ規範意識や、世界有数の犯罪率の低さにも影響が無いとは言うまい。
子供に宗教に寄らない善悪を徹底する効果が確かにあるといえる。

この「昔話」だが、やはり民族の差が出てくるようだ。
と言うのは世界で最も富豪やノーベル賞受賞者を作り出したユダヤ人。その彼らの「昔話」、タルムードはまた毛色が違う。
感想としては「こんなん幼少期から聞かされて考え続けたらそりゃ成功者になるわ」です。

この話には、「リスクの取り方」「備えることの重要性」「議論することの重要性」社会で成功するためのノウハウが詰まっているのだ。
しかも面白いのは、この説話の最中、親は途中で話を止めて子供に「どうすればいいと思う?」「どうすべきだと思う?」はたまた「この先どうなると思う?」と考えさせると言う。
そうして「考える」習慣も身に着けさせるのだという…

そりゃ大人になったときに差がつくわけだわ(汗

続きを読む

NodeJS でRESTサーバを最短で(Salesforceのコールアウトテストサーバ)

Salesforce でほぼ標準の開発環境 sfdx ですが、コレ、nodejs 経由でインストール出来るのですよね。
逆に言うと、nodejs と親和性が高いとも言えます。

そこで、Salesforce のコールアウト(Apex*1 から、Salesforce 外の HTTP リクエストを送信する仕組み)のテスト用サーバをしれっと作ろうというのが今回の課題。

目次

  • 目次
  • NodeJS で秒殺RESTサーバ
  • ngrok で URL を取得する
    • 補足

NodeJS で秒殺RESTサーバ

全部デフォルトでOKなのでコマンドを実行

  • npm init : nodejs のプロジェクトを作成
  • npm install -d express : Nodejs の軽量サーバライブラリインストール
  • npm install -d compression : レスポンスの圧縮ライブラリ

index.js を作成して

const compression = require('compression');
const express = require('express');

const app = express();
const port = 3000;
app.use(compression());
app.use(express.json());

app.get('/test', (req, res) => {
  res.json({ message: 'Welcome to JSON!' });
})

app.use('/', express.static('static'));

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

見たままですね。/test にアクセスすると JSON 応答が返り、それ以外のURLアクセスは static フォルダ内を参照します。
node index.js で起動。

> node index.js
Example app listening at http://localhost:3000

ちなみに、ExpressJS の公式サイトは下記です。

expressjs.com

*1:Salesforce 専用の開発言語

続きを読む

Python2.7 で死ぬほどシンプルな REST サーバ

とりあえずコレを見つけて、楽だったのでメモ

Simple and functional REST server for Python (2.7) using no dependencies beyond the Python standard library. · GitHub

このシンプルさでしれっと REST サーバ立つのはいいね。

#!/usr/bin/env python
import sys, os, re, shutil, json, urllib, urllib2, BaseHTTPServer

# Fix issues with decoding HTTP responses
reload(sys)
sys.setdefaultencoding('utf8')

here = os.path.dirname(os.path.realpath(__file__))

# この辺で、ロジックを定義
def get_records(handler):
    return { 'test': 'Result!' }

# 半分おまじないみたいな感じ
def rest_call_json(url, payload=None, with_payload_method='PUT'):
    'REST call with JSON decoding of the response and JSON payloads'
    if payload:
        if not isinstance(payload, basestring):
            payload = json.dumps(payload)
        # PUT or POST
        response = urllib2.urlopen(MethodRequest(url, payload, {'Content-Type': 'application/json'}, method=with_payload_method))
    else:
        # GET
        response = urllib2.urlopen(url)
    response = response.read().decode()
    return json.loads(response)

# 全体のレスポンス設定
class MethodRequest(urllib2.Request):
    def __init__(self, *args, **kwargs):
        if 'method' in kwargs:
            self._method = kwargs['method']
            del kwargs['method']
        else:
            self._method = None
        return urllib2.Request.__init__(self, *args, **kwargs)

    def get_method(self, *args, **kwargs):
        return self._method if self._method is not None else urllib2.Request.get_method(self, *args, **kwargs)

# リクエストのルーティング定義
class RESTRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        self.routes = {
            r'^/$': {'file': 'web/index.html', 'media_type': 'text/html'},
            r'^/get$': {'GET': get_records, 'media_type': 'application/json'},
        }
        
        return BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
    
    def do_HEAD(self):
        self.handle_method('HEAD')
    
    def do_GET(self):
        self.handle_method('GET')

    def do_POST(self):
        self.handle_method('POST')

    def do_PUT(self):
        self.handle_method('PUT')

    def do_DELETE(self):
        self.handle_method('DELETE')
    
    def get_payload(self):
        payload_len = int(self.headers.getheader('content-length', 0))
        payload = self.rfile.read(payload_len)
        payload = json.loads(payload)
        return payload
        
    def handle_method(self, method):
        route = self.get_route()
        if route is None:
            self.send_response(404)
            self.end_headers()
            self.wfile.write('Route not found\n')
        else:
            if method == 'HEAD':
                self.send_response(200)
                if 'media_type' in route:
                    self.send_header('Content-type', route['media_type'])
                self.end_headers()
            else:
                if 'file' in route:
                    if method == 'GET':
                        try:
                            f = open(os.path.join(here, route['file']))
                            try:
                                self.send_response(200)
                                if 'media_type' in route:
                                    self.send_header('Content-type', route['media_type'])
                                self.end_headers()
                                shutil.copyfileobj(f, self.wfile)
                            finally:
                                f.close()
                        except:
                            self.send_response(404)
                            self.end_headers()
                            self.wfile.write('File not found\n')
                    else:
                        self.send_response(405)
                        self.end_headers()
                        self.wfile.write('Only GET is supported\n')
                else:
                    if method in route:
                        content = route[method](self)
                        if content is not None:
                            self.send_response(200)
                            if 'media_type' in route:
                                self.send_header('Content-type', route['media_type'])
                            self.end_headers()
                            if method != 'DELETE':
                                self.wfile.write(json.dumps(content))
                        else:
                            self.send_response(404)
                            self.end_headers()
                            self.wfile.write('Not found\n')
                    else:
                        self.send_response(405)
                        self.end_headers()
                        self.wfile.write(method + ' is not supported\n')
                    
    
    def get_route(self):
        for path, route in self.routes.iteritems():
            if re.match(path, self.path):
                return route
        return None

# サーバ起動処理
def rest_server(port):
    'Starts the REST server'
    http_server = BaseHTTPServer.HTTPServer(('', port), RESTRequestHandler)
    print 'Starting HTTP server at port %d' % port
    try:
        http_server.serve_forever()
    except KeyboardInterrupt:
        pass
    print 'Stopping HTTP server'
    http_server.server_close()


# 実行
def main(argv):
    rest_server(3000)

if __name__ == '__main__':
    main(sys.argv[1:])

セキュリティ気にしないなら、コレを crontab にでも仕込んでおけば良いかも?
ポート 3000 なんて一般公開しないでしょうよ…ローカルプロセスの都合で一時的にテストサーバがほしいとかでも使える

システム起動時に特定のコマンドを実行するには − @IT

漫画 バビロン大富豪の教え

書籍

読んだのは「バビロン大富豪の教え」。原書は英語で、日本語訳も当然ある。
漫画で読みやすくなっていて、設定がそこそこ現代日本風にアレンジはされている。

原書は「バビロンいちの大金持ち(The Richest Man In Babyron)」

目次

  • 書籍
  • 目次
  • レビュー
  • 各章概要
    • なぜ、同じように働いているのに、貧乏人と大金持ちがいるのか?
      • 学び
    • 大富豪だけが知っている「黄金に愛される7つ道具」
      • 学び
    • 価値があるのは、金貨が入った袋か?知恵が詰まった袋か?
      • 学び
    • 賢者の助言によって、貯金が賢明に働き出す
    • 「守るべきもの」があるから人は何度でも立ち上がれる
      • 学び
    • 己の心は「奴隷」のものか、「自由民」のものか
      • 学び
    • はるか昔の借金返済記録が現代人を救う
      • 学び
    • なぜ人は働くのか。それは金のためではなかった
      • 学び
    • エピローグ:最後の黄金法則

レビュー

主人公は借金が理由で妻子に逃げられ、失意で何もする気が起きない元考古学者。
彼の友人が、仕事の一環で古代バビロニアの石版を発見し、主人公に解析の依頼を行う。

石版は遠い昔古代バビロニア時代の自伝で、貧民から富豪に成り上がった人物のものだった。
彼の生涯の記録を通し、金に愛され、増やすための考え方、行動を学び、現代社会においても借金を返しきり、妻子を取り戻す。

という話である。
非常に興味深い点は

  1. 100年前の小説にも関わらず、ほぼ現代日本でも通じるお金の基礎が書かれていること
  2. 漫画化にあたり、非常に読みやすく、説教臭く無いため、スラスラ読めること
  3. 本気でこれをやるだけで、本当に貯蓄できること(というか同じ論理で貯蓄している)

決して FIRE を目指すというわけではない。
しかし、これを読んで行動を起こすことは決して無駄では無いと思う。

これと「お金の大学」(こちらは具体的なアクションの方法を載せてくれている)と合わせれば、小金持ちは十分狙える良書だと考えます。

続きを読む

JavaScript (+LWC)のチュートリアル: リバーシを作る(3)

JavaScript (+LWC)のチュートリアル: リバーシを作る(3)

概要

LWC らしくコンポーネント化していきます。

前提知識

前回までの知識

実装

ボードの適用

初期状態はこんな感じ

見ればわかりますが、リバーシのボードは、単純に 1 マス 1 マスのブロックの集まりの繰り返しですので、単純にそれを1コンポーネントにしてみます。

  • src
    • modules
      • reversi
        • column (新規作成)
          • column.css
          • column.js
          • column.html

これを作成します。
基本的に空のテキストファイルで用意します。

続きを読む

CentOS 7 の Apache (+PHP) から、シェルを経由して sfdx コマンドを実行する

Docker に CentOS + Apache + PHP

Dockerfile を作る

CentOS インストールと PHP インストールまで自動化

FROM centos:7
RUN yum update -y && yum clean all

# Repository
# EPEL
RUN yum install -y epel-release

# remi
RUN yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

# installs
RUN yum install -y httpd
RUN yum -y install --enablerepo=remi,remi-php81 php php-devel php-mbstring php-pdo php-xml php-gd php-fpm php-mysqlnd php-opcache php-pecl-zip libzip5

EXPOSE 80
ENTRYPOINT ["/usr/sbin/httpd", "-DFOREGROUND"]

起動

コンパイルとコンテナ作成

> docker build --tag develop:1.0 .
> docker run -ti --publish 8080:80 --detach --name develop develop:1.0

http://localhost:8080 で見れるはず

コンテナ番号を確認して

> docker container ls
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS         PORTS                  NAMES
bcd685553188   develop:1.0   "/usr/sbin/httpd -DF…"   2 minutes ago   Up 2 minutes   0.0.0.0:8080->80/tcp   develop

ログイン

>  docker exec -it bcd685553188 bash
[root@bcd685553188 /]#

Vim を突っ込んで、確認用画面作成

# yum install vim -y
# cd /var/www/html
# vim index.php

内容は

<?
phpinfo();
?>

あれ?出ない…てかHTTP応答がまんまソースやんけ
モジュールは /etc/httpd/conf.d とか /etc/httpd/modules に入ってるから、 httpd.conf いじって

LoadModule php7_module modules/libphp7.so
AddHandler php7-script .php
DirectoryIndex index.php

コンテナ再起動して再アクセスするも変化なし…?
もしかして php.ini か?とおもったらアタリ

short_open_tag = Off

テメーか原因は…ってことで On で書き換えて再起動。
表示された。

PHP から sfdx コマンドコールする

nodejs インストール

この辺 からインストールコマンドを引っ張る

# curl -fsSL https://rpm.nodesource.com/setup_18.x | bash -
# yum install -y nodejs

すると

Error: Package: 2:nodejs-18.13.0-1nodesource.x86_64 (nodesource)
           Requires: libstdc++.so.6(GLIBCXX_3.4.20)(64bit)
Error: Package: 2:nodejs-18.13.0-1nodesource.x86_64 (nodesource)
           Requires: libc.so.6(GLIBC_2.28)(64bit)
Error: Package: 2:nodejs-18.13.0-1nodesource.x86_64 (nodesource)
           Requires: libstdc++.so.6(CXXABI_1.3.9)(64bit)
Error: Package: 2:nodejs-18.13.0-1nodesource.x86_64 (nodesource)
           Requires: libstdc++.so.6(GLIBCXX_3.4.21)(64bit)
Error: Package: 2:nodejs-18.13.0-1nodesource.x86_64 (nodesource)
           Requires: libm.so.6(GLIBC_2.27)(64bit)

…さいで…

# yum install -y glibc libstdc++

でも状況変わらず…どうも要求ライブラリバージョンが合わないようで…  

yum install yum install https://rpm.nodesource.com/pub_16.x/el/7/x86_64/nodejs-16.17.0-1nodesource.x86_64.rpm

しょうがないから 16 入れるか…

sfdx 入れる

悩むこたぁない…。

# npm install sfdx-cli --global
# sfdx --version
sfdx-cli/7.185.0 linux-x64 node-v16.17.0

インストールされる先は

# ls /usr/lib/node_modules/
corepack  npm  sfdx-cli

つまり直たたきができるはず

# /usr/lib/node_modules/sfdx-cli/bin/run --version
sfdx-cli/7.185.0 linux-x64 node-v16.17.0

php から sfdx を呼んでみる

apache(+PHP) から exec コマンドを実行するとき、Path とかその他諸々空っぽなので、パス設定を行うシェルコマンドを作成しておく。

NPM_PACKAGES="/usr/lib/node_modules"
echo "export PATH="\""\$PATH:$NPM_PACKAGES/bin"\""
npm config set prefix $NPM_PACKAGES" | tee '/etc/profile.d/node-path.sh'

実行結果と確認。

# NPM_PACKAGES="/usr/lib/node_modules"
# echo "export PATH="\""\$PATH:$NPM_PACKAGES/bin"\""
> npm config set prefix $NPM_PACKAGES" | tee '/etc/profile.d/node-path.sh'
export PATH="$PATH:/usr/lib/node_modules/bin"
npm config set prefix /usr/lib/node_modules
# cat /etc/profile.d/node-path.sh
export PATH="$PATH:/usr/lib/node_modules/bin"
npm config set prefix /usr/lib/node_modules

/etc/profile.d は非ログインシェルでも読み込まれる。
apache ユーザから呼べるようにまずは指定(権限委譲まではいらんかも知れんけど、これ専用なので一応…)

# chown apache:apache /etc/profile.d/node-path.sh
# chmod +x /etc/profile.d/node-path.sh

そしたら /var/www/example.sh を作成 ※権限は気をつけて

#!/bin/bash
/etc/profile.d/node-path.sh
sfdx --version
exit 0

/etc/profile.d/node-path.sh は書かなくても読み込まれるとは思うが、一応…)
で、index.php を下記で書き換えます。

<?
$cmd = '/bin/bash /var/www/example.sh';
$output=null;
$retval=null;
exec($cmd, $output, $retval);
echo "Returned with status $retval and output:\n";
print_r($output);
?>

そして画面アクセスすると、無事に下記が表示されました。

Returned with status 0 and output: Array ( [0] => sfdx-cli/7.185.0 linux-x64 node-v16.17.0 )

JavaScript (+LWC)のチュートリアル: リバーシを作る(2)

概要

今回が単純なリバーシの実装と、シンプルな画面表示まで。   画面はこだわったものではなく、単純に文字表示のもの。つまり UI ではなく、ゲームのルール実装を優先します。

前提知識

前回までの知識

目次

  • 概要
  • 前提知識
  • 目次
  • ディレクトリ構造を変更する
  • ロジックの実装
    • まずはリバーシのボードを表示しよう
    • 石を置けるようにしよう
    • 石をひっくり返そう
    • 石が置けない事を通知する
    • ステータス表示
  • ここまでのコードまとめ

ディレクトリ構造を変更する

前回までで LWC を設定しましたはが、「example」ディレクトリって…と思ったのは自分だけか?
ということで、これをまず変更する。

具体的にはこんな感じになっているので

  • modules
    • example
      • app

これをこうしたい

  • modules
    • reversi
      • app

これ自体は簡単なので、とりあえずディレクトリ名を変更しよう。 これだけで、 npm run dev でサーバを起動すると…

500 - Error retrieving view for route "example"

と言われてしまいます。
これは example/app をアプリケーションの起動ルートとして設定されているからですね…

この設定ファイルは lwr.config.json ファイルで、こんな感じで書き換えます。

{
    "lwc": { "modules": [{ "dir": "$rootDir/src/modules" }] },
    "routes": [
        {
            "id": "reversi",
            "path": "/",
            "rootComponent": "reversi/app"
        }
    ]
}

その後再起動してみてください。前回までの画面が見えたと思います。

続きを読む