postfixでのメール受信時ウィルスチェック(ClamAntiVirus)でメール削除、受信者への通知を行えるようにする
目的
postfixでメールを受信したときにClamAVのウィルスチェックを行い、チェックに引っかかったものはメール削除、および受信者に対してメールの送信があったことを通知できるようにする。
procmailからpythonを呼び出したのだが、procmail周りの情報が少なくてこずったのでメモを残しておきます。
○postfix設定
postfixがメール受信時にprocmailを実行するようにmaibox_commandを設定しておく
・/etc/postfix/main.cf
mailbox_command = /usr/bin/procmail
○procmail設定
受信者全員に対して処理を行うので/etc/procmailrcに設定を記述する。
メール受信時にclamAVの常駐プロセス(clamd)でスキャンし、スキャン結果を新規でメールヘッダ(x-virus)に追加する。
それからprocmailのレシピでメールヘッダ(x-virus)の値を見て、ウィルス判定されていたらメール削除、メール送信があったことを受信者へ通知できるようにする。
procmailからpythonスクリプトを呼び出す場合は、以下のようにprocmailのシェル呼び出しのアクションを実行する
|/usr/bin/python /var/procmail/sh/mail.python
python側では標準入力からメールヘッダ、本文の内容が入力されるので、これを使って処理する。
標準入力からの受け取り方を知らなかったのでここら辺でもちょっとてこずった、サンプルをあげると以下のようになる。
import sys if __name__ == "__main__": param = sys.argv print param f=open('/home/user/mail.txt', 'w') lines = sys.stdin.readlines() for line in lines: f.write(line.encode('utf-8')) f.close()
でprocmail側の処理をまとめると以下のようになる。
パスを設定 PATH=/bin:/usr/bin:/usr/local/bin # メールボックスの設定 MAILDIR=$HOME/Maildir DEFAULT=$MAILDIR/ # Procmailのログファイル出力先を指定 LOGFILE=$MAILDIR/procmaillog # ロックファイルのパスを指定 LOCKFILE=$HOME/.lockmail # Clam AntiVirusによるウィルスチェック AV_REPORT=`/usr/bin/clamdscan --no-summary - 2>&1| awk -F\n -v ORS=' ' '{print}'|awk '{print $NF}'` VIRUS=`if [ "$AV_REPORT" != "OK" ]; then echo Yes; else echo No;fi` :0fw | formail -i "X-Virus: $VIRUS" # Clam AntiVirusによるウィルスチェック # Clam AntiVirusがウィルス判定したメール通知後削除 :0 * ^X-Virus: Yes |/usr/bin/python /var/procmail/sh/mail.python `echo "$DEFAULT"` :0 * ^X-Virus: Yes /dev/null
pythonに対して環境変数DEFAULTを渡しているのがポイントで、これを通知メールの送付先の情報として使っています。
もともとはメールヘッダのTOを見て通知しようとしていたのですが、よくよく考えると複数人に対してメールの送信
があると1人ずつprocmail実行されるので問題だと思ってやめました。
でpython側は以下のようになるかと思います。
タイトルとか本文は日本語のデコードが必要になります。
# coding: UTF-8 import sys import codecs import smtplib import re from email.MIMEText import MIMEText from email.Header import Header from email.Utils import formatdate from datetime import datetime def mail_send( from_address, to_address, mail_text, charset): msg = MIMEText(mail_text.encode(charset),"plain",charset) msg["Subject"] = Header(u"ウィルスチェックによるメール削除のご連絡",charset) msg["From"] = from_address msg["To"] = to_address msg["Date"] = formatdate(localtime=True) smtp = smtplib.SMTP("localhost", 25) smtp.sendmail(from_address,to_address,msg.as_string()) smtp.close() def array2text(args, mail_info): msg = u"ウィルスチェックにより以下メールを削除しました。\n" msg += u"------------------------------------\n" #msg += u"送信者:" + args[1].encode('utf-8') + u"\n" msg += u"送信者:" + ",".join(mail_info[0]).encode('utf-8') + u"\n" msg += u"受信者:" for to_address in mail_info[1]: msg += to_address.encode('utf-8') msg += u"件名:" + mail_info[2].encode('utf-8') + "\n" msg += u"時間:" + datetime.now().strftime("%Y/%m/%d %H:%M:%S") + "\n" msg += u"------------------------------------\n" #msg += u"本文:\n" #for arg in args: # msg += arg.encode('utf-8') + "\n" #msg += u"------------------------------------\n" return msg def get_mail_info(lines): from_address = [] to_address = [] Info=[from_address, to_address, ""] find_to = False for line in lines: if find_to is True: if line.find(":") > 0: find_to = False else: to_address.append(line) if re.match("^To:", line) is not None: to_address.append(line[3:].strip()) find_to = True if re.match("^From:", line) is not None: from_address.append(line[5:].strip()) if re.match("^Subject:", line) is not None: Info[2] = line.encode("utf8") return Info if __name__ == "__main__": param = sys.argv print param lines = sys.stdin.readlines() mail_info = get_mail_info(lines) mail_text = array2text(param, mail_info) mail_receiver = param[1].split("/")[2] + "@○○○" mail_send( '○○○@○○○', mail_receiver, mail_text, "UTF8")
Rの環境構築(CentOS, ubuntu)
統計解析で広く用いられているRをCentOS, ubuntuにインストールして
使えるようにする。
統計解析ではPythonも人気で自力で計算処理を実装するならライブラリが豊富にある
というメリットがあるのだろうが、Rの方が初心者にも扱いやすい印象があるのでまずは
Rに慣れたいと思う。
今回は環境を構築するところまでが目的で、以下の環境でRを使えるようにする。
・Ubuntu 14.04.3 LTS
・CentOS Linux release 7.2.1511
○CentOS R構築
まずはepelリポジトリからRをインストールする
1.RインストールCentOS RStudio Server 構築
yum --enablerepo=epel -y install R
○CentOS RStudio Server 構築
次にRのIDEであるRStduioをインストールする。
公式サイト(https://www.rstudio.com/products/rstudio/download-server/)のほうで
構築手順の説明があるので、それに従うのが安全
1.RStudio Serverインストール
wget https://download2.rstudio.org/rstudio-server-rhel-0.99.879-x86_64.rpm sudo yum install --nogpgcheck rstudio-server-rhel-0.99.879-x86_64.rpm
2.動作確認
#起動 firewall-cmd --add-port=8787/tcp --permanent firewall-cmd --reload rstudio-server start
ブラウザでアクセスするとログイン画面が表示されるので、osユーザでログインできることを確認する
○CentOS shiny Server構築
Rで作ったアプリを降下するためのshiny serverもインストールする
こちらも公式サイト(https://www.rstudio.com/products/rstudio/download-server/)に詳しい構築手順
がのっている
1.shiny Serverインストール
sudo su - \ -c "R -e \"install.packages('shiny', repos='https://cran.rstudio.com/')\"" wget https://download3.rstudio.org/centos5.9/x86_64/shiny-server-1.4.1.759-rh5-x86_64.rpm sudo yum install --nogpgcheck shiny-server-1.4.1.759-rh5-x86_64.rpm
2.shiny server起動
firewall-cmd --add-port=3838/tcp --permanent firewall-cmd --reload systemctl start shiny-server
3.動作確認
ためしにRStduioのでshinyのプロジェクトを作成し、shiny serverにデプロイしてみる
RStudioのメニューから File → New Project → New Directory → Shiny Web Applicationを
実行し新規にプロジェクトを作成する。
それから作成したプロジェクトを/srv/shiny-server/にコピーし、ブラウザでアクセスする(ポートはデフォルトだと3838になる)
Ubuntu R-studio構築
1.dpkgダウンロード
https://www.rstudio.com/products/rstudio/download/からdpkgをダウンロードし
サーバにアップロードしておく
3.依存解決
sudo apt-get install gdebi-core ※警告が出たら以下実行 sudo apt-get -f install sudo apt-get install r-base sudo apt-get install gdebi-core
2.rstudio-serverインストール(公式の手順を確認するhttps://www.rstudio.com/products/rstudio/download-server/)
wget https://download2.rstudio.org/rstudio-server-0.99.879-amd64.deb sudo gdebi rstudio-server-0.99.879-amd64.deb
3.動作確認
#起動 sudo ufw allow 8787 sudo ufw reload sudo rstudio-server start #停止 sudo rstudio-server stop
○rstudio設定
公式を確認する(http://memorandum2015.sakura.ne.jp/docs/server/configuration.html)
/etc/rstudio/rserver.conf /etc/rstudio/rsession.conf
Ubuntu shiny Server 構築
sudo su - \ -c "R -e \"install.packages('shiny', repos='https://cran.rstudio.com/')\"" sudo apt-get install gdebi-core wget https://download3.rstudio.org/ubuntu-12.04/x86_64/shiny-server-1.4.1.759-amd64.deb sudo gdebi shiny-server-1.4.1.759-amd64.deb
MeCabとCaboChaをpythonから呼び出せるようにする(ubuntu, centos)
日本語で自然言語処理をする上で必須となる形態素解析、それから係り受け解析のエンジンである
MeCabとCaboChaをpythonから呼び出せるようにする。
動作確認はUbuntu 14.04.3 LTSとCentOS Linux release 7.2.1511で行いました。
1.Mecab
1.1 Ubuntu 14.04.3でMeCab環境構築
//ubuntuにpython-mecabのインストール
$ sudo aptitude install python-nltk python-numpy python-matplotlib python-networkx prover9
$ sudo apt-get install mecab libmecab-dev mecab-ipadic
$ sudo aptitude install mecab-ipadic-utf8
$ sudo apt-get install python-mecab
//動作確認
$ python
# coding: utf-8
import sys
import MeCabm = MeCab.Tagger ("-Ochasen")
print ("私の名前はボブです。")
print m.parse("私の名前はボブです。")
1.2 CentOS 7.2でMeCab環境構築
○mecabのインストール
$ cd /var/tmp
$ curl -O https://mecab.googlecode.com/files/mecab-0.996.tar.gz
$ tar zxfv mecab-0.996.tar.gz
$ cd mecab-0.996
$ ./configure
$ make
$ sudo make install
○辞書(ipadict)インストール
$ cd /var/tmp
$ curl -O https://mecab.googlecode.com/files/mecab-ipadic-2.7.0-20070801.tar.gz
$ tar zxfv mecab-ipadic-2.7.0-20070801.tar.gz
$ cd mecab-ipadic-2.7.0-20070801
$ ./configure --with-charset=utf8
$ make
$ sudo make install
○pythonからmecabを使えるようにする
$ yum groupinstall -y 'development tools'
$ yum install python-devel
$ pyenv install anaconda2-2.4.1
$ pyenv global anaconda2-2.4.1
$ vi /etc/ld.so.conf #共有ライブラリにパスを通す
include ld.so.conf.d/*.conf
/usr/local/lib
$ curl -O https://mecab.googlecode.com/files/mecab-python-0.996.tar.gz
$ tar zxfv mecab-python-0.996.tar.gz
$ cd mecab-python-0.996
$ vi setup.py #cmd2("~の部分を絶対パスに変える
#!/usr/bin/env python
from distutils.core import setup,Extension,os
import stringdef cmd1(str):
return os.popen(str).readlines()[0][:-1]def cmd2(str):
return string.split (cmd1(str))setup(name = "mecab-python",
#version = cmd1("mecab-config --version"),
version = cmd1("/usr/local/bin/mecab-config --version"),
py_modules=["MeCab"],
ext_modules = [
Extension("_MeCab",
["MeCab_wrap.cxx",],
#include_dirs=cmd2("mecab-config --inc-dir"),
include_dirs=[r"/usr/local/bin/mecab-config --inc-dir"],
#library_dirs=cmd2("mecab-config --libs-only-L"),
library_dirs=[r"/usr/local/bin/mecab-config --libs-only-L"],
#libraries=cmd2("mecab-config --libs-only-l"))
libraries=["libmecab"])
])
$ python setup.py build
$ sudo python setup.py install
//動作確認
python
# coding: utf-8
import sys
import MeCabmecab = MeCab.Tagger ("-Ochasen")
print ("私の名前はボブです。")
print mecab.parse("私の名前はボブです。")sent = u"かれのくるまでまつ".encode('utf-8')
print mecab.parse(sent)node = mecab.parseToNode(sent)
node = node.next
while node:
print node.surface, node.feature
node = node.next
2.CaboCha
2.1 Ubuntu 14.04.3でCaboCha環境構築
//ubuntuにcabochaを入れてpythonから使う
$ sudo apt-get install mecab libmecab-dev mecab-utils mecab-ipadic-utf8
$ sudo apt-get install build-essential
$ tar zxvf CRF++-0.58.tar.gz
$ cd CRF++-0.58
$ ./configure
$ make
$ sudo make install
$ vi /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
include /usr/local/lib
$ sudo ldconfig
cabocha-0.68.tar.bz2をサーバにアップロードしておく
$ bzip2 -dc cabocha-0.68.tar.bz2 | tar xvf -
$ cd cabocha-0.68
$ ./configure --with-mecab-config=`which mecab-config` --with-charset=UTF8
$ make
$ make check
$ sudo make install
$ cd python
$ sudo apt-get install python-dev
$ python setup.py build
$ sudo python setup.py install
import CaboCha
cabocha = CaboCha.Parser('--charset=UTF8')
sent = u"太郎はこの本を二郎を見た女性に渡した。".encode('utf-8')
print cabocha.parseToString(sent)
tree = cabocha.parse(sent)
print tree.toString(CaboCha.FORMAT_LATTICE)
print tree.toString(CaboCha.FORMAT_XML)
2.2 CentOS 7.2でCaboCha環境構築
//CentOsにcabochaを入れてpythonから使う
○crf++のインストール(事前にファイルをあげておく)
$ tar xzf CRF++-0.58.tar.gz
$ cd CRF++-0.58
$ ./configure
$ make
$ su
# make install
○cabochaのインストール(事前にファイルをあげておく)
$ bzip2 -dc cabocha-0.66.tar.bz2 | tar xvf -
$ cd cabocha-0.66
$ ./configure --with-mecab-config=`which mecab-config` --with-charset=UTF8
$ make
$ make check
$ su
# make install
○pythonからCaboChaを使えるようにする
$ cd python
$ python setup.py build
$ python setup.py install
$ ldconfig
//動作確認
$ python
import CaboCha
cabocha = CaboCha.Parser('--charset=UTF8')
sent = u"太郎はこの本を二郎を見た女性に渡した。".encode('utf-8')
print cabocha.parseToString(sent)
tree = cabocha.parse(sent)
print tree.toString(CaboCha.FORMAT_LATTICE)
print tree.toString(CaboCha.FORMAT_XML)for i in range(tree.chunk_size()):
chunk = tree.chunk(i)
print 'Chunk:', i
print ' Score:', chunk.score
print ' Link:', chunk.link
print ' Size:', chunk.token_size
print ' Pos:', chunk.token_pos
print ' Head:', chunk.head_pos # 主辞
print ' Func:', chunk.func_pos # 機能語
print ' Features:',
for j in range(chunk.feature_list_size):
print ' ' + chunk.feature_list(j)
print 'Text'
for ix in range(chunk.token_pos,chunk.token_pos + chunk.token_size):
print ' ', tree.token(ix).surfacefor i in range(tree.token_size()):
token = tree.token(i)
print 'Surface:', token.surface
print ' Normalized:', token.normalized_surface
print ' Feature:', token.feature
print ' NE:', token.ne # 固有表現
print ' Info:', token.additional_info
print ' Chunk:', token.chunk
メールサーバ構築
環境
idcfクラウド上のCentOS7に環境構築
メール送信にはサーバのドメインが必要なので、Dynamic DO!(http://ddo.jp/)を利用してクラウド
サーバにドメインを割り当てる。
メールサーバの環境構築としてMTA(Message Transfer Agent)にpostfix, MDA(Mail Deliver Agent)に
dovecotでそれぞれ設定の設定を行う。
postfixについては、CentOS7に最初からインストールされていると思うが、以下のコマンドで確認してみる。
postfixがインストールされていなければ以下のコマンドでインストールする。
次に、自動起動の設定をしておく
systemctl enable postfix
dnsと同じでpostfixもchrootで起動するようにする。
以下のコマンドでpostfixをchroot化する。
それからmaster.cfにchrootの設定を行う。
vi /etc/postfix/master.cf
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
smtp inet n - y - - smtpd
#smtp inet n - n - 1 postscreen
#smtpd pass - - n - - smtpd
#dnsblog unix - - n - 0 dnsblog
#tlsproxy unix - - n - 0 tlsproxy
#submission inet n - n - - smtpd
# -o syslog_name=postfix/submission
# -o smtpd_tls_security_level=encrypt
# -o smtpd_sasl_auth_enable=yes
# -o smtpd_reject_unlisted_recipient=no
# -o smtpd_client_restrictions=$mua_client_restrictions
# -o smtpd_helo_restrictions=$mua_helo_restrictions
# -o smtpd_sender_restrictions=$mua_sender_restrictions
# -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
# -o milter_macro_daemon_name=ORIGINATING
#smtps inet n - n - - smtpd
# -o syslog_name=postfix/smtps
# -o smtpd_tls_wrappermode=yes
# -o smtpd_sasl_auth_enable=yes
# -o smtpd_reject_unlisted_recipient=no
# -o smtpd_client_restrictions=$mua_client_restrictions
# -o smtpd_helo_restrictions=$mua_helo_restrictions
# -o smtpd_sender_restrictions=$mua_sender_restrictions
# -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
# -o milter_macro_daemon_name=ORIGINATING
#628 inet n - n - - qmqpd
pickup unix n - y 60 1 pickup
cleanup unix n - y - 0 cleanup
qmgr unix n - n 300 1 qmgr
#qmgr unix n - n 300 1 oqmgr
tlsmgr unix - - n 1000? 1 tlsmgr
rewrite unix - - y - - trivial-rewrite
bounce unix - - y - 0 bounce
defer unix - - y - 0 bounce
trace unix - - n - 0 bounce
verify unix - - n - 1 verify
flush unix n - y 1000? 0 flush
proxymap unix - - y - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - y - - smtp
relay unix - - y - - smtp
# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq unix n - y - - showq
error unix - - y - - error
retry unix - - n - - error
discard unix - - n - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - y - - lmtp
anvil unix - - n - 1 anvil
scache unix - - n - 1 scache
#
# ====================================================================
上記に従い、chrootにyをつけたあとpostfixの設定再読み込み、または再起動を
するとchrootでの起動になっているはずである。
systemctl restart postfix
chrootで起動しているかはpsを確認したときに、プロセスに-cのオプションがついていること
で確認できる。
次にpostfixの設定ファイル/etc/postfix/main.cfを編集する。
vi /etc/postfix/main.cf
#ホスト名
myhostname = ooo.xxx.ddo.jp#メールアドレスのドメイン
myorijin = $mydomain#SMTP接続を待ち受けるネットワークインターフェース
inet_interfaces = all#IPv4/IPv6の動作をall/ipv4/ipv6で指定
inet_protocols = all#ローカル階層を行うドメイン名
mydestination = $myhostname, localhost.$mydomain, localhost#中継を許可するSMTPクライアントのアドレス
mynetowrks = 192.168.xx.00/24, 127.0.0.0/8#メールすプールディレクトリ
mail_spool_directory = /var/spool/mail#メールボックスへのパス
home_mailbox = Maildir/#一人当たりのメールボックス要領
mailbox_size_limit = 104857600
#mailbox_size_limit = 0
次にpostfixを再起動する。
systemctl restart postfix
それから各ユーザーのメールボックスとして/home/*/Maildirが指定されているので
まず、ユーザディレクトリの雛形にMaildirを追加する
mkdir -p /etc/skel/Maildir/{new,cur,tmp}
chmod -R 700 /etc/skel/Maildir/
すでにユーザーが作成されている場合は以下のコマンドでメールボックス用のフォルダを作成できる
find /home/ -maxdepth 1 -mindepth 1 -type d | gawk '{print $NF}' | xargs -i mkdir -p {}/Maildir/{new,cur,tmp}
find /home/ -maxdepth 1 -mindepth 1 -type d | gawk '{print $NF}' | xargs -i chmod -R 700 {}/Maildir
次にsendmailコマンドでローカルのユーザーにメールが送信されているかを確認する
sendmail □□□@xxx.ddo.jp
From: □□□@xxx.ddo.jp
To: △△△@xxx.ddo.jp
Subject: test
this is test mail.
.
これでメールが送信されるはずだが実際に受け取っているかはmailコマンドで確かめてみる
mailコマンド実行できなかったらyumでインストールする
yum install mailx
mailコマンド使えるようになったら以下のコマンドでメール送信、受信できたか確認してみる
mail -f /home/△△△/Maildir
そしたらメールが受信されていることを確認されているはずである。
メールが受信されていなかったらメールログなど確認してみる。
tail -f /var/log/maillog
Redisとspring-boot連携
Redisとは
インメモリのkey-valueストア
メモリ上にデータを格納するので,非常に高速にデータの書き込み・読み込みを行うことができる。
キャッシュの保存とかセッション管理とかに使えるかも。
Redisインストール
epelリポジトリに含まれていたからyumでインストールした
yum install redis
インストールできなかったらepelリポジトリ追加して試す
yum install epel-release yum --enablerepo=epel install redis
redisサーバの起動
redis-server
redisクライアントの起動
redis-cli
spring boot連携
・pom.xmlに以下を追加
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency>
・application.propertiesにredisの接続情報を記入
spring.redis.host=localhost #spring.redis.password=secret spring.redis.port=6379
・Redisに値をset, getする
@Controller public class SampleController { @Autowired private HelloWorldService helloWorldService; @Autowired private StringRedisTemplate template; @RequestMapping("/") @ResponseBody public String helloWorld(HttpServletRequest request) { ValueOperations<String, String> ops = this.template.opsForValue(); String key = "spring.boot.redis.test"; if (!this.template.hasKey(key)) { ops.set(key, "foo"); } System.out.println("Found key " + key + ", value=" + ops.get(key)); return this.helloWorldService.getHelloMessage(); } }
Redisに保存されているかは以下のコマンドで確認できる。
redis-cli 127.0.0.1:6379> KEYS * 1) "spring.boot.redis.test" 127.0.0.1:6379> get spring.boot.redis.test "foo"
pythonのWebフレームワークTornadoでhttpのプロキシサーバを作ってみた
PythonではTornadoというWebフレームワークを使うことで簡単にノンブロッキングWebサーバの開発ができるらしい。
最近ではnode.jsなどがノンブロッキングWebサーバとして注目を集めているが、文字列操作の点でpythonの方が優れていそうだったのでとりあえずpythonで実装してみることにした。
○まずはTornadoをPythonにインストール
Tornadoのインストールはpipで簡単に行える
pip install tornado
pipを使わない場合はソースコードを落としてきて、直接インストールできる。
tar xvzf tornado-4.1.tar.gz cd tornado-4.1 python setup.py build sudo python setup.py install
○Tornadoを使ってプロキシサーバを作成
以下のコードでプロキシサーバになる
#!/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function import sys import os import tornado.httpserver import tornado.ioloop import tornado.iostream import tornado.web import tornado.httpclient import ssl class ProxyHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): return self.myRequest() @tornado.web.asynchronous def post(self): return self.myRequest() def myRequest(self): #self.render("test.html") def get_response( response): if response.error and not isinstance(response.error,tornado.httpclient.HTTPError): self.set_status(500) self.write('500 error:\n' + str(response.error)) self.finish() else: self.set_status(response.code) for header in ('Date', 'Cache-Control', 'Server', 'Content-Type', 'Location'): v = response.headers.get(header) if v: self.set_header(header, v) if response.body: print(self.request.uri) #self.write(response.body.replace("<body".encode("utf-8"), "<script type='text/javascript'>(function(){alert('hello');})();</script><body".encode("utf-8"))) self.write(response.body) #self.render(response.body) self.finish() req = tornado.httpclient.HTTPRequest( url=self.request.uri, method=self.request.method, body=self.request.body, headers=self.request.headers, follow_redirects=False, allow_nonstandard_methods=True) client = tornado.httpclient.AsyncHTTPClient() try: #コールバック関数にhandle_responseを指定。ここにアクセスしたレスポンスが入る client.fetch(req, get_response) except tornado.httpclient.HTTPError as e: if hasattr(e, 'response') and e.response: get_response(e.response) else: self.set_status(500) self.write('500 error:\n' + str(e)) self.finish() def run_proxy(port): app = tornado.web.Application( [(r'.*', ProxyHandler),] ) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(port) print("Server is up ...") tornado.ioloop.IOLoop.instance().start() #プロキシサーバを稼働させる if __name__ == "__main__": port = 8888 if len(sys.argv) > 1: port = int(sys.argv[1]) print ("Starting cache proxy on port %d" % port) run_proxy(port) #run_ssl_proxy(8888)
やっていることとしては、ポートの8888でサーバを立てておきリクエストを受け取ったらをそのまま中継させるだけだ。
サーバを立ち上げているのは以下の部分になります。
app = tornado.web.Application(
[(r'.*', ProxyHandler),]
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(port)
tornado.web.Applicationの引数で受け取るリクエストと呼びだすクラスを指定しています。
また、今回は指定していませんがSSL通信を行いたい場合は以下のようにオプションとして証明書と秘密鍵の指定が必要になります。
app = tornado.web.Application( [(r'.*', ProxyHandler),], template_path=os.path.join(os.getcwd(), "static"), static_path=os.path.join(os.getcwd(), "static") ) http_server = tornado.httpserver.HTTPServer(app, ssl_options={ "certfile":"/var/pyTest/ca2.crt", "keyfile":"/var/pyTest/ca2_out.key", #ssl_version= ssl.PROTOCOL_TLSv1 })
○ブラウザのプロキシとして設定する
ブラウザからアクセスする場合は、プロキシとして通す設定が必要になる。
Firefoxの場合はオプション→詳細→ネットワーク→接続設定で行うことができる。
ブラウザ設定後リクエストを飛ばしたらGET,POSTにかかわらずmyRequest()メソッドを実行するようにしています。リクエストは以下の部分で設定を行っています。内容としては受け取ったリクエストをそのままセットしているだけです。
req = tornado.httpclient.HTTPRequest( url=self.request.uri, method=self.request.method, body=self.request.body, headers=self.request.headers, follow_redirects=False, allow_nonstandard_methods=True) client = tornado.httpclient.AsyncHTTPClient()
リクエストのコールバックはclient.fetch(req, get_response)で指定しています。
それからリクエストの送信とエラー時の処理を定義しています。
if hasattr(e, 'response') and e.response: get_response(e.response) else: self.set_status(500) self.write('500 error:\n' + str(e)) self.finish()
コールバックメソッド実行時結果が500エラーなら500 errorとクライアントに表示します。
if response.error and not isinstance(response.error,tornado.httpclient.HTTPError): self.set_status(500) self.write('500 error:\n' + str(response.error)) self.finish()
結果を受け取れた場合、レスポンスヘッダとボディにそのままの内容を書き込んでいます。
self.writeの部分で文字列操作を行うと、クライアント側の表示を書き換えることができます。今回は使っておりませんが、lxmlというサードパーティモジュールがhtmlの操作に便利そうです。
else: self.set_status(response.code) for header in ('Date', 'Cache-Control', 'Server', 'Content-Type', 'Location'): v = response.headers.get(header) if v: self.set_header(header, v) if response.body: print(self.request.uri) #self.write(response.body.replace("<body".encode("utf-8"), "<script type='text/javascript'>(function(){alert('hello');})();</script><body".encode("utf-8"))) self.write(response.body) #self.render(response.body) self.finish()
結果ですが、httpプロトコルについてはうまくプロキシサーバとして動いておりましたがhttpsでSSL通信を行おうとしたらエラーが発生しました。自分で準備したオレオレ証明書と秘密鍵の部分でエラーが発生していたので、ちょっと調べたいと思います。
→ちょっと調べてみました。どうやらこのやり方だとクライアント→プロキシサーバ間もssl通信をする想定になっているようだが、クライアント側のFirefoxでssl通信のプロキシとして指定したらクライアント→プロキシサーバ間でssl通信を行っていないようなのでエラーになってそうな気がする。
ブラウザのプロキシに指定するのではなくhttps://localhost:8888/https://google.co.jpとかでリクエストは受け取れていたので多分そうです。
まあ、httpsの場合はプロキシサーバはキャッシュの転送だけを行うルータのように動くのが前提のプロトコルのようなので、無理そうか
HTTPSプロキシの仕組み - ソフトウェアエンジニア現役続行
Spring Boot基礎
Spring Bootについて調べた内容をまとめてみる
○springアプリケーション起動
SpringApplicatin.runを実行することで設定を読み込みアプリを起動する
@autowiredや@ControllerアノテーションはSpringApplication.run以下のパッケージで有効になる。
なのでアプリの根っこの部分のパッケージなどにSpringApplication.runを実行するクラスを配置させる。
○リクエストを受け取れるようにする
まずはリクエストを受け取れるようにする。
リクエストを受け取れるようにするには@RequestMappingアノテーションでURLのパスを指定する。
@RequestMappingはクラスとメソッドの両方で有効なアノテーションである。
そのため、以下のようにクラスとメソッドに@RequestMappingアノテーションを指定した場合、exec()メソッドを呼び出す場合のURLはhttp://ホスト名:ポート/login/execになる。
@Controller @RequestMapping("/login") public class LoginAction { @RequestMapping(value="/exec", method={RequestMethod.POST, RequestMethod.GET}) public String exec(HttpServletRequest request, @ModelAttribute LoginForm form, Model model) { UserMstEntity user = userMstService.findUserByIdPass(form.getUserId(), form.getPassword()); if(isLoginSuccess( user) ){ //メイン画面へ遷移 request.getSession().setAttribute(UserMstEntity.class.getName(), user); return "redirect:/main/"; }else{ model.addAttribute("msg", "ログイン情報が間違っています"); return "/login/index"; } }
それからjspを返す場合はクラスに@ControllerアノテーションをつけておきRequestMappingで呼び出されるメソッドの戻り値でjsp1ファイルへのパスを返すようにする。またjspファイルではなくサーバサイドのRequestMappingが指定されたメソッドを呼び出す場合はreturn "redirect:/main/";のようにredirect:でサーバサイドのメソッドのリクエストマッピングを指定するようにする。Reqiest
spring bootでjspを使用する場合は、pom.xmlのdependenciesに以下の定義を追加する。
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> ||< jspファイルを呼び出す場合、@RequestMappingで呼び出されたメソッドが以下のようにjspファイルのパスを指定すればよい。 return "/login/index"; だが、その前にsrc/main/resourcesにapplication.propertesを配置し以下の内容を記述しておく必要がある。 >|| spring.view.prefix: /WEB-INF/views/ spring.view.suffix: .jsp
これでjspファイルのパスのルートが/WEB-INF/views/になる。
また、今回はpropertiesファイルを使ったがymlを使うこともできるので使いやすい方を使えばよいでしょう。
また、サーバサイドからjspに値を渡す場合は以下のようにmodel.addAttributeをすれば良い。
@RequestMapping(value="/logout") public String logout(HttpServletRequest request, Model model) { request.getSession().setAttribute(UserMstEntity.class.getName(), null); model.addAttribute("msg", "ログアウトしました"); return "/login/index"; }
model.addAttributeされた値を使うにはstrutsと同じようにel式(${msg})で指定する。
○リクエストされた値をサーバサイドで受け取れるようにする。
クライアントからリクエストされた値を受け取る場合、簡単な方法としては以下のように@RequestParamアノテーションを使ったものがある。
RequestMapping(value="/exec", method={RequestMethod.POST, RequestMethod.GET}) public String exec(HttpServletRequest request,@RequestParam("userId") String userId, @RequestParam("password") String password, Model model) { ||< ひとつずつ値を受け取るのが面倒な場合はFormクラスを作っておいて求めて受け取れるようにする方法がある。 formを使用する場合、通常だったらgetter,setterを用意しなければ行けないのだがlombockといってコンパイルしたクラスファイルに自動でgetter,setterを追加してくれる便利なライブラリがある。lombockが使用可能なら以下のようにクラスファイルに@Dataアノテーションを使用することでeclipseがコンパイルしたあとのクラスファイルファイルに手を加えてくれる。 >|java| import lombok.Data; @Data public class LoginForm { private String userId; private String password; private String msg; }
lombockを使用するにはまず、pom.xmlに以下の定義を加える。
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.4</version> <scope>provided</scope> </dependency> ||< これで@Dataアノテーションを使ったとしてもエラーはなくなるはずである。ただこれだけでは不十分でありクラスファイルを確認してもgetter,setterメソッドがセットされていないのがわかる。@Dataアノテーションを使用する場合はeclipse自体の設定を変更しなければいけず、方法としては公式サイトからダウンロードしてきたlombock.jarを実行して使用するeclipseを指定するというものだ。これで@Dataアノテーションがついているものにはgetter,setterが自動で吹かされていることが確認できる。 クライアントからのリクエストをFormにセットする場合は、クラスに@Dataアノテーションを記述したあと以下のように@ModelAttributeアノテーションを使う。 >|java| @RequestMapping(value="/exec", method={RequestMethod.POST, RequestMethod.GET}) public String exec(HttpServletRequest request, @ModelAttribute LoginForm form, Model model) { UserMstEntity user = userMstService.findUserByIdPass(form.getUserId(), form.getPassword());
とりあえずこれで、クライアントとサーバサイドで基本的な情報のやり取りができるようになった。
DBと依存性周りについてはまた今度で、