2013-04-25
■[Java][Groovy][.NET][C#][JavaScript][Node.js][Ruby][PHP][Python] 記号文字の URL エンコード - Java, .NET, JavaScript, Ruby, Python, PHP
下記のような文字をいくつかのプログラム言語の標準的な API で URL (URI) エンコードしてみたらどうなるか試してみました。
; / ? : @ = & % $ - _ . + ! * ' " ( ) , { } | \ ^ ~ [ ]
使用した言語は下記の通りです。
ソースは http://github.com/fits/try_samples/tree/master/blog/20130425/
Java の場合
今回は Groovy で実装してみました。
url_encode.groovy
str = ';/?:@=&% $-_.+!*\'"(),{}|\\^~[]' println URLEncoder.encode(str)
実行結果
> groovy url_encode.groovy %3B%2F%3F%3A%40%3D%26%25+%24-_.%2B%21*%27%22%28%29%2C%7B%7D%7C%5C%5E%7E%5B%5D
- 半角スペースが + になる
- - _ . * はエンコードされない
.NET の場合
.NET では下記メソッドが使えます。
今回は C# で実装してみました。
url_encode.cs
using System; using System.Web; class UrlEncode { static void Main(string[] args) { var str = ";/?:@=&% $-_.+!*'\"(),{}|\\^~[]"; // (1) Console.WriteLine(Uri.EscapeUriString(str)); // (2) Console.WriteLine(HttpUtility.UrlEncode(str)); } }
実行結果
> csc url_encode.cs ・・・ > url_encode.exe ;/?:@=&%25%20$-_.+!*'%22(),%7B%7D%7C%5C%5E~%5B%5D %3b%2f%3f%3a%40%3d%26%25+%24-_.%2b!*%27%22()%2c%7b%7d%7c%5c%5e%7e%5b%5d
- (1) Uri.EscapeUriString()
- 半角スペースが %20 になる
- ; / ? : @ = & $ - _ . + ! * ' ( ) , ~ はエンコードされない
- (2) HttpUtility.UrlEncode()
- 半角スペースが + になる
- - _ . ! * ( ) はエンコードされない
- アルファベットが小文字
JavaScript の場合
JavaScript では下記 API が使えます。
- escape()
- encodeURI()
- encodeURIComponent()
node.js で実行してみました。
url_encode.js
var str = ';/?:@=&% $-_.+!*\'"(),{}|\\^~[]'; // (1) console.log(escape(str)); // (2) console.log(encodeURI(str)); // (3) console.log(encodeURIComponent(str));
実行結果
> node url_encode.js %3B/%3F%3A@%3D%26%25%20%24-_.+%21*%27%22%28%29%2C%7B%7D%7C%5C%5E%7E%5B%5D ;/?:@=&%25%20$-_.+!*'%22(),%7B%7D%7C%5C%5E~%5B%5D %3B%2F%3F%3A%40%3D%26%25%20%24-_.%2B!*'%22()%2C%7B%7D%7C%5C%5E~%5B%5D
- (1) escape()
- 半角スペースが %20 になる
- / @ - _ . + * がエンコードされない
- (2) encodeURI()
- 半角スペースが %20 になる
- ; / ? : @ = & $ - _ . + ! * ' ( ) , ~ がエンコードされない
- (3) encodeURIComponent()
- 半角スペースが %20 になる
- - _ . ! * ' ( ) ~ がエンコードされない
Ruby の場合
url_encode.rb
require 'uri' require 'cgi' str = ';/?:@=&% $-_.+!*\'"(),{}|\\^~[]' # (1) puts URI.escape(str) puts URI.encode(str) # (2) puts CGI.escape(str)
実行結果
> ruby url_encode.rb ;/?:@=&%25%20$-_.+!*'%22(),%7B%7D%7C%5C%5E~[] ;/?:@=&%25%20$-_.+!*'%22(),%7B%7D%7C%5C%5E~[] %3B%2F%3F%3A%40%3D%26%25+%24-_.%2B%21%2A%27%22%28%29%2C%7B%7D%7C%5C%5E%7E%5B%5D
- (1) URI.escape(), encode()
- 半角スペースが %20 になる
- ; / ? : @ = & $ - _ . + ! * ' ( ) , ~ [ ] がエンコードされない
- (2) CGI.escape()
- 半角スペースが + になる
- - _ . がエンコードされない
Python の場合
- urllib.quote()
url_encode.py
import urllib str = ';/?:@=&% $-_.+!*\'"(),{}|\\^~[]' print urllib.quote(str)
実行結果
> python url_encode.py %3B/%3F%3A%40%3D%26%25%20%24-_.%2B%21%2A%27%22%28%29%2C%7B%7D%7C%5C%5E%7E%5B%5D
- 半角スペースが %20 になる
- / - _ . がエンコードされない
PHP の場合
- urlencode()
- rawurlencode()
url_encode.php
<?php $str = ';/?:@=&% $-_.+!*\'"(),{}|\\^~[]'; // (1) echo urlencode($str), "\n"; // (2) echo rawurlencode($str); ?>
実行結果
> php url_encode.php %3B%2F%3F%3A%40%3D%26%25+%24-_.%2B%21%2A%27%22%28%29%2C%7B%7D%7C%5C%5E%7E%5B%5D %3B%2F%3F%3A%40%3D%26%25%20%24-_.%2B%21%2A%27%22%28%29%2C%7B%7D%7C%5C%5E~%5B%5D
- (1) urlencode()
- 半角スペースが + になる
- - _ . がエンコードされない
- (2) rawurlencode()
- 半角スペースが %20 になる
- - _ . ~ がエンコードされない
まとめ
まとめると以下のようになりました。
こうしてみると微妙な違いが結構ありますね。
| 言語環境 | API | 半角スペースのエンコード | エンコードされない文字 |
|---|---|---|---|
| Java | URLEncoder.encode() | + | - _ . * |
| .NET | Uri.EscapeUriString() | %20 | ; / ? : @ = & $ - _ . + ! * ' ( ) , ~ |
| .NET | HttpUtility.UrlEncode() | + | - _ . ! * ( ) |
| JavaScript | escape() | %20 | / @ - _ . + * |
| JavaScript | encodeURI() | %20 | ; / ? : @ = & $ - _ . + ! * ' ( ) , ~ |
| JavaScript | encodeURIComponent() | %20 | - _ . ! * ' ( ) ~ |
| Ruby | URI.escape(), encode() | %20 | ; / ? : @ = & $ - _ . + ! * ' ( ) , ~ [ ] |
| Ruby | CGI.escape() | + | - _ . |
| Python | urllib.quote() | %20 | / - _ . |
| PHP | urlencode() | + | - _ . |
| PHP | rawurlencode() | %20 | - _ . ~ |
2012-08-09
■[Java][Scala][Groovy][Ruby][PHP][Node.js][CoffeeScript] Markdown の HTML 変換 - Ruby, PHP, Groovy, Scala, Node.js
Markdown 形式の文字列を HTML 変換する処理を複数のプログラム言語で試してみました。
処理としては、標準入力から UTF-8 の Markdown 形式の文字列を取得し HTML 変換した結果を標準出力へ UTF-8 で出力しています。
ちなみに、Markdown 文字列は LOGGiX プロジェクトの日本語版サンプル markdown-sample.text を使用しました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20120809/
Ruby の場合
Pure Ruby な kramdown モジュールを JRuby で実行してみました。
- JRuby 1.7.0 preview1
- kramdown 0.13.7
tohtml.rb
require 'kramdown' Encoding.default_external = 'utf-8' puts Kramdown::Document.new($stdin.read).to_html
実行例
jruby tohtml.rb < markdown-sample.text > ruby_result.txt
結果
特に問題なく変換されました。
更に kramdown は以下のような表組みにも対応していました。
プログラム言語 | モジュール名 ---------------|------------- PHP | Markdown Ruby | kramdown Groovy | MarkdownJ Scala | knockoff Node.js | markdown
HTML 変換結果は以下の通りです。
<table> <thead> <tr> <th>プログラム言語</th> <th>モジュール名</th> </tr> </thead> <tbody> <tr> <td>PHP</td> <td>Markdown</td> </tr> ・・・ </tbody> </table>
PHP の場合
Markdown Extra をダウンロードし、markdown.php をカレントディレクトリに配置して、実行しました。
- PHP 5.4.4
- Markdown Extra 1.2.5
tohtml.php
<?php include_once "markdown.php"; $mkStr = stream_get_contents(STDIN); echo Markdown($mkStr); ?>
実行例
php tohtml.php < markdown-sample.text > php_result.txt
結果
kramdown と同様、特に問題なく変換され表組みにも対応していました。
Extra の付いていない Markdown 1.0.1o の方は表組みに対応していないのでご注意ください。
Groovy の場合
Java 用のモジュール MarkdownJ を Groovy で使いました。
- Groovy 2.0.0
- MarkdownJ Core 0.4.1
tohtml.groovy
@Grab('com.madgag:markdownj-core:0.4.1') import com.petebevin.markdown.* def mk = new MarkdownProcessor() def enc = 'UTF-8' System.out.withWriter enc, {w -> w.print mk.markdown(System.in.getText(enc)) }
実行例
groovy tohtml.groovy < markdown-sample.text > groovy_result.txt
結果
以下のような問題が発生しました。
・・・ <p> 「リファレンス」スタイル: ! <a title="Loggix" href="../../../theme/images/loggix-logo.png">Loggix</a> </p>
Scala の場合
Scala は 2種類のモジュールを試してみました。Scala 2.9.2 用のモジュールが見当たらなかったため、2.9.1 用のモジュールを 2.9.2 でスクリプト実行しました。
tohtml.scala (Knockoff 版)
import scala.io._ import com.tristanhunt.knockoff.DefaultDiscounter._ val mkStr = new BufferedSource(System.in)(Codec.UTF8).mkString val ps = new java.io.PrintStream(System.out, false, "UTF-8") ps.println(toXHTML(knockoff(mkStr)))
実行例1 (Knockoff 版)
scala -nc -cp knockoff_2.9.1-0.8.0-16.jar tohtml.scala < markdown-sample.text > scala_result.txt
ここで -nc オプションを指定している点にご注意ください。-nc オプションを付けないとコンパイラのプロセスがリダイレクト先のファイルを開いたままになり不都合が生じます。
結果
以下のような問題が発生しました。
<p>Markdown Sample ======================= </p><p>サンプルドキュメント日本語版 ----------------------- </p>
tohtml2.scala (Actuarius 版)
import scala.io._ import eu.henkelmann.actuarius.ActuariusApp val mkStr = new BufferedSource(System.in)(Codec.UTF8).mkString val ps = new java.io.PrintStream(System.out, false, "UTF-8") ps.println(ActuariusApp(mkStr))
実行例2 (Actuarius 版)
scala -nc -cp actuarius_2.9.1-0.2.3.jar tohtml2.scala < markdown-sample.text > scala_result2.txt
結果2 (Actuarius 版)
Knockoff とは別の問題が発生しました。
- '+' と '-' のリストが正しく変換されなかった
<p>(プラス記号で記述)</p> <p>+ モツァレラチーズ + パスタ + ワイン</p> <p>(ハイフン(マイナス記号)で記述)</p> <p>- モツァレラチーズ - パスタ - ワイン</p>
なお、Scala IO を使うと標準入出力まわりの処理が以下のようになります。
ToHtml.scala
package fits.sample
import scalax.io.JavaConverters._
import com.tristanhunt.knockoff.DefaultDiscounter._
object ToHtml extends App {
val mkStr = System.in.asUnmanagedInput.slurpString
val html = toXHTML(knockoff(mkStr)).mkString
System.out.asUnmanagedOutput.write(html)
}
Node.js (CoffeeScript) の場合
Markdown-js を npm install markdown でインストールし、CoffeeScript で実行しました。
- Node.js 0.8.0
- CoffeeScript 1.3.3
- Markdown-js 0.4.0
tohtml.coffee
mk = require 'markdown' process.stdin.resume() process.stdin.on 'data', (data) -> console.log mk.markdown.toHTML data.toString()
実行例
coffee tohtml.coffee < markdown-sample.text > coffee_result.txt
結果
以下のような問題が発生しました。
- '=' と '-' の見出しが正しく変換されなかった
- '<' '>' '&' などの不要なエスケープが実施されてしまった
<pre><code><blockquote></code></pre> <p> <p>For example.</p> </blockquote> </p> <h3>HTMLとの共存</h3> ・・・ <p>例:April 1<sup>st</sup>
まとめ
今回の結果をまとめると以下のようになります。
| 言語 | モジュール | 見出し = - | リファレンススタイル(画像) | リスト + - | エスケープ | 表組み |
|---|---|---|---|---|---|---|
| Ruby | kramdown 0.13.7 | ○ | ○ | ○ | ○ | ○ |
| PHP | Markdown Extra 1.2.5 | ○ | ○ | ○ | ○ | ○ |
| Groovy | MarkdownJ Core 0.4.1 | ○ | × | ○ | ○ | × |
| Scala | Knockoff 0.8.0 | × | × | ○ | ○ | × |
| Scala | Actuarius 0.2.3 | ○ | ○ | × | ○ | × |
| Node.js (CoffeeScript) | Markdown-js 0.4.0 | × | ○ | ○ | × | × |
2011-10-02
■[Ruby][Java][C#][Groovy][PHP] 信頼されない証明書を使ったHTTPSサーバーにBasic認証でPOST - Ruby, PHP, C#, Java, Groovy
信頼されないSSL証明書(自己証明書)を使ったサイトに対して、Basic認証を行い POST するサンプルを Ruby, PHP, C#, Java, Groovy で実装してみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20111002/
サンプルは、第1引数に URL、第2引数と第3引数に Basic 認証のユーザー名とパスワード、第4引数に POST するデータを指定するようにしています。
なお、POST データを & で区切ると Windows のコマンドプロンプトなどで実行するには不都合があるので ; で区切っている点に注意。
サンプルの実行例
> jruby basic_post_novalidate_certificate.rb https://localhost:8443/ user1 pass1 mode=test;id=123 hello
HTTPS サーバープログラム作成
まずは、クライアントからの処理を受け付けるサーバープログラムを作成し、起動しておきます。
サーバープログラムは HTTPS・Basic認証・POST を処理する必要がありますので、今回は Sinatra + WEBrick で実現してみました。
実装が容易で証明書などの準備も不要なのでテスト用途にはお勧めな方法です。(証明書が未指定なら実行時に自動生成してくれる)
下記では ポート 8443 で実行、クライアントの検証を実施せず(:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE)、ユーザー名が user1 ならBasic認証を通るように実装しています。
https_server.rb (Sinatra を使った HTTPS サーバープログラム)
require "rubygems" require "sinatra/base" require "webrick/https" require "openssl" class SampleApp < Sinatra::Base #Basic認証 use Rack::Auth::Basic do |user, pass| user == 'user1' end #POST の処理 post '/' do p params 'hello' end end #WEBrick で SSL を使用するための処理 #(実行時に自己証明書が自動生成される) Rack::Handler::WEBrick.run SampleApp, { :Port => 8443, :SSLEnable => true, #クライアントを検証しないための設定 :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, :SSLCertName => [ ["CN", WEBrick::Utils::getservername] ] }
サーバープログラム実行例
> jruby https_server.rb
今回はサーバープログラムを JRuby 1.6.4 (JavaSE 7) や Ruby 1.8.7 で実行しています。
Ruby 1.9.2(RubyInstaller for Windows)で実行した場合では、Java 系のクライアントからの接続に失敗するようなので注意が必要です。
Ruby による Web クライアント
Ruby では、use_ssl を true にした Net::HTTP オブジェクトの verify_mode に OpenSSL::SSL::VERIFY_NONE を設定すれば自己証明書を処理できるようになります。
basic_post_novalidate_certificate.rb
require 'net/https' require 'uri' url = URI.parse(ARGV[0]) user = ARGV[1] pass = ARGV[2] postData = ARGV[3] https = Net::HTTP.new(url.host, url.port) #SSLの有効化 https.use_ssl = true #SSL証明書を検証しないための設定 https.verify_mode = OpenSSL::SSL::VERIFY_NONE res = https.start do req = Net::HTTP::Post.new(url.path) #Basic認証 req.basic_auth user, pass #POSTデータの設定 req.body = postData #POST https.request(req) end #結果の出力 print res.body
PHP による Web クライアント
PHP で file_get_contents を使えば HTTPS を特に意識しなくてもよいので簡単に実装できます。(自己証明書を意識する必要も無いみたいです)
ただし、php_openssl の extension を有効化する等、file_get_contents で HTTPS を処理するための環境設定が必要かもしれません。(今回は php.ini で php_openssl の extension を有効化しました)
Basic認証に関しては URL でユーザー名とパスワードを指定する方法もありますが、今回は Authorization ヘッダーで指定する方法をとっています。
- PHP 5.3.8
basic_post_novalidate_certificate.php
<?php $url = $argv[1]; $user = $argv[2]; $pass = $argv[3]; $postData = $argv[4]; $options = array('http' => array( 'method' => 'POST', 'header' => "Authorization: Basic " . base64_encode("$user:$pass") . "\r\n" . "Content-Type: application/x-www-form-urlencoded\r\n", 'content' => $postData )); //POST $res = file_get_contents($url, false, stream_context_create($options)); //結果の出力 echo $res; ?>
C# による Web クライアント
C# では ServicePointManager.ServerCertificateValidationCallback に true を返すコールバックを設定するだけで自己証明書を処理できるようになります。
Basic認証も POST も WebClient クラスを使えば簡単に実装できます。
- .NET Framework 4.0
BasicPostNovalidateCertificate.cs
using System; using System.Net; class BasicPostNovalidateCertificate { public static void Main(string[] args) { var url = args[0]; var user = args[1]; var pass = args[2]; var postData = args[3]; //SSL 証明書を検証しないようにする設定 //(証明書を何でも受け入れるようにする) ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) => true; using (WebClient wc = new WebClient()) { //Basic認証 wc.Credentials = new NetworkCredential(user, pass); //POST var res = wc.UploadString(url, "POST", postData); //結果の出力 Console.Write(res); } } }
なお、上記を実行すると結果は一応返って来ますがサーバー側でエラー出力(既存の接続はリモートホストに強制的に切断されました)されます。(解決策は不明。サーバーの実行環境が JRuby・Ruby の違いに関わらず発生する)
Java による Web クライアント
Java では SSLContext を何もしない X509TrustManager で初期化し、どんなホスト名でも受け入れる HostnameVerifier を設定します。
- JavaSE 7
BasicPostNovalidateCertificate.java
import java.io.*; import java.net.*; import java.security.SecureRandom; import java.security.cert.X509Certificate; import javax.net.ssl.*; public class BasicPostNovalidateCertificate { public static void main(String[] args) throws Exception { URL url = new URL(args[0]); final String user = args[1]; final String pass = args[2]; String postData = args[3]; //Basic認証 Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(user, pass.toCharArray()); } }); // ホスト名を検証しないようにする設定 //(openConnection する前に設定しておく必要あり) HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(String host, SSLSession ses) { return true; } }); HttpsURLConnection con = (HttpsURLConnection)url.openConnection(); con.setDoOutput(true); con.setRequestMethod("POST"); //SSL 証明書を検証しないための設定 SSLContext sslctx = SSLContext.getInstance("SSL"); sslctx.init(null, new X509TrustManager[] { new X509TrustManager() { public void checkClientTrusted(X509Certificate[] arg0, String arg1) { } public void checkServerTrusted(X509Certificate[] arg0, String arg1) { } public X509Certificate[] getAcceptedIssuers() { return null; } } }, new SecureRandom()); con.setSSLSocketFactory(sslctx.getSocketFactory()); //POSTデータの出力 OutputStream os = con.getOutputStream(); PrintStream ps = new PrintStream(os); ps.print(postData); ps.close(); //結果の出力 InputStream is = con.getInputStream(); BufferedInputStream bis = new BufferedInputStream(is); int len = 0; byte[] buf = new byte[1024]; while ((len = bis.read(buf, 0, buf.length)) > -1) { System.out.write(buf, 0, len); } bis.close(); } }
なお、Java でも C# と同様にサーバー側でエラー出力(既存の接続はリモートホストに強制的に切断されました)されます。
Groovy による Web クライアント
Groovy は基本的に Java 版の内容をそのまま実装しました。
- Groovy 1.8.2(JavaSE 7)
basic_post_novalidate_certificate.groovy
import java.security.SecureRandom import java.security.cert.X509Certificate import javax.net.ssl.* def url = new URL(args[0]) def user = args[1] def pass = args[2] def postData = args[3] //Basic認証(getPasswordAuthentication() をオーバーライド) Authenticator.default = { new PasswordAuthentication(user, pass.toCharArray()) } as Authenticator // ホスト名を検証しないようにする設定 //(注)openConnection する前に設定しておく必要あり HttpsURLConnection.defaultHostnameVerifier = { host, session -> true } as HostnameVerifier def con = url.openConnection() con.doOutput = true con.requestMethod = "POST" //SSL 証明書を検証しないための設定 def sslctx = SSLContext.getInstance("SSL") //X509TrustManager インターフェースの実装 def tmanager = [ checkClientTrusted: {chain, authType -> }, checkServerTrusted: {chain, authType -> }, getAcceptedIssuers: {null} ] as X509TrustManager sslctx.init(null as KeyManager[], [tmanager] as X509TrustManager[], new SecureRandom()) con.SSLSocketFactory = sslctx.socketFactory //POSTデータの出力 con.outputStream.withWriter { it.print postData } //結果の出力 print con.inputStream.text
なお、Java と同様にサーバー側でエラー出力(既存の接続はリモートホストに強制的に切断されました)されます。
2010-12-04
■[.NET][C#][PHP] PHP, C# での Excel準拠 CSV ファイルのパース処理
前回 id:fits:20101129 の続きで、Excel の仕様に準拠した CSV ファイル(改行・カンマ・ダブルクォーテーションを要素内に含む)をパースするサンプルの PHP と C# 版です。
使用する CSV ファイルや出力結果の例は、前回 id:fits:20101129 を参照。
サンプルのソースは http://github.com/fits/try_samples/tree/master/blog/20101204/
PHP の場合
PHP では標準で用意されている fgetcsv を使います。
parse_csv.php
<?php if (($h = fopen($argv[1], "r")) !== FALSE) { while (($r = fgetcsv($h)) !== FALSE) { echo "$r[0] : $r[2]\n"; } fclose($h); }
str_getcsv というのもありますが、これは 1行分の文字列を処理するために使います。
以下の環境で実行してみました。
- PHP 5.3.3 Win32 VC9
実行例
> php parse_csv.php test.csv
C# の場合
C# というか .NET では、Microsoft.VisualBasic.FileIO.TextFieldParser を使えば、今回のような CSV ファイルを処理できます。
デフォルト設定だとファイルの文字コードが UTF-8 で処理されてしまうので、エンコードを指定しています。
parse_csv.cs
using System; using System.Text; using Microsoft.VisualBasic.FileIO; class CSVParse { public static void Main(string[] args) { using (var reader = new TextFieldParser(args[0], Encoding.Default)) { //区切り文字を設定する必要あり reader.SetDelimiters(","); while (!reader.EndOfData) { var r = reader.ReadFields(); Console.WriteLine("{0} : {1}", r[0], r[2]); } } } }
以下の環境でビルド実行してみました。
- Visual C# 2010(.NET Framework 4.0)
実行例
> csc /r:Microsoft.VisualBasic.dll parse_csv.cs > parse_csv.exe test.csv
2010-11-12
■[Java][PHP] CakePHP on Java Servlet - quercus 使用
前回 id:fits:20101111 に続き、今度は CakePHP を Java Servlet Engine(jetty)上で実行してみました。
使用した環境は以下の通り。CakePHP 以外は id:fits:20101111 と同じです。
- CakePHP 1.3.5
- quercus 4.0.11
- Maven 3.0(jetty 6.1.26)
サンプルのソースコード、CakePHP の変更ファイルは http://github.com/fits/try_samples/tree/master/blog/20101112/
Maven プロジェクトの作成・設定や quercus のファイルの配置に関しては id:fits:20101111 を参照。
CakePHP を Java Servlet で実行するための作業
Java Servlet(quercus)で CakePHP を実行するには、以下を実施します。
- CakePHPのシングルトンの実装を変更
- CakePHP用の Servlet Filter を用意
- キャッシュを無効化
なお、事前準備として src/main/webapp に CakePHP のソースファイルを配置して、core.php に必要最小限の設定を行っておきます。
CakePHPのシングルトンの実装を変更
まず、シングルトンの実装を変更します。
これをやらないとシングルトンが有効に機能せず、ページの表示時に Fatal Error が発生し、CakePHP が正常に動作しません。
cake/libs 内のソースファイルを対象に getInstance の実装を以下のように変更します。
変更後(src/main/webapp/cake/libs/configure.php)
function &getInstance() { if (is_null(self::$instance)) { self::$instance =& new App(); self::$instance->__map = (array)Cache::read('file_map', '_cake_core_'); } return self::$instance; } //$instance を外出し static $instance = null;
ちなみに、元の実装は以下のようになっています。
変更前(src/main/webapp/cake/libs/configure.php)
function &getInstance() { static $instance = array(); if (!$instance) { $instance[0] =& new App(); $instance[0]->__map = (array)Cache::read('file_map', '_cake_core_'); } return $instance[0]; }
なお、変更対象ファイルは以下のようなものです。
シングルトンの実装変更対象ファイル
- src/main/webapp/cake/libs
また、変数未定義の Notice が発生する箇所も必要に応じて修正します。(例えば、debugger.php の compact に渡す helpID の変数定義など)
CakePHP用の Servlet Filter を用意
.htaccess の代わりにアクセス先を変更する Servlet Filter を用意します。
必要最低限の処理はファイルの有無に応じて app/webroot もしくは app/webroot/index.php?url=[URL] にフォワードする事です。今回も自作してみました。
src/main/java/fits/sample/CakeRoutingFilter.java
package fits.sample; ・・・ public class CakeRoutingFilter implements Filter { ・・・ public void init(FilterConfig config) { this.ctx = config.getServletContext(); } //フィルター処理 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest)request; String contextPath = req.getContextPath(); String requestPath = req.getRequestURI(); String path = requestPath.replace(contextPath + "/", "").trim(); if (!path.startsWith("app/webroot")) { String vPath = "app/webroot/" + path; String rPath = this.ctx.getRealPath(vPath); if (new File(rPath).exists()) { this.dispatchForward("/" + vPath, request, response); return; } else { this.dispatchForward("/app/webroot/index.php?url=" + path, request, response); return; } } chain.doFilter(request, response); } ・・・ private void dispatchForward(String path, ServletRequest request, ServletResponse response) throws ServletException, IOException { request.getRequestDispatcher(path).forward(request, response); } }
キャッシュを無効化
キャッシュが有効な状態だと 2度目以降のアクセスで例外が発生するようになるため、app/config/core.php で Cache.disable を true に設定しキャッシュを無効化します。
src/main/webapp/app/config/core.php
Configure::write('Cache.disable', true);
動作確認
DB に MySQL を使って、簡単なモデル・ビュー・コントローラーを実行してみます。
DB・テーブルを作成(sql/todo_db.sql 参照)し、app/config/database.php を作成します。
なお、src/main/webapp/WEB-INF/lib に MySQL の JDBC ドライバー(mysql-connector-java-5.1.13-bin.jar)を配置しておきます。
src/main/webapp/app/config/database.php
<?php class DATABASE_CONFIG { var $default = array( 'driver' => 'mysql', 'persistent' => false, 'host' => 'localhost', 'login' => 'root', 'password' => '', 'database' => 'todo', 'prefix' => '', 'encoding' => 'utf8' ); }
モデル・コントローラーを作成します。
src/main/webapp/app/models/datasources/tasks.php
<?php class Task extends AppModel { var $name = "Task"; }
src/main/webapp/app/controllers/tasks_controller.php
<?php class TasksController extends AppController { var $name = "Tasks"; function index() { $tasks = $this->Task->find('all'); $this->set('tasks', $tasks); } //追加 function add() { $this->Task->save($this->data); $this->redirect('.'); } }
ビューを作成します。(レイアウトは src/main/webapp/app/views/layouts/default.ctp 参照)
Java Servlet として実行する場合、find の結果がテーブル名 tasks の連想配列の配列になるようなので注意。(通常はモデル名 Task の連想配列の配列になる)
src/main/webapp/app/views/tasks/index.ctp
<div> <?php echo $form->create('Task', array('type' => 'POST', 'action' => 'add')); echo $form->label(null, 'タイトル'); echo $form->text('Task.title'); echo $form->submit('追加'); echo $form->end(); ?> </div> <div> <table> <tr> <th>ID</th> <th>タイトル</th> <th>登録日</th> <th>更新日</th> </tr> <?php foreach ($tasks as $task) { //Java で実行する場合はテーブル名 tasks の連想配列になっている //XAMPP で普通に実行する場合はモデル名 Task の連想配列になる $t = $task['tasks']; ?> <tr> <td><?php echo $t['task_id']; ?></td> <td><?php echo $t['title']; ?></td> <td><?php echo $t['created']; ?></td> <td><?php echo $t['modified']; ?></td> </tr> <?php } ?> </table> </div>
jetty を起動して http://localhost:8080/cakephp-sample/tasks/ にアクセスすれば、一応動作している事が確認できると思います。
jetty 起動
> mvn jetty:run
ただし、原因は未調査ですが。
タイトルに日本語を入力して「追加」ボタンを押すと文字化けするようなので注意。(同一コードを XAMPP で実行しても文字化けは発生しない)
