paulwong

          簡(jiǎn)單的用 Java Socket 編寫的 HTTP 服務(wù)器應(yīng)用,幫助學(xué)習(xí)HTTP協(xié)議

          所謂HTTP協(xié)議,就是TCP協(xié)議+狀態(tài)信息之類的字符。

          資源:http://www.aygfsteel.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è)程序,然后打開(kāi)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://開(kāi)頭的超鏈接上單擊鼠標(biāo),HTTP事務(wù)處理的第一個(gè)階段--建立連接階段就開(kāi)始了.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)頭行的最后是回車換行.常見(jiàn)的報(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).最常見(jiàn)的是200,表示成功.常見(jiàn)的報(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í)體的開(kāi)始.
           *    下面是一個(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)聽(tīng)連接.向客戶返回文本時(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)聽(tīng)端口, 默認(rèn)為 80.
               
          */
              public static int PORT=80;//標(biāo)準(zhǔn)HTTP端口
              
              /**
               * 開(kāi)始服務(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ú)法開(kāi)始服務(wù)器
                  new Thread(this).start();
                  System.out.println("HTTP服務(wù)器正在運(yùn)行,端口:"+PORT);
              }
              
              /**
               * 運(yùn)行服務(wù)器主線程, 監(jiān)聽(tīng)客戶端請(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 {
                                  // 第一階段: 打開(kāi)輸入流
                                  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)擊打開(kāi)abc.gif, 是個(gè)服務(wù)器虛擬路徑的圖片文件.</a>");
                                      out.println("<br>這是個(gè)會(huì)反饋 404 錯(cuò)誤的的圖片:<img src='test.jpg'><br><a href='test.jpg'>點(diǎn)擊打開(kāi)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 + "離開(kāi)了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();
              }
              

          posted on 2013-02-25 22:29 paulwong 閱讀(332) 評(píng)論(0)  編輯  收藏 所屬分類: J2SE

          主站蜘蛛池模板: 隆回县| 闵行区| 青阳县| 乾安县| 巢湖市| 灌云县| 阿尔山市| 疏勒县| 扎鲁特旗| 谢通门县| 健康| 翼城县| 随州市| 绵阳市| 云安县| 青河县| 西城区| 临洮县| 奇台县| 本溪| 墨脱县| 万宁市| 平果县| 全椒县| 托克托县| 阿图什市| 东源县| 常山县| 两当县| 钦州市| 通州区| 西峡县| 冕宁县| 灌南县| 平利县| 方山县| 突泉县| 江都市| 肥西县| 铁力市| 文登市|