越重要。Java開創了消費設備中小型的儲存容量的先河,它是用於開發手機、傳呼機及其他微型設備應用程序
的理想語言。
在本文中,我們將學習如何從一個J2ME客戶機上向服務器發送一條HTTP GET請求和一條HTTP POST請
求。雖然這只是一篇探討性質的文章,但是我還是假定讀者已經熟悉Java,J2ME,以及Java Midlets(MIDP應
用程序)的運作機制。我們將使用J2ME的MIDP簡表,並利用SUN的J2ME的無線應用程序開發工具包編譯、配
置和測試我們的應用程序。對於HTTP服務器,任何WWW地址都可以被訪問,但是默認時我們將使用一個簡單
的Java Servlet來返回我們的HTTP請求的細節。
如何使用J2ME客戶機向Web服務器和類似的支持HTTP的服務器發送HTTP請求呢?答案就是使用可在
Javax.microedition.io程序包中可找到的J2ME的網絡類。本文就想具體闡述這個問題。
本文概述∶
使用J2ME設計無線網絡應用程序
.發送一條超文本GET請求
.發送一條超文本POST請求
.使用J2ME進行無線網絡編程
Java的網絡編程能力是相當健壯的。Java 2標准版( J2SE)在Java.io和Java.Net程序包中定義了100多個接口
程序,類和異常。通過這些庫實現的功能是很強大的,但是這只適用於傳統的計算機系統,這些計算機系統有
強大的CPU處理能力,快速的內存和持久的數據儲存,但是這些在大多數的無線設備上是不現實的。因此,
J2ME定義了這些函數的子集,並提供了一套用於網絡和文件訪問的固定的程序包--- Javax.microedition.io程序
包。由於可移動設備種類繁多,這個程序包僅僅定義了一套接口,而為每個可移動設備供應廠商留下了實際的
應用程序接口實現。這就在可移植性和設備特定特征的應用中找到了一個最佳的平衡點。
定義在Javax.microedition.io類中的抽象網絡和文件輸入輸出框架稱為通用連接框架(Generic Connection
Framework,簡稱GCF)。GCF定義了一套有關抽象化的內容來描述不同的通信方法。最高級的抽象被稱作連
接(Connection),還聲明了六個接口(四個是直接的,兩個是間接的)。這七個接口就構成了J2ME的CLDC
的一部分,CLDC是大多數的能使用Java的無線設備使用的配置。設計這個配置的目的就是為所有的CLDC設備
(手提電話,雙向傳呼機,低檔的PDA等等)提供公用的網絡和文件輸入輸出能力。雖然GCF的目的是公用網絡
和文件輸入輸出框架,但是生產商並不要求實現GCF中聲明的所有的接口。有的廠家可以決定只支持socket連
接,而其它的廠家可以選擇只支持基於數據報的通信。為了促進跨越類似裝置的可移植性,MIDP規范要求所有
的MIDP設備實現HttpConnection接口。HttpConnection不是GCF的一部分,但是它是從GCF的一個接口
ContentConnection衍生出來的。我們將使用HttpConnection接口構造我們樣本應用程序。
發送一個HTTP GET請求
這一節將重點解釋程序代碼,在下一節中我們將只講述被用來發送HTTP請求並檢索由服務器返回的響應通
用連接框架接口和HttpConnection接口。創建MIDP用戶界面的程序代碼見附錄。
我們先要定義一個方法來放用於發送HTTP GET請求的代碼。因為這個方法中的有些操作有潛在的拋出
IOException的可能,所以我們將把這樣的意外(exception)拋給調用方法。
public String sendHttpGet( String url ) throws IOException {
HttpConnection hcon = null;
DataInputStream dis = null;
StringBuffer message = "";
try {
第一步是使用Connector類打開一個到服務器的連接,這是GCF的關鍵。我們將把這個連接強制轉換為需
要的類型,在本例中為HttpConnection類型。
hcon = ( HttpConnection ) Connector.open( url );
接下來,我們得到HttpConnection上的一個DataInputStream,允許我們一個字符一個字符的讀取服務器
的響應數據。
dis = new DataInputStream( hcon.openInputStream() );
使用DataInputStream的read ()方法,服務器響應的每個字符都被集中起來放入StringBuffer對象。
int ch;
while ( ( ch = dis.read() ) != -1 ) {
message = message.append( ( char ) ch );
}
最後,連接對象被淨空以保存資源,而信息從這個方法中返回。
} finally {
if ( hcon != null ) hcon.close();
if ( dis != null ) dis.close();
}//結束try/finally代碼段
return message.toString();
}//結束 sendGetRequest( String )
如何發送一個HTTP POST請求
你可以想象,發送一個HTTP POST請求的處理過程其實與發送一個GET請求非常地類似。我們將修改一個
現有命令,添加少量的新的命令,並添加一個來自通用連接框架的附加的對象和一個附加的StringBuffer對象把
POST請求體重的內容發送到服務器中。剩下的命令將保持不變。
復制我們剛才創建的sendHttpGet()方法,把它粘貼進同一個類文件,改名為sendHttpPost()。 現在,我
們將修改這個新方法來發送一個HTTP POST請求到服務器。 在方法的頂部添加兩個新的變量說明。 聲明一個
類型為DataOutputStream的變量和另一個String類型的變量。 我們將使用DataOutputStream對象把存在於字
符串變量中的POST請求體發送到服務器中。
DataOutputStream DOS = null;
String requestBody = null;
修改connector.open()命令包含另一個參數,指出連接將允許客戶端可以通過連接在服務器上讀和寫。
hcon = ( HttpConnection ) Connector.open( url, Connector.READ_WRITE );
設置HttpConnection對象使用的請求方法為POST(默認的方法是GET)。
hcon.setRequestMethod( HttpConnection.POST );
得到一個用於現有的HTTP連接的DataOutputStream對象。
DOS = hc.openDataOutputStream();
聲明一個字節數組並通過檢索一個來自requestBody字符串的字節數組初始化。 然後把DataOutputStream
的緩沖寫入字節數組內。
byte[] byteRequest = requestBody.getBytes();
for( int i = 0; i < byteRequest.length; i++ ) {
DOS.writeByte(byteRequest[i]);
}//結束for( int i = 0; i < byteRequest.length; i++ )
DOS.flush(); //包含本句,在某些設被上將可能會產生不可預期的結果
調用flush ()方法的意圖是發送已經寫入的數據到DataOutputStream的服務器的緩沖區中。 在某些電話
上,這個操作工作正常,在其他的電話上,它導致HTTP請求的Transfer - Encoding被設置為" chunked ",有一
些隨機字符被放到請求本身的前面和後面。那又怎樣處理這個問題呢?這個方法調用實際上是根本不需要的。
在接下來的一行中,服務器連接打開(通過openInputStream ()),將自動輸入緩沖區。因此,你最好不要調
用緩沖區的flush()方法。這個方法其余的部分保持不變,除了DataOutputStream對象必須在finally{}語句塊中關
閉。
} finally {
if ( hc != null ) hc.close();
if ( dis != null ) dis.close();
if ( DOS != null ) dis.close();
}//結束 try/finally
這就是所有的程序代碼!並請參見本文後附帶的程序代碼。
隨著可以使用國際互聯網絡和支持網絡的無線設備日益的增多普及,Java和J2ME的重要性也在不斷的變
大。因為HTTP協議是當前僅有的,被所有的遵從MIDP規范的設備支持的網絡協議,它也是用於開發無線網絡
應用程序的最好的候選者。
在本文中,我們探究了無線網絡編程的基本結構和幾個核心問題,我們看了如何調用兩個最常用的HTTP請
求方法:GET和POST。J2ME仍然在它的發展初期,並且無線設備也即將得到大面積的普及。所以,所有有志
投身於無線網絡編程中的開發者們將得到大展拳腳的好機會。
附錄:
/*
* HttpMidlet.Java
*/
import Javax.microedition.midlet.*;
import Javax.microedition.lcdui.*;
import Javax.microedition.io.*;
import Java.io.*;
public class HttpMidlet extends MIDlet implements CommandListener {
//使用默認的URL。用戶可以從圖形用戶接口改變這個值
private static String defaultURL = "http://localhost:8080/test/servlet/EchoServlet";
// 主MIDP 顯示
private Display myDisplay = null;
// 輸入URL的圖形用戶接口組件
private Form requestScreen;
private TextField requestFIEld;
// 用於提交請求的圖形用戶接口組件
private List list;
private String[] menuItems;
// 用於顯示服務器響應的圖形用戶接口組件
private Form resultScreen;
private StringItem resultFIEld;
//用於requestScreen的"send"按鈕
Command sendCommand;
// 用於requestScreen的"exit"按鈕
Command exitCommand;
// 用於requestScreen的"back"按鈕
Command backCommand;
public HttpMidlet(){
// 初始化圖形用戶接口組件
myDisplay = Display.getDisplay( this );
sendCommand = new Command( "SEND", Command.OK, 1 );
exitCommand = new Command( "EXIT", Command.OK, 1 );
backCommand = new Command( "BACK", Command.OK, 1 );
//顯示請求的URL
requestScreen = new Form( "Type in a URL:" );
requestField = new TextField( null, defaultURL, 100, TextFIEld.URL );
requestScreen.append( requestFIEld );
requestScreen.addCommand( sendCommand );
requestScreen.addCommand( exitCommand );
requestScreen.setCommandListener( this );
// 選擇想要的HTTP請求方法
menuItems = new String[] {"GET Request", "POST Request"};
list = new List( "Select an HTTP method:", List.IMPLICIT, menuItems, null );
list.setCommandListener( this );
// 先是從服務器上收到的信息
resultScreen = new Form( "Server Response:" );
resultScreen.addCommand( backCommand );
resultScreen.setCommandListener( this );
}//結束HttpMidlet()
public void startApp() {
myDisplay.setCurrent( requestScreen );
}//結束 startApp()
public void commandAction( Command com, Displayable disp ) {
// 當用戶點擊"send"按鈕
if ( com == sendCommand ) {
myDisplay.setCurrent( list );
} else if ( com == backCommand ) {
requestFIEld.setString( defaultURL );
myDisplay.setCurrent( requestScreen );
} else if ( com == exitCommand ) {
destroyApp( true );
notifyDestroyed();
}//結束 if ( com == sendCommand )
if ( disp == list && com == List.SELECT_COMMAND ) {
String result;
if ( list.getSelectedIndex() == 0 ) // 發送一個 GET 請求到服務器
result = sendHttpGet( requestFIEld.getString() );
else // 發送一個 POST 請求到服務器
result = sendHttpPost( requestFIEld.getString() );
resultFIEld = new StringItem( null, result );
resultScreen.append( resultFIEld );
myDisplay.setCurrent( resultScreen );
}//結束if ( dis == list && com == List.SELECT_COMMAND )
}//結束 commandAction( Command, Displayable )
private String sendHttpGet( String url )
{
HttpConnection hcon = null;
DataInputStream dis = null;
StringBuffer responseMessage = new StringBuffer();
try {
//使用READ權限的標准的 HttpConnection
hcon = ( HttpConnection )Connector.open( url );
//從HttpConnection取得一個 DataInputStream
dis = new DataInputStream( hcon.openInputStream() );
// 從服務器上取回響應
int ch;
while ( ( ch = dis.read() ) != -1 ) {
responseMessage.append( (char) ch );
}//結束while ( ( ch = dis.read() ) != -1 )
}
catch( Exception e )
{
e.printStackTrace();
responseMessage.append( "ERROR" );
} finally {
try {
if ( hcon != null ) hcon.close();
if ( dis != null ) dis.close();
} catch ( IOException ioe ) {
ioe.printStackTrace();
}//結束try/catch
}//結束try/catch/finally
return responseMessage.toString();
}//結束sendHttpGet( String )
private String sendHttpPost( String url )
{
HttpConnection hcon = null;
DataInputStream dis = null;
DataOutputStream DOS = null;
StringBuffer responseMessage = new StringBuffer();
// 請求體
String requeststring = "This is a POST.";
try {
// 使用讀寫權限的 HttpConnection
hcon = ( HttpConnection )Connector.open( url, Connector.READ_WRITE );
//設置請求方法為POST
hcon.setRequestMethod( HttpConnection.POST );
// 取得發送請求字符串的DataOutputStream
DOS = hcon.openDataOutputStream();
byte[] request_body = requeststring.getBytes();
// 發送請求字符串到服務器
for( int i = 0; i < request_body.length; i++ ) {
DOS.writeByte( request_body[i] );
}//結束 for( int i = 0; i < request_body.length; i++ )
// 取得做為接收服務器響應的DataInputStream
dis = new DataInputStream( hcon.openInputStream() );
// 從服務器上取回響應
int ch;
while( ( ch = dis.read() ) != -1 ) {
responseMessage.append( (char)ch );
}//結束while( ( ch = dis.read() ) != -1 ) {
}
catch( Exception e )
{
e.printStackTrace();
responseMessage.append( "ERROR" );
}
finally {
// 釋放輸入輸出流和HTTP連接
try {
if( hcon != null ) hcon.close();
if( dis != null ) dis.close();
if( dos != null ) DOS.close();
} catch ( IOException ioe ) {
ioe.printStackTrace();
}//結束try/catch
}//結束try/catch/finally
return responseMessage.toString();
}//結束sendHttpPost( String )
public void pauseApp() {
}//結束pauseApp()
public void destroyApp( boolean unconditional ) {
myDisplay = null;
requestScreen = null;
requestFIEld = null;
resultScreen = null;
resultFIEld = null;
}//結束 destroyApp( boolean )
}//結束HttpMidlet