http://fins.javaeye.com/blog/261525?page=1#comments
這里所說的"模擬" 是指 : 在java中,使用類似 XMLHttpRequest 的方式來實現(xiàn)"同步/異步的HttpRequest"功能.
用 java 實現(xiàn)一個HTTPRequest 并不難. 通過 java.net 包內(nèi)提供的東東 可以很容易的實現(xiàn).
而且我們還有 apache 的 HttpClient 一類的組件可供我們使用 .
實現(xiàn) 異步的HTTPRequest 當然同樣簡單 , 再使用一個 Thread就ok了.
但是 使用上面的 方法 , 實現(xiàn)的往往只是一個 "異步的HTTPRequest"的功能而已,
使用方式上 還是 很 java的.
我在這里所要介紹的 則是使用很"ajax"的方式來實現(xiàn)"異步的HTTPRequest"的功能.
這個 AjaxHttpRequest 類 模仿了 XMLHttpRequest 的實現(xiàn),
從屬性 方法 使用方式上 都盡量的和 XMLHttpRequest 接近.
利用 AjaxHttpRequest 類 可以更自然的實現(xiàn) ajax請求的服務端代理 等工作.
=============================
當然 本文的技術研究的價值 也許遠遠大于 實用的價值. :P .
我先來舉一個例子 , AjaxHttpRequest 類的代碼&注釋 在后面附上 附件中也會提供該java文件.
大家注意看代碼注釋 ,我要說的東西更多的都在注釋里呢.
一個 用 java 來調用
google 翻譯服務的例子 (利用了后面附上的 AjaxHttpRequest 類) :
/**
* 利用這個AjaxHttpReuqest類來實現(xiàn) 對google translate服務的訪問 .
* 只演示了 "英-->漢"的翻譯.
* 返回的是JSON字符串,需要使用Json工具類進行轉換,這個不難 就不詳細舉例了.
*/
public static void testGoogleTranslate(String words,boolean async) throws IOException {
Map params=new HashMap();
params.put("q",words);
params.put("v","1.0");
params.put("langpair","en|zh-CN");
String url="http://ajax.googleapis.com/ajax/services/language/translate";
// 以上為 調用google翻譯服務所需要的參數(shù).
// 下面 是用java 來調用這個 翻譯服務.
// 在 AjaxHttpRequest 類的 幫助下 我們可以使用 類似操作瀏覽器XHR對象的方式來 實現(xiàn)該功能.
// 創(chuàng)建 AjaxHttpRequest對象 相當于 創(chuàng)建 XHR對象.
// 這里的final 主要是為了"使監(jiān)聽器內(nèi)部能夠調用ajax對象"而加的.
final AjaxHttpRequest ajax=new AjaxHttpRequest();
// 設置狀態(tài)監(jiān)聽器,類似 XHR對象的 onreadystatechange 屬性.
// 由于js 和java的本質區(qū)別 導致這里可能和 xhr有點不一樣,但是應該不難理解.
ajax.setReadyStateChangeListener(
// 監(jiān)聽器, 根據(jù)需求 實現(xiàn)onReadyStateChange方法即可.
new AjaxHttpRequest.ReadyStateChangeListener(){
public void onReadyStateChange() {
int readyState = ajax.getReadyState();
//判斷狀態(tài) 然后做出相應處理.
if (readyState==AjaxHttpRequest.STATE_COMPLETE){
System.out.println( ajax.getResponseText() );
}
}
}
);
// 這里就和 xhr 幾乎完全一樣了
ajax.open("POST", url, true);
//這里這個send方法接受的參數(shù)是一個map ,當然AjaxHttpRequest類也提供了string的方法
ajax.send(params);
}
有了上面的功能 當頁面中要使用google的翻譯服務時 就不用在引入google的那些js了.
也不用擔心 客戶端無法訪問google的情況了.
下面附上 AjaxHttpRequest類 的完整代碼 ( 附件中有下載 )
package com.fins.gt.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Level;
/**
* AjaxHttpRequest ,用java 模擬 瀏覽器的 XMLHttpRequest 對象.
* 目的是 用 操作瀏覽器中的XHR對象的 方式來處理java端的 http請求.
*
* @author fins
*
* 本類的實現(xiàn)借鑒了 cobra 組件的 org.lobobrowser.html.test.SimpleHttpRequest 類.
* 可以看作是對 SimpleHttpRequest 類的一個完善和補充.
*
* cobra 組件是一個 Java HTML Renderer & Parser,
* 官方網(wǎng)站 : http://lobobrowser.org/cobra.jsp
*/
public class AjaxHttpRequest {
// 對應 XMLHttpRequest 的5種狀態(tài).
public static final int STATE_UNINITIALIZED = 0;
public static final int STATE_LOADING = 1;
public static final int STATE_LOADED = 2;
public static final int STATE_INTERACTIVE = 3;
public static final int STATE_COMPLETE = 4;
// 默認的 userAgent
public static final String DEFAULT_USERAGENT = "Mozilla/4.0 (compatible; MSIE 6.0;) JavaAjax/1.0";
// 默認的 編碼
public static final String DEFAULT_AJAX_CHARSET = "UTF-8";
public static final String DEFAULT_HTTP_CHARSET = "ISO-8859-1";
// 默認的 HTTP method
public static final String DEFAULT_REQUEST_METHOD = "POST";
private int readyState;
private int status;
private String statusText;
private String responseHeaders;
private byte[] responseBytes;
private Map responseHeadersMap;
private Map requestHeadersMap;
private ReadyStateChangeListener readyStateChangeListener;
private boolean async;
private boolean sent;
private URLConnection connection;
private String userAgent = DEFAULT_USERAGENT;
private String postCharset = DEFAULT_AJAX_CHARSET;
private Proxy proxy;
private URL requestURL;
protected String requestMethod;
protected String requestUserName;
protected String requestPassword;
/////////////////////////////////
/////////////////////////////////
/**
* 構造方法. 自動添加 XMLHttpRequest 的一些缺省頭信息.
* 如果不需要這些頭信息,可在創(chuàng)建 AjaxHttpRequest 實例后,
* 通過 setRequestHeader/removeRequestHeader/removeAllRequestHeaders 方法
* 進行修改或移除.
*/
public AjaxHttpRequest() {
requestHeadersMap = new LinkedHashMap();
setRequestHeader("X-Requested-With", "XMLHttpRequest");
setRequestHeader("Accept",
"text/javascript, text/html, application/xml, application/json, text/xml, */*");
}
/**
* 類似 XMLHttpRequest 中的 readyState 屬性.
*/
public synchronized int getReadyState() {
return this.readyState;
}
/**
* 類似 XMLHttpRequest 中的 status 屬性.
*/
public synchronized int getStatus() {
return this.status;
}
/**
* 類似 XMLHttpRequest 中的 statusText 屬性.
*/
public synchronized String getStatusText() {
return this.statusText;
}
/**
* 類似 XMLHttpRequest 中的 setRequestHeader 方法.
*/
public void setRequestHeader(String key, String value) {
this.requestHeadersMap.put(key, value);
}
/**
* 類似 XMLHttpRequest 中的 open 方法.
*/
public void open(String method, String url, boolean async, String userName, String password)
throws IOException {
URL urlObj = createURL(null, url);
open(method, urlObj, async, userName, password);
}
/**
* 類似 XMLHttpRequest 中的 open 方法.
*/
public void open(final String method, final URL url, boolean async, final String userName,
final String password) throws IOException {
this.abort();
Proxy proxy = this.proxy;
URLConnection c = proxy == null || proxy == Proxy.NO_PROXY ? url.openConnection() : url
.openConnection(proxy);
synchronized (this) {
this.connection = c;
this.async = async;
this.requestMethod = method;
this.requestURL = url;
this.requestUserName = userName;
this.requestPassword = password;
}
this.changeState(AjaxHttpRequest.STATE_LOADING, 0, null, null);
}
/**
* 類似 XMLHttpRequest 中的 open 方法.
* 省略部分參數(shù)的形式.
*/
public void open(String url, boolean async) throws IOException {
open(DEFAULT_REQUEST_METHOD, url, async, null, null);
}
/**
* 類似 XMLHttpRequest 中的 open 方法.
* 省略部分參數(shù)的形式.
*/
public void open(String method, String url, boolean async) throws IOException {
open(method, url, async, null, null);
}
/**
* 類似 XMLHttpRequest 中的 send 方法.
* 支持發(fā)送 key-value 形式的數(shù)據(jù)集合(Map).
* 傳入map參數(shù), 自動轉換成string形式 并調用 send(String) 方法發(fā)送.
*/
public void send(Map parameters) throws IOException {
Iterator keyItr=parameters.keySet().iterator();
StringBuffer strb=new StringBuffer();
while (keyItr.hasNext()){
Object key = keyItr.next();
String keyStr = encode(key);
String valueStr = encode(parameters.get(key));
strb.append(keyStr).append("=").append(valueStr);
strb.append("&");
}
send(strb.toString());
}
/**
* 類似 XMLHttpRequest 中的 send 方法.
*/
public void send(final String content) throws IOException {
final URL url = this.requestURL;
if (url == null) {
throw new IOException("No URL has been provided.");
}
if (this.isAsync()) {
new Thread("AjaxHttpRequest-" + url.getHost()) {
public void run() {
try {
sendSync(content);
} catch (Throwable thrown) {
log(Level.WARNING, "send(): Error in asynchronous request on " + url, thrown);
}
}
}.start();
} else {
sendSync(content);
}
}
/**
* 類似 XMLHttpRequest 中的 getResponseHeader 方法.
*/
public synchronized String getResponseHeader(String headerName) {
return this.responseHeadersMap == null ? null : (String) this.responseHeadersMap.get(headerName);
}
/**
* 類似 XMLHttpRequest 中的 getAllResponseHeaders 方法.
*/
public synchronized String getAllResponseHeaders() {
return this.responseHeaders;
}
/**
* 類似 XMLHttpRequest 中的 responseText 屬性.
*/
public synchronized String getResponseText() {
byte[] bytes = this.responseBytes;
String encoding = getCharset(this.connection);
if (encoding==null){
encoding=getPostCharset();
}
if (encoding == null) {
encoding = DEFAULT_HTTP_CHARSET;
}
try {
return bytes == null ? null : new String(bytes, encoding);
} catch (UnsupportedEncodingException uee) {
log(Level.WARNING, "getResponseText(): Charset '" + encoding + "' did not work. Retrying with "
+ DEFAULT_HTTP_CHARSET + ".", uee);
try {
return new String(bytes, DEFAULT_HTTP_CHARSET);
} catch (UnsupportedEncodingException uee2) {
// Ignore this time
return null;
}
}
}
/**
* 類似 XMLHttpRequest 中的 responseXML 屬性.
* TODO : 此方法在java中 并不常使用, 而且需要兩個額外的包,兩個包不是所有的java環(huán)境都有的.
* 所以我(fins)把此方法注釋條了.
* 如果確實需要此方法 請自行取消該方法的注釋,并引入下面列出的類/接口.
* javax.xml.parsers.DocumentBuilderFactory;
* org.w3c.dom.Document;
*/
// public synchronized Document getResponseXML() {
// byte[] bytes = this.responseBytes;
// if (bytes == null) {
// return null;
// }
// InputStream in = new ByteArrayInputStream(bytes);
// try {
// return DocumentBuilderFactory.newInstance().newDocumentBuilder() .parse(in);
// }catch (Exception err) {
// log(Level.WARNING, "Unable to parse response as XML.", err); return null;
// }
// }
/**
* 類似 XMLHttpRequest 中的 responseBody 屬性.
* @deprecated 這個方法命名源自XMLHttpRequest中的responseBody屬性.
* 不過這個名字并不是好名字.建議使用 getResponseBytes 方法代替之.
*/
public synchronized byte[] getResponseBody() {
return this.getResponseBytes();
}
/**
* 類似 XMLHttpRequest 中的 responseBody 屬性.
* 建議使用此方法代替 getResponseBody 方法.
*/
public synchronized byte[] getResponseBytes() {
return this.responseBytes;
}
/**
* 與 XMLHttpRequest 中的 responseStream 屬性對應的方法 暫不提供.
* 因為1 不常用 2 通常可用 getResponseBytes 方法代替
*/
/**
* 類似 XMLHttpRequest 中的 onreadystatechange 屬性.
* 設置一個監(jiān)聽器,用來跟蹤HttpRequest狀態(tài)變化.
* 參數(shù)是一個 ReadyStateChangeListener 對象.
* ReadyStateChangeListener 是一個抽象類. 只需 實現(xiàn) onReadyStateChange方法即可.
*/
public void setReadyStateChangeListener(ReadyStateChangeListener listener) {
this.readyStateChangeListener = listener;
}
/**
* 中斷 Request 請求. 類似 XMLHttpRequest 中的 abort.
*/
public void abort() {
URLConnection c = null;
synchronized (this) {
c = this.getConnection();
}
if (c instanceof HttpURLConnection) {
((HttpURLConnection) c).disconnect();
} else if (c != null) {
try {
c.getInputStream().close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
///////////////////////////////////////////////
//// 以上 為 模擬 XMLHttpRequest 對象 相關的方法 ////
///////////////////////////////////////////////
///////////////////////////////////////////////
/**
* 返回 此次HttpRequest是否是"異步"的.
*/
public boolean isAsync() {
return async;
}
/**
* 返回 此次HttpRequest是否已經(jīng)發(fā)送.
* 已經(jīng)發(fā)送 且還沒有完全處理完此次發(fā)送的返回信息時,是不能再次發(fā)送的.
* 如果需要聯(lián)系發(fā)送請求, 請再另行創(chuàng)建一個 AjaxHttpRequest對象.
*/
public boolean hasSent() {
return sent;
}
protected void setSent(boolean sent) {
this.sent=sent;
}
/**
* 設置/取得 偽造的 userAgent 信息.通常不用理會.
* 很少有http服務會對此做嚴格的判斷.
*/
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
public String getUserAgent() {
return this.userAgent;
}
/**
* 取得/設置 默認的 AJAX編碼.AJAX請求都是UTF-8編碼 此屬性通常無需改變.
*/
public String getPostCharset() {
return this.postCharset;
}
public void setPostCharset(String postCharset) {
this.postCharset = postCharset;
}
/**
* 實現(xiàn)發(fā)送數(shù)據(jù)功能的方法,是整個類的核心.
* 我(fins)借鑒了 cobra 組件的 org.lobobrowser.html.test.SimpleHttpRequest 類中的同名方法.
* 略作改動.
*/
protected void sendSync(String content) throws IOException {
if (hasSent()){
log(Level.WARNING, "This AjaxHttpRequest Object has sent", null);
return ;
}
try {
URLConnection c;
synchronized (this) {
c = this.connection;
}
if (c==null){
log(Level.WARNING, "Please open AjaxHttpRequest first.", null);
return ;
}
setSent(true);
initConnectionRequestHeader(c);
int istatus;
String istatusText;
InputStream err;
if (c instanceof HttpURLConnection) {
HttpURLConnection hc = (HttpURLConnection) c;
String method = this.requestMethod==null?DEFAULT_REQUEST_METHOD:this.requestMethod;
method = method.toUpperCase();
hc.setRequestMethod(method);
if ("POST".equals(method) && content != null) {
hc.setDoOutput(true);
byte[] contentBytes = content.getBytes(this.getPostCharset());
hc.setFixedLengthStreamingMode(contentBytes.length);
OutputStream out = hc.getOutputStream();
try {
out.write(contentBytes);
} finally {
out.flush();
}
}
istatus = hc.getResponseCode();
istatusText = hc.getResponseMessage();
err = hc.getErrorStream();
} else {
istatus = 0;
istatusText = "";
err = null;
}
synchronized (this) {
this.responseHeaders = getConnectionResponseHeaders(c);
this.responseHeadersMap = c.getHeaderFields();
}
this.changeState(AjaxHttpRequest.STATE_LOADED, istatus, istatusText, null);
InputStream in = err == null ? c.getInputStream() : err;
int contentLength = c.getContentLength();
this.changeState(AjaxHttpRequest.STATE_INTERACTIVE, istatus, istatusText, null);
byte[] bytes = loadStream(in, contentLength == -1 ? 4096 : contentLength);
this.changeState(AjaxHttpRequest.STATE_COMPLETE, istatus, istatusText, bytes);
} finally {
synchronized (this) {
this.connection = null;
setSent(false);
}
}
}
/**
* 當狀態(tài)變化時 重新設置各種狀態(tài)值,并觸發(fā)狀態(tài)變化監(jiān)聽器.
*/
protected void changeState(int readyState, int status, String statusMessage, byte[] bytes) {
synchronized (this) {
this.readyState = readyState;
this.status = status;
this.statusText = statusMessage;
this.responseBytes = bytes;
}
if (this.readyStateChangeListener!=null){
this.readyStateChangeListener.onReadyStateChange();
}
}
/**
* 對字符串進行 URLEncoder 編碼.
*/
protected String encode(Object str){
try {
return URLEncoder.encode(String.valueOf(str), getPostCharset());
} catch (UnsupportedEncodingException e) {
return String.valueOf(str);
}
}
/**
* 將設置的 RequestHeader 真正的設置到鏈接請求中.
*/
protected void initConnectionRequestHeader(URLConnection c) {
c.setRequestProperty("User-Agent", this.getUserAgent());
Iterator keyItor = this.requestHeadersMap.keySet().iterator();
while (keyItor.hasNext()) {
String key = (String) keyItor.next();
String value = (String) this.requestHeadersMap.get(key);
c.setRequestProperty(key, value);
}
}
/**
* 以下 4個 方法 負責處理 requestHeader信息.
*/
public String getRequestHeader(String key) {
return (String) this.requestHeadersMap.get(key);
}
public String removeRequestHeader(String key) {
return (String)this.requestHeadersMap.remove(key);
}
public void removeAllRequestHeaders() {
this.requestHeadersMap.clear();
}
public Map getAllRequestHeaders() {
return this.requestHeadersMap;
}
public URLConnection getConnection() {
return connection;
}
public void setConnection(URLConnection connection) {
this.connection = connection;
}
public Proxy getProxy() {
return proxy;
}
public void setProxy(Proxy proxy) {
this.proxy = proxy;
}
/////////////////////////////////////////////////////////////////////
// 以下是 Static Method //////////////////////////////////////////////
// 這些靜態(tài)方法其實可以(應該)單獨提取出去的, ///////////////////////////////
// 不過為了讓這個程序結構簡單些 , 我(fins)就全部 all in one 了.///////////////////
// 這些方法也不都是我(fins)自己寫的 很多是copy 借鑒來的 功能都挺簡單的 就不詳細說明了 //
/////////////////////////////////////////////////////////////////////
public static void log(Level level, String msg, Throwable thrown) {
System.out.println(level.getName() + " : " + thrown.getMessage() + " ----- " + msg);
}
public static String getConnectionResponseHeaders(URLConnection c) {
int idx = 0;
String value;
StringBuffer buf = new StringBuffer();
while ((value = c.getHeaderField(idx)) != null) {
String key = c.getHeaderFieldKey(idx);
buf.append(key);
buf.append(": ");
buf.append(value);
idx++;
}
return buf.toString();
}
public static String getCharset(URLConnection connection) {
String contentType = connection==null?null:connection.getContentType();
if (contentType != null) {
StringTokenizer tok = new StringTokenizer(contentType, ";");
if (tok.hasMoreTokens()) {
tok.nextToken();
while (tok.hasMoreTokens()) {
String assignment = tok.nextToken().trim();
int eqIdx = assignment.indexOf('=');
if (eqIdx != -1) {
String varName = assignment.substring(0, eqIdx).trim();
if ("charset".equalsIgnoreCase(varName)) {
String varValue = assignment.substring(eqIdx + 1);
return unquote(varValue.trim());
}
}
}
}
}
return null;
}
public static String unquote(String text) {
if (text.startsWith("\"") && text.endsWith("\"")) {
return text.substring(1, text.length() - 2);
}
return text;
}
protected static URL createURL(URL baseUrl, String relativeUrl) throws MalformedURLException {
return new URL(baseUrl, relativeUrl);
}
protected static byte[] loadStream(InputStream in, int initialBufferSize) throws IOException {
if (initialBufferSize == 0) {
initialBufferSize = 1;
}
byte[] buffer = new byte[initialBufferSize];
int offset = 0;
for (;;) {
int remain = buffer.length - offset;
if (remain <= 0) {
int newSize = buffer.length * 2;
byte[] newBuffer = new byte[newSize];
System.arraycopy(buffer, 0, newBuffer, 0, offset);
buffer = newBuffer;
remain = buffer.length - offset;
}
int numRead = in.read(buffer, offset, remain);
if (numRead == -1) {
break;
}
offset += numRead;
}
if (offset < buffer.length) {
byte[] newBuffer = new byte[offset];
System.arraycopy(buffer, 0, newBuffer, 0, offset);
buffer = newBuffer;
}
return buffer;
}
///////////////////////////////////////////////////////////
// Listener Class /////////////////////////////////////////
///////////////////////////////////////////////////////////
/**
* 一個用來監(jiān)聽 HttpRequest狀態(tài) 的監(jiān)聽器. 是一個內(nèi)部靜態(tài)抽象類.
* 可以根據(jù)實際情況來自行重構(如 增加方法、變?yōu)楠毩⒌耐獠款惖?.
*/
public static abstract class ReadyStateChangeListener {
public abstract void onReadyStateChange();
}
///////////////////////////////////////////////////////////
// Test Method ////////////////////////////////////////////
///////////////////////////////////////////////////////////
/**
* 利用這個AjaxHttpReuqest類來實現(xiàn) 對google translate服務的訪問 .
* 只演示了 "英-->漢"的翻譯.
* 返回的是JSON字符串,需要使用Json工具類進行轉換,這個不難 就不詳細舉例了.
*/
public static void testGoogleTranslate(String words,boolean async) throws IOException {
Map params=new HashMap();
params.put("q",words);
params.put("v","1.0");
params.put("langpair","en|zh-CN");
String url="http://ajax.googleapis.com/ajax/services/language/translate";
// 以上為 調用google翻譯服務所需要的參數(shù).
// 下面 是用java 來調用這個 翻譯服務.
// 在 AjaxHttpRequest 類的 幫助下 我們可以使用 類似操作瀏覽器XHR對象的方式來 實現(xiàn)該功能.
// 創(chuàng)建 AjaxHttpRequest對象 相當于 創(chuàng)建 XHR對象.
// 這里的final 主要是為了"使監(jiān)聽器內(nèi)部能夠調用ajax對象"而加的.
final AjaxHttpRequest ajax=new AjaxHttpRequest();
// 設置狀態(tài)監(jiān)聽器,類似 XHR對象的 onreadystatechange 屬性.
// 由于js 和java的本質區(qū)別 導致這里可能和 xhr有點不一樣,但是應該不難理解.
ajax.setReadyStateChangeListener(
// 監(jiān)聽器, 根據(jù)需求 實現(xiàn)onReadyStateChange方法即可.
new AjaxHttpRequest.ReadyStateChangeListener(){
public void onReadyStateChange() {
int readyState = ajax.getReadyState();
//判斷狀態(tài) 然后做出相應處理.
if (readyState==AjaxHttpRequest.STATE_COMPLETE){
System.out.println( ajax.getResponseText() );
}
}
}
);
// 這里就和 xhr 幾乎完全一樣了
ajax.open("POST", url, true);
//這里這個send方法接受的參數(shù)是一個map ,當然AjaxHttpRequest類也提供了string的方法
ajax.send(params);
}
public static void main(String[] args) throws IOException {
testGoogleTranslate("Hello world!",false);
}
}
AjaxHttpRequest.zip (6.5 KB)
描述: AjaxHttpRequest類 的完整代碼
下載次數(shù): 281