package org.voovan.http.message;
import org.voovan.http.message.packet.Cookie;
import org.voovan.http.message.packet.Part;
import org.voovan.tools.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
/**
* Http 报文解析类
* @author helyho
*
* Voovan Framework.
* WebSite: https://github.com/helyho/Voovan
* Licence: Apache v2 License
*/
public class HttpParser {
private static final String HTTP_PROTOCOL = "HTTP/";
private static final String FL_METHOD = "FL_Method";
private static final String FL_PATH = "FL_Path";
private static final String FL_PROTOCOL = "FL_Protocol";
private static final String FL_VERSION = "FL_Version";
private static final String FL_STATUS = "FL_Status";
private static final String FL_STATUSCODE = "FL_StatusCode";
private static final String FL_QUERY_STRING = "FL_QueryString";
private static final String HEAD_CONTENT_ENCODING = "Content-Encoding";
private static final String HEAD_CONTENT_TYPE = "Content-Type";
private static final String HEAD_TRANSFER_ENCODING = "Transfer-Encoding";
private static final String HEAD_CONTENT_LENGTH = "Content-Length";
private static final String HEAD_COOKIE = "Cookie";
private static final String BODY_PARTS = "Body_Parts";
private static final String BODY_VALUE = "Body_Value";
private static final String BODY_FILE = "Body_File";
/**
* 私有构造函数
* 该类无法被实例化
*/
private HttpParser(){
}
/**
* 解析协议信息
* http 头的第一行
* @param protocolLine
* Http 报文协议行字符串
* @throws UnsupportedEncodingException
*/
private static Map<String, Object> parseProtocol(String protocolLine) throws UnsupportedEncodingException{
Map<String, Object> protocol = new HashMap<String, Object>();
//请求方法
String[] lineSplit = protocolLine.split(" ");
if(protocolLine.indexOf(HTTP_PROTOCOL)!=0){
protocol.put(FL_METHOD, lineSplit[0]);
//请求路径和请求串
String[] pathSplit = lineSplit[1].split("\\?");
protocol.put(FL_PATH, pathSplit[0]);
if(pathSplit.length==2){
protocol.put(FL_QUERY_STRING, pathSplit[1]);
}
//协议和协议版本
String[] protocolSplit= lineSplit[2].split("/");
protocol.put(FL_PROTOCOL, protocolSplit[0]);
protocol.put(FL_VERSION, protocolSplit[1]);
}else if(protocolLine.indexOf(HTTP_PROTOCOL)==0){
String[] protocolSplit= lineSplit[0].split("/");
protocol.put(FL_PROTOCOL, protocolSplit[0]);
protocol.put(FL_VERSION, protocolSplit[1]);
protocol.put(FL_STATUS, lineSplit[1]);
String statusCode = "";
for(int i=2;i<lineSplit.length;i++){
statusCode += lineSplit[i]+" ";
}
statusCode = TString.removeSuffix(statusCode);
protocol.put(FL_STATUSCODE, statusCode);
}
return protocol;
}
/**
* 解析 HTTP Header属性行
* @param propertyLine
* Http 报文头属性行字符串
* @return
*/
private static Map<String,String> parsePropertyLine(String propertyLine){
Map<String,String> property = new HashMap<String, String>();
String[] propertySplit = propertyLine.split(": ");
if(propertySplit.length==2){
String propertyName = propertySplit[0];
String properyValue = propertySplit[1];
property.put(propertyName, properyValue);
}
return property;
}
/**
* 解析字符串中的所有等号表达式成 Map
* @param str
* 等式表达式
* @return 等号表达式 Map
*/
public static Map<String, String> getEqualMap(String str){
Map<String, String> equalMap = new HashMap<String, String>();
String[] searchedStrings = TString.searchByRegex(str,"([^ ;,]+=[^ ;,]+)");
for(String groupString : searchedStrings){
//这里不用 split 的原因是有可能等号后的值字符串中出现等号
String[] equalStrings = new String[2];
int equalCharIndex= groupString.indexOf("=");
equalStrings[0] = groupString.substring(0,equalCharIndex);
equalStrings[1] = groupString.substring(equalCharIndex+1,groupString.length());
if(equalStrings.length==2){
String key = equalStrings[0];
String value = equalStrings[1];
if(value.startsWith("\"") && value.endsWith("\"")){
value = value.substring(1,value.length()-1);
}
equalMap.put(key, value);
}
}
return equalMap;
}
/**
* 获取HTTP 头属性里等式的值
* 可以从字符串 Content-Type: multipart/form-data; boundary=ujjLiiJBznFt70fG1F4EUCkIupn7H4tzm
* 直接解析出boundary的值.
* 使用方法:getPerprotyEqualValue(packetMap,"Content-Type","boundary")获得ujjLiiJBznFt70fG1F4EUCkIupn7H4tzm
* @param propertyName 属性名
* @param valueName 属性值
* @return
*/
private static String getPerprotyEqualValue(Map<String,Object> packetMap,String propertyName,String valueName){
Object propertyValueObj = packetMap.get(propertyName);
if(propertyValueObj == null){
return null;
}
String propertyValue = propertyValueObj.toString();
Map<String, String> equalMap = getEqualMap(propertyValue);
return equalMap.get(valueName);
}
/**
* 处理消息的Cookie
* @param packetMap 报文 MAp 对象
* @param cookieLine Http 头中 Cookie 报文行
*/
@SuppressWarnings("unchecked")
private static void parseCookie(Map<String, Object> packetMap,String cookieLine){
if(!packetMap.containsKey(HEAD_COOKIE)){
packetMap.put(HEAD_COOKIE, new ArrayList<Map<String, String>>());
}
List<Map<String, String>> cookies = (List<Map<String, String>>) packetMap.get(HEAD_COOKIE);
//解析 Cookie 行
Map<String, String>cookieMap = getEqualMap(cookieLine);
//响应 response 的 cookie 形式 一个cookie 一行
if(cookieLine.contains("Set-Cookie")){
//处理非键值的 cookie 属性
if(cookieLine.toLowerCase().contains("httponly")){
cookieMap.put("httponly", "");
}
if(cookieLine.toLowerCase().contains("secure")){
cookieMap.put("secure", "");
}
cookies.add(cookieMap);
}
//请求 request 的 cookie 形式 多个cookie 一行
else if(cookieLine.contains(HEAD_COOKIE)){
for(Entry<String,String> cookieMapEntry: cookieMap.entrySet()){
HashMap<String, String> cookieOneMap = new HashMap<String, String>();
cookieOneMap.put(cookieMapEntry.getKey(), cookieMapEntry.getValue());
cookies.add(cookieOneMap);
}
}
}
/**
* 处理 body 段
* 判断是否使用 GZIP 压缩,如果使用则解压缩后返回,如果没有压缩则直接返回
* @param packetMap
* @param contentBytes
* @return
* @throws IOException
*/
private static byte[] dealBodyContent(Map<String, Object> packetMap,byte[] contentBytes) throws IOException{
byte[] bytesValue;
if(contentBytes.length == 0 ){
return contentBytes;
}
//是否支持 GZip
boolean isGZip = packetMap.get(HEAD_CONTENT_ENCODING)==null ? false : packetMap.get(HEAD_CONTENT_ENCODING).toString().contains("gzip");
//如果是 GZip 则解压缩
if(isGZip && contentBytes.length>0){
bytesValue =TZip.decodeGZip(contentBytes);
} else {
bytesValue = contentBytes;
}
return TObject.nullDefault(bytesValue,new byte[0]);
}
/**
* 解析 HTTP 报文
* 解析称 Map 形式,其中:
* 1.protocol 解析成 key/value 形式
* 2.header 解析成 key/value 形式
* 3.cookie 解析成 List[Map[String,String]] 形式
* 3.part 解析成 List[Map[Stirng,Object]](因为是递归,参考 HTTP 解析形式) 形式
* 5.body 解析成 key=BODY_VALUE 的Map 元素
* @param byteBufferChannel 输入流
* @param timeOut 读取超时时间参数
* @return 解析后的 Map
* @throws IOException IO 异常
*/
public static Map<String, Object> parser(ByteBufferChannel byteBufferChannel, int timeOut) throws IOException{
Map<String, Object> packetMap = new HashMap<String, Object>();
int headerLength = 0;
boolean isBodyConent = false;
int lineNum = 0;
//按行遍历HTTP报文
for(String currentLine = byteBufferChannel.readLine();
currentLine!=null;
currentLine = byteBufferChannel.readLine()){
currentLine = currentLine.trim();
lineNum++;
//空行分隔处理,遇到空行标识下面有可能到内容段
if(currentLine.isEmpty()){
isBodyConent = true;
}
//解析 HTTP 协议行
if(!isBodyConent && currentLine.contains("HTTP") && lineNum==1){
packetMap.putAll(parseProtocol(currentLine));
}
//处理 cookie 和 header
if(!isBodyConent){
if(currentLine.contains(HEAD_COOKIE)){
parseCookie(packetMap,currentLine);
}else{
packetMap.putAll(parsePropertyLine(currentLine));
}
}
//解析 HTTP 请求 body
if(isBodyConent){
String contentType =packetMap.get(HEAD_CONTENT_TYPE)==null ? "" : packetMap.get(HEAD_CONTENT_TYPE).toString();
String transferEncoding = packetMap.get(HEAD_TRANSFER_ENCODING)==null ? "" : packetMap.get(HEAD_TRANSFER_ENCODING).toString();
//1. 解析 HTTP 的 POST 请求 body part
if(contentType.contains("multipart/form-data")){
//用来保存 Part 的 list
List<Map<String, Object>> bodyPartList = new ArrayList<Map<String, Object>>();
//取boundary 用于 part 内容分段
String boundary = "--" + getPerprotyEqualValue(packetMap, HEAD_CONTENT_TYPE, "boundary");
ByteBuffer boundaryEnd = ByteBuffer.allocate(2);
while(true) {
//等待数据
if (!byteBufferChannel.waitData(boundary.getBytes(), timeOut)) {
throw new IOException("Http Parser read data error");
}
int index = byteBufferChannel.indexOf(boundary.getBytes("UTF-8"));
//跳过 boundary
byteBufferChannel.shrink((index + boundary.length()) * -1);
//取 boundary 结尾字符
boundaryEnd.clear();
byteBufferChannel.readHead(boundaryEnd);
//确认 boundary 结尾字符, 如果是"--" 则标识报文结束
if (Arrays.equals(boundaryEnd.array(), "--".getBytes())) {
break;
}
byte[] mark = "\r\n\r\n".getBytes();
//等待数据
if (!byteBufferChannel.waitData(mark, timeOut)) {
throw new IOException("Http Parser read data error");
}
int partHeadEndIndex = byteBufferChannel.indexOf(mark);
//Part 头读取
ByteBuffer partHeadBuffer = ByteBuffer.allocateDirect(partHeadEndIndex + 4);
byteBufferChannel.readHead(partHeadBuffer);
//构造新的 Bytebufer 递归解析
ByteBufferChannel partByteBufferChannel = new ByteBufferChannel(partHeadEndIndex + 4); //包含换行符
partByteBufferChannel.writeEnd(partHeadBuffer);
Map<String, Object> partMap = parser(partByteBufferChannel, timeOut);
TByteBuffer.release(partHeadBuffer);
partByteBufferChannel.release();
String fileName = getPerprotyEqualValue(partMap, "Content-Disposition", "filename");
//解析 Part 报文体
//重置 index
index = -1;
if (fileName == null) {
//等待数据
if (!byteBufferChannel.waitData(boundary.getBytes(), timeOut)) {
throw new IOException("Http Parser read data error");
}
index = byteBufferChannel.indexOf(boundary.getBytes("UTF-8"));
ByteBuffer bodyByteBuffer = ByteBuffer.allocate(index - 2);
int readSize = byteBufferChannel.readHead(bodyByteBuffer);
index = index - readSize;
partMap.put(BODY_VALUE, bodyByteBuffer.array());
} else {
String fileExtName = TFile.getFileExtension(fileName);
fileExtName = fileExtName.equals("") ? ".tmp" : fileExtName;
//拼文件名
String localFileName = TFile.assemblyPath(TFile.getTemporaryPath(),
"org.voovan.webserver",
"upload",
"VOOVAN_" + System.currentTimeMillis() + "." + fileExtName);
int count = 0;
do{
index = byteBufferChannel.indexOf(boundary.getBytes("UTF-8"));
int length = index == -1 ? byteBufferChannel.size() : (index - 2);
byteBufferChannel.saveToFile(localFileName, index - 2);
count++;
} while (!byteBufferChannel.waitData(boundary.getBytes(), 100) && count*100 < timeOut);
if(index == -1){
new File(localFileName).delete();
throw new IOException("Http Parser read data error");
}else{
partMap.remove(BODY_VALUE);
partMap.put(BODY_FILE, localFileName.getBytes());
}
}
//加入bodyPartList中
bodyPartList.add(partMap);
}
//将存有多个 part 的 list 放入packetMap
packetMap.put(BODY_PARTS, bodyPartList);
}
//2. 解析 HTTP 响应 body 内容段的 chunked
else if("chunked".equals(transferEncoding)){
ByteBufferChannel chunkedByteBufferChannel = new ByteBufferChannel(3);
String chunkedLengthLine = "";
while(chunkedLengthLine!=null){
// 等待数据
if(!byteBufferChannel.waitData("\r\n".getBytes(), timeOut)){
throw new IOException("Http Parser read data error");
}
String chunkedLengthLine1 = byteBufferChannel.readLine();
chunkedLengthLine = chunkedLengthLine1.trim();
if("0".equals(chunkedLengthLine)){
break;
}
if(chunkedLengthLine.isEmpty()){
continue;
}
int chunkedLength = 0;
//读取chunked长度
try {
chunkedLength = Integer.parseInt(chunkedLengthLine, 16);
}catch(Exception e){
System.out.println(chunkedLengthLine);
e.printStackTrace();
break;
}
// 等待数据
if(!byteBufferChannel.waitData(chunkedLength, timeOut)){
throw new IOException("Http Parser read data error");
}
int readSize = 0;
if(chunkedLength > 0) {
//按长度读取chunked内容
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(chunkedLength);
readSize = byteBufferChannel.readHead(byteBuffer);
if(readSize != chunkedLength){
throw new IOException("Http Parser read chunked data error");
}
//如果多次读取则拼接
chunkedByteBufferChannel.writeEnd(byteBuffer);
TByteBuffer.release(byteBuffer);
}
//跳过换行符号
byteBufferChannel.shrink(-2);
}
byte[] value = dealBodyContent(packetMap, chunkedByteBufferChannel.array());
chunkedByteBufferChannel.release();
packetMap.put(BODY_VALUE, value);
}
//3. HTTP(请求和响应) 报文的内容段中Content-Length 提供长度,按长度读取 body 内容段
else if(packetMap.containsKey(HEAD_CONTENT_LENGTH)){
int contentLength = Integer.parseInt(packetMap.get(HEAD_CONTENT_LENGTH).toString());
// 等待数据
if(!byteBufferChannel.waitData(contentLength, timeOut)){
throw new IOException("Http Parser read data error");
}
ByteBuffer byteBuffer = ByteBuffer.allocate(contentLength);
byteBufferChannel.readHead(byteBuffer);
byte[] contentBytes = byteBuffer.array();
byte[] value = dealBodyContent(packetMap, contentBytes);
packetMap.put(BODY_VALUE, value);
}
//4. 容错,没有标识长度则默认读取全部内容段
else if(packetMap.get(BODY_VALUE)==null || packetMap.get(BODY_VALUE).toString().isEmpty()){
byte[] contentBytes = byteBufferChannel.array();
if(contentBytes!=null && contentBytes.length>0){
contentBytes = Arrays.copyOf(contentBytes, contentBytes.length);
}
byte[] value = dealBodyContent(packetMap, contentBytes);
packetMap.put(BODY_VALUE, value);
}
break;
}else{
headerLength = headerLength+currentLine.length()+2;
}
}
byteBufferChannel.clear();
return packetMap;
}
/**
* 解析报文成 HttpRequest 对象
* @param byteBufferChannel 输入字节流
* @param timeOut 读取超时时间参数
* @return 返回请求报文
* @throws IOException IO 异常
*/
@SuppressWarnings("unchecked")
public static Request parseRequest(ByteBufferChannel byteBufferChannel, int timeOut) throws IOException{
Map<String, Object> parsedPacket = parser(byteBufferChannel, timeOut);
//如果解析的Map为空,则直接返回空
if(parsedPacket==null || parsedPacket.isEmpty()){
return null;
}
Request request = new Request();
//填充报文到请求对象
Set<Entry<String, Object>> parsedItems= parsedPacket.entrySet();
for(Entry<String, Object> parsedPacketEntry: parsedItems){
String key = parsedPacketEntry.getKey();
switch (key) {
case FL_METHOD:
request.protocol().setMethod(parsedPacketEntry.getValue().toString());
break;
case FL_PROTOCOL:
request.protocol().setProtocol(parsedPacketEntry.getValue().toString());
break;
case FL_QUERY_STRING:
request.protocol().setQueryString(parsedPacketEntry.getValue().toString());
break;
case FL_VERSION:
request.protocol().setVersion(Float.valueOf(parsedPacketEntry.getValue().toString()));
break;
case FL_PATH:
request.protocol().setPath(parsedPacketEntry.getValue().toString());
break;
case HEAD_COOKIE:
List<Map<String, String>> cookieMap = (List<Map<String, String>>)parsedPacket.get(HEAD_COOKIE);
//遍历 Cookie,并构建 Cookie 对象
for(Map<String,String> cookieMapItem : cookieMap){
Cookie cookie = Cookie.buildCookie(cookieMapItem);
request.cookies().add(cookie);
}
cookieMap.clear();
break;
case BODY_VALUE:
byte[] value = (byte[])(parsedPacketEntry.getValue());
request.body().write(value);
break;
case BODY_PARTS:
List<Map<String, Object>> parsedParts = (List<Map<String, Object>>)(parsedPacketEntry.getValue());
//遍历 part List,并构建 Part 对象
for(Map<String, Object> parsedPartMap : parsedParts){
Part part = new Part();
//将 part Map中的值,并填充到新构建的 Part 对象中
for(Entry<String, Object> parsedPartMapItem : parsedPartMap.entrySet()){
//填充 Value 中的值到 body 中
if(parsedPartMapItem.getKey().equals(BODY_VALUE)){
part.body().chaneToBytes((byte[])parsedPartMapItem.getValue());
} if(parsedPartMapItem.getKey().equals(BODY_FILE)){
String filePath = new String((byte[])parsedPartMapItem.getValue());
part.body().changeToFile(filePath);
} else {
//填充 header
String partedHeaderKey = parsedPartMapItem.getKey();
String partedHeaderValue = parsedPartMapItem.getValue().toString();
part.header().put(partedHeaderKey, partedHeaderValue);
if("Content-Disposition".equals(partedHeaderKey)){
//对Content-Disposition中的"name=xxx"进行处理,方便直接使用
Map<String, String> contentDispositionValue = HttpParser.getEqualMap(partedHeaderValue);
part.header().putAll(contentDispositionValue);
}
}
}
request.parts().add(part);
parsedPartMap.clear();
}
break;
default:
request.header().put(parsedPacketEntry.getKey(), parsedPacketEntry.getValue().toString());
break;
}
}
parsedPacket.clear();
return request;
}
/**
* 解析报文成 HttpResponse 对象
* @param byteBufferChannel 输入字节流
* @param timeOut 读取超时时间参数
* @return 返回响应报文
* @throws IOException IO 异常
*/
@SuppressWarnings("unchecked")
public static Response parseResponse(ByteBufferChannel byteBufferChannel, int timeOut) throws IOException{
Response response = new Response();
Map<String, Object> parsedPacket = parser(byteBufferChannel, timeOut);
//填充报文到响应对象
Set<Entry<String, Object>> parsedItems= parsedPacket.entrySet();
for(Entry<String, Object> parsedPacketEntry: parsedItems){
String key = parsedPacketEntry.getKey();
switch (key) {
case FL_PROTOCOL:
response.protocol().setProtocol(parsedPacketEntry.getValue().toString());
break;
case FL_VERSION:
response.protocol().setVersion(Float.parseFloat(parsedPacketEntry.getValue().toString()));
break;
case FL_STATUS:
response.protocol().setStatus(Integer.parseInt(parsedPacketEntry.getValue().toString()));
break;
case FL_STATUSCODE:
response.protocol().setStatusCode(parsedPacketEntry.getValue().toString());
break;
case HEAD_COOKIE:
List<Map<String, String>> cookieMap = (List<Map<String, String>>)parsedPacketEntry.getValue();
//遍历 Cookie,并构建 Cookie 对象
for(Map<String,String> cookieMapItem : cookieMap){
Cookie cookie = Cookie.buildCookie(cookieMapItem);
response.cookies().add(cookie);
}
break;
case BODY_VALUE:
response.body().write((byte[])parsedPacketEntry.getValue());
break;
default:
response.header().put(parsedPacketEntry.getKey(), parsedPacketEntry.getValue().toString());
break;
}
}
parsedPacket.clear();
return response;
}
}