在上一篇中,我們完成了web server的第一個項目搭建,完成了瀏覽器到我們服務器的通路,算是給兩端搭上線了。但這實在太過於基礎了。本期我們打算添加哪些內容呢?一起來看我們的本期目標:
1、按照http協議,解析瀏覽器發送過來的數據,順帶拿到請求參數;
2、根據瀏覽器請求路徑的不同,返回不同的內容給瀏覽器。
走起,老鐵們!
第一步,我們要從http報文中解析出相關的字段,拿到請求參數。先把上一節的服務器跑起來,然後回顧一下請求的報文格式和內容。我們平時工作中常用的http請求方法也就get和post這倆, 用的比較多。其他像put,delete,option啥的,用的就比較少了。我們就發一個最簡單的get請求吧。爲了方便調試,我們安裝一下postman這個工具,用來模擬瀏覽器發送情趣,哦,不!發送請求給服務端。
服務端收到的內容:
我們要拿到GET——請求方法,/getUserById.do——請求url,HTTP/1.1——http版本(這個版本開始支持ka,也就是下面的connection:keep-alive,不詳細展開了,大體就是一次請求結束後不釋放連接,畢竟重建連接成本很高。)
我們先寫一個解析http的通用方法:
/**
*
* @param httpContent
* @return
*/
public String parseHttpContent(String httpContent) {
/**簡單處理了,不做格式校驗了*/
if ("".equals(httpContent)) {
return null;
}
/**獲取報文第一行*/
String firstRow = httpContent.split(Constant.CRLF)[0];
/**解析請求方法*/
String methodName = firstRow.split(Constant.BLANK)[0].trim().toLowerCase();
System.out.println("請求方法:"+methodName);
/**解析請求url*/
String requestUrl = firstRow.split(Constant.BLANK)[1].trim();
System.out.println("請求url:"+requestUrl);
/**解析請求協議版本*/
String protocolVersion = firstRow.split(Constant.BLANK)[2].trim();
System.out.println("請求協議版本:"+protocolVersion);
parseRequestParam(httpContent);
return null;
}
這樣我們就拿到了http的請求頭,請求url,請求協議的版本 這三個比較關鍵的字段
接下來就是拿到請求參數了,我們先看看常見的請求格式有哪幾類吧。
GET http://localhost:8008/getUsers.do
GET http://localhost:8008/getUserByid.do?userId=123
GET http://localhost:8008/getUserByid.do?userId=123&areaId=345
POST http://localhost:8008/getUserByid.do
POST http://localhost:8008/addUser.do ,body參數 {"username":"lyn"}
POST http://localhost:8008/addUser.do ,body參數 {"username":"lyn","password":"lyn123"}
POST http://localhost:8008/addUser.do?username=lyn
POST http://localhost:8008/addUser.do?username=lyn&password=lyn123
POST http://localhost:8008/addUser.do?areaId=345 ,body參數 {"username":"lyn"}
POST http://localhost:8008/addUser.do?areaId=345 ,body參數 {"username":"lyn","password":"lyn123"}
POST http://localhost:8008/addUser.do?areaId=345&officeId=789 ,body參數 {"username":"lyn"}
POST http://localhost:8008/addUser.do?areaId=345&officeId=789 ,body參數 {"username":"lyn","password":"lyn123"}
乍一看可能比較多,其實我們真正關注的就這幾個點:get還是post,請求中帶不帶“?”,參數有幾個,請求體中有沒有參數。上面就是這幾種情況的組合。
具體到代碼上:
/**
* 解析請求參數,暫只考慮get和post方式,參數封裝爲map,k-v格式,考慮一對多
* @param httpContent
*/
public void parseRequestParam(String httpContent) {
/**url後帶問號的形式,/addUser.do?username=xx&pwd=xxx */
String firstRow = httpContent.split(Constant.CRLF)[0];
String methodName = firstRow.split(Constant.BLANK)[0].trim().toLowerCase();
String urlStr = firstRow.split(Constant.BLANK)[1];
/**根據請求體中是否包含參數來分類*/
String[] httpBodyArray = httpContent.split("\n\r\n");
if ("".equals(firstRow) || (!"post".equals(methodName) && !urlStr.contains("?")) ) return;
if (urlStr.contains("?")) {
/**格式爲 key1=value1&key2=value2... */
String paramStr = urlStr.split("\\?")[1];
//只有一個參數
if (!urlStr.contains("&")) {
String paramKey = paramStr.split("=")[0];
String paramValue = paramStr.split("=")[1];
System.out.println("參數:"+paramKey+" , 值:"+paramValue);
}else { //多個參數
String[] paramArray = paramStr.split("&");
for (String param:paramArray) {
System.out.println("參數:"+param.split("=")[0]+" , 值:"+param.split("=")[1]);
}
}
}
/**如果請求體有參數,繼續解析*/
if (httpBodyArray.length > 1) {
String bodyParamStr = httpBodyArray[1];
if (bodyParamStr.contains("&")) {
String[] bodyParamArray = bodyParamStr.split("&");
for (String bodyParam:bodyParamArray) {
System.out.println("Body參數: "+bodyParam.split("=")[0]+", Body值:"+bodyParam.split("=")[1]);
}
}else {
System.out.println("Body參數: "+bodyParamStr.split("=")[0]+", Body值:"+bodyParamStr.split("=")[1]);
}
}
}
我們來測試一下:
看下服務端:
這樣,我們就順利地從請求報文中拿到了常用的幾個關鍵屬性,比如請求方法,請求url,而且還拿到了請求參數。後面我們能做得事情就很多了。根據不同url執行不同業務方法返回不同內容。別慌,一步步來。
本期代碼路徑:https://github.com/justein/lyn-server/tree/master/server-parse,後面我們將在這個基礎上實現參數的存儲和靈活讀取。我們下期再見!