在上次Java Socket现实简单的HTTP服务我们实现了简单的HTTP服务,它可以用来模拟HTTP服务,用它可以截获HTTP请求的原始码流,让我们很清楚的了解到我们向服务发的HTTP消息的结构,对HTTP请求消息有个清晰的认识。这一节我想写了一个客户的程序,就是用来模拟浏览器,用来向服务器发送HTTP请求,最得要的是可以用它来显示服务器发回来的HTTP响应消息的一般结构。
下面是代码实现:
import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; /** * 一个简单的HTTP客户端,发送HTTP请求,模拟浏览器 * 可打印服务器发送过来的HTTP消息 */ public class SimpleHttpClient { private static String encoding = "GBK"; public static void main(String[] args) { try { Socket s = new Socket(InetAddress.getLocalHost(), 8080); OutputStreamWriter osw = new OutputStreamWriter(s.getOutputStream()); StringBuffer sb = new StringBuffer(); sb.append("GET /HttpStream/gb2312.jsp HTTP/1.1\r\n"); sb.append("Host: localhost:8088\r\n"); sb.append("Connection: Keep-Alive\r\n"); //注,这是关键的关键,忘了这里让我搞了半个小时。这里一定要一个回车换行,表示消息头完,不然服务器会等待 sb.append("\r\n"); osw.write(sb.toString()); osw.flush(); //--输出服务器传回的消息的头信息 InputStream is = s.getInputStream(); String line = null; int contentLength = 0;//服务器发送回来的消息长度 // 读取所有服务器发送过来的请求参数头部信息 do { line = readLine(is, 0); //如果有Content-Length消息头时取出 if (line.startsWith("Content-Length")) { contentLength = Integer.parseInt(line.split(":")[1].trim()); } //打印请求部信息 System.out.print(line); //如果遇到了一个单独的回车换行,则表示请求头结束 } while (!line.equals("\r\n")); //--输消息的体 System.out.print(readLine(is, contentLength)); //关闭流 is.close(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /* * 这里我们自己模拟读取一行,因为如果使用API中的BufferedReader时,它是读取到一个回车换行后 * 才返回,否则如果没有读取,则一直阻塞,直接服务器超时自动关闭为止,如果此时还使用BufferedReader * 来读时,因为读到最后一行时,最后一行后不会有回车换行符,所以就会等待。如果使用服务器发送回来的 * 消息头里的Content-Length来截取消息体,这样就不会阻塞 * * contentLe 参数 如果为0时,表示读头,读时我们还是一行一行的返回;如果不为0,表示读消息体, * 时我们根据消息体的长度来读完消息体后,客户端自动关闭流,这样不用先到服务器超时来关闭。 */ private static String readLine(InputStream is, int contentLe) throws IOException { ArrayList lineByteList = new ArrayList(); byte readByte; int total = 0; if (contentLe != 0) { do { readByte = (byte) is.read(); lineByteList.add(Byte.valueOf(readByte)); total++; } while (total < contentLe);//消息体读还未读完 } else { do { readByte = (byte) is.read(); lineByteList.add(Byte.valueOf(readByte)); } while (readByte != 10); } byte[] tmpByteArr = new byte[lineByteList.size()]; for (int i = 0; i < lineByteList.size(); i++) { tmpByteArr[i] = ((Byte) lineByteList.get(i)).byteValue(); } lineByteList.clear(); return new String(tmpByteArr, encoding); } }
运行时访问一个页面打印如下:
HTTP/1.1 200 OK |
下面来个文件下载的看怎么样?
请求的Jsp页面如下:
<%@page import="java.io.InputStream" contentType="text/html; charset=GB2312"%> <%@page import="java.io.FileInputStream"%> <%@page import="java.io.OutputStream"%><html> <body> <br> <% try { InputStream is = new FileInputStream("e:/tmp/file2.txt"); OutputStream os = response.getOutputStream(); byte[] readContent = new byte[1024]; int readCount = 0; while (is.available() > 0) { readCount = is.read(readContent); os.write(readContent, 0, readCount); } is.close(); //注这里一定要关闭,不然的话抛异常,异常请见下面,原因就是response.getWriter() //与response.getOutputStream()不能同时使用,如果在这里关闭了,前面与后面向 //out对象里写的数据就不会刷新到客户端了,只有向response.getOutputStream()写的 //数据会输出到客户端。 os.close(); } catch (Exception e) { e.printStackTrace(); } %> </body> </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类文件:
package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; import java.io.InputStream; import java.io.FileInputStream; import java.io.OutputStream; public final class gb2312_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { private static java.util.List _jspx_dependants; public Object getDependants() { return _jspx_dependants; } public void _jspService(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, ServletException { JspFactory _jspxFactory = null; PageContext pageContext = null; HttpSession session = null; ServletContext application = null; ServletConfig config = null; JspWriter out = null; Object page = this; JspWriter _jspx_out = null; PageContext _jspx_page_context = null; try { _jspxFactory = JspFactory.getDefaultFactory(); response.setContentType("text/html; charset=GB2312"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("\r\n"); out.write("\r\n"); out.write("\r\n"); out.write("<html>\r\n"); out.write("\t<body> <br>\r\n"); out.write("\t\t"); try { InputStream is = new FileInputStream("e:/tmp/file2.txt"); OutputStream os = response.getOutputStream(); byte[] readContent = new byte[1024]; int readCount = 0; while (is.available() > 0) { readCount = is.read(readContent); os.write(readContent, 0, readCount); } is.close(); //注这里一定要关闭,不然的话抛异常,异常请见下面,原因就是response.getWriter() //与response.getOutputStream()不能同时使用,如果在这里关闭了,前面与后面向 //out对象里写的数据就不会刷新到客户端了,只有向response.getOutputStream()写的 //数据会输出到客户端。 os.close(); } catch (Exception e) { e.printStackTrace(); } out.write("\r\n"); out.write("\t</body>\r\n"); out.write("</html>"); } catch (Throwable t) { if (!(t instanceof SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) out.clearBuffer(); if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); } } finally { if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context); } } }
最后是服务向客户端输出的码流如下:
HTTP/1.1 200 OK 这是测试文件的内容: |
转载自:http://jiangzhengjun.iteye.com/blog/513217