(これは東北Tech道場のハンズオンの資料でもあります)
アプリの応用範囲を広げられるように、本家のチュートリアルを構成変更して、実用性を高めています。
#1: Google App Engineとは?
簡単に説明するならGoogleが提供する至れり尽くせりなサーバ環境です。
こんな↓アイコンです。
昔はこんな↓アイコンでした。
むかしむかし、サーバを構築するには
・物理マシンを購入してきて、
・LinuxなどのOSをインストールして、
・データベース・サーバ(MySQLとか)、
・Webサーバ(Apache HTTP Serverとか)、
・開発環境(PHP、Perl、Pythonとか)
などをインストールする必要がありました。
さらに、それらが正しく動くように設定する必要がありました。
こういうのをまとめてLAMP環境(Linux,、Apache, MySQL, P系言語)などと言います。
お金と手間がかかって、サーバを構築するだけで一苦労でした。
多くの人が挫折を繰り返し、
多くの人が残業を繰り返しました。
昔はね。
多くの人が挫折を繰り返し、
多くの人が残業を繰り返しました。
昔はね。
App Engineはそういうのを初めから全部準備してくれているサービスです。
OS?データベース?Webサーバ?開発環境?全部あるよ、App Engineにね。
(もちろん、これら以外にも沢山のサーバ機能が準備されています。)
#2: でもお高いんでしょ?
ちょっとしか使わないなら無料なので、安心して使いはじめることができます。
たとえばアップロードするファイル容量は合計で1GBまで無料で使えます。
データベースの容量(Datastoreなど)も1GBまで無料で使えます。
無料で使える制限を超えてしまった場合は、クレジットカードを登録しない限りは課金されないので、それも安心。
たとえば、
ファイル容量を1GB追加したかったら月$0.026(3円ぐらい)かかります。
ファイル容量を1GB追加したかったら月$0.026(3円ぐらい)かかります。
データベースの容量を1GB追加したかったら月$0.18(20円ぐらい)かかります。
安いだけでなく、使えば使うほど、値上がり幅を小さくすると今年の6月ぐらいに発表されました。
値上がり幅の詳細も公表されています。
安いだけでなく、使えば使うほど、値上がり幅を小さくすると今年の6月ぐらいに発表されました。
値上がり幅の詳細も公表されています。
#3: プログラミング始めたばかりですが…
現時点でApp EngineはPython/Java/Go/PHPの4つの言語が使えます。
Android開発からプログラミングを始めました、という人にとっては安心のJavaが含まれています。
聞いたことのない言語があるかもしれません。
プログラミング言語は道具なので、何を使っても良いのですが、道具を色々使えると今後の楽しみが増えるので、今回はPythonを使ってApp Engineのアプリケーション開発の方法を説明します。
人:「おめー、言語、何使ってんよ?」
俺:「Java」
人:「ふーん」
俺:「と、Python」
人:「ぉ、おぉぅ!」
俺:「おめーは?」
人:「まぁ、いろいろよ」
って感じになれるはずです。
Pythonじゃなくてもいいけど、Pythonもいいよ。Pythonがいいよ。
でも、言語自慢してると、
人2:「おれHaskell」
人3:「おれCOBOL」
人4:「おれBrainF*ck」
などと、ややこしそうな人が寄ってくるのでホドホドに。
(同じ現象がエディタにも起こるので、そちら方面も注意!)
人:「おめー、言語、何使ってんよ?」
俺:「Java」
人:「ふーん」
俺:「と、Python」
人:「ぉ、おぉぅ!」
俺:「おめーは?」
人:「まぁ、いろいろよ」
って感じになれるはずです。
Pythonじゃなくてもいいけど、Pythonもいいよ。Pythonがいいよ。
でも、言語自慢してると、
人2:「おれHaskell」
人3:「おれCOBOL」
人4:「おれBrainF*ck」
などと、ややこしそうな人が寄ってくるのでホドホドに。
(同じ現象がエディタにも起こるので、そちら方面も注意!)
ちなみにPythonのアイコンはこれ↓
ニシキヘビ(Python)です。
これが可愛く見えてきたら立派なパイソニストです。
これが可愛く見えてきたら立派なパイソニストです。
(パイソニスタとかパイソニアンとかパイソナーと呼ぶ人もいます)
ちなみにJava使いはJavaer(じゃばー)、PHP使いはPHPer(ぺちぱー)とか呼ばれます。Goは?
ちなみにJava使いはJavaer(じゃばー)、PHP使いはPHPer(ぺちぱー)とか呼ばれます。Goは?
#4: Google App Engine(Python)をはじめよう!
#4-1: App Engineアプリの開発環境を整えよう
ここからはMac OS X版をベースに紹介します。
WindowsやLinuxを使っている人もMac版とほぼ同じです。
本家でもインストール手順を紹介しています。
とっても簡単なので参考にして環境構築して下さい。
ダウンロードしてたファイルをダブルクリックするとシンプルなアイコンが登場します。
Pythonはversion2.7系が必要です。
Mac OSXの場合ははじめからインストールされています。(たぶん)
Pythonのバージョンを確認しましょう。
まずターミナルを開いて
/usr/bin/env python -V
と入力して下さい。
するとバージョンが表示されます。
2.7.6と表示されたのがバージョン名です。
バージョン名が表示されなかったり、バージョンが2.7.*でなかったら本家からダウンロードしてインストールしましょう。
これでApp Engineを始める準備が完了しました。
Pythonがインストールされている環境であればApp EngineのSDK(ランチャー)をダウンロードするだけです。
これでもうサーバアプリケーションの開発ができるようになりました。
挫折した人は比較的少ないのではないでしょうか?
残業する人もぐっと減ったのではないでしょうか?
お金はまだかかっていません。
WindowsやLinuxを使っている人もMac版とほぼ同じです。
本家でもインストール手順を紹介しています。
とっても簡単なので参考にして環境構築して下さい。
ダウンロードしてたファイルをダブルクリックするとシンプルなアイコンが登場します。
Mac OSXの場合ははじめからインストールされています。(たぶん)
まずターミナルを開いて
/usr/bin/env python -V
するとバージョンが表示されます。
2.7.6と表示されたのがバージョン名です。
バージョン名が表示されなかったり、バージョンが2.7.*でなかったら本家からダウンロードしてインストールしましょう。
これでApp Engineを始める準備が完了しました。
Pythonがインストールされている環境であればApp EngineのSDK(ランチャー)をダウンロードするだけです。
これでもうサーバアプリケーションの開発ができるようになりました。
挫折した人は比較的少ないのではないでしょうか?
残業する人もぐっと減ったのではないでしょうか?
お金はまだかかっていません。
#4-2: App Engineアプリを登録する
App Engineのアプリを開発を始めましょう。
アプリはインターネット上に公開されますので、まずはApp Engineアプリを登録する必要があります。
と言ってもとても簡単で、1ステップです。
アプリ登録画面を開きます。
2項目を入力するだけです。
Application Identifierは世界で唯一の名前を指定する必要があります。
「Check Availability」ボタンで名前が利用可能か調べましょう。
すでに誰かに使われていたり、使えない名前だと「Sorry,...」と表示されます。
使える名前を探しましょう。
Application Identifierは「Application ID.appspot.com」というサーバのドメイン(ブラウザでアクセスするURL)名にも使われます。
あなたが開発したアプリが公開されるアドレスです。
(独自ドメインを後から設定することもできます)
といっても、気に入らなければ新しいのを作れば良いので、気軽に始めましょう。
あとはApplication Title(アプリのタイトル)を指定して、「Create Application」ボタンをクリックするだけです。
すると「Application Registered Successfully」という
2項目も入力しちゃった!ちょっと休憩しましょう。
さて、いよいよ、App Engineアプリを開発していきます。
ApplicationフォルダのApp Engineランチャーアイコンをダブルクリック。
ランチャーが起動したら、左下の「+」をクリックしてアプリを追加します。
Application IDに先ほど登録したApplication Identifierの文字をと入力します。
Application Directoryには任意のフォルダを指定します。
文字の入力が完了したら「Create」ボタンをクリックするとアプリのベースが完成です。
指定したフォルダの中にApplication ID名のフォルダができていて、その中に幾つかファイルができていれば成功です。
実は、この時点でHello world!を表示するアプリがすでに完成しています。
試しにアプリを動かしてみます。
ランチャーにはApplication IDがリスト表示されているので、
それを選択し左上の「Run」ボタンをクリックします。
そうすると開発サーバが自動で起動します。
起動したら「Stop」ボタンと「Browse」ボタンがクリックできるようになるので、
「Browser」ボタンをクリックします。
すると、アプリが実行された画面が表示されます。
勝手にブラウザが開きます。
開いたページは以下のようになります。
Hello world!とだけ表示されました。
言われるがままにポチポチしてたら何か出た。
という感じだと思うので、ここで少し何がどうなっているのかを説明します。
このアプリの実行に必要なファイルはapp.yamlとmain.pyの2つです。
app.yamlの内容は以下のようになっています。
YAML(ヤムル)という形式の書き方です。
色々設定らしき項目が見えますが、注目は「url」の項目です。
その下に「script: main.app」と書かれています。
これはurlで指定したURLのパス(ここの表記だと全てのパス)にブラウザでアクセスされた時にはmain.pyを実行する、ということを書いています。
次にmain.pyを開いてみましょう。
実際のファイルには色々とコメントが書かれていますが、プログムで必要な部分はこれだけです。
下から3行分がURLのパスとプログラムを関連付けるところです。
パス「/」に「MainHandler」というクラスが関連付けられています。
つまり、App Engineはapp.yamlを読み取り、ユーザが「/」にアクセスしたらmain.pyを事項し、main.pyの中で「/」に関連付けられているMainHandlerクラスを実行します。
ブラウザを開いてアプリにアクセスする場合、HTTP GETメソッドが使われますので、MainHandlerクラスのgetというメソッドが実際には実行されます。
実行内容はたった1行で
です。
これがApp Engineのアプリでブラウザに「Hello world!」を表示する仕組みです。
(注:これはApp EngineというよりdjangoというPythonフレームワークの仕組みです)
「/say」にアクセスした時に、別の文字を表示するようにカスタマイズしてみます。
main.pyにパスとクラスの関連付けを追加します。
/sayにアクセスした時にSayHandlerクラスが実行されるようにしました。
次にSayHandlerクラスを定義します。
ちなみにPythonは「{」や「}」のようなコードブロックを明示する構文がありません。その代わりに、スペースやタブの数がコードブロックを表します。なのでPythonのプログラムを書く時やコピペするときはスペースの数を間違えないように気をつけましょう。
ブラウザで/sayにアクセスするとこうなります。
Say hello!が表示されました。
簡単ですね!
では、さっそく、この素晴らしいアプリを全世界に公開してみましょう。
全世界とかコワイ!とかビビらなくても大丈夫です。
どこからもリンク貼られてないので、実質、誰からも見つけられませんw
公開するのも、とても簡単です。
「Deploy」ボタンをクリックするだけ。
クリックするとGoogleアカウントの認証が出てきますので、アカウントとパスワードを入力します。
認証が完了したら、アプリのアップロード(これをデプロイと言います)が開始されます。
デプロイが完了すると、ブラウザで「http://{{ Application ID }}.appspot.com」にアクセスしてみましょう。
ちゃんと表示されました!/sayはどうでしょうか?
完璧です。たった3〜5行ぐらい書いただけでサーバアプリが完成しました。
現在のコードはこんな感じになっています。
アプリ開発から少し脱線します。
Hello world!だけだと味気ないので、HTMLを表示してみましょう。
直接self.response.write()メソッドでHTMLを書いても良いのですが、実用向けじゃないのでJinja2というテンプレートエンジンを使います。
いきなり難しそうですが、たいして難しくありません。
しかも、一度書いてしまえば、ずっと使いまわせるので、この際やっちゃいましょう。
まずはアプリにライブラリを追加します。app.yamlのlibrariesという項目にjinja2を追加して以下のように記述します。2行追加しただけ。
main.pyの先頭にJinja2の設定を記入します。
なにやらちょっとややこしそう。。。
いつか、設定を変更したいということがあったら、このプログラムの周辺を調べてみてください。いろいろ設定できて便利です。
今回は、この設定で十分なので、このままコピペしちゃいましょう。
テンプレートでHTMLを表示する時は
これで準備完了です。
MainHandlerはwebapp2.RequestHandlerを継承していましたが、先ほど定義したBaseHandlerを継承するように変更します。
そしてHTMLを表示するように変更します。
アプリフォルダに以下のmain.htmlを作ります。
完成です。
アプリフォルダの中身はこんな感じになっています。
10行もプログラムかいちゃいましたね。
さっそく開発サーバでアプリを動かしてみます。
ちゃんと動いていますね!
でも表示が同じなのでHTMLなのかあやしいです。
HTMLを覗いてみるとどうなっているでしょうか?
ブラウザで開いているページを右クリックして「ページのソースを表示」を選択するとHTMLを見ることができます。
これでHTMLを表示させる方法が習得できました!
今現在のソースコードはこんな感じになっていますよ。
ちなみに、app.yamlはこんな感じです。
モバイルアプリの場合、サーバをデータの保存、共有目的で使うことが多いと思います。
またWebアプリでも、やはり、データを保存、共有することが多いでしょう。
なので、さっそくデータを保存できるようにしましょう。
先ほどのmain.htmlを修正してデータを入力できるフォームを作ります。
#4-3: App Engineのアプリケーションを作ろう
さて、いよいよ、App Engineアプリを開発していきます。
ApplicationフォルダのApp Engineランチャーアイコンをダブルクリック。
ランチャーが起動したら、左下の「+」をクリックしてアプリを追加します。
Application IDに先ほど登録したApplication Identifierの文字をと入力します。
Application Directoryには任意のフォルダを指定します。
文字の入力が完了したら「Create」ボタンをクリックするとアプリのベースが完成です。
指定したフォルダの中にApplication ID名のフォルダができていて、その中に幾つかファイルができていれば成功です。
#4-4: アプリケーションを実行する
実は、この時点でHello world!を表示するアプリがすでに完成しています。
試しにアプリを動かしてみます。
ランチャーにはApplication IDがリスト表示されているので、
それを選択し左上の「Run」ボタンをクリックします。
そうすると開発サーバが自動で起動します。
起動したら「Stop」ボタンと「Browse」ボタンがクリックできるようになるので、
「Browser」ボタンをクリックします。
すると、アプリが実行された画面が表示されます。
勝手にブラウザが開きます。
開いたページは以下のようになります。
Hello world!とだけ表示されました。
#4-5: 挙動の説明するよ
という感じだと思うので、ここで少し何がどうなっているのかを説明します。
このアプリの実行に必要なファイルはapp.yamlとmain.pyの2つです。
app.yamlの内容は以下のようになっています。
YAML(ヤムル)という形式の書き方です。
application: helloworld-adamrocker version: 1 runtime: python27 api_version: 1 threadsafe: yes handlers: - url: /favicon\.ico static_files: favicon.ico upload: favicon\.ico - url: .* script: main.app libraries: - name: webapp2 version: "2.5.2"
色々設定らしき項目が見えますが、注目は「url」の項目です。
その下に「script: main.app」と書かれています。
これはurlで指定したURLのパス(ここの表記だと全てのパス)にブラウザでアクセスされた時にはmain.pyを実行する、ということを書いています。
次にmain.pyを開いてみましょう。
#!/usr/bin/env python import webapp2 class MainHandler(webapp2.RequestHandler): def get(self): self.response.write('Hello world!') app = webapp2.WSGIApplication([ ('/', MainHandler) ], debug=True)
実際のファイルには色々とコメントが書かれていますが、プログムで必要な部分はこれだけです。
下から3行分がURLのパスとプログラムを関連付けるところです。
パス「/」に「MainHandler」というクラスが関連付けられています。
つまり、App Engineはapp.yamlを読み取り、ユーザが「/」にアクセスしたらmain.pyを事項し、main.pyの中で「/」に関連付けられているMainHandlerクラスを実行します。
ブラウザを開いてアプリにアクセスする場合、HTTP GETメソッドが使われますので、MainHandlerクラスのgetというメソッドが実際には実行されます。
実行内容はたった1行で
self.response.write('Hello world!')
です。
これがApp Engineのアプリでブラウザに「Hello world!」を表示する仕組みです。
(注:これはApp EngineというよりdjangoというPythonフレームワークの仕組みです)
#4-6: ちょっとカスタマイズしてみよう
「/say」にアクセスした時に、別の文字を表示するようにカスタマイズしてみます。
main.pyにパスとクラスの関連付けを追加します。
app = webapp2.WSGIApplication([ ('/', MainHandler), ('/say', SayHandler) ], debug=True)
/sayにアクセスした時にSayHandlerクラスが実行されるようにしました。
次にSayHandlerクラスを定義します。
class MainHandler(webapp2.RequestHandler): def get(self): self.response.write('Hello world!') class SayHandler(webapp2.RequestHandler): def get(self): self.response.write('Say hello!')
ちなみにPythonは「{」や「}」のようなコードブロックを明示する構文がありません。その代わりに、スペースやタブの数がコードブロックを表します。なのでPythonのプログラムを書く時やコピペするときはスペースの数を間違えないように気をつけましょう。
ブラウザで/sayにアクセスするとこうなります。
Say hello!が表示されました。
簡単ですね!
では、さっそく、この素晴らしいアプリを全世界に公開してみましょう。
全世界とかコワイ!とかビビらなくても大丈夫です。
どこからもリンク貼られてないので、実質、誰からも見つけられませんw
公開するのも、とても簡単です。
「Deploy」ボタンをクリックするだけ。
クリックするとGoogleアカウントの認証が出てきますので、アカウントとパスワードを入力します。
認証が完了したら、アプリのアップロード(これをデプロイと言います)が開始されます。
デプロイが完了すると、ブラウザで「http://{{ Application ID }}.appspot.com」にアクセスしてみましょう。
ちゃんと表示されました!/sayはどうでしょうか?
完璧です。たった3〜5行ぐらい書いただけでサーバアプリが完成しました。
現在のコードはこんな感じになっています。
#!/usr/bin/env python import webapp2 class MainHandler(webapp2.RequestHandler): def get(self): self.response.write('Hello world!') class SayHandler(BaseHandler): def get(self): self.response.write('Say hello!') app = webapp2.WSGIApplication([ ('/', MainHandler), ('/say', SayHandler), ], debug=True)
#4-7: ログを確認しよう
アプリ開発から少し脱線します。
プログラムが正しく動作しない場合、エラーログを確認して問題の手がかりを得て修正します。
App Engineでもエラーログを確認する方法がちゃんと提供されています。
ランチャーのLogsがそれです。
Logsをクリックするとログコンソールが表示されます。
デバッグする時、ログコンソールに文字を出力したい場合が多々あると思います。
その方法も簡単。以下の様なコードで出力できます。
import logging class SayHandler(webapp2.RequestHandler): def get(self): logging.info("============= /say ==============") self.response.write('Say hello!')
「/say」にアクセスすると以下のようにログが出力されます。
ちゃんとログが出力されています。
デプロイしたApp Engineアプリのログは、App Engineのコンソールにアクセスし、左にあるメニューの「Logs」で確認できます。
現在App EngineのコンソールはUI改善中のようです。ページの上部に「Try the new Logs Viewer」をクリックすると、下記のような新しいログ表示画面に移動できます。ちょっと見やすくなっています。
ちゃんとログが出力されています。
デプロイしたApp Engineアプリのログは、App Engineのコンソールにアクセスし、左にあるメニューの「Logs」で確認できます。
現在App EngineのコンソールはUI改善中のようです。ページの上部に「Try the new Logs Viewer」をクリックすると、下記のような新しいログ表示画面に移動できます。ちょっと見やすくなっています。
#5: HTMLを表示しよう!
Hello world!だけだと味気ないので、HTMLを表示してみましょう。
直接self.response.write()メソッドでHTMLを書いても良いのですが、実用向けじゃないのでJinja2というテンプレートエンジンを使います。
いきなり難しそうですが、たいして難しくありません。
しかも、一度書いてしまえば、ずっと使いまわせるので、この際やっちゃいましょう。
まずはアプリにライブラリを追加します。app.yamlのlibrariesという項目にjinja2を追加して以下のように記述します。2行追加しただけ。
libraries: - name: webapp2 version: latest - name: jinja2 version: latest
main.pyの先頭にJinja2の設定を記入します。
import os import jinja2 JINJA_ENVIRONMENT = jinja2.Environment( loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), extensions=['jinja2.ext.autoescape'], autoescape=True)
なにやらちょっとややこしそう。。。
いつか、設定を変更したいということがあったら、このプログラムの周辺を調べてみてください。いろいろ設定できて便利です。
今回は、この設定で十分なので、このままコピペしちゃいましょう。
テンプレートでHTMLを表示する時は
template = JINJA_ENVIRONMENT.get_template("file.html") values = { 'var1': 1 } self.response.write(template.render(values))などと、同じことを何度も書くことになるので、使いまわせるようにテンプレートを使うクラスBaseHandlerを定義しておきましょう。
class BaseHandler(webapp2.RequestHandler): def render(self, html, values={}): template = JINJA_ENVIRONMENT.get_template(html) self.response.write(template.render(values))
これで準備完了です。
MainHandlerはwebapp2.RequestHandlerを継承していましたが、先ほど定義したBaseHandlerを継承するように変更します。
そしてHTMLを表示するように変更します。
class MainHandler(BaseHandler): def get(self): self.render("main.html")
アプリフォルダに以下のmain.htmlを作ります。
<html> <body> <div>Say hello!</div> </body> </html>
完成です。
アプリフォルダの中身はこんな感じになっています。
10行もプログラムかいちゃいましたね。
さっそく開発サーバでアプリを動かしてみます。
ちゃんと動いていますね!
でも表示が同じなのでHTMLなのかあやしいです。
HTMLを覗いてみるとどうなっているでしょうか?
ブラウザで開いているページを右クリックして「ページのソースを表示」を選択するとHTMLを見ることができます。
これでHTMLを表示させる方法が習得できました!
今現在のソースコードはこんな感じになっていますよ。
#!/usr/bin/env python import webapp2 import os import jinja2 import logging JINJA_ENVIRONMENT = jinja2.Environment( loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), extensions=['jinja2.ext.autoescape'], autoescape=True) class BaseHandler(webapp2.RequestHandler): def render(self, html, values={}): template = JINJA_ENVIRONMENT.get_template(html) self.response.write(template.render(values)) class MainHandler(BaseHandler): def get(self): self.render('main.html') class SayHandler(webapp2.RequestHandler): def get(self): logging.info("============= /say ==============") self.response.write('Say hello!') app = webapp2.WSGIApplication([ ('/', MainHandler), ('/say', SayHandler) ], debug=True)
ちなみに、app.yamlはこんな感じです。
application: helloworld-adamrocker version: 1 runtime: python27 api_version: 1 threadsafe: yes handlers: - url: /favicon\.ico static_files: favicon.ico upload: favicon\.ico - url: .* script: main.app libraries: - name: webapp2 version: latest - name: jinja2 version: latest
#6: データを保存しよう!
またWebアプリでも、やはり、データを保存、共有することが多いでしょう。
なので、さっそくデータを保存できるようにしましょう。
#6-1: データ入力フォームを作る
先ほどのmain.htmlを修正してデータを入力できるフォームを作ります。
<html> <body> <form action="/" method="POST"> name: <input name="name" type="text" /><br /> age: <input name="age" type="text" /><br /> <button type="submit">Save</button> </form> </body> </html>
nameに名前を入れ、ageに年齢を入れて保存するだけの投稿フォームです。
methodがPOSTで、actionが「/」になっているので、「/」に対してHTTP POSTリクエストを要求するフォームです。
さっきまではブラウザにURLを直接入力アクセスしていたのでHTTP GETリクエストでしたが、今回はPOSTリクエストを使います。
GETとPOSTリクエストの違いは色々あるのですが、一番わかり易い違いは、URLに表示されるか否かです。
POSTリクエストはURLをブラウザに入力しても表示されませんし、リクエストでサーバに渡すパラメータ(今回だとnameとかage)もURLには表示されません。
サーバ側でのデータ保存にはDatastoreを使います。
画像や大きなファイルを保存するには別の方法(BlobstoreとかGoogle Cloud Storage)を使ったりするので興味があったら発展として調べてみてください。
さっそく何を保存するのかを定義します。
と言っても特別な設定ファイルが必要なわけではなく、プログラム上でクラスを定義するだけです。
今回はnameという文字列とageという数字(整数)を保存します。
文字列や整数以外の値を保存したい場合は、本家のPropertiesというところを参考に、必要なデータを追加してみてください。
あとは、「/」にPOSTリクエストが届くはずなので、まずはそのリクエストを受け付けてデータを保存する処理を書きます。
MainHandlerにpostというメソッドを追加しています。
これがPOSTリクエストを受け付けた時に実行されるメソッドです。
ここでは、POSTリクエストのnameとageというパラメータを受取り、UserDataのインスタンス(user)の各変数に格納してputメソッドでDatastoreに保存しています。保存が終わったら「/」にリダイレクト(移動)しています。
たったこれだけでサーバにデータを保存できます。簡単ですね。
ただ、これだとデータを保存しただけで、取り出すことができません。
最後に、データを取り出す方法を学んでこのエントリーを終わりにしましょう。
MainHandlerのgetメソッドでデータを取り出せるようにします。
取り出し方もかなり簡単です。
たったこれだけでUserDataに保存したデータを新しい順に10個取り出せます。
fetchメソッドの引数が取り出す個数です。最大1000個まで同時に取り出せます。
orderメソッドは並び順です。
取り出したデータをdataの大きい順に取り出しています。
いわゆる降順と呼ばれる並び順です。
反対にデータの小さい順(昇順)に取り出すには。
と書きます。プラスかマイナスを指定するだけです。
queryメソッドで取り出すデータの条件を指定しています。
今回は何も指定していないので、全てのデータです。
(特定の条件に合致するデータを取得する方法は本エントリー下部の発展#1,2を参考にして下さい)
methodがPOSTで、actionが「/」になっているので、「/」に対してHTTP POSTリクエストを要求するフォームです。
さっきまではブラウザにURLを直接入力アクセスしていたのでHTTP GETリクエストでしたが、今回はPOSTリクエストを使います。
GETとPOSTリクエストの違いは色々あるのですが、一番わかり易い違いは、URLに表示されるか否かです。
POSTリクエストはURLをブラウザに入力しても表示されませんし、リクエストでサーバに渡すパラメータ(今回だとnameとかage)もURLには表示されません。
#6-2: データを受け取って保存する
サーバ側でのデータ保存にはDatastoreを使います。
画像や大きなファイルを保存するには別の方法(BlobstoreとかGoogle Cloud Storage)を使ったりするので興味があったら発展として調べてみてください。
さっそく何を保存するのかを定義します。
と言っても特別な設定ファイルが必要なわけではなく、プログラム上でクラスを定義するだけです。
今回はnameという文字列とageという数字(整数)を保存します。
from google.appengine.ext import ndb class UserData(ndb.Model): name = ndb.StringProperty() age = ndb.IntegerProperty() date = ndb.DateTimeProperty(auto_now_add=True)
文字列や整数以外の値を保存したい場合は、本家のPropertiesというところを参考に、必要なデータを追加してみてください。
あとは、「/」にPOSTリクエストが届くはずなので、まずはそのリクエストを受け付けてデータを保存する処理を書きます。
class MainHandler(BaseHandler): def get(self): self.render('main.html') def post(self): name = self.request.get('name') age_str = self.request.get('age') if name is None or age_str is None: self.redirect('/') user = UserData() user.name = name user.age = int(age_str) user.put() self.redirect('/')
MainHandlerにpostというメソッドを追加しています。
これがPOSTリクエストを受け付けた時に実行されるメソッドです。
ここでは、POSTリクエストのnameとageというパラメータを受取り、UserDataのインスタンス(user)の各変数に格納してputメソッドでDatastoreに保存しています。保存が終わったら「/」にリダイレクト(移動)しています。
たったこれだけでサーバにデータを保存できます。簡単ですね。
ただ、これだとデータを保存しただけで、取り出すことができません。
最後に、データを取り出す方法を学んでこのエントリーを終わりにしましょう。
#6-3: データを取り出す
MainHandlerのgetメソッドでデータを取り出せるようにします。
取り出し方もかなり簡単です。
users = UserData.query().order(-UserData.date).fetch(10)
たったこれだけでUserDataに保存したデータを新しい順に10個取り出せます。
fetchメソッドの引数が取り出す個数です。最大1000個まで同時に取り出せます。
orderメソッドは並び順です。
取り出したデータをdataの大きい順に取り出しています。
いわゆる降順と呼ばれる並び順です。
反対にデータの小さい順(昇順)に取り出すには。
order(UserData.date)
と書きます。プラスかマイナスを指定するだけです。
queryメソッドで取り出すデータの条件を指定しています。
今回は何も指定していないので、全てのデータです。
(特定の条件に合致するデータを取得する方法は本エントリー下部の発展#1,2を参考にして下さい)
これをMainHandlerのgetメソッドの中に仕込み、main.htmlの中で表示するようにします。
class MainHandler(BaseHandler): def get(self): users = UserData.query().order(-UserData.date).fetch(10) values = { 'users':users } self.render('main.html', values)
<html> <body> <form action="/" method="POST"> name: <input name="name" type="text" /> age: <input name="age" type="text" /> <button type="submit">Save</button> </form> <hr /> {% for user in users %} <p>{{ user.name }} = {{ user.age }}</p> {% endfor %} </body> </html>
HTMLの中にHTMLらしからぬコードが入っていますね。
これがテンプレートエンジンの便利なところです。
HTMLを自動で作ってくれるのです。
どういうことかと言うと、
{% for ... %}がプログラムでよく書くfor文ですね。
{% endfor %}までの間を繰り返してくれます。
繰返す内容は
<p>{{ user.name }} = {{ user.age }}</p>
です。
つまり、データベースから取得した最大10個のデータ(users)を一つづつ<p>で囲ったHTMLを作成しています。
テンプレートHTMLにデータを渡す方法はMainHandlerのvalues変数がそれです。
サーバのデータを使って、HTMLが自動で作れるなんて便利ですね!
さぁ、完成です。
開発サーバで「/」にアクセスしてみます。
これで自分が作ったサーバにデータを入力して取り出せるようになりました!
お疲れ様でした。これでApp Engineを使って自分だけのサーバを作り、公開できるようになりました。
#7: おわりに
最後に、Google App Engineアプリのサーバ上での状況はコンソールから確認できます。
サーバの使用状況、Datastoreの中身、ログを確認したり。
課金もコンソールから設定できます。
ちょっとでも興味を持たれた方は、本家のチュートリアルにチャレンジして下さい。
本エントリにも発展として#1〜#3までを準備していますが、おさらいにもなるので、本家のチュートリアルはオススメです。
さらに、色々なAPIを使う練習ができるトレーニングコースもあります。App Engineで何ができるのかを勉強するにはもってこいです。
もっと発展して勉強したい方は、本家が公開している、サンプルコードを覗いてみても良いでしょう。
では。お疲れ様でした!
最後のソースコードはこんな感じです。
#!/usr/bin/env python # -*- coding: utf-8 -*- import os import webapp2 import jinja2 import logging from google.appengine.ext import ndb JINJA_ENVIRONMENT = jinja2.Environment( loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), extensions=['jinja2.ext.autoescape'], autoescape=True) class UserData(ndb.Model): name = ndb.StringProperty() age = ndb.IntegerProperty() date = ndb.DateTimeProperty(auto_now_add=True) class BaseHandler(webapp2.RequestHandler): def render(self, html, values={}): template = JINJA_ENVIRONMENT.get_template(html) self.response.write(template.render(values)) class MainHandler(BaseHandler): def get(self): users = UserData.query().order(-UserData.date).fetch(10) values = { 'users':users } self.render('main.html', values) def post(self): name = self.request.get('name') age_str = self.request.get('age') if name is None or age_str is None: self.redirect('/') user = UserData() user.name = name user.age = int(age_str) user.put() self.redirect('/') class SayHandler(webapp2.RequestHandler): def get(self): logging.info("============= /say ==============") self.response.write('Hello world!') app = webapp2.WSGIApplication([ ('/', MainHandler), ('/say', SayHandler), ], debug=True)
app.yamlはこんな感じです。
application: helloworld-adamrocker version: 1 runtime: python27 api_version: 1 threadsafe: yes handlers: - url: /favicon\.ico static_files: favicon.ico upload: favicon\.ico - url: .* script: main.app libraries: - name: webapp2 version: latest - name: jinja2 version: latest
#発展1: 特定の条件でデータを取得する
Datastoreでデータを取得する方法は以下のコードでした。
users = UserData.query().order(-UserData.date).fetch(10)
queryメソッドに条件を加える事で、特定のデータだけを取り出すことができます。
例えば、ageが18以上のデータを取り題したい場合は
query(UserData.age >= 18)
とします。
ただ、これだとエラーになります。
App EngineのDatastoreの特徴で、2つ以上の変数で並び替えができなくなっています。
変数ageでは並び替えをしていないように見えますが、実は、内部のデータとして、ageで並び替えをしているのです。そのおかげで「>=」という範囲指定のフィルタリングができています。
queryメソッドのageにくわえて、orderメソッドでdataについて並び替えをしていますので、2つ以上の変数で並び替えをしているためエラーになります。
回避方法は簡単です。orderメソッドを外すだけです。
users = UserData.query().fetch(10)
さらに、dataで並び替えしたい場合は、取得したusersを並び替えれるプログラムを組めばOKです。
sorted_users = sorted(users, key=lambda u:u.age)
値が大きい順にソートしたい場合は、
sorted_users = sorted(users, key=lambda u:u.age, reverse=True)
#発展2: 値を指定してデータを取得する
では、ageの値を指定して取得したらどうなるでしょう?
こんな感じ、
query(UserData.age == 18)
これは大丈夫。ちゃんと動きます。
ただし、ちょっと注意が必要です。
アプリを作った時に自動で作られたファイルにindex.yamlがありました。
いままで、なににも使っていなかったのですが、このファイルをおもむろに開いてみましょう
indexes: - kind: UserData properties: - name: age - name: date direction: desc
自動で何かが追加されています。
これはどう読みとくかというと、
UserDataに対して、ageとdataというパラメータで条件をつけてデータを取り出すので予め準備しておいてね。
特にdataは降順(desc)でとり出すからそこんところよろしく。
ということです。
Datastoreの場合は、事前にアクセスする方法をデータベースに教えて必要があります。
ちょっと煩わしいですが、自動で追加されるので、便利ですね。
開発サーバで事項しないとindex.yamlは自動で追加されないので、その時は自分で記入することになります。
自動で追加されるということは、自分の知らない間にドンドン増えていく事になります。
何が起きているかだけでも把握しておくと良いでしょう。
#発展3: Androidからデータを保存する
AndroidでApp Engineのサーバにデータを保存してみましょう。
private void access() { ArrayList params = new ArrayList(); params.add(new BasicNameValuePair("name", "ADAM")); params.add(new BasicNameValuePair("age", "1")); HttpPost httpPost = new HttpPost("http://helloworld-adamrocker.appspot.com/"); try { httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } DefaultHttpClient client = new DefaultHttpClient(); try { HttpResponse httpResponse = client.execute(httpPost); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } new AsyncTask() { @Override protected String doInBackground(String... params) { access(); return null; } }.execute();
ブラウザで結果を確認してみます。
ちゃんとADAMと1が入っていました!
これでAndroidからクラウドにデータを保存することができました。
これを取り出してアプリに反映させることが出来れば、、、
他の人とデータを共有できれば、、、
アプリの可能性がドンドン広がりそうですね^^