AzurよりSendGridを使用してメールを送信する
Azureでのメール送信はSendGridらしい
詳しくは知らないけどSendGridの送信手段を調べてみました。
環境
CentOS7
PHP7
Azure設定
サービスを追加
アプリ名やパスワードを設定
料金プランを設定
アクセス情報を設定
同意確認を行う
作成ボタンを押下
ダッシュボードに作成されたことを確認
Azureの管理画面より「Manage」を押下してSendGridの管理ページへジャンプ
メールを送信して認証チェックを行う
メール送信完了画面
自分のメールクライアントよりメールを受信してボタンを押下
認証後はそのまま管理画面を表示
APIキー作成画面を表示
作成ボタンを押下して作成画面を表示
APIキー名と権限を設定
キーの値をクリックしてコピーする
Azureの設定画面で資格情報を取得
※パスワードは設定したもの、ユーザ名はこれを使用することでメール送信のパラメータとなります。
送信用設定
yum
udo rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm sudo yum install --enablerepo=remi,remi-php70 php php-devel php-mbstring php-pdo php-gd php-xml php-intl
composer
mkdir -p sample1 cd sample1 php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('SHA384', 'composer-setup.php') === '669656bab3166a7aff8a7506b8cb2d1c292f042046c5a994c43155c0be6190fa0355160742ab2e1c88d40d5be660b410') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" php composer-setup.php php -r "unlink('composer-setup.php');" php composer.phar require swiftmailer/swiftmailer
composer.json
{ "require": { "swiftmailer/swiftmailer": "5.3.0" } }
実装
sendmail1.php
<?php include_once "vendor/autoload.php"; // 本文 $text = "Hi!\nHow are you?\n"; // 送信元 $from = array( '送信元メールアドレス' => 'sample_from' ); // 送信先 $to = array( '送信先メールアドレス' => 'sample_to', ); // タイトル $subject = 'Example PHP Email'; // ユーザ情報 $username = '資格情報のユーザ名'; $password = '資格情報のパスワード'; // 送信設定 $transport = Swift_SmtpTransport::newInstance('smtp.sendgrid.net', 587); $transport->setUsername($username); $transport->setPassword($password); $swift = Swift_Mailer::newInstance($transport); // メッセージ生成 $message = new Swift_Message($subject); // 送信情報設定 $message->setFrom($from); $message->setBody($text, 'text/plain'); $message->setTo($to); // 送信処理 if ($recipients = $swift->send($message, $failures)) { echo "送信成功"; } else { echo "送信失敗"; }
sendmail2.php
<?php // 接続情報 $url = 'https://api.sendgrid.com/'; $user = '資格情報より取得したユーザ名'; $pass = '資格情報より取得したパスワード'; // 送信情報 $params = array( 'api_user' => $user, 'api_key' => $pass, 'to' => '送信先のメールアドレス', 'subject' => 'testing from curl', 'text' => 'testing body', 'from' => '送信元のメールアドレス', ); // リクエスト先設定 $request = $url.'api/mail.send.json'; // curl初期化 $session = curl_init($request); // curl設定(POST送信) curl_setopt ($session, CURLOPT_POST, true); // curl設定(パラメータ設定) curl_setopt ($session, CURLOPT_POSTFIELDS, $params); // curl設定(ヘッダー不要、レスポンス必要?) curl_setopt($session, CURLOPT_HEADER, false); curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // 送信して終了 $response = curl_exec($session); curl_close($session); // 処理結果 print_r ($response);
※WEB APIを使用してメール送信するサンプルでもユーザとパスワードは資格情報のものを使用します。
所感
参考にしたドキュメントの通りにやることでなんとかできた。
最近は日本語のドキュメントが豊富なので環境を用意するのも簡単で助かります。
一部英語でわからないこともあるけど
まあなんとかなるかな…
Azure App FunctionsでLINE Botへプッシュ送信してみる
準備
LINEの開発アカウントは取得しておく
LINE Business Center
App Functionsを用意
前回のものを利用
スクリプト修正
index.js
module.exports = function (context, myBlob) { // Blob情報 var msg = "blobInfo " + "\n" + "Name:" + context.bindingData.name + "\n" + "Size:" + myBlob.length + "Bytes" + "\n"; var accessToken = "チャネルアクセストークン"; var request = require('request'); var options = { uri: "https://api.line.me/v2/bot/message/push", method: "POST", headers: { "Content-type": "application/json", "Authorization": "Bearer " + accessToken }, json: { "to": "テスト用のプッシュID", "messages":[{ "type": "text", "text": msg }] } }; request.post(options, function(error, response, body){ context.log("error"); }); context.done(); };
実行
所感
なんかちょっと遅く感じるような…
設定などに応じて時間が少しかかっているのかもしれない
まあ、動作することは確認できたのでOKとする
Azure App Functionsをやってみる
今回やること
Blobへアップロードすることは前回やったのでこれをトリガーとしてコンソールにログを出力するだけのサンプルを作成する。
Azure
新規作成
Function Appを選択
作成を選択
アプリ名と他の設定を行い、作成する
ダッシュボードで作成されていることを確認
関数を追加
カスタム関数を作成する
BlobTrigger(Javascript)を選択する
作成を選択する
Blobのストレージへファイルをアップロードする
参考
Azure Functions: BlobストレージをトリガーとするPython関数を作成してみる|Azure Machine Learning | | ナレコムazureレシピ
Azure Functions × LINE Bot をNode.jsでやってみよう - Qiita
所感
簡単な起動は確認できた、他のWebAPIへ投げたりできるか確認して見る。
Azure Strage File ApiをPHPでBLOBを取得
仕事がやっと
落ち着いた感じがする。
特別忙しいというわけではないがタイミングなどの問題もあってブログを書くことを忘れてしまっているのはマズイ
忘れていることもあるので勉強を再開してみる。
Azure Strage File
簡単にいうとAWSのS3と思う、厳密には違うと思うけど一部のデータを保存したり取得したりするのでそのレベルの認識で覚えておく
今回は設定して登録したデータをphpで取得することを行う。
Azureの設定
ストレージアカウントを選択する
追加ボタンを押下
アカウント名と他の設定を行い、作成ボタンを押下
作成されたことを確認し、選択する
コンテナを追加する
コンテナ名を設定してOKを押下
アクセスキーをコピーする
準備
php
sudo rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm sudo yum install --enablerepo=remi,remi-php70 php php-devel php-mbstring php-pdo php-gd php-xml
compoer
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('SHA384', 'composer-setup.php') === '669656bab3166a7aff8a7506b8cb2d1c292f042046c5a994c43155c0be6190fa0355160742ab2e1c88d40d5be660b410') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" php composer-setup.php php -r "unlink('composer-setup.php');"
compoer.json
{ "require": { "microsoft/windowsazure": "^0.4" } }
設定情報
setting.php
<?php $connectionString = "DefaultEndpointsProtocol=https;AccountName=mshige1979sample1;AccountKey=事前にコピーしたアクセスキー";
ファイルを追加
sample_add.php
<?php require_once 'vendor/autoload.php'; require_once 'setting.php'; use WindowsAzure\Common\ServicesBuilder; use MicrosoftAzure\Storage\Common\ServiceException; // 接続文字列を使用して、Blobサービスのインスタンスを作成する $blobRestProxy = ServicesBuilder::getInstance()->createBlobService($connectionString); // 取得処理 try { // ファイル名 $date = "testdata_" . date("YmdHis") . ".txt"; echo $date . "\n"; // データの中身 $data = md5(uniqid());; echo $data . "\n"; // 追加処理 $blobRestProxy->createBlockBlob("test01", $date, $data); } catch(ServiceException $e) { // 例外処理 $code = $e->getCode(); $error_message = $e->getMessage(); echo $code.": ".$error_message."\n"; }
↓
$ php sample_add.php testdata_20170716233116.txt 08a8e1964d2c0dadc558bd4f54b3de5a $ php sample_add.php testdata_20170716233127.txt 6ff9937a10cbc58bee43367299f49a80 $
一覧取得
sample1.php
<?php require_once 'vendor/autoload.php'; require_once 'setting.php'; use WindowsAzure\Common\ServicesBuilder; use MicrosoftAzure\Storage\Common\ServiceException; // 接続文字列を使用して、Blobサービスのインスタンスを作成する $blobRestProxy = ServicesBuilder::getInstance()->createBlobService($connectionString); // 取得処理 try { // コンテナのオブジェクトを取得 $blob_list = $blobRestProxy->listBlobs("test01"); $blobs = $blob_list->getBlobs(); // 一覧を出力 foreach ($blobs as $blob) { echo $blob->getName().": ".$blob->getUrl()."\n"; } } catch(ServiceException $e) { // 例外処理 $code = $e->getCode(); $error_message = $e->getMessage(); echo $code.": ".$error_message."\n"; }
↓
$ php sample_list.php testdata_20170716233116.txt: https://mshige1979sample1.blob.core.windows.net/test01/testdata_20170716233116.txt testdata_20170716233127.txt: https://mshige1979sample1.blob.core.windows.net/test01/testdata_20170716233127.txt $
読込処理
sample_get.php
<?php require_once 'vendor/autoload.php'; require_once 'setting.php'; use WindowsAzure\Common\ServicesBuilder; use MicrosoftAzure\Storage\Common\ServiceException; // 接続文字列を使用して、Blobサービスのインスタンスを作成する $blobRestProxy = ServicesBuilder::getInstance()->createBlobService($connectionString); // 取得処理 try { // ファイル名 $date = "testdata_20170716233116.txt"; // 取得処理 $blob = $blobRestProxy->getBlob("test01", $date); echo stream_get_contents($blob->getContentStream()) . "\n"; } catch(ServiceException $e) { // 例外処理 $code = $e->getCode(); $error_message = $e->getMessage(); echo $code.": ".$error_message."\n"; }
↓
$ php sample_get.php 08a8e1964d2c0dadc558bd4f54b3de5a $
参考
CentOS6/CentOS7にPHP5.6/PHP7をyumでインストール - Qiita
Composer
15分で出来るPHPからAzure BLOBストレージを使用する方法 | ナレコムazureレシピ
Azure Storageへのアクセス方法 【Shared Access Signatures (SAS)】 - Qiita
まとめ
AWSとAzureはそれぞれ勝手が違う。
このレベルのことは基本として理解しておきたい、やっていないからわかりませんということは仕方がないにしても
ITに携わるものとして新しい技術に触れ続けていくことは重要と思うので…
AWSのSESで送信者名を指定する
実装
import java.io.UnsupportedEncodingException; import javax.mail.internet.InternetAddress; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient; import com.amazonaws.services.simpleemail.model.Body; import com.amazonaws.services.simpleemail.model.Content; import com.amazonaws.services.simpleemail.model.Destination; import com.amazonaws.services.simpleemail.model.Message; import com.amazonaws.services.simpleemail.model.SendEmailRequest; public class Sample01 { private static final String ACCESS_KEY = "アクセスキー"; private static final String SECRET_ACCESS_KEY = "シークレットアクセスキー"; public static void main(String[] args) { String TO = "送信先のメールアドレス"; String FROM = "送信元のメールアドレス"; String SUBJECT = "テストメール"; String BODY = "テストメールです。"; // 宛先 Destination destination = new Destination().withToAddresses(new String[]{TO}); // 件名 Content subject = new Content().withData(SUBJECT); // 本文 Body body = new Body().withText(new Content().withData(BODY)); // メッセージ Message message = new Message() .withSubject(subject) .withBody(body); // リクエストを作成 SendEmailRequest request = new SendEmailRequest() .withSource(senderAddressBuilder(FROM,"送信者です")) .withDestination(destination) .withMessage(message); // 認証 AmazonSimpleEmailServiceClient client = new AmazonSimpleEmailServiceClient( new BasicAWSCredentials(ACCESS_KEY, SECRET_ACCESS_KEY)); // リージョンを設定 client.setRegion(Region.getRegion(Regions.US_WEST_2)); // メール送信 client.sendEmail(request); } private static String senderAddressBuilder(String fromAddress, String senderName) { try { InternetAddress address = new InternetAddress(fromAddress, senderName, "ISO-2022-JP"); return address.toString(); } catch (UnsupportedEncodingException e) { System.out.println("Internet Address Constructor throws UnsupportedEncoding"); } return fromAddress; } }
文字コードは任意で設定すればいいかも
別に必須ではないかもしれません。
address.toString()である程度はいい感じに対応してくれそうです。
Spring Bootで画面からAPIへRest Templateでファイルを送信する
シンプルな使い方
jsonで送信する
難しそうな使い方
fileを送信する
やりたいこと
画面からsubmitまたはajaxでファイルを送信する。
問題点
処理の流れが
①画面でファイルを指定する
②画面でsubmitする
③Rest Templateへファイルを送信する
この場合、ファイルオブジェクトの情報をRest Templateへ渡す必要がありますが、MultipartFileをそのまま渡すことはできなかった。
別のファイルを出力したりしないといけないのかと思っていました…
Rest Templateのサンプル
Sample
package com.example.web; import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import com.example.form.UploadForm; @Controller public class UploadController { private static final Logger logger = LoggerFactory.getLogger(UploadController.class); @RequestMapping(value = "/upload", method = {RequestMethod.POST}) public String upload(Model model, @ModelAttribute UploadForm form) throws IOException { logger.info(form.getName()); logger.info(form.getUploadFile().getName()); logger.info(form.getUploadFile().getOriginalFilename()); logger.info(String.valueOf(form.getUploadFile().getSize())); RestTemplate restTemplate = new RestTemplate(); MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>(); ByteArrayResource contentsAsResource = new ByteArrayResource(form.getUploadFile().getBytes()){ @Override public String getFilename(){ return form.getUploadFile().getOriginalFilename(); } }; map.add("name", form.getName()); map.add("uploadFile", contentsAsResource); HttpHeaders formHeaders = new HttpHeaders(); formHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<MultiValueMap<String, Object>>(map, formHeaders); ResponseEntity<String> resposne = restTemplate.exchange("http://localhost:8080/api/upload", HttpMethod.POST, formEntity, String.class); return "/upload"; } }
MultipartFileのデータをByteArrayResourceへ再定義したものを用意して送信することでRestTemplateはファイルとして認識して送信してくれる感じ
受信データ自体はよしなに対応すればいい感じかと…
受信側
package com.example.api; import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.example.dto.UploadDto; import com.example.web.UploadController; @RestController public class ApiUploadController { private static final Logger logger = LoggerFactory.getLogger(ApiUploadController.class); @RequestMapping(value = "/api/upload", method = {RequestMethod.POST}) public String upload(UploadDto reqDto) throws IOException { logger.info("/api/upload" + "start"); logger.info(reqDto.getName()); logger.info(reqDto.getUploadFile().getName()); logger.info(reqDto.getUploadFile().getOriginalFilename()); logger.info(new String(reqDto.getUploadFile().getBytes(), "UTF-8")); logger.info("/api/upload" + "end"); return "aaaaa"; } }
Form、DTO
package com.example.form; import java.io.Serializable; import org.springframework.web.multipart.MultipartFile; public class UploadForm implements Serializable { /** * */ private static final long serialVersionUID = 1L; private String name; private MultipartFile uploadFile; public String getName() { return name; } public void setName(String name) { this.name = name; } public MultipartFile getUploadFile() { return uploadFile; } public void setUploadFile(MultipartFile uploadFile) { this.uploadFile = uploadFile; } }
参考
spring mvc - Sending Multipart File as POST parameters with RestTemplate requests - Stack Overflow
所感
まあ、いろいろ探してみるもんかと思いました。
最初はできないような気がすると思っていましたがよくよく考えたらそのあたりのことを考慮していないわけがないと思いましたし…
Springは難しいですね(^^)
WatsonのVisualRecognitionに画像をURLで指定
やること
WatsonのVisualRecognitionへ画像情報を渡す際、ファイルとして送るのではなく、AWSのS3で作成した期限付きURL情報を渡して結果を取得したい。
実装
Sample1.java
import java.io.File; import java.util.Arrays; import java.util.List; import com.ibm.watson.developer_cloud.visual_recognition.v3.*; import com.ibm.watson.developer_cloud.visual_recognition.v3.model.ClassifyImagesOptions; import com.ibm.watson.developer_cloud.visual_recognition.v3.model.VisualClassification; public class Sample01 { private static final String API_KEY = "作成したAPIキー"; public static void main(String[] args) { // TODO Auto-generated method stub System.out.println( "VisualRecognition classify start" ); // インスタンス生成 VisualRecognition service = new VisualRecognition(VisualRecognition.VERSION_DATE_2016_05_20); service.setApiKey(API_KEY); // classifierIds用 List<String> list = Arrays.asList("分類器ID"); // スコアを取得 ClassifyImagesOptions options = new ClassifyImagesOptions.Builder() .classifierIds(list) .threshold(0.0) .url("https://awsuploadsample.s3-ap-northeast-1.amazonaws.com/dalmation_0012.jpg?response-content-type=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170205T071927Z&X-Amz-SignedHeaders=host&X-Amz-Expires=431989&X-Amz-Credential=AKIAIEUCF6U4XJIMGKYQ%2F20170205%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=f9a857f597db9fc07c099aa82c7d12573d9943acd55b3b6e7343ad9cdfa36064") .build(); VisualClassification result = service.classify(options).execute(); // 結果を表示 System.out.println(result); System.out.println( "VisualRecognition classify end" ); } }
↓
VisualRecognition classify start { "images_processed": 1, "images": [ { "classifiers": [ { "classifier_id": "dogs_605755035", "name": "dogs", "classes": [ { "class": "beagle", "score": 0.0529675 }, { "class": "dalmation", "score": 0.536878 } ] } ], "source_url": "https://awsuploadsample.s3-ap-northeast-1.amazonaws.com/dalmation_0012.jpg?response-content-type\u003dimage%2Fjpeg\u0026X-Amz-Algorithm\u003dAWS4-HMAC-SHA256\u0026X-Amz-Date\u003d20170205T071927Z\u0026X-Amz-SignedHeaders\u003dhost\u0026X-Amz-Expires\u003d431989\u0026X-Amz-Credential\u003dAKIAIEUCF6U4XJIMGKYQ%2F20170205%2Fap-northeast-1%2Fs3%2Faws4_request\u0026X-Amz-Signature\u003df9a857f597db9fc07c099aa82c7d12573d9943acd55b3b6e7343ad9cdfa36064", "resolved_url": "https://awsuploadsample.s3-ap-northeast-1.amazonaws.com/dalmation_0012.jpg?response-content-type\u003dimage%2Fjpeg\u0026X-Amz-Algorithm\u003dAWS4-HMAC-SHA256\u0026X-Amz-Date\u003d20170205T071927Z\u0026X-Amz-SignedHeaders\u003dhost\u0026X-Amz-Expires\u003d431989\u0026X-Amz-Credential\u003dAKIAIEUCF6U4XJIMGKYQ%2F20170205%2Fap-northeast-1%2Fs3%2Faws4_request\u0026X-Amz-Signature\u003df9a857f597db9fc07c099aa82c7d12573d9943acd55b3b6e7343ad9cdfa36064" } ] } VisualRecognition classify end
できました(^^)
所感
APIでデータを送信する際、ファイルオブジェクトではなく、URL文字列でもきちんと判定してくれることがわかりました。
ローカル環境にファイルを配置できない場合などに使える感じです。