Java Socket傳送與接收HTTP訊息簡單實現
在上次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 這是測試檔案的內容: |