IT業務効率化

Python responderを使ってリクエストに応じたwebページを返す2

はじめに

私のブログ全体に言えることなのですが、苦労した箇所や混乱した箇所は抜け漏れなく書いております。そのため体系的にまとめられた記事ではございません。今回は特に四苦八苦しましたので、お見苦しい内容でございますがご了承ください。

しかし結論はなるだけ早めの項で述べるようにしておりますので、お忙しい方はぜひそこまでご覧になってください。

前回の記事をからの続きですが今回のテーマは、検索画面とその検索結果を返す画面を同じページに作ることです。

結論

HTML

<!DOCTYPE>
<html lang="ja">
	<meta charset="utf-8"/>
	<head>
		<title>hirayukiのhello world</title>
	</head>
	<body>
		<p>Hello, {{user}}!</p>
		<form action="/hello.html" method="post">
			<p>
				名前:<input type="text" name="name" size="40">
			</p>
			<p>
				<input type="submit" value="送信">
			</p>
		</form>
	{% if person_you_chose|length > 0 %}<p>Profile: {{person_you_chose}}のプロフィール</p>{% endif %}
	</body>
</html>

Python

import responder
import time

api = responder.API()


@api.route("/hello.html")
async def hello_html(req, resp):
    user = "hirayuki"
    person_you_chose = ""
    try:
        data = await req.media()
        person_you_chose = data["name"]
    except:
        pass
    resp.html = api.template('index.html', user=user,
                             person_you_chose=person_you_chose)

api.run(port=3000)

悩み:POSTを送信すると、URLが変わってしまう

今の課題は同じURL上で作業し続けたいのに、データをリクエストすると、別のURL常に遷移してしまうことです。HTMLのformタグは以下のようになっております。

<form action="/incoming" method="post">

pythonコードは以下のようになっております。

import responder
import time

api = responder.API()


@api.route("/hello.html")
def hello_html(req, resp):
    my_name = "hirayuki"
    resp.html = api.template('index.html', name=my_name)

@api.route("/incoming")
async def receive_incoming(req, resp):
    data = await req.media()
    resp.html = api.template('index.html', name=data)
    print(data)

api.run(port=3000)

そもそもこの15行目でindex.htmlを返そうとしていること自体がが間違いなのかもしれませんね。しかしどうやって修正していけばいいのか検討もつきません。Djangoの記事はresponderよりも多く出回っているので、それを参考にしたら近道でわかりそうです。しかし、今回HTMLのformタグを初めて書いているため、まずはformタグの基礎を学んで、そこからヒントを得ようと思います。

formタグのactionとは?

formタグの中のデータを送信する先のURLを指定する。それだけでした。ドキュメントにも諸先輩がたの記事にも、ただただそう書いてありました。そしてあとはデータを送信された先の処理の話になります。

formの送信先はどこだろうか?

今回のpythonコードでいうと、関数recieve_incomingでしょう。@api.route(“/incoming”)で定義されているため、ここにform submissionが飛ぶことはまず理解できます。そしてここでtemplatesを返そうとしています。これがよくないのかもしれません。incomingにデータが送信されたあと、データを元に任意の値を取得し、その値を付与して、hello_html関数を呼ぶことはできないでしょうか?

本題:hello.htmlドメインのまま、リクエストに応えるには

関数recieve_incomingの中でhello_html関数を実行してみる

まずは関数hello_htmlに引数を一つ増やしても動作するか調べます。

@api.route("/hello.html")
def hello_html(req, resp, new_data=""):
    my_name = "hirayuki"
    resp.html = api.template('index.html', name=my_name)

引数を一つ足しても無事に起動できました。では、この関数をrecieve_incomingの中で実行するとどうなるのか試そうと思います。

@api.route("/hello.html")
def hello_html(req, resp, new_data=""):
    my_name = "hirayuki"
    if new_data:
        my_name = new_data
    resp.html = api.template('index.html', name=my_name)

@api.route("/incoming")
async def receive_incoming(req, resp):
    data = await req.media()
    hello_html(req, resp, data)

reqとrespはそのままです。うまくいく気がしませんがGO。

えい!

何がどうなってしまったのかわかりませんが、URLは以前incomingのままですし、リクエストパラメータも引き継がれていません。大失敗です。ログにもエラーがあるわけではないです。

Single-Page Web Appsにしてみる

公式ドキュメントの中に見つけました。シングルページウェブアプリケーションにしたかったらapi.add_route(“/”, static=True)と記述し、staticの配下にindex.htmlを置くと書いてあります。ものは試しでやってみようと思います。

apiという変数を作成した直後に、下記のように記述しました。

api.add_route("/", static=True)

すぐに気づいたのですが、ドメイン指定がない時にそのindex.htmlを返す機能でした。これじゃないですね。。

検索ページと結果表示ページが同じなら、URLも同じ?

結局これが自分の中の答えでした。formタグのaction属性を/hello.htmlにしてしまえばよかったのです。(最適なソリューションだったかはわかりませんが、これ以上調べてもより良い答えは見つけられませんでした。多分あっているはず!!)

こちらの記事が大変参考になりました。phpの例ですが、リクエストパラメータを引き継いで、formのaction属性に書き込まれたURLに遷移し、リクエスト内容に応じたページを返しています。私はresponderのquickstartの事例から、post先は別指定するべきだと勘違いしていました。今回の私がやりたいことのケースにおいては、その必要はありませんでした。

以上の結果を踏まえて、pythonコードを以下のように修正しました。

@api.route("/hello.html")
async def hello_html(req, resp):
    user = "hirayuki"
    person_you_chose = ""
    try:
        data = await req.media()
        person_you_chose = data["name"]
    except:
        pass
    resp.html = api.template('index.html', user=user,
                             person_you_chose=person_you_chose)

1回目にページを表示するときは、req.media()を実行するとjson decode errorが発生します。リクエストパラメータがないからだと思いますが、次回の記事にしようと思います。try, exceptで処理しているのは恥ずかしいですが、一旦ご容赦ください。

そしてHTMLのformタグのactionは/hello.htmlにしました。

<form action="/hello.html" method="post">

また、搭載されているjinjaを利用して、送信した名前がないときは何も表示しないように設定しました。(jinjaはlen関数が使えません。代わりに|lengthを利用します。)

{% if person_you_chose|length > 0 %}<p>Profile: {{person_you_chose}}のプロフィール</p>{% endif %}

これで理想通りのページを作ることができました。

これが一番最初に起動した時のページです。

そして名前:のところでしろくまと検索した後の結果です。

responder-introdauction

できましたー!!

終わりに

try, exceptで処理している箇所はとてもダサいので、次回改修方法を模索いたします。とてもとても苦戦しましたが、色々と勉強になってよかったです。

responderの記事を読んでいくとtemplateでhtmlを返す機能はあくまでの昨日の一部であり、APIとして深い機能を要していることがわかりました。このあたりを今後触ってみたいと思っております。特に社内の機械学習系の部署で、学習データを収集するときに使えそうな気がしております。

今回作った形は最善解ではないかもしれませんが、お読みいただきありがとうございました。コメント等もお待ちしております。

githubのリンク

Python responderを使ってリクエストに応じたwebページを返す2はじめに 私のブログ全体に言えることなのですが、苦労した箇所や混乱した箇所は抜け漏れなく書いております。そのため体系的にまとめられた記...
ABOUT ME
hirayuki
今年で社会人3年目になります。 日々体当たりで仕事を覚えています。 テーマはIT・教育です。 少しでも技術に親しんでもらえるよう、noteで4コマ漫画も書いています。 https://note.mu/hirayuki