上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。


--.--.--|スポンサー広告||TOP↑


スポンサーサイト
2010.06.30|パソコンな日々コメント(0)TOP↑


http://www.cresc.co.jp/tech/java/Servlet_Tutorial/Lesson_38.htm

継続した接続とチャンクド応答

Tomcat 4.0でHTTP/1.1応答を調べる)

 

HTTP/1.1対応サーブレット・エンジンは継続した(パーシスタント)TCP接続にどう対応するのでしょうか?ここでは、スタンドアロンでHTTP/1.1対応となったTomcat4.0版をダウンロードして調べます。Tomcat 4.0はベータ版で、今までの3.xとは別のCatalina(これもTomcatも米海軍機の名前であり、ここからこのプロジェクトの名前がつけられたのかも知れません)というプロジェクトの開発によるものです。ここではチャンクド応答についても詳しく説明します。

 


 


VisualAge for JavaWebsphereテスト環境、Apache Tomcatテスト環境双方ともに現在はHTTP/1.1サーバに対応していない。しかしながら、Tomcatのスタンドアロンのモードは4.0版からHTTP/1.1対応である。4.0版はこれまでと別のCatalinaと呼ばれるプロジェクトグループ(TomcatCatalinaも有名な海軍機なので、その辺からこの名前がつけられたのかもしれない)が開発したものを採用したもので、まだベータ版であるが、実験的にこれを用いて、皆さんのコンピュータだけでこの継続した接続とチャンク応答を実習することができる。Tomcat 4.0 Servlet仕様書2.3版及びJSP 1.2仕様にも対応している。Tomcat 4.0のダウンロードとインストールは添付資料を参照されたい。

 

継続した接続に対するHTTP/1.1サーバの対応は以下のようになるのが好ましい。しかしながら、Tomcat4.0のスタンドアロンのモードではちょっと異なる。即ち:

-      HTTP/1.0クライアントではとにかく応答したらTCP接続を切る

-      HTTP/1.1クライアントには応答の後は、クライアントが切断するかタイムアウトになるまでは絶対TCP接続を切らない

-      HTTP/1.1クライアントにはサーブレットでContent-Lengthを付けない場合はChunkedで応答を返す(つまりバッファに入ったコンテントはその都度直ちに送信、即ちコミットされた状態にする)。サーブレットがContent-Lengthを付けた場合はChunkedにはしない

 

クライアントの要求

HTTP/1.1サーバの対応

Tomcat 4.0スタンドアロンの対応

HTTP/1.0クライアント

Connection: Keep-Aliveヘッダ行なし。またはConnection: Close行つき。

応答にConnection: Closeをかける。

TomcatConnection: Close行を付加してTCP接続を切る。Connection: Keep-Aliveヘッダ行は無視。

Connection: Keep-Aliveヘッダ行つき。

Content-length:ヘッダ行をつけ、Connection: Keep-Aliveヘッダ行または、Connection: Close行をつける。Content-length:ヘッダ行をつけないときはConnection: Close行をつける

サーブレットでContent-length:ヘッダ行をつけてもつけなくても、TomcatConnection: Close行を付加してTCP接続を切る。またConnection: Keep-Aliveヘッダ行をつけてもTCP接続を切る。

HTTP/1.1クライアント

Connection: Close行つき。

応答にConnection: Closeをかける。

Chunkedエンコードして、サーブレットが作ったConnection: Close行は無視してTCP接続は切らない。

その他

Content-length:ヘッダ行またはTransfer-Encoding: Chunked行をつけ、最後の応答以外はConnection: Close行をつけない。Content-length:ヘッダ行もTransfer-Encoding: Chunked行も付けないときはConnection: Closeをかける。

Chunkedエンコードして、TCP接続は切らない。

 

皆さん各自これをHelloWorldAnotherHelloWorldのサーブレットを使ってTelnetで確認していただきたい。HTTP/1.0クライアントに「継続した接続」を全く許していないのは、Netscapeのユーザには不満かもしれない。このようにサーブレット・コンテナによって細かいところで実装に相違がでるので、最終的に皆さんのアプリケーションが導入(デプロイ)されるサーバの特性を確認することが重要である。

 

以下にチャンクド・エンコーディングについて説明する。

 

HTTP/1.1サーバは、複数の要求に対応したコンテントをひとつの応答に含めて返すことができる。その場合はコンテントごとを固まり (Chunking:厚切りとか、かたまりとかいう意味だが、おだんごとか、もっと下品に馬糞とか?いうイメージ) にしてこれを複数ボディ部に収容する。このようなHTTP応答のヘッダには:

Transfer-Encoding: Chunked

なる行を含めるとともに、以下のルールでボディ部を構成する。

 

チャンクド・エンコーディング

-      ボディ部は複数のチャンクからなり、最後のチャンクは長さゼロ(“0”)とする。

-      その後にオプショナルなフッタがつき、最後に空白行(\r\nのみ)がボディ部の終わりを示す。

-      各チャンクは長さを示す行とデータからなる。

-      長さは16進表示である。この行には長さの後にセミコロンに続くパラメタを付すこともできるが、これの用途は特に規定されていない。この行の終わりに復帰改行(\r\n)がつく。

-      データ部はデータ自身でそれに復帰改行(\r\n)がつく。

 

下表はその例である。ヘッダ行にはTransfer-Encoding: chunkedなる行があり、ボディ部がチャンクの集まりであることを示している。最初のチャンクは26バイト(16進で1a)、2番目のチャンクは16バイト(16進で10)のデータからなる。一連のチャンクの終わりは長さ0のチャンクである。次の2行がオプショナルなフッタ行で、最後の空白行がボディの終わりを意味する。データがテキストでなく、完全なバイナリの場合はMIMEエンコーディングで7ビット文字列に変換する場合が多い。

 

チャンクド応答の例

これと等価な非チャンクド応答

HTTP/1.1 200 OK

Date: Fri, 25 May 2001 23:59:59 GMT

Content-Type: text/plain

Transfer-Encoding: chunked

[空白行]

1a; ignore-this-parameter

abcdefghijklmnopqrstuvwxyz

10

1234567890abcdef

0

some-footer: some-value

another-footer: another-value

[空白行]

HTTP/1.1 200 OK

Date: Fri, 25 May 2001 23:59:59 GMT

Content-Type: text/plain

Content-Length: 42

some-footer: some-value

another-footer: another-value

[空白行]

abcdefghijklmnopqrstuvwxyz1234567890abcdef

 

以下は、実際にHelloWorldのサーブレットをTomcat 4.0がチャンクドで返したものである。なお、Host:のヘッダ行はHTTP/1.1では必須の行であり、これを省略すると誤りとされるので、面倒ではあるが省略してはならない。詳細は添付資料「HTTP(Hyper Text Transfer Protocol)の基礎」を参照のこと。

 

 

GET /examples/servlet/HelloWorld HTTP/1.1  <HTTP/1.1で要求した>

Host: localhost:8080                         <HTTP/1.1ではこの行は必須>

Connection: Close                            <この指示はエンジンが無視>

 

HTTP/1.1 200 OK

Content-Type: text/html; charset=Shift_JIS

Date: Fri, 08 Jun 2001 05:52:11 GMT          <エンジンが付加>

Transfer-Encoding: chunked                   <エンジンが付加>

Server: Apache Tomcat/4.0-b5 (HTTP/1.1 Connector)

 

6                             <チャンク1の長さ>

<HTML>

2                             <チャンク2は\r\nの2バイト>

 

 

27

<HEAD><TITLE>Hello World</TITLE></HEAD>

2

 

 

6

<BODY>

2

 

 

27

<BIG>Hello World from(株)クレス</BIG>

2

 

 

e

</BODY></HTML>

2

 

 

0                             <終了チャンクは0バイト>

 

 

 

ところで、サーブレットでConnection: Chunkedヘッダ行をつけ、チャンクド形式のデータをストリームで出力したらTomcat 4.0エンジンはどのような対応をするであろうか?応えは否である。次のようなChunkedHelloWorldサーブレットで試してみよう。

import java.lang.*;

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

/**

 * ChunkedHelloWorldは、チャンク(塊)としてHTTP応答のボディを作成するサンプルである。

 * @author: Terry

 */

public class ChunkedHelloWorld extends HttpServlet {

/**

 * Process incoming HTTP GET requests

 */

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

         performTask(request, response);

}

/**

 * Process incoming HTTP POST requests

 */

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

         performTask(request, response);

}

/**

 * Returns the servlet info string.

 */

public String getServletInfo() {

         return "ChunkedHelloWorld for Persistent-Connection evaluation, Version 1.0 by Terry";

}

/**

 * ChunkedHelloWorldの要求処理部では、日本語混じりのHTMLテキストをOutputStreamWriterで

 * バイトデータに変換してByteArrayOutputStreamバッファに蓄積する。このバッファのサイズから

 * バイトサイズを知ってチャンクを作成できる。

 */

public void performTask(HttpServletRequest request, HttpServletResponse response) {

         try

         {

                  

                   //プロトコル指定可能ではありませんでした

//                 response.setHeader("Protocol", "HTTP/1.1");

                  

                   response.setContentType("text/html");

                   //必要ならここに文字セット指定を以下のように追加する

//                 response.setContentType("text/html; charaset=Shift_JIS");

 

                   //ボディがチャンクドであることをヘッダ行で知らせる

                   response.setHeader("Transfer-Encoding", "Chunked");

 

                   //必要ならこれでTCP接続の開放(Close)や継続(Keep-Alive)を行う

                   response.setHeader("Connection", "Keep-Alive");

 

                   //バイト応答出力用OutputStreamの取得

                   OutputStream os = response.getOutputStream();

                  

                   //バイト変換したデータ受領用ストリームを用意する

                   ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);

                   //指定したサイズは、不足が生じたら自動的に拡大される

                  

                   //バイトデータ一時退避用ストリームを用意する

                   ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024);

                   //指定したサイズは、不足が生じたら自動的に拡大される

                  

                   //バイト列に変換のためのOutputStreamWriterの取得

                   OutputStreamWriter osw = new OutputStreamWriter(baos, "Shift_JIS");

                   System.out.println("OutputStreamWriter.getEncoding: " + osw.getEncoding());

                   //エンコーディングは"Shift_JIS"や"ISO-2022-JP"や"EUC-JP"などを指定する

                   //デフォルトのサイズは8192バイトである

 

                   //これを更にBufferdWriterで包んでコードコンバータがwrite()毎に呼ばれないようにする

                   Writer out = new BufferedWriter(osw);

                   //デフォルトのサイズは8192バイトである

 

                   //HTMLページの最初の部分の記述

                   out.write("<HTML>");

                   out.write("<HEAD><TITLE>Chunked Hello World: Part 1</TITLE></HEAD>");

                   out.write("<BODY><BIG>Chunked Hello World from クレス (Part 1)<BR>");

 

                   //文字変換とバイト形式でバイトアレーバッファへの移しこみ

                   out.flush();

//                 osw.flush();       //これはflush()の伝播により不要であるので今後書かない

 

                   //最初のチャンクの退避

                   baos.writeTo(bytes);

 

                   //最初のチャンクのサイズ表示行の入力

                   baos.reset();

                   out.write(Integer.toHexString(bytes.size())+"\r\n");

                   out.flush();

                   bytes.writeTo(baos);

                   bytes.reset();

 

                   //最初のチャンクを送信バッファに移してこれをコミットする

                   System.out.println("ByteArrayOutputStream: " + baos.toString());

                   baos.writeTo(os);

                   baos.reset();

                   os.flush();

                  

                   //HTMLページの最後の部分の記述

                   out.write("Chunked Hello World from クレス (Part 2)</BIG></BODY>");

                   out.write("</HTML>");

                           

                   //文字変換とバイト形式でバイトアレーバッファへの移しこみ

                   out.flush();

 

                   //最後のチャンクの退避

                   baos.writeTo(bytes);

 

                   //最後のチャンクのサイズ表示行の入力

                   baos.reset();

                   out.write("\r\n"+Integer.toHexString(bytes.size())+"\r\n");

                   out.flush();

                   bytes.writeTo(baos);

                   bytes.reset();

 

                   //最終チャンク表示行とパケット終了の空白行を入力

                   out.write("\r\n0\r\n\r\n");

                   out.flush();

 

                   //最後のチャンクと終了行を送信バッファに移してこれをコミットする

                   System.out.println("ByteArrayOutputStream: " + baos.toString());

                   baos.writeTo(os);

                   baos.reset();

                   os.flush();

                  

                   //全てのバッファのクローズ

                   out.close();

                   osw.close();

                   bytes.close();

                   os.close();

         }

         catch(UnsupportedEncodingException e){

                            System.err.println("Encoding not supported: " + e.getMessage());

         }

         catch(IOException e)

         {

                            System.err.println("IOException during AnotherHelloWorld.performTask: " + e.getMessage());

         }

}

}

これに対するTomcat 4.0エンジンの応答は次のようになる。

GET /examples/servlet/ChunkedHelloWorld HTTP/1.1

Host: localhost:8080

 

HTTP/1.1 200 OK

Content-Type: text/html

Date: Mon, 11 Jun 2001 03:24:10 GMT

Transfer-Encoding: Chunked

Transfer-Encoding: chunked

Server: Apache Tomcat/4.0-b5 (HTTP/1.1 Connector)

Connection: Keep-Alive

 

78

74

<HTML><HEAD><TITLE>Chunked Hello World: Part 1</TITLE></HEAD><BODY><BIG>Chunked

Hello World from クレス (Part 1)<BR>

49

 

3c

Chunked Hello World from クレス (Part 2)</BIG></BODY></HTML>

0

 

 

0

 

つまりTomcat 4.0エンジンはサーブレットが作ったTransfer-Encoding: chunkedヘッダ行を受け付けず、テキストであろうがバイトストリームであろうが勝手にチャンクド形式に変換してしまうのである。従って、チャンクド形式はプログラマからは操作不能となっているのである。

 

 

前節     目次     次節

 



2010.06.30|DreamNewsコメント(0)TOP↑

http://www.cresc.co.jp/tech/java/Servlet_Tutorial/Handouts/Servlet_Tutorial_A.doc

proxy
http://www.atmarkit.co.jp/fsecurity/rensai/webhole05/webhole01.html

2010.06.30|DreamNewsコメント(0)TOP↑
Google app Engineを使うにあったぶち当たった問題点
1.インストールの問題
(1)eclipse
    いきなり日本語化でぶちあたった。
    pleiades_1.3.0を入れるのだが、これがどうも解凍にlhaplusを使っていたのだが、
    解凍時にエラーがでていたみたいで、多分フルパス名が大きすぎたんだろう。

    初期化エラーがでてeclipseが起動しなかった。

    ExtractNowで解凍すれば、ちゃんと動いた。

(2)他は、googleのhpを見ればその通り動いた。
    http://code.google.com/intl/ja/appengine/

(3)@Override アノテーションでエラー
    なんとJDK1.6以上でないと使えない。
    このときは切れそうになった。

2.Slim3 との格闘
(1)Datastoreの部分のみ使うべく奮闘した。
    これは楽勝で、お勧めだと思う。

(2)framework? これはまだ格闘中だ。後日お楽しみに。

3.開発環境について
(1)eclipseに接続できるクライアントが初期設定では自分のpcのみだ。
    これには参った。
    でもインターネットでは既に解決済みだった。

    デバッグの構成で、起動時のパラメータに --address=0.0.0.0を追加すれば解決した。
    これはうれしかった。

(2)log4j を使っているとコンソールにログが出力されない件

    appengine-web.xml
    <system-properties>
        <property name="org.apache.commons.logging.Log" value="org.apache.commons.logging.impl.Log4JLogger"/>
    </system-properties>

    lo4j.propertiesの変更

    A2というアペンダーを追加する。rootをDEBUGにして、DWRパッケージは WARN以上に設定。

    log4j.rootLogger=DEBUG,A2
    log4j.appender.A2=org.apache.log4j.ConsoleAppender
    log4j.appender.A2.layout=org.apache.log4j.PatternLayout
    log4j.appender.A2.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
    log4j.logger.org.directwebremoting=WARN,A2

4.アプリケーションの工夫


(1)クライアントにテキストそのままで出力すると、
    開発環境ではそのまま出力されるが、
    アップロードすると、なぜか<html>などの余分なテキストが付与される。
    
    ということで、リソースの定義をしてから、
    出力してみることにした。
    
    これはまだ実験中だ。
    
    ひょっとすると、response.setContextType("text/plane")も入れてるかも。
    まだ結果は出ていない。お楽しみに!!

    appengine-web.xml
    <resource-files>
      <include path="/*">
    </resource-files>

    web.xml
    <servlet>
        <servlet-name>(サーブレット名)</servlet-name>
        <servlet-class>(パッケージ+クラス)</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>(サーブレット名)</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

(2)今の日本時間を求めるには
    こんな感じでどうでしょう。

    SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
    // 日付の表示フォーマットにタイムゾーンを設定
    sdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
    String dateString = sdf.format(new Date());




5.その他
(1)
eclipse-jee-ganymede-SR2-win32(3.4)
pleiades_1.3.0

commons-codec-1.3
commons-httpclient-3.1
commons-lang-2.5
commons-logging-1.1.1
log4j-1.2.15
slim3-1.0.4


slim3-gen-1.0.4


appenging-api-1.0-sdk-1.3.4
appenging-api-labs-1.3.4
appenging-jsr107cache-1.3.4
datanucleus-appenging-1.0.7.final
datanucleus-core-1.1.5
datanucleus-jpa-1.1.5
geronimo-jpa_3.0_spec-1.1.1
geronimo-jta_1.1_spec-1.1.1
jdo2-api-2.3-eb
jsr107cache-1.1


http://works.nri.co.jp/service/pdf


2010.06.29|パソコンな日々コメント(0)TOP↑
http://koma.webso.jp/2010/06/google-app-engineexcel.html

Google App EngineでExcel出力する

Google App Engine(以下はGAEと称する)でjexcelapiを利用して、Excelを作成してダウンロードする処理を実装しました。

GAEにはホワイトリストがありますの、このリスト以外のクラスがサポートされていません、例えばApache POIはExcel作成によく使われるんですが、GAE環境にはなかなか使えません。(多分Apache POIがJava低レベルAPIを使っているかもしれません)いろいろ調べると、jexcelapiが見つかりました、これを利用して問題なくExcel ファイルが作成でき、ダウンロードすることもできるようになりました。このサンプルを作成しましたので、ご覧ください。


public class GaesampleServlet extends HttpServlet
{
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
    {
        try
        {
            // Get Excel Data
            ByteArrayOutputStream bytes = generateExcelReport();

            // Initialize Http Response Headers
            resp.setHeader("Content-disposition", "attachment; filename=exportUsers.xls");
            resp.setContentType("application/vnd.ms-excel");

            // Write data on response output stream
            if (bytes != null)
            {
                resp.getOutputStream().write(bytes.toByteArray());
            }
        }
        catch (WriteException e)
        {
            resp.setContentType("text/plain");
            resp.getWriter().print("An error as occured");
        }
    }

    public ByteArrayOutputStream generateExcelReport() throws IOException, WriteException
    {
        // Stream containing excel data
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        // Create Excel WorkBook and Sheet
        WritableWorkbook workBook = Workbook.createWorkbook(outputStream);
        WritableSheet sheet = workBook.createSheet("User List", 0);

        // Generates Headers Cells
        WritableFont headerFont = new WritableFont(WritableFont.TAHOMA, 12, WritableFont.BOLD);
        WritableCellFormat headerCellFormat = new WritableCellFormat(headerFont);
        headerCellFormat.setBackground(Colour.PALE_BLUE);
        sheet.addCell(new Label(1, 1, "LastName", headerCellFormat));
        sheet.addCell(new Label(2, 1, "FirstName", headerCellFormat));

        // Generates Data Cells
        WritableFont dataFont = new WritableFont(WritableFont.TAHOMA, 12);
        WritableCellFormat dataCellFormat = new WritableCellFormat(dataFont);
        int currentRow = 2;
        sheet.addCell(new Label(1, currentRow, "aaaaa", dataCellFormat));
        sheet.addCell(new Label(2, currentRow, "bbbbb", dataCellFormat));

        // Write & Close Excel WorkBook
        workBook.write();
        workBook.close();

        return outputStream;
    }
}


2010.06.29|パソコンな日々コメント(0)TOP↑
http://www.hellohiro.com/timezone.htm

世界のタイムゾーンの現在の 時刻を取得(DateFormatを使用)
java.text.DateFormatクラスを使用して、日本の東京、アメリカのLos Angeles、イギリスのLondonの3つの現在の時刻を表示してみたいと思います。
■ソースコード
ShowWorldTime.java(ここからダウンロード)
import java.util.*;
import java.text.*;

public class ShowWorldTime {
  public static void main(String[] args) {
    // 現在の日付を取得
    Date date = new Date();
    // 日付のデフォルトのフォーマットを取得
    DateFormat df = DateFormat.getDateTimeInstance();

    TimeZone tz;
    /** 東京のタイムゾーンを取得 **/
    tz = TimeZone.getTimeZone("Asia/Tokyo");
    // 日付の表示フォーマットにタイムゾーンを設定
    df.setTimeZone(tz);
    // 表示
    System.out.println(tz.getID() + " [" + tz.getDisplayName() + "]:" + df.format(date));

    /** Los Angelesのタイムゾーンを取得 **/
    tz = TimeZone.getTimeZone("America/Los_Angeles");
    // 日付の表示フォーマットにタイムゾーンを設定
    df.setTimeZone(tz);
    // 表示
    System.out.println(tz.getID() + " [" + tz.getDisplayName() + "]:" + df.format(date));

    /** Londonのタイムゾーンを取得 **/
    tz = TimeZone.getTimeZone("Europe/London");
    // 日付の表示フォーマットにタイムゾーンを設定
    df.setTimeZone(tz);
    // 表示
    System.out.println(tz.getID() + " [" + tz.getDisplayName() + "]:" + df.format(date));
  }
}
 


2010.06.29|パソコンな日々コメント(0)TOP↑



http://d.hatena.ne.jp/androidzaurus/20100511/1273534452



GAE/Jの開発サーバをlocalhost以外からアクセス(Eclipse編)
Add Starrgfxdirablue

08:34 | GAE/Jの開発サーバをlocalhost以外からアクセス(Eclipse編) - Android 
Zaurusの日記 を含むブックマーク はてなブックマーク - GAE/Jの開発サーバをlocalhost以外からアクセス(Eclipse編) - 
Android Zaurusの日記

昨日、コマンドラインからGAE/J開発サーバを起動する日記を書いたら、@vvwakameさんからEclipseからでもできるよ!*1と教えてい ただいて、よくよくDebug/Runの設定を見てみたら、(x)=Arguments タブで--address=0.0.0.0を追加できるじゃないの。分かってしまえば簡単なことでしたね。


ところで、なんでGAE/Jかというと。Androidの アプリケーションを作るときに、バックエンドにGAEなんて素敵、GAE/JならAndroidJavaだし素敵と 思い立って、ちょろっとテストをしてみたけど、Androidエ ミュレータ内のアプリケーションから、GAE/J開発サーバにアクセス出来なくて、ひょっとして環境設定が悪いのか、Ubuntuの ネットワーク設定が悪いのかと調べまくって、結局Jettyのacceptがlocalhostに括りついているんじゃな いかと思い至って、改めてドキュメントを読んでみたら、--address=0.0.0.0というオプションが必要という顛末。何はともあれ、これで Eclipse + Javaだけで完璧にスケールして世界中のAndroid端 末に配れるアプリケーションを開発できる環境がローカルに構築できるという、なんとも妄想の広がる世界。すてきですね。



2010.06.29|パソコンな日々コメント(0)TOP↑

http://sites.google.com/site/mmatsuoka10/programming/java/slim3/gaede-no-moji-bake-taisaku

Google App Engine/Javaでの文字化け対策

  1. 文 字コードはUTF-8のみを使用する。
  2. ストリームを読み込む時にストリームから読み込むデータの文字コードを指定する。
    URL url = new URL(urlStr);
    connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("GET");
    inputStream = connection.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
  3. レスポンスを書き出す時に文字コードを指定する。
    String encoding = request.getCharacterEncoding();
    if (encoding == null) {
    encoding = "UTF-8";
    } logger.info("encoding=[" + request.getCharacterEncoding() + "]");
    response.setCharacterEncoding(encoding);
    response.setContentType("text/html; charset=UTF-8");
    Writer writer = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), encoding));
    writer.write(mes);
    writer.flush();
    writer.close();
  4. htmlファイルはUTF-8で作成する。
  5. metaタグで文字コードを指定する。  

  6. appengine-web.xmlで使用する文字 コードを指定する。







コメント -:承認待ちコメント
このコメントは管理者の承認待ちです

2010.06.29|パソコンな日々コメント(1)TOP↑
http://blog.nvw-on.com/2010/03/blackberry-mime-on-gaej.html

BlackBerry のアプリケーション配布のための MIME 設定 on GAE/J

 私には、Google App Engine (GAE)のことを知った際、単に Java のアプリケーションサーバーがただで利用できるのか!という部分だけに驚き登録したので、明確な使用用途が無かった。
 Bloggerにしても、Apps のサイトにしても、普通のファイルをアップロードして配布することに関してはあまり困らないと思いますが、BlackBerryのアプリを置いてみよう か?と考えた際、ブラウザからアクセスしてインストールできないのでは?と考えました。そこで、GAEを BlackBerry のアプリの置き場として利用してみます。

 社内でDominoを使って配布した際にすこしはまったのですが、理由は簡単。BBのアプリを Over The Air (OTA)でインストールする際に使用される(ブラウザからアクセスされる)ファイルは以下の通りです。


.jad -> 定義ファイル。バージョンとかベンダーなどのメタ情報。
.cod -> アプリケーションファイル。Java ですが、BlackBerry の独自フォーマット。

 BlackBerry Browser から、jad を開くことで jad に記載の cod ファイルをダウンロードしてアプリケーションをインストールすることになりますが、Webサーバーがレスポンスヘッダーに適切な MIME Type を返してくれないと、BlackBerry Browser はサポートされていないファイルだと認識してしまい、インストールできないことになります。

* 必要なMIMEタイプ
.jad -> text/vnd.sun.j2me.app-descriptor
.cod -> application/vnd.rim.cod

 GAE のプロジェクトの中では、 war に適当なフォルダを作成し、そこに HTML や CSS などの静的なファイルを格納すれば利用できますが、MIME Type を操作するには、リソースとして対象のファイルを格納し、制御する必要があるとヘルプにあります。

http://code.google.com/intl/ja/appengine/docs/java/config/appconfig.html

静的ファイルは、ファイルの拡張子に基づいて選択された MIME タイプで提供されます。ファイルをカスタム MIME タイプで提供するには、ファイルを静的ファイルではなくリソース ファイルにし、カスタム MIME タイプでデータを提供するサーブレットを作成します。
  何のことやら?と思いましたが、こんな形で対処しました。

1. appengine-web.xml
 以下のようにリソースファイルとして *.jad および *.cod を登録。
   <resource-files>
      <include path="/**.cod">
      <include path="/**.jad">
   </resource-files>

2. web.xml
 以下のように *.jad および *.cod へのリクエストを特定のサーブレットにマッピング。
    <servlet>
        <servlet-name>(サーブレット名)</servlet-name>
        <servlet-class>(パッケージ+クラス)</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>(サーブレット名)</servlet-name>
        <url-pattern>*.jad</url-pattern>
        <url-pattern>*.cod</url-pattern>
    </servlet-mapping>

3. サーブレット作成
 2で登録したサーブレットを作成し、doGet に以下のようなコードを書く。

        // target file
        String targetpath = req.getRequestURI();
       
        // resource file
        InputStream is = null;
       
        try{
            is = this.getServletContext().getResourceAsStream(targetpath);

            // check: is file avalable?
            if (is == null) {
                // not exist
                resp.setContentType("text/plain");
                resp.getWriter().println("File not found...");
            } else {
                // file exist
                int filesize = is.available();

                // set response header
                setResponseHeader(resp, targetpath, filesize);

                // output
                OutputStream out = resp.getOutputStream();
                byte[] byteBuf = new byte[8192];
                for (;;) {
                    int iRead = is.read(byteBuf, 0, byteBuf.length);
                    if (iRead < 0) {
                        break;
                    }
                    out.write(byteBuf, 0, iRead);
                }
            }
        } finally{
            if(is != null){
                is.close();
            }
        }

 呼び出している setResponseHeaderも追加。PDFやExcelはおまけ。コピーしてきたコードがこんな状態だったので、そのまま残してあるだけ。
   private void setResponseHeader(final HttpServletResponse response,
            String filename, final int fileLength) {
        if (filename.endsWith(".pdf")) {
            // PDF
            response.setContentType("application/pdf");
        } else if (filename.endsWith(".xls")) {
            // Excel
            response.setContentType("application/vnd.ms-excel");
        } else if (filename.endsWith(".xml")) {
            // XML
            response.setContentType("text/xml");
        } else if (filename.endsWith(".jad")) {
            // JAD
            response.setContentType("text/vnd.sun.j2me.app-descriptor");
        } else if (filename.endsWith(".cod")) {
            // COD
            response.setContentType("application/vnd.rim.cod");
        } else {
            // other is binary
            response.setContentType("application/octet-stream");
        }

        if (fileLength > 0) {
            // Length
            response.setContentLength(fileLength);
        }

        // Disable cache
        response.addHeader("Cache-Control", "no-cache");
        response.addHeader("Pragma", "no-cache");
        response.addHeader("Expires", "0");
    }

  あとは、 適当なフォルダに .jad や .cod を置き、リンクなどを張ってブラウザからアクセスさせれば、BlackBerry Browserもちゃんと認識でき、アプリのインストールが可能。

 また、社内で利用しているDominoにも同様のMIMEタイプの追加をしてあるが、こちらはプログラムなしに、Domino Directory上に拡張子とMIMEタイプなどの情報を登録するだけなので楽チンです。


2010.06.29|DreamNewsコメント(0)TOP↑
<web-apps>
├─<description>*
├─<display-name>*
├─<icon>*
│  ├─<small-icon>?
│  └─<large-icon>?
├─<distributable>*
├─<context-param>*
│  ├─<description>*
│  ├─<param-name>
│  └─<param-value>
├─<filter>*
│  ├─<description>*
│  ├─<display-name>*
│  ├─<icon>*
│  │  ├─<small-icon>?
│  │  └─<large-icon>?
│  ├─<filter-name>
│  ├─<filter-class>
│  └─<init-param>*
│      ├─<description>*
│      ├─<param-name>
│      └─<param-value>
├─<filter-mapping>*
│  ├─<filter-name>
│  ├─(<url-pattern>|<servlet-name>)
│  └─<dispatcher>0..4
├─<listener>*
│  ├─<description>*
│  ├─<display-name>*
│  ├─<icon>*
│  │  ├─<small-icon>?
│  │  └─<large-icon>?
│  └─<listener-class>
├─<servlet>*
│  ├─<description>*
│  ├─<display-name>*
│  ├─<icon>*
│  │  ├─<small-icon>?
│  │  └─<large-icon>?
│  ├─<servlet-name>
│  ├─(<servlet-class>|<jsp-file>)
│  ├─<init-param>*
│  │  ├─<description>*
│  │  ├─<param-name>
│  │  └─<param-value>
│  ├─<load-on-startup>?
│  ├─<run-as>?
│  └─<security-role-ref>*
│      ├─<description>*
│      ├─<role-name>
│      └─<role-link>?
├─<servlet-mapping>*
│  ├─<servlet-name>
│  └─<url-pattern>
├─<session-config>*
│  └─<session-timeout>?
├─<mime-mapping>*
│  ├─<extension>
│  └─<mime-type>
├─<welcome-file-list>*
│  └─<welcome-file>+
├─<error-page>*
│  ├─(<error-code>|<exception-type>)
│  └─<location>
├─<jsp-config>*[2.4]
│  ├─<taglib>*
│  │  ├─<taglib-uri>
│  │  └─<taglib-location>
│  └─<jsp-property-group>*
├─<security-constraint>*
│  ├─<display-name>*
│  ├─<web-resource-collection>+
│  │  ├─<web-resource-name>
│  │  ├─<description>*
│  │  ├─<url-pattern>+
│  │  └─<http-method>*
│  ├─<auth-constraint>?
│  │  ├─<description>*
│  │  └─<role-name>*
│  └─<user-data-constraint>?
│      ├─<description>*
│      └─<transport-guarantee>
├─<login-config>*
│  ├─<auth-method>?
│  ├─<realm-name>?
│  └─<form-login-config>?
│      ├─<form-login-page>
│      └─<form-error-page>
├─<security-role>*
│  ├─<description>*
│  └─<role-name>
├─<env-entry>*
│  ├─<description>*
│  ├─<env-entry-name>
│  ├─<env-entry-type>
│  └─<env-entry-value>?
├─<ejb-ref>*
│  ├─<description>*
│  ├─<ejb-ref-name>
│  ├─<ejb-ref-type>
│  ├─<home>
│  ├─<remote>
│  └─<ejb-link>?
├─<ejb-local-ref>*
│  ├─<description>*
│  ├─<ejb-ref-name>
│  ├─<ejb-ref-type>
│  ├─<local-home>
│  ├─<local>
│  └─<ejb-link>?
├─<service-ref>*[2.4]
│  ├─<description>*
│  ├─<display-name>*
│  ├─<icon>*
│  │  ├─<small-icon>?
│  │  └─<large-icon>?
│  ├─<service-ref-name>
│  ├─<service-interface>
│  ├─<wsdl-file>?
│  ├─<jaxrpc-mapping-file>?
│  ├─<service-qname>?
│  ├─<port-component-ref>*
│  └─<handler>*
├─<resource-ref>*
│  ├─<description>*
│  ├─<res-ref-name>
│  ├─<res-type>
│  ├─<res-auth>
│  └─<res-sharing-scope>?
├─<resource-env-ref>*
│  ├─<description>*
│  ├─<resource-env-ref-name>
│  └─<resource-env-ref-type>
├─<message-destination-ref>*
│  ├─<description>*
│  ├─<message-destination-ref-name>
│  ├─<message-destination-type>
│  ├─<message-destination-usage>
│  └─<message-destination-link>?
├─<message-destination>*[2.4]
│  ├─<description>*
│  ├─<display-name>*
│  ├─<icon>*
│  │  ├─<small-icon>?
│  │  └─<large-icon>?
│  └─<message-destination-name>
└─<locale-encoding-mapping-list>*[2.4]
    └─<locale-encoding-mapping>+
        ├─<locale>
        └─<encoding>


2010.06.29|パソコンな日々コメント(0)TOP↑
http://hydra404.blog82.fc2.com/blog-entry-95.html

@Overrideアノテーションでエラー

プロジェクトをチェックアウトしてみたらエラーになった。
@Override の行でオーバーライドしなくちゃだめだよ~って言われてる。

んー。オーバーライドしてるんだけどなぁ、と思ったらどうも他の人はエラーに ならないらしい。そりゃそうか、エラーのコードをコミットするようなレベルじゃないもんね。

となるとボクの環境が怪しいわけで、ちょっと 調べてみたところすべての@Overrideがエラーになっているわけではないようです。

結論、コンパイラの準拠レベルが原因。
JDK5 ではインターフェースの実装には@Overrideが使えないらしい。
JDK6ではOKと。

なにも考えずにJDK5を使ってたわ けですが、JDK6が常識ですか。そうですか。

なんかもう言語のバージョンアップについていくのが面倒。
ハッ!?まさかこれが 歳ってやつですか!?

さっそくPHP6をダウンロードしてくるでありますよ。


2010.06.29|DreamNewsコメント(0)TOP↑
http://d.hatena.ne.jp/tetsuya_odaka/20090731/1249031693

Google App Engine/Javaで commons-logging+Log4jでロギングする。Add Star

| 18:14 | はてなブックマーク -  Google App Engine/Javaでcommons-logging+Log4jでロギングする。 -
 北海道を愛するプログラマの覚書  Google App 
Engine/Javaでcommons-logging+Log4jでロギングする。 - 北海道を愛するプログラマの覚書 のブックマークコメント

Google App Engineのスタブ(appengine-web.xml)に

<system-properties>
    <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>

http://d.hatena.ne.jp/tetsuya_odaka/20090731/1249031693

とあるように、デフォル トの設定ではjava.util.logging を使う設定になっている。

これと一緒に、src下 にlog4j.propertiesの雛形が用意されている。

先のログでDWR2を配備した際に、commons-loggingを配置した。DWRは 「org.directwebremoting.util.CommonsLoggingOutput」という奴が、commons-logging を使って、Infoレベルで余計なログを吐く。

開発の際には、debugログが必要になるので、log4jを 使う設定に変えてみる。

log4j.jarの追加

war/WEB-INF/libに、手持ちのlog4j-1.2.15.jarをコピー(ちょいといい加減)。


appengine-web.xmlの変更

commons-logging は、システムプロパ ティーの設定を見に行く。これは、(上記のように)appengine-web.xmlに設定されているので、これを変更する。

<system-properties>
    <property name="org.apache.commons.logging.Log" value="org.apache.commons.logging.impl.Log4JLogger"/>
</system-properties>

lo4j.propertiesの変更

A2というアペンダーを追加する。rootをDEBUGにして、DWRパッケージは WARN以上に設定。

log4j.rootLogger=DEBUG,A2
log4j.appender.A2=org.apache.log4j.ConsoleAppender
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
log4j.logger.org.directwebremoting=WARN,A2

Demo.javaの変更

debugログを吐くように、Demo.javaを変更。呼び出されたら「say hello called. '渡された値(name)'」をConsoleに出力する。

package test;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * DWRのデモ
 * 
 * 名前(name)を受け取って、「こんにちは、name」を返す。
 * 
 */
public class Demo {
    
    private static final Log log = LogFactory.getLog(Demo.class);
    
    public String sayHello(String name) {
        log.debug("say hello called. "+name);
        return "こんばんわ " + name + " さん";
    }
}

デプロイと実 行

いつもどおりにデプロイし て、以下の画面を実行した。

f:id:tetsuya_odaka:20090729194249p:image

以下がGAEの 管理画面に上がったログ。時刻表示は米 国時間っぽい。サーバーのシ ステム時刻かなぁ。直し方が分からない(クラウド環境 でそもそも直せるのかな。とりあえず、スルー)。DEBUGモードで書き出されたことが分かるが、GAE上はInfomationの扱いになっている。それ と、日本語はログに吐き出せなかった。できるのかな(これもスルーしとこう)?


f:id:tetsuya_odaka:20090729193729p:image


(注記)上の例では、昔風にlog4j.propertiesになっているが、xml形式のlog4j.xmlにしてもOK。



2010.06.29|パソコンな日々コメント(0)TOP↑
DreamNews
今日のイチオシDreamNews

がんばれ!岡田ジャパン「W杯サムライ仕様の酸素カプセル」を限定販売開始! 株式会社神戸メディケア

2010.06.28|DreamNewsコメント(0)TOP↑

[]AppEngineでsessionを有効にしていると遅くなる 23:18 AppEngineでsessionを有効にしていると遅くなる - あおうさ@日記 を含むブックマーク はてなブックマーク - AppEngineでsessionを有効にしていると遅くなる - あおうさ@日記 AppEngineでsessionを有効にしていると遅くなる - あおうさ@日記 のブックマークコメントAdd Startakeboruta

Google App Engineのsession情報はDatastoreを使っているため若干遅い。セッションを使わないアプリならセッションを無効にしておいた方が良 い。

appengine-web.xmlにて<sessions- enabled>false</sessions-enabled>する

※デフォルト(sessions-enabledの記述がない場合)ではセッションは無効になっている。


Datastoreを使うということはDatastoreTimeoutExceptionが発生する可能性があるということ。なので極力 切っておいたほうがいいようだ。

あと、jspは セッションがデフォルトでは有効になっているようなので無効にしておいた方がよい。

<%@page pageEncoding="UTF-8" session="false"%>

今日のエントリは全部twitterの情報メモ。

追記

いくつかtwitterでツッコミを頂けたのでjspのセッション無効 が意味があるのかをちょっと検証してみた。

検証内容としてはセッションを有効にしている場合にセッションをjspでset,getしていないにも関わらず Datastoreへのアクセスが発生するのかどうか。先に結論を言うとjspのセッションを無効にするのは意味がないと思う。

検証方法はmakeSyncCallをフックしてDatastoreへのアクセスが発生したらログを出力するようにする。AppEngine ではDatastoreやMemcacheなどサービスへのアクセスが発生した場合はmakeSyncCallというMethodが実行される仕組みに なっている。このmakeSyncCallはApiProxy.setDelegate()を使う事で実行する処理を変更できる。今回使用したソースは下 記。

import java.util.concurrent.Future;
import java.util.logging.Logger;

import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.ApiConfig;
import com.google.apphosting.api.ApiProxy.ApiProxyException;
import com.google.apphosting.api.ApiProxy.Delegate;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.apphosting.api.ApiProxy.LogRecord;

public class TestDelegate implements Delegate<Environment> {

    private static final Logger logger =
        Logger.getLogger(TestDelegate.class.getName());

    private static Delegate<Environment> originalDelegate = ApiProxy.getDelegate();

    public void setUp() throws Exception {
        logger.info("setUp");
        ApiProxy.setDelegate(this);
    }

    public byte[] makeSyncCall(Environment env, String service, String method,
            byte[] requestBuf) throws ApiProxyException {
        logger.info("makeSyncCall/" + service + "/" + method);
        return originalDelegate.makeSyncCall(env, service, method, requestBuf);
    }

    public Future<byte[]> makeAsyncCall(Environment env, String service,
            String method, byte[] requestBuf, ApiConfig config) {
        logger.info("makeAsyncCall/" + service + "/" + method);
        return originalDelegate.makeAsyncCall(
            env,
            service,
            method,
            requestBuf,
            config);
    }

    public void log(Environment env, LogRecord rec) {
        originalDelegate.log(env, rec);
    }
}

自分のアプリケーションのどこかでnew TestDelegate().setUp();を実行した後のサービスは必ず上記のクラスのmakeSyncCallを経由するようになる。jspには session="false"は記載しなかった。この仕組みで検証した結果次のようになった。

■appengine-web.xmlにて<sessions- enabled>true</sessions-enabled>

  • session変数にアクセスしなかった場合のリクエスト
  1. makeSyncCall/memcache/Get
  2. makeSyncCall/memcache/Set

※session変数にアクセスしなくてもmemcacheのアクセスが発生しているが、Datastoreへのアクセスは発生していない。

  • session変数にアクセスした場合のリクエスト
  1. makeSyncCall/memcache/Get
  2. makeSyncCall/datastore_v3/Put
  3. makeSyncCall/memcache/Set

※session変数にアクセスするとDatastoreへのアクセスが発生する。

また、appengine-web.xmlにて<sessions- enabled>false</sessions-enabled>の場合とsessions-enabledタグ自体をなくした場合 でも同様の実験をしたところセッションが無効な状態で、session変数にアクセスしなかった場合のリクエストはmemcacheへのアクセスも発生し なかった。この実験をするまでは、セッション変数を使っても使わなくてもとりあえずセッション変数は有効にしておけばいいかなと思っていたのだが、上記結 果の通りセッションを使わなくてもmemcacheへのアクセスが発生しコストがかかることがわかった。可能な限りセッションは使わない方が AppEngineではいいようだ。


そうそう、セッションを使う場合の注意点としてほかにもセッションはDatastoreに登録されるため定期的に削除する必要がある。 AppEngineでは下記URLを実行するとセッション情報を消してくれる便利な機能がある。

/_ah/sessioncleanup?clear

セッションを使う場合は下記のようにcron.xmlで60分毎に削除などの設定をしておいた方がいいだ ろう。

  <cron>
    <url>/_ah/sessioncleanup?clear</url>
    <description>Session cleanup every 60 minutes</description>
    <schedule>every 60 minutes</schedule>
  </cron>

あわせてweb.xmlにて下記のセキュリティ設定もしておいた方がいい。 セキュリティ設定をしていない場合は上記のURLを実行されて意図せずセッションを消される恐れがある。

    <security-constraint>
       <web-resource-collection>
           <url-pattern>/_ah/*</url-pattern>
       </web-resource-collection>
       <auth-constraint>
           <role-name>admin</role-name>
       </auth-constraint>
    </security-constraint>


2010.06.22|パソコンな日々コメント(0)TOP↑

[]Google App Engineでよくある質問 22:33 Google App Engineでよくある質問 - 
あおうさ@日記 を含むブックマーク はてなブックマーク -
 Google App Engineでよくある質問 - あおうさ@日記 Google App 
Engineでよくある質問 - あおうさ@日記 のブックマークコメントAdd Starbluerabbitkazunori_279kilvistyleripjyrkijitoraneko

質問形式でとりあえずApp Engineの情報を整理しようと思います。

  • 開発時にローカルで登録したDatastoreのデータはどのようにすれば確認できますか?
    • http://localhost:8888/_ah/adminか らDatastore Viewerにアクセスすればデータ内容の確認ができます。(いま[SDK1.3]は残念なことに編集はできません。)
      • また、/_ah/adminの管理コンソールからは実行中のTask Queue、XMPPやInbound Mailのテストが出来ます。
  • 開発中にローカルのDatastoreのデータを全て削除したいのですがどのようにすればいいですか?
    • war/WEB-INF/appengine-generated/local_db.binがDatastoreのデータファイルで す。これを削除してlocal_db.binというファイル名で再作成すればデータが削除されます。
  • 開発中にローカルのDatastoreのデータが突然きえてしまいました。
    • App-ID(appengine-web.xmlのapplication値)を変更しませんでした か?DatastoreのデータはApp-IDとひもづいています。App-IDを元に戻してみて下さい。
  • AppEngineではバージョン毎にDatastore(データベースの値)は異なるのですか?
    • バージョンが異なってもDatastoreの値は同じです。データを追加するテストなどは新たにApp-IDを取得するなどしましょ う。
  • DataStoreを使わないアプリなら、バージョンを分ければいくつでもデプロイでき る?
    • バージョンは10個まで作成可能です。また、バージョン番号は数字だけでなく"dev"など文字列も可能です。
  • JDOで 開発しているとたまにClassNotPersistenceCapableExceptionが発生します。The class "The class "model.User" is not persistable. This means that it either hasnt been enhanced
    • JDOで はコンパイ ル時にエンハンサーと呼ばれる処理が実行されます。この仕組みを使ってJDOは永続化処理を行います。その処理ができなかった場 合に当該エラーが発生します。
  • http://localhost:8888/_ah/admin/で データを確認したら値が『?????』になっている
    • ローカルの管理コンソールでは日本語が文字化けしま す。ローカルの管理コンソールは文字化けする のでログなどで確認しましょう。
  • ファイルのアップロード数に制限はありますか?
    • 以前は1000ファイルでしたが現在は3000ファイルです。
  • ログレベルの設定を変更しても反映されません。
    • ログの設定ファイルはwar\WEB-INF\appengine-web.xmlで指定されます。よくある間違いとしてEclipseの pluginで新規にWebアプリケーションプロジェクトを作成した場合にプロジェクトで使用されるloggingはwar\WEB-INF \logging.propertiesです。src\log4j.propertiesが自動で作成されていますが、これは設定されていませんので注意 しましょう。
  • サーバにあるDatastoreのデータを削除したいのですがDropTableはできないのですか?
    • SQLを 実行してデータを削除することはできません。削除するコードを書く必要があります。こちらを参照ください。
  • web.xmlで コメントを日本語入力したらエラーになりました。日本語は入力できないのでしょうか?
  • セッション変数を扱いたいです。(または、 Session support is not enabled in appengine-web.xml. To enable sessions, put <sessions-enabled>true</sessions-enabled>というエラーが出ました。)
    • appengine-web.xmlに<sessions- enabled>true</sessions-enabled>を追加してください。
  • セッションを有効にしたらDataViewerに_ah_SESSIONというKindが作成されました。これはなんですか?
    • セッション情報はDatasotreに_ah_SESSIONというKindで保存されます。また、このデータは定期的に自分で削除す る必要があります。削除方法はこちらを参考にして下さい。
  • サーバのログをローカルにダウンロードしたいのですができますか?
    • できます。詳しくはこちらを参考にして下さい。
  • XMLXPath解析し たいのですができますか?
    • できます。詳しくはこちらを参考にして下さい。
  • TaskQueueでエラーが発生した場合にリトライされますが、リトライ回数やタスク名を取得することはできますか?
    • できます。詳しくはこちらを参考にして下さい。
  • Counterを作りたいのですが。。。
  • AppEngineでは最大1000件までしか取得できない制限がありますが、。。。
    • 現在のAppEngineでは最大1000件の制約はありません。
  • ローカルでメール送信のテストをしていたのですがメール送信されません。
    • ローカルでは実際のメール送信は行われません。ログで確認しましょう。
  • UserServiceFactory.getUserService().createLoginURL(uri)を使って認証した場 合に出てくる認証画面の「○○ではログインに Google アカウントを使用します。」の○○はどこで設定すればいいですか?
    • 管 理画面の[Application Settings]-[Application Title]から変更して下さい。
Message msg = new Message();
msg.setSender(fromAddress);
msg.setTo(toAddress);
msg.setSubject(subject);
msg.setTextBody(body);
MailServiceFactory.getMailService().send(msg);


2010.06.22|DreamNewsコメント(0)TOP↑

[]AppEngineでKind(テーブル)を削除する方法 22:05 AppEngineでKind(テーブル)を削除する方法 - あおうさ@日記 を含むブックマーク はてなブックマーク - AppEngineでKind(テーブル)を削除する方法 - あおうさ@日記 AppEngineでKind(テーブル)を削除する方法 - あおうさ@日記 のブックマークコメントAdd Star

Google App Engineではデータを削除したい時にサーバのデータに対してDelete文を実行したりDrop Tableしたりということができません。Kindをdrop(KindにあるEntity[レコード]を全件削除)するには現時点では削除するコードを サーバにデプロイして ブラウザからリクエストを送ります。そこで使用するソー スコードを紹介します。

import java.util.ArrayList;
import java.util.List;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.DatastoreTimeoutException;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;

    public static void dropKind(String kindName) {
        DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
        Query query = new Query(kindName);
        query.setKeysOnly();
        List<Entity> list =
            ds.prepare(query).asList(FetchOptions.Builder.withOffset(0)); // ※DatastoreTimeoutException の可能性がありますが省略しています。
        List<Key> keys = new ArrayList<Key>(500);
        for (Entity entity : list) {
            keys.add(entity.getKey());
            if (keys.size() == 500) {
                try {
                    ds.delete(keys); // batch delete
                } catch (DatastoreTimeoutException ignore) {
                }
                keys.clear();
            }
        }
        if (keys.size() > 0){
            ds.delete(keys); // batch delete ※DatastoreTimeoutException の可能性がありますが省略しています。
        }
    }

使い方は簡単でdropKindの引数にKind名を指定するだけです。おそらくJDOを使っている人は下記のようなコードになると思いま す。

例)dropKind(Emp.class.getSimpleName());

  • 解説
    • KindのKey(PK)を全件取得
      • KindのKeyを取得する際に、query.setKeysOnly();をしています。データ(プロパティ値)が不要な場合は setKeysOnlyすることで取得が4倍近く早くなります。
    • KeyをListに詰める
      • DatastoreServiceのdeleteは1つのKeyを引数に取ることもできますが、KeyをListに詰めた後に ds.delete(keys);する事でbatch deleteとなり高速に削除できます。
    • 500件毎にdelete
      • 500件毎に削除しているのはDatastoreServiceのput(insert/update)およびdeleteは最大 500件だからです。
    • ※DatastoreTimeoutExceptionをtry catch。DatastoreTimeoutExceptionが発生したら途中まで消えたり消えなかったりします。

*一応書いておくと、このメソッド一回で確実にKindをDropすることを保証するものではありません。何回も実行して下さいな。もし、こ れでも件数が多くてだめな場合はTaskQueueで件数を分割して投げまくって下さい。



2010.06.22|パソコンな日々コメント(0)TOP↑

http://satoshi.blogs.com/life/2010/02/app_engine.html

Google App Engine上のベスト・プラクティス、その1: Datastore

 Google App Engine上でアプリを作りはじめて約二ヶ月。いろいろと分かって来たこともあるので、自分へのメモも含めてまとめてみる。まずは、Datastore の話から。

なによりも大切なのはデータベースの設計

 あたりまえと言えばあたりまえの話 だが、App Engine上でアプリを作る上でもっとも大切なこと(=頭を使うべきところ)は、データベースの設計である。特にリレーショナル・データベース (RDB)上でのアプリ作りに慣れた人には、大きな「発想の転換」が必要なので、ここは注意が必要。

 特に絶対にやっては行けないのは、

  • 将来RDB上へ移行できるようにレイヤーを作って、その上にアプリを作る
  • RDB上に作ったアプリをデータモデルを大幅に変更せずにApp Engine上に移植する
  • RDBを前提に設計されたフレームワークをApp Engine上に載せて、その上にアプリを作る

など。App Engineの利点を活用するためには、その特徴を最大に活かすように(データ・モデルだけでなく)アプリケーションそのもののアーキテクチャを最適化す べき。逆の言い方をすれば、それができない状況であればApp Engine上に載せる意味はあまりない。レイヤーをもうけてポータビリティを高めようとか、RDB上に作られたアプリをアーキテクチャの大幅な変更なし に移植しようとしている人がいるのであれば、そもそも「何でApp Engine上にアプリを載せたいのか」という発想が本当に正しいのかどうかから見直すべき。

Datastoreの特性を 理解する

 Datastoreの特性についてはGoogleから提供されているドキュメントに必要なことは書いてあるの で、ここではいちいち説明しないが、それを頭に入れた上での私なりのベスト・プラクティスを箇条書きにするとこんな感じになる。

  • ReadとWriteのスピードの違いが桁違いに大きい → 1つのHTTPアクセス中にするputの数はできるだけ少なくして おく。通常は0から1。よほど複雑な場合でも2、3回。5つ以上のputが必要な状況に陥ったとしたらデータ・モデルの設計から見直す べき。何らかの理由でたくさんの数のEntityを更新しなければならない場合(たとえばスキーマ変更のために1000個のEntityを一気に書き換え る)などは、TaskQueueを使って順繰りに実行させる。
  • Entity Groupはトランザクションのためにある → Entity Groupはできるだけ小さくしておき、トランザ クションが不要なところに親子関係は持たせないようにする。エンド・ユーザーから見て親子関係にあるからと言って、必ずしも同じ Entity Groupに入れる必要はないのでそこを間違えないように。たとえば、ブログのエントリーと、それに対するコメントが良い例。まずは、親子関係を一切持た せずにモデルを設計して行き、どうしてもアトミックに複数のEntityを同時に変更する必要が出て来た段階で、親子関係を持たせてEntity Groupを作る、という作り方をおすすめする。
  • データの正規化は基本的にはしない → エンド・ユーザーから見て一つの「もの」(たとえば商品、ブログエントリー)があった場合、それに付随する情報(たとえば、タイトル、値段、更新日時、作 者)をEntityそのもののプロパティとしてもたせておき、Queryなしに一回のgetですべて取得できるようにしておく。これは、RDB における「ベスト・プラクティス」とは正反対の方向を向いたものなので、RDB/SQLに慣れた人こそ気をつけて設計すべきである。
  • JOINがない → SQLにおけるJOINをApp Engine上で実現しようとすると、queryの結果をもとに複数のqueryをするというnested queryをせざるを得ないが、そもそもJOIN が必要となるような状況になったことが、データ・モデルの設計がすでにApp Engine向けのものになっていない証拠なので、「あ あ、JOINが使えたら楽なのに」と嘆く前に、今一度データ・モデルの設計から見直すべき。
  • Queryはキーの取得だけだととても高速 →  ReadはWriteと比べて早いとは言え、数百のEntityをQueryで取得するとそれなりの時間(千cpu_ms以上)がかかる。ただし、 Queryもキーの取得だけだと圧倒的に早いので、「いかにキーの取得だけで済むように設計するか」という部分で頭を絞るべき。 場合によっては、keyそのものに情報を埋め込んでおくことで、Entityを取得せずにEntityの情報を得るということも可能なので、ノウハウの一 つとして覚えておくと良い。
  • キーを使ったEntityの取得はQueryよりはるかに早い → なんらかの方法でキーを取得しておけば、そこから一つのEntityを取得するのは非常に早いので、それを利用して一つのHTTP GETを非常に高速に(100cpu_ms以下で)処理をすることが可能なので、これは頭に入れておくと良い。
  • Datastoreへのアクセスは無視できない確率で必ず失敗する(実測値で600回に1回程度)→ それに対応したコードを書いておく。HTMLを直接ブラウザーに返す場合は、サーバー側でretryをするしかないが、AJAXでデータを取得する場合は 必ずクライアント側にretryの仕組みを入れておく(3回で十分)。
  • App Engineは高レベルなAPIやGQLを提供して既存のアプリケーションの移植を簡単にしている → GoogleがApp Engineに関して言っていることはおおむね正しいが、ここだけは「うそも方便」だと思った方が良い。App Engineの上でスケーラブルなアプリを作りたいのであれば、低レベルのAPIを直接たたくべき。ちなみに、私はGQLは一切使わ ず、Pythonでdb.Modelを直接操作しているが、APIそのものはとてもストレートで簡単なので、すぐに慣れる。


2010.06.22|パソコンな日々コメント(0)TOP↑
eclipse wiki 日本語化 のキーワードで検索して、日本語化しよう。

日本語化ツールはPleiadesでやろう。

ところで、解答するとき、フルパスが長くなりすぎて、

うまく回答されていないケースがあってエラーがでていた。lhaplus

だから、extractNowで回答すればうまくいった。

なんとのう!!

2010.06.22|パソコンな日々コメント(0)TOP↑

refer http://hibituredure.blogspot.com/2009/04/google-app-engine.html


メモ
http://code.google.com/intl/ja/

http://www.atmarkit.co.jp/fjava/index/index_gaej.html


社内でGoogle App Engineのミニ勉強会をやることになったので、技術者らしくAPIとかBigTableまわりとかやろうかなーと思って色々ドキュメントをあさっていたところ、思いがけず非常に面白いものを見つけてしまった。

それは、料金体系。たいていのGAE紹介サイトには、〜まで無料、以降〜みたいな形でさらっと書いてあるんだけど、思った以上に奥が深い。DoCoMoやソフトバンクモバイルより奥が深い。しかも、この料金体系によってアプリケーションの作り方にまで影響が出てくる可能性がある。こうなってくると、JPAとかJDOとか開発環境とか紹介するのなんて馬鹿馬鹿しくなってしまった。Google App Engine勉強会、って名目で人呼んどきながら、ずーっと料金の話ってありだろうか?なしだろうなぁ。

基本的な概念、リソースとクォータ
さて、Google App Engineには利用可能な資源、リソースが定義されており、リソースにはクォータ(割当)が設定されている。逆に言えば、クォータが設定されているものがリソースであるとも言える。リソースには2種類存在し、固定的に割り当てられていて基本的にはクォータの上限が変更できないもの(Fixed)と、支払いによって上限を増やせるもの(Billable)がある。
ややこしいことに、Fixedのリソースのクォータは、Account保持者が支払い可能状態になると上限が増える(一方Billableなリソースのクォータは、実際に上限を増やすように設定しないと増えない)。

また、クォータにも2種類あり、日ごとの上限値と、分ごとの上限値が設定できる。分ごとの上限値は、日ごとに割り当てられたリソースが急速に消費されつくし、日のうち大部分が稼働不可能になる...といった状況を避けるために設定できるようになっている。

リソースの枯渇と補充
リソースは、クォータの上限値から消費されていき、Pacific Timeの0時にリセットされてクォータの最大値まで補充される。
リソースが枯渇した状態で枯渇したリソースにアクセスしようとした際には、例外が発生する。リソース枯渇に適切に対応しようとする場合、適切にこの例外をハンドリングしなければならない。たとえば、ユーザーにそのサービスが利用不可能であることを通知したり、管理者にメールで通知する等の事後処理が考えられるだろう。これが、アプリケーション設計に影響を与える要素の1つである。

リソースの種類
以下でリソースを紹介するが、支払いによって上限を増やせるものは、オリジナルのドキュメントに従って[B(illable)]と表記する。また、参考のために、無料かつ支払い可能でない状態での日ごとの上限値を並記する。

■ リクエスト系

  • リクエスト数(HTTPSを含む): 1,300,000 requests
  • 出力帯域総数[B](HTTPSを含む): 10 gigabytes
  • 入力帯域総数[B](HTTPSを含む): 10 gigabytes
  • CPU時間[B] : 46 CPU-hours
CPU時間はデータストアで使った時間も含む。外部サービス呼び出し等による待機時間は含まない。また、CPU消費時間の目安として、以下のようなものがあげられている。
  • データストアへの書き込みは、読み込みに比べておよそ5倍のCPU時間を使う
  • インデックス更新が必要な書き込みは、そうでないものよりCPU時間を使う
  • Entityのプロパティが多いほど、読み込みでも書き込みでもCPU時間を使う
  • Queryは常にIndexを使うため、Queryはほとんどの場合に同じくらいのCPUしか使わない
  • ただし、フェッチが必要な場合は追加のCPU時間が必要になる
リクエスト系のリソースの特筆すべき点は、これらのリソースが不足した場合にはリクエストのハンドリングを開始すらできないため、ユーザーにはHTTP Status403が返されてしまう、という点だ。

■ データストア系
  • データストアAPI呼び出し回数 : 10,000,000 calls
  • 格納データおよび関連インデックスのサイズ[B] : 1 gigabyte
  • APIに送信されたデータのサイズ : 12 gigabytes
  • APIから取得したデータのサイズ : 115 gigabytes
  • データストアに使ったCPU時間 : 60 CPU-hours
■ メール系
  • メールAPI呼び出し回数 : 7,000 calls
  • メール送信先アドレスの総数[B] : 2,000 recipients
  • 送信メール総数 : 5,000 mails
  • メッセージボディのデータサイズ総数 : 60 megabytes
  • 送信添付ファイル総数 : 2,000 attachments
  • 送信添付ファイルデータサイズ総数 : 100 megabytes
■ URLフェッチ系
  • URLフェッチAPI呼び出し回数 : 657,000 calls
  • URLフェッチデータ送信サイズ総数 : 4 gigabytes
  • URLフェッチデータ受信サイズ総数 : 4 gigabytes
■ Image操作系
  • Image操作API呼び出し回数 : 864,000 calls
  • APIへのデータ送信サイズ総数 : 1 gigabytes
  • APIからのデータ受信サイズ総数 : 5 gigabytes
  • 変換実行回数 : 2,5000,000 transforms
■ Memcache系
  • MemcacheAPI呼び出し回数 : 8,600,000 calls
  • APIへのデータ送信サイズ総数 : 10 gigabytes
  • APIからのデータ受信サイズ総数 : 50 gigabytes
■ デプロイ
  • デプロイ回数 : 未定義
リソース[B]の単位あたりコスト
  • 出力帯域総数:1 gigabytes あたり 約12円
  • 入力帯域総数:1 gigabytes あたり 約10円
  • CPU時間:1 CPU時間 あたり 約10円
  • 格納データサイズ:1ヶ月あたり1 gigabytesで約15円
  • メール送信先アドレスの総数:1アドレスあたり約0.01円
安…!いよね?

アプリケーション開発への影響
こんな感じで、明確に上限回数やリソース追加のコストが明示されたら、アプリケーション開発にはどんな影響があるだろう?まず、テストが変わるんじゃないだろうか。従来も行われてきたであろう、リソースの上限を突破しないか、という観点での試験に加えて、費用対効果を考慮した試験がより厳密に行われるようになる可能性がある。なにせ、無駄にCPUリソースを消費した処理を作ると、明確に費用として跳ね返ってくるようになるのだ。そうなると、富豪プログラミングにかわり、効率の良いアルゴリズムが再び脚光を浴びるようになるかもしれない。つまり、(今後の料金変動や景気、規模にもよりそうだが)今よりも省エネな設計が求められるようになる可能性があるんじゃなかろーか。

その他雑感
こうして、使用リソースに応じた料金表を見ていると、携帯電話料金表や電気料金表を見ながら最安のプランを考えているときと同じ感覚をおぼえて「あぁ、本当にユーティリティコンピューティングの時代が来たんだなぁ」と思いがけず実感してしまう。
と同時に、CPU消費時間で費用が変わるような世界は、よくよく考えると、本や口伝えでしか見聞きしたことがないような、コンピューターリソースが貴重だった時代に似ているような気もしてくる。マシン使用時間に応じて料金がかかっていたらしい、自分の知らない時代。
トレンドは螺旋を描いて戻ってくるとはよく言ったものだ。


2010.06.15|パソコンな日々コメント(0)TOP↑
DreamNews
今日のイチオシDreamNews

ねこちゃんと「おはなし」してYouTubeに投稿しよう! “シェフ”「おはなし」ニャンちゅーぶキャンペーン開催! ~入選作応募者にはアイシアのキャットフード“1年分”をプレゼント~ アイシア株式会社

2010.06.15|DreamNewsコメント(0)TOP↑
カテゴリー
最近の記事
最近のコメント
最近のトラックバック
ブログ検索
管理人のブログ一覧
管理人へメール

名前:
メール:
件名:
本文:

月別アーカイブ
プロフィール

やまもも実験室

Author:やまもも実験室
FC2ブログへようこそ!

RSSフィード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。