http://www.cnblogs.com/zudn/archive/2011/09/13/2174413.html
然后这个也有说明如何获取解释的,
http://openhome.cc/Gossip/ServletJSP/GetReaderInputStream.html
http://yefeng.iteye.com/blog/315847
这篇文章更加经典,大家可以看看:
http://www.laruence.com/2009/09/26/1103.html
【勘误:还记得上次用reader获取的request content的内容吗?http://blog.youkuaiyun.com/cdnight/article/details/8867466那个获取出来的参数是错误的,因为用了readLine,而readLine是以回车换行作为结束符号(不包含回车换行,即:读取出来的数据没有/r/n等特殊符号),但是我们假如要解释mutipart-data的数据,必须要包含回车及换行,所以,这一篇文章我读取request正文的方法会作出调整】
这个是利用readLine读取的数据,没有回车及换行:
-----------------------------7dd2de2e380484Content-Disposition: form-data; name="imagefiles"; filename="testupload.txt"Content-Type: text/plainthis is the txt upload file,yes,you can select this file to upload for read.-----------------------------7dd2de2e380484Content-Disposition: form-data; name="fileNormal"; filename=""Content-Type: application/octet-stream-----------------------------7dd2de2e380484Content-Disposition: form-data; name="userName"myUserName-----------------------------7dd2de2e380484Content-Disposition: form-data; name="password"input passowrd-----------------------------7dd2de2e380484Content-Disposition: form-data; name="checkcode"checkcode is???-----------------------------7dd2de2e380484--
【下面是读取完整数据(含回车及换行)】
------WebKitFormBoundaryiM5Ip2MHB86rZWFC
Content-Disposition: form-data; name="imagefiles"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundaryiM5Ip2MHB86rZWFC
Content-Disposition: form-data; name="head"; filename="testupload.txt"
Content-Type: text/plain
this is the txt upload file,yes,you can select this file to upload for read.
------WebKitFormBoundaryiM5Ip2MHB86rZWFC
Content-Disposition: form-data; name="fileNormal"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundaryiM5Ip2MHB86rZWFC
Content-Disposition: form-data; name="userName"
myUserName
------WebKitFormBoundaryiM5Ip2MHB86rZWFC
Content-Disposition: form-data; name="password"
input passowrd
------WebKitFormBoundaryiM5Ip2MHB86rZWFC
Content-Disposition: form-data; name="checkcode"
checkcode is???
------WebKitFormBoundaryiM5Ip2MHB86rZWFC
Content-Disposition: form-data; name="none"
------WebKitFormBoundaryiM5Ip2MHB86rZWFC--
【为了让大家看得清楚,也为了让我可以照着这种格式来解析数据,下面我添加了回车换行等符号上去(自然换行是为了分开来看清楚,下面将当作是空字符)】
------WebKitFormBoundaryiM5Ip2MHB86rZWFC\r\n
Content-Disposition: form-data; name="imagefiles"; filename=""
\r\n
Content-Type: application/octet-stream
\r\n\r\n
\r\n
------WebKitFormBoundaryiM5Ip2MHB86rZWFC\r\n
Content-Disposition: form-data; name="head"; filename="testupload.txt"
\r\n
Content-Type: text/plain
\r\n\r\n
this is the txt upload file,yes,you can select this file to upload for read.
\r\n
------WebKitFormBoundaryiM5Ip2MHB86rZWFC\r\n
Content-Disposition: form-data; name="fileNormal"; filename=""
\r\n
Content-Type: application/octet-stream
\r\n\r\n
\r\n
------WebKitFormBoundaryiM5Ip2MHB86rZWFC\r\n
Content-Disposition: form-data; name="userName"
\r\n\r\n
myUserName
\r\n
------WebKitFormBoundaryiM5Ip2MHB86rZWFC\r\n
Content-Disposition: form-data; name="password"
\r\n\r\n
input passowrd
\r\n
------WebKitFormBoundaryiM5Ip2MHB86rZWFC\r\n
Content-Disposition: form-data; name="checkcode"
\r\n\r\n
checkcode is???
\r\n
------WebKitFormBoundaryiM5Ip2MHB86rZWFC\r\n
Content-Disposition: form-data; name="none"
\r\n\r\n
\r\n
------WebKitFormBoundaryiM5Ip2MHB86rZWFC--\r\n
那么我们得到了真实的数据及格式,就可以着手进行解析了:
首先要获取边界(boundary),类似于
------WebKitFormBoundaryiM5Ip2MHB86rZWFC
这种字符串,这个字符串是随机的由浏览器生成的,它就放在http协议的contentype里面,假如你打开gooel浏览器的调试工具,可以看到http报头含有contentType的数据,类似下面:
multipart/form-data; boundary=----WebKitFormBoundaryiM5Ip2MHB86rZWFC
看到没有?boundary后面的字符串就是边界了。值得注意的是mutipart-data的数据格式分隔符是:"--"+boundary+"\r\n",即boundary前面多了两个横线,而结束符号是:"--"+boundary+"--"+"\r\n",而每一个form data里面
这部分是参数的名称,假如是file上传控件的话,格式如下(没有选择文件的话,filename=""):
Content-Disposition: form-data; name="head"; filename="testupload.txt"
\r\n
Content-Type: text/plain
\r\n\r\n
this is the txt upload file,yes,you can select this file to upload for read.
\r\n
假如是普通参数的话,格式如下:
Content-Disposition: form-data; name="checkcode"
\r\n\r\n
checkcode is???
\r\n
知道了格式,知道了分隔符,那么就编写代码分析数据。
【补充:下面补充一下application/x-www-form-urlencoded及text/plain字符编码】
userName=myUserName&password=input+passowrd&checkcode=checkcode+is%3F%3F%3F&none=
【application/x-wwww-form-urlencoded的格式,没有换行及回车】
userName=myUserName
password=input passowrd
checkcode=checkcode is???
none=
【text/plain,包含换行及回车】
userName=myUserName
\r\n
password=input passowrd
\r\n
checkcode=checkcode is???
\r\n
none=
【text/plain,包含换行及回车(为了更加直观,将换行回车标示出来)】
经过修改以后的代码如下:
【主体文件代码】
package Easis.HTTP;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;
import java.util.regex.Pattern;
import Easis.HTTP.RequestFile;
import Easis.Common.StringUtil;
/**
* Created with IntelliJ IDEA.
* User: Administrator
* Date: 13-4-28
* Time: 下午9:03
* To change this template use File | Settings | File Templates.
*/
public class RequestEnhance {
private String _QueryString="";
private String _QueryString_Handled="";
private String _urlEncoding="";
public static String UrlEncoding_GBK="GBK";
public static String UrlEncoding_UTF8="utf-8";
public static String UrlEncoding_ISO_8559_1="iso-8559-1";
private HttpServletRequest _RequestCopy=null;
private Hashtable<String,RequestFile> _files=new Hashtable<String, RequestFile>();
private List<Integer> _RequestContent_IntegerList=new ArrayList<Integer>();
private StringBuilder _RequestContent=new StringBuilder();
private String _RequestContent_Str="";
private Hashtable<String,String> _paras_by_get=new Hashtable<String, String>();
private Hashtable<String,String> _paras_by_post=new Hashtable<String, String>();
public RequestEnhance(HttpServletRequest request,String URLEncoding){
_init(request,URLEncoding);
}
///这里要利用原始的requeset对象来获取querystring及getReader,
///注意:使用了getReander以后你就无法在
///原始的request对象再使用getParameter,getInputStream及getReader了,
///作为补偿,这里用getRequestContent来给大家访问最原始的request正文内容。
public RequestEnhance(HttpServletRequest request){
_init(request,"");
}
private void _init(HttpServletRequest request,String URLEncoding){
this._urlEncoding=URLEncoding;
this._RequestCopy=request;
try{
this._QueryString=request.getQueryString();
BufferedReader bf=request.getReader();
char[] carr_tmp=new char[4096];
int readSize=bf.read(carr_tmp);
String tttt="";
StringBuilder sb_charaters=new StringBuilder();
while(readSize>0){
for(int i_index=0;i_index<readSize;i_index++){
//this._RequestContent.append()
this._RequestContent.append(carr_tmp[i_index]);
}
// this._RequestContent.append(carr_tmp);
readSize=bf.read(carr_tmp);
tttt="";
}
this._RequestContent_Str=this._RequestContent.toString();
// this._RequestContent=sb2.toString();
this.InitParamByGet(this._QueryString);
//--处理表单数据,注意三种方式,“multipart/form-data”、“text/plain”、“application/x-www-form-urlencoded”
InitParamByPost();
String End11="";
}
catch (Exception e){
e.printStackTrace();
}
}
private void InitParamByGet(String URLQUERY) throws Exception{
String _str=URLQUERY;
if(URLQUERY==null||URLQUERY.trim().length()<1){}
else{
_str=URLQUERY.trim();
_str= URLQUERY.replaceAll("&+","&");
this._QueryString_Handled=_str;
String[] str_res= _str.split("&+");
if(str_res!=null&&str_res.length>0){
for(String tmp_mix:str_res){
String[] t_arr_key_value=tmp_mix.split("=",2);
if(t_arr_key_value!=null&&t_arr_key_value.length>1){
String s1= t_arr_key_value[0];
if(this._urlEncoding.length()<1){
String tKey=new String(t_arr_key_value[0].getBytes());
String tValue=new String(t_arr_key_value[1].getBytes());
this._paras_by_get.put(tKey,tValue);
}
else{
String tKey=new String(t_arr_key_value[0].getBytes(this._urlEncoding));
String tValue=new String(t_arr_key_value[1].getBytes(this._urlEncoding));
this._paras_by_get.put(tKey,tValue);
}
}
}}}}
public String getQueryString(){
return _QueryString;
}
public StringBuilder getRequestContent(){
return _RequestContent;
}
public String getQueryStringHandled(){
return _QueryString_Handled;
}
private void InitParamByPost() throws Exception {
String _contentType= this._RequestCopy.getContentType();
if(_contentType==null){return;}
if(_contentType.equals("text/plain")){
_initParam_text_plain();
}
else if(_contentType.equals("application/x-www-form-urlencoded")){
_initParam_application_x_www_form_urlencoded();
}
else if(_contentType.indexOf("multipart/form-data;")==0){
String _boundary=_contentType.replaceAll("multipart/form-data;","");
_boundary=_boundary.trim().replaceFirst("boundary=","");
_initParam_multipart_form_data(_boundary);
}
else{
throw new Exception("please make sure that you form data has a certain content type(one of \"text/plain\" or \"application/x-www-form-urlencoded\" or \"multipart/form-data\")");
}
}
///这个用于处理普通post文档,application/x-www-form-urlencoded,(浏览器默认的是这个无法传送文件,解释方式与url的一样)
private void _initParam_application_x_www_form_urlencoded() throws Exception{
String _str=this._RequestContent==null?"":this._RequestContent.toString().trim();
if(_str==null||_str.trim().length()<1){}
else{
_str= _str.replaceAll("&+","&");
this._QueryString_Handled=_str;
String[] str_res= _str.split("&+");
if(str_res!=null&&str_res.length>0){
for(String tmp_mix:str_res){
String[] t_arr_key_value=tmp_mix.split("=",2);
if(t_arr_key_value!=null&&t_arr_key_value.length>1){
String s1= t_arr_key_value[0];
if(this._urlEncoding.length()<1){
String tKey=new String(t_arr_key_value[0].getBytes());
String tValue=new String(t_arr_key_value[1].getBytes());
this._paras_by_post.put(tKey, tValue);
}
else{
String tKey=new String(t_arr_key_value[0].getBytes(this._urlEncoding));
String tValue=new String(t_arr_key_value[1].getBytes(this._urlEncoding));
this._paras_by_post.put(tKey,tValue);
}
}
}}}
}
///只有encryptype为multipart/form-data的表单会发送文档流。
private void _initParam_multipart_form_data(String _boundary){
String _end_boundary=_boundary+"--";
String _realBoundary="--"+_boundary;
//分割字符流
String[] arr_str=null;
if(this._RequestContent==null||this._RequestContent.toString().length()<1){
return;
}
arr_str=this._RequestContent.toString().split(_realBoundary);
if(arr_str==null||arr_str.length<1){
return;
}
if(arr_str.length==1){
if(arr_str[0].trim().equals("--\r\n")){
return;
}
}
//--遍历数组,分析参数或者文件内容
for(String str_item:arr_str){
String[] str_parts=str_item.split("\r\n\r\n",2);
if(str_parts==null||str_parts.length<1){
continue;
}
//--查看第一部分,看看是参数还是文件
if(str_parts[0]==null||str_parts[0].length()<1||StringUtil.ignoreCaseIndexOf(str_parts[0].trim(),"Content-Disposition: form-data;")!=0){
continue;
}
String keyInfos=str_parts[0].trim().substring("Content-Disposition: form-data;".length(),str_parts[0].trim().length());
//--判断是文件还是普通参数
if(keyInfos.contains("\r\n")){
//--文件
String __paraName="";
String __fileName="";
String __contentType="";
String[] arr_names_and_contentType=keyInfos.split("\r\n",2);
for (String ii_tkeys:arr_names_and_contentType[0].split(";")){
String[] for_key_value3=ii_tkeys.split("=",2);
if(for_key_value3[0].trim().toLowerCase().equals("name")){
__paraName=for_key_value3[1].replace("\"","");
}
else if(for_key_value3[0].trim().toLowerCase().equals("filename")){
__fileName=for_key_value3[1].replace("\"","");
}
}
String tmpstr1=arr_names_and_contentType[1].trim();
__contentType=tmpstr1.substring(tmpstr1.indexOf(":")+1,tmpstr1.length()).trim();
tmpstr1=str_parts[1];
String __fileContent=tmpstr1.substring(0,tmpstr1.lastIndexOf("\r\n"));
RequestFile rfile=new RequestFile();
rfile.FileName=__fileName;
rfile.FileContent=__fileContent;
rfile.ContentType=__contentType;
this._files.put(__paraName,rfile);
}
else{
//--普通参数
String[] arr_key_valuePairs=keyInfos.split("=",2);
String __paraName=arr_key_valuePairs[1].trim().replace("\"","");
String tmpstr1=str_parts[1];
String __paraValue=tmpstr1.substring(0,tmpstr1.lastIndexOf("\r\n"));
this._paras_by_post.put(__paraName,__paraValue);
}
}
}
///这个处理text/plain方式的字符串(不知道如何处理,假如是a1=value1&a2=value2,那么就变成:a1=value1a2=value2,处理不了。)
private void _initParam_text_plain(){
String theRequestContent=this._RequestContent.toString();
//--分割符号
String[] arr_parts=theRequestContent.split("\r\n");
if(arr_parts==null||arr_parts.length<1){
return;
}
for (String stritem:arr_parts){
String[] arr_key_value=stritem.split("=",2);
if(arr_key_value==null||arr_key_value.length<1){
continue;
}
String theKey=arr_key_value[0];
String theValue=arr_key_value[1];
this._paras_by_post.put(theKey,theValue);
}
}
public Hashtable<String,String> getUrlParas(){
return this._paras_by_get;
}
public Hashtable<String,RequestFile> getFiles(){
return this._files;
}
}
【需要引用到的两个文件】
【RequestFile】
package Easis.HTTP;
public class RequestFile {
public String FileName="";
public String FileContent="";
public String ContentType="";
}
【StringUtil--摘自网上不知名网站,不过不知道原作者。】
package Easis.Common;
public class StringUtil {
public static void main(String[] args) {
String t="aaaaaaaaaa<table></table>aaa<table></table>";
String s="<TABLE";
// t="";
System.out.println("length="+t.length());
System.out.println(t.indexOf(s,0));
System.out.println(ignoreCaseIndexOf(t, s,0));
System.out.println(t.lastIndexOf(s));
System.out.println(ignoreCaseLastIndexOf(t, s));
}
/**
* 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始,不区分大小。
*
* @param subject 被查找字符串。
* @param search 要查找的子字符串。
* @return 指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。
*/
public static int ignoreCaseIndexOf(String subject, String search) {
return ignoreCaseIndexOf(subject, search,-1);
}
/**
* 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始,不区分大小。
*
* @param subject 被查找字符串。
* @param search 要查找的子字符串。
* @param fromIndex 开始查找的索引位置。其值没有限制,如果它为负,则与它为 0 的效果同样:将查找整个字符串。
* 如果它大于此字符串的长度,则与它等于此字符串长度的效果相同:返回 -1。
* @return 指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。
*/
public static int ignoreCaseIndexOf(String subject, String search,
int fromIndex) {
//当被查找字符串或查找子字符串为空时,抛出空指针异常。
if (subject == null || search == null) {
throw new NullPointerException("输入的参数为空");
}
fromIndex = fromIndex < 0 ? 0 : fromIndex;
if (search.equals("")) {
return fromIndex >= subject.length() ? subject.length() : fromIndex;
}
int index1 = fromIndex;
int index2 = 0;
char c1;
char c2;
loop1: while (true) {
if (index1 < subject.length()) {
c1 = subject.charAt(index1);
c2 = search.charAt(index2);
} else {
break loop1;
}
while (true) {
if (isEqual(c1, c2)) {
if (index1 < subject.length() - 1
&& index2 < search.length() - 1) {
c1 = subject.charAt(++index1);
c2 = search.charAt(++index2);
} else if (index2 == search.length() - 1) {
return fromIndex;
} else {
break loop1;
}
} else {
index2 = 0;
break;
}
}
//重新查找子字符串的位置
index1 = ++fromIndex;
}
return -1;
}
/**
* 返回指定子字符串在此字符串中最右边出现处的索引。
*
* @param subject 被查找字符串。
* @param search 要查找的子字符。
* @return 在此对象表示的字符序列中最后一次出现该字符的索引;如果在该点之前未出现该字符,则返回 -1
*/
public static int ignoreCaseLastIndexOf(String subject, String search){
if(subject==null){
throw new NullPointerException("输入的参数为空");
}
else{
return ignoreCaseLastIndexOf(subject,search,subject.length());
}
}
/**
* 返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向查找。
* @param subject 被查找字符串 。
* @param search 要查找的子字符串。
* @param fromIndex 开始查找的索引。fromIndex 的值没有限制。如果它大于等于此字符串的长度,则与它小于此字符串长度减 1 的效果相同:将查找整个字符串。
* 如果它为负,则与它为 -1 的效果相同:返回 -1。
* @return 在此对象表示的字符序列(小于等于 fromIndex)中最后一次出现该字符的索引;
* 如果在该点之前未出现该字符,则返回 -1
*/
public static int ignoreCaseLastIndexOf(String subject, String search,
int fromIndex) {
//当被查找字符串或查找子字符串为空时,抛出空指针异常。
if (subject == null || search == null) {
throw new NullPointerException("输入的参数为空");
}
if (search.equals("")) {
return fromIndex >= subject.length() ? subject.length() : fromIndex;
}
fromIndex = fromIndex >= subject.length() ? subject.length() - 1 : fromIndex;
int index1 = fromIndex;
int index2 = 0;
char c1;
char c2;
loop1: while (true) {
if (index1 >= 0) {
c1 = subject.charAt(index1);
c2 = search.charAt(index2);
} else {
break loop1;
}
while (true) {
//判断两个字符是否相等
if (isEqual(c1, c2)) {
if (index1 < subject.length() - 1
&& index2 < search.length() - 1) {
c1 = subject.charAt(++index1);
c2 = search.charAt(++index2);
} else if (index2 == search.length() - 1) {
return fromIndex;
} else {
break loop1;
}
} else {
//在比较时,发现查找子字符串中某个字符不匹配,则重新开始查找子字符串
index2 = 0;
break;
}
}
//重新查找子字符串的位置
index1 = --fromIndex;
}
return -1;
}
/**
* 判断两个字符是否相等。
* @param c1 字符1
* @param c2 字符2
* @return 若是英文字母,不区分大小写,相等true,不等返回false;
* 若不是则区分,相等返回true,不等返回false。
*/
private static boolean isEqual(char c1,char c2){
// 字母小写 字母大写
if(((97<=c1 && c1<=122) || (65<=c1 && c1<=90))
&& ((97<=c2 && c2<=122) || (65<=c2 && c2<=90))
&& ((c1-c2==32) || (c2-c1==32))){
return true;
}
else if(c1==c2){
return true;
}
return false;
}
}
上面的类已经基本可以识别get,post及上传的文件了,调试基本通过,但是有一点非常重要的是,里面没有考虑字符编码问题,遇到汉字及特殊符号会乱码,下一篇来分析乱码原因及作出修正。