ラベル google app engine の投稿を表示しています。 すべての投稿を表示
ラベル google app engine の投稿を表示しています。 すべての投稿を表示

2016/04/01

AppEngineで簡単WANアドレス取得アプリ作成

昔 Google App EngineでWANアドレス管理サーバー なんて記事を書いたが、今回は最も簡単な方法で作ってみる。管理いらずの方法で目的は果たせる。

前回、Google Cloud Platformでプロジェクト作成してGitホスティングする記事を書いたが、そこまでは同じ。

プロジェクトのダッシュボードへ行く。
(AppEngineのダッシュボードはアプリ稼働させてから行くところ)

「Try App Engine」をクリックして、"Hellow World"アプリ作成とデプロイまでのチュートリアルをスタートさせる。

どの言語を使用するかだが、Java、Python、PHP、Go の4つが無料枠で使用できる。
そのうちのお好みの言語で。
ブラウザはChromeがいいかも。Firefoxでやったらコピペしたい文字列が空欄になった・・

ここでは最も簡単なPHPとGoでやってみる(だけど自分でコードは一切書かない)

「PHPのチュートリアルを開始する」で開始すると右側にチュートリアルが出てくる
「次へ」
「プロジェクトを作成する」 前記事の要領で
「次へ」
左上のサービスメニューから”開発”を選択 (言う通りにしないとチュートリアルが進まない)
「次へ」
ソースコードリストから"app.yaml"ファイルをクリックしてオープンする
”/”をクリックしてリストへ戻る (という手順のチュートリアルなのだ)
「次へ」
Google Cloud Shellを起動 (ナビバーの「>.」アイコン)
"show me how"というリンクをクリックすればどこをクリックすればいいのか教えてくれる。
初めての起動だと、インスタンスの構築に若干時間がかかる。2016年末まで無料だそうな。

Shellで
$ cd src/(project ID)/gcloud でクローン済みのリポジトリへ移動
チュートリアルに表示されている文字列をShellへコピペ実行でもOK
「次へ」
$ gcloud preview app deploy でデプロイする
チュートリアルに表示されている文字列をShellへコピペ実行でもOK
「次へ」もしくはチュートリアルキャンセル
チュートリアルはAppEngineのダッシュボードでステータス表示と続くが、この辺でキャンセルしてもいい。

WANアドレス表示アプリに改造

”開発”ダッシュボードからhelloworld.phpをクリック
「編集」で編集モードに
'Hello, world!' を $_SERVER['REMOTE_ADDR'] に書き換え
commit」でコミットする
Google Cloud Shellで
$ git pull する
$ gcloud preview app deploy でデプロイする

Goでも流れは同じだけど、Shellで編集する流れにすると
$ emacs hello/hello.go (なんとCloud ShellでEmacsが最初から使えるよ!)
handlerメソッドの fmt.Fprint(w, "Hello, world!") を fmt.Fprint(w, r.RemoteAddr) に変更
キーバインドが違うんでカーソル移動が厳しいか・・・
$ gcloud preview app deploy (デプロイ)
一応commit&pushも
$ git config --global user.email "my_name@gmail.com" (セットしてなければ)
$ git config --global user.name "my name" (セットしてなければ)
$ git commit -a -m "メッセージ"でコミット
$ git push origin
プレビュー確認
$ goapp serve  (Goのチュートリアルやると分かるがCloudShellからテストサーバー起動出来る)
Shellウインドウ左端の「ウェブでプレビュー」アイコンクリックすると表示される

https://(プロジェクト ID).appspot.com へアクセスするとWANアドレスが表示される

チュートリアルでGoogle Cloud Shellを使う流れになっているけど、2017年になったらどういうチュートリアルになるんだろ?素朴な疑問

javaの場合は
$ mvn gcloud:run テストサーバー起動
$ mvn gcloud:deploy デプロイ
忘れそうなので書き残しておく

gcloudブランチ
チュートリアルから作成したリポジトリは、masterではなくgcloudブランチを使う流れになっている。
masterとgcloudブランチの違いは、app.yamlファイル。
masterにはapplication: ~と version: ~の記述が冒頭にあるけど、gcloudブランチには無い。

試しにmasterブランチをgcloudデプロイしてみたら、applicationとversion記述はもう要らぬ!と怒られてデプロイできなかった。


ログからWANアドレスを知る

自宅以外の場所から自宅の現在のWANアドレスを知ために、以前は今回のようなアプリに更にアドレスをアプリで保存管理出来るようにしたり、変更された際にはメール通知するとか色々めんどくさいことをしていた。

今回は最も簡単(いい加減?)な方法で確実に知ることが出来る方法だ。

「ツールとサービス」メニューから”Stackdriverのログ"から、GETリクエストログの1つを開けてみれば、その先頭にWANアドレスが書かれている。

Stackdriverサービスのログリスト観覧だけなら、これも無料枠で大丈夫。

cron/crontabで定期的にwgetでアクセスさせておく。認証もしないシンプルアクセス

我が家のサーバーへアクセスしたくなった時、このログを出先で確認すればいいのです。
もう認証やらメール通知やら、小難しいことしないでも十分実用になっております。

懸念点としては、攻撃にあって通信量で無料枠オーバーとか不特定多数のログが多すぎて
自分のアクセスが見つからない・・・とか?
今のところその心配は皆無です。自分以外のアクセスは0です。

ログを見れば分かるからAppEngineアプリもHelloWorldのままでいいじゃんとも思うが、
外でも仕事で今のWANアドレス(GWはどれ通ってる?)知りたい時に手軽に利用できるんで
表示させるだけでも便利なのです。

2012/06/30

Google App EngineでWANアドレス管理サーバー(開発編)

Google App Engineサイトを活用して家のWANアドレス管理サーバーを構築する

開発環境は前回整えたので、サーバーアプリ作ってみた。と言ってもやることは
ごく簡単なことなのでAppEngineの知識はチュートリアルレベルで十分。

アプリケーションを定義するapp.yamlは以下の様な感じ。
[app.yaml]
application: アプリ名
version: 1
runtime: python27
api_version: 1
threadsafe: yes

handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico

- url: .*
  script: main.app
#  login: required

libraries:
- name: webapp2
  version: "2.5.1"
サンプルをコピペしてapplication: のところを変更しただけかな。

アプリにアクセスした時に起動するメインプログラムがこちら。
[main.py]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import webapp2
from google.appengine.api import users
from google.appengine.ext import db
from datetime import datetime,timedelta


class AppUser(db.Model):
    account = db.UserProperty()
    lanaddr = db.StringProperty()
    address = db.StringProperty()
    apppath = db.StringProperty()
    updated = db.DateTimeProperty()


class RootHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write( self.request.remote_addr )


class MyHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/html'

        user = users.get_current_user()
        if user:
            self.response.out.write(''+user.email()+'')

            app_users = AppUser.gql("where account = :1",user)
            for au in app_users:
                if self.request.remote_addr == au.address:
                    url = "http://"+au.lanaddr+au.apppath
                    non = "http://"+au.address+au.apppath
                else:
                    url = "http://"+au.address+au.apppath
                    non = "http://"+au.lanaddr+au.apppath
                self.response.out.write(""+url+"
")
                self.response.out.write(non+"
")
        else:
            self.redirect(users.create_login_url(self.request.uri))


class CommitHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        user = users.get_current_user()
        if user:
            app_users = AppUser.gql("where account = :1",user)

            if app_users.count() == 0:
                ip = AppUser()
            else:
                ip = app_users[0]

            ip.account = user
            ip.updated = datetime.now()
            ip.address = self.request.remote_addr
            ip.apppath = self.request.get('path')
            ip.lanaddr = self.request.get('lan')
            if ip.lanaddr == "":
                ip.lanaddr = "localhost" 
            ip.put()
            self.response.out.write(ip.address+" "+ip.lanaddr+" "+ip.apppath+" COMMITED")
        else:
            self.redirect(users.create_login_url(self.request.uri))


class AdminHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        user = users.get_current_user()
        if user:
            if users.is_current_user_admin():
                self.response.out.write('AppUsers')
                for u in AppUser().all():
                    self.response.out.write("\n"+u.account.email())
                    self.response.out.write(" "+u.lanaddr)
                    self.response.out.write(" "+u.address)
                    self.response.out.write(" "+u.apppath)
                    self.response.out.write(" "+str(u.updated+timedelta(hours=9)))
        else:
            self.redirect(users.create_login_url(self.request.uri))

            
app = webapp2.WSGIApplication([('/', RootHandler),
                               ('/my', MyHandler),
                               ('/commit', CommitHandler),
                               ('/admin', AdminHandler)
                               ],
                              debug=True)
サンプルをコピーして、必要なコードを書き加えていったところ、こんななりました。
Apacheライセンスである事が記述されてますが、サンプルがそうなっていたのでそのまま残してます。自分はそんなつもりはないんだけど。

順を追って説明しておくと

import webapp2
'webapp2'フレームワークを使いますってこと。サンプルがそうなってた。

from google.appengine.api import users
ユーザサービスというアカウント認証関係のAPIを使用する。WANアドレスをこのアカウントと紐付けるため。

from google.appengine.ext import db
データストアというデータベースを使用する。これ使ってデータ管理する。

from datetime import datetime,timedelta
これがちょっと悩んだんですが、サーバー側の時間が全部UTCなんですね。そこからJSTへ変換するための最小モジュールをインポート。pythonモジュールで時間関係の便利なモジュールは沢山あるけれど、やりたいことはたった一つ
self.response.out.write(" "+str(u.updated+timedelta(hours=9)))
の部分なので、timedelta使うことに。
この辺は、'google app engine utc jst'でググれば沢山親切ページが現れる。

以下の様な定義を入れておくことで、データストアにAppUserという自分専用のデータテーブルが作成される

class AppUser(db.Model):
    account = ユーザアカウント(Googleアカウントオブジェクトインスタンス)
    lanaddr = LANアドレス(コミット時に自分でパラメータ指定する)
    address = WANアドレス(self.request.remote_addrで得られる。目的の物!)
    apppath = 自分ちのサーバーアプリへのURL(コミット時に自分でパラメータ指定する)
    updated = コミット時の更新日時
プログラム上では、この'AppUser'クラスを使ってDBへアクセスする。

最初に実行されるフレームワークアプリケーションオブジェクト生成部分でハンドラの定義をする。

app = webapp2.WSGIApplication([('/', RootHandler),
                               ('/my', MyHandler),
                               ('/commit', CommitHandler),
                               ('/admin', AdminHandler)

http://[app-name].appspot.com だったら、RootHandler
http://[app-name].appspot.com/my だったら、MyHandler
http://[app-name].appspot.com/commit だったら、CommitHandler
http://[app-name].appspot.com/admin だったら、AdminHandler
で、それぞれの'get'メソッドが呼ばれる。Railsでいうところのroutesかな。

class RootHandler(webapp2.RequestHandler):
ここはとにかく単純にWANアドレスを返す。そういうサイトよくあるよね。
バッチ処理とかで使いやすいようにプレーンテキストでアドレスのみを返すようにした。結構便利。

class MyHandler(webapp2.RequestHandler):
自分のアドレスを返すところ。
ログインユーザのアカウントに紐付いたデータが登録されていれば、それをリンクで表示。これがやりたかっただけなんだけど、道のり長いっす。
ログインしていないなら、ログインページへ飛ばす。ログインしたら戻ってくる。
self.redirect(users.create_login_url(self.request.uri))
がフレームワークとユーザサービスの便利な組み合わせ。
これもチュートリアルの範囲内。

class CommitHandler(webapp2.RequestHandler):
"http://[app-name].appspot.com/commit?lan=XXXX&path=YYYY" ってアクセスする。
アカウント認証されたユーザならば、DB検索してマッチしたデータエンティティに、なければ新たなエンティティにデータを記録するところ。ログインしてなければ同じようにログインページへ飛ぶ。

class AdminHandler(webapp2.RequestHandler):
ここはデータテーブルの一覧を表示するところ。管理者(自分)のみが観覧できる。

チュートリアルに従って作って、ブラウザ上からアクセスして機能テスト。問題なし!

実際にはここまで作るだけでも試行錯誤したけど・・・本題とは遠いところで詰まった。
例えば、UTC=>JSTのシンプル解決に悩んだりとか。
適当なモジュールをインポートしてもサーバーになかったりとか。
途中からDBモデルにlanaddrを追加したんだけれど、なぜか更新されないとか。
'https://appengine.google.com'のアプリ管理からデータテーブルを削除したり、ログ見たり・・・

だけど、一番苦労したのは、ログイン認証だ。
サーバーは出来たが誰でもアクセスできちゃう。管理ユーザ(自分)だけしかアクセスできないパーミッション設定が可能だけど、それはそれで面倒だ。
個人のデータを保持するにはやはり個人を特定する認証が必要なのだ。
(もし利用希望者がおられれば、URLお教えしますが・・・)

ブラウザからならログインページが出て、ログインしたら後はブラウザがよろしくやってくれるけど、やりたいのは自分ちのUbuntuServerから、cronタスクで処理したいので、ブラウザがやってることを自分でやらんとならん。

googleアカウント認証は、こちらを参考にしましたよ
http://johannilsson.com/2011/04/13/authenticated-requests-on-app-engine.html
http://maimon-it.blogspot.jp/2010/04/authenticating-google-app-engine-apps.html

スクリプトでのログイン認証と、認証付きアクセスを記述したものがこちら
[gauth.py]
#!/usr/bin/python
# -*- coding: utf-8 -*-

from subprocess import *
from os import path

mail = 'Googleメールアドレス'
password = 'パスワード'
app = 'アプリ名'

app_url = 'http://'+app+'.appspot.com/'
cookie_file = path.expanduser("~")+'/.google_myapp_cookie'

def login():
    try:
        # API認証キーを取得
        print 'Get Auth-Key...\n'
        auth = Popen(['curl','-f','-s',
                      'https://www.google.com/accounts/ClientLogin',
                      '-d','accountType=HOSTED_OR_GOOGLE',
                      '-d','Email='  + mail,
                      '-d','Passwd=' + password,
                      '-d','source=' + app,
                      '-d','service=ah'],
                     stdout=PIPE).communicate()[0]
        # ログインしてクッキー取得
        print 'Login...\n'
        login = Popen(['curl','-c','-',
                       app_url+'_ah/login?auth='+auth[ auth.rindex("Auth=")+5:-1]],
                      stdout=PIPE).communicate()[0]
        cookie = login[login.rindex("ACSID")+6:-1]

        # クッキーの保存
        f = open(cookie_file,'w')
        f.write(cookie)
        f.close()
        return cookie
    except:
        return ""

def get(controller):
    try:
        f = open(cookie_file)
        cookie = f.read()
        f.close()
    except:
        cookie = login()

    try:
        return Popen(['curl',app_url+controller,'-b','ACSID='+cookie],
                     stdout=PIPE).communicate()[0]
    except:
        return ''

pythonのsubprocessや、外部コマンド'curl'については、親切解説しているところが沢山あるんで。そちらで、よろしくです。

処理内容を簡単に説明する

loginメソッド:(ログインしてクッキーを得るまで)

'https://www.google.com/accounts/ClientLogin' へ所定のパラメータ付きでアクセスしてアカウント認証データを取得。
'http://[app-name].appspot.com/_ah/login?auth=認証データのAuth=以降のデータ列' ってやるとログイン出来る。
ログインした時に得られるデータ内に"ACSID"というキーワードのデータ列があるんで、その部分を切り出して保存。
これがこのサイトへのログインクッキーデータ。
ドキュメントによると、Googleアカウントだけじゃなく、OpenIDでの認証も出来るらしいけど、その辺は無視。

getメソッド:(クッキー使ってアクセス)

クッキーデータをファイルから読んで、認証付きで指定URLへGETアクセスする。
手順がやっつけっぽいが、一応機能する。
Cookieが無効になる期限がどれくらいなのかイマイチ不明なので、その辺調べてはっきりしてから、ちゃんとした手順に直そうかな。
・・・
ということで、修正した getメソッドがこちら

def get(controller):
    for i in range(2):
        try:
            f = open(cookie_file)
            cookie = f.read()
            f.close()
            ret = Popen(['curl',app_url+controller,'-b','ACSID='+cookie],
                        stdout=PIPE).communicate()[0]
            if ret.find('COMMITED')>0:
                return 'ok'
            else:
                raise
        except:
            cookie = login()
    return 'err'

Cookieが無効になるのは、https://appengine.google.com から、アプリ設定ページでCookie Expirationというところで設定するのだった。1日、1週間、2週間が設定できる。デフォルトは1日だった。最長の2週間に設定したがそれでも短いので、コミットに失敗したらログインからやり直しするように変更した。

cronで実行するスクリプト
[gcommith.py]
#!/usr/bin/python
# -*- coding: utf-8 -*-
import gauth
# コミット
#gauth.get('commit?lan=192.168.X.Y&path=/foo')
gauth.get('commit?lan=192.168.X.Y')

cronタスク
crontabでユーザレベルタスクで回してます。
$ crontab -e
ってやって、自分の好きなエディタで開いたら

0 3 * * * python ~/Grive/gcommit.py

って書いておく。毎日3時にコミット実行です。crontabで回すと自分にメールでタスク結果が飛んでくる。開発中は飛んでくるのが便利だけど、落ち着くとうざいね。止めちゃおっかな。

この記事をよく読んだ奇特な方はおや?と思ったかも。
gcommit.pyの冒頭に、#!/usr/bin/pythonって書いてあるのに、なぜに「python ~/Grive/gcommit.py」なのかと。その謎は、パス名[/Grive]に隠されております。
どうでも良い話ですが・・・次回はその話にちょっと脱線します。

2012/06/27

Google App EngineでWANアドレス管理サーバー(開設編)

自分の元wavecastマシンへ外からアクセスするには誰かがWAN側のIPアドレスを管理する必要がある訳だけど、今まではwavecast.tvがその役割をしていた。けれど、UbuntuServerで再構築してしまったので、自分でなんとかしないと。
固定IPとかDDNSではお金がかる。

そこで思ったのが、今流行のクラウドサービスを利用すれば無料で出来るんじゃん?と。
で軽く調べると世の中には沢山似たようなサービスがあったとさ。ありすぎてよくわからないので、自分に一番身近で無料で気軽に使えそうなのが、Goole App Engine だった。

ここで使えるスクリプト言語は日頃使っているJavaやPythonで書けるらしいと。
(ほんとはRubyで書きたいけど。実はそういう環境を作れたりするんだなこれが)
無料で10個の独立したアプリケーションを作れるらしいと。
MySQLじゃないけど、もどきSQLDBっぽいものが使えるらしいと。
Cronタスクも走らせられるらしいと。
日本語ドキュメントも分かりやすい。いけそうだ。

プランとしては、
app-engine上の自分のアプリにcronとかで定期的に元wavecast(名前が欲しいな・・)でアクセスして、そんときのWANアドレスをアプリ側のDBで覚えておいて、アプリにブラウザでアクセスしたら、WANアドレスでのURLが出てくるようにすればよい。
wavecast.tvとイメージは同じだ。

サイト開設のために必要なもの
・当然googleアカウントがいる。
・携帯キャリアのメールアドレスがいる。うーん。これがいやだ。

アプリケーション作成(サーバー)
appengine.google.comでアプリケーション名を決めてアプリ作成。
ここで作成した名前はhttp://アプリ名.appspot.comでアクセスできる。
自分は.comドメインを持ってるんで頭を同じ名前をつけた。
早い者勝ちなので、何はさておき、作っておきます。

SDKダウンロード
ローカルでアプリ開発するためのライブラリとアップロード・ダウンロードするツール一式が入ったSDKをダウンロードする。JavaにするとJRubyが使えるようになるらしいけど、今んところPythonじゃないとサポートしていないサービスがあるらしいのでPythonにした。


そうそう。Mac/Windows版のSDKを使えばGUIで便利にできるらしい。
私は端末一つで十分なので使わない。試しにWindows版を入れてみたけどMinGWやCygwinとか色々入ってるためかまともに動かなかった。やっぱりConsoleが一番!


アプリケーション作成(ローカル)
チュートリアルにそって、ざっと流れを把握する。これってRailsみたいだ。
全くその通りのことを説明されているぺーじもありました→appengineでRoR
チュートリアルのHelloWorldはせっせと必要なファイルを手作業で作る説明になってるけど、SDKに、'new_project_template'があるんで、これをコピって使うのが手っ取り早い。
アプリ名と同じ名前のディレクトリを作って、そこに放り込む。

app.yaml
application: サーバー側に作成したアプリ名を記述

main.py
あとは、ここのgetメソッドに色々書けばアプリとして動作する。

動作テスト
$ dev_appserver.py [app-name] で、テストサーバーを起動する。
http://localhost:3000でアクセスして動作を確認。

アップロード
$ appcfg.py update [app-name] で、サーバーにアップロード
動作テストが終わったら、本物へアップロードして動作確認。

サイト開設から、アプリ動作確認まで出来たので、次回は目的のものを作ってみる。