GAE/Jで Socket 使う場合には urlfetch しか使えない。
こいつはかなり低機能なので HttpURLConnection がラッパとして有るのだがこいつも結構使いづらい。

JavaScript使いなら当然 XMLHttpRequest が使いたいよね、
と言うわけで urlfetch で XMLHttpRequest を実装してみました。
非同期は出来ないけど。


package kotemaru.wsjs.gae;

import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.mozilla.javascript.*;
//import kotemaru.util.LOG ;
//import kotemaru.wsjs.Config ;
import javax.servlet.http.HttpSession;
import com.google.appengine.api.urlfetch.*;
//import kotemaru.wsjs.ssjs.SsjsEnv;

/**
TODO: 非同期、キャッシュ、クッキー
*/
public class XMLHttpRequest {
private static final String GET = "GET";
private static final String POST = "POST";
private static final String CONTENT_TYPE = "content-type";
private static final String SET_COOKIE = "set-cookie";

//private final SsjsEnv __ENV__;
private final URLFetchService fetcher;
private HTTPRequest request = null;
private String overrideMimeType = null;

private HTTPResponse response = null;
private int status = 0;
private Map<String, String> responseHeaderMap = null;
private String responseText = null;
private Document responseXML = null;
public String charset = null; //TODO:

//public XMLHttpRequest(SsjsEnv env) {
public XMLHttpRequest() {
//__ENV__ = env;
fetcher = URLFetchServiceFactory.getURLFetchService();

/* TODO: urlfetch のプロキシの設定方法が分からん。
String proxyHost = Config.getProxyHost();
int proxyPort = Config.getProxyPort();
if (proxyHost != null && proxyHost.length()>0 ) {
client.getHostConfiguration().setProxy(proxyHost, proxyPort);
}
*/
}

public void open(String type, String url) throws Exception {
open(type, url, false);
}
public void open(String type, String url, boolean isAsync) throws Exception {
if (isAsync) {
throw new RuntimeException("Unsupported async XMLHttpRequest.");
}
if (GET.equalsIgnoreCase(type)) {
request = new HTTPRequest(new URL(url), HTTPMethod.GET);
} else {
request = new HTTPRequest(new URL(url), HTTPMethod.POST);
this.setRequestHeader(CONTENT_TYPE, "text/plain; charset=utf-8");
}
this.setRequestHeader("Connection","close");
}
public void close() throws Exception {
// nop.?
}

public void overrideMimeType(String ctype) {
overrideMimeType = ctype.trim().toLowerCase();
}
public void setRequestHeader(String name, String value) {
request.setHeader(new HTTPHeader(name, value));
}
public String getResponseHeader(String name) {
return responseHeaderMap.get(name);
}

private String getContentType() {
if (overrideMimeType != null) return overrideMimeType;
String val = getResponseHeader(CONTENT_TYPE);
if (val == null) return null;
return val.trim().toLowerCase();
}

public void send(String body) throws Exception {
//__ENV__.LOG.info("XREQ.send|"+__ENV__.getUser()+"|"+__ENV__.getPage()+"|"+request.getMethod()+" "+request.getURL());
//if (__ENV__.getWsjsContext().getAppConfig() != null) {
// __ENV__.getWsjsContext().getAppConfig().addXreqCount();
//}
try {
_send(body);
} finally {
// TODO: close
}
}
private void _send(String body) throws Exception {

if (request.getMethod() == HTTPMethod.POST) {
request.setPayload(body.getBytes("utf-8"));
}
response = fetcher.fetch(request);
status = response.getResponseCode();
responseHeaderMap = parseResponseHeader(response);

//if (__ENV__.LOG.isDebugEnabled()) debugLog();

String ctype = getContentType();
if (ctype.startsWith("text/xml")
|| ctype.startsWith("application/xhtml+xml")
|| ctype.startsWith("application/rss+xml")) {
DocumentBuilder builder =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
byte[] resbody = response.getContent();
ByteArrayInputStream in = new ByteArrayInputStream(resbody);
responseXML = builder.parse(in);
} else {
byte[] resbody = response.getContent();
if (charset == null) {
responseText = new String(resbody, "utf-8");
} else {
responseText = new String(resbody, charset);
}
}
}
private Map<String, String> parseResponseHeader(HTTPResponse response) {
List<HTTPHeader> headers = response.getHeaders();
Map<String, String> map = new HashMap<String, String>(headers.size());
for (HTTPHeader h : headers) {
String name = h.getName().toLowerCase();
String val = map.get(name);
if (val == null) {
map.put(name, h.getValue());
} else {
map.put(name, val+", "+h.getValue());
}
if (CONTENT_TYPE.equals(name)) {
this.charset = parseCharset(h.getValue());
}
if (SET_COOKIE.equals(name)) {
// TODO:Cookieパーザ
}
}
return map;
}
private String parseCharset(String ctype) {
String[] parts = ctype.split(";");
for (int i=0; i<parts.length; i++) {
String part = parts[i].trim();
if (part.startsWith("charset")) {
String[] pair = part.split("=");
if (pair.length > 1) return pair[1].trim();
}
}
return null;
}


public int getStatus() {
return status;
}
public String getResponseText() {
return responseText;
}
public Document getResponseXML() throws Exception {
return responseXML;
}

/*
private void debugLog() throws Exception {
String msg = "XREQ request detail.\n";
msg += ">> "+request.getMethod()+" "+request.getURL()+"\n";
List<HTTPHeader> headers = response.getHeaders();
for (HTTPHeader h : headers) {
msg += ">> "+h.getName()+": "+h.getValue()+"\n";
}

msg += "\n<< "+response.getResponseCode()+"\n";
headers = response.getHeaders();
for (HTTPHeader h : headers) {
msg += "<< "+h.getName()+": "+h.getValue()+"\n";
}

__ENV__.LOG.debug(msg);
}
*/
}



Rhino からこんな感じで使えてる。

function doGet(req, res) {
var xreq = new XMLHttpRequest();
xreq.open("GET","http://www.google.co.jp/");
xreq.send(null);

res.setContentType("text/plain");
res.writer.write(xreq.responseText);
}