深入刨析tomcat 之---第2篇,解决第3章bug 页面不显示内容http://localhost:8080/servlet/ModernServlet?userName=zhangyantao&password=1234
writedby 张艳涛7月2日,
在学习第4张的过程中,发现了前一篇文章写的是关于1,2张的bug不用设置response响应头,需要在servlet的service()方法里面写是错误想
servlet是tomcat的容器内的居住者,主要功能在于交互式地浏览和生成数据,生成动态Web内容,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。那么这个servlet实际上是基于http协议的响应体的实现,可以这么说,servlet本质上是对于一个http请求的响应,servlet程序段service()方法,根据http协议来回复客户端的http请求, 对于一个http 请求,回复的内容在哪里? 在response的body里面,如果你是一个网页那么,Content-Type="text/html", response.setContentType("text/html");其实如果你不设置content_type浏览器也能只能的显示为网页;突然发现浏览器很傻_B,如果你返回给浏览器为json格式那么你就给一个Content-Type:
out = new PrintWriter(socket.getOutputStream()); if (filePath.endsWith("jpg") || filePath.endsWith("ico")) { in = new FileInputStream(filePath);//读图片 ByteArrayOutputStream baos = new ByteArrayOutputStream(); int i=0; while (((i = in.read()) != -1)) { baos.write(i); } byte[] array= baos.toByteArray(); out.println("HTTP/1.1 200 OK"); out.println("Server: Molly"); out.println("Content-Type: image/jpeg"); out.println("Content-Length: "+array.length); out.println(""); out.flush(); socket.getOutputStream().write(array,0,array.length);//写入图片 System.out.println(Thread.currentThread().getName());
这样子就成功了,
上一篇文章对于response头信息的设置实现
对于xxx.servlet文件中
response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>");
如果out.println("内容"),这个out是socket.getoutputstream的对象,肯定不行,那么就直接发送了,如果保证在头对象之后是这个设计的精髓
这个out对象是
ResponseStream
因为response.getWriter();内容为
HttpResponse类中 @Override public PrintWriter getWriter() throws IOException { ResponseStream newStream = new ResponseStream(this); newStream.setCommit(false); OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding()); writer = new ex03.pyrmont.connector.ResponseWriter(osr); return writer; }
那么久确定了out.println("内容"),的out是ResponseStream ,那么会通过printwrite调用write()
public void write(byte b[], int off, int len) throws IOException { if (closed) throw new IOException("responseStream.write.closed"); int actual = len; if ((length > 0) && ((count + len) >= length)) actual = length - count; response.write(b, off, actual); count += actual; if (actual < len) throw new IOException("responseStream.write.count"); }
那么
HttpResponse类中 public void write(int b) throws IOException{ if (bufferCount>=buffer.length) { flushBuffer(); } buffer[bufferCount++] = ((byte) b); contentCount++; } public void write(byte b[],int off,int len) throws IOException{ if (len==0) { return; } if (len<=buffer.length-bufferCount) { System.arraycopy(b,off,buffer,bufferCount,len); bufferCount += len; contentCount += len; return; } flushBuffer(); /** 将buffercount置为0;清空buffer*/ int iterations= len/buffer.length; /**len太大 看一共有几倍 */ int leftoverStart=iterations*buffer.length; int leftoverLen = len - leftoverStart; for (int i = 0; i < iterations; i++) { write(b, off + (i * buffer.length), buffer.length); } if(leftoverLen>0){ write(b, off + leftoverStart, leftoverLen); } }
看代码,层层分析之后能发现,最终调用的给buffer数组赋值,只有到flush之后才会发送网络数据,那么,要做的就是先调用sendHeadrs在out.flush();
ServletProcessor类中 servlet = ((Servlet) myClass.newInstance()); HttpRequestFacade httpRequestFacade = new HttpRequestFacade(request); HttpResponseFacade httpResponseFacade = new HttpResponseFacade(response); servlet.service(httpRequestFacade,httpResponseFacade); ((HttpResponse) response).finishResponse();
接着
public void finishResponse() throws IOException { sendHeaders(); if (writer != null) {// writer = new ex03.pyrmont.connector.ResponseWriter(osr);,这里是 writer.flush(); writer.close(); } }
代码和上面的描述一致的
那么,看sendheaders代码
protected void sendHeaders() throws IOException{ if (isCommitted()) { return; } OutputStreamWriter osr = null; try { osr = new OutputStreamWriter(getStream(), getCharacterEncoding()); } catch (UnsupportedEncodingException e) { osr = new OutputStreamWriter(getStream()); } final PrintWriter outputWriter = new PrintWriter(osr); // Send the "Status:" header outputWriter.print((this.request.getProtocol())); outputWriter.print(" "); outputWriter.print(status); if(message!=null){ outputWriter.print(" "); outputWriter.print(message); } outputWriter.print("\r\n"); if(getContentType()!=null){ outputWriter.print(" "); outputWriter.print("Content-Type: " + getContentType() + "\r\n"); } if (getContentLength() >= 0) { outputWriter.print("Content-Length: " + getContentLength() + "\r\n"); } synchronized (headers) { Iterator names = headers.keySet().iterator(); while (names.hasNext()) { String name = (String) names.next(); ArrayList values = (ArrayList) headers.get(name); Iterator items = values.iterator(); while (items.hasNext()) { String value = (String) items.next(); outputWriter.print(name); outputWriter.print(": "); outputWriter.print(value); outputWriter.println("\r\n"); } } } synchronized (cookies) { Iterator items = cookies.iterator(); while (items.hasNext()) { Cookie cookie = (Cookie) items.next(); outputWriter.print(CookieTools.getCookieHeaderName(cookie)); outputWriter.print(": "); outputWriter.print(CookieTools.getCookieHeaderValue(cookie)); outputWriter.print("\r\n"); } } outputWriter.print("\r\n"); outputWriter.flush(); committed = true; }
这个outwriter直接取值socket.getoutputstream方法
以上代码全来深入刨析tocmat第3章
那么第三章的bug是什么呢?,浏览器执行
http://localhost:8080/servlet/ModernServlet?userName=zhangyantao&password=1234
运行程序页面没反应!!!
给程序打断点也发送sendHeaders,这颗怎么办呢?chrome f12 看不出来什么呢,只能看人才的了
package com.zyt.tomcat.ex03.startup; import java.io.*; import java.net.Socket; public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("localhost", 8080); System.out.println(socket); PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); InputStream inputStream = socket.getInputStream(); pw.println("GET /servlet/ModernServlet?userName=zhangyantao&password=1234l HTTP/1.1"); pw.println("Host: localhost:8080"); pw.println("Connection: keep-alive\n" + "sec-ch-ua: \" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\"\n" + "sec-ch-ua-mobile: ?0\n" + "Upgrade-Insecure-Requests: 1\n" + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\n" + "Sec-Fetch-Site: none\n" + "Sec-Fetch-Mode: navigate\n" + "Sec-Fetch-User: ?1\n" + "Sec-Fetch-Dest: document\n" + "Accept-Encoding: gzip, deflate, br\n" + "Accept-Language: zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6\n" + "Cookie: Webstorm-33fda740=2357c3d1-7421-48dd-b63d-9dfa59664905; Idea-d43876f9=54507cfb-5164-4924-84ad-1ddc6e24306e"); pw.println("\n\r"); pw.flush(); int b; while ((b=inputStream.read())!=-1){ System.out.print(((char) b)); } } }
写了一个client,注意必须是\n\r否则不行
打印的内容为
D:\dev\JDK8\bin\java.exe -javaagent:D:\dev\ideaIU-2020.3.1.win\lib\idea_rt.jar=58985:D:\dev\ideaIU-2020.3.1.win\bin -Dfile.encoding=UTF-8 -classpath D:\dev\JDK8\jre\lib\charsets.jar;D:\dev\JDK8\jre\lib\deploy.jar;D:\dev\JDK8\jre\lib\ext\access-bridge-64.jar;D:\dev\JDK8\jre\lib\ext\cldrdata.jar;D:\dev\JDK8\jre\lib\ext\dnsns.jar;D:\dev\JDK8\jre\lib\ext\jaccess.jar;D:\dev\JDK8\jre\lib\ext\jfxrt.jar;D:\dev\JDK8\jre\lib\ext\localedata.jar;D:\dev\JDK8\jre\lib\ext\nashorn.jar;D:\dev\JDK8\jre\lib\ext\sunec.jar;D:\dev\JDK8\jre\lib\ext\sunjce_provider.jar;D:\dev\JDK8\jre\lib\ext\sunmscapi.jar;D:\dev\JDK8\jre\lib\ext\sunpkcs11.jar;D:\dev\JDK8\jre\lib\ext\zipfs.jar;D:\dev\JDK8\jre\lib\javaws.jar;D:\dev\JDK8\jre\lib\jce.jar;D:\dev\JDK8\jre\lib\jfr.jar;D:\dev\JDK8\jre\lib\jfxswt.jar;D:\dev\JDK8\jre\lib\jsse.jar;D:\dev\JDK8\jre\lib\management-agent.jar;D:\dev\JDK8\jre\lib\plugin.jar;D:\dev\JDK8\jre\lib\resources.jar;D:\dev\JDK8\jre\lib\rt.jar;D:\wksp_study\designbook\target\classes;D:\repo\junit\junit\4.11\junit-4.11.jar;D:\repo\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\repo\org\openjdk\jol\jol-core\0.10\jol-core-0.10.jar;D:\repo\javax\servlet\servlet-api\2.3\servlet-api-2.3.jar;D:\wksp_study\designbook\lib\jaas.jar;D:\wksp_study\designbook\lib\jnet.jar;D:\wksp_study\designbook\lib\jsse.jar;D:\wksp_study\designbook\lib\mx4j.jar;D:\wksp_study\designbook\lib\jcert.jar;D:\wksp_study\designbook\lib\servlet.jar;D:\wksp_study\designbook\lib\xercesImpl.jar;D:\wksp_study\designbook\lib\tomcat-util.jar;D:\wksp_study\designbook\lib\naming-common.jar;D:\wksp_study\designbook\lib\xmlParserAPIs.jar;D:\wksp_study\designbook\lib\commons-daemon.jar;D:\wksp_study\designbook\lib\naming-factory.jar;D:\wksp_study\designbook\lib\commons-logging.jar;D:\wksp_study\designbook\lib\commons-modeler.jar;D:\wksp_study\designbook\lib\commons-digester.jar;D:\wksp_study\designbook\lib\naming-resources.jar;D:\wksp_study\designbook\lib\commons-beanutils.jar;D:\wksp_study\designbook\lib\jakarta-regexp-1.2.jar;D:\wksp_study\designbook\lib\commons-collections.jar com.zyt.tomcat.ex03.startup.Client Socket[addr=localhost/127.0.0.1,port=8080,localport=58988] <html> <head> <title>Modern Servlet</title> </head> <body> <h2>Headers</h2 <br>sec-fetch-mode : navigate <br>sec-fetch-site : none <br>accept-language : zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6 <br>cookie : Webstorm-33fda740=2357c3d1-7421-48dd-b63d-9dfa59664905; Idea-d43876f9=54507cfb-5164-4924-84ad-1ddc6e24306e <br>sec-fetch-user : ?1 <br>accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 <br>sec-ch-ua : " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91" <br>sec-ch-ua-mobile : ?0 <br>host : localhost:8080 <br>upgrade-insecure-requests : 1 <br>connection : keep-alive <br>accept-encoding : gzip, deflate, br <br>user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 <br>sec-fetch-dest : document <br><h2>Method</h2 <br>GET <br><h2>Parameters</h2 <br>password : 1234l <br>userName : zhangyantao HTTP/1.1 200 OK Server: Pyrmont Servlet Container <br><h2>Query String</h2 <br>userName=zhangyantao&password=1234l <br><h2>Request URI</h2 <br>/servlet/ModernServlet </body> </html> Process finished with exit code 0
发现发送了响应头,但是位置不对,进一步发现是他程序写错了
public void write(byte b[],int off,int len) throws IOException{ if (len==0) { return; } if (len<=buffer.length-bufferCount) { System.arraycopy(b,off,buffer,bufferCount,len); bufferCount += len; contentCount += len; return; } flushBuffer(); /** 将buffercount置为0;清空buffer*/ int iterations= len/buffer.length; /**len太大 看一共有几倍 */ int leftoverStart=iterations*buffer.length; int leftoverLen = len - leftoverStart; for (int i = 0; i < iterations; i++) { write(b, off + (i * buffer.length), buffer.length); } if(leftoverLen>0){ write(b, off + leftoverStart, leftoverLen); } }
缓存区大小
public class HttpResponse implements HttpServletResponse { private static final int BUFFER_SIZE=1024; protected byte[] buffer = new byte[BUFFER_SIZE]; }
如果缓存区满了 ,内容还没写完超过了1024个字节,他就发送了,那么就比响应头的发送提前了,之后才会执行sendheaders和发送后一部分内容
如果你设置 private static final int BUFFER_SIZE=4096;
那么你就获取了一个惊喜
D:\dev\JDK8\bin\java.exe -javaagent:D:\dev\ideaIU-2020.3.1.win\lib\idea_rt.jar=57838:D:\dev\ideaIU-2020.3.1.win\bin -Dfile.encoding=UTF-8 -classpath D:\dev\JDK8\jre\lib\charsets.jar;D:\dev\JDK8\jre\lib\deploy.jar;D:\dev\JDK8\jre\lib\ext\access-bridge-64.jar;D:\dev\JDK8\jre\lib\ext\cldrdata.jar;D:\dev\JDK8\jre\lib\ext\dnsns.jar;D:\dev\JDK8\jre\lib\ext\jaccess.jar;D:\dev\JDK8\jre\lib\ext\jfxrt.jar;D:\dev\JDK8\jre\lib\ext\localedata.jar;D:\dev\JDK8\jre\lib\ext\nashorn.jar;D:\dev\JDK8\jre\lib\ext\sunec.jar;D:\dev\JDK8\jre\lib\ext\sunjce_provider.jar;D:\dev\JDK8\jre\lib\ext\sunmscapi.jar;D:\dev\JDK8\jre\lib\ext\sunpkcs11.jar;D:\dev\JDK8\jre\lib\ext\zipfs.jar;D:\dev\JDK8\jre\lib\javaws.jar;D:\dev\JDK8\jre\lib\jce.jar;D:\dev\JDK8\jre\lib\jfr.jar;D:\dev\JDK8\jre\lib\jfxswt.jar;D:\dev\JDK8\jre\lib\jsse.jar;D:\dev\JDK8\jre\lib\management-agent.jar;D:\dev\JDK8\jre\lib\plugin.jar;D:\dev\JDK8\jre\lib\resources.jar;D:\dev\JDK8\jre\lib\rt.jar;D:\wksp_study\designbook\target\classes;D:\repo\junit\junit\4.11\junit-4.11.jar;D:\repo\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\repo\org\openjdk\jol\jol-core\0.10\jol-core-0.10.jar;D:\repo\javax\servlet\servlet-api\2.3\servlet-api-2.3.jar;D:\wksp_study\designbook\lib\jaas.jar;D:\wksp_study\designbook\lib\jnet.jar;D:\wksp_study\designbook\lib\jsse.jar;D:\wksp_study\designbook\lib\mx4j.jar;D:\wksp_study\designbook\lib\jcert.jar;D:\wksp_study\designbook\lib\servlet.jar;D:\wksp_study\designbook\lib\xercesImpl.jar;D:\wksp_study\designbook\lib\tomcat-util.jar;D:\wksp_study\designbook\lib\naming-common.jar;D:\wksp_study\designbook\lib\xmlParserAPIs.jar;D:\wksp_study\designbook\lib\commons-daemon.jar;D:\wksp_study\designbook\lib\naming-factory.jar;D:\wksp_study\designbook\lib\commons-logging.jar;D:\wksp_study\designbook\lib\commons-modeler.jar;D:\wksp_study\designbook\lib\commons-digester.jar;D:\wksp_study\designbook\lib\naming-resources.jar;D:\wksp_study\designbook\lib\commons-beanutils.jar;D:\wksp_study\designbook\lib\jakarta-regexp-1.2.jar;D:\wksp_study\designbook\lib\commons-collections.jar com.zyt.tomcat.ex03.startup.Client Socket[addr=localhost/127.0.0.1,port=8080,localport=57842] HTTP/1.1 200 OK Server: Pyrmont Servlet Container <html> <head> <title>Modern Servlet</title> </head> <body> <h2>Headers</h2 <br>sec-fetch-mode : navigate <br>sec-fetch-site : none <br>accept-language : zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6 <br>cookie : Webstorm-33fda740=2357c3d1-7421-48dd-b63d-9dfa59664905; Idea-d43876f9=54507cfb-5164-4924-84ad-1ddc6e24306e <br>sec-fetch-user : ?1 <br>accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 <br>sec-ch-ua : " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91" <br>sec-ch-ua-mobile : ?0 <br>host : localhost:8080 <br>upgrade-insecure-requests : 1 <br>connection : keep-alive <br>accept-encoding : gzip, deflate, br <br>user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 <br>sec-fetch-dest : document <br><h2>Method</h2 <br>GET <br><h2>Parameters</h2 <br>password : 1234l <br>userName : zhangyantao <br><h2>Query String</h2 <br>userName=zhangyantao&password=1234l <br><h2>Request URI</h2 <br>/servlet/ModernServlet </body> </html> Process finished with exit code 0
浏览器正常显示页面
这书,怎么好呢?解决一个问题太费劲了...一章一个问题,真是费劲呢...,
接下来分析长连接的实现吧,和短链接的区别