文章目錄
一、前言
利用Java的com.sun.net
包下的httpserver類,實現的簡易http服務器。全部代碼已上傳至github,鏈接在文末。
主要功能爲:
1、只處理GET方式和POST方式的請求,其他的返回 501 Not Implemented
2、GET:1)如果請求路徑爲一個存在的html文件,返回200 OK
和該html文件的全部內容。(對子目錄下的文件同樣也要能返回);2)如果請求路徑爲一個文件夾,且該文件夾內有index.html文件,返回200 OK
和index.html文件的全部內容;3)如果請求的文件不存在,或者請求的路徑下無index.html文件,返回404 Not Found
。
3、POST:1)構造一個HTTP請求,包含兩個鍵值對:Name:姓名 和ID:學號,提交給/Post_show
。服務器收到請求後返回200 OK
且輸出以POST方式發送的Name和ID;2)其他情況如請求路徑不爲Post_show
或鍵的名稱不爲Name和ID,返回404 Not Found
4、使用多線程處理,具體爲線程池方式實現
5、支持長選項參數:1)–ip 設置服務器端的IP地址;2)–port 設置服務器端監聽的端口號;3)–number-thread 線程數目。
二、功能展示
這裏只貼windows環境下測試的結果了。
1、Idea上運行
Run->Edit Configurations 設置參數
也可以不設置,默認值就是上圖中的數
點擊運行
這個“當前路徑”設置的是:
private static final String WEB_ROOT = System.getProperty("user.dir")
+ File.separator + "webroot";
後文有解釋。在webroot文件夾內存放了一些子文件夾和html文件等。
1.1、瀏覽器輸入請求
瀏覽器中輸入
127.0.0.1:8888/sub/02.jpg
便可請求到圖片D:\Study\Java\httpserver\webroot\sub\02.jpg
1.2、curl命令請求
curl現在是win10自帶的了吧,不用安裝直接能用。不過需要注意的是windows和linux下的curl使用存在區別。
1.3、Postman請求
Postman是個很棒的工具!
2、命令行上java -jar運行
參考IDEA打包jar包詳盡流程打成jar包,放在E盤
此時將webroot和httpserver.jar放在同一目錄下
請求方式和1中介紹過的類似
三、具體實現
1、支持長選項參數(long options)
主要用到的是org.apache.commons.cli
包,需要下載。
private static void HandleOption(String[] args) {
CommandLineParser parser = new BasicParser();
Options options = new Options();
options.addOption("i", "ip", true, "Specify the server IP address");
options.addOption("p", "port", true, "Selects which port the HTTP server listens on for incoming connections");
options.addOption("n", "number-thread", true, "The number of threads");
try {
CommandLine commandLine = parser.parse(options, args);
if (commandLine.hasOption('i')) {
IP = commandLine.getOptionValue('i');
}
if (commandLine.hasOption('p')) {
PORT = Integer.parseInt(commandLine.getOptionValue('p'));
}
if (commandLine.hasOption('n')) {
ThreadNum = Integer.parseInt(commandLine.getOptionValue('n'));
}
} catch (ParseException e) {
e.printStackTrace();
}
}
如參數給出--ip 127.0.0.1 --port 8888 --number-thread 2
,則將全局變量IP設爲"127.0.0.1",PORT設爲8888,ThreadNum設爲2。這些值也是默認值。
2、構建httpserver
public static void main(String[] args) {
//System.out.println(WEB_ROOT);
HandleOption(args);
HttpServerProvider provider = HttpServerProvider.provider();
HttpServer httpserver = null;
try {
httpserver = provider.createHttpServer(new InetSocketAddress(IP, PORT), ThreadNum);//監聽端口PORT,能同時接受ThreadNum個請求
httpserver.setExecutor(Executors.newFixedThreadPool(ThreadNum));
} catch (Exception e) {
e.printStackTrace();
}
httpserver.createContext("/", new MyHandler());
httpserver.createContext("/Post_show", new PostShowHandler());
httpserver.start();
System.out.println("當前路徑:"+WEB_ROOT);
System.out.println("Http Server is running...");
}
接收完命令行參數後,監聽地址IP的端口PORT,設能同時接收ThreadNum個請求。然後設置服務器的線程池對象,這裏用的是定長線程池newFixedThreadPool,最大線程數設爲ThreadNum,也可以使用其他的線程池對象。
然後創建服務器監聽的上下文,這裏創建了兩個。其實創建一個就好了,這裏爲了方便就創建了兩個。第二個表示只要匹配到/Post_show,就會調用PostShowHandler()處理對應的請求。第一個本可以表示只要匹配到/,就會調用MyHandle()處理對應的請求,因爲又設置了/Post_show的上下文監聽器,所以若http請求爲127.0.0.1:8888/Post_show
的話,只會調用PostShowHandler()而不會調用MyHandler()了。
然後開啓服務。
WEB_ROOT爲:
private static final String WEB_ROOT = System.getProperty("user.dir") + ...
其中,System.getProperty(“user.dir”)爲獲得程序當前路徑。
3、處理Post請求
3.1、解析URI參數
static class PostShowHandler implements HttpHandler {
@Override
public void handle(HttpExchange httpExchange) throws IOException {
String query = httpExchange.getRequestURI().getQuery();
String[] keyValues = query.split("&");
if (keyValues.length != 2) {
System.out.println(keyValues.length);
try {
NotFoundResponse(httpExchange);
} catch (Exception e) {
e.printStackTrace();
}
return;
}
String[] NamePara = keyValues[0].split("=");
String[] IDPara = keyValues[1].split("=");
if (!NamePara[0].equals("Name") || !IDPara[0].equals("ID") || NamePara.length != 2 || IDPara.length != 2) {
try {
NotFoundResponse(httpExchange);
} catch (Exception e) {
e.printStackTrace();
}
return;
}
try {
HandlePostRequest(httpExchange, NamePara[1], IDPara[1]);
} catch (Exception e) {
e.printStackTrace();
}
}
}
要處理的POST請求形如:127.0.0.1:8888/Post_show?Name=jsglyzcq&ID=40889820
。
首先利用getQuery()函數得到Query部分Name=jsglyzcq&ID=40889820
,然後利用split函數分割字符串,以&
隔開得到鍵值對Name=jsglyzcq
和ID=40889820
,再利用split函數以=
分割得到具體的值Name和ID。
如果參數不是兩個,或者參數的名稱不是Name
和ID
等,都返回404,調用函數NotFoundResponse
,該函數實現見3.2。
3.2、不滿足條件
private static void NotFoundResponse(HttpExchange httpExchange) throws Exception {
Headers responseHeaders = httpExchange.getResponseHeaders();
responseHeaders.set("Content-Type", "text/html;charset=utf-8");
OutputStream responseBody = httpExchange.getResponseBody();
OutputStreamWriter writer = new OutputStreamWriter(responseBody, "UTF-8");
String response = "<html><title>404 Not Found</title><body bgcolor=ffffff>" + CRLF +
"Not Found" + CRLF +
"<hr><em>HTTP Web Server</em>" + CRLF +
"</body></html>";
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, response.getBytes("UTF-8").length);
writer.write(response);
writer.close();
responseBody.close();
}
比較簡單,看函數名稱就知道在做啥。測試如下:
預覽:
3.3、滿足條件
private static void HandlePostRequest(HttpExchange httpExchange, String Name, String ID) throws Exception {
Headers responseHeaders = httpExchange.getResponseHeaders();
responseHeaders.set("Content-Type", "text/html; charset=utf-8");
OutputStream responseBody = httpExchange.getResponseBody();
OutputStreamWriter writer = new OutputStreamWriter(responseBody, "UTF-8");
String response = "<html><title>" + httpExchange.getRequestMethod() + "</title><body bgcolor=ffffff>" + CRLF +
"Your Name: " + Name + CRLF +
"ID: " + ID + CRLF +
"<hr><em>HTTP Web Server</em>" + CRLF +
"</body></html>";
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.getBytes("UTF-8").length);
writer.write(response);
writer.close();
responseBody.close();
}
和3.2差不多,就是返回內容有所更改
預覽:
4、處理Get請求
4.1、判斷何種方式請求
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange httpExchange) throws IOException {
String requestMethord = httpExchange.getRequestMethod();
try {
if (is404NotFound(httpExchange)) {
System.out.println(httpExchange.getRequestURI() + " does not exist!");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
if (requestMethord.equalsIgnoreCase("GET")) {
HandleGetRequest(httpExchange);
} else if (requestMethord.equalsIgnoreCase("POST")) {
//
} else {
Headers responseHeaders = httpExchange.getResponseHeaders();
responseHeaders.set("Content-Type", "text/html;charset=utf-8");
OutputStream responseBody = httpExchange.getResponseBody();
OutputStreamWriter writer = new OutputStreamWriter(responseBody, "UTF-8");
String response = "<html><title>501 Not Implemented</title><body bgcolor=ffffff>" + CRLF +
"Not Implemented" + CRLF +
"<p>Does not implement this methord: " + requestMethord + CRLF +
"<hr><em>HTTP Web Server</em>" + CRLF +
"</body></html>";
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_IMPLEMENTED, response.getBytes("UTF-8").length);
writer.write(response);
writer.close();
responseBody.close();
}
}
}
利用getRequestMethod()函數得到是何種方式的請求。判斷訪問路徑是否存在見4.2。
如果訪問路徑存在,並且訪問方式爲GET
,則調用函數HandleGetRequest,見4.3;若訪問方式爲POST
,也可以調用相應的函數,但我在之前已經實現過了,這裏就不填了;若訪問方式爲除了GET
和POST
以外的方式,則返回501 Not IMPLEMENTED:
預覽:
4.2、判斷訪問路徑是否存在
private static boolean is404NotFound(HttpExchange httpExchange) throws Exception {
String requestURI = String.valueOf(httpExchange.getRequestURI());
File file = new File(WEB_ROOT, requestURI);
if (file.exists()) return false;
else {
System.out.println(file.getPath() + "does not exist!" + CRLF);
NotFoundResponse(httpExchange);
return true;
}
}
前文提到,WEB_ROOT爲獲取的當前程序正在運行的路徑,加上requestURI得到請求的文件,利用exists()函數判斷是否存在。若存在則返回false,表示不是404;若不存在則調用3.2中提過的函數NotFoundResponse()。
4.3、滿足條件
private static void HandleGetRequest(HttpExchange httpExchange) {
String requestURI = String.valueOf(httpExchange.getRequestURI());
File file = new File(WEB_ROOT, requestURI);
File filetoSend = null;
boolean hasIndexHtml = false;
if (file.isDirectory()) {//若爲文件夾
String[] files = file.list();
for (String filename : files) {
if (filename.equals("index.html")) {
filetoSend = new File(file.getPath() + File.separator + filename);
hasIndexHtml = true;
break;
}
}
if (!hasIndexHtml) {
NotFoundResponse(httpExchange);//這裏要處理異常,爲了減少篇幅刪去了
} else {//這裏要處理異常,爲了減少篇幅刪去了
FileSendResponse(httpExchange, filetoSend, "text/html; charset=utf-8");
}
} else {
if (file.getPath().endsWith(".jpg")) {
//這裏要處理異常,爲了減少篇幅刪去了
FileSendResponse(httpExchange, file, "image/jpeg");
}else if(file.getPath().endsWith(".html")){
//這裏要處理異常,爲了減少篇幅刪去了
FileSendResponse(httpExchange, file, "text/html; charset=utf-8");
}
}
}
調用這個函數時說明訪問路徑是存在的。然後進一步判斷訪問的是一個文件夾還是文件,如果訪問的是文件夾,那麼查找該文件夾內是否有文件名爲index.html
,若有則調用FileSendResponse()函數發送文件,見4.4,否則報404;如果訪問的是一個文件,就根據文件後綴名,設置相應的媒體類型mime。
4.4、發送文件
private static void FileSendResponse(HttpExchange httpExchange, File file, String mime) throws Exception {
Headers responseHeaders = httpExchange.getResponseHeaders();
responseHeaders.set("Content-Type", mime);
OutputStream responseBody = httpExchange.getResponseBody();
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, file.length());
if (file.exists()) {
//System.out.println(file.length());
System.out.println(file.getAbsolutePath() + " Exist");
byte[] buffer = new byte[10240];
FileInputStream fis = new FileInputStream(file);
int readLength;
while ((readLength = fis.read(buffer, 0, 10240)) > 0) {
responseBody.write(buffer, 0, readLength);
}
}
responseBody.close();
}
將文件以字節流的形式發送出去。這裏的緩衝區大小爲10240。
例:
四、後記
這是筆者對大三下選修的雲計算實驗二基礎版本的實現,因爲時間關係,直接選用了httpserver來實現。畢竟是兩天內完成的,若文中有詞不達意、錯漏之處,敬請指正🐟
代碼鏈接:待上傳
五、主要參考資料
1、java解析命令行參數——Iteye(org.apache.commons.cli包的使用)
2、Java服務端使用HttpServer處理Http請求——CSDN
3、Java項目——簡單的WebServer(一)——CSDN
4、使用Java實現一個最簡單的Web Server——CSDN