Let's Encryptのサーバ証明書の有効期限は3ヶ月なので、例えば
2月、5月、8月、11月の1日の01:00に更新する。
ポイントは、Let's Encrypt更新用コマンドを実行する前にhttpdを止めること。
(CentOS 6)
1 0 1 2,5,8,11 * root /sbin/service httpd stop > /dev/null 2>&1 && /usr/bin/certbot-auto certonly -n --standalone -d コモンネーム > /dev/null 2>&1;/sbin/service httpd start > /dev/null 2>&1
(CentOS 7)
1 0 1 2,5,8,11 * root /bin/systemctl stop httpd.service > /dev/null 2>&1 && /bin/certbot certonly -n --webroot -w /var/www/html -d コモンネーム > /dev/null 2>&1;/bin/systemctl start httpd.service > /dev/null 2>&1
(Ubuntu 16.04)
1 0 1 2,5,8,11 * root /bin/systemctl stop apache2.service > /dev/null 2>&1 && /usr/bin/letsencrypt certonly --standalone -d コモンネーム > /dev/null 2>&1;/bin/systemctl start apache2.service > /dev/null 2>&1
木曜日, 9月 21, 2017
Windowsサーバにセッションの空きがなくてリモートデスクトップ接続できない場合(sshで接続できる前提)
Windowsサーバにリモートデスクトップ接続したいが、いつまで経ってもつながらない場合、Activeなセッションが複数あって空きがない可能性が考えられる。
WindowsサーバにCygwinがインストールされていて、sshdが立っていれば手がある。
ターミナルアプリでWindowsサーバにsshで接続し
$ query session
を実行して、リモートデスクトップ接続のセッションIDと状態を確認する。
リモートデスクトップ接続のセッション名は
rdp-tcp#0
のようになっている。
状態が「Active」なセッションについて
$ /cygdrive/c/Windows/system32/reset session セッションID
を実行して削除する。
以上。
WindowsサーバにCygwinがインストールされていて、sshdが立っていれば手がある。
ターミナルアプリでWindowsサーバにsshで接続し
$ query session
を実行して、リモートデスクトップ接続のセッションIDと状態を確認する。
リモートデスクトップ接続のセッション名は
rdp-tcp#0
のようになっている。
状態が「Active」なセッションについて
$ /cygdrive/c/Windows/system32/reset session セッションID
を実行して削除する。
以上。
木曜日, 8月 10, 2017
木曜日, 7月 27, 2017
Azure Speech Service API (Text To Speech) サンプル
Azureの管理ポータルでの設定は省略。
HTMLファイルとPHPファイルのサンプル。
HTMLファイルだけで実装することも可能だが、APIを利用するためのキーの文字列をソースに入れたくないので、APIを利用する部分はPHPで実装する。
いずれのファイルもhttpdのドキュメントルート下の /cognitive/ に配置される前提。
サーバサイドは、Apache 2.2、PHP 5.3。
クライアントはHTML5 (Web Audio API)対応ブラウザ。
Windows10のChrome、Edgeで確認。IEはダメ。
text_to_speech.html
----- ここから -----
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noindex,nofollow" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Speech Service API (Text To Speech)</title>
<link rel="stylesheet" href="//stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
<style>
<!--
.page-title {
color: #555555;
font-size: x-large;
font-weight: bold;
text-shadow: 1px 1px 1px #999999;
}
-->
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-12">
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="/cognitive/">Cognitive Services Examples</a>
</nav>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h3 class="page-title">Speech Service API (Text To Speech)</h3>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<strong> ロケールを選択し、テキストを入力して「送信」ボタンをクリックしてください。</strong>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<form id="text-to-speech-form" method="POST">
<div class="form-group row">
<label for="locale" class="col-sm-2 col-form-label">ロケール</label>
<div class="col-sm-4">
<select name="locale" id="locale" class="form-control">
<option value="ja">ja</option>
<option value="en">en</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="text_for_speech" class="col-sm-2 col-form-label">テキスト</label>
<div class="col-sm-10">
<textarea name="text_for_speech" id="text_for_speech" cols="100" rows="8" class="form-control"></textarea>
</div>
</div>
<div class="form-group row">
<div class="col-sm-10 ml-sm-auto">
<button type="button" id="submit-btn" class="btn btn-primary">送信</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- javascript -->
<script src="//code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min.js" integrity="sha256-9wRM03dUw6ABCs+AU69WbK33oktrlXamEXMvxUaF+KU=" crossorigin="anonymous"></script>
<script>
(function() {
"use strict";
const root = this;
const $ = root.jQuery;
root.AudioContext = root.AudioContext || root.webkitAudioContext;
const context = new AudioContext();
// Audio 用の buffer を読み込む
const getAudioBuffer = function(url, param, fn) {
$.blockUI({
message : '<img src="/common/img/gif-load.gif" />',
css : {
color : '',
border : '',
backgroundColor : ''
},
overlayCSS : {
backgroundColor: '#FFFFFF'
}
});
const req = new XMLHttpRequest();
req.open('POST', url, true);
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.responseType = 'arraybuffer';
req.onreadystatechange = function() {
$.unblockUI();
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
context.decodeAudioData(req.response, function(buffer) {
fn(buffer);
});
}
};
param = encodeURI(param);
req.send(param);
};
// サウンドを再生
const playSound = function(buffer) {
const source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start(0);
};
$(function() {
$('#submit-btn').on('click', function() {
if (!$('#text_for_speech').val()) {
alert('テキストを入力してください。');
return false;
}
const textForSpeech = $('#text_for_speech').val();
getAudioBuffer('/cognitive/text_to_speech.php', 'text_for_speech=' + textForSpeech + '&locale=' + $('#locale').val(), function(buffer) {
playSound(buffer);
});
});
});
}).call(this);
</script>
</body>
</html>
----- ここまで -----
text_to_speech.php
----- ここから -----
<?php
/**
* Azure Cognitive ServicesのSpeech Service API (Text To Speech) を使って
* 音声データを取得する
*/
global $php_errormsg;
$access_token_url = 'https://westus.api.cognitive.microsoft.com/sts/v1.0/issuetoken';
$api_key = 'Azure管理ポータルで取得したキー';
$tts_service_url = 'https://westus.tts.speech.microsoft.com/cognitiveservices/v1';
$user_agent = 'Azure管理ポータル上のリソース名';
$output_format = 'riff-24khz-16bit-mono-pcm';
$voice_fonts = array(
'ja' => array('ja-JP', 'ja-JP, Ichiro, Apollo'),
'en' => array('en-US', 'en-US, BenjaminRUS')
);
// リクエストパラメータの文字列を変換した音声データを取得
if (!empty($_POST['text_for_speech'])
and !empty($_POST['locale'])) {
$request = 'curl -Ss -X POST "' . $access_token_url . '"'
. ' -H "Content-type: application/x-www-form-urlencoded"'
. ' -H "Content-Length: 0"'
. ' -H "Ocp-Apim-Subscription-Key: ' . $api_key . '"';
$access_token = `$request`;
if (!$access_token) {
throw new Exception("Problem with $access_token_url, $php_errormsg");
}
else {
$locale = $_POST['locale'];
$text_for_speech = $_POST['text_for_speech'];
$voice_font_locale = $voice_fonts[$locale][0];
$voice_font = $voice_fonts[$locale][1];
$request = 'curl -Ss -X POST "' . $tts_service_url . '"'
. ' -A "' . $user_agent . '"'
. ' -H "X-Microsoft-OutputFormat: ' . $output_format . '"'
. ' -H "Content-Type: application/ssml+xml"'
. ' -H "Authorization: Bearer ' . $access_token . '"'
. ' -H "cache-control: no-cache"'
. ' --data'
. ' "<speak version=\"1.0\" xml:lang=\"' . $voice_font_locale . '\">'
. '<voice xml:lang=\"' . $voice_font_locale . '\" xml:gender=\"Male\" name=\"Microsoft Server Speech Text to Speech Voice (' . $voice_font . ')\">' . $text_for_speech . '</voice>'
. '</speak>"';
$result = `$request`;
if (!$result) {
throw new Exception("Problem with $tts_service_url, $php_errormsg");
}
else {
header('Content-Type: audio/x-wav');
header('Content-Length: ' . strlen($result));
echo $result;
}
}
}
?>
----- ここまで -----
HTMLファイルとPHPファイルのサンプル。
HTMLファイルだけで実装することも可能だが、APIを利用するためのキーの文字列をソースに入れたくないので、APIを利用する部分はPHPで実装する。
いずれのファイルもhttpdのドキュメントルート下の /cognitive/ に配置される前提。
サーバサイドは、Apache 2.2、PHP 5.3。
クライアントはHTML5 (Web Audio API)対応ブラウザ。
Windows10のChrome、Edgeで確認。IEはダメ。
text_to_speech.html
----- ここから -----
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noindex,nofollow" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Speech Service API (Text To Speech)</title>
<link rel="stylesheet" href="//stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
<style>
<!--
.page-title {
color: #555555;
font-size: x-large;
font-weight: bold;
text-shadow: 1px 1px 1px #999999;
}
-->
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-12">
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="/cognitive/">Cognitive Services Examples</a>
</nav>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h3 class="page-title">Speech Service API (Text To Speech)</h3>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<strong> ロケールを選択し、テキストを入力して「送信」ボタンをクリックしてください。</strong>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<form id="text-to-speech-form" method="POST">
<div class="form-group row">
<label for="locale" class="col-sm-2 col-form-label">ロケール</label>
<div class="col-sm-4">
<select name="locale" id="locale" class="form-control">
<option value="ja">ja</option>
<option value="en">en</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="text_for_speech" class="col-sm-2 col-form-label">テキスト</label>
<div class="col-sm-10">
<textarea name="text_for_speech" id="text_for_speech" cols="100" rows="8" class="form-control"></textarea>
</div>
</div>
<div class="form-group row">
<div class="col-sm-10 ml-sm-auto">
<button type="button" id="submit-btn" class="btn btn-primary">送信</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- javascript -->
<script src="//code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min.js" integrity="sha256-9wRM03dUw6ABCs+AU69WbK33oktrlXamEXMvxUaF+KU=" crossorigin="anonymous"></script>
<script>
(function() {
"use strict";
const root = this;
const $ = root.jQuery;
root.AudioContext = root.AudioContext || root.webkitAudioContext;
const context = new AudioContext();
// Audio 用の buffer を読み込む
const getAudioBuffer = function(url, param, fn) {
$.blockUI({
message : '<img src="/common/img/gif-load.gif" />',
css : {
color : '',
border : '',
backgroundColor : ''
},
overlayCSS : {
backgroundColor: '#FFFFFF'
}
});
const req = new XMLHttpRequest();
req.open('POST', url, true);
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.responseType = 'arraybuffer';
req.onreadystatechange = function() {
$.unblockUI();
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
context.decodeAudioData(req.response, function(buffer) {
fn(buffer);
});
}
};
param = encodeURI(param);
req.send(param);
};
// サウンドを再生
const playSound = function(buffer) {
const source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start(0);
};
$(function() {
$('#submit-btn').on('click', function() {
if (!$('#text_for_speech').val()) {
alert('テキストを入力してください。');
return false;
}
const textForSpeech = $('#text_for_speech').val();
getAudioBuffer('/cognitive/text_to_speech.php', 'text_for_speech=' + textForSpeech + '&locale=' + $('#locale').val(), function(buffer) {
playSound(buffer);
});
});
});
}).call(this);
</script>
</body>
</html>
----- ここまで -----
text_to_speech.php
----- ここから -----
<?php
/**
* Azure Cognitive ServicesのSpeech Service API (Text To Speech) を使って
* 音声データを取得する
*/
global $php_errormsg;
$access_token_url = 'https://westus.api.cognitive.microsoft.com/sts/v1.0/issuetoken';
$api_key = 'Azure管理ポータルで取得したキー';
$tts_service_url = 'https://westus.tts.speech.microsoft.com/cognitiveservices/v1';
$user_agent = 'Azure管理ポータル上のリソース名';
$output_format = 'riff-24khz-16bit-mono-pcm';
$voice_fonts = array(
'ja' => array('ja-JP', 'ja-JP, Ichiro, Apollo'),
'en' => array('en-US', 'en-US, BenjaminRUS')
);
// リクエストパラメータの文字列を変換した音声データを取得
if (!empty($_POST['text_for_speech'])
and !empty($_POST['locale'])) {
$request = 'curl -Ss -X POST "' . $access_token_url . '"'
. ' -H "Content-type: application/x-www-form-urlencoded"'
. ' -H "Content-Length: 0"'
. ' -H "Ocp-Apim-Subscription-Key: ' . $api_key . '"';
$access_token = `$request`;
if (!$access_token) {
throw new Exception("Problem with $access_token_url, $php_errormsg");
}
else {
$locale = $_POST['locale'];
$text_for_speech = $_POST['text_for_speech'];
$voice_font_locale = $voice_fonts[$locale][0];
$voice_font = $voice_fonts[$locale][1];
$request = 'curl -Ss -X POST "' . $tts_service_url . '"'
. ' -A "' . $user_agent . '"'
. ' -H "X-Microsoft-OutputFormat: ' . $output_format . '"'
. ' -H "Content-Type: application/ssml+xml"'
. ' -H "Authorization: Bearer ' . $access_token . '"'
. ' -H "cache-control: no-cache"'
. ' --data'
. ' "<speak version=\"1.0\" xml:lang=\"' . $voice_font_locale . '\">'
. '<voice xml:lang=\"' . $voice_font_locale . '\" xml:gender=\"Male\" name=\"Microsoft Server Speech Text to Speech Voice (' . $voice_font . ')\">' . $text_for_speech . '</voice>'
. '</speak>"';
$result = `$request`;
if (!$result) {
throw new Exception("Problem with $tts_service_url, $php_errormsg");
}
else {
header('Content-Type: audio/x-wav');
header('Content-Length: ' . strlen($result));
echo $result;
}
}
}
?>
----- ここまで -----
月曜日, 1月 30, 2017
Azure Face APIサンプル
Azureの管理ポータルでの設定は省略。
HTMLファイルとPHPファイルのサンプル。
HTMLファイルだけで実装することも可能だが、APIを利用するためのキーの文字列をソースに入れたくないので、その部分だけPHPで実装する。
いずれのファイルもhttpdのドキュメントルート下の /cognitive/ に配置される前提。
画像ファイルを一時的に置くため、httpdのドキュメントルート下に /tmp/cognitive/ というディレクトリを作成。
アップロードする画像が規定より大きい場合、canvasを使ってリサイズしたものをアップロードするようにしてある。要HTML5。
face_api.html
----- ここから -----
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noindex,nofollow" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Face API</title>
<link rel="stylesheet" href="//stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
<style>
<!--
@media all {
.page-title {
color: #555555;
font-size: x-large;
font-weight: bold;
text-shadow: 1px 1px 1px #999999;
}
#result {
font-size: large;
font-weight: bold;
text-shadow: 1px 1px 1px #333333;
}
}
@media screen and (max-width: 480px) {
#canvas {
width: 90%;
width: 90vw;
}
}
-->
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-12">
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="/cognitive/">Cognitive Services Examples</a>
</nav>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h3 class="page-title">Face API</h3>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<strong> 画像ファイルを選択し、「送信」ボタンをクリックしてください。</strong>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<form id="upload-img-file-form" method="POST">
<div class="form-group row">
<label for="img-file" class="col-sm-2 col-form-label">画像ファイル</label>
<div class="col-sm-4">
<input type="file" id="img-file" name="img-file" class="form-control btn btn-default" />
</div>
</div>
<div class="form-group row">
<div class="col-sm-10 ml-sm-auto">
<button type="button" id="submit-btn" class="btn btn-primary">送信</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<canvas id="canvas"></canvas>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-4">
<div id="result"></div>
</div>
</div>
</div>
<!-- template -->
<script type="text/template" id="result-table-template">
<table class="table table-bordered">
<tr>
<td colspan="2">性別</td><td><div class="float-sm-right"><%= gender %></div></td>
</tr>
<tr>
<td colspan="2">年齢</td><td><div class="float-sm-right"><%= age %></div></td>
</tr>
<tr>
<td colspan="2">眼鏡</td><td><div class="float-sm-right"><%= glasses %></div></td>
</tr>
<tr>
<td rowspan="8">感情</td><td>Anger (怒り)</td><td><div class="float-sm-right"><%= anger %></div></td>
</tr>
<tr>
<td>Contempt (軽蔑)</td><td><div class="float-sm-right"><%= contempt %></div></td>
</tr>
<tr>
<td>Disgust (嫌悪)</td><td><div class="float-sm-right"><%= disgust %></div></td>
</tr>
<tr>
<td>Fear (恐怖)</td><td><div class="float-sm-right"><%= fear %></div></td>
</tr>
<tr>
<td>Happiness (幸福)</td><td><div class="float-sm-right"><%= happiness %></div></td>
</tr>
<tr>
<td>Neutral (ニュートラル)</td><td><div class="float-sm-right"><%= neutral %></div></td>
</tr>
<tr>
<td>Sadness (悲しみ)</td><td><div class="float-sm-right"><%= sadness %></div></td>
</tr>
<tr>
<td>Surprise (驚き)</td><td><div class="float-sm-right"><%= surprise %></div></td>
</tr>
<tr>
<td rowspan="2">化粧</td><td>目</td><td><div class="float-sm-right"><%= eyeMakeup %></div></td>
</tr>
<tr>
<td>口</td><td><div class="float-sm-right"><%= lipMakeup %></div></td>
</tr>
</table>
</script>
<!-- javascript -->
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js" integrity="sha256-G7A4JrJjJlFqP0yamznwPjAApIKPkadeHfyIwiaa9e0=" crossorigin="anonymous"></script>
<script src="//code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min.js" integrity="sha256-9wRM03dUw6ABCs+AU69WbK33oktrlXamEXMvxUaF+KU=" crossorigin="anonymous"></script>
<script>
(function() {
"use strict";
const root = this;
const _ = root._;
const $ = root.jQuery;
const apiUrl = '/cognitive/face_api.php';
const resultTableTemplate
= _.template($('#result-table-template').html());
const mathRound = function(val) {
return (val * 100).toFixed(2);
};
const toBlob = function(base64, type) {
const bin = atob(base64.replace(/^.*,/, ''));
const buffer = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) {
buffer[i] = bin.charCodeAt(i);
}
const blob = new Blob([buffer.buffer], {
type: type
});
return blob;
};
$(function() {
let file;
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const fd = new FormData();
const mimeType = new Array('image/jpeg', 'image/png', 'image/gif', 'image/bmp');
const maxFileSizeMB = 4;
const minPix = 36;
const maxPixels = 4096;
$('#img-file').on('change', function(evt) {
$('#result').html("");
file = evt.target.files[0];
if (mimeType.indexOf(file.type) == -1) {
alert('ファイル形式がJPEG、PNG、GIF、BMP以外です。');
return false;
}
if (file.size > maxFileSizeMB * 1024 * 1024) {
alert('ファイルサイズが' + maxFileSizeMB + 'MBを超えています。');
return false;
}
const reader = new FileReader();
reader.onload = (function(f) {
return function(e) {
const imgPath = e.target.result;
const img = new Image();
img.src = imgPath;
img.onload = function() {
if (img.width < minPix || img.height < minPix) {
alert('画像のサイズは、縦横ともに' + minPix + 'ピクセル以上必要です。');
return false;
}
let scale = 1;
if (img.width > img.height && img.width > maxPixels) {
scale = maxPixels / img.width;
alert('画像の最大幅が' + maxPixels + 'ピクセルを超えています。');
return false;
}
else if (img.width < img.height && img.height > maxPixels) {
scale = maxPixels / img.height;
alert('画像の最大高が' + maxPixels + 'ピクセルを超えています。');
return false;
}
canvas.width = img.width * scale;
canvas.height = img.height * scale;
context.drawImage(this, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
fd.append("img-file", toBlob(canvas.toDataURL(file.type), file.type), file.name);
};
};
})(file);
reader.readAsDataURL(file);
});
$('#submit-btn').on('click', function() {
$('#result').html("");
if (!file) {
alert('画像を選択してください。');
return false;
}
$.blockUI({
message : '<img src="/common/img/gif-load.gif" />',
css : {
color : '',
border : '',
backgroundColor : ''
},
overlayCSS : {
backgroundColor: '#FFFFFF'
}
});
$.ajax({
url : apiUrl + '?api_type=detect&returnFaceAttributes=age,gender,smile,facialHair,glasses,headPose,emotion,hair,makeup,accessories',
type : "POST",
data : fd,
processData : false,
contentType : false,
dataType : "json"
}).done(function(data, status, xhr) {
for (let i in data) {
$('#result').append(
'<span id="result' + (i + 1) + '">'
+ resultTableTemplate({
gender : data[i].faceAttributes.gender === 'male' ? '男性' : '女性',
age : data[i].faceAttributes.age,
glasses : data[i].faceAttributes.glasses === 'NoGlasses' ? 'なし' : 'あり',
anger : mathRound(data[i].faceAttributes.emotion.anger),
contempt : mathRound(data[i].faceAttributes.emotion.contempt),
disgust : mathRound(data[i].faceAttributes.emotion.disgust),
fear : mathRound(data[i].faceAttributes.emotion.fear),
happiness : mathRound(data[i].faceAttributes.emotion.happiness),
neutral : mathRound(data[i].faceAttributes.emotion.neutral),
sadness : mathRound(data[i].faceAttributes.emotion.sadness),
surprise : mathRound(data[i].faceAttributes.emotion.surprise),
eyeMakeup : data[i].faceAttributes.makeup.eyeMakeup === true ? 'あり' : 'なし',
lipMakeup : data[i].faceAttributes.makeup.lipMakeup === true ? 'あり' : 'なし',
})
+ '</span><br />'
);
context.beginPath();
if (i % 7 === 0) {
context.strokeStyle = 'rgb(255, 0, 0)';
$('#result' + (i + 1)).css('color', '#FF0000');
}
else if (i % 7 === 1) {
context.strokeStyle = 'rgb(0, 255, 0)';
$('#result' + (i + 1)).css('color', '#00FF00');
}
else if (i % 7 === 2) {
context.strokeStyle = 'rgb(0, 0, 255)';
$('#result' + (i + 1)).css('color', '#0000FF');
}
else if (i % 7 === 3) {
context.strokeStyle = 'rgb(0, 255, 255)';
$('#result' + (i + 1)).css('color', '#00FFFF');
}
else if (i % 7 === 4) {
context.strokeStyle = 'rgb(255, 255, 0)';
$('#result' + (i + 1)).css('color', '#FFFF00');
}
else if (i % 7 === 5) {
context.strokeStyle = 'rgb(255, 0, 255)';
$('#result' + (i + 1)).css('color', '#FF00FF');
}
else if (i % 7 === 6) {
context.strokeStyle = 'rgb(0, 0, 0)';
$('#result' + (i + 1)).css('color', '#000000');
}
context.strokeRect(data[i].faceRectangle.left, data[i].faceRectangle.top, data[i].faceRectangle.width, data[i].faceRectangle.height);
}
}).fail(function(xhr, status, error) {
console.log(JSON.stringify(xhr));
console.log(status);
console.log(error);
}).always(function() {
$(document).ajaxStop($.unblockUI);
});
});
});
}).call(this);
</script>
</body>
</html>
HTMLファイルとPHPファイルのサンプル。
HTMLファイルだけで実装することも可能だが、APIを利用するためのキーの文字列をソースに入れたくないので、その部分だけPHPで実装する。
いずれのファイルもhttpdのドキュメントルート下の /cognitive/ に配置される前提。
画像ファイルを一時的に置くため、httpdのドキュメントルート下に /tmp/cognitive/ というディレクトリを作成。
アップロードする画像が規定より大きい場合、canvasを使ってリサイズしたものをアップロードするようにしてある。要HTML5。
face_api.html
----- ここから -----
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noindex,nofollow" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Face API</title>
<link rel="stylesheet" href="//stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
<style>
<!--
@media all {
.page-title {
color: #555555;
font-size: x-large;
font-weight: bold;
text-shadow: 1px 1px 1px #999999;
}
#result {
font-size: large;
font-weight: bold;
text-shadow: 1px 1px 1px #333333;
}
}
@media screen and (max-width: 480px) {
#canvas {
width: 90%;
width: 90vw;
}
}
-->
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-12">
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="/cognitive/">Cognitive Services Examples</a>
</nav>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h3 class="page-title">Face API</h3>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<strong> 画像ファイルを選択し、「送信」ボタンをクリックしてください。</strong>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<form id="upload-img-file-form" method="POST">
<div class="form-group row">
<label for="img-file" class="col-sm-2 col-form-label">画像ファイル</label>
<div class="col-sm-4">
<input type="file" id="img-file" name="img-file" class="form-control btn btn-default" />
</div>
</div>
<div class="form-group row">
<div class="col-sm-10 ml-sm-auto">
<button type="button" id="submit-btn" class="btn btn-primary">送信</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-12">
<canvas id="canvas"></canvas>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-4">
<div id="result"></div>
</div>
</div>
</div>
<!-- template -->
<script type="text/template" id="result-table-template">
<table class="table table-bordered">
<tr>
<td colspan="2">性別</td><td><div class="float-sm-right"><%= gender %></div></td>
</tr>
<tr>
<td colspan="2">年齢</td><td><div class="float-sm-right"><%= age %></div></td>
</tr>
<tr>
<td colspan="2">眼鏡</td><td><div class="float-sm-right"><%= glasses %></div></td>
</tr>
<tr>
<td rowspan="8">感情</td><td>Anger (怒り)</td><td><div class="float-sm-right"><%= anger %></div></td>
</tr>
<tr>
<td>Contempt (軽蔑)</td><td><div class="float-sm-right"><%= contempt %></div></td>
</tr>
<tr>
<td>Disgust (嫌悪)</td><td><div class="float-sm-right"><%= disgust %></div></td>
</tr>
<tr>
<td>Fear (恐怖)</td><td><div class="float-sm-right"><%= fear %></div></td>
</tr>
<tr>
<td>Happiness (幸福)</td><td><div class="float-sm-right"><%= happiness %></div></td>
</tr>
<tr>
<td>Neutral (ニュートラル)</td><td><div class="float-sm-right"><%= neutral %></div></td>
</tr>
<tr>
<td>Sadness (悲しみ)</td><td><div class="float-sm-right"><%= sadness %></div></td>
</tr>
<tr>
<td>Surprise (驚き)</td><td><div class="float-sm-right"><%= surprise %></div></td>
</tr>
<tr>
<td rowspan="2">化粧</td><td>目</td><td><div class="float-sm-right"><%= eyeMakeup %></div></td>
</tr>
<tr>
<td>口</td><td><div class="float-sm-right"><%= lipMakeup %></div></td>
</tr>
</table>
</script>
<!-- javascript -->
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js" integrity="sha256-G7A4JrJjJlFqP0yamznwPjAApIKPkadeHfyIwiaa9e0=" crossorigin="anonymous"></script>
<script src="//code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min.js" integrity="sha256-9wRM03dUw6ABCs+AU69WbK33oktrlXamEXMvxUaF+KU=" crossorigin="anonymous"></script>
<script>
(function() {
"use strict";
const root = this;
const _ = root._;
const $ = root.jQuery;
const apiUrl = '/cognitive/face_api.php';
const resultTableTemplate
= _.template($('#result-table-template').html());
const mathRound = function(val) {
return (val * 100).toFixed(2);
};
const toBlob = function(base64, type) {
const bin = atob(base64.replace(/^.*,/, ''));
const buffer = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) {
buffer[i] = bin.charCodeAt(i);
}
const blob = new Blob([buffer.buffer], {
type: type
});
return blob;
};
$(function() {
let file;
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const fd = new FormData();
const mimeType = new Array('image/jpeg', 'image/png', 'image/gif', 'image/bmp');
const maxFileSizeMB = 4;
const minPix = 36;
const maxPixels = 4096;
$('#img-file').on('change', function(evt) {
$('#result').html("");
file = evt.target.files[0];
if (mimeType.indexOf(file.type) == -1) {
alert('ファイル形式がJPEG、PNG、GIF、BMP以外です。');
return false;
}
if (file.size > maxFileSizeMB * 1024 * 1024) {
alert('ファイルサイズが' + maxFileSizeMB + 'MBを超えています。');
return false;
}
const reader = new FileReader();
reader.onload = (function(f) {
return function(e) {
const imgPath = e.target.result;
const img = new Image();
img.src = imgPath;
img.onload = function() {
if (img.width < minPix || img.height < minPix) {
alert('画像のサイズは、縦横ともに' + minPix + 'ピクセル以上必要です。');
return false;
}
let scale = 1;
if (img.width > img.height && img.width > maxPixels) {
scale = maxPixels / img.width;
alert('画像の最大幅が' + maxPixels + 'ピクセルを超えています。');
return false;
}
else if (img.width < img.height && img.height > maxPixels) {
scale = maxPixels / img.height;
alert('画像の最大高が' + maxPixels + 'ピクセルを超えています。');
return false;
}
canvas.width = img.width * scale;
canvas.height = img.height * scale;
context.drawImage(this, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
fd.append("img-file", toBlob(canvas.toDataURL(file.type), file.type), file.name);
};
};
})(file);
reader.readAsDataURL(file);
});
$('#submit-btn').on('click', function() {
$('#result').html("");
if (!file) {
alert('画像を選択してください。');
return false;
}
$.blockUI({
message : '<img src="/common/img/gif-load.gif" />',
css : {
color : '',
border : '',
backgroundColor : ''
},
overlayCSS : {
backgroundColor: '#FFFFFF'
}
});
$.ajax({
url : apiUrl + '?api_type=detect&returnFaceAttributes=age,gender,smile,facialHair,glasses,headPose,emotion,hair,makeup,accessories',
type : "POST",
data : fd,
processData : false,
contentType : false,
dataType : "json"
}).done(function(data, status, xhr) {
for (let i in data) {
$('#result').append(
'<span id="result' + (i + 1) + '">'
+ resultTableTemplate({
gender : data[i].faceAttributes.gender === 'male' ? '男性' : '女性',
age : data[i].faceAttributes.age,
glasses : data[i].faceAttributes.glasses === 'NoGlasses' ? 'なし' : 'あり',
anger : mathRound(data[i].faceAttributes.emotion.anger),
contempt : mathRound(data[i].faceAttributes.emotion.contempt),
disgust : mathRound(data[i].faceAttributes.emotion.disgust),
fear : mathRound(data[i].faceAttributes.emotion.fear),
happiness : mathRound(data[i].faceAttributes.emotion.happiness),
neutral : mathRound(data[i].faceAttributes.emotion.neutral),
sadness : mathRound(data[i].faceAttributes.emotion.sadness),
surprise : mathRound(data[i].faceAttributes.emotion.surprise),
eyeMakeup : data[i].faceAttributes.makeup.eyeMakeup === true ? 'あり' : 'なし',
lipMakeup : data[i].faceAttributes.makeup.lipMakeup === true ? 'あり' : 'なし',
})
+ '</span><br />'
);
context.beginPath();
if (i % 7 === 0) {
context.strokeStyle = 'rgb(255, 0, 0)';
$('#result' + (i + 1)).css('color', '#FF0000');
}
else if (i % 7 === 1) {
context.strokeStyle = 'rgb(0, 255, 0)';
$('#result' + (i + 1)).css('color', '#00FF00');
}
else if (i % 7 === 2) {
context.strokeStyle = 'rgb(0, 0, 255)';
$('#result' + (i + 1)).css('color', '#0000FF');
}
else if (i % 7 === 3) {
context.strokeStyle = 'rgb(0, 255, 255)';
$('#result' + (i + 1)).css('color', '#00FFFF');
}
else if (i % 7 === 4) {
context.strokeStyle = 'rgb(255, 255, 0)';
$('#result' + (i + 1)).css('color', '#FFFF00');
}
else if (i % 7 === 5) {
context.strokeStyle = 'rgb(255, 0, 255)';
$('#result' + (i + 1)).css('color', '#FF00FF');
}
else if (i % 7 === 6) {
context.strokeStyle = 'rgb(0, 0, 0)';
$('#result' + (i + 1)).css('color', '#000000');
}
context.strokeRect(data[i].faceRectangle.left, data[i].faceRectangle.top, data[i].faceRectangle.width, data[i].faceRectangle.height);
}
}).fail(function(xhr, status, error) {
console.log(JSON.stringify(xhr));
console.log(status);
console.log(error);
}).always(function() {
$(document).ajaxStop($.unblockUI);
});
});
});
}).call(this);
</script>
</body>
</html>
----- ここまで -----
face_api.php
----- ここから -----
<?php
require_once 'HTTP/Request2.php';
if ($_FILES['img-file']['tmp_name']) {
$req_param = "";
foreach ($_GET as $key => $value) {
if ($key != 'api_type') {
$req_param .= '&' . $key . '=' . $value;
}
}
if ($req_param) {
$req_param = ltrim($req_param, '&');
$req_param = '?' . $req_param;
}
$request = new Http_Request2('https://westus.api.cognitive.microsoft.com/face/v1.0/' . $_GET['api_type'] . $req_param);
$request->setAdapter('curl');
foreach ($_GET as $key => $value) {
if ($key != 'api_type') {
$req_param .= '&' . $key . '=' . $value;
}
}
if ($req_param) {
$req_param = ltrim($req_param, '&');
$req_param = '?' . $req_param;
}
$request = new Http_Request2('https://westus.api.cognitive.microsoft.com/face/v1.0/' . $_GET['api_type'] . $req_param);
$request->setAdapter('curl');
$headers = array(
'Content-Type' => 'application/json',
'Ocp-Apim-Subscription-Key' => 'Azureの管理ポータルでコピーしたキー文字列',
);
$request->setHeader($headers);
$request->setMethod(HTTP_Request2::METHOD_POST);
$uploaddir = '/var/www/html/tmp/cognitive/';
$uploadfile = $uploaddir . basename($_FILES['img-file']['name']);
move_uploaded_file($_FILES['img-file']['tmp_name'], $uploadfile);
$url_base = 'http://www.hpi-portal.com/tmp/cognitive/';
$url = $url_base . basename($_FILES['img-file']['name']);
$data = array('url' => $url);
$request->setBody(json_encode($data));
try {
$response = $request->send();
echo $response->getBody();
if (file_exists($uploadfile)) {
unlink($uploadfile);
}
}
catch (HttpException $ex) {
echo $ex;
if (file_exists($uploadfile)) {
unlink($uploadfile);
}
}
}
?>
$request->setMethod(HTTP_Request2::METHOD_POST);
$uploaddir = '/var/www/html/tmp/cognitive/';
$uploadfile = $uploaddir . basename($_FILES['img-file']['name']);
move_uploaded_file($_FILES['img-file']['tmp_name'], $uploadfile);
$url_base = 'http://www.hpi-portal.com/tmp/cognitive/';
$url = $url_base . basename($_FILES['img-file']['name']);
$data = array('url' => $url);
$request->setBody(json_encode($data));
try {
$response = $request->send();
echo $response->getBody();
if (file_exists($uploadfile)) {
unlink($uploadfile);
}
}
catch (HttpException $ex) {
echo $ex;
if (file_exists($uploadfile)) {
unlink($uploadfile);
}
}
}
?>
----- ここまで -----
土曜日, 1月 07, 2017
初Mojolicious::Liteアプリサンプル
OS: CentOS 7.3
Perl: 5.24.0
Mojolicious: 7.14
DBI: 1.636
DBIx::Connector: 0.56
DBD::ODBC: 1.56
----- ここから -----
#!/usr/bin/env perl
use strict;
use warnings;
use Mojolicious::Lite;
use DBIx::Connector;
my $db_host = 'db1';
my $dsn = 'dbi:ODBC:' . $db_host;
my $db_name = 'sample';
my $db_user = 'db_user_name';
my $db_passwd = 'db_user_passwd';
# DB接続
my $conn = DBIx::Connector->new($dsn, $db_user, $db_passwd) or die $!;
helper db => sub { return $conn->dbh; };
# サイトID取得
helper get_site_id => sub {
my $self = shift;
my $stmt = "SELECT [site_id] FROM $db_name.$db_user.site";
my $sth = $self->db->prepare($stmt) or die $self->db->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute or die $self->db->errstr;
my @site_ids = ();
while (my @row = $sth->fetchrow_array) {
push @site_ids, $row[0];
}
$sth->finish;
return \@site_ids;
};
# サイトプロパティ取得
helper get_site_properties => sub {
my $self = shift;
$site_id = $_[0];
if ($site_id) {
my $stmt = "SELECT [property_key], [property_value], [description] FROM $db_name.$db_user.site_properties WHERE site_id = ?";
my $sth = $self->db->prepare($stmt) or die $self->db->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($site_id) or die $self->db->errstr;
my @properties = ();
while (my @row = $sth->fetchrow_array) {
push @properties, {'property_key' => $row[0], 'property_value' => $row[1], 'description' => $row[2]};
}
$sth->finish;
return \@properties;
}
};
# ベースルートの設定
any '/' => sub {
my $self = shift;
my $site_ids = $self->get_site_id();
$self->stash( site_ids => $site_ids );
$self->render('index');
};
# サイトプロパティ取得用ルートの設定
any '/site_properties' => sub {
my $self = shift;
my $site_id = $self->param('site_id');
my $properties = $self->get_site_properties($site_id);
$self->render(json => $properties);
};
app->start;
__DATA__
@@ index.html.ep
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="robots" content="noindex,nofollow" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>e2R Utilities</title>
<%= stylesheet '//www.foobar.com/common/css/bootstrap.min.css' %>
<%= stylesheet '//www.foobar.com/common/css/bootstrap-theme.min.css' %>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<h3>ユーティリティ</h3>
</div>
</div>
<br />
<div class="row">
<div class="col-md-12">
<div class="well well-sm">
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="site_id" class="col-sm-2 control-label">サイトID</label>
<div class="col-sm-6">
<select name="site_id" class="form-control">
<option value=""></option>
% foreach my $site_id (@$site_ids) {
<option value="<%= $site_id %>"><%= $site_id %></option>
% }
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" id="site-property-btn" class="btn btn-primary">サイトプロパティ表示</button>
</div>
</div>
</form>
</div>
</div>
</div>
<br />
<div class="row">
<div class="col-md-12">
<div id="site-property-block">
<strong>サイトプロパティ</strong><br />
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>キー</th>
<th>値</th>
<th>説明</th>
</tr>
</thead>
<tbody id="site-property-list"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- template -->
<script type="text/template" id="property-list-template">
<tr>
<td>{{ property_key }}</td>
<td>{{ property_value }}</td>
<td>{{ description }}</td>
</tr>
</script>
<!-- javascript -->
%= javascript '//www.foobar.com/common/js/underscore.min.js';
%= javascript '//www.foobar.com/common/js/jquery.min.js';
%= javascript begin
(function() {
var root = this,
_ = root._,
$ = root.jQuery;
_.templateSettings = {
interpolate: /\{\{(.+?)\}\}/g
};
$.ajaxSettings.contentType
= 'application/x-www-form-urlencoded; charset=UTF-8';
var propertyListTemplate
= _.template($("#property-list-template").html());
var escapeHtml = function(string) {
if (typeof string !== 'string') {
return string;
}
return string.replace(/[&'`"<>]/g, function(match) {
return {
'&': '&',
"'": ''',
'`': '`',
'"': '"',
'<': '<',
'>': '>',
}[match]
});
};
$(function() {
$('#site-property-block').hide();
$('#site-property-btn').on('click', function() {
$('#site-property-block').hide();
if (!$('[name=site_id]').val()) {
alert('サイトIDを選択してください。');
return false;
}
$.ajax({
url : '/app/site_properties',
type : "POST",
cache : false,
dataType : "json",
data : {
"site_id" : $('[name=site_id]').val()
}
}).done(function(json) {
var propertyListHtml = "";
_.each(json, function(value) {
propertyListHtml
+= propertyListTemplate({
property_key : value["property_key"],
property_value : escapeHtml(value["property_value"]),
description : value["description"]
});
});
$('#site-property-list').html(propertyListHtml);
$('#site-property-block').show();
}).fail(function() {
alert("サイトプロパティの取得に失敗しました。");
return false;
});
});
});
}).call(this);
% end
</body>
</html>
Perl: 5.24.0
Mojolicious: 7.14
DBI: 1.636
DBIx::Connector: 0.56
DBD::ODBC: 1.56
----- ここから -----
#!/usr/bin/env perl
use strict;
use warnings;
use Mojolicious::Lite;
use DBIx::Connector;
my $db_host = 'db1';
my $dsn = 'dbi:ODBC:' . $db_host;
my $db_name = 'sample';
my $db_user = 'db_user_name';
my $db_passwd = 'db_user_passwd';
# DB接続
my $conn = DBIx::Connector->new($dsn, $db_user, $db_passwd) or die $!;
helper db => sub { return $conn->dbh; };
# サイトID取得
helper get_site_id => sub {
my $self = shift;
my $stmt = "SELECT [site_id] FROM $db_name.$db_user.site";
my $sth = $self->db->prepare($stmt) or die $self->db->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute or die $self->db->errstr;
my @site_ids = ();
while (my @row = $sth->fetchrow_array) {
push @site_ids, $row[0];
}
$sth->finish;
return \@site_ids;
};
# サイトプロパティ取得
helper get_site_properties => sub {
my $self = shift;
$site_id = $_[0];
if ($site_id) {
my $stmt = "SELECT [property_key], [property_value], [description] FROM $db_name.$db_user.site_properties WHERE site_id = ?";
my $sth = $self->db->prepare($stmt) or die $self->db->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($site_id) or die $self->db->errstr;
my @properties = ();
while (my @row = $sth->fetchrow_array) {
push @properties, {'property_key' => $row[0], 'property_value' => $row[1], 'description' => $row[2]};
}
$sth->finish;
return \@properties;
}
};
# ベースルートの設定
any '/' => sub {
my $self = shift;
my $site_ids = $self->get_site_id();
$self->stash( site_ids => $site_ids );
$self->render('index');
};
# サイトプロパティ取得用ルートの設定
any '/site_properties' => sub {
my $self = shift;
my $site_id = $self->param('site_id');
my $properties = $self->get_site_properties($site_id);
$self->render(json => $properties);
};
app->start;
__DATA__
@@ index.html.ep
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="robots" content="noindex,nofollow" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>e2R Utilities</title>
<%= stylesheet '//www.foobar.com/common/css/bootstrap.min.css' %>
<%= stylesheet '//www.foobar.com/common/css/bootstrap-theme.min.css' %>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<h3>ユーティリティ</h3>
</div>
</div>
<br />
<div class="row">
<div class="col-md-12">
<div class="well well-sm">
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="site_id" class="col-sm-2 control-label">サイトID</label>
<div class="col-sm-6">
<select name="site_id" class="form-control">
<option value=""></option>
% foreach my $site_id (@$site_ids) {
<option value="<%= $site_id %>"><%= $site_id %></option>
% }
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" id="site-property-btn" class="btn btn-primary">サイトプロパティ表示</button>
</div>
</div>
</form>
</div>
</div>
</div>
<br />
<div class="row">
<div class="col-md-12">
<div id="site-property-block">
<strong>サイトプロパティ</strong><br />
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>キー</th>
<th>値</th>
<th>説明</th>
</tr>
</thead>
<tbody id="site-property-list"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- template -->
<script type="text/template" id="property-list-template">
<tr>
<td>{{ property_key }}</td>
<td>{{ property_value }}</td>
<td>{{ description }}</td>
</tr>
</script>
<!-- javascript -->
%= javascript '//www.foobar.com/common/js/underscore.min.js';
%= javascript '//www.foobar.com/common/js/jquery.min.js';
%= javascript begin
(function() {
var root = this,
_ = root._,
$ = root.jQuery;
_.templateSettings = {
interpolate: /\{\{(.+?)\}\}/g
};
$.ajaxSettings.contentType
= 'application/x-www-form-urlencoded; charset=UTF-8';
var propertyListTemplate
= _.template($("#property-list-template").html());
var escapeHtml = function(string) {
if (typeof string !== 'string') {
return string;
}
return string.replace(/[&'`"<>]/g, function(match) {
return {
'&': '&',
"'": ''',
'`': '`',
'"': '"',
'<': '<',
'>': '>',
}[match]
});
};
$(function() {
$('#site-property-block').hide();
$('#site-property-btn').on('click', function() {
$('#site-property-block').hide();
if (!$('[name=site_id]').val()) {
alert('サイトIDを選択してください。');
return false;
}
$.ajax({
url : '/app/site_properties',
type : "POST",
cache : false,
dataType : "json",
data : {
"site_id" : $('[name=site_id]').val()
}
}).done(function(json) {
var propertyListHtml = "";
_.each(json, function(value) {
propertyListHtml
+= propertyListTemplate({
property_key : value["property_key"],
property_value : escapeHtml(value["property_value"]),
description : value["description"]
});
});
$('#site-property-list').html(propertyListHtml);
$('#site-property-block').show();
}).fail(function() {
alert("サイトプロパティの取得に失敗しました。");
return false;
});
});
});
}).call(this);
% end
</body>
</html>
----- ここまで -----
Mojolicious::LiteのWebアプリを実行する環境設定
OSはCentOS 7.3。
アプリはStarman(PSGIサーバ)で実行する。
1. perlbrewのインストール
$ curl -kL http://install.perlbrew.pl | bash
$
$ echo "source ~/perl5/perlbrew/etc/bashrc" >> ~/.bashrc
$ source ~/.bashrc
2. 最新安定版Perlをperlbrewでインストール
$ perlbrew available
$
$ perlbrew install 5.24.0
$
$ perlbrew list
$ perlbrew switch 5.24.0
$ which perl
$ perl -v
3. cpanmのインストール
$ perlbrew install-cpanm
4. Mojoliciousのインストール
$ cpanm Mojolicious
5. DBD::ODBCとDBIx::Connectorのインストール
$ cpanm DBD::ODBC
$
$ cpanm DBIx::Connector
6. LWPのインストール
$ cpan Bundle::LWP
7. Starmanのインストール
$ cpanm Starman
8. Starman起動スクリプトサンプル
ログインIDがuser1、アプリのファイルmojo_test.plが/var/www/app/に配置されている前提。
/var/www/appにはuser1の実行・読み・書き権限があること。
----- ここから -----
#!/bin/sh
/usr/bin/su - user1 -c '/home/user1/perl5/perlbrew/perls/perl-5.24.0/bin/starman --port=3000 --daemonize --pid=/var/www/app/pid /var/www/app/mojo_test.pl'
----- ここまで -----
実行は
$ sudo starman_start.sh
のように行う。
9. Starman停止スクリプトサンプル
----- ここから -----
#!/bin/sh
/usr/bin/cat /var/www/app/pid | /usr/bin/xargs /usr/bin/kill
----- ここまで -----
アプリはStarman(PSGIサーバ)で実行する。
1. perlbrewのインストール
$ curl -kL http://install.perlbrew.pl | bash
$
$ echo "source ~/perl5/perlbrew/etc/bashrc" >> ~/.bashrc
$ source ~/.bashrc
2. 最新安定版Perlをperlbrewでインストール
$ perlbrew available
$
$ perlbrew install 5.24.0
$
$ perlbrew list
$ perlbrew switch 5.24.0
$ which perl
$ perl -v
3. cpanmのインストール
$ perlbrew install-cpanm
4. Mojoliciousのインストール
$ cpanm Mojolicious
5. DBD::ODBCとDBIx::Connectorのインストール
$ cpanm DBD::ODBC
$
$ cpanm DBIx::Connector
6. LWPのインストール
$ cpan Bundle::LWP
7. Starmanのインストール
$ cpanm Starman
8. Starman起動スクリプトサンプル
ログインIDがuser1、アプリのファイルmojo_test.plが/var/www/app/に配置されている前提。
/var/www/appにはuser1の実行・読み・書き権限があること。
----- ここから -----
#!/bin/sh
/usr/bin/su - user1 -c '/home/user1/perl5/perlbrew/perls/perl-5.24.0/bin/starman --port=3000 --daemonize --pid=/var/www/app/pid /var/www/app/mojo_test.pl'
----- ここまで -----
実行は
$ sudo starman_start.sh
のように行う。
9. Starman停止スクリプトサンプル
----- ここから -----
#!/bin/sh
/usr/bin/cat /var/www/app/pid | /usr/bin/xargs /usr/bin/kill
----- ここまで -----
実行は
$ sudo starman_stop.sh
のように行う。
10. Apacheのリバースプロキシの設定サンプル
$ sudo touch /etc/httpd/conf.d/starman.conf
$ sudo vi /etc/httpd/conf.d/starman.conf
----- ここから -----
ProxyRequests Off
ProxyPass /app http://localhost:3000
ProxyPassReverse /app http://localhost:3000
----- ここまで -----
$ sudo starman_stop.sh
のように行う。
10. Apacheのリバースプロキシの設定サンプル
$ sudo touch /etc/httpd/conf.d/starman.conf
$ sudo vi /etc/httpd/conf.d/starman.conf
----- ここから -----
ProxyRequests Off
ProxyPass /app http://localhost:3000
ProxyPassReverse /app http://localhost:3000
----- ここまで -----
LinuxからWindowsのSQL ServerにODBCで接続
以前にも似たような投稿をしているが、現時点で最新の環境であることと、以前とは少し設定が異なるので再度投稿。
LinuxはCentOS 7.3。
1. 各種RPMパッケージインストール
$ sudo yum install unixODBC*
$ sudo yum install freetds*
$ sudo yum install perl-DBI
$ sudo yum install perl-DBIx-Connector --enablerepo=epel
$ sudo yum install perl-DBD-ODBC
2. 設定ファイル編集、作成
$ sudo vi /etc/odbcinst.ini
末尾につぎの行を追加(odbc.iniで指定するドライバの設定)
----- ここから -----
[FreeTDS]
Description=FreeTDS ODBC
Driver=/usr/lib64/libtdsodbc.so
Setup=/usr/lib64/libtdsS.so
UsageCount=1
----- ここまで -----
$ sudo touch /etc/odbc.ini
$ sudo vi /etc/odbc.ini
つぎの行を追加(ホスト名でDSNの設定をする)
----- ここから -----
[db1]
Description=SQL Server 2012
Driver=FreeTDS
Trace=No
Server=192.168.10.1
Database=master
Port=1433
TDS_Version=8.0
ClientCharset=UTF-8
----- ここまで -----
3. 接続テスト
$ isql -v db1 db_user_name db_user_passwd
4. Perlサンプル
データベースsampleにカラム[group_id][user_id]を持つテーブルtest_tblがある前提。
----- ここから -----
#!/usr/bin/env perl
use strict;
use warnings;
use DBIx::Connector;
my $db_host = 'db1';
my $dsn = 'dbi:ODBC:' . $db_host;
my $db_name = 'sample';
my $db_user = 'db_user_name';
my $db_passwd = 'db_user_passwd';
my $test_user = 'connect_test_user';
my $test_group = 'connect_test_group';
my $conn = DBIx::Connector->new($dsn, $db_user, $db_passwd) or die $!;
my $dbh = $conn->dbh;
# test_tblに対象レコードがないことの確認(何も出力されない)
my $stmt = "SELECT * FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
my $sth = $dbh->prepare($stmt) or die $dbh->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($test_user) or die $dbh->errstr;
while (my @row = $sth->fetchrow_array) {
print "select1:@row\n";
}
$sth->finish;
# test_tblにレコード作成
$stmt = "INSERT INTO $db_name.$db_user.test_tbl ([group_id], [user_id]) VALUES (?, ?)";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
my $rc = $sth->execute($test_group, $test_user) or die $dbh->errstr;
print "insert:$rc\n";
$sth->finish;
# test_tblに対象レコードがあることの確認
$stmt = "SELECT * FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($test_user) or die $dbh->errstr;
while (my @row = $sth->fetchrow_array) {
print "select2:@row\n";
}
$sth->finish;
# test_tblからレコード削除
$stmt = "DELETE FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
$rc = $sth->execute($test_user) or die $dbh->errstr;
print "delete:$rc\n";
$sth->finish;
# test_tblに対象レコードがないことの確認(何も出力されない)
$stmt = "SELECT * FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($test_user) or die $dbh->errstr;
while (my @row = $sth->fetchrow_array) {
print "select3:@row\n";
}
$sth->finish;
$conn->disconnect;
exit;
----- ここまで -----
LinuxはCentOS 7.3。
1. 各種RPMパッケージインストール
$ sudo yum install unixODBC*
$ sudo yum install freetds*
$ sudo yum install perl-DBI
$ sudo yum install perl-DBIx-Connector --enablerepo=epel
$ sudo yum install perl-DBD-ODBC
2. 設定ファイル編集、作成
$ sudo vi /etc/odbcinst.ini
末尾につぎの行を追加(odbc.iniで指定するドライバの設定)
----- ここから -----
[FreeTDS]
Description=FreeTDS ODBC
Driver=/usr/lib64/libtdsodbc.so
Setup=/usr/lib64/libtdsS.so
UsageCount=1
----- ここまで -----
$ sudo touch /etc/odbc.ini
$ sudo vi /etc/odbc.ini
つぎの行を追加(ホスト名でDSNの設定をする)
----- ここから -----
[db1]
Description=SQL Server 2012
Driver=FreeTDS
Trace=No
Server=192.168.10.1
Database=master
Port=1433
TDS_Version=8.0
ClientCharset=UTF-8
----- ここまで -----
3. 接続テスト
$ isql -v db1 db_user_name db_user_passwd
4. Perlサンプル
データベースsampleにカラム[group_id][user_id]を持つテーブルtest_tblがある前提。
----- ここから -----
#!/usr/bin/env perl
use strict;
use warnings;
use DBIx::Connector;
my $db_host = 'db1';
my $dsn = 'dbi:ODBC:' . $db_host;
my $db_name = 'sample';
my $db_user = 'db_user_name';
my $db_passwd = 'db_user_passwd';
my $test_user = 'connect_test_user';
my $test_group = 'connect_test_group';
my $conn = DBIx::Connector->new($dsn, $db_user, $db_passwd) or die $!;
my $dbh = $conn->dbh;
# test_tblに対象レコードがないことの確認(何も出力されない)
my $stmt = "SELECT * FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
my $sth = $dbh->prepare($stmt) or die $dbh->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($test_user) or die $dbh->errstr;
while (my @row = $sth->fetchrow_array) {
print "select1:@row\n";
}
$sth->finish;
# test_tblにレコード作成
$stmt = "INSERT INTO $db_name.$db_user.test_tbl ([group_id], [user_id]) VALUES (?, ?)";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
my $rc = $sth->execute($test_group, $test_user) or die $dbh->errstr;
print "insert:$rc\n";
$sth->finish;
# test_tblに対象レコードがあることの確認
$stmt = "SELECT * FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($test_user) or die $dbh->errstr;
while (my @row = $sth->fetchrow_array) {
print "select2:@row\n";
}
$sth->finish;
# test_tblからレコード削除
$stmt = "DELETE FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
$rc = $sth->execute($test_user) or die $dbh->errstr;
print "delete:$rc\n";
$sth->finish;
# test_tblに対象レコードがないことの確認(何も出力されない)
$stmt = "SELECT * FROM $db_name.$db_user.test_tbl WHERE user_id = ?";
$sth = $dbh->prepare($stmt) or die $dbh->errstr;
$sth->{LongTruncOk}=1;
$sth->{LongReadLen}=2000000;
$sth->execute($test_user) or die $dbh->errstr;
while (my @row = $sth->fetchrow_array) {
print "select3:@row\n";
}
$sth->finish;
$conn->disconnect;
exit;
----- ここまで -----
登録:
投稿 (Atom)