在上次Java Socket现实简单的HTTP服务我 们实现了简单的HTTP服务,它可以用来模拟HTTP服务,用它可以截获HTTP请求的原始码流,让我们很清楚的了解到我们向服务发的HTTP消息的结 构,对HTTP请求消息有个清晰的认识。这一节我想写了一个客户的程序,就是用来模拟浏览器,用来向服务器发送HTTP请求,最得要的是可以用它来显示服 务器发回来的HTTP响应消息的一般结构。


  1. import java.io.IOException;  
  2. import java.io.InputStream;  
  3. import java.io.OutputStreamWriter;  
  4. import java.net.InetAddress;  
  5. import java.net.Socket;  
  6. import java.net.UnknownHostException;  
  7. import java.util.ArrayList;  
  8.   
  9. /** 
  10.  * 一个简单的HTTP客户端,发送HTTP请求,模拟浏览器 
  11.  * 可打印服务器发送过来的HTTP消息 
  12.  */  
  13. public class SimpleHttpClient {  
  14.     private static String encoding = "GBK";  
  15.   
  16.     public static void main(String[] args) {  
  17.         try {  
  18.             Socket s = new Socket(InetAddress.getLocalHost(), 8080);  
  19.             OutputStreamWriter osw = new OutputStreamWriter(s.getOutputStream());  
  20.             StringBuffer sb = new StringBuffer();  
  21.             sb.append("GET /HttpStream/gb2312.jsp HTTP/1.1\r\n");  
  22.             sb.append("Host: localhost:8088\r\n");  
  23.             sb.append("Connection: Keep-Alive\r\n");  
  24.             //注,这是关键的关键,忘了这里让我搞了半个小时。这里一定要一个回车换行,表示消息头完,不然服务器会等待  
  25.             sb.append("\r\n");  
  26.             osw.write(sb.toString());  
  27.             osw.flush();  
  28.   
  29.             //--输出服务器传回的消息的头信息  
  30.             InputStream is = s.getInputStream();  
  31.             String line = null;  
  32.             int contentLength = 0;//服务器发送回来的消息长度  
  33.             // 读取所有服务器发送过来的请求参数头部信息  
  34.             do {  
  35.                 line = readLine(is, 0);  
  36.                 //如果有Content-Length消息头时取出  
  37.                 if (line.startsWith("Content-Length")) {  
  38.                     contentLength = Integer.parseInt(line.split(":")[1].trim());  
  39.                 }  
  40.                 //打印请求部信息  
  41.                 System.out.print(line);  
  42.                 //如果遇到了一个单独的回车换行,则表示请求头结束  
  43.             } while (!line.equals("\r\n"));  
  44.   
  45.             //--输消息的体  
  46.             System.out.print(readLine(is, contentLength));  
  47.   
  48.             //关闭流  
  49.             is.close();  
  50.   
  51.         } catch (UnknownHostException e) {  
  52.             e.printStackTrace();  
  53.         } catch (IOException e) {  
  54.             e.printStackTrace();  
  55.         }  
  56.     }  
  57.   
  58.     /* 
  59.      * 这里我们自己模拟读取一行,因为如果使用API中的BufferedReader时,它是读取到一个回车换行后 
  60.      * 才返回,否则如果没有读取,则一直阻塞,直接服务器超时自动关闭为止,如果此时还使用BufferedReader 
  61.      * 来读时,因为读到最后一行时,最后一行后不会有回车换行符,所以就会等待。如果使用服务器发送回来的 
  62.      * 消息头里的Content-Length来截取消息体,这样就不会阻塞 
  63.      *  
  64.      * contentLe 参数 如果为0时,表示读头,读时我们还是一行一行的返回;如果不为0,表示读消息体, 
  65.      * 时我们根据消息体的长度来读完消息体后,客户端自动关闭流,这样不用先到服务器超时来关闭。 
  66.      */  
  67.     private static String readLine(InputStream is, int contentLe) throws IOException {  
  68.         ArrayList lineByteList = new ArrayList();  
  69.         byte readByte;  
  70.         int total = 0;  
  71.         if (contentLe != 0) {  
  72.             do {  
  73.                 readByte = (byte) is.read();  
  74.                 lineByteList.add(Byte.valueOf(readByte));  
  75.                 total++;  
  76.             } while (total < contentLe);//消息体读还未读完  
  77.         } else {  
  78.             do {  
  79.                 readByte = (byte) is.read();  
  80.                 lineByteList.add(Byte.valueOf(readByte));  
  81.             } while (readByte != 10);  
  82.         }  
  83.   
  84.         byte[] tmpByteArr = new byte[lineByteList.size()];  
  85.         for (int i = 0; i < lineByteList.size(); i++) {  
  86.             tmpByteArr[i] = ((Byte) lineByteList.get(i)).byteValue();  
  87.         }  
  88.         lineByteList.clear();  
  89.   
  90.         return new String(tmpByteArr, encoding);  
  91.     }  
  92. }  


 

运行时访问一个页面打印如下:

 


HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=61F659691475622CE7AB9C84E7AE7273; Path=/HttpStream
Content-Type: text/html;charset=GB2312
Content-Length: 81
Date: Mon, 09 Nov 2009 13:15:23 GMT

  
<html>  
    <body>  
  你好,这是一个简单的测试
    </body> 
</html>


 

下面来个文件下载的看怎么样?

 

请求的Jsp页面如下:


  1. <%@page import="java.io.InputStream" contentType="text/html; charset=GB2312"%>  
  2. <%@page import="java.io.FileInputStream"%>  
  3.   
  4. <%@page import="java.io.OutputStream"%><html>  
  5.     <body> <br>  
  6.         <%  
  7.             try {  
  8.                 InputStream is = new FileInputStream("e:/tmp/file2.txt");  
  9.                 OutputStream os = response.getOutputStream();  
  10.                 byte[] readContent = new byte[1024];  
  11.                 int readCount = 0;  
  12.                 while (is.available() > 0) {  
  13.                     readCount = is.read(readContent);  
  14.                     os.write(readContent, 0, readCount);  
  15.                 }  
  16.                   
  17.                 is.close();  
  18.                 //注这里一定要关闭,不然的话抛异常,异常请见下面,原因就是response.getWriter()  
  19.                 //与response.getOutputStream()不能同时使用,如果在这里关闭了,前面与后面向  
  20.                 //out对象里写的数据就不会刷新到客户端了,只有向response.getOutputStream()写的  
  21.                 //数据会输出到客户端。  
  22.                 os.close();  
  23.             } catch (Exception e) {  
  24.                 e.printStackTrace();  
  25.             }  
  26.         %>  
  27.     </body>  
  28. </html>  


 

如里上面Jsp下载页面中的 os.close() 注释掉的话会抛如下异常:

exception

org.apache.jasper.JasperException: getOutputStream() has already been called for this response
org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:476)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:383)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:315)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
javax.servlet.http.HttpServlet.service(HttpServlet.java:803)


 

root cause

java.lang.IllegalStateException: getOutputStream() has already been called for this response
org.apache.catalina.connector.Response.getWriter(Response.java:601)
org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:196)
org.apache.jasper.runtime.JspWriterImpl.initOut(JspWriterImpl.java:125)
org.apache.jasper.runtime.JspWriterImpl.flushBuffer(JspWriterImpl.java:118)
org.apache.jasper.runtime.PageContextImpl.release(PageContextImpl.java:185)
org.apache.jasper.runtime.JspFactoryImpl.internalReleasePageContext(JspFactoryImpl.java:116)
org.apache.jasper.runtime.JspFactoryImpl.releasePageContext(JspFactoryImpl.java:76)
org.apache.jsp.gb2312_jsp._jspService(gb2312_jsp.java:78)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:328)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:315)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
javax.servlet.http.HttpServlet.service(HttpServlet.java:803)


 

以下是服务器经过编译生成的servlet类文件:




[java]​view plain​​​​copy​


  1. package org.apache.jsp;  
  2.   
  3. import javax.servlet.*;  
  4. import javax.servlet.http.*;  
  5. import javax.servlet.jsp.*;  
  6. import java.io.InputStream;  
  7. import java.io.FileInputStream;  
  8. import java.io.OutputStream;  
  9.   
  10. public final class gb2312_jsp extends org.apache.jasper.runtime.HttpJspBase  
  11.     implements org.apache.jasper.runtime.JspSourceDependent {  
  12.   
  13.   private static java.util.List _jspx_dependants;  
  14.   
  15.   public Object getDependants() {  
  16.     return _jspx_dependants;  
  17.   }  
  18.   
  19.   public void _jspService(HttpServletRequest request, HttpServletResponse response)  
  20.         throws java.io.IOException, ServletException {  
  21.   
  22.     JspFactory _jspxFactory = null;  
  23.     PageContext pageContext = null;  
  24.     HttpSession session = null;  
  25.     ServletContext application = null;  
  26.     ServletConfig config = null;  
  27.     JspWriter out = null;  
  28.     Object page = this;  
  29.     JspWriter _jspx_out = null;  
  30.     PageContext _jspx_page_context = null;  
  31.   
  32.   
  33.     try {  
  34.       _jspxFactory = JspFactory.getDefaultFactory();  
  35.       response.setContentType("text/html; charset=GB2312");  
  36.       pageContext = _jspxFactory.getPageContext(this, request, response,  
  37.                 null, true, 8192, true);  
  38.       _jspx_page_context = pageContext;  
  39.       application = pageContext.getServletContext();  
  40.       config = pageContext.getServletConfig();  
  41.       session = pageContext.getSession();  
  42.       out = pageContext.getOut();  
  43.       _jspx_out = out;  
  44.   
  45.       out.write("\r\n");  
  46.       out.write("\r\n");  
  47.       out.write("\r\n");  
  48.       out.write("<html>\r\n");  
  49.       out.write("\t<body> <br>\r\n");  
  50.       out.write("\t\t");  
  51.   
  52.             try {  
  53.                 InputStream is = new FileInputStream("e:/tmp/file2.txt");  
  54.                 OutputStream os = response.getOutputStream();  
  55.                 byte[] readContent = new byte[1024];  
  56.                 int readCount = 0;  
  57.                 while (is.available() > 0) {  
  58.                     readCount = is.read(readContent);  
  59.                     os.write(readContent, 0, readCount);  
  60.                 }  
  61.                   
  62.                 is.close();  
  63.                 //注这里一定要关闭,不然的话抛异常,异常请见下面,原因就是response.getWriter()  
  64.                 //与response.getOutputStream()不能同时使用,如果在这里关闭了,前面与后面向  
  65.                 //out对象里写的数据就不会刷新到客户端了,只有向response.getOutputStream()写的  
  66.                 //数据会输出到客户端。  
  67.                 os.close();  
  68.             } catch (Exception e) {  
  69.                 e.printStackTrace();  
  70.             }  
  71.           
  72.       out.write("\r\n");  
  73.       out.write("\t</body>\r\n");  
  74.       out.write("</html>");  
  75.     } catch (Throwable t) {  
  76.       if (!(t instanceof SkipPageException)){  
  77.         out = _jspx_out;  
  78.         if (out != null && out.getBufferSize() != 0)  
  79.           out.clearBuffer();  
  80.         if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);  
  81.       }  
  82.     } finally {  
  83.       if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);  
  84.     }  
  85.   }  
  86. }  


 

最后是服务向客户端输出的码流如下:

 

 

HTTP/1.1 200 OK

Server: Apache-Coyote/1.1

Set-Cookie: JSESSIONID=328097D70C625E8A9279FF9472319A5D; Path=/HttpStream

Content-Type: text/html;charset=GB2312

Content-Length: 60

Date: Mon, 09 Nov 2009 13:19:22 GMT