Java/CGI

最近、プロジェクトでPHPを扱っているが、PHPを使うメリットの一つは、コードの実行結果をブラウザですぐに確認できることだと思う。ちょっとコードを書いてたら、ブラウザでアクセスして、またコードを書いたらブラウザでアクセスして…
JavaのWEBアプリでも、JSPをいじれば再コンパイルされるし、クラスをいじれば再ロードされるけど、PHPの軽快さには及ばないように感じる。
Javaでも似たようなことができないかな…と思ったので、JavaでCGIを作ってみた。

jcgi.cgi(jcgi.c)

サーバから呼び出されてjavaコマンドをキックするモジュール。Javaでの作り方がよくわからなかったので(できるのかな…?)、Cで作った。


#include
#include
#include
#include

#define BUF_CMD 4096
#define JAVA_CMD "java"
#define JAVA_CGI_CLASS "foo.bar.CGI"

void error(const char* msg);
char *get(void);
void post(char **buf);
void run_java_cgi(const char *query);

int main(void) {
char *request_method = (char *)getenv("REQUEST_METHOD");

if(request_method == NULL) {
error("undefined environment variable to 'REQUEST_METHOD'.");
}

char *query;

if(strcmp(request_method, "GET") == 0) {
query = get();
} else if(strcmp(request_method, "POST") == 0) {
post(&query);
} else {
query = "";
}

run_java_cgi(query);

return EXIT_SUCCESS;
}

char *get(void) {
char *query_string = (char *)getenv("QUERY_STRING");

if(query_string == NULL) {
error("undefined environment variable to 'QUERY_STRING'.");
}

return query_string;
}

void post(char **buf) {
char *content_length = getenv("CONTENT_LENGTH");

if(content_length == NULL) {
error("undefined environment variable to 'CONTENT_LENGTH'.");
}

int len = atoi(content_length);
*buf = (char *)malloc(len + 1);

if(*buf == NULL) {
error("cannot allocate memory.");
}

if(fread(*buf, len, 1, stdin) < 1) {
error("cannot read stdin.");
}

(*buf)[len] = '\0';
}

void run_java_cgi(const char *query) {
char cmd[BUF_CMD];
cmd[0] = '\0';

char *java_home = getenv("JAVA_HOME");

if(java_home != NULL) {
strcat(cmd, java_home);
strcat(cmd, "/bin/");
}

strcat(cmd, JAVA_CMD);

char *java_cgi_cp = getenv("JAVA_CGI_CP");

if(java_cgi_cp != NULL) {
strcat(cmd, " -cp ");
strcat(cmd, java_cgi_cp);
}

strcat(cmd, " ");

char *java_cgi_class = getenv("JAVA_CGI_CLASS");

if(java_cgi_class != NULL) {
strcat(cmd, java_cgi_class);
} else {
strcat(cmd, JAVA_CGI_CLASS);
}

char *path_info = (char *)getenv("PATH_INFO");

if(path_info != NULL) {
strcat(cmd, " ");
strcat(cmd, path_info);
}

FILE *java_cgi;
java_cgi = popen(cmd, "w");

if(java_cgi == NULL) {
error("cannot execute java command.");
}

fprintf(java_cgi, "%s", query);

int code = pclose(java_cgi);

if(code != 0) {
const char *template = "exit failure to '%s'.";
char msg[BUF_CMD + strlen(template)];
sprintf(msg, template, cmd);
error(msg);
}
}

void error(const char* msg) {
printf("Content-Type: text/html\n\n");
printf("\n");
printf(" \n");
printf(" ERROR - [<a class="keyword" href="http://d.hatena.ne.jp/keyword/Java">Java</a>/CGI]\n");
printf(" \n");
printf(" \n");
printf(" %s\n", msg);
printf(" \n");
printf("\n");
exit(EXIT_FAILURE);
}

久しぶりのCのコードのなのであっているか不明。

foo.bar.CGI

jcgi.cgiから呼び出されるコントローラクラス。設定ファイルの読み込みとリクエストパラメータの解析を行って、各コンポーネントを呼び出す。


package foo.bar;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import foo.bar.impl.CGIConfImpl;

class CGI {

private static final int BUF_LEN = 256;

private static final String CONFIG_FILE = "jcgi.properties";

private static final String CONFIG_PREFIX = "jcgi.";

public static void main(String args) {

try {
StringWriter outBuf = new StringWriter();
PrintWriter writer = new PrintWriter(outBuf);

writer.println("Content-Type: text/html");
writer.println();

CGIConf config = new CGIConfImpl(CONFIG_FILE, CONFIG_PREFIX);
String enc = config.getEncoding();

String extPath = getExtendedPath(args);

String query = getQuery();
Map params = getParams(query, enc);

CGIRunner runner = config.getRunner();

runner.setConfig(config);
runner.setExtendedPath(extPath);
runner.setParams(params);
runner.run(writer);

writer.flush();

System.out.println(outBuf.toString());

writer.close();
} catch (Exception e) {
System.out.println("Content-Type: text/plain");
System.out.println();
e.printStackTrace(System.out);
System.out.println(System.getProperties());
System.out.println();
}
}

private static String getQuery() throws IOException {
InputStreamReader stdin = new InputStreamReader(System.in);
BufferedReader bufin = new BufferedReader(stdin);

StringBuffer strBuf = new StringBuffer();
char cbuf = new char[BUF_LEN + 1];
int n = -1;

while ( (n = bufin.read(cbuf, 0, BUF_LEN)) != -1) {
strBuf.append(cbuf, 0, n);
}

bufin.close();
stdin.close();

return strBuf.toString();
}

private static String getExtendedPath(String args) {
if (args == null || args.length < 1) {
return "";
} else {
return args[0];
}
}

private static Map getParams(String query, String enc) throws IOException {
Map params = new HashMap();

if (query == null || query.trim().equals("")) {
return params;
}

String pairs = query.split("&");

for (int i = 0; i < pairs.length; i++) {
String pair = pairs[i];
String[] name_value = pair.split("=");
String name = name_value[0];
String value = (name_value.length > 1) ? name_value[1] : "";

if (enc != null) {
value = URLDecoder.decode(value, enc);
}

if (params.containsKey(name)) {
Object values = params.get(name);

if (values instanceof List) {
((List) values).add(value);
} else {
List list = new ArrayList();
list.add(values);
list.add(value);
values = list;
}

params.put(name, values);
} else {
params.put(name, value);
}
}

return params;
}

}

foo.bar.impl.SampleRunner

実際の業務処理を行うコンポーネント。


package foo.bar.impl;

import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Map;

import foo.bar.CGIConf;
import foo.bar.CGIRunner;

public class SampleRunner implements CGIRunner {

private CGIConf config = null;

private String extPath = null;

private Map params = null;

public void setConfig(CGIConf config) {
this.config = config;
}

public void setExtendedPath(String extPath) {
this.extPath = extPath;
}

public void setParams(Map params) {
this.params = params;
}

public void run(PrintWriter writer) {
writer.println("");
writer.println(" ");
writer.println(" hello, Java/CGI");
writer.println(" ");
writer.println(" ");
writer.println(" hello, Java/CGI");
writer.println("


");
writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println("
Configurations("
+ config.getClass().getName() + ")
Runner" + config.getRunner().getClass().getName()
+ "
Encoding" + config.getEncoding() + "
");
writer.println("
");
writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); if (extPath != null && !extPath.equals("")) { writer.println(" "); } else { writer.println(" "); } writer.println(" "); writer.println("
Extended path
" + extPath + "empty.
");
writer.println("
");
writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); if (params.size() > 0) { Iterator ite = params.keySet().iterator(); while (ite.hasNext()) { String name = (String) ite.next(); Object value = params.get(name); writer.println(" "); writer.println(" "); writer.println(" "); writer.println(" "); } } else { writer.println(" "); writer.println(" "); writer.println(" "); } writer.println("
Parameters
" + name + "" + value + "
empty.
");
writer.println(" ");
writer.println("");
}

}

実行結果

AN HTTPDで結果を確認。
http://localhost/jcgi/bin/c/jcgi.cgi/ext/path?foo=bar&hoge=hogera&hoge=hogehogeの表示内容が以下のような感じ。



hello, Java/CGI


Configurations(foo.bar.impl.CGIConfImpl)
Runner foo.bar.impl.SampleRunner
Encoding UTF-8


Extended path
/ext/path


Parameters
foo bar
hoge [hogera, hogehoge]

Eclipseのプロジェクトのデフォルト出力フォルダーにクラスパスを通しておくと、ソースを変更するたびにクラスがリビルドされて、スクリプト言語のような感覚でCGIを実行できた。
JSPが使えないので、プレゼンテーション層がタコだけど、サーブレットコンテナが不要なのでシンプルといえばシンプル。プレゼンテーション層をVelocityにすれば、(多少は)実用的になるかも…。

Eclipseのプロジェクト一式はここ