所謂HTTP協(xié)議,就是TCP協(xié)議+狀態(tài)信息之類的字符。
資源:
http://www.tkk7.com/nokiaguy/category/38517.html
/**
* SimpleHttpServer.java
*/
import java.io.*;
import java.net.*;
import java.util.StringTokenizer;
/**
* 一個(gè)簡(jiǎn)單的用 Java Socket 編寫的 HTTP 服務(wù)器應(yīng)用, 演示了請(qǐng)求和應(yīng)答的協(xié)議通信內(nèi)容以及
* 給客戶端返回 HTML 文本和二進(jìn)制數(shù)據(jù)文件(一個(gè)圖片), 同時(shí)展示了 404, 200 等狀態(tài)碼.
* 首先運(yùn)行這個(gè)程序,然后打開Web瀏覽器,鍵入http://localhost,則這個(gè)程序能夠顯示出瀏覽器發(fā)送了那些信息
* 并且向?yàn)g覽器返回一個(gè)網(wǎng)頁(yè)和一副圖片, 并測(cè)試同瀏覽器對(duì)話.
* 當(dāng)瀏覽器看到 HTML 中帶有圖片地址時(shí), 則會(huì)發(fā)出第二次連接來(lái)請(qǐng)求圖片等資源.
* 這個(gè)例子可以幫您理解 Java 的 HTTP 服務(wù)器軟件是基于 J2SE 的 Socket 等軟件編寫的概念, 并熟悉
* HTTP 協(xié)議.
* 相反的用 Telnet 連接到已有的服務(wù)器則可以幫忙理解瀏覽器的運(yùn)行過(guò)程和服務(wù)器端的返回內(nèi)容.
*
* <pre>
* 當(dāng)用戶在Web瀏覽器地址欄中輸入一個(gè)帶有http://前綴的URL并按下Enter后,或者在Web頁(yè)面中某個(gè)以http://開頭的超鏈接上單擊鼠標(biāo),HTTP事務(wù)處理的第一個(gè)階段--建立連接階段就開始了.HTTP的默認(rèn)端口是80.
* 隨著連接的建立,HTTP就進(jìn)入了客戶向服務(wù)器發(fā)送請(qǐng)求的階段.客戶向服務(wù)器發(fā)送的請(qǐng)求是一個(gè)有特定格式的ASCII消息,其語(yǔ)法規(guī)則為:
* < Method > < URL > < HTTP Version > <\n>
* { <Header>:<Value> <\n>}*
* <\n>
* { Entity Body }
* 請(qǐng)求消息的頂端是請(qǐng)求行,用于指定方法,URL和HTTP協(xié)議的版本,請(qǐng)求行的最后是回車換行.方法有GET,POST,HEAD,PUT,DELETE等.
* 在請(qǐng)求行之后是若干個(gè)報(bào)頭(Header)行.每個(gè)報(bào)頭行都是由一個(gè)報(bào)頭和一個(gè)取值構(gòu)成的二元對(duì),報(bào)頭和取值之間以":"分隔;報(bào)頭行的最后是回車換行.常見的報(bào)頭有Accept(指定MIME媒體類型),Accept_Charset(響應(yīng)消息的編碼方式),Accept_Encoding(響應(yīng)消息的字符集),User_Agent(用戶的瀏覽器信息)等.
* 在請(qǐng)求消息的報(bào)頭行之后是一個(gè)回車換行,表明請(qǐng)求消息的報(bào)頭部分結(jié)束.在這個(gè)\n之后是請(qǐng)求消息的消息實(shí)體(Entity Body).具體的例子參看httpRequest.txt.
* Web服務(wù)器在收到客戶請(qǐng)求并作出處理之后,要向客戶發(fā)送應(yīng)答消息.與請(qǐng)求消息一樣,應(yīng)答消息的語(yǔ)法規(guī)則為:
* < HTTP Version> <Status Code> [<Message>]<\n>
* { <Header>:<Value> <\n> } *
* <\n>
* { Entity Body }
* 應(yīng)答消息的第一行為狀態(tài)行,其中包括了HTTP版本號(hào),狀態(tài)碼和對(duì)狀態(tài)碼進(jìn)行簡(jiǎn)短解釋的消息;狀態(tài)行的最后是回車換行.狀態(tài)碼由3位數(shù)字組成,有5類:
* 參看:HTTP應(yīng)答碼及其意義
*
* 1XX 保留
* 2XX 表示成功
* 3XX 表示URL已經(jīng)被移走
* 4XX 表示客戶錯(cuò)誤
* 5XX 表示服務(wù)器錯(cuò)誤
* 例如:415,表示不支持改媒體類型;503,表示服務(wù)器不能訪問(wèn).最常見的是200,表示成功.常見的報(bào)頭有:Last_Modified(最后修改時(shí)間),Content_Type(消息內(nèi)容的MIME類型),Content_Length(內(nèi)容長(zhǎng)度)等.
* 在報(bào)頭行之后也是一個(gè)回車換行,用以表示應(yīng)答消息的報(bào)頭部分的結(jié)束,以及應(yīng)答消息實(shí)體的開始.
* 下面是一個(gè)應(yīng)答消息的例子:
* HTTP/1.0 200 OK
* Date: Moday,07-Apr-97 21:13:02 GMT
* Server:NCSA/1.1
* MIME_Version:1.0
* Content_Type:text/html
* Last_Modified:Thu Dec 5 09:28:01 1996
* Coentent_Length:3107
*
* <HTML><HEAD><TITLE></HTML>
*
* 在用Java語(yǔ)言實(shí)現(xiàn)HTTP服務(wù)器時(shí),首先啟動(dòng)一個(gè)java.net.ServerSocket在提供服務(wù)的端口上監(jiān)聽連接.向客戶返回文本時(shí),可以用PrintWriter,但是如果返回二進(jìn)制數(shù)據(jù),則必須使用OutputStream.write(byte[])方法,返回的應(yīng)答消息字符串可以使用String.getBytes()方法轉(zhuǎn)換為字節(jié)數(shù)組返回,或者使用PrintStream的print()方法寫入文本,用write(byte[])方法寫入二進(jìn)制數(shù)據(jù).
*
* </pre>
* @author 劉長(zhǎng)炯
* @version 1.0 2007-07-24 Sunday
*/
public class SimpleHttpServer implements Runnable {
/**
*
*/
ServerSocket serverSocket;//服務(wù)器Socket
/**
* 服務(wù)器監(jiān)聽端口, 默認(rèn)為 80.
*/
public static int PORT=80;//標(biāo)準(zhǔn)HTTP端口
/**
* 開始服務(wù)器 Socket 線程.
*/
public SimpleHttpServer() {
try {
serverSocket=new ServerSocket(PORT);
} catch(Exception e) {
System.out.println("無(wú)法啟動(dòng)HTTP服務(wù)器:"+e.getLocalizedMessage());
}
if(serverSocket==null) System.exit(1);//無(wú)法開始服務(wù)器
new Thread(this).start();
System.out.println("HTTP服務(wù)器正在運(yùn)行,端口:"+PORT);
}
/**
* 運(yùn)行服務(wù)器主線程, 監(jiān)聽客戶端請(qǐng)求并返回響應(yīng).
*/
public void run() {
while(true) {
try {
Socket client=null;//客戶Socket
client=serverSocket.accept();//客戶機(jī)(這里是 IE 等瀏覽器)已經(jīng)連接到當(dāng)前服務(wù)器
if(client!=null) {
System.out.println("連接到服務(wù)器的用戶:"+client);
try {
// 第一階段: 打開輸入流
BufferedReader in=new BufferedReader(new InputStreamReader(
client.getInputStream()));
System.out.println("客戶端發(fā)送的請(qǐng)求信息:\n***************");
// 讀取第一行, 請(qǐng)求地址
String line=in.readLine();
System.out.println(line);
String resource=line.substring(line.indexOf('/'),line.lastIndexOf('/')-5);
//獲得請(qǐng)求的資源的地址
resource=URLDecoder.decode(resource, "UTF-8");//反編碼 URL 地址
String method = new StringTokenizer(line).nextElement().toString();// 獲取請(qǐng)求方法, GET 或者 POST
// 讀取所有瀏覽器發(fā)送過(guò)來(lái)的請(qǐng)求參數(shù)頭部信息
while( (line = in.readLine()) != null) {
System.out.println(line);
if(line.equals("")) break;
}
// 顯示 POST 表單提交的內(nèi)容, 這個(gè)內(nèi)容位于請(qǐng)求的主體部分
if("POST".equalsIgnoreCase(method)) {
System.out.println(in.readLine());
}
System.out.println("請(qǐng)求信息結(jié)束\n***************");
System.out.println("用戶請(qǐng)求的資源是:"+resource);
System.out.println("請(qǐng)求的類型是: " + method);
// GIF 圖片就讀取一個(gè)真實(shí)的圖片數(shù)據(jù)并返回給客戶端
if(resource.endsWith(".gif")) {
fileService("images/test.gif", client);
closeSocket(client);
continue;
}
// 請(qǐng)求 JPG 格式就報(bào)錯(cuò) 404
if(resource.endsWith(".jpg")) {
PrintWriter out=new PrintWriter(client.getOutputStream(),true);
out.println("HTTP/1.0 404 Not found");//返回應(yīng)答消息,并結(jié)束應(yīng)答
out.println();// 根據(jù) HTTP 協(xié)議, 空行將結(jié)束頭信息
out.close();
closeSocket(client);
continue;
} else {
// 用 writer 對(duì)客戶端 socket 輸出一段 HTML 代碼
PrintWriter out=new PrintWriter(client.getOutputStream(),true);
out.println("HTTP/1.0 200 OK");//返回應(yīng)答消息,并結(jié)束應(yīng)答
out.println("Content-Type:text/html;charset=GBK");
out.println();// 根據(jù) HTTP 協(xié)議, 空行將結(jié)束頭信息
out.println("<h1> Hello Http Server</h1>");
out.println("你好, 這是一個(gè) Java HTTP 服務(wù)器 demo 應(yīng)用.<br>");
out.println("您請(qǐng)求的路徑是: " + resource + "<br>");
out.println("這是一個(gè)支持虛擬路徑的圖片:<img src='abc.gif'><br>" +
"<a href='abc.gif'>點(diǎn)擊打開abc.gif, 是個(gè)服務(wù)器虛擬路徑的圖片文件.</a>");
out.println("<br>這是個(gè)會(huì)反饋 404 錯(cuò)誤的的圖片:<img src='test.jpg'><br><a href='test.jpg'>點(diǎn)擊打開test.jpg</a><br>");
out.println("<form method=post action='/'>POST 表單 <input name=username value='用戶'> <input name=submit type=submit value=submit></form>");
out.close();
closeSocket(client);
}
} catch(Exception e) {
System.out.println("HTTP服務(wù)器錯(cuò)誤:"+e.getLocalizedMessage());
}
}
//System.out.println(client+"連接到HTTP服務(wù)器");//如果加入這一句,服務(wù)器響應(yīng)速度會(huì)很慢
} catch(Exception e) {
System.out.println("HTTP服務(wù)器錯(cuò)誤:"+e.getLocalizedMessage());
}
}
}
/**
* 關(guān)閉客戶端 socket 并打印一條調(diào)試信息.
* @param socket 客戶端 socket.
*/
void closeSocket(Socket socket) {
try {
socket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
System.out.println(socket + "離開了HTTP服務(wù)器");
}
/**
* 讀取一個(gè)文件的內(nèi)容并返回給瀏覽器端.
* @param fileName 文件名
* @param socket 客戶端 socket.
*/
void fileService(String fileName, Socket socket)
{
try
{
PrintStream out = new PrintStream(socket.getOutputStream(), true);
File fileToSend = new File(fileName);
if(fileToSend.exists() && !fileToSend.isDirectory())
{
out.println("HTTP/1.0 200 OK");//返回應(yīng)答消息,并結(jié)束應(yīng)答
out.println("Content-Type:application/binary");
out.println("Content-Length:" + fileToSend.length());// 返回內(nèi)容字節(jié)數(shù)
out.println();// 根據(jù) HTTP 協(xié)議, 空行將結(jié)束頭信息
FileInputStream fis = new FileInputStream(fileToSend);
byte data[] = new byte[fis.available()];
fis.read(data);
out.write(data);
out.close();
fis.close();
}
}
catch(Exception e)
{
System.out.println("傳送文件時(shí)出錯(cuò):" + e.getLocalizedMessage());
}
}
/**
* 打印用途說(shuō)明.
*/
private static void usage() {
System.out.println("Usage: java HTTPServer <port>\nDefault port is 80.");
}
/**
* 啟動(dòng)簡(jiǎn)易 HTTP 服務(wù)器
* @param args
*/
public static void main(String[] args) {
try {
if(args.length != 1) {
usage();
} else if(args.length == 1) {
PORT = Integer.parseInt(args[0]);
}
} catch (Exception ex) {
System.err.println("Invalid port arguments. It must be a integer that greater than 0");
}
new SimpleHttpServer();
}
}