继续我们的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); } }