From Gossip@Openhome

Java Gossip: 簡單 HTTP 伺服器

這個程式改寫自O'reilly的Java網路程式設計書中的jhttp.java程式,我將啟動的介面作了一些改變,並服務客戶端的執行緒部份獨立出來,使這個程式的服務類別更具一般性,不受限制於伺服器介面的管理方式。

這個程式可以判別客戶端請求的資源型態,並傳回適當的標頭,因為這是一個最簡單的HTTP伺服器,它不處理客戶端的標頭,並只接受基本的GET方法。

基本上這個程式一點都不難,執行緒與Socket的使用都很基本,只要您稍微懂得HTTP協定,都可以看得懂這個程式,有興趣的可以參照HTTP協定繼續完成功能更豐富的HTTP伺服器。

以下為程式碼內容:
  • HttpClient.java
package onlyfun.caterpillar;

import java.io.*;
import java.net.*;
import java.util.Date;

class HttpClient implements Runnable {
private Socket clientSkt;
private File docRoot;
private String defaultFile;;

public HttpClient(Socket s, String root, String indexFile) {
clientSkt = s;
docRoot = new File(root);
defaultFile = indexFile;
}

// 根據要求的檔名判斷傳回型態
public String returnContentType(String fileName) {
if(fileName.endsWith(".html") || fileName.endsWith(".htm"))
return "text/html"; // HTML網頁
else if(fileName.endsWith(".txt") || fileName.endsWith(".java"))
return "text/plain"; // 文字型態
else if(fileName.endsWith(".gif"))
return "image/gif"; // GIF圖檔
else if(fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))
return "image/jpeg"; // JPEG圖檔
else if(fileName.endsWith(".class"))
return "application/octec-stream"; // 二進位應用?{式檔案
else // 其它不可判別的檔案一律視為文字檔型態
return "text/plain";
}

public void run() {
String request; // 要求的方法
String contentType; // 檔案型態
String httpVersion = ""; // HTTP協定版本
File requestedFile; // 要求的檔案

try {
PrintStream printStream = new PrintStream(
clientSkt.getOutputStream());
BufferedReader clientReader = new BufferedReader(
new InputStreamReader(clientSkt.getInputStream()));

String get = clientReader.readLine();

// 分離Request的內容
String[] tokens = get.split("[ \t]");
request = tokens[0];

if(request.equals("GET")) {
String file = tokens[1];

if(file.endsWith("/")) // GET /
file += defaultFile;
contentType = returnContentType(file);

if(tokens.length >= 3) {
httpVersion = tokens[2];
}

// 我們不處理客戶端的標頭
while((get = clientReader.readLine()) != null) {
if(get.trim().equals(""))
break;
}

try {
requestedFile = new File(
docRoot, file.substring(1, file.length()));
FileInputStream fileInputStream =
new FileInputStream(requestedFile);

// 讀入請求的檔案
int fileLength = (int)requestedFile.length();
byte[] requestedData = new byte[fileLength];
fileInputStream.read(requestedData);
fileInputStream.close();

// 寫出標頭至客戶端
if(httpVersion.startsWith("HTTP/")) {
printStream.print("HTTP/1.0 200 OK\r\n");
Date now = new Date();
printStream.print("Date: " + now + "\r\n");
printStream.print("Server: TinyHttpd v0.1\r\n");
printStream.print(
"Content-length: " + fileLength + "\r\n");
printStream.print(
"Content-type: " + contentType + "\r\n\r\n");
}

// 將檔案傳給客戶端
printStream.write(requestedData);
printStream.close();
}
catch(IOException e) { // 找不到檔案
if(httpVersion.startsWith("HTTP/")) {
printStream.print("HTTP/1.0 404 File Not Found\r\n");
Date now = new Date();
printStream.print("Date: " + now + "\r\n");
printStream.print("Server: TinyHttpd v0.1\r\n");
printStream.print("Content-type: text/html\r\n\r\n");
}
}

// 顯示錯誤訊息網頁
printStream.println(
"<HTML><HEAD><TITLE>File Not Found</TITLE></HEAD>"
+ "<BODY><H1>HTTP Error 404: File Not Found" +
"</H1></BODY></HTML>");
printStream.close();
}
else { // 客戶端請求了一個沒有實作的方法,例如POST等
if(httpVersion.startsWith("HTTP/")) {
printStream.print("HTTP/1.0 501 Not Implemented\r\n");
Date now = new Date();
printStream.print("Date: " + now + "\r\n");
printStream.print("Server: TinyHttpd v1.0\r\n");
printStream.print("Content-type: text/html\r\n\r\n");
}
}
printStream.println(
"<HTML><HEAD><TITLE>Not Implemented</TITLE></HEAD>"
+ "<BODY><H1>HTTP Error 501: Not Implemented" +
"</H1></BODY></HTML>");
printStream.close();
}
catch(IOException e) {
e.printStackTrace();
}

try {
clientSkt.close();
}
catch(IOException e) {
e.printStackTrace();
}
}
}

  • SimpleHttpServer.java
package onlyfun.caterpillar;

import java.io.*;
import java.net.*;

public class SimpleHttpServer {
public static void main(String args[]) {
String docRoot;
String indexFile;
int port;

ServerSocket serverSkt;
Socket clientSkt;

try {
BufferedReader buf = new
BufferedReader(new InputStreamReader(System.in));
System.out.println("TinyHttpd v0.1");
System.out.print("伺服器文件根目錄: ");
docRoot = buf.readLine();
System.out.print("索引文件: ");
indexFile = buf.readLine();
System.out.print("伺服器連接埠: ");
port = Integer.parseInt(buf.readLine());

if(port < 0 || port > 65535) {
port = 80;
}
}
catch(Exception e) {
System.err.println("錯誤: " + e.toString());
System.out.print("採用內定選項");
docRoot = "/var/www/html/";
indexFile = "index.html";
port = 80;
System.out.println("文件根目錄: " + docRoot +
"\n索引文件: " + indexFile +
"\n連接埠: " + port);
}

try {
serverSkt = new ServerSocket(port);
System.out.println("傾聽客戶端於 " +
serverSkt.getLocalPort() + " 連接埠....");

while(true) {
clientSkt = serverSkt.accept();
System.out.println("客戶端連線: " + clientSkt.getInetAddress());

// 啟動一個客戶端執行緒
Thread clientThread = new Thread(
new HttpClient(clientSkt, docRoot, indexFile));
clientThread.start();
}
}
catch(IOException e) {
e.printStackTrace();
}
}
}