继续我们的Tomcat ,我们完成了 参数封装成map,下面我们处理,Cookie 和session
我们先引入两个类Session,和SessionFacade(也是门面模式)
public class JxdSession implements HttpSession {
private String sessionid;
private long creationTime;
private boolean valid;
private Map<String,Object> attributes = new ConcurrentHashMap<>();
@Override
public long getCreationTime() {
return this.creationTime;
}
@Override
public String getId() {
return this.sessionid;
}
@Override
public long getLastAccessedTime() {
return 0;
}
@Override
public ServletContext getServletContext() {
return null;
}
@Override
public void setMaxInactiveInterval(int i) {
}
@Override
public int getMaxInactiveInterval() {
return 0;
}
@Override
public HttpSessionContext getSessionContext() {
return null;
}
@Override
public Object getAttribute(String s) {
return this.attributes.get(s);
}
@Override
public Object getValue(String s) {
return this.attributes.get(s);
}
@Override
public Enumeration<String> getAttributeNames() {
return Collections.enumeration(this.attributes.keySet());
}
@Override
public String[] getValueNames() {
return new String[0];
}
@Override
public void setAttribute(String name, Object value) {
this.attributes.put(name, value);
}
@Override
public void putValue(String name, Object value) {
this.attributes.put(name, value);
}
@Override
public void removeAttribute(String name) {
this.attributes.remove(name);
}
@Override
public void removeValue(String s) {
}
@Override
public void invalidate() {
this.valid = false;
}
@Override
public boolean isNew() {
return false;
}
public void setCreationTime(long creationTime) {
this.creationTime = creationTime;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
public void setId(String sessionid) {
this.sessionid = sessionid;
}
}
public class SessionFacade implements HttpSession{
private HttpSession session;
public SessionFacade(HttpSession session) {
this.session = session;
}
@Override
public long getCreationTime() {
return session.getCreationTime();
}
@Override
public String getId() {
return session.getId();
}
@Override
public long getLastAccessedTime() {
return session.getLastAccessedTime();
}
@Override
public ServletContext getServletContext() {
return session.getServletContext();
}
@Override
public void setMaxInactiveInterval(int interval) {
session.setMaxInactiveInterval(interval);
}
@Override
public int getMaxInactiveInterval() {
return session.getMaxInactiveInterval();
}
@Override
public HttpSessionContext getSessionContext() {
return session.getSessionContext();
}
@Override
public Object getAttribute(String name) {
return session.getAttribute(name);
}
@Override
public Object getValue(String name) {
return session.getValue(name);
}
@Override
public Enumeration<String> getAttributeNames() {
return session.getAttributeNames();
}
@Override
public String[] getValueNames() {
return session.getValueNames();
}
@Override
public void setAttribute(String name, Object value) {
session.setAttribute(name, value);
}
@Override
public void putValue(String name, Object value) {
session.putValue(name, value);
}
@Override
public void removeAttribute(String name) {
session.removeAttribute(name);
}
@Override
public void removeValue(String name) {
session.removeValue(name);
}
@Override
public void invalidate() {
session.invalidate();
}
@Override
public boolean isNew() {
return session.isNew();
}
}
下面开始解析,在JxdRequest 完整的实现方法,注意:parseCookieHeaderTow这个方法是我用自己方式去写的 我写完以后看源码,发现 确实 还是源码写的比较好,你们可以参考一下。
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.Principal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class JxdRequest implements HttpServletRequest {
private InputStream input;
private SocketInputStream sis;
private String uri;
InetAddress address;
int port;
protected HashMap<String, String> headers = new HashMap<>();
protected Map<String, String[]> parameters = new ConcurrentHashMap<>();
HttpRequestLine requestLine = new HttpRequestLine();
String sessionid;
SessionFacade sessionFacade;
Cookie[] cookies;
HttpSession session;
private boolean parsed = false;
private String queryString;
public void parse(Socket socket) {
try {
input = socket.getInputStream();
this.sis = new SocketInputStream(this.input, 2048);
parseConnection(socket);
this.sis.readRequestLine(requestLine);
parseRequestLine();//解析数据
parseHeaders();
} catch (IOException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
}
}
private void parseRequestLine() {
int question = requestLine.indexOf("?");
if (question >= 0) {
queryString = new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1);
uri = new String(requestLine.uri, 0, question);
//处理参数串中带有jsessionid的情况
int sessionIndex = uri.indexOf(DefaultHeaders.JSESSIONID_NAME);
if (sessionIndex >= 0) {
sessionid = uri.substring(sessionIndex + DefaultHeaders.JSESSIONID_NAME.length());
uri = uri.substring(0, sessionIndex);
}
} else {
queryString = null;
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
//因为post 请求又是会带sessionid
int sessionIndex = uri.indexOf(DefaultHeaders.JSESSIONID_NAME);
if (sessionIndex >= 0) {
sessionid = uri.substring(sessionIndex + DefaultHeaders.JSESSIONID_NAME.length());
uri = uri.substring(0, sessionIndex);
}
}
}
private void parseConnection(Socket socket) {
address = socket.getInetAddress();
port = socket.getPort();
}
private void parseHeaders() throws IOException, ServletException {
while (true) {
HttpHeader header = new HttpHeader();
sis.readHeader(header);
//表示读取完毕
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
} else {
throw new ServletException("httpProcessor.parseHeaders.colon");
}
}
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
// 设置相应的请求头
if (name.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
headers.put(name, value);
} else if (name.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
headers.put(name, value);
} else if (name.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
headers.put(name, value);
} else if (name.equals(DefaultHeaders.HOST_NAME)) {
headers.put(name, value);
} else if (name.equals(DefaultHeaders.CONNECTION_NAME)) {
headers.put(name, value);
} else if (name.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
headers.put(name, value);
} else if (name.equals(DefaultHeaders.COOKIE_NAME)) {
headers.put(name, value);
//处理cookie和session
Cookie[] cookiearr = parseCookieHeader(value);
//parseCookieHeaderTow(0,value.toCharArray());
this.cookies = cookiearr;
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("jsessionid")) {
this.sessionid = cookies[i].getValue();
}
}
} else {
headers.put(name, value);
}
}
}
/**
* 这个方式是cookieValue = sessionid=123;username=jxd
* 以;分成两节 每一节依次进行解析 =号前变得是key 后边的是value
* 然后把后一节的数据覆盖到cookieValue=username=jxd
* 继续解析
*/
public Cookie[] parseCookieHeader(String cookieValue) {
if ((cookieValue == null) || (cookieValue.length() < 1)) return (new Cookie[0]);
ArrayList<Cookie> cookieal = new ArrayList<>();
while (cookieValue.length() > 0) {
int semicolon = cookieValue.indexOf(';');
if (semicolon < 0)
semicolon = cookieValue.length();
if (semicolon == 0)
break;
String token = cookieValue.substring(0, semicolon);
if (semicolon < cookieValue.length())
cookieValue = cookieValue.substring(semicolon + 1);
else
cookieValue = "";
try {
int equalsIndex = token.indexOf('=');
if (equalsIndex > 0) {
String name = token.substring(0, equalsIndex).trim();
String value = token.substring(equalsIndex + 1).trim();
cookieal.add(new Cookie(name, value));
}
} catch (Throwable e) {
}
}
return ((Cookie[]) cookieal.toArray(new Cookie[cookieal.size()]));
}
/**
*这个方法是 我自己想到的另一种解析方法,和tomcat 比起来确实 有点差距
*
*/
private static void parseCookieHeaderTow(int i,char[] strArr){
char[] key = new char[strArr.length];
char[] value = new char[strArr.length];
int keyindex =0;
for (; strArr[i] != '=' ; i++) {
key[keyindex++] = strArr[i];
}
int valueindex =0;
i++;//跳过一位
for ( ; i<strArr.length&&strArr[i] != ';' ; i++) {
value[ valueindex++] = strArr[i];
}
System.out.println(new String(key,0,keyindex)+" key");
System.out.println(new String(value,0,valueindex)+" value");
if(i< strArr.length){
parseCookieHeaderTow(++i,strArr);
}
}
protected void parseParameters() {
String encoding = getCharacterEncoding();
System.out.println(encoding);
if (encoding == null) {
encoding = "ISO-8859-1";
}
String qString = getQueryString();
System.out.println("getQueryString:" + qString);
if (qString != null) {
byte[] bytes;
try {
bytes = qString.getBytes(encoding);
parseParameters(this.parameters, bytes, encoding);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
;
}
}
String contentType = getContentType();
if (contentType == null)
contentType = "";
int semicolon = contentType.indexOf(';');
if (semicolon >= 0) {
contentType = contentType.substring(0, semicolon).trim();
} else {
contentType = contentType.trim();
}
if ("POST".equals(getMethod()) && (getContentLength() > 0) && "application/x-www-form-urlencoded".equals(contentType)) {
try {
int max = getContentLength();
int len = 0;
byte buf[] = new byte[getContentLength()];
ServletInputStream is = getInputStream();
while (len < max) {
int next = is.read(buf, len, max - len);
if (next < 0) {
break;
}
len += next;
}
is.close();
if (len < max) {
throw new RuntimeException("Content length mismatch");
}
parseParameters(this.parameters, buf, encoding);
} catch (UnsupportedEncodingException ue) {
} catch (IOException e) {
throw new RuntimeException("Content read fail");
}
}
}
/**
* parseParameters 这个方法 举例 例如data name=jxd&age=18
* 他会从0位置遍历到最后一位 同时设置两个指针一个ix 一个ox
* ix 就是从char 数组0位开始遍历到最后
* ox 是一个查找指针 ,当遇到 = 或者&时候,进行取舍,=前边表示key ,& 前边表示value 如果没有& 表示结尾
* 要注意一个细节 当遇到 =或者& 时候会把ox 赋值0 ,但是为啥要 default: data[ox++] = c;
* ,当我们遇到第一个=的时候 下一个是value=jxd 那么之前那个key(name) 就没有用了,因为已经赋值到map里边了 ,所以读取
* jxd 时候覆盖掉前边的nam,然后 ox指针因为是从0开始 等遇到jxd后变边的&时候
* 照样能把value=jxd 取出来 这样 一个数组 就能完成了,虽然data 原数组被改变 这样看似不太好但是,但是节省空间 不然你就要2个数组才能完成,一个取值一个
* 放值 不得不说设计的很巧妙 。
*/
public void parseParameters(Map<String, String[]> map, byte[] data, String encoding)
throws UnsupportedEncodingException {
if (parsed)
return;
System.out.println(data);
if (data != null && data.length > 0) {
int pos = 0;
int ix = 0;
int ox = 0;
String key = null;
String value = null;
while (ix < data.length) {
byte c = data[ix++];
switch ((char) c) {
case '&':
value = new String(data, 0, ox, encoding);
if (key != null) {
putMapEntry(map, key, value);
key = null;
}
ox = 0;
break;
case '=':
key = new String(data, 0, ox, encoding);
ox = 0;
break;
case '+':
data[ox++] = (byte) ' ';
break;
case '%':
data[ox++] = (byte) ((convertHexDigit(data[ix++]) << 4)
+ convertHexDigit(data[ix++]));
break;
default:
data[ox++] = c;
}
}
//The last value does not end in '&'. So save it now.
//最后一个参数没有&结尾
if (key != null) {
value = new String(data, 0, ox, encoding);
putMapEntry(map, key, value);
}
}
parsed = true;
}
private byte convertHexDigit(byte b) {
if ((b >= '0') && (b <= '9')) return (byte) (b - '0');
if ((b >= 'a') && (b <= 'f')) return (byte) (b - 'a' + 10);
if ((b >= 'A') && (b <= 'F')) return (byte) (b - 'A' + 10);
return 0;
}
/**
* 这个方式是 存入map集合 因为有的value值对应多个key 所以是数组形式存储value
*/
private static void putMapEntry(Map<String, String[]> map, String name, String value) {
String[] newValues = null;
String[] oldValues = (String[]) map.get(name);
if (oldValues == null) {
newValues = new String[1];
newValues[0] = value;
} else {
newValues = new String[oldValues.length + 1];
System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
newValues[oldValues.length] = value;
}
map.put(name, newValues);
}
public String getUri() {
return uri;
}
public String getSessionId() {
return this.sessionid;
}
//如果有存在的session,直接返回,如果没有,创建一个新的session
@Override
public HttpSession getSession(boolean create) {
if (sessionFacade != null)
return sessionFacade;
if (sessionid != null) {
session = JxdHttpConnector.sessions.get(sessionid);
if (session != null) {
sessionFacade = new SessionFacade(session);
return sessionFacade;
} else {
session = JxdHttpConnector.createSession();
sessionFacade = new SessionFacade(session);
return sessionFacade;
}
} else {
session = JxdHttpConnector.createSession();
sessionFacade = new SessionFacade(session);
sessionid = session.getId();
return sessionFacade;
}
}
@Override
public String getAuthType() {
return null;
}
@Override
public Cookie[] getCookies() {
return this.cookies;
}
@Override
public long getDateHeader(String s) {
return 0;
}
@Override
public String getHeader(String s) {
return null;
}
@Override
public Enumeration<String> getHeaders(String s) {
return null;
}
@Override
public Enumeration<String> getHeaderNames() {
return null;
}
@Override
public int getIntHeader(String s) {
return 0;
}
@Override
public String getMethod() {
return new String(this.requestLine.method, 0, this.requestLine.methodEnd);
}
@Override
public String getPathInfo() {
return null;
}
@Override
public String getPathTranslated() {
return null;
}
@Override
public String getContextPath() {
return null;
}
@Override
public String getQueryString() {
return this.queryString;
}
@Override
public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
}
@Override
public int getContentLength() {
return Integer.parseInt(headers.get(DefaultHeaders.CONTENT_LENGTH_NAME));
}
@Override
public long getContentLengthLong() {
return 0;
}
@Override
public String getContentType() {
return headers.get(DefaultHeaders.CONTENT_TYPE_NAME);
}
@Override
public ServletInputStream getInputStream() throws IOException {
return this.sis;
}
@Override
public String getParameter(String name) {
parseParameters();
String values[] = parameters.get(name);
if (values != null)
return (values[0]);
else
return (null);
}
@Override
public Enumeration<String> getParameterNames() {
parseParameters();
return (Collections.enumeration(parameters.keySet()));
}
@Override
public String[] getParameterValues(String name) {
parseParameters();
String values[] = (String[]) parameters.get(name);
if (values != null)
return (values);
else
return null;
}
@Override
public Map<String, String[]> getParameterMap() {
parseParameters();
return (this.parameters);
}
public static void main(String[] args) {
String str = "sessionid=123;username=jxd";
char[] strArr = str.toCharArray();
parseCookieHeaderTow(0,strArr);
}
}