MIDP 1.0规范规定,HTTP协议是必须实现的协议,为了能在不同类型的手机上移植,我们尽量采用HTTP作为网络连接的首选协议,这样还能重用服务器端的代码。本文介绍J2ME编程实践之联网开发。 
						
							
以下载一则新闻为例,一个完整的HTTP GET请求过程如下: 
首先,用户通过点击某个屏幕的命令希望阅读指定的一则新闻,在commandAction事件中,我们初始化HttpWaitUI和显示数据的NewsUI屏幕:
public void commandAction(Command c, Displayable d) {
  HttpWaitUI wait = new HttpWaitUI("http://192.168.0.1/news.do?id=1", new NewsUI());
  ControllerMIDlet.forward(wait);
  } 
  | 
NewsUI实现DataHandler接口并负责显示下载的数据:
public class NewsUI extends Form implements DataHandler {
  public void setData(DataInputStream input) throws IOException {
  String title = input.readUTF();
  Date date = new Date(input.readLong());
  String text = input.readUTF();
  append(new StringItem("Title", title));
  append(new StringItem("Date", date.toString()));
  append(text);
  }
  } 
  | 
服务器端只要以String, long, String的顺序依次写入DataOutputStream,MIDP客户端就可以通过DataInputStream依次取得相应的数据,完全不需要解析XML之类的文本,非常高效而且方便。
需要获得联网数据的屏幕只需实现DataHandler接口,并向HttpWaitUI传入一个URL即可复用上述代码,无须关心如何连接网络以及如何处理用户中断连接。
使用POST发送数据
以POST方式发送数据主要是为了向服务器发送较大量的客户端的数据,它不受URL的长度限制。POST请求将数据以URL编码的形式放在HTTP正文中,字段形式为fieldname=value,用&分隔每个字段。注意所有的字段都被作为字符串处理。实际上我们要做的就是模拟浏览器POST一个表单。以下是IE发送一个登陆表单的POST请求:
POST http://127.0.0.1/login.do HTTP/1.0
  Accept: image/gif, image/jpeg, image/pjpeg, */*
  Accept-Language: en-us,zh-cn;q=0.5
  Content-Type: application/x-www-form-urlencoded
  User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
  Content-Length: 28
  \r\n
  username=admin&password=1234 
  | 
要在MIDP应用程序中模拟浏览器发送这个POST请求,首先设置HttpConnection的请求方式为POST:
hc.setRequestMethod(HttpConnection.POST);
然后构造出HTTP正文:
byte[] data = "username=admin&password=1234".getBytes();
并计算正文长度,填入Content-Type和Content-Length:
hc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
  hc.setRequestProperty("Content-Length", String.valueOf(data.length)); 
  | 
然后打开OutputStream将正文写入:
OutputStream output = hc.openOutputStream();
output.write(data);
需要注意的是,数据仍需要以URL编码格式编码,由于MIDP库中没有J2SE中与之对应的URLEncoder类,因此,需要自己动手编写这个encode()方法,可以参考java.net.URLEncoder.java的源码。剩下的便是读取服务器响应,代码与GET一致,这里就不再详述。
使用multipart/form-data发送文件
如果要在MIDP客户端向服务器上传文件,我们就必须模拟一个POST multipart/form-data类型的请求,Content-Type必须是multipart/form-data。
以multipart/form-data编码的POST请求格式与application/x-www-form-urlencoded完全不同,multipart/form-data需要首先在HTTP请求头设置一个分隔符,例如ABCD:
hc.setRequestProperty("Content-Type", "multipart/form-data; boundary=ABCD");
然后,将每个字段用“--分隔符”分隔,最后一个“--分隔符--”表示结束。例如,要上传一个title字段"Today"和一个文件C:\1.txt,HTTP正文如下:
--ABCD
  Content-Disposition: form-data; name="title"
  \r\n
  Today
  --ABCD
  Content-Disposition: form-data; name="1.txt"; filename="C:\1.txt"
  Content-Type: text/plain
  \r\n
  <这里是1.txt文件的内容>
  --ABCD--
  \r\n 
  | 
请注意,每一行都必须以\r\n结束,包括最后一行。如果用Sniffer程序检测IE发送的POST请求,可以发现IE的分隔符类似于——7d4a6d158c9,这是IE产生的一个随机数,目的是防止上传文件中出现分隔符导致服务器无法正确识别文件起始位置。我们可以写一个固定的分隔符,只要足够复杂即可。
发送文件的POST代码如下:
String[] props = ... // 字段名
  String[] values = ... // 字段值
  byte[] file = ... // 文件内容
  String BOUNDARY =  "---------------------------7d4a6d158c9"; // 分隔符
  StringBuffer sb = new StringBuffer();
  // 发送每个字段:
  for(int i=0; i sb = sb.append("--");
  sb = sb.append(BOUNDARY);
  sb = sb.append("\r\n");
  sb = sb.append("Content-Disposition: form-data;  name=\""+ props[i] + "\"\r\n\r\n");
  sb = sb.append(URLEncoder.encode(values[i]));
  sb = sb.append("\r\n");
  }
  // 发送文件:
  sb = sb.append("--");
  sb = sb.append(BOUNDARY);
  sb = sb.append("\r\n");
  sb = sb.append("Content-Disposition: form-data;  name=\"1\"; filename=\"1.txt\"\r\n");
  sb = sb.append("Content-Type: application/octet-stream\r\n\r\n");
  byte[] data = sb.toString().getBytes();
  byte[] end_data = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
  // 设置HTTP头:
  hc.setRequestProperty("Content-Type", MULTIPART_FORM_DATA + "; boundary=" + BOUNDARY);
  hc.setRequestProperty("Content-Length",  String.valueOf(data.length + file.length + end_data.length));
  // 输出:
  output = hc.openOutputStream();
  output.write(data);
  output.write(file);
  output.write(end_data);
  // 读取服务器响应:
  // TODO... 
  | 
使用Cookie保持Session
通常服务器使用Session来跟踪会话。Session的简单实现就是利用Cookie。当客户端第一次连接服务器时,服务器检测到客户端没有相应的Cookie字段,就发送一个包含一个识别码的Set-Cookie字段。在此后的会话过程中,客户端发送的请求都包含这个Cookie,因此服务器能够识别出客户端曾经连接过服务器。
要实现与浏览器一样的效果,MIDP应用程序必须也能识别Cookie,并在每个请求头中包含此Cookie。
在处理每次连接的响应中,我们都检查是否有Set-Cookie这个头,如果有,则是服务器第一次发送的Session ID,或者服务器认为会话超时,需要重新生成一个Session ID。如果检测到Set-Cookie头,就将其保存,并在随后的每次请求中附加它:
String session = null;
  String cookie = hc.getHeaderField("Set-Cookie");
  if(cookie!=null) {
  int n = cookie.indexOf(';');
  session = cookie.substring(0, n);
  } 
  | 
使用Sniffer程序可以捕获到不同的Web服务器发送的Session。WebLogic Server 7.0返回的Session如下:
Set-Cookie: JSESSIONID=CxP4FMwOJB06XCByBWfwZBQ0IfkroKO2W7FZpkLbmWsnERuN5u2L!-1200402410; path=/
而Resin 2.1返回的Session则是: 
Set-Cookie: JSESSIONID= aTMCmwe9F5j9;  path=/  | 
运行ASP.Net的IIS返回的Session:
Set-Cookie: ASPSESSIONIDQATSASQB=GNGEEJIDMDFCMOOFLEAKDGGP;  path=/ 
  | 
我们无须关心Session ID的内容,服务器自己会识别它。我们只需在随后的请求中附加上这个Session ID即可:
if(session!=null)
  hc.setRequestProperty("Cookie", session); 
  | 
对于URL重写来保持Session的方法,在PC客户端可能很有用,但是,由于MIDP程序很难分析出URL中有用的Session信息,因此,不推荐使用这种方法。
查看本文来源